public function GeneralEntityReferenceFormatter::viewElements in Formatter Suite 8
Builds a renderable array for a field value.
Parameters
\Drupal\Core\Field\FieldItemListInterface $items: The field values to be rendered.
string $langcode: The language that should be used to render the field.
Return value
array A renderable array for $items, as an array of child elements keyed by consecutive numeric indexes starting from 0.
Overrides FormatterInterface::viewElements
File
- src/
Plugin/ Field/ FieldFormatter/ GeneralEntityReferenceFormatter.php, line 948
Class
- GeneralEntityReferenceFormatter
- Formats an entity reference as one or more links.
Namespace
Drupal\formatter_suite\Plugin\Field\FieldFormatterCode
public function viewElements(FieldItemListInterface $items, $langcode) {
if ($items
->isEmpty() === TRUE) {
return [];
}
$this
->sanitizeSettings();
//
// Get entities.
// -------------
// From the item list, get the entities to process. If there aren't
// any, return nothing.
$entities = $this
->getEntitiesToView($items, $langcode);
if (empty($entities) === TRUE) {
return [];
}
// Prepare custom classes.
// -----------------------
// If the admin entered custom classes, add them.
$classes = $this
->getSetting('classes');
if (empty($classes) === TRUE) {
$classes = [];
}
else {
// Security: Class names are entered by an administrator.
// They may not include anything but CSS-compatible words, and
// certainly no HTML.
//
// Here, the class text is stripped of HTML tags as a start.
// A regex tosses unacceptable characters in a CSS class name.
$classes = strip_tags($classes);
$classes = mb_ereg_replace('[^_a-zA-Z0-9- ]', '', $classes);
if ($classes === FALSE) {
$classes = [];
}
$classes = explode(' ', $classes);
}
//
// Prepare token prefix.
// ---------------------
// Tokens have a prefix (e.g. "node") that distinguishes the tokens of
// one entity type or generic purpose from another. Normally, the prefix
// matches the entity type, but this is not enforced. Since the token
// API has no way to map entity types to token prefixes, assume the
// prefix is the entity type except for known Drupal core special cases.
$entityType = reset($entities)
->getEntityType()
->id();
$fixPrefixes = self::getWellKnownTokenPrefixExceptions();
if (isset($fixPrefixes[$entityType]) === TRUE) {
$prefix = $fixPrefixes[$entityType];
}
else {
$prefix = $entityType;
}
// Verify that the prefix exists. If it doesn't, then token replacement
// cannot work for the standard reference styles.
if ($this->tokenService === NULL) {
$prefix = '';
}
else {
$tokenInfo = $this->tokenService
->getInfo();
if (isset($tokenInfo['tokens'][$prefix]) === FALSE) {
// Prefix is unknown. Don't use a prefix.
$prefix = '';
}
}
//
// Prepare token pattern.
// ----------------------
// Get the pattern and pre-process it. If the admin entered a custom
// pattern, clean it of dangerous HTML. Otherwise get a well-known
// pattern.
$entityReferenceStyle = $this
->getSetting('entityReferenceStyle');
if ($entityReferenceStyle === 'custom') {
// Custom token pattern.
$pattern = $this
->getSetting('titleCustomText');
if (empty($pattern) === TRUE) {
// If the custom pattern is empty, revert to the title.
$entityReferenceStyle = 'title';
}
else {
// Security: A custom pattern is entered by an administrator.
// It may legitimately include HTML entities and minor HTML, but
// it should not include dangerous HTML.
$pattern = Xss::filterAdmin($this
->getSetting('titleCustomText'));
}
}
elseif ($prefix === '') {
// A standard style is requested, but a token prefix for the entity
// type could not be found. This prevents us from using token replacement
// for the standard styles. Below, several of these are implemented
// with explicit code. But anything else needs to be reduced.
switch ($entityReferenceStyle) {
case 'id':
case 'title':
case 'title-id':
// These have custom code below.
break;
default:
// All other style choices do not. Reduce them to 'title'.
$entityReferenceStyle = 'title';
break;
}
// Because these styles are hard-coded below, we don't need to
// get a token pattern.
$pattern = '';
}
else {
// Get the token pattern for a standard style.
$entityReferencePatterns = self::getEntityReferenceTokenPatterns();
$pattern = $entityReferencePatterns[$entityReferenceStyle];
// Replace the placeholder "__PREFIX" with the entity type's prefix
// determined above.
$pattern = str_replace('__PREFIX', $prefix, $pattern);
// Also map from generic tokens to the specific tokens for
// well-known entity types.
$fixPatterns = self::getWellKnownTokenReplacements();
$pattern = str_replace(array_keys($fixPatterns), array_values($fixPatterns), $pattern);
}
//
// Replace tokens.
// ---------------
// Loop through the entities and do token replacement for each one.
$elements = [];
foreach ($entities as $delta => $entity) {
if ($entity === NULL) {
continue;
}
$url = $entity
->toUrl();
if (empty($url) === TRUE) {
$url = NULL;
}
else {
$urlOptions = $url
->getOptions();
}
// We'd like to use generic token patterns for all styles
// in the formatter's menu, but this doesn't work across all entity
// types because there are no standards for tokens. For example,
// the entity ID for a node is "nid", for a file is "fid", for
// a comment is "cid", and for a user is "uid". Other entity types
// will generically support an "id" token. We get similar issues
// for the name and author of an entity.
//
// While we do support generic pattern fixes below, we do so only
// for custom patterns where the user entering the tokens may not
// know to use the right tokens for the right entity type.
switch ($entityReferenceStyle) {
case 'id':
$title = (string) $entity
->id();
break;
case 'title':
$title = $entity
->label();
break;
case 'title-id':
$title = $entity
->label() . ' (' . (string) $entity
->id() . ')';
break;
default:
// Replace tokens in the pattern.
$title = $this->tokenService
->replace($pattern, [
$entityType => $entity,
], [
'langcode' => $langcode,
'clear' => TRUE,
]);
break;
}
// Because replaced text may include HTML, we cannot pass it directly as
// the '#title' on a link, which will escape the HTML. Instead we
// use FormattableMarkup.
$title = new FormattableMarkup($title, []);
// If the link is disabled, show the title text within a <span>.
// Otherwise, build a URL and create a link.
if ($this
->getSetting('showLink') === FALSE || $url === NULL) {
$elements[$delta] = [
'#type' => 'html_tag',
'#tag' => 'span',
'#value' => $title,
'#attributes' => [
'class' => $classes,
],
'#cache' => [
'tags' => $entity
->getCacheTags(),
],
];
}
else {
$rel = '';
$target = '';
$download = FALSE;
switch ($this
->getSetting('openLinkIn')) {
case '_self':
$target = '_self';
break;
case '_blank':
$target = '_blank';
break;
case 'download':
$download = TRUE;
break;
}
$topic = $this
->getSetting('linkTopic');
if ($topic !== 'any') {
$rel .= $topic;
}
if (empty($rel) === FALSE) {
$urlOptions['attributes']['rel'] = $rel;
}
if (empty($target) === FALSE) {
$urlOptions['attributes']['target'] = $target;
}
if ($download === TRUE) {
$urlOptions['attributes']['download'] = '';
}
$url
->setOptions($urlOptions);
$elements[$delta] = [
'#type' => 'link',
'#title' => $title,
'#options' => $url
->getOptions(),
'#url' => $url,
'#attributes' => [
'class' => $classes,
],
'#cache' => [
'tags' => $entity
->getCacheTags(),
],
];
if (empty($items[$delta]->_attributes) === FALSE) {
// There are item attributes. Add them to the link's options.
$elements[$delta]['#options'] += [
'attributes' => $items[$delta]->_attributes,
];
// And remove them from the item since they are now included
// on the link.
unset($items[$delta]->_attributes);
}
}
}
if (empty($elements) === TRUE) {
return [];
}
//
// Add multi-value field processing.
// ---------------------------------
// If the field has multiple values, redirect to a theme and pass
// the list style and separator.
$isMultiple = $this->fieldDefinition
->getFieldStorageDefinition()
->isMultiple();
if ($isMultiple === TRUE) {
// Replace the 'field' theme with ours, which supports lists.
$elements['#theme'] = 'formatter_suite_field_list';
// Set the list style.
$elements['#list_style'] = $this
->getSetting('listStyle');
// Set the list separator.
//
// Security: The list separator is entered by an administrator.
// It may legitimately include HTML entities and minor HTML, but
// it should not include dangerous HTML.
//
// Because it may include HTML, we cannot pass it as-is and let a TWIG
// template use {{ }}, which will process the text and corrupt any
// entered HTML or HTML entities.
//
// We therefore use an Xss admin filter to remove any egreggious HTML
// (such as scripts and styles), and then FormattableMarkup() to mark the
// resulting text as safe.
$listSeparator = Xss::filterAdmin($this
->getSetting('listSeparator'));
$elements['#list_separator'] = new FormattableMarkup($listSeparator, []);
}
return $elements;
}