Drupal blocks can be selectively shown or hidden based on different criteria.
In core, these are Content types, Roles, Pages, and Language.
In order to add new criteria, you previously had to implement hook_form_block_form_alter() and hook_block_access() to add visibility settings.
However, if you were to accidentally return TRUE; from your hook_block_access() implementation, you would bypass all other access checks.
Now in Drupal 8, we have a new Conditions Plugin system, which allows modules to provide generic conditions for anything, not just block visibility.
Conditions can specify sets of required "context" that must be satisfied.
For example, the "Content types" condition requires a Node object.
In order to allow the block system to satisfy these contexts, a new Event was created:
Drupal\block\Event\BlockEvents::CONDITION_CONTEXT
A base class is provided: \Drupal\block\EventSubscriber\BlockContextSubscriberBase
After extending the base class, implement the determineBlockContext() method, and call $this->addContext('name', $context);
All of the added contexts will be gathered and provided to all conditions.
In Drupal 7, you would need to maintain a separate DB table. One example is the node.module's {block_node_type} table:
function node_form_block_admin_configure_alter(&$form, &$form_state) {
$default_type_options = db_query("SELECT type FROM {block_node_type} WHERE module = :module AND delta = :delta", array(
':module' => $form['module']['#value'],
':delta' => $form['delta']['#value'],
))->fetchCol();
$form['visibility']['node_type'] = array(
'#type' => 'fieldset',
'#title' => t('Content types'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'visibility',
'#weight' => 5,
);
$form['visibility']['node_type']['types'] = array(
'#type' => 'checkboxes',
'#title' => t('Show block for specific content types'),
'#default_value' => $default_type_options,
'#options' => node_type_get_names(),
'#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
);
$form['#submit'][] = 'node_form_block_admin_configure_submit';
}
function node_form_block_admin_configure_submit($form, &$form_state) {
db_delete('block_node_type')
->condition('module', $form_state['values']['module'])
->condition('delta', $form_state['values']['delta'])
->execute();
$query = db_insert('block_node_type')->fields(array('type', 'module', 'delta'));
foreach (array_filter($form_state['values']['types']) as $type) {
$query->values(array(
'type' => $type,
'module' => $form_state['values']['module'],
'delta' => $form_state['values']['delta'],
));
}
$query->execute();
}
function node_block_list_alter(&$blocks) {
global $theme_key;
// Build an array of node types for each block.
$block_node_types = array();
$result = db_query('SELECT module, delta, type FROM {block_node_type}');
foreach ($result as $record) {
$block_node_types[$record->module][$record->delta][$record->type] = TRUE;
}
$node = menu_get_object();
$node_types = node_type_get_types();
if (arg(0) == 'node' && arg(1) == 'add' && arg(2)) {
$node_add_arg = strtr(arg(2), '-', '_');
}
foreach ($blocks as $key => $block) {
if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
// This block was added by a contrib module, leave it in the list.
continue;
}
// If a block has no node types associated, it is displayed for every type.
// For blocks with node types associated, if the node type does not match
// the settings from this block, remove it from the block list.
if (isset($block_node_types[$block->module][$block->delta])) {
if (!empty($node)) {
// This is a node or node edit page.
if (!isset($block_node_types[$block->module][$block->delta][$node->type])) {
// This block should not be displayed for this node type.
unset($blocks[$key]);
continue;
}
}
elseif (isset($node_add_arg) && isset($node_types[$node_add_arg])) {
// This is a node creation page
if (!isset($block_node_types[$block->module][$block->delta][$node_add_arg])) {
// This block should not be displayed for this node type.
unset($blocks[$key]);
continue;
}
}
else {
// This is not a node page, remove the block.
unset($blocks[$key]);
continue;
}
}
}
}
This is now replaced by a condition and a context subscriber:
namespace Drupal\node\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
/**
* Provides a 'Node Type' condition.
*
* @Condition(
* id = "node_type",
* label = @Translation("Node Bundle"),
* context = {
* "node" = {
* "type" = "entity:node"
* }
* }
* )
*/
class NodeType extends ConditionPluginBase {
public function buildConfigurationForm(array $form, array &$form_state) {
$options = array();
foreach (node_type_get_types() as $type) {
$options[$type->type] = $type->name;
}
$form['bundles'] = array(
'#title' => t('Node types'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->configuration['bundles'],
);
return parent::buildConfigurationForm($form, $form_state);
}
public function submitConfigurationForm(array &$form, array &$form_state) {
$this->configuration['bundles'] = array_filter($form_state['values']['bundles']);
parent::submitConfigurationForm($form, $form_state);
}
public function evaluate() {
if (empty($this->configuration['bundles']) && !$this->isNegated()) {
return TRUE;
}
$node = $this->getContextValue('node');
return !empty($this->configuration['bundles'][$node->getType()]);
}
}
namespace Drupal\block\EventSubscriber;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\Entity\Node;
class NodeRouteContext extends BlockConditionContextSubscriberBase {
protected $routeMatch;
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
protected function determineBlockContext() {
if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) {
$context = new Context(new ContextDefinition($route_contexts['node']['type']));
if ($node = $this->routeMatch->getParameter('node')) {
$context->setContextValue($node);
}
$this->addContext('node', $context);
}
elseif ($this->routeMatch->getRouteName() == 'node.add') {
$node_type = $this->routeMatch->getParameter('node_type');
$context = new Context(new ContextDefinition('entity:node'));
$context->setContextValue(Node::create(array('type' => $node_type->id())));
$this->addContext('node', $context);
}
}
}