Skip to content
Snippets Groups Projects
Commit 8b42ef9a authored by Brent Gees's avatar Brent Gees Committed by Maarten Steurs
Browse files

Issue #3348876: Create a description replacement field

parent 201845d2
Branches
Tags
1 merge request!52Issue #3348876: Create a description replacement field
<?php
namespace Drupal\rocketship_core\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\NodeInterface;
use Drupal\text\Plugin\Field\FieldFormatter\TextDefaultFormatter;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Formatter to load the field_description if not replaced.
*
* Plugin implementation of the 'contentblock_description_replacement_formatter'
* formatter.
*
* @FieldFormatter(
* id = "contentblock_description_replacement_formatter",
* label = @Translation("Description Replacement Formatter"),
* field_types = {
* "contentblock_description_replacement"
* }
* )
*/
class ContentBlockDescriptionReplacementFormatter extends TextDefaultFormatter {
/**
* Entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The current route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The entity type manager interface.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$class = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$class->entityRepository = $container->get('entity.repository');
$class->renderer = $container->get('renderer');
$class->routeMatch = $container->get('current_route_match');
$class->entityTypeManager = $container->get('entity_type.manager');
return $class;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
return $summary;
}
/**
* Fetch the content entity object from the current route.
*
* @return \Drupal\Core\Entity\ContentEntityInterface|null
* The currently viewed content entity object.
*/
protected function fetchEntityFromRoute(): ?ContentEntityInterface {
$entity_type_id = $this->getEntityTypeFromRoute();
if ($entity_type_id === NULL) {
return NULL;
}
$entity = $this->routeMatch->getParameter($entity_type_id);
if ($entity === NULL) {
$subject = $this->routeMatch->getRawParameter('section_storage') ?? '';
preg_match('/' . $entity_type_id . '\.([0-9]+)/', $subject, $matches);
if (!empty($matches[1])) {
$entity = $matches[1];
}
}
// Get node from section storage if node is not available in the route.
if (is_numeric($entity)) {
$entity = $this->entityTypeManager->getStorage($entity_type_id)
->load($entity);
}
return $entity;
}
/**
* Fetch the entity type from the current route.
*
* @return string|null
* The currently viewed content entity type.
*/
protected function getEntityTypeFromRoute(): ?string {
$route_name = $this->routeMatch->getRouteName();
$parts = explode('.', $route_name);
if ($parts[0] === 'entity') {
return $parts[1];
}
if ($parts[0] === 'layout_builder' && $parts[1] === 'overrides') {
return $parts[2];
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
// Note: don't trust the passed langcode, it is for the block which may
// always be EN, it may match the current language, it may be anything
// so don't use it, rely on entity repository for the entity itself.
// If the description is being replaced, the correct value is already inside
// $items anyway.
$route_name = $this->routeMatch->getRouteName();
// Since on first save the items is empty, we need to add some fallback.
if ($items->count() === 0) {
$item = new \stdClass();
$item->replace = FALSE;
$item->value = '';
$items = [$item];
}
foreach ($items as $delta => $item) {
$entity = $this->fetchEntityFromRoute();
$entity_type_id = $this->getEntityTypeFromRoute();
// Show the current entity description.
if (!$item->replace && $entity instanceof EntityInterface) {
switch ($route_name) {
case "entity.{$entity_type_id}.canonical":
$entity = $this->entityRepository->getCanonical($entity_type_id, $entity->id());
$entity = $this->entityRepository->getTranslationFromContext($entity);
$value = $entity->get('field_description')->value;
$this->renderer->addCacheableDependency($elements, $entity);
break;
case 'entity.node.revision':
$revision_id = $this->routeMatch
->getParameter('node_revision');
if ($revision_id instanceof NodeInterface) {
$entity = $revision_id;
}
else {
$entity = $this->entityTypeManager
->getStorage('node')
->loadRevision($revision_id);
}
$entity = $this->entityRepository->getTranslationFromContext($entity);
$value = $entity->get('field_description')->value;
$this->renderer->addCacheableDependency($elements, $entity);
break;
case (bool) preg_match('/layout_builder.*/', $route_name):
case 'entity.node.latest_version':
$entity = $this->entityRepository->getActive($entity_type_id, $entity->id());
$entity = $this->entityRepository->getTranslationFromContext($entity);
$value = $entity->get('field_description')->value;
$this->renderer->addCacheableDependency($elements, $entity);
break;
case 'diff.revisions_diff':
$revision_id = $this->routeMatch
->getParameter('right_revision');
$entity = $this->entityTypeManager
->getStorage($entity_type_id)
->loadRevision($revision_id);
$entity = $this->entityRepository->getTranslationFromContext($entity);
$value = $entity->get('field_description')->value;
$this->renderer->addCacheableDependency($elements, $entity);
break;
default:
$value = $this->t('Placeholder for replacement description');
// Don't cache.
$this->renderer->addCacheableDependency($elements, new \stdClass());
break;
}
}
// Fallback value.
if (!$entity && !$item->replace && !$value) {
$value = $this->t('Placeholder for replacement description');
// Don't cache.
$this->renderer->addCacheableDependency($elements, new \stdClass());
}
$elements[$delta] = [
'#markup' => $value,
];
}
return $elements;
}
}
<?php
namespace Drupal\rocketship_core\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\text\Plugin\Field\FieldType\TextLongItem;
/**
* Description replacement field.
*
* @FieldType(
* id = "contentblock_description_replacement",
* label = @Translation("Current Node Description replacement"),
* description = @Translation("Special field that grabs the current node and uses that description if this field is empty"),
* default_widget = "contentblock_description_replacement_widget",
* default_formatter = "contentblock_description_replacement_formatter",
* cardinality = 1
* )
*/
class ContentBlockDescriptionReplacement extends TextLongItem {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties = parent::propertyDefinitions($field_definition);
$properties['replace'] = DataDefinition::create('boolean')
->setLabel(t('Boolean value'))
->setRequired(TRUE);
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = parent::schema($field_definition);
$schema['columns']['replace'] = [
'type' => 'int',
'size' => 'tiny',
];
return $schema;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$values = parent::generateSampleValue($field_definition);
$values['replace'] = mt_rand(0, 1);
return $values;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
// If the checkbox wasn't checked, consider this field empty.
$value = $this->get('replace')->getValue();
return $value === NULL || $value === '' || $value === FALSE;
}
}
<?php
namespace Drupal\rocketship_core\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\text\Plugin\Field\FieldWidget\TextareaWidget;
/**
* Plugin implementation of the 'contentblock_description_replacement_widget' widget.
*
* @FieldWidget(
* id = "contentblock_description_replacement_widget",
* label = @Translation("Description Replacement Widget"),
* field_types = {
* "contentblock_description_replacement"
* }
* )
*/
class ContentBlockDescriptionReplacementWidget extends TextareaWidget {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'checkbox_title' => 'Replace the description',
'checkbox_description' => "Replace the description on the detail page for this piece of content with a different description, which can include the following html: &lt;em&gt;&lt;/em&gt; and &lt;strong&gt;&lt;/strong&gt; Leave this unchecked to use the description of this piece of content as is.",
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$element['checkbox_title'] = [
'#type' => 'textfield',
'#title' => t('Checkbox title'),
'#default_value' => $this->getSetting('checkbox_title'),
'#required' => TRUE,
];
$element['checkbox_description'] = [
'#type' => 'textarea',
'#title' => t('Checkbox description'),
'#default_value' => $this->getSetting('checkbox_description'),
'#description' => t('Text that will be shown below the checkbox to indicate what checking it will do and what you can use the replacement description field for. Escape the tags yourself as needed.'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$checkbox_title = $this->getSetting('checkbox_title');
if (!empty($checkbox_title)) {
$summary[] = t('Checkbox title: @title', ['@title' => $checkbox_title]);
}
$description = $this->getSetting('checkbox_description');
if (!empty($description)) {
$summary[] = t('Checkbox description: @description', [
'@description' => substr($description, 0, 50) . '...',
]);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// We put the element in a separate object, otherwise the states will cause
// issues.
$main_widget = parent::formElement($items, $delta, $element, $form, $form_state);
$element['value'] = $main_widget;
// Create a unique selector to use with #states.
$selector = "{$items->getEntity()->getEntityTypeId()}_{$items->getFieldDefinition()->getName()}_delta_{$delta}";
// Apply #states.
$element['value']['#states'] = [
'visible' => [
':input[data-state-selector="' . $selector . '"]' => ['checked' => TRUE],
],
];
$element['replace'] = [
'#title' => t($this->getSetting('checkbox_title')),
'#description' => t($this->getSetting('checkbox_description')),
'#type' => 'checkbox',
'#default_value' => !empty($items[$delta]->replace),
// Add our unique selector as data attribute.
'#attributes' => [
'data-state-selector' => $selector,
],
'#weight' => -50,
];
// Add our validate function.
if (!isset($element['#element_validate'])) {
$element['#element_validate'] = [];
}
$element['#element_validate'][] = [$this, 'validate'];
return $element;
}
/**
* Validate the entire element.
*/
public function validate($element, FormStateInterface $form_state) {
if (!empty($element['replace']['#value']) && empty($element['value']['value']['#value'])) {
// If replace description was checked, then the text field becomes required.
$form_state->setError($element, t('The %field field is required.', ['%field' => $element['value']['value']['#title']]));
}
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
$new_values = [];
// We only need the values in 'value'.
foreach ($values as $value) {
$value['value']['replace'] = $value['replace'];
$new_values[] = $value['value'];
}
return parent::massageFormValues($new_values, $form, $form_state);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment