footer elements are almost displayed

This commit is contained in:
Bachir Soussi Chiadmi
2018-01-02 16:32:17 +01:00
parent 9c6a1ce9a9
commit 7b9de31a42
70 changed files with 6581 additions and 65 deletions
@@ -0,0 +1,215 @@
<?php
namespace Drupal\context;
use Drupal\Core\Condition\ConditionInterface;
use Drupal\Core\Condition\ConditionPluginCollection;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\context\Plugin\ContextReactionPluginCollection;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
interface ContextInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
/**
* The default value for a context that is not assigned to a group.
*/
const CONTEXT_GROUP_NONE = NULL;
/**
* Get the ID of the context.
*
* @return string
*/
public function id();
/**
* Get the machine name of the context.
*
* @return string
*/
public function getName();
/**
* Set the machine name of the context.
*
* @param string $name
*
* @return $this
*/
public function setName($name);
/**
* Get the context label.
*
* @return string
*/
public function getLabel();
/**
* Set the context label.
*
* @param string $label
*
* @return $this
*/
public function setLabel($label);
/**
* Get the context description.
*
* @return string
*/
public function getDescription();
/**
* Set the context description.
*
* @param string $description
*
* @return $this
*/
public function setDescription($description);
/**
* Get the group this context belongs to.
*
* @return null|string
*/
public function getGroup();
/**
* Set the group this context should belong to.
*
* @param null|string $group
*
* @return $this
*/
public function setGroup($group);
/**
* Get the weight for this context.
*
* @return int
*/
public function getWeight();
/**
* Set the weight for this context.
*
* @param int $weight
* The weight to set for this context.
*
* @return $this
*/
public function setWeight($weight);
/**
* If the context requires all conditions to validate.
*
* @return boolean
*/
public function requiresAllConditions();
/**
* Set if all conditions should be required for this context to validate.
*
* @param bool $require
* If a condition is required or not.
*
* @return $this
*/
public function setRequireAllConditions($require);
/**
* Get a list of all conditions.
*
* @return ConditionInterface[]|ConditionPluginCollection
*/
public function getConditions();
/**
* Get a condition with the specified ID.
*
* @param string $condition_id
* The condition to get.
*
* @return \Drupal\Core\Condition\ConditionInterface
*/
public function getCondition($condition_id);
/**
* Set the conditions.
*
* @param array $configuration
* The configuration for the condition plugin.
*
* @return string
*/
public function addCondition(array $configuration);
/**
* Remove the specified condition.
*
* @param string $condition_id
* The id of the condition to remove.
*
* @return $this
*/
public function removeCondition($condition_id);
/**
* Check to see if the context has the specified condition.
*
* @param string $condition_id
* The ID of the condition to check for.
*
* @return bool
*/
public function hasCondition($condition_id);
/**
* Get a list of all the reactions.
*
* @return ContextReactionInterface[]|ContextReactionPluginCollection
*/
public function getReactions();
/**
* Get a reaction with the specified ID.
*
* @param string $reaction_id
* The ID of the reaction to get.
*
* @return ContextReactionInterface
*/
public function getReaction($reaction_id);
/**
* Add a context reaction.
*
* @param array $configuration
*
* @return string
*/
public function addReaction(array $configuration);
/**
* Remove the specified reaction.
*
* @param string $reaction_id
* The id of the reaction to remove.
*
* @return $this
*/
public function removeReaction($reaction_id);
/**
* Check to see if the context has the specified reaction.
*
* @param string $reaction_id
* The ID of the reaction to check for.
*
* @return bool
*/
public function hasReaction($reaction_id);
}
@@ -0,0 +1,360 @@
<?php
namespace Drupal\context;
use Drupal\context\Entity\Context;
use Drupal\context\Plugin\ContextReaction\Blocks;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Condition\ConditionPluginCollection;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Condition\ConditionAccessResolverTrait;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Theme\ThemeManagerInterface;
/**
* This is the manager service for the context module and should not be
* confused with the built in contexts in Drupal.
*/
class ContextManager {
use ConditionAccessResolverTrait;
use StringTranslationTrait;
/**
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $entityQuery;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
protected $contextRepository;
/**
* @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
*/
protected $contextHandler;
/**
* If the context conditions has been evaluated then this is set to TRUE
* otherwise FALSE.
*
* @var bool
*/
protected $contextConditionsEvaluated = FALSE;
/**
* An array of contexts that have been evaluated and are active.
*
* @var array
*/
protected $activeContexts = [];
/**
* @var \Drupal\Core\Entity\EntityFormBuilderInterface
*/
private $entityFormBuilder;
/**
* @var \Drupal\Core\Theme\ThemeManagerInterface;
*/
protected $themeManager;
/**
* Construct.
*
* @param QueryFactory $entityQuery
* The Drupal entity query service.
*
* @param EntityManagerInterface $entityManager
* The Drupal entity manager service.
*
* @param ContextRepositoryInterface $contextRepository
* The drupal context repository service.
*
* @param ContextHandlerInterface $contextHandler
* The Drupal context handler service.
*
* @param ThemeManagerInterface $themeManager
* The Drupal theme manager service.
*
* @param \Drupal\Core\Entity\EntityFormBuilderInterface $entityFormBuilder
*/
function __construct(
QueryFactory $entityQuery,
EntityManagerInterface $entityManager,
ContextRepositoryInterface $contextRepository,
ContextHandlerInterface $contextHandler,
EntityFormBuilderInterface $entityFormBuilder,
ThemeManagerInterface $themeManager
)
{
$this->entityQuery = $entityQuery;
$this->entityManager = $entityManager;
$this->contextRepository = $contextRepository;
$this->contextHandler = $contextHandler;
$this->entityFormBuilder = $entityFormBuilder;
$this->themeManager = $themeManager;
}
/**
* Get all contexts.
*
* @return Context[]
*/
public function getContexts() {
$contextIds = $this->entityQuery
->get('context')
->execute();
$contexts = $this->entityManager
->getStorage('context')
->loadMultiple($contextIds);
// Sort the contexts by their weight.
uasort($contexts, [$this, 'sortContextsByWeight']);
return $contexts;
}
/**
* Get all contexts sorted by their group and sorted by their weight inside
* of each group.
*
* @return array
*/
public function getContextsByGroup() {
$contexts = $this->getContexts();
$groups = [];
// Add each context to their respective groups.
foreach ($contexts as $context_id => $context) {
$group = $context->getGroup();
if ($group === Context::CONTEXT_GROUP_NONE) {
$group = 'not_grouped';
}
$groups[$group][$context_id] = $context;
}
return $groups;
}
/**
* Check to validate that the context name does not already exist.
*
* @param string $name
* The machine name of the context to validate.
*
* @return bool
*/
public function contextExists($name) {
$entity = $this->entityQuery->get('context')
->condition('name', $name)
->execute();
return (bool) $entity;
}
/**
* Check to see if context conditions has been evaluated.
*
* @return bool
*/
public function conditionsHasBeenEvaluated() {
return $this->contextConditionsEvaluated;
}
/**
* Get the evaluated and active contexts.
*
* @return \Drupal\context\ContextInterface[]
*/
public function getActiveContexts() {
if ($this->conditionsHasBeenEvaluated()) {
return $this->activeContexts;
}
$this->evaluateContexts();
return $this->activeContexts;
}
/**
* Evaluate all context conditions.
*/
public function evaluateContexts() {
/** @var \Drupal\context\ContextInterface $context */
foreach ($this->getContexts() as $context) {
if ($this->evaluateContextConditions($context) && !$context->disabled()) {
$this->activeContexts[] = $context;
}
}
$this->contextConditionsEvaluated = TRUE;
}
/**
* Get all active reactions or reactions of a certain type.
*
* @param string $reactionType
* Either the reaction class name or the id of the reaction type to get.
*
* @return ContextReactionInterface[]
*/
public function getActiveReactions($reactionType = NULL) {
$reactions = [];
foreach ($this->getActiveContexts() as $context) {
// If no reaction type has been specified then add all reactions and
// continue to the next context.
if (is_null($reactionType)) {
foreach ($context->getReactions() as $reaction) {
// Only return block reaction if there is a block applied to the current theme.
if ($reaction instanceof Blocks) {
$blocks = $reaction->getBlocks();
$current_theme = $this->getCurrentTheme();
foreach ($blocks as $block) {
if ($block->getConfiguration()['theme'] == $current_theme) {
$reactions[] = $reaction;
break;
}
}
}
else {
$reactions[] = $reaction;
}
}
continue;
}
$contextReactions = $context->getReactions();
// Filter the reactions based on the reaction type.
foreach ($contextReactions as $reaction) {
if (class_exists($reactionType) && $reaction instanceof $reactionType) {
$reactions[] = $reaction;
continue;
}
if ($reaction->getPluginId() === $reactionType) {
$reactions[] = $reaction;
continue;
}
}
}
return $reactions;
}
/**
* Evaluate a contexts conditions.
*
* @param ContextInterface $context
* The context to evaluate conditions for.
*
* @return bool
*/
public function evaluateContextConditions(ContextInterface $context) {
$conditions = $context->getConditions();
// Apply context to any context aware conditions.
$this->applyContexts($conditions);
// Set the logic to use when validating the conditions.
$logic = $context->requiresAllConditions()
? 'and'
: 'or';
// Of there are no conditions then the context will be
// applied as a site wide context.
if (!count($conditions)) {
$logic = 'and';
}
return $this->resolveConditions($conditions, $logic);
}
/**
* Apply context to all the context aware conditions in the collection.
*
* @param ConditionPluginCollection $conditions
* A collection of conditions to apply context to.
*
* @return bool
*/
protected function applyContexts(ConditionPluginCollection &$conditions) {
foreach ($conditions as $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
try {
$contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
$this->contextHandler->applyContextMapping($condition, $contexts);
}
catch (ContextException $e) {
return FALSE;
}
}
}
return TRUE;
}
/**
* Get a rendered form for the context.
* @param \Drupal\context\ContextInterface $context
* @param string $formType
* @param array $form_state_additions
* @return array
*/
public function getForm(ContextInterface $context, $formType = 'edit', array $form_state_additions = array()) {
return $this->entityFormBuilder->getForm($context, $formType, $form_state_additions);
}
/**
* Sorts an array of context entities by their weight.
*
* Callback for uasort().
*
* @param ContextInterface $a
* First item for comparison.
*
* @param ContextInterface $b
* Second item for comparison.
*
* @return int
* The comparison result for uasort().
*/
public function sortContextsByWeight(ContextInterface $a, ContextInterface $b) {
if ($a->getWeight() == $b->getWeight()) {
return 0;
}
return ($a->getWeight() < $b->getWeight()) ? -1 : 1;
}
/**
* Get current active theme.
*
* @return string
* Current active theme name.
*/
private function getCurrentTheme() {
return $this->themeManager->getActiveTheme()->getName();
}
}
@@ -0,0 +1,51 @@
<?php
namespace Drupal\context;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Menu\MenuActiveTrail;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\menu_link_content\Entity\MenuLinkContent;
/**
* Extend the MenuActiveTrail class.
*/
class ContextMenuActiveTrail extends MenuActiveTrail {
/**
* @var \Drupal\context\ContextManager.
*/
protected $contextManager;
/**
* {@inheritdoc}
*/
public function __construct(MenuLinkManagerInterface $menu_link_manager, RouteMatchInterface $route_match, CacheBackendInterface $cache, LockBackendInterface $lock, ContextManager $context_manager) {
parent::__construct($menu_link_manager, $route_match, $cache, $lock);
$this->contextManager = $context_manager;
}
/**
* {@inheritdoc}
*/
public function getActiveLink($menu_name = NULL) {
$found = parent::getActiveLink($menu_name);
// Get active reaction of Menu type.
foreach($this->contextManager->getActiveReactions('menu') as $reaction) {
$menu_items = $reaction->execute();
foreach ($menu_items as $menu_link_content) {
$menu = strtok($menu_link_content, ':');
if ($menu == $menu_name) {
$plugin_id = substr($menu_link_content, strlen($menu) + 1);
return $this->menuLinkManager->createInstance($plugin_id);
}
}
}
return $found;
}
}
@@ -0,0 +1,25 @@
<?php
namespace Drupal\context;
use Drupal\Core\Executable\ExecutableInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Core\Plugin\PluginFormInterface;
interface ContextReactionInterface extends ConfigurablePluginInterface, PluginFormInterface, PluginInspectionInterface, ExecutableInterface {
/**
* Get the unique ID of this context reaction.
*
* @return string|null
*/
public function getId();
/**
* Provides a human readable summary of the condition's configuration.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
*/
public function summary();
}
@@ -0,0 +1,21 @@
<?php
namespace Drupal\context;
use Traversable;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
class ContextReactionManager extends DefaultPluginManager {
/**
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/ContextReaction', $namespaces, $module_handler, 'Drupal\context\ContextReactionInterface', 'Drupal\context\Reaction\Annotation\ContextReaction');
$this->alterInfo('context_condition_info');
$this->setCacheBackend($cache_backend, 'context_condition_plugins');
}
}
@@ -0,0 +1,68 @@
<?php
namespace Drupal\context;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginBase;
abstract class ContextReactionPluginBase extends PluginBase implements ContextReactionInterface {
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public function getId() {
if (isset($this->getConfiguration()['id'])) {
return $this->getConfiguration()['id'];
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return [
'id' => $this->getPluginId(),
] + $this->configuration;
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$this->configuration = $configuration + $this->defaultConfiguration();
return $this;
}
/**
* Form validation handler is optional.
*
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'saved' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
}
@@ -0,0 +1,23 @@
<?php
namespace Drupal\context;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
/**
* Alter the service container to use a custom class.
*/
class ContextServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
// Override the menu active trail with a new class.
$definition = $container->getDefinition('menu.active_trail');
$definition->setClass('Drupal\context\ContextMenuActiveTrail');
$definition->addArgument($container->getDefinition('context.manager'));
}
}
@@ -0,0 +1,376 @@
<?php
namespace Drupal\context\Entity;
use Drupal;
use InvalidArgumentException;
use Drupal\context\ContextInterface;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Condition\ConditionPluginCollection;
use Drupal\context\Plugin\ContextReactionPluginCollection;
/**
* Defines the Context entity.
*
* @ConfigEntityType(
* id = "context",
* label = @Translation("Context"),
* handlers = {
* "access" = "Drupal\context\Entity\ContextAccess",
* "list_builder" = "Drupal\context_ui\ContextListBuilder",
* "form" = {
* "add" = "Drupal\context_ui\Form\ContextAddForm",
* "edit" = "Drupal\context_ui\Form\ContextEditForm",
* "delete" = "Drupal\context_ui\Form\ContextDeleteForm",
* "disable" = "Drupal\context_ui\Form\ContextDisableForm",
* }
* },
* links = {
* "edit-form" = "/admin/structure/context/{context}",
* "delete-form" = "/admin/structure/context/{context}/delete",
* "disable-form" = "/admin/structure/context/{context}/disable",
* "collection" = "/admin/structure/context",
* },
* admin_permission = "administer contexts",
* entity_keys = {
* "id" = "name",
* "label" = "label",
* },
* config_export = {
* "name",
* "label",
* "group",
* "description",
* "requireAllConditions",
* "disabled",
* "conditions",
* "reactions",
* "weight",
* }
* )
*/
class Context extends ConfigEntityBase implements ContextInterface {
/**
* The machine name of the context.
*
* @var string
*/
protected $name;
/**
* The label of the context.
*
* @var string
*/
protected $label;
/**
* A description for this context.
*
* @var string
*/
protected $description = '';
/**
* The group this context belongs to.
*
* @var string|null
*/
protected $group = self::CONTEXT_GROUP_NONE;
/**
* If all conditions must validate for this context.
*
* @var bool
*/
protected $requireAllConditions = FALSE;
/**
* The context conditions as a collection.
*
* @var ConditionPluginCollection
*/
protected $conditionsCollection;
/**
* The context reactions as a collection.
*
* @var ContextReactionPluginCollection
*/
protected $reactionsCollection;
/**
* A list of conditions this context should react to.
*
* @var array
*/
protected $conditions = [];
/**
* A list of reactions that should be taken when conditions match.
*
* @var array
*/
protected $reactions = [];
/**
* If the context is disabled or not.
*
* @var bool
*/
protected $disabled = FALSE;
/**
* The weight for this context.
*
* @var int
*/
protected $weight = 0;
/**
* Returns the ID of the context. The ID is the unique machine name of the
* context.
*/
public function id() {
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->name;
}
/**
* {@inheritdoc}
*/
public function setName($name) {
if (!is_string($name)) {
throw new InvalidArgumentException('The context name must be a string.');
}
$this->name = $name;
return $this;
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->label;
}
/**
* {@inheritdoc}
*/
public function setLabel($label) {
if (!is_string($label)) {
throw new InvalidArgumentException('The context label must be a string.');
}
$this->label = $label;
return $this;
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
if (!is_string($description)) {
throw new InvalidArgumentException('The context description must be a string.');
}
$this->description = $description;
return $this;
}
/**
* {@inheritdoc}
*/
public function getGroup() {
return $this->group;
}
/**
* {@inheritdoc}
*/
public function setGroup($group) {
$this->group = (is_string($group) && !empty($group)) ? $group : self::CONTEXT_GROUP_NONE;
return $this;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->weight;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->weight = (int) $weight;
return $this;
}
/**
* {@inheritdoc}
*/
public function requiresAllConditions() {
return $this->requireAllConditions;
}
/**
* {@inheritdoc}
*/
public function setRequireAllConditions($require) {
$this->requireAllConditions = (bool) $require;
return $this;
}
/**
* {@inheritdoc}
*/
public function getConditions() {
if (!$this->conditionsCollection) {
$conditionManager = Drupal::service('plugin.manager.condition');
$this->conditionsCollection = new ConditionPluginCollection($conditionManager, $this->conditions);
}
return $this->conditionsCollection;
}
/**
* {@inheritdoc}
*/
public function getCondition($condition_id) {
return $this->getConditions()->get($condition_id);
}
/**
* {@inheritdoc}
*/
public function addCondition(array $configuration) {
// Add an UUID to the condition to make sure the configuration is saved
// since the configuration export from the conditions collection wont
// export configuration that has not been "configured".
$configuration['uuid'] = $this->uuidGenerator()->generate();
$this->getConditions()->addInstanceId($configuration['id'], $configuration);
return $configuration['id'];
}
/**
* {@inheritdoc}
*/
public function removeCondition($condition_id) {
$this->getConditions()->removeInstanceId($condition_id);
return $this;
}
/**
* {@inheritdoc}
*/
public function hasCondition($condition_id) {
return $this->getConditions()->has($condition_id);
}
/**
* {@inheritdoc}
*/
public function getReactions() {
if (!$this->reactionsCollection) {
$reactionManager = Drupal::service('plugin.manager.context_reaction');
$this->reactionsCollection = new ContextReactionPluginCollection($reactionManager, $this->reactions);
}
return $this->reactionsCollection;
}
/**
* {@inheritdoc}
*/
public function getReaction($reaction_id) {
return $this->getReactions()->get($reaction_id);
}
/**
* {@inheritdoc}
*/
public function addReaction(array $configuration) {
// Add an UUID to the condition to make sure the configuration is saved
// since the configuration export from the conditions collection wont
// export configuration that has not been "configured".
$configuration['uuid'] = $this->uuidGenerator()->generate();
$this->getReactions()->addInstanceId($configuration['id'], $configuration);
return $configuration['id'];
}
/**
* {@inheritdoc}
*/
public function removeReaction($reaction_id) {
$this->getReactions()->removeInstanceId($reaction_id);
return $this;
}
/**
* {@inheritdoc}
*/
public function hasReaction($reaction_id) {
return $this->getReactions()->has($reaction_id);
}
/**
* Gets the plugin collections used by this entity.
*
* @return \Drupal\Component\Plugin\LazyPluginCollection[]
* An array of plugin collections, keyed by the property name they use to
* store their configuration.
*/
public function getPluginCollections() {
return [
'reactions' => $this->getReactions(),
'conditions' => $this->getConditions(),
];
}
/**
* Disable context.
*/
public function disable() {
$this->disabled = !$this->disabled();
$this->save();
}
/**
* {@inheritdoc}
*/
public function disabled() {
return $this->disabled;
}
}
@@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\context\Entity\ContextAccess.
*/
namespace Drupal\context\Entity;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the access control handler for the page entity type.
*/
class ContextAccess extends EntityAccessControlHandler implements EntityHandlerInterface {
/**
* Constructs an access control handler instance.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*/
public function __construct(EntityTypeInterface $entity_type) {
parent::__construct($entity_type);
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static($entity_type);
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
return parent::checkAccess($entity, $operation, $account);
}
}
@@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\context\EventSubscriber\BlockPageDisplayVariantSubscriber.
*/
namespace Drupal\context\EventSubscriber;
use Drupal\context\ContextManager;
use Drupal\Core\Render\RenderEvents;
use Drupal\context\Plugin\ContextReaction\Blocks;
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Selects the block page display variant.
*
* @see \Drupal\block\Plugin\DisplayVariant\BlockPageVariant
*/
class BlockPageDisplayVariantSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\context\ContextManager
*/
private $contextManager;
/**
* @param \Drupal\context\ContextManager $contextManager
*/
function __construct(ContextManager $contextManager) {
$this->contextManager = $contextManager;
}
/**
* Selects the context block page display variant.
*
* @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event
* The event to process.
*/
public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $event) {
// Activate the context block page display variant if any of the reactions
// is a blocks reaction.
foreach ($this->contextManager->getActiveReactions() as $reaction) {
if ($reaction instanceof Blocks) {
$event->setPluginId('context_block_page');
break;
}
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RenderEvents::SELECT_PAGE_DISPLAY_VARIANT][] = array('onSelectPageDisplayVariant');
return $events;
}
}
@@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\context\Form\AjaxFormTrait.
*/
namespace Drupal\context\Form;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
/**
* Provides helper methods for using an AJAX modal. This is a copy of the
* ctools AjaxFormTrait.
*/
trait AjaxFormTrait {
/**
* Gets attributes for use with an AJAX modal.
*
* @return array
*/
public static function getAjaxAttributes() {
return [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 1000,
]),
];
}
/**
* Gets attributes for use with an add button AJAX modal.
*
* @return array
*/
public static function getAjaxButtonAttributes() {
return NestedArray::mergeDeep(AjaxFormTrait::getAjaxAttributes(), [
'class' => [
'button',
'button--small',
'button-action',
],
]);
}
}
@@ -0,0 +1,36 @@
<?php
namespace Drupal\context\Plugin\Condition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\system\Plugin\Condition\RequestPath;
/**
* Provides a 'Request path exclusion' condition.
*
* @Condition(
* id = "request_path_exclusion",
* label = @Translation("Request path exclusion"),
* context = {
* "request_path_exclusion" = @ContextDefinition("request_path_exclusion", label = @Translation("Request path exclusion"))
* }
* )
*/
class RequestPathExclusion extends RequestPath implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
unset($form['negate']);
return $form;
}
/**
* {@inheritdoc}
*/
public function evaluate() {
return !parent::evaluate();
}
}
@@ -0,0 +1,681 @@
<?php
namespace Drupal\context\Plugin\ContextReaction;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Core\Form\FormState;
use Drupal\Core\Render\Element;
use Drupal\context\ContextInterface;
use Drupal\context\Form\AjaxFormTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\context\ContextReactionPluginBase;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\context\Reaction\Blocks\BlockCollection;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a content reaction that will let you place blocks in the current
* themes regions.
*
* @ContextReaction(
* id = "blocks",
* label = @Translation("Blocks")
* )
*/
class Blocks extends ContextReactionPluginBase implements ContainerFactoryPluginInterface {
use AjaxFormTrait;
/**
* An array of blocks to be displayed with this reaction.
*
* @var array
*/
protected $blocks = [];
/**
* Contains a temporary collection of blocks.
*
* @var BlockCollection
*/
protected $blocksCollection;
/**
* The Drupal UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuid;
/**
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* @var ContextRepositoryInterface
*/
protected $contextRepository;
/**
* @var ContextHandlerInterface
*/
protected $contextHandler;
/**
* @var AccountInterface
*/
protected $account;
/**
* {@inheritdoc}
*/
function __construct(
array $configuration,
$pluginId,
$pluginDefinition,
UuidInterface $uuid,
ThemeManagerInterface $themeManager,
ThemeHandlerInterface $themeHandler,
ContextRepositoryInterface $contextRepository,
ContextHandlerInterface $contextHandler,
AccountInterface $account
) {
parent::__construct($configuration, $pluginId, $pluginDefinition);
$this->uuid = $uuid;
$this->themeManager = $themeManager;
$this->themeHandler = $themeHandler;
$this->contextRepository = $contextRepository;
$this->contextHandler = $contextHandler;
$this->account = $account;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
return new static(
$configuration,
$pluginId,
$pluginDefinition,
$container->get('uuid'),
$container->get('theme.manager'),
$container->get('theme_handler'),
$container->get('context.repository'),
$container->get('context.handler'),
$container->get('current_user')
);
}
/**
* Executes the plugin.
*
* @param array $build
* The current build of the page.
*
* @param string|null $title
* The page title.
*
* @param string|null $main_content
* The main page content.
*
* @return array
*/
public function execute(array $build = array(), $title = NULL, $main_content = NULL) {
$cacheability = CacheableMetadata::createFromRenderArray($build);
// Use the currently active theme to fetch blocks.
$theme = $this->themeManager->getActiveTheme()->getName();
$regions = $this->getBlocks()->getAllByRegion($theme);
// Add each block to the page build.
foreach ($regions as $region => $blocks) {
/** @var $blocks BlockPluginInterface[] */
foreach ($blocks as $block_id => $block) {
$configuration = $block->getConfiguration();
$block_placement_key = $this->blockShouldBePlacedUniquely($block)
? $block_id
: $block->getConfiguration()['id'];
if ($block instanceof MainContentBlockPluginInterface) {
if (isset($build['content']['system_main'])) {
unset($build['content']['system_main']);
}
$block->setMainContent($main_content);
}
// Make sure the user is allowed to view the block.
$access = $block->access($this->account, TRUE);
$cacheability->addCacheableDependency($access);
// If the user is not allowed then do not render the block.
if (!$access->isAllowed()) {
continue;
}
if ($block instanceof TitleBlockPluginInterface) {
if (isset($build['content']['messages'])) {
unset($build['content']['messages']);
}
$block->setTitle($title);
}
// Inject runtime contexts.
if ($block instanceof ContextAwarePluginInterface) {
$contexts = $this->contextRepository->getRuntimeContexts($block->getContextMapping());
$this->contextHandler->applyContextMapping($block, $contexts);
}
// Create the render array for the block as a whole.
// @see template_preprocess_block().
$blockBuild = [
'#theme' => 'block',
'#attributes' => [
'class' => [$block->getConfiguration()['css_class']]
],
'#configuration' => $configuration,
'#plugin_id' => $block->getPluginId(),
'#base_plugin_id' => $block->getBaseId(),
'#derivative_plugin_id' => $block->getDerivativeId(),
'#block_plugin' => $block,
'#pre_render' => [[$this, 'preRenderBlock']],
'#cache' => [
'keys' => ['context_blocks_reaction', 'block', $block_placement_key],
'tags' => $block->getCacheTags(),
'contexts' => $block->getCacheContexts(),
'max-age' => $block->getCacheMaxAge(),
],
];
// Add contextual links to block.
$content = $block->build();
if (isset($content['#contextual_links'])) {
$blockBuild['#contextual_links'] = $content['#contextual_links'];
}
// Add additional contextual link, for editing block configuration.
$blockBuild['#contextual_links']['context_block'] = [
'route_parameters' => [
'context' => $configuration['context_id'],
'reaction_id' => 'blocks',
'block_id' => $block->getConfiguration()['uuid'],
],
];
if (array_key_exists('weight', $configuration)) {
$blockBuild['#weight'] = $configuration['weight'];
}
$build[$region][$block_placement_key] = $blockBuild;
// After merging with blocks from Block layout, we want to sort all of
// them again.
$build[$region]['#sorted'] = FALSE;
// The main content block cannot be cached: it is a placeholder for the
// render array returned by the controller. It should be rendered as-is,
// with other placed blocks "decorating" it. Analogous reasoning for the
// title block.
if ($block instanceof MainContentBlockPluginInterface || $block instanceof TitleBlockPluginInterface) {
unset($build[$region][$block_placement_key]['#cache']['keys']);
}
$cacheability->addCacheableDependency($block);
}
}
$cacheability->applyTo($build);
return $build;
}
/**
* Renders the content using the provided block plugin.
*
* @param array $build
* @return array
*/
public function preRenderBlock($build) {
$content = $build['#block_plugin']->build();
unset($build['#block_plugin']);
// Abort rendering: render as the empty string and ensure this block is
// render cached, so we can avoid the work of having to repeatedly
// determine whether the block is empty. E.g. modifying or adding entities
// could cause the block to no longer be empty.
if (is_null($content) || Element::isEmpty($content)) {
$build = [
'#markup' => '',
'#cache' => $build['#cache'],
];
// If $content is not empty, then it contains cacheability metadata, and
// we must merge it with the existing cacheability metadata. This allows
// blocks to be empty, yet still bubble cacheability metadata, to indicate
// why they are empty.
if (!empty($content)) {
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromRenderArray($content))
->applyTo($build);
}
}
else {
$build['content'] = $content;
}
return $build;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'blocks' => []
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$this->configuration = $configuration + $this->defaultConfiguration();
if (isset($configuration['blocks'])) {
$this->blocks = $configuration['blocks'];
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return [
'blocks' => $this->getBlocks()->getConfiguration(),
] + parent::getConfiguration();
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Lets you add blocks to the selected themes regions');
}
/**
* Get all blocks as a collection.
*
* @return BlockPluginInterface[]|BlockCollection
*/
public function getBlocks() {
if (!$this->blocksCollection) {
$blockManager = \Drupal::service('plugin.manager.block');
$this->blocksCollection = new BlockCollection($blockManager, $this->blocks);
}
return $this->blocksCollection;
}
/**
* Get a block by id.
*
* @param string $blockId
* The ID of the block to get.
*
* @return BlockPluginInterface
*/
public function getBlock($blockId) {
return $this->getBlocks()->get($blockId);
}
/**
* Add a new block.
*
* @param array $configuration
*/
public function addBlock(array $configuration) {
$configuration['uuid'] = $this->uuid->generate();
$this->getBlocks()->addInstanceId($configuration['uuid'], $configuration);
return $configuration['uuid'];
}
/**
* Update an existing blocks configuration.
*
* @param string $blockId
* The ID of the block to update.
*
* @param $configuration
* The updated configuration for the block.
*
* @return $this
*/
public function updateBlock($blockId, array $configuration) {
$existingConfiguration = $this->getBlock($blockId)->getConfiguration();
$this->getBlocks()->setInstanceConfiguration($blockId, $configuration + $existingConfiguration);
return $this;
}
/**
* @param $blockId
* @return $this
*/
public function removeBlock($blockId) {
$this->getBlocks()->removeInstanceId($blockId);
return $this;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL) {
$form['#attached']['library'][] = 'block/drupal.block';
$themes = $this->themeHandler->listInfo();
$default_theme = $this->themeHandler->getDefault();
// Select list for changing themes.
$form['theme'] = [
'#type' => 'select',
'#title' => $this->t('Theme'),
'#options' => [],
'#description' => $this->t('Select the theme you want to display regions for.'),
'#default_value' => $form_state->getValue('theme', $default_theme),
'#ajax' => [
'url' => Url::fromRoute('context.reaction.blocks.regions', [
'context' => $context->id(),
]),
],
];
// Add each theme to the theme select.
foreach ($themes as $theme_id => $theme) {
if ($theme_id === $default_theme) {
$form['theme']['#options'][$theme_id] = $this->t('%theme (Default)', [
'%theme' => $theme->info['name'],
]);
}
else {
$form['theme']['#options'][$theme_id] = $theme->info['name'];
}
}
$form['blocks'] = [
'#type' => 'container',
'#attributes' => [
'id' => 'context-reaction-blocks-container',
],
];
$form['blocks']['include_default_blocks'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include blocks from Block layout'),
'#description' => $this->t('if checked, all blocks from default Block layout will also be included in page build.'),
'#weight' => -10,
'#default_value' => isset($this->getConfiguration()['include_default_blocks']) ? $this->getConfiguration()['include_default_blocks'] : FALSE,
];
$form['blocks']['block_add'] = [
'#type' => 'link',
'#title' => $this->t('Place block'),
'#attributes' => [
'id' => 'context-reaction-blocks-region-add',
] + $this->getAjaxButtonAttributes(),
'#url' => Url::fromRoute('context.reaction.blocks.library', [
'context' => $context->id(),
'reaction_id' => $this->getPluginId(),
], [
'query' => [
'theme' => $form_state->getValue('theme', $default_theme),
],
]),
];
$form['blocks']['blocks'] = [
'#type' => 'table',
'#header' => [
$this->t('Block'),
$this->t('Category'),
$this->t('Unique'),
$this->t('Region'),
$this->t('Weight'),
$this->t('Operations'),
],
'#empty' => $this->t('No regions available to place blocks in.'),
'#attributes' => [
'id' => 'blocks',
],
];
// If a theme has been selected use that to get the regions otherwise use
// the default theme.
$theme = $form_state->getValue('theme', $default_theme);
// Get all blocks by their regions.
$blocks = $this->getBlocks()->getAllByRegion($theme);
// Get regions of the selected theme.
$regions = $this->getSystemRegionList($theme);
// Add each region.
foreach ($regions as $region => $title) {
// Add the tabledrag details for this region.
$form['blocks']['blocks']['#tabledrag'][] = [
'action' => 'match',
'relationship' => 'sibling',
'group' => 'block-region-select',
'subgroup' => 'block-region-' . $region,
'hidden' => FALSE,
];
$form['blocks']['blocks']['#tabledrag'][] = [
'action' => 'order',
'relationship' => 'sibling',
'group' => 'block-weight',
'subgroup' => 'block-weight-' . $region,
];
// Add the theme region.
$form['blocks']['blocks']['region-' . $region] = [
'#attributes' => [
'class' => ['region-title'],
],
'title' => [
'#markup' => $title,
'#wrapper_attributes' => [
'colspan' => 6,
],
],
];
$regionEmptyClass = empty($blocks[$region])
? 'region-empty'
: 'region-populated';
$form['blocks']['blocks']['region-' . $region . '-message'] = [
'#attributes' => [
'class' => ['region-message', 'region-' . $region . '-message', $regionEmptyClass],
],
'message' => [
'#markup' => '<em>' . $this->t('No blocks in this region') . '</em>',
'#wrapper_attributes' => [
'colspan' => 6,
],
],
];
// Add each block specified for the region if there are any.
if (isset($blocks[$region])) {
/** @var BlockPluginInterface $block */
foreach ($blocks[$region] as $block_id => $block) {
$configuration = $block->getConfiguration();
$operations = [
'edit' => [
'title' => $this->t('Edit'),
'url' => Url::fromRoute('context.reaction.blocks.block_edit', [
'context' => $context->id(),
'reaction_id' => $this->getPluginId(),
'block_id' => $block_id,
], [
'query' => [
'theme' => $theme,
],
]),
'attributes' => $this->getAjaxAttributes(),
],
'delete' => [
'title' => $this->t('Delete'),
'url' => Url::fromRoute('context.reaction.blocks.block_delete', [
'context' => $context->id(),
'block_id' => $block_id,
]),
'attributes' => $this->getAjaxAttributes(),
],
];
$form['blocks']['blocks'][$block_id] = [
'#attributes' => [
'class' => ['draggable'],
],
'label' => [
'#markup' => $block->label(),
],
'category' => [
'#markup' => $block->getPluginDefinition()['category'],
],
'unique' => [
'#markup' => $this->blockShouldBePlacedUniquely($block) ? $this->t('Yes') : $this->t('No'),
],
'region' => [
'#type' => 'select',
'#title' => $this->t('Region for @block block', ['@block' => $block->label()]),
'#title_display' => 'invisible',
'#default_value' => $region,
'#options' => $regions,
'#attributes' => [
'class' => ['block-region-select', 'block-region-' . $region],
],
],
'weight' => [
'#type' => 'weight',
'#default_value' => isset($configuration['weight']) ? $configuration['weight'] : 0,
'#title' => $this->t('Weight for @block block', ['@block' => $block->label()]),
'#title_display' => 'invisible',
'#attributes' => [
'class' => ['block-weight', 'block-weight-' . $region],
],
],
'operations' => [
'#type' => 'operations',
'#links' => $operations,
],
];
}
}
}
return $form;
}
/**
* Check to see if the block should be uniquely placed.
*
* @param BlockPluginInterface $block
*
* @return bool
*/
private function blockShouldBePlacedUniquely(BlockPluginInterface $block) {
$configuration = $block->getConfiguration();
return (isset($configuration['unique']) && $configuration['unique']);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$blocks = $form_state->getValue(['blocks', 'blocks'], []);
// Save configuration for including default blocks.
$config = $this->getConfiguration();
$config['include_default_blocks'] = $form_state->getValue(['blocks', 'include_default_blocks'], FALSE);
$this->setConfiguration($config);
if (is_array($blocks)) {
foreach ($blocks as $block_id => $configuration) {
$block = $this->getBlock($block_id);
$configuration += $block->getConfiguration();
$block_state = (new FormState())->setValues($configuration);
$block->submitConfigurationForm($form, $block_state);
// If the block is context aware then add context mapping to the block.
if ($block instanceof ContextAwarePluginInterface) {
$block->setContextMapping($block_state->getValue('context_mapping', []));
}
$this->updateBlock($block_id, $block_state->getValues());
}
}
}
/**
* Should reaction include default blocks from Block layout.
*
* @return bool
*/
public function includeDefaultBlocks() {
$config = $this->getConfiguration();
return isset($config['include_default_blocks']) ? $config['include_default_blocks'] : FALSE;
}
/**
* Wraps system_region_list().
*
* @param string $theme
* The theme to get a list of regions for.
*
* @param string $show
* What type of regions that should be returned, defaults to all regions.
*
* @return array
*
* @todo This could be moved to a service since we use it in a couple of places.
*/
protected function getSystemRegionList($theme, $show = REGIONS_ALL) {
return system_region_list($theme, $show);
}
}
@@ -0,0 +1,66 @@
<?php
namespace Drupal\context\Plugin\ContextReaction;
use Drupal\context\ContextReactionPluginBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a content reaction that adds a new css class.
*
* @ContextReaction(
* id = "body_class",
* label = @Translation("Body class")
* )
*/
class BodyClass extends ContextReactionPluginBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'body_class' => '',
];
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->getConfiguration()['body_class'];
}
/**
* {@inheritdoc}
*/
public function execute(array &$vars = []) {
return [
'class' => explode(' ', $this->getConfiguration()['body_class']),
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['body_class'] = [
'#title' => $this->t('Section class'),
'#type' => 'textfield',
'#description' => $this->t('Provides this text as additional body class in the html.html.twig.'),
'#default_value' => $this->getConfiguration()['body_class'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->setConfiguration([
'body_class' => $form_state->getValue('body_class'),
]);
}
}
@@ -0,0 +1,91 @@
<?php
namespace Drupal\context\Plugin\ContextReaction;
use Drupal\context\ContextReactionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuParentFormSelectorInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a content reaction that adds a css 'active' class to menu item.
*
* @ContextReaction(
* id = "menu",
* label = @Translation("Menu")
* )
*/
class Menu extends ContextReactionPluginBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\Core\Menu\MenuParentFormSelector
*/
protected $menuParentFormSelector;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuParentFormSelectorInterface $menu_parent_form_selector) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->menuParentFormSelector = $menu_parent_form_selector;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu.parent_form_selector')
);
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Set active menu item based on conditions.');
}
/**
* {@inheritdoc}
*/
public function execute(array &$vars = []) {
$config = $this->getConfiguration();
return $config['menu'];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$parent_element = $this->menuParentFormSelector->parentSelectElement('main:');
$config = $this->getConfiguration();
$form['menu_items'] = [
'#title' => $this->t('Menu'),
'#type' => 'select',
'#options' => $parent_element['#options'],
'#multiple' => TRUE,
'#default_value' => isset($config['menu']) ? $config['menu'] : '',
'#size' => 15,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$values = array_keys($form_state->getValue('menu_items'));
$this->setConfiguration([
'menu' => $values,
]);
}
}
@@ -0,0 +1,55 @@
<?php
namespace Drupal\context\Plugin\ContextReaction;
use Drupal\context\ContextReactionPluginBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a content reaction that will let you change theme.
*
* @ContextReaction(
* id = "page_template_suggestions",
* label = @Translation("Page template suggestions")
* )
*/
class PageTemplateSuggestions extends ContextReactionPluginBase {
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Gives you ability to add template suggestions.');
}
/**
* Executes the plugin.
*/
public function execute() {
$config = $this->getConfiguration();
return $config['suggestions'];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$config = $this->getConfiguration();
$form['suggestions'] = [
'#type' => 'textarea',
'#title' => $this->t('Page template suggestions'),
'#default_value' => isset($config['suggestions']) ? $config['suggestions'] : '',
'#description' => $this->t('Enter page template suggestions such as "page__front", one per line, in order of preference (using underscores instead of hyphens). Entered template suggestions will override page.html.twig template.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$config['suggestions'] = str_replace("\r\n", "\n", $form_state->getValue('suggestions'));
$this->setConfiguration($config);
}
}
@@ -0,0 +1,170 @@
<?php
namespace Drupal\context\Plugin\ContextReaction;
use Drupal\context\ContextReactionPluginBase;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a content reaction that will let you disable regions.
*
* @ContextReaction(
* id = "regions",
* label = @Translation("Regions")
* )
*/
class Regions extends ContextReactionPluginBase implements ContainerFactoryPluginInterface {
/**
* An array of regions to be disabled with this reaction.
*
* @var array
*/
protected $regions = [];
/**
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* {@inheritdoc}
*/
function __construct(
array $configuration,
$pluginId,
$pluginDefinition,
ThemeManagerInterface $themeManager,
ThemeHandlerInterface $themeHandler
) {
parent::__construct($configuration, $pluginId, $pluginDefinition);
$this->themeManager = $themeManager;
$this->themeHandler = $themeHandler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
return new static(
$configuration,
$pluginId,
$pluginDefinition,
$container->get('theme.manager'),
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Lets you remove regions from selected theme.');
}
/**
* Executes the plugin.
*/
public function execute() {
// TODO: Implement execute() method.
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$themes = $this->themeHandler->listInfo();
$default_theme = $this->themeHandler->getDefault();
// Build configuration form for each installed theme.
foreach ($themes as $theme_id => $theme) {
if ($theme_id == $default_theme) {
$title = $this->t('Disable Regions in %theme (Default)', [
'%theme' => $theme->info['name'],
]);
}
else {
$title = $this->t('Disable Regions in %theme', [
'%theme' => $theme->info['name'],
]);
}
$form[$theme_id] = [
'#type' => 'details',
'#title' => $title,
'#weight' => 5,
'#open' => FALSE,
];
// Get regions of the theme.
$regions = $this->getSystemRegionList($theme_id);
// Get disabled regions.
$disabled_regions = $this->getDisabledRegions();
$form[$theme_id]['regions'] = [
'#type' => 'checkboxes',
'#options' => $regions,
'#title' => $this->t('Disable the following'),
'#default_value' => isset($disabled_regions[$theme_id]) ? $disabled_regions[$theme_id] : [],
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$themes = $form_state->getValues();
if (is_array($themes)) {
foreach ($themes as $theme_name => $region) {
$disabled_regions = array_keys(array_filter($region['regions']));
if (!empty($disabled_regions)) {
$configuration['regions'][$theme_name] = $disabled_regions;
$configuration += $this->getConfiguration();
}
else {
$configuration['regions'][$theme_name] = [];
$configuration += $this->getConfiguration();
}
$this->setConfiguration($configuration);
}
}
}
/**
* Wraps system_region_list().
*
* @param string $theme
* The theme to get a list of regions for.
*
* @param string $show
* What type of regions that should be returned, defaults to all regions.
*
* @return array
*
* @todo This could be moved to a service since we use it in a couple of places.
*/
protected function getSystemRegionList($theme, $show = REGIONS_ALL) {
return system_region_list($theme, $show);
}
/**
* Get disabled regions.
*/
protected function getDisabledRegions() {
$configurations = $this->getConfiguration();
return isset($configurations['regions']) ? $configurations['regions'] : [];
}
}
@@ -0,0 +1,105 @@
<?php
namespace Drupal\context\Plugin\ContextReaction;
use Drupal\context\ContextReactionPluginBase;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a content reaction that will let you change theme.
*
* @ContextReaction(
* id = "theme",
* label = @Translation("Theme")
* )
*/
class Theme extends ContextReactionPluginBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* {@inheritdoc}
*/
function __construct(
array $configuration,
$pluginId,
$pluginDefinition,
ThemeManagerInterface $themeManager,
ThemeHandlerInterface $themeHandler
) {
parent::__construct($configuration, $pluginId, $pluginDefinition);
$this->themeManager = $themeManager;
$this->themeHandler = $themeHandler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
return new static(
$configuration,
$pluginId,
$pluginDefinition,
$container->get('theme.manager'),
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Gives you ability to change theme.');
}
/**
* Executes the plugin.
*/
public function execute() {
// TODO: Implement execute() method.
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$themes = $this->themeHandler->listInfo();
$default_theme = $this->themeHandler->getDefault();
$theme_options = [];
foreach ($themes as $theme_id => $theme) {
$theme_options[$theme_id] = $theme->info['name'];
}
$configuration = $this->getConfiguration();
$form['theme'] = [
'#type' => 'radios',
'#options' => $theme_options,
'#title' => $this->t('Select theme'),
'#default_value' => isset($configuration['theme']) ? $configuration['theme'] : $default_theme,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$configuration['theme'] = $form_state->getValue('theme');
$configuration += $this->getConfiguration();
$this->setConfiguration($configuration);
}
}
@@ -0,0 +1,17 @@
<?php
namespace Drupal\context\Plugin;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
class ContextReactionPluginCollection extends DefaultLazyPluginCollection {
/**
* {@inheritdoc}
*
* @return \Drupal\context\ContextReactionInterface
*/
public function &get($instance_id) {
return parent::get($instance_id);
}
}
@@ -0,0 +1,133 @@
<?php
namespace Drupal\context\Plugin\DisplayVariant;
use Drupal\Component\Utility\NestedArray;
use Drupal\context\ContextManager;
use Drupal\Core\Display\VariantBase;
use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Display\VariantManager;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a page display variant that decorates the main content with blocks.
*
* @see \Drupal\Core\Block\MainContentBlockPluginInterface
* @see \Drupal\Core\Block\MessagesBlockPluginInterface
*
* @PageDisplayVariant(
* id = "context_block_page",
* admin_label = @Translation("Page with blocks")
* )
*/
class ContextBlockPageVariant extends VariantBase implements PageVariantInterface, ContainerFactoryPluginInterface {
/**
* @var ContextManager
*/
protected $contextManager;
/**
* The render array representing the main page content.
*
* @var array
*/
protected $mainContent = [];
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/**
* Constructs a new ContextBlockPageVariant.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
*
* @param string $plugin_id
* The plugin ID for the plugin instance.
*
* @param mixed $plugin_definition
* The plugin implementation definition.
*
* @param ContextManager $contextManager
* The context module manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ContextManager $contextManager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->contextManager = $contextManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('context.manager')
);
}
/**
* {@inheritdoc}
*/
public function setMainContent(array $main_content) {
$this->mainContent = $main_content;
return $this;
}
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* {@inheritdoc}
*/
public function build() {
$build = [
'#cache' => [
'tags' => ['context_block_page', $this->getPluginId()],
],
];
// Place main content block, it will be removed by the reactions if a main
// content block has been manually placed.
$build['content']['system_main'] = $this->mainContent;
// Execute each block reaction and let them modify the page build.
foreach ($this->contextManager->getActiveReactions('blocks') as $reaction) {
$build = $reaction->execute($build, $this->title, $this->mainContent);
}
// Execute each block reaction and check if default block should be included in page build.
foreach ($this->contextManager->getActiveReactions('blocks') as $reaction) {
if ($reaction->includeDefaultBlocks()) {
$build = NestedArray::mergeDeep($this->getBuildFromBlockLayout(), $build);
return $build;
}
}
return $build;
}
/**
* Get build from Block layout.
*/
private function getBuildFromBlockLayout() {
$plugin_manager = \Drupal::service('plugin.manager.display_variant');
$display_variant = $plugin_manager->createInstance('block_page', $plugin_manager->getDefinition('block_page'));
$display_variant->setTitle($this->title);
return $display_variant->build();
}
}
@@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\context\Reaction\Annotation\ContextReaction.
*/
namespace Drupal\context\Reaction\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines an context reaction annotation object.
*
* Plugin Namespace: Plugin\ContextReaction
*
* @Annotation
*/
class ContextReaction extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the context reaction.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* A brief description of the context reaction.
*
* This will be shown when adding or configuring this context reaction.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation (optional)
*/
public $description = '';
}
@@ -0,0 +1,69 @@
<?php
namespace Drupal\context\Reaction\Blocks;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
class BlockCollection extends DefaultLazyPluginCollection {
/**
* {@inheritdoc}
*
* @return BlockPluginInterface
*/
public function &get($instance_id) {
return parent::get($instance_id);
}
/**
* Returns all blocks keyed by their region. Base code from the ctools block
* plugin collection.
*
* @param string $theme
* The theme to get blocks for.
*
* @return BlockPluginInterface[]
* An associative array keyed by region, containing an associative array of
* block plugins.
*/
public function getAllByRegion($theme) {
$region_assignments = [];
/** @var BlockPluginInterface[] $this */
foreach ($this as $block_id => $block) {
$configuration = $block->getConfiguration();
if ($configuration['theme'] !== $theme) {
continue;
}
$region = isset($configuration['region'])
? $configuration['region']
: NULL;
$region_assignments[$region][$block_id] = $block;
}
foreach ($region_assignments as $region => $region_assignment) {
// @todo Determine the reason this needs error suppression.
@uasort($region_assignment, function (BlockPluginInterface $a, BlockPluginInterface $b) {
$a_config = $a->getConfiguration();
$a_weight = isset($a_config['weight']) ? $a_config['weight'] : 0;
$b_config = $b->getConfiguration();
$b_weight = isset($b_config['weight']) ? $b_config['weight'] : 0;
if ($a_weight == $b_weight) {
return strcmp($a->label(), $b->label());
}
return $a_weight > $b_weight ? 1 : -1;
});
$region_assignments[$region] = $region_assignment;
}
return $region_assignments;
}
}
@@ -0,0 +1,210 @@
<?php
namespace Drupal\context\Reaction\Blocks\Controller;
use Drupal\context\ContextManager;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Url;
use Drupal\context\ContextInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
class ContextReactionBlocksController extends ControllerBase {
/**
* @var \Drupal\Core\Block\BlockManagerInterface
*/
protected $blockManager;
/**
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
protected $contextRepository;
/**
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* @var \Drupal\context\ContextManager
*/
protected $contextManager;
/**
* Construct.
*
* @param \Drupal\Core\Block\BlockManagerInterface $blockManager
* @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $contextRepository
* @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
* @param \Drupal\context\ContextManager $contextManager
*/
function __construct(
BlockManagerInterface $blockManager,
ContextRepositoryInterface $contextRepository,
ThemeHandlerInterface $themeHandler,
ContextManager $contextManager
) {
$this->blockManager = $blockManager;
$this->contextRepository = $contextRepository;
$this->themeHandler = $themeHandler;
$this->contextManager = $contextManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block'),
$container->get('context.repository'),
$container->get('theme_handler'),
$container->get('context.manager')
);
}
/**
* Display a library of blocks that can be added to the context reaction.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @param \Drupal\context\ContextInterface $context
* The context the blocks reaction belongs to.
*
* @param string $reaction_id
* The ID of the blocks reaction that the selected block
* should be added to.
*
* @return array
*/
public function blocksLibrary(Request $request, ContextInterface $context, $reaction_id) {
// If a theme has been defined in the query string then use this for
// the add block link, default back to the default theme.
$theme = $request->query->get('theme', $this->themeHandler->getDefault());
// Only add blocks which work without any available context.
$blocks = $this->blockManager->getDefinitionsForContexts($this->contextRepository->getAvailableContexts());
// Order by category, and then by admin label.
$blocks = $this->blockManager->getSortedDefinitions($blocks);
$build['filter'] = [
'#type' => 'search',
'#title' => $this->t('Filter'),
'#title_display' => 'invisible',
'#size' => 30,
'#placeholder' => $this->t('Filter by block name'),
'#attributes' => [
'class' => ['context-table-filter'],
'data-element' => '.block-add-table',
'title' => $this->t('Enter a part of the block name to filter by.'),
],
];
$headers = [
$this->t('Block'),
$this->t('Category'),
$this->t('Operations'),
];
$build['blocks'] = [
'#type' => 'table',
'#header' => $headers,
'#rows' => [],
'#empty' => $this->t('No blocks available.'),
'#attributes' => [
'class' => ['block-add-table'],
],
];
// Add each block definition to the table.
foreach ($blocks as $block_id => $block) {
$links = [
'add' => [
'title' => $this->t('Place block'),
'url' => Url::fromRoute('context.reaction.blocks.block_add', [
'context' => $context->id(),
'reaction_id' => $reaction_id,
'block_id' => $block_id,
], [
'query' => [
'theme' => $theme,
],
]),
'attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 700,
]),
],
],
];
$build['blocks']['#rows'][] = [
'title' => [
'data' => [
'#type' => 'inline_template',
'#template' => '<div class="context-table-filter-text-source">{{ label }}</div>',
'#context' => [
'label' => $block['admin_label'],
],
],
],
'category' => [
'data' => $block['category'],
],
'operations' => [
'data' => [
'#type' => 'operations',
'#links' => $links,
],
],
];
}
$build['#attached']['library'][] = 'context_ui/admin';
return $build;
}
/**
* Callback for the theme select list on the Context blocks reaction form.
*
* @param Request $request
* The current request.
*
* @param ContextInterface $context
* The context the block reaction is located on.
*
* @return \Drupal\Core\Ajax\AjaxResponse
*/
public function blocksFormThemeSelect(Request $request, ContextInterface $context) {
$theme = $request->request->get('reactions[blocks][theme]', '', TRUE);
// Get the context form and supply it with the blocks theme value.
$form = $this->contextManager->getForm($context, 'edit', [
'reactions' => [
'blocks' => [
'theme' => $theme,
],
],
]);
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('#context-reactions', $form['reactions']));
return $response;
}
}
@@ -0,0 +1,30 @@
<?php
namespace Drupal\context\Reaction\Blocks\Form;
use Drupal\Core\StringTranslation\TranslatableMarkup;
class BlockAddForm extends BlockFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'context_reaction_blocks_add_block_form';
}
/**
* {@inheritdoc}
*/
protected function getSubmitValue() {
return $this->t('Add block');
}
/**
* {@inheritdoc}
*/
protected function prepareBlock($block_id) {
return $this->blockManager->createInstance($block_id);
}
}
@@ -0,0 +1,151 @@
<?php
namespace Drupal\context\Reaction\Blocks\Form;
use Drupal\context\ContextInterface;
use Drupal\context\ContextManager;
use Drupal\context\Plugin\ContextReaction\Blocks;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class BlockDeleteForm extends ConfirmFormBase {
/**
* The context that the block is being removed from.
*
* @var ContextInterface
*/
protected $context;
/**
* The blocks reaction.
*
* @var Blocks
*/
protected $reaction;
/**
* The block that is being removed.
*
* @var BlockPluginInterface
*/
protected $block;
/**
* The Context module context manager.
*
* @var \Drupal\context\ContextManager
*/
protected $contextManager;
/**
* Construct a condition delete form.
*
* @param ContextManager $contextManager
*/
public function __construct(ContextManager $contextManager) {
$this->contextManager = $contextManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static (
$container->get('context.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'context_reaction_blocks_delete_block_form';
}
/**
* Returns the question to ask the user.
*
* @return string
* The form question. The page title will be set to this value.
*/
public function getQuestion() {
return $this->t('Are you sure you want to remove the %label block?', [
'%label' => $this->block->getConfiguration()['label'],
]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return $this->context->urlInfo();
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL, $block_id = NULL) {
$this->context = $context;
$this->reaction = $this->context->getReaction('blocks');
$this->block = $this->reaction->getBlock($block_id);
$form = parent::buildForm($form, $form_state);
// Remove the cancel button if this is an AJAX request since Drupals built
// in modal dialogues does not handle buttons that are not a primary
// button very well.
if ($this->getRequest()->isXmlHttpRequest()) {
unset($form['actions']['cancel']);
}
// Submit the form with AJAX if possible.
$form['actions']['submit']['#ajax'] = [
'callback' => '::submitFormAjax'
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$configuration = $this->block->getConfiguration();
$this->reaction->removeBlock($configuration['uuid']);
$this->context->save();
// If this is not an AJAX request then redirect and show a message.
if (!$this->getRequest()->isXmlHttpRequest()) {
drupal_set_message($this->t('The %label block has been removed.', [
'%label' => $configuration['label']]
));
$form_state->setRedirectUrl($this->getCancelUrl());
}
}
/**
* Handle when the form is submitted trough AJAX.
*
* @return AjaxResponse
*/
public function submitFormAjax() {
$contextForm = $this->contextManager->getForm($this->context, 'edit');
$response = new AjaxResponse();
$response->addCommand(new CloseModalDialogCommand());
$response->addCommand(new ReplaceCommand('#context-reactions', $contextForm['reactions']));
return $response;
}
}
@@ -0,0 +1,28 @@
<?php
namespace Drupal\context\Reaction\Blocks\Form;
class BlockEditForm extends BlockFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'context_reaction_blocks_edit_block_form';
}
/**
* {@inheritdoc}
*/
protected function getSubmitValue() {
return $this->t('Update block');
}
/**
* {@inheritdoc}
*/
protected function prepareBlock($block_id) {
return $this->reaction->getBlock($block_id);
}
}
@@ -0,0 +1,343 @@
<?php
namespace Drupal\context\Reaction\Blocks\Form;
use Drupal\context\ContextManager;
use Drupal\context\ContextReactionManager;
use Drupal\context\Form\AjaxFormTrait;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormState;
use Drupal\context\ContextInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Render\Element\StatusMessages;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
abstract class BlockFormBase extends FormBase {
use AjaxFormTrait;
/**
* The plugin being configured.
*
* @var \Drupal\Core\Block\BlockPluginInterface
*/
protected $block;
/**
* The context entity the reaction belongs to.
*
* @var ContextInterface
*/
protected $context;
/**
* The blocks reaction this block should be added to.
*
* @var \Drupal\context\Plugin\ContextReaction\Blocks
*/
protected $reaction;
/**
* The block manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $blockManager;
/**
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
protected $contextRepository;
/**
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* @var \Drupal\context\ContextReactionManager
*/
protected $contextReactionManager;
/**
* @var \Drupal\context\ContextManager
*/
protected $contextManager;
/**
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Constructs a new VariantPluginFormBase.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $block_manager
* The block manager.
*
* @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $contextRepository
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
* @param \Drupal\Core\Form\FormBuilderInterface $formBuilder
* @param \Drupal\context\ContextReactionManager $contextReactionManager
* @param \Drupal\context\ContextManager $contextManager
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
*/
public function __construct(
PluginManagerInterface $block_manager,
ContextRepositoryInterface $contextRepository,
ThemeHandlerInterface $themeHandler,
FormBuilderInterface $formBuilder,
ContextReactionManager $contextReactionManager,
ContextManager $contextManager,
RequestStack $requestStack
)
{
$this->blockManager = $block_manager;
$this->contextRepository = $contextRepository;
$this->themeHandler = $themeHandler;
$this->formBuilder = $formBuilder;
$this->contextReactionManager = $contextReactionManager;
$this->contextManager = $contextManager;
$this->request = $requestStack->getCurrentRequest();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block'),
$container->get('context.repository'),
$container->get('theme_handler'),
$container->get('form_builder'),
$container->get('plugin.manager.context_reaction'),
$container->get('context.manager'),
$container->get('request_stack')
);
}
/**
* Prepares the block plugin based on the block ID.
*
* @param string $block_id
* Either a block ID, or the plugin ID used to create a new block.
*
* @return \Drupal\Core\Block\BlockPluginInterface
* The block plugin.
*/
abstract protected function prepareBlock($block_id);
/**
* Get the value to use for the submit button.
*
* @return TranslatableMarkup
*/
abstract protected function getSubmitValue();
/**
* Form constructor.
*
* @param array $form
* An associative array containing the structure of the form.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @param ContextInterface $context
* The context the reaction belongs to.
*
* @param string|null $reaction_id
* The ID of the blocks reaction the block should be added to.
*
* @param string|null $block_id
* The ID of the block to show a configuration form for.
*
* @return array
*/
public function buildForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL, $reaction_id = NULL, $block_id = NULL) {
$this->context = $context;
$this->reaction = $this->context->getReaction($reaction_id);
$this->block = $this->prepareBlock($block_id);
// If a theme was defined in the query use this theme for the block
// otherwise use the default theme.
$theme = $this->getRequest()->query->get('theme', $this->themeHandler->getDefault());
// Some blocks require the theme name in the form state like Site Branding
$form_state->set('block_theme', $theme);
// Some blocks require contexts, set a temporary value with gathered
// contextual values.
$form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
$configuration = $this->block->getConfiguration();
$form['#tree'] = TRUE;
$form['settings'] = $this->block->buildConfigurationForm([], $form_state);
$form['settings']['id'] = [
'#type' => 'value',
'#value' => $this->block->getPluginId(),
];
$form['region'] = [
'#type' => 'select',
'#title' => $this->t('Region'),
'#description' => $this->t('Select the region where this block should be displayed.'),
'#options' => $this->getThemeRegionOptions($theme),
'#default_value' => isset($configuration['region']) ? $configuration['region'] : '',
];
$form['unique'] = [
'#type' => 'checkbox',
'#title' => $this->t('Unique'),
'#description' => $this->t('Check if the block should be uniquely placed, this means that the block can not be overridden by other blocks of the same type in the selected region.'),
'#default_value' => isset($configuration['unique']) ? $configuration['unique'] : FALSE,
];
$form['theme'] = [
'#type' => 'value',
'#value' => $theme,
];
$form['css_class'] = [
'#type' => 'textfield',
'#title' => $this->t('Block Class'),
'#default_value' => isset($configuration['css_class']) ? $configuration['css_class'] : '',
];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->getSubmitValue(),
'#button_type' => 'primary',
'#ajax' => [
'callback' => '::submitFormAjax'
],
];
// Remove ajax from submit, if this is not ajax request.
if (!$this->request->isXmlHttpRequest()) {
unset($form['actions']['submit']['#ajax']);
}
// Disable cache on form to prevent ajax forms from failing.
$form_state->disableCache();
return $form;
}
/**
* Form submission handler.
*
* @param array $form
* An associative array containing the structure of the form.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$settings = (new FormState())->setValues($form_state->getValue('settings'));
// Call the plugin submit handler.
$this->block->submitConfigurationForm($form, $settings);
// Update the original form values.
$form_state->setValue('settings', $settings->getValues());
// Add available contexts if this is a context aware block.
if ($this->block instanceof ContextAwarePluginInterface) {
$this->block->setContextMapping($form_state->getValue(['settings', 'context_mapping'], []));
}
$configuration = array_merge($this->block->getConfiguration(), [
'region' => $form_state->getValue('region'),
'theme' => $form_state->getValue('theme'),
'css_class' => $form_state->getValue('css_class'),
'unique' => $form_state->getValue('unique'),
'context_id' => $this->context->id(),
]);
// Add/Update the block.
if (!isset($configuration['uuid'])) {
$this->reaction->addBlock($configuration);
} else {
$this->reaction->updateBlock($configuration['uuid'], $configuration);
}
$this->context->save();
$form_state->setRedirectUrl(Url::fromRoute('entity.context.edit_form', [
'context' => $this->context->id(),
]));
}
/**
* Handle when the form is submitted trough AJAX.
*
* @return AjaxResponse
*/
public function submitFormAjax(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
if ($form_state->getErrors()) {
$messages = StatusMessages::renderMessages(NULL);
$output[] = $messages;
$output[] = $form;
$form_class = '.' . str_replace('_', '-', $form_state->getFormObject()->getFormId()) ;
// Remove any previously added error messages.
$response->addCommand(new RemoveCommand('#drupal-modal .messages--error'));
// Replace old form with new one and with error message.
$response->addCommand(new ReplaceCommand($form_class, $output));
}
else {
$form = $this->contextManager->getForm($this->context, 'edit');
$response->addCommand(new CloseModalDialogCommand());
$response->addCommand(new ReplaceCommand('#context-reactions', $form['reactions']));
}
return $response;
}
/**
* Get a list of regions for the select list.
*
* @param string $theme
* The theme to get a list of regions for.
*
* @param string $show
* What type of regions that should be returned, defaults to all regions.
*
* @return array
*/
protected function getThemeRegionOptions($theme, $show = REGIONS_ALL) {
$regions = system_region_list($theme, $show);
foreach ($regions as $region => $title) {
$regions[$region] = $title;
}
return $regions;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\context\Reaction;
use Drupal\context\ContextInterface;
use Drupal\context\ContextReactionInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
abstract class ContextReactionFormBase extends FormBase {
/**
* @var ContextInterface
*/
protected $context;
/**
* @var ContextReactionInterface
*/
protected $reaction;
/**
* Form constructor.
*
* @param array $form
* An associative array containing the structure of the form.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @param \Drupal\context\ContextInterface $context
* The context that contains the reaction.
*
* @param $reaction_id
* The id of the reaction that is being configured.
*
* @return array The form structure.
* The form structure.
*/
public function buildForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL, $reaction_id = NULL) {
$this->context = $context;
$this->reaction = $this->context->getReaction($reaction_id);
$form['reaction'] = [
'#tree' => TRUE,
];
$form['actions'] = [
'#type' => 'actions'
];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->context->save();
}
}
@@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\context\Theme\ThemeSwitcherNegotiator.
*/
namespace Drupal\context\Theme;
use Drupal\context\Plugin\ContextReaction\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
class ThemeSwitcherNegotiator implements ThemeNegotiatorInterface {
/**
* @var string
*/
protected $theme;
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
$context_manager = \Drupal::service('context.manager');
// If there is no Theme reaction set, do not try to get active reactions,
// since this causes infinite loop.
$theme_reaction = FALSE;
foreach ($context_manager->getContexts() as $context) {
foreach ($context->getReactions() as $reaction) {
if ($reaction instanceof Theme) {
$theme_reaction = TRUE;
break;
}
}
}
if ($theme_reaction) {
foreach($context_manager->getActiveReactions('theme') as $theme_reaction) {
$configuration = $theme_reaction->getConfiguration();
$this->theme = $configuration['theme'];
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
return $this->theme;
}
}