/** * */ protected function applyContexts(ConditionPluginCollection &$conditions, $logic) { $have_1_testable_condition = FALSE; foreach ($conditions as $id => $condition) { if ($condition instanceof ContextAwarePluginInterface) { try { $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping())); $this->contextHandler->applyContextMapping($condition, $contexts); $have_1_testable_condition = TRUE; } catch (ContextException $e) { if ($logic == 'and') { // Logic is all and found condition with contextException. return FALSE; } $conditions->removeInstanceId($id); } } else { $have_1_testable_condition = TRUE; } } if ($logic == 'or' && !$have_1_testable_condition) { return FALSE; } return TRUE; }
/** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $entity = $this->entity; // Store theme settings in $form_state for use below. if (!($theme = $entity->getTheme())) { $theme = $this->config('system.theme')->get('default'); } $form_state->set('block_theme', $theme); // Store the gathered contexts in the form state for other objects to use // during form building. $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); $form['#tree'] = TRUE; $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state); $form['visibility'] = $this->buildVisibilityInterface([], $form_state); // If creating a new block, calculate a safe default machine name. $form['id'] = array('#type' => 'machine_name', '#maxlength' => 64, '#description' => $this->t('A unique name for this block instance. Must be alpha-numeric and underscore separated.'), '#default_value' => !$entity->isNew() ? $entity->id() : $this->getUniqueMachineName($entity), '#machine_name' => array('exists' => '\\Drupal\\block\\Entity\\Block::load', 'replace_pattern' => '[^a-z0-9_.]+', 'source' => array('settings', 'label')), '#required' => TRUE, '#disabled' => !$entity->isNew()); // Theme settings. if ($entity->getTheme()) { $form['theme'] = array('#type' => 'value', '#value' => $theme); } else { $theme_options = array(); foreach ($this->themeHandler->listInfo() as $theme_name => $theme_info) { if (!empty($theme_info->status)) { $theme_options[$theme_name] = $theme_info->info['name']; } } $form['theme'] = array('#type' => 'select', '#options' => $theme_options, '#title' => t('Theme'), '#default_value' => $theme, '#ajax' => array('callback' => '::themeSwitch', 'wrapper' => 'edit-block-region-wrapper')); } // Region settings. $entity_region = $entity->getRegion(); $region = $entity->isNew() ? $this->getRequest()->query->get('region', $entity_region) : $entity_region; $form['region'] = array('#type' => 'select', '#title' => $this->t('Region'), '#description' => $this->t('Select the region where this block should be displayed.'), '#default_value' => $region, '#empty_value' => BlockInterface::BLOCK_REGION_NONE, '#options' => system_region_list($theme, REGIONS_VISIBLE), '#prefix' => '<div id="edit-block-region-wrapper">', '#suffix' => '</div>'); $form['#attached']['library'][] = 'block/drupal.block.admin'; return $form; }
/** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { /** @var \Drupal\block\BlockInterface $entity */ if ($operation != 'view') { return parent::checkAccess($entity, $operation, $account); } // Don't grant access to disabled blocks. if (!$entity->status()) { return AccessResult::forbidden()->addCacheableDependency($entity); } else { $conditions = []; $missing_context = FALSE; foreach ($entity->getVisibilityConditions() as $condition_id => $condition) { if ($condition instanceof ContextAwarePluginInterface) { try { $contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping())); $this->contextHandler->applyContextMapping($condition, $contexts); } catch (ContextException $e) { $missing_context = TRUE; } } $conditions[$condition_id] = $condition; } if ($missing_context) { // If any context is missing then we might be missing cacheable // metadata, and don't know based on what conditions the block is // accessible or not. For example, blocks that have a node type // condition will have a missing context on any non-node route like the // frontpage. // @todo Avoid setting max-age 0 for some or all cases, for example by // treating available contexts without value differently in // https://www.drupal.org/node/2521956. $access = AccessResult::forbidden()->setCacheMaxAge(0); } elseif ($this->resolveConditions($conditions, 'and') !== FALSE) { // Delegate to the plugin. $block_plugin = $entity->getPlugin(); try { if ($block_plugin instanceof ContextAwarePluginInterface) { $contexts = $this->contextRepository->getRuntimeContexts(array_values($block_plugin->getContextMapping())); $this->contextHandler->applyContextMapping($block_plugin, $contexts); } $access = $block_plugin->access($account, TRUE); } catch (ContextException $e) { // Setting access to forbidden if any context is missing for the same // reasons as with conditions (described in the comment above). // @todo Avoid setting max-age 0 for some or all cases, for example by // treating available contexts without value differently in // https://www.drupal.org/node/2521956. $access = AccessResult::forbidden()->setCacheMaxAge(0); } } else { $access = AccessResult::forbidden(); } $this->mergeCacheabilityFromConditions($access, $conditions); // Ensure that access is evaluated again when the block changes. return $access->addCacheableDependency($entity); } }
/** * Get a list of blocks that can be placed in a mega menu. * * @param Request $request * @param MegaMenuInterface $mega_menu * @return array */ public function blockLibrary(Request $request, MegaMenuInterface $mega_menu) { // Get the query parameters needed. $link = $request->query->get('link'); $region = $request->query->get('region'); // 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' => ['block-filter-text'], '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('mega_menu.block_add', ['mega_menu' => $mega_menu->id(), 'block_id' => $block_id], ['query' => ['link' => $link, 'region' => $region]]), '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="block-filter-text-source">{{ label }}</div>', '#context' => ['label' => $block['admin_label']]]], 'category' => ['data' => $block['category']], 'operations' => ['data' => ['#type' => 'operations', '#links' => $links]]]; } return $build; }
/** * Build the mega menu link/content tree. * * @param MegaMenuInterface $mega_menu * * @return array */ private function buildMegaMenuTree(MegaMenuInterface $mega_menu) { $tree = $this->loadMenuTree($mega_menu->getTargetMenu()); $build = $this->menuLinkTree->build($tree); $build['#mega_menu'] = $mega_menu; $cacheability = CacheableMetadata::createFromRenderArray($build); $cacheability->addCacheableDependency($mega_menu); // Add content from the mega menus to the link tree. foreach ($build['#items'] as $item_key => $item) { $safe_item_key = str_replace('.', '_', $item_key); $layout = $mega_menu->getLinkLayout($safe_item_key); if ($layout === MegaMenuInterface::NO_LAYOUT) { continue; } $build['#items'][$item_key]['attributes']['data-mega-menu-content-target'] = $item_key; /** @var LayoutInterface $layout_plugin */ $layout_plugin = $this->layoutPluginManager->createInstance($layout); $plugin_definition = $layout_plugin->getPluginDefinition(); // Build an array of the region names in the right order. $empty = array_fill_keys(array_keys($plugin_definition['region_names']), []); $full = $mega_menu->getBlocksByLink($safe_item_key)->getAllByRegion(); // Merge it with the actual values to maintain the ordering. $block_assignments = array_intersect_key(array_merge($empty, $full), $empty); $build['#items'][$item_key]['content'] = ['#prefix' => '<div data-mega-menu-content="' . $item_key . '" class="mega-menu-content">', '#suffix' => '</div>', '#theme' => $plugin_definition['theme'], '#settings' => [], '#layout' => $plugin_definition]; if (isset($plugin_definition['library'])) { $build['#items'][$item_key]['content']['#attached']['library'][] = $plugin_definition['library']; } foreach ($block_assignments as $region => $blocks) { $build['#items'][$item_key]['content'][$region] = []; /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */ foreach ($blocks as $block_id => $block) { if ($block instanceof ContextAwarePluginInterface) { $contexts = $this->contextRepository->getRuntimeContexts($block->getContextMapping()); $this->contextHandler->applyContextMapping($block, $contexts); } // 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; } $configuration = $block->getConfiguration(); // Create the render array for the block as a whole. // @see template_preprocess_block(). $block_build = ['#theme' => 'block', '#attributes' => [], '#weight' => $configuration['weight'], '#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' => ['mega_menu', $mega_menu->id(), 'block', $block_id], 'tags' => Cache::mergeTags($mega_menu->getCacheTags(), $block->getCacheTags()), 'contexts' => $block->getCacheContexts(), 'max-age' => $block->getCacheMaxAge()]]; $build['#items'][$item_key]['content'][$region][$block_id] = $block_build; $cacheability->addCacheableDependency($block); } } } $cacheability->applyTo($build); return $build; }
/** * 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 null $block_id * The id of the block to place. * * @return array The form structure. * The form structure. */ public function buildForm(array $form, FormStateInterface $form_state, $block_id = NULL) { $this->entityLayout = $this->getEntityLayoutFromRouteMatch(); $this->block = $this->prepareBlock($block_id); // Some blocks require contexts, set a temporary value with gathered // contextual values. $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); $form['#tree'] = TRUE; $form['settings'] = $this->block->buildConfigurationForm([], $form_state); $form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Save block'), '#button_type' => 'primary']; return $form; }
/** * Form constructor. * * @param array $form * An associative array containing the structure of the form. * @param FormStateInterface $form_state * The current state of the form. * @param MegaMenuInterface $mega_menu * The mega menu 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, Request $request = NULL, MegaMenuInterface $mega_menu = NULL, $block_id = NULL) { $this->megaMenu = $mega_menu; // Get the query parameters needed. $form_state->set('link', $request->query->get('link')); $form_state->set('region', $request->query->get('region')); $this->block = $this->prepareBlock($form_state->get('link'), $block_id); // Some blocks require contexts, set a temporary value with gathered // contextual values. $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); $form['#tree'] = TRUE; $form['settings'] = $this->block->buildConfigurationForm([], $form_state); $form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->getSubmitValue(), '#button_type' => 'primary']; return $form; }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, BlockVisibilityGroupInterface $block_visibility_group = NULL, $condition_id = NULL, $redirect = NULL) { $this->block_visibility_group = $block_visibility_group; $this->condition = $this->prepareCondition($condition_id); $this->setRedirectValue($form, $redirect); // Store the gathered contexts in the form state for other objects to use // during form building. $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); // Allow the condition to add to the form. $form['condition'] = $this->condition->buildConfigurationForm([], $form_state); $form['condition']['#tree'] = TRUE; $form['actions'] = ['#type' => 'actions']; $form['actions']['submit'] = [ '#type' => 'submit', '#value' => $this->submitButtonText(), '#button_type' => 'primary', ]; return $form; }
/** * Gets a list of all available blocks sorted by category and label. * * @return array[] */ public function getSystemBlocks() { $contexts = $this->contextRepository->getAvailableContexts(); $blocks = $this->blockManager->getDefinitionsForContexts($contexts); return $this->blockManager->getSortedDefinitions($blocks); }