Skip to content
Snippets Groups Projects
Commit 23d751ac authored by Andy Marquis's avatar Andy Marquis
Browse files

Issue #3486757 by apmsooner, giuse69: Hierarchical Term Formatter for entity_reference terms

parent 52c11cbb
No related branches found
No related tags found
1 merge request!723486757: Hierarchy formatter.
Pipeline #343705 passed with warnings
......@@ -158,6 +158,21 @@ custom_field.formatter.*:
link:
type: boolean
label: 'Link label to the referenced entity'
hierarchy_display:
type: string
label: 'Terms to display'
hierarchy_link:
type: boolean
label: 'Link each term'
hierarchy_wrap:
type: string
label: 'Wrap each term'
hierarchy_separator:
type: string
label: 'Separator'
hierarchy_reverse:
type: boolean
label: 'Reverse order'
field.formatter.settings.custom_formatter:
type: custom_field_formatter_base
......
......@@ -34,6 +34,15 @@ function custom_field_theme(): array {
return [
'custom_field' => $item,
'custom_field_item' => $item,
'custom_field_hierarchical_formatter' => [
'variables' => [
'terms' => [],
'wrapper' => '',
'separator' => ' » ',
'link' => FALSE,
],
'file' => 'custom_field_hierarchical_formatter.theme.inc',
],
];
}
......
<?php
/**
* @file
* Theme preprocess used to prepare Twig variables.
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Link;
use Drupal\Core\Render\Markup;
/**
* Prepares term objects for Twig template.
*
* @param array $variables
* An associative array with preprocess variables for this theme.
* by theme_preprocess.
*/
function template_preprocess_custom_field_hierarchical_formatter(array &$variables) {
$terms = [];
$variables['terms_objects'] = $variables['terms'];
/** @var \Drupal\taxonomy\TermInterface|[] $item */
foreach ($variables['terms'] as $item) {
if (is_array($item)) {
$group = [];
foreach ($item as $value) {
if ($variables['link']) {
$link = Link::fromTextAndUrl($value->label(), $value->toUrl())->toRenderable();
$group[] = \Drupal::service('renderer')->render($link);
}
else {
$group[] = $value->label();
}
}
$terms[] = Markup::create(implode('<span class="child-separator">, </span>', $group));
}
else {
if ($variables['link']) {
$link = Link::fromTextAndUrl($item->label(), $item->toUrl())->toRenderable();
$terms[] = \Drupal::service('renderer')->render($link);
}
else {
$terms[] = $item->label();
}
}
}
if ($variables['wrapper'] != 'none') {
$count = 0;
foreach ($terms as &$term) {
$count++;
$term = [
'#type' => 'html_tag',
'#tag' => in_array($variables['wrapper'], ['ol', 'ul']) ? 'li' : $variables['wrapper'],
'#value' => $term,
'#attributes' => [
'class' => [
Html::cleanCssIdentifier('taxonomy-term'),
Html::cleanCssIdentifier("count {$count}"),
],
],
];
}
}
unset($variables['link']);
$variables['terms'] = $terms;
}
<?php
namespace Drupal\custom_field\Plugin\CustomField\FieldFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\custom_field\Plugin\CustomFieldTypeInterface;
/**
* Plugin implementation of the 'hierarchical_term_formatter' formatter.
*
* @FieldFormatter(
* id = "hierarchical_term_formatter",
* label = @Translation("Hierarchical term"),
* description = @Translation("Display the term hierarchy."),
* field_types = {
* "entity_reference",
* }
* )
*/
class HierarchicalFormatter extends EntityReferenceFormatterBase {
/**
* Returns a list of supported display options.
*
* @return array
* An array whose keys are display machine names
* and whose values are their labels.
*/
private function displayOptions(): array {
return [
'all' => $this->t('The selected term and all of its parents'),
'parents' => $this->t('Just the parent terms'),
'root' => $this->t('Just the topmost/root term'),
'nonroot' => $this->t('Any non-topmost/root terms'),
'leaf' => $this->t('Just the selected term'),
];
}
/**
* Returns a list of supported wrapping options.
*
* @return array
* An array whose keys are wrapper machine names
* and whose values are their labels.
*/
private function wrapOptions(): array {
return [
'none' => $this->t('None'),
'span' => $this->t('@tag elements', ['@tag' => '<span>']),
'div' => $this->t('@tag elements', ['@tag' => '<div>']),
'ul' => $this->t('@tag elements surrounded by a @parent_tag', [
'@tag' => '<li>',
'@parent_tag' => '<ul>',
]),
'ol' => $this->t('@tag elements surrounded by a @parent_tag', [
'@tag' => '<li>',
'@parent_tag' => '<ol>',
]),
];
}
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
return [
'hierarchy_display' => 'all',
'hierarchy_link' => FALSE,
'hierarchy_wrap' => 'none',
'hierarchy_separator' => ' » ',
'hierarchy_reverse' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state, array $settings) {
$settings += static::defaultSettings();
$element['hierarchy_display'] = [
'#title' => $this->t('Terms to display'),
'#description' => $this->t('Choose what terms to display.'),
'#type' => 'select',
'#options' => $this->displayOptions(),
'#default_value' => $settings['hierarchy_display'],
];
$element['hierarchy_link'] = [
'#title' => $this->t('Link each term'),
'#description' => $this->t('If checked, the terms will link to their corresponding term pages.'),
'#type' => 'checkbox',
'#default_value' => $settings['hierarchy_link'],
];
$element['hierarchy_reverse'] = [
'#title' => $this->t('Reverse order'),
'#description' => $this->t('If checked, children display first, parents last.'),
'#type' => 'checkbox',
'#default_value' => $settings['hierarchy_reverse'],
];
$element['hierarchy_wrap'] = [
'#title' => $this->t('Wrap each term'),
'#description' => $this->t('Choose what type of html elements you would like to wrap the terms in, if any.'),
'#type' => 'select',
'#options' => $this->wrapOptions(),
'#default_value' => $settings['hierarchy_wrap'],
];
$element['hierarchy_separator'] = [
'#title' => $this->t('Separator'),
'#description' => $this->t('Enter some text or markup that will separate each term in the hierarchy. Leave blank for no separator. Example: <em>»</em>'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $settings['hierarchy_separator'],
];
return $element;
}
/**
* {@inheritdoc}
*/
public function formatValue(FieldItemInterface $item, CustomFieldTypeInterface $field, array $settings) {
/** @var \Drupal\taxonomy\Entity\Term $entity */
$entity = $settings['value'];
if (!$entity instanceof EntityInterface) {
return NULL;
}
/** @var \Drupal\taxonomy\TermStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
$access = $this->checkAccess($entity);
if (!$access->isAllowed()) {
return NULL;
}
$formatter_settings = $settings['formatter_settings'] + static::defaultSettings();
$tid = $entity->id();
$term_tree = [];
switch ($formatter_settings['hierarchy_display']) {
case 'leaf':
$term_tree = [$entity];
break;
case 'root':
$parents = $storage->loadAllParents($tid);
if (!empty($parents)) {
$term_tree = [array_pop($parents)];
}
break;
case 'parents':
$term_tree = array_reverse($storage->loadAllParents($tid));
array_pop($term_tree);
break;
case 'nonroot':
$parents = $storage->loadAllParents($tid);
if (count($parents) > 1) {
$term_tree = array_reverse($parents);
// This gets rid of the first topmost term.
array_shift($term_tree);
// Terms can have multiple parents. Now remove any remaining topmost
// terms.
foreach ($term_tree as $key => $term) {
$has_parents = $storage->loadAllParents($term->id());
// This has no parents and is topmost.
if (empty($has_parents)) {
unset($term_tree[$key]);
}
}
}
break;
default:
$term_tree = array_reverse($storage->loadAllParents($tid));
break;
}
// Change output order if Reverse order is checked.
if ($formatter_settings['hierarchy_reverse'] && count($term_tree)) {
$term_tree = array_reverse($term_tree);
}
// Remove empty elements caused by discarded items.
$term_tree = array_filter($term_tree);
foreach ($term_tree as $index => $term) {
if ($term->hasTranslation($item->getLangcode())) {
$term_tree[$index] = $term->getTranslation($item->getLangcode());
}
}
$build = [
'#theme' => 'custom_field_hierarchical_formatter',
'#terms' => $term_tree,
'#wrapper' => $formatter_settings['hierarchy_wrap'],
'#separator' => $formatter_settings['hierarchy_separator'],
'#link' => $formatter_settings['hierarchy_link'],
];
return $build;
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity) {
return $entity->access('view label', NULL, TRUE);
}
/**
* {@inheritdoc}
*/
public static function isApplicable(CustomFieldTypeInterface $custom_item): bool {
return $custom_item->getTargetType() === 'taxonomy_term';
}
}
......@@ -83,4 +83,12 @@ abstract class CustomFieldFormatterBase extends PluginSettingsBase implements Cu
return [];
}
/**
* {@inheritdoc}
*/
public static function isApplicable(CustomFieldTypeInterface $custom_item): bool {
// By default, formatters are available for all fields.
return TRUE;
}
}
......@@ -86,4 +86,15 @@ interface CustomFieldFormatterInterface {
*/
public function onFormatterDependencyRemoval(array $dependencies, array $settings): array;
/**
* Returns if the formatter can be used for the provided field.
*
* @param \Drupal\custom_field\Plugin\CustomFieldTypeInterface $custom_item
* The custom field type.
*
* @return bool
* TRUE if the formatter can be used, FALSE otherwise.
*/
public static function isApplicable(CustomFieldTypeInterface $custom_item): bool;
}
{#
/**
* @file
* Default twig file for Hierarchical Term Formatter.
*
* Available variables:
* - terms: The processed taxonomy terms to display.
* - wrapper: The HTML tag that should wrap each of the terms.
* - separator: The text used to separate each of the terms.
*
* @see template_preprocess_hierarchical_term_formatter()
*
* @ingroup themeable
*/
#}
{% if wrapper == 'ol' or wrapper == 'ul' %}
<{{ wrapper }} class="terms-hierarchy">
{% endif %}
{% for term in terms -%}
{{ term }}
{% if loop.last == false %}
<span class="separator">{{ separator }}</span>
{% endif %}
{%- endfor %}
{% if wrapper == 'ol' or wrapper == 'ul' %}
</{{ wrapper }}>
{% endif %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment