diff --git a/ChillMainBundle.php b/ChillMainBundle.php
index 2f53f8e69..579908ea4 100644
--- a/ChillMainBundle.php
+++ b/ChillMainBundle.php
@@ -9,6 +9,8 @@ use Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass;
use Chill\MainBundle\DependencyInjection\TimelineCompilerClass;
use Chill\MainBundle\DependencyInjection\RoleProvidersCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
+use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
+
class ChillMainBundle extends Bundle
{
@@ -20,5 +22,6 @@ class ChillMainBundle extends Bundle
$container->addCompilerPass(new TimelineCompilerClass());
$container->addCompilerPass(new RoleProvidersCompilerPass());
$container->addCompilerPass(new ExportsCompilerPass());
+ $container->addCompilerPass(new WidgetsCompilerPass());
}
}
diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php
index ef3fed0ae..e3a0f0d25 100644
--- a/DependencyInjection/ChillMainExtension.php
+++ b/DependencyInjection/ChillMainExtension.php
@@ -1,5 +1,22 @@
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
namespace Chill\MainBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -7,20 +24,43 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
+use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
+use Chill\MainBundle\DependencyInjection\Configuration;
/**
- * This is the class that loads and manages your bundle configuration
- *
- * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
+ * This class load config for chillMainExtension.
*/
-class ChillMainExtension extends Extension implements PrependExtensionInterface
+class ChillMainExtension extends Extension implements PrependExtensionInterface,
+ Widget\HasWidgetFactoriesExtensionInterface
{
+ /**
+ * widget factory
+ *
+ * @var WidgetFactoryInterface[]
+ */
+ protected $widgetFactories = array();
+
+ public function addWidgetFactory(WidgetFactoryInterface $factory)
+ {
+ $this->widgetFactories[] = $factory;
+ }
+
+ /**
+ *
+ * @return WidgetFactoryInterface[]
+ */
+ public function getWidgetFactories()
+ {
+ return $this->widgetFactories;
+ }
+
/**
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
- $configuration = new Configuration();
+ // configuration for main bundle
+ $configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_main.installation_name',
@@ -34,14 +74,25 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface
$container->setParameter('chill_main.pagination.item_per_page',
$config['pagination']['item_per_page']);
+
+ // add the key 'widget' without the key 'enable'
+ $container->setParameter('chill_main.widgets',
+ array('homepage' => $config['widgets']['homepage']));
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$loader->load('services/logger.yml');
$loader->load('services/repositories.yml');
$loader->load('services/pagination.yml');
+
}
-
+
+ public function getConfiguration(array $config, ContainerBuilder $container)
+ {
+ return new Configuration($this->widgetFactories, $container);
+ }
+
+
public function prepend(ContainerBuilder $container)
{
$bundles = $container->getParameter('kernel.bundles');
@@ -57,7 +108,8 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface
//add installation_name and date_format to globals
$chillMainConfig = $container->getExtensionConfig($this->getAlias());
- $config = $this->processConfiguration(new Configuration(), $chillMainConfig);
+ $config = $this->processConfiguration($this
+ ->getConfiguration($chillMainConfig, $container), $chillMainConfig);
$twigConfig = array(
'globals' => array(
'installation' => array(
diff --git a/DependencyInjection/CompilerPass/WidgetsCompilerPass.php b/DependencyInjection/CompilerPass/WidgetsCompilerPass.php
new file mode 100644
index 000000000..b28b8fdbb
--- /dev/null
+++ b/DependencyInjection/CompilerPass/WidgetsCompilerPass.php
@@ -0,0 +1,34 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\DependencyInjection\CompilerPass;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass;
+
+/**
+ * Compile the service definition to register widgets.
+ *
+ */
+class WidgetsCompilerPass extends AbstractWidgetsCompilerPass {
+
+ public function process(ContainerBuilder $container)
+ {
+ $this->doProcess($container, 'chill_main', 'chill_main.widgets');
+ }
+}
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 8bb6c9805..21c0d6a84 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -4,14 +4,34 @@ namespace Chill\MainBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
/**
- * This is the class that validates and merges configuration from your app/config files
- *
- * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
+ * Configure the main bundle
*/
class Configuration implements ConfigurationInterface
{
+
+ use AddWidgetConfigurationTrait;
+
+ /**
+ *
+ * @var ContainerBuilder
+ */
+ private $containerBuilder;
+
+
+ public function __construct(array $widgetFactories = array(),
+ ContainerBuilder $containerBuilder)
+ {
+ $this->setWidgetFactories($widgetFactories);
+ $this->containerBuilder = $containerBuilder;
+ }
+
/**
* {@inheritDoc}
*/
@@ -25,18 +45,18 @@ class Configuration implements ConfigurationInterface
->scalarNode('installation_name')
->cannotBeEmpty()
->defaultValue('Chill')
- ->end()
+ ->end() // end of scalar 'installation_name'
->arrayNode('available_languages')
->defaultValue(array('fr'))
->prototype('scalar')->end()
- ->end()
+ ->end() // end of array 'available_languages'
->arrayNode('routing')
->children()
->arrayNode('resources')
->prototype('scalar')->end()
- ->end()
- ->end()
- ->end()
+ ->end() // end of array 'resources'
+ ->end() // end of children
+ ->end() // end of array node 'routing'
->arrayNode('pagination')
->canBeDisabled()
->children()
@@ -44,11 +64,20 @@ class Configuration implements ConfigurationInterface
->info('The number of item to show in the page result, by default')
->min(1)
->defaultValue(50)
- ->end()
- ->end()
- ->end()
- ->end();
+ ->end() // end of integer 'item_per_page'
+ ->end() // end of children
+ ->end() // end of pagination
+ ->arrayNode('widgets')
+ ->canBeDisabled()
+ ->children()
+ ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder))
+ ->end() // end of widgets/children
+ ->end() // end of widgets
+ ->end() // end of root/children
+ ->end() // end of root
+ ;
+
return $treeBuilder;
}
}
diff --git a/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php b/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php
new file mode 100644
index 000000000..f0a1205e9
--- /dev/null
+++ b/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php
@@ -0,0 +1,388 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\DependencyInjection\Widget;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\Definition;
+use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
+use Chill\MainBundle\DependencyInjection\Widget\HasWidgetFactoriesExtensionInterface;
+
+/**
+ * Compile the configurations and inject required service into container.
+ *
+ * The widgets are services tagged with :
+ *
+ * ```
+ * { name: chill_widget, alias: my_alias, place: my_place }
+ * ```
+ *
+ * Or, if the tag does not exist or if you need to add some config to your
+ * service depending on the config, you should use a `WidgetFactory` (see
+ * `WidgetFactoryInterface`.
+ *
+ * To reuse this compiler pass, simple execute the doProcess metho in your
+ * compiler. Example :
+ *
+ * ```
+ * namespace Chill\MainBundle\DependencyInjection\CompilerPass;
+ *
+ * use Symfony\Component\DependencyInjection\ContainerBuilder;
+ * use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass;
+ * class WidgetsCompilerPass extends AbstractWidgetsCompilerPass {
+ *
+ * public function process(ContainerBuilder $container)
+ * {
+ * $this->doProcess($container, 'chill_main', 'chill_main.widgets');
+ * }
+ * }
+ * ```
+ *
+ *
+ */
+abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
+{
+ private $widgetServices = array();
+
+ /**
+ *
+ * @var WidgetFactoryInterface[]
+ */
+ private $widgetFactories;
+
+ /**
+ * The service which will manage the widgets
+ *
+ * @var string
+ */
+ const WIDGET_MANAGER = 'chill.main.twig.widget';
+
+ /**
+ * the method wich register the widget into give service.
+ */
+ const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget';
+
+ /**
+ * the value of the `name` key in service definitions's tag
+ *
+ * @var string
+ */
+ const WIDGET_SERVICE_TAG_NAME = 'chill_widget';
+
+ /**
+ * the key used to collect the alias in the service definition's tag.
+ * the alias must be
+ * injected into the configuration under 'alias' key.
+ *
+ * @var string
+ */
+ const WIDGET_SERVICE_TAG_ALIAS = 'alias';
+
+ /**
+ * the key used to collect the authorized place in the service definition's tag
+ *
+ * @var string
+ */
+ const WIDGET_SERVICE_TAG_PLACES = 'place';
+
+ /**
+ * the key to use to order widget for a given place
+ */
+ const WIDGET_CONFIG_ORDER = 'order';
+
+ /**
+ * the key to use to identify widget for a given place
+ */
+ const WIDGET_CONFIG_ALIAS = 'widget_alias';
+
+
+ /**
+ * process the configuration and the container to add the widget available
+ *
+ * @param ContainerBuilder $container
+ * @param string $extension the extension of your bundle
+ * @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration
+ * @throws \LogicException
+ * @throws \UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface
+ * @throws \InvalidConfigurationException if there are errors in the config
+ */
+ public function doProcess(ContainerBuilder $container, $extension,
+ $containerWidgetConfigParameterName)
+ {
+ if (!$container->hasDefinition(self::WIDGET_MANAGER)) {
+ throw new \LogicException("the service ".self::WIDGET_MANAGER." should".
+ " be present. It is required by ".self::class);
+ }
+
+ $managerDefinition = $container->getDefinition(self::WIDGET_MANAGER);
+
+ // collect the widget factories
+ /* @var $extensionClass HasWidgetFactoriesExtensionInterface */
+ $extensionClass = $container->getExtension($extension);
+ // throw an error if extension does not implement HasWidgetFactoriesExtensionInterface
+ if (!$extensionClass instanceof HasWidgetFactoriesExtensionInterface) {
+ throw new \UnexpectedValueException(sprintf("The extension for %s "
+ . "do not implements %s. You should implement %s on %s",
+ $extension,
+ HasWidgetFactoriesExtensionInterface::class,
+ HasWidgetFactoriesExtensionInterface::class,
+ get_class($extensionClass)));
+ }
+
+
+ $this->widgetFactories = $extensionClass->getWidgetFactories();
+
+ // collect the availabled tagged services
+ $this->collectTaggedServices($container);
+
+ // collect the widgets and their config :
+ $widgetParameters = $container->getParameter($containerWidgetConfigParameterName);
+
+ // and add them to the delegated_block
+ foreach($widgetParameters as $place => $widgets) {
+
+ foreach ($widgets as $param) {
+ $alias = $param[self::WIDGET_CONFIG_ALIAS];
+ // check that the service exists
+ if (!array_key_exists($alias, $this->widgetServices)) {
+ throw new InvalidConfigurationException(sprintf("The alias %s".
+ " is not defined.", $alias));
+ }
+
+ // check that the widget is allowed at this place
+ if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) {
+ throw new \InvalidConfigurationException(sprintf(
+ "The widget with alias %s is not allowed at place %s",
+ $alias,
+ $place
+ ));
+ }
+
+ // get the order, eventually corrected
+ $order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]);
+
+ // register the widget with config to the service, using the method
+ // `addWidget`
+ if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) {
+ /* @var $factory WidgetFactoryInterface */
+ $factory = $this->widgetServices[$alias];
+ // get the config (under the key which equals to widget_alias
+ $config = isset($param[$factory->getWidgetAlias()]) ?
+ $param[$factory->getWidgetAlias()] : array();
+ // register the service into the container
+ $serviceId =$this->registerServiceIntoContainer($container,
+ $factory, $place, $order, $config);
+
+ $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER,
+ array(
+ $place,
+ $order,
+ new Reference($serviceId),
+ $config
+ ));
+ } else {
+ $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER,
+ array(
+ $place,
+ $order,
+ new Reference($this->widgetServices[$alias]),
+ array() // the config is alway an empty array
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * register the service into container.
+ *
+ * @param ContainerBuilder $container
+ * @param WidgetFactoryInterface $factory
+ * @param string $place
+ * @param float $order
+ * @param array $config
+ * @return string the id of the new service
+ */
+ protected function registerServiceIntoContainer(
+ ContainerBuilder $container,
+ WidgetFactoryInterface $factory,
+ $place,
+ $order,
+ array $config
+ ) {
+ $serviceId = $factory->getServiceId($container, $place, $order, $config);
+ $definition = $factory->createDefinition($container, $place,
+ $order, $config);
+ $container->setDefinition($serviceId, $definition);
+
+ return $serviceId;
+ }
+
+ /**
+ * cache of ordering by place.
+ *
+ * @internal used by function cacheAndGetOrdering
+ * @var array
+ */
+ private $cacheOrdering = array();
+
+ /**
+ * check if the ordering has already be used for the given $place and,
+ * if yes, correct the ordering by incrementation of 1 until the ordering
+ * has not be used.
+ *
+ * recursive method.
+ *
+ * @param string $place
+ * @param float $ordering
+ * @return float
+ */
+ private function cacheAndGetOrdering($place, $ordering) {
+ // create a key in the cache array if not exists
+ if (!array_key_exists($place, $this->cacheOrdering)) {
+ $this->cacheOrdering[$place] = array();
+ }
+
+ // check if the order exists
+ if (array_search($ordering, $this->cacheOrdering[$place])) {
+ // if the order exists, increment of 1 and try again
+ return $this->cacheAndGetOrdering($place, $ordering + 1);
+ } else {
+ // cache the ordering
+ $this->cacheOrdering[$place] = $ordering;
+
+ return $ordering;
+ }
+ }
+
+ /**
+ * get the places where the service is allowed
+ *
+ * @param Definition $definition
+ * @return unknown
+ */
+ private function isPlaceAllowedForWidget($place, $widgetAlias, ContainerBuilder $container)
+ {
+ if ($this->widgetServices[$widgetAlias] instanceof WidgetFactoryInterface) {
+ if (in_array($place, $this->widgetServices[$widgetAlias]
+ ->getAllowedPlaces())) {
+ return true;
+ }
+
+ } else {
+ $definition = $container->findDefinition($this->widgetServices[$widgetAlias]);
+
+ foreach($definition->getTag(self::WIDGET_SERVICE_TAG_NAME) as $attrs) {
+ $placeValue = $attrs[self::WIDGET_SERVICE_TAG_PLACES];
+
+ if ($placeValue === $place) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and
+ * add also the widget defined by factories
+ *
+ * This method also check that the service is correctly tagged with `alias` and
+ * `places`, or the factory give a correct alias and more than one place.
+ *
+ * @param ContainerBuilder $container
+ * @throws InvalidConfigurationException
+ * @throws InvalidArgumentException
+ */
+ protected function collectTaggedServices(ContainerBuilder $container)
+ {
+ // first, check the service tagged in service definition
+ foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) {
+ foreach ($attrs as $attr) {
+
+ // check the alias is set
+ if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) {
+ throw new InvalidConfigurationException("you should add an ".self::WIDGET_SERVICE_TAG_ALIAS.
+ " key on the service ".$id);
+ }
+
+ // check the place is set
+ if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) {
+ throw new InvalidConfigurationException(sprintf(
+ "You should add a %s key on the service %s",
+ self::WIDGET_SERVICE_TAG_PLACES,
+ $id
+ ));
+ }
+
+ // check the alias does not exists yet
+ if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) {
+ throw new InvalidArgumentException("a service has already be defined with the ".
+ self::WIDGET_SERVICE_TAG_ALIAS." ".$attr[self::WIDGET_SERVICE_TAG_ALIAS]);
+ }
+
+ // register the service as available
+ $this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id;
+ }
+ }
+
+ // add the services defined by factories
+ foreach($this->widgetFactories as $factory) {
+ /* @var $factory WidgetFactoryInterface */
+ $alias = $factory->getWidgetAlias();
+
+ // check the alias is not empty
+ if (empty($alias)) {
+ throw new \LogicException(sprintf(
+ "the widget factory %s returns an empty alias",
+ get_class($factory)));
+ }
+
+ // check the places are not empty
+ if (!is_array($factory->getAllowedPlaces())) {
+ throw new \UnexpectedValueException("the method 'getAllowedPlaces' "
+ . "should return a non-empty array. Unexpected value on ".
+ get_class($factory));
+ }
+
+ if (count($factory->getAllowedPlaces()) == 0) {
+ throw new \LengthException("The method 'getAllowedPlaces' should "
+ . "return a non-empty array, but returned 0 elements on ".
+ get_class($factory).'::getAllowedPlaces()');
+ }
+
+ // check the alias does not exists yet
+ if (array_key_exists($alias, $this->widgetServices)) {
+ throw new InvalidArgumentException("a service has already be defined with the ".
+ self::WIDGET_SERVICE_TAG_ALIAS." ".$alias);
+ }
+
+ // register the factory as available
+ $this->widgetServices[$factory->getWidgetAlias()] = $factory;
+
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/DependencyInjection/Widget/AddWidgetConfigurationTrait.php b/DependencyInjection/Widget/AddWidgetConfigurationTrait.php
new file mode 100644
index 000000000..04c0088ba
--- /dev/null
+++ b/DependencyInjection/Widget/AddWidgetConfigurationTrait.php
@@ -0,0 +1,243 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\DependencyInjection\Widget;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass as WidgetsCompilerPass;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+
+/**
+ * This trait allow to add automatic configuration for widget inside your config.
+ *
+ * Usage
+ * ======
+ *
+ * 1. Register widget factories
+ * ----------------------------
+ *
+ * Add widget factories, using `setWidgetFactories`
+ *
+ * Example :
+ *
+ * ```
+ * use Symfony\Component\DependencyInjection\ContainerBuilder;
+ * use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
+ *
+ * class MyConfig
+ * {
+ *
+ * use addWidgetConfigurationTrait;
+ *
+ * public function __construct(array $widgetFactories = array(), ContainerBuilder $container)
+ * {
+ * $this->setWidgetFactories($widgetFactories);
+ * // will be used on next step
+ * $this->container = $container;
+ * }
+ *
+ * }
+ * ```
+ *
+ *
+ *
+ * 2. add widget config to your config
+ * -----------------------------------
+ *
+ * ```
+ * use Symfony\Component\DependencyInjection\ContainerBuilder;
+ * use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
+ * use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+ *
+ * class MyConfig
+ * {
+ *
+ * use addWidgetConfigurationTrait;
+ *
+ * private $container;
+ *
+ * public function __construct(array $widgetFactories = array(), ContainerBuilder $container)
+ * {
+ * $this->setWidgetFactories($widgetFactories);
+ * $this->container;
+ * }
+ *
+ * public function getConfigTreeBuilder()
+ * {
+ * $treeBuilder = new TreeBuilder();
+ * $root = $treeBuilder->root('my_root');
+ *
+ * $root->children()
+ * ->arrayNode('widgets')
+ * ->canBeDisabled()
+ * ->children()
+ * ->append($this->addWidgetsConfiguration('homepage', $this->container))
+ * ->end()
+ * ->end()
+ * ;
+ *
+ * return $treeBuilder;
+ * }
+ *
+ * }
+ * ```
+ *
+ * the above code will add to the config :
+ *
+ * ```
+ * widgets:
+ * enabled: true
+ *
+ * # register widgets on place "homepage"
+ * homepage:
+ * order: ~ # Required
+ * widget_alias: ~ # One of "person_list"; "add_person", Required
+ * person_list:
+ * # options for the person_list
+ * ```
+ *
+ *
+ */
+trait AddWidgetConfigurationTrait
+{
+ /**
+ * @param WidgetFactoryInterface[]
+ */
+ private $widgetFactories;
+
+ /**
+ *
+ * @param WidgetFactoryInterface[] $widgetFactories
+ */
+ public function setWidgetFactories(array $widgetFactories)
+ {
+ $this->widgetFactories = $widgetFactories;
+ }
+
+ /**
+ * @return WidgetFactoryInterface[]
+ */
+ public function getWidgetFactories()
+ {
+ return $this->widgetFactories;
+ }
+
+ /**
+ * add configuration nodes for the widget at the given place.
+ *
+ * @param type $place
+ * @param ContainerBuilder $containerBuilder
+ * @return type
+ */
+ protected function addWidgetsConfiguration($place, ContainerBuilder $containerBuilder)
+ {
+ $treeBuilder = new TreeBuilder();
+ $root = $treeBuilder->root($place)
+ ->canBeUnset()
+ ->info('register widgets on place "'.$place.'"');
+
+ // if no childen, return the root
+ if (count(iterator_to_array($this->filterWidgetByPlace($place))) === 0) {
+ return $root;
+ }
+
+ $prototypeChildren = $root->prototype('array')->children();
+
+ $prototypeChildren
+ ->floatNode(WidgetsCompilerPass::WIDGET_CONFIG_ORDER)
+ ->isRequired()
+ ->info("the ordering of the widget. May be a number with decimal")
+ ->example("10.58")
+ ->end()
+ ->enumNode(WidgetsCompilerPass::WIDGET_CONFIG_ALIAS)
+ ->values($this->getWidgetAliasesbyPlace($place, $containerBuilder))
+ ->info("the widget alias (see config for your bundle)")
+ ->isRequired()
+ ->end()
+ ;
+
+ // adding the possible config on each widget under the widget_alias
+ foreach ($this->filterWidgetByPlace($place) as $factory) {
+ $builder = new TreeBuilder();
+ $widgetOptionsRoot = $builder->root($factory->getWidgetAlias());
+ $widgetOptionsRoot->canBeUnset()
+ ->info(sprintf('the configuration for the widget "%s" (only required if this widget is set in widget_alias)',
+ $factory->getWidgetAlias()));
+ $factory->configureOptions($place, $widgetOptionsRoot->children());
+ $prototypeChildren->append($widgetOptionsRoot);
+ }
+
+ return $root;
+ }
+
+ /**
+ * get all widget factories for the given place.
+ *
+ * @param string $place
+ * @return \Generator a generator containing a widget factory
+ */
+ protected function filterWidgetByPlace($place)
+ {
+ foreach($this->widgetFactories as $factory) {
+ if (in_array($place, $factory->getAllowedPlaces())) {
+ yield $factory;
+ }
+ }
+ }
+
+ /**
+ * get the all possible aliases for the given place. This method
+ * search within service tags and widget factories
+ *
+ * @param type $place
+ * @param ContainerBuilder $containerBuilder
+ * @return type
+ * @throws InvalidConfigurationException if a service's tag does not have the "alias" key
+ */
+ protected function getWidgetAliasesbyPlace($place, ContainerBuilder $containerBuilder)
+ {
+ $result = array();
+
+ foreach ($this->filterWidgetByPlace($place) as $factory) {
+ $result[] = $factory->getWidgetAlias();
+ }
+
+ // append the aliases added without factory
+ foreach ($containerBuilder
+ ->findTaggedServiceIds(WidgetsCompilerPass::WIDGET_SERVICE_TAG_NAME)
+ as $serviceId => $tags) {
+ foreach ($tags as $tag) {
+ // throw an error if no alias in definition
+ if (!array_key_exists(WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS, $tag)) {
+ throw new InvalidConfigurationException(sprintf(
+ "The service with id %s does not have any %d key",
+ $serviceId,
+ WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS
+ ));
+ }
+ // add the key to the possible results
+ $result[] = $tag[WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS];
+ }
+ }
+
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php b/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php
new file mode 100644
index 000000000..7e5a12871
--- /dev/null
+++ b/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php
@@ -0,0 +1,57 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\DependencyInjection\Widget\Factory;
+
+use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Allow to easily create WidgetFactory.
+ *
+ * Extending this factory, the widget will be created using the already defined
+ * service created "as other services" in your configuration (the most frequent
+ * way is using `services.yml` file.
+ *
+ * If you need to create different service based upon place, position or
+ * definition, you should implements directly
+ * `Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface`
+ *
+ *
+ */
+abstract class AbstractWidgetFactory implements WidgetFactoryInterface
+{
+
+ /**
+ *
+ * {@inheritdoc}
+ *
+ * Will create the definition by returning the definition from the `services.yml`
+ * file (or `services.xml` or `what-you-want.yml`).
+ *
+ * @see \Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface::createDefinition()
+ */
+ public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config)
+ {
+ return $containerBuilder->getDefinition($this
+ ->getServiceId($containerBuilder, $place, $order, $config)
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php b/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php
new file mode 100644
index 000000000..6e7836b01
--- /dev/null
+++ b/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php
@@ -0,0 +1,103 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\DependencyInjection\Widget\Factory;
+
+
+use Symfony\Component\Config\Definition\Builder\NodeBuilder;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Factory for creating configuration of widgets.
+ *
+ * When you need a widget with some configuration, you should implements this
+ * interface on a factory. The factory will add configuration to the bundle
+ * giving the places for your widget.
+ *
+ * Using this interface, **you do not need** to define the service in your
+ * container configuration (`services.yml` files).
+ *
+ * Once the class is created, you should inject the factory inside the container
+ * at compile time, in your `Bundle` class :
+ *
+ *
+ * ```
+ * namespace Chill\PersonBundle;
+ *
+ * use Symfony\Component\HttpKernel\Bundle\Bundle;
+ * use Symfony\Component\DependencyInjection\ContainerBuilder;
+ * use Chill\PersonBundle\Widget\PersonListWidgetFactory;
+ *
+ * class ChillPersonBundle extends Bundle
+ * {
+ * public function build(ContainerBuilder $container)
+ * {
+ * parent::build($container);
+ * // register a widget factory into chill_main :
+ * $container->getExtension('chill_main')
+ * ->addWidgetFactory(new PersonListWidgetFactory());
+ * }
+ * }
+ * ```
+ *
+ *
+ *
+ */
+interface WidgetFactoryInterface
+{
+ /**
+ * configure options for the widget. Those options will be added in
+ * configuration in the bundle where the widget will be used.
+ *
+ * @param type $place
+ * @param NodeBuilder $node
+ */
+ public function configureOptions($place, NodeBuilder $node);
+
+ /**
+ * get the widget alias. This alias will be used in configuration (`config.yml`)
+ */
+ public function getWidgetAlias();
+
+ /**
+ * Create a definition for the service which will render the widget.
+ *
+ * (Note: you can define the service by yourself, as other services,
+ * using the `AbstractWidgetFactory`)
+ *
+ * @param ContainerBuilder $containerBuilder
+ * @param type $place
+ * @param type $order
+ * @param array $config
+ */
+ public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config);
+
+ /**
+ * return the service id to build the widget.
+ *
+ * @param ContainerBuilder $containerBuilder
+ * @param string $place
+ * @param float $order
+ * @param array $config
+ *
+ * @return string the service definition
+ *
+ */
+ public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config);
+}
diff --git a/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php b/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php
new file mode 100644
index 000000000..b25c487b6
--- /dev/null
+++ b/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php
@@ -0,0 +1,43 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\DependencyInjection\Widget;
+
+use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
+
+/**
+ * Register widget factories to an extension.
+ *
+ */
+interface HasWidgetFactoriesExtensionInterface {
+
+ /**
+ * register a widget factory
+ *
+ * @param \Chill\MainBundle\DependencyInjection\Widget\WidgetFactoryInterface $factory
+ */
+ public function addWidgetFactory(WidgetFactoryInterface $factory);
+
+ /**
+ * get all the widget factories registered for this extension
+ *
+ * @return WidgetFactoryInterface[]
+ */
+ public function getWidgetFactories();
+}
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
index 199c9850e..a5c4b4687 100644
--- a/Resources/config/services.yml
+++ b/Resources/config/services.yml
@@ -58,8 +58,8 @@ services:
tags:
- { name: twig.extension }
- chill.main.twig.delegated_block:
- class: Chill\MainBundle\Templating\DelegatedBlockRenderingTwig
+ chill.main.twig.widget:
+ class: Chill\MainBundle\Templating\Widget\WidgetRenderingTwig
arguments:
- "@event_dispatcher"
tags:
diff --git a/Resources/views/layout.html.twig b/Resources/views/layout.html.twig
index e93f0cb62..e8c8db00e 100644
--- a/Resources/views/layout.html.twig
+++ b/Resources/views/layout.html.twig
@@ -137,6 +137,10 @@
+
+
+ {{ chill_delegated_block('homepage', {} ) }}
+
{{ chill_menu('homepage', {
diff --git a/Templating/DelegatedBlockRenderingTwig.php b/Templating/DelegatedBlockRenderingTwig.php
deleted file mode 100644
index 77027aaef..000000000
--- a/Templating/DelegatedBlockRenderingTwig.php
+++ /dev/null
@@ -1,87 +0,0 @@
-
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-namespace Chill\MainBundle\Templating;
-
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-
-/**
- * Add the function `chill_delegated_block`.
- *
- * In a template, you can now allow rendering of a block from other bundle.
- *
- * The layout template must explicitly call the rendering of other block,
- * with the twig function
- *
- * ```
- * chill_delegated_block('block_name', { 'array' : 'with context' } )
- * ```
- *
- * This will launch an event
- * `Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent` with
- * the event's name 'chill_block.block_name'.
- *
- * You may add content to the page using the function
- * `DelegatedBlockRenderingEvent::addContent`.
- *
- * See also the documentation of
- * `Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent`
- * for usage of this event class
- *
- *
- * @author Julien Fastré
- */
-class DelegatedBlockRenderingTwig extends \Twig_Extension
-{
- /**
- *
- * @var EventDispatcherInterface
- */
- protected $eventDispatcher;
-
- public function __construct(EventDispatcherInterface $eventDispatcher)
- {
- $this->eventDispatcher = $eventDispatcher;
- }
-
-
- public function getName()
- {
- return 'chill_main_delegated_block';
- }
-
- public function getFunctions()
- {
- return array(
- new \Twig_SimpleFunction('chill_delegated_block',
- array($this, 'renderingDelegatedBlock'),
- array('is_safe' => array('html')))
- );
- }
-
- public function renderingDelegatedBlock($block, array $context)
- {
- $event = new Events\DelegatedBlockRenderingEvent($context);
-
- $this->eventDispatcher->dispatch('chill_block.'.$block, $event);
-
- return $event->getContent();
- }
-
-}
diff --git a/Templating/Widget/WidgetInterface.php b/Templating/Widget/WidgetInterface.php
new file mode 100644
index 000000000..76af9af56
--- /dev/null
+++ b/Templating/Widget/WidgetInterface.php
@@ -0,0 +1,10 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\MainBundle\Templating\Widget;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Chill\MainBundle\Templating\Widget\WidgetInterface;
+use Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent;
+
+/**
+ * Add the function `chill_delegated_block`.
+ *
+ * In a template, you can now allow rendering of a block from other bundle.
+ *
+ * The layout template must explicitly call the rendering of other block,
+ * with the twig function
+ *
+ * ```
+ * chill_delegated_block('block_name', { 'array' : 'with context' } )
+ * ```
+ *
+ * This will launch an event
+ * `Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent` with
+ * the event's name 'chill_block.block_name'.
+ *
+ * You may add content to the page using the function
+ * `DelegatedBlockRenderingEvent::addContent`.
+ *
+ * See also the documentation of
+ * `Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent`
+ * for usage of this event class
+ *
+ *
+ * @author Julien Fastré
+ */
+class WidgetRenderingTwig extends \Twig_Extension
+{
+
+ /**
+ * Contains the widget. This is a double dimension array :
+ *
+ * - first key is the place,
+ * - second key is the ordering ;
+ * - the value is an array where the widget is the first argument and the
+ * second is the config
+ *
+ * i.e :
+ *
+ * $widget['place']['ordering'] = array($widget, $config);
+ *
+ *
+ *
+ * @var array an array of widget by place and ordering
+ */
+ protected $widget = array();
+
+ /**
+ *
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ public function __construct(EventDispatcherInterface $eventDispatcher)
+ {
+ $this->eventDispatcher = $eventDispatcher;
+ }
+
+
+ public function getName()
+ {
+ return 'chill_main_widget';
+ }
+
+ public function getFunctions()
+ {
+ return array(
+ new \Twig_SimpleFunction('chill_delegated_block',
+ array($this, 'renderingWidget'),
+ array(
+ 'is_safe' => array('html'),
+ 'needs_environment' => true,
+ 'deprecated' => true, 'alternative' => 'chill_widget'
+ )),
+ new \Twig_SimpleFunction('chill_widget',
+ array($this, 'renderingWidget'),
+ array('is_safe' => array('html'), 'needs_environment' => true))
+ );
+ }
+
+ public function renderingWidget(\Twig_Environment $env, $block, array $context = array())
+ {
+ // get the content of widgets
+ $content = '';
+ foreach ($this->getWidgetsArraysOrdered($block) as $a) {
+ /* @var $widget Widget\WidgetInterface */
+ $widget = $a[0];
+ $config = $a[1];
+
+ $content = $widget->render($env, $block, $context, $config);
+ }
+
+ // for old rendering events (deprecated)
+ $event = new DelegatedBlockRenderingEvent($context);
+
+ $this->eventDispatcher->dispatch('chill_block.'.$block, $event);
+
+ return $content." ".$event->getContent();
+ }
+
+ /**
+ * add a widget to this class, which become available for a future call.
+ *
+ * This function is used by DependencyInjection\CompilerPass\WidgetCompilerPass,
+ * which add the widget to this class when it is created by from DI, according
+ * to the given config under `chill_main`.
+ *
+ * @param string $place
+ * @param WidgetInterface $widget
+ * @param array $config
+ */
+ public function addWidget($place, $ordering, $widget, array $config = array())
+ {
+ $this->widget[$place][$ordering] = array($widget, $config);
+ }
+
+ /**
+ *
+ * @param string $place
+ * @return array
+ */
+ protected function getWidgetsArraysOrdered($place)
+ {
+ if (!array_key_exists($place, $this->widget)) {
+ $this->widget[$place] = array();
+ }
+
+ \ksort($this->widget[$place]);
+
+ return $this->widget[$place];
+ }
+
+
+
+}