g2-8.x-1.x-dev/src/Plugin/Block/WotdBlock.php
src/Plugin/Block/WotdBlock.php
<?php
declare(strict_types=1);
namespace Drupal\g2\Plugin\Block;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\g2\G2;
use Drupal\g2\WOTD;
use Drupal\views\Entity\View;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class Wotd is the Word of the Day plugin.
*
* @Block(
* id = "g2_wotd",
* admin_label = @Translation("G2 Word of the day"),
* category = @Translation("G2"),
* help = @Translation("This block displays a once-a-day entry from the G2 glossary."),
* )
*
* @state g2.wotd.date
* @state g2.wotd.entry
* @phpstan-consistent-constructor
*/
class WotdBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The block setting controlling the RSS feed icon presence.
*/
const FEED_SETTING = 'feed_icon';
const ICON_CLASS = 'g2-feed-icon';
/**
* The entity_type.manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $etm;
/**
* The core path.validator service.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected PathValidatorInterface $pathValidator;
/**
* The g2.wotd service.
*
* @var \Drupal\g2\WOTD
*/
protected WOTD $wotd;
/**
* Static factory.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
* @param array<string,mixed> $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param array<string,mixed> $plugin_definition
* The plugin definition.
*
* @return static
* The plugin instance.
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
): static {
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $etm */
$etm = $container->get(G2::SVC_ETM);
/** @var \Drupal\Core\Path\PathValidatorInterface $pv */
$pv = $container->get('path.validator');
/** @var \Drupal\g2\WOTD $wotd */
$wotd = $container->get(G2::SVC_WOTD);
return new static($configuration, $plugin_id, $plugin_definition,
$etm, $pv, $wotd,
);
}
/**
* Constructor.
*
* @param array<string,mixed> $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param array<string,mixed> $plugin_definition
* The plugin definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $etm
* The core entity_type.manager service.
* @param \Drupal\Core\Path\PathValidatorInterface $pathValidator
* The core path.validator service.
* @param \Drupal\g2\WOTD $wotd
* The g2.wotd service.
*/
public function __construct(
array $configuration,
string $plugin_id,
array $plugin_definition,
EntityTypeManagerInterface $etm,
PathValidatorInterface $pathValidator,
WOTD $wotd,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->etm = $etm;
$this->pathValidator = $pathValidator;
$this->wotd = $wotd;
}
/**
* Build the block configuration subform.
*
* @param array<string,mixed> $form
* The initial form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return array<string,mixed>
* The modified form.
*/
public function blockForm($form, FormStateInterface $form_state): array {
$form = parent::blockForm($form, $form_state);
$form[static::FEED_SETTING] = [
'#type' => 'checkbox',
'#title' => $this->t("Display feed icon"),
'#default_value' => $this->configuration[static::FEED_SETTING] ?? TRUE,
'#description' => $this->t('Add the standard RSS feed icon at the bottom of the block, linking to the WOTD feed'),
];
return $form;
}
/**
* The block configuration form submit handler.
*
* @param array<string,mixed> $form
* The initial form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function blockSubmit($form, FormStateInterface $form_state): void {
parent::blockSubmit($form, $form_state);
$this->configuration[static::FEED_SETTING] = $form_state->getValue(static::FEED_SETTING);
}
/**
* Builds and returns the renderable array for this block plugin.
*
* If a block should not be rendered because it has no content, then this
* method must also ensure to return no content: it must then only return an
* empty array, or an empty array with #cache set (with cacheability metadata
* indicating the circumstances for it being empty).
*
* @return array<string,mixed>
* A renderable array representing the content of the block.
*
* @see \Drupal\block\BlockViewBuilder
*/
public function build(): array {
$viewBuilder = $this->etm->getViewBuilder(G2::TYPE);
$showFeedIcon = $this->configuration[static::FEED_SETTING] ?? TRUE;
$entry = $this->wotd->get();
if (empty($entry)) {
return [];
}
$build = $viewBuilder->view($entry, G2::VM_BLOCK);
if ($showFeedIcon) {
[, , $displayName] = explode('.', G2::ROUTE_FEED_WOTD);
$view = $this->etm
->getStorage('view')
->load(G2::VIEW_WOTD);
assert($view instanceof View);
$display = $view
->getDisplay($displayName);
$title = $display['display_title'];
$description = $display['display_options']['display_description'];
$build[static::FEED_SETTING] = [
'#theme' => 'feed_icon',
'#url' => Url::fromRoute(G2::ROUTE_FEED_WOTD, [],
// Absolute is a best practice for RSS feed links.
['absolute' => TRUE],
),
'#title' => $title,
// Ignored by #3371937, hence the need for g2_preprocess_feed_icon.
'#attributes' => [
'class' => [self::ICON_CLASS],
'title' => $description,
],
'#weight' => 15,
];
}
return $build;
}
/**
* Implements hook_preprocess_feed_icon().
*
* Adds the self::ICON_CLASS to the feed, which default theme handling does
* not fix drupal_common_theme() declaration for "feed_icon" to include
* attributes.
*
* @param array<string,mixed> $variables
* The variables array to preprocess. The 'url' key should contain the URL
* for the feed icon, either as a string or DrupalCore\Url.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function preprocessFeedIcon(array &$variables): void {
global $base_url;
$url = $variables['url'] ?? NULL;
// With most themes, we get a string, but with Olivero we get a Url.
// We know nothing about other behaviors.
if (empty($url) || !(is_string($url) || $url instanceof Url)) {
return;
}
if (is_string($url)) {
// The path validator only operates on local paths, not absolute URLs.
if (UrlHelper::isExternal($url)&&
!UrlHelper::externalIsLocal($url, $base_url)) {
return;
};
['path' => $path] = UrlHelper::parse($url);
$url = $this->pathValidator
->getUrlIfValidWithoutAccessCheck($path);
if (empty($url)) {
return;
}
}
try {
/** @var \Drupal\Core\Url $url */
$route = $url->getRouteName();
}
catch (\UnexpectedValueException $e) {
$route = '';
}
if ($route !== G2::ROUTE_FEED_WOTD) {
return;
}
[, , $displayName] = explode('.', G2::ROUTE_FEED_WOTD);
$view = $this->etm
->getStorage('view')
->load(G2::VIEW_WOTD);
assert($view instanceof View);
$display = $view
->getDisplay($displayName);
$description = $display['display_options']['display_description'];
$variables['attributes'] = [
'class' => [self::ICON_CLASS],
'title' => $description,
];
}
}
