Skip to content

Commit

Permalink
Add a view display extender for the localgov_page_header for the lede
Browse files Browse the repository at this point in the history
Fix #127

Adds a setting to views that allows for a page header category, with the option
to set a custom lede inside the view, which will then be used by the
page header block.
  • Loading branch information
Andy Broomfield authored and Andy Broomfield committed Nov 13, 2024
1 parent 3795544 commit d02dade
Show file tree
Hide file tree
Showing 6 changed files with 583 additions and 0 deletions.
9 changes: 9 additions & 0 deletions config/schema/localgov_core.views.schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
views.display_extender.localgov_page_header_display_extender:
type: views_display_extender
mapping:
lede:
type: text
label: 'Lede'
tokenize:
type: boolean
label: 'Should replacement tokens be used from the first row'
22 changes: 22 additions & 0 deletions src/Plugin/Block/PageHeaderBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Drupal\localgov_core\Event\PageHeaderDisplayEvent;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Term;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

Expand Down Expand Up @@ -130,6 +132,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
$route = $this->currentRouteMatch->getRouteObject();
if (!is_null($route)) {
$parameters = $route->getOption('parameters');
$defaults = $route->getDefaults();
if (!is_null($parameters)) {
foreach ($parameters as $name => $options) {
if (!isset($options['type'])) {
Expand All @@ -152,6 +155,13 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
}
}
}
if (isset($defaults['view_id']) && isset($defaults['display_id'])) {
$view = Views::getView($defaults['view_id']);
if ($view) {
$view->setDisplay($defaults['display_id']);
$this->entity = $view;
}
}
}

// Dispatch event to allow modules to alter block content.
Expand Down Expand Up @@ -224,6 +234,18 @@ protected function getLede() {
];
}

// Return view custom summary.
if ($this->entity instanceof ViewExecutable) {
$extender = $this->entity->getDisplay()->getExtenders()['localgov_page_header_display_extender'] ?? NULL;
$this->entity->render();
$lede = $extender->getLede();
return [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => $lede,
];
}

return NULL;
}

Expand Down
251 changes: 251 additions & 0 deletions src/Plugin/views/display_extender/PageHeaderDisplayExtender.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<?php

namespace Drupal\localgov_core\Plugin\views\display_extender;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Metatag display extender plugin.
*
* @ingroup views_display_extender_plugins
*
* @ViewsDisplayExtender(
* id = "localgov_page_header_display_extender",
* title = @Translation("Page header display extender"),
* help = @Translation("Page header settings for this view."),
* no_ui = FALSE
* )
*/
class PageHeaderDisplayExtender extends DisplayExtenderPluginBase {

use StringTranslationTrait;

/**
* The first row tokens on the style plugin.
*
* @var array
*/
protected static $firstRowTokens;

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
/** @var \Drupal\metatag_views\Plugin\views\display_extender\MetatagDisplayExtender */
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);

return $instance;
}

/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();

$options['lede'] = ['default' => ''];
$options['tokenize'] = ['default' => FALSE];

return $options;
}

/**
* Provide a form to edit options for this plugin.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {

if ($form_state->get('section') == 'page_header') {
$form['#title'] .= $this->t('The page header summary');

// Build/inject the Metatag form.
$form['lede'] = [
'#type' => 'textarea',
'#title' => $this->t('Lede'),
'#description' => $this->t('Summary displayed under the page title.'),
'#default_value' => $this->options['lede'],
];
$this->tokenForm($form['page_header'], $form_state);
}
}

/**
* Handle any special handling on the validate form.
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
if ($form_state->get('section') == 'page_header') {
// Process submitted metatag values and remove empty tags.
$page_header_values = $form_state->cleanValues()->getValues();
$this->options['tokenize'] = $page_header_values['tokenize'] ?? FALSE;
unset($page_header_values['tokenize']);
$this->options['lede'] = $page_header_values['lede'];
}
}

/**
* Verbatim copy of TokenizeAreaPluginBase::tokenForm().
*/
public function tokenForm(&$form, FormStateInterface $form_state) {
$form['tokenize'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use replacement tokens from the first row'),
'#default_value' => $this->options['tokenize'],
];

// Get a list of the available fields and arguments for token replacement.
$options = [];
$optgroup_arguments = (string) new TranslatableMarkup('Arguments');
$optgroup_fields = (string) new TranslatableMarkup('Fields');
foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) {
$options[$optgroup_fields]["{{ $field }}"] = $handler->adminLabel();
}

foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
$options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', ['@argument' => $handler->adminLabel()]);
$options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', ['@argument' => $handler->adminLabel()]);
}

if (!empty($options)) {
$form['tokens'] = [
'#type' => 'details',
'#title' => $this->t('Replacement patterns'),
'#open' => TRUE,
'#id' => 'edit-options-token-help',
'#states' => [
'visible' => [
':input[name="options[tokenize]"]' => ['checked' => TRUE],
],
],
];
$form['tokens']['help'] = [
'#markup' => '<p>' . $this->t('The following tokens are available. You may use Twig syntax in this field.') . '</p>',
];
foreach (array_keys($options) as $type) {
if (!empty($options[$type])) {
$items = [];
foreach ($options[$type] as $key => $value) {
$items[] = $key . ' == ' . $value;
}
$form['tokens'][$type]['tokens'] = [
'#theme' => 'item_list',
'#items' => $items,
];
}
}
}

$this->globalTokenForm($form, $form_state);
}

/**
* Provide the default summary for options in the views UI.
*
* This output is returned as an array.
*/
public function optionsSummary(&$categories, &$options) {
$categories['page_header'] = [
'title' => $this->t('Page header'),
'column' => 'second',
];
$options['page_header'] = [
'category' => 'page_header',
'title' => $this->t('Page header'),
'value' => $this->options['lede'] ? $this->t('Custom lede') : $this->t('No lede'),
];
}

/**
* Lists defaultable sections and items contained in each section.
*/
public function defaultableSections(&$sections, $section = NULL) {
}

/**
* Get the lede for display.
*
* @param bool $raw
* Flag if to return raw (untokenised) output.
*
* @return string
* Lede for display.
*/
public function getLede(bool $raw = FALSE) : string {
$view = $this->view;
$lede = '';

if (!empty($this->options['lede'])) {
$lede = $this->options['lede'];
}

if ($this->options['lede'] && !$raw) {
if (!empty(self::$firstRowTokens[$view->current_display])) {
self::setFirstRowTokensOnStylePlugin($view, self::$firstRowTokens[$view->current_display]);
}
// This is copied from TokenizeAreaPluginBase::tokenizeValue().
$style = $view->getStyle();
$lede = $style->tokenizeValue($lede, 0);
$lede = $this->globalTokenReplace($lede);
}

return $lede;
}

/**
* Store first row tokens on the class.
*
* The function metatag_views_metatag_route_entity() loads the View fresh, to
* avoid rebuilding and re-rendering it, preserve the first row tokens.
*/
public function setFirstRowTokens(array $first_row_tokens) {
self::$firstRowTokens[$this->view->current_display] = $first_row_tokens;
}

/**
* Set the first row tokens on the style plugin.
*
* @param \Drupal\views\ViewExecutable $view
* The view.
* @param array $first_row_tokens
* The first row tokens.
*/
public static function setFirstRowTokensOnStylePlugin(ViewExecutable $view, array $first_row_tokens) {
$style = $view->getStyle();
self::getFirstRowTokensReflection($style)->setValue($style, [$first_row_tokens]);
}

/**
* Get the first row tokens from the style plugin.
*
* @param \Drupal\views\ViewExecutable $view
* The view.
*
* @return array
* The first row tokens.
*/
public static function getFirstRowTokensFromStylePlugin(ViewExecutable $view) {
$style = $view->getStyle();
return self::getFirstRowTokensReflection($style)->getValue($style)[0] ?? [];
}

/**
* Get the first row tokens for this Views object iteration.
*
* @param \Drupal\views\Plugin\views\style\StylePluginBase $style
* The style plugin used for this request.
*
* @return \ReflectionProperty
* The rawTokens property.
*/
protected static function getFirstRowTokensReflection(StylePluginBase $style): \ReflectionProperty {
$r = new \ReflectionObject($style);
$p = $r->getProperty('rowTokens');
$p->setAccessible(TRUE);
return $p;
}

}
Loading

0 comments on commit d02dade

Please sign in to comment.