Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
LinkTargetService
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
7 / 7
28
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTargets
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 resolveUrl
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 resolveEntitySelfUrl
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 resolveFieldTargetUrl
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 resolveEntityReferenceUrl
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 resolveLinkFieldUrl
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Service;
6
7use Drupal\Core\Entity\EntityFieldManager;
8use Drupal\Core\Entity\EntityInterface;
9use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
10use Drupal\Core\Field\FieldDefinitionInterface;
11use Drupal\Core\Field\FieldItemListInterface;
12use Drupal\Core\StringTranslation\TranslatableMarkup;
13use Drupal\Core\Url;
14
15/**
16 * Resolves formatter link targets and URLs for name fields.
17 *
18 * @internal
19 */
20final class LinkTargetService implements LinkTargetInterface {
21
22  /**
23   * Constructs a LinkTargetService.
24   *
25   * @param \Drupal\Core\Entity\EntityFieldManager $entityFieldManager
26   *   The entity field manager.
27   */
28  public function __construct(
29    private readonly EntityFieldManager $entityFieldManager,
30  ) {}
31
32  /**
33   * {@inheritdoc}
34   */
35  public function getTargets(
36    FieldDefinitionInterface $fieldDefinition,
37    TranslatableMarkup $entityUrlLabel,
38  ): array {
39    $targets = ['_self' => $entityUrlLabel];
40    $bundle = $fieldDefinition->getTargetBundle();
41    $entity_type_id = $fieldDefinition->getTargetEntityTypeId();
42    $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
43    foreach ($fields as $field) {
44      if ($field->getFieldStorageDefinition()->isBaseField()) {
45        continue;
46      }
47      $type = $field->getType();
48      $is_link_target_type = ($type === 'entity_reference' || $type === 'link');
49      if ($is_link_target_type) {
50        $targets[$field->getName()] = $field->getLabel();
51      }
52    }
53
54    return $targets;
55  }
56
57  /**
58   * {@inheritdoc}
59   */
60  public function resolveUrl(FieldItemListInterface $items, string $linkTarget): Url {
61    $parent = $items->getEntity();
62
63    if ($linkTarget === '_self') {
64      return $this->resolveEntitySelfUrl($parent) ?? Url::fromRoute('<none>');
65    }
66
67    if ($parent->hasField($linkTarget)) {
68      $target_items = $parent->get($linkTarget);
69      if (!$target_items->isEmpty()) {
70        return $this->resolveFieldTargetUrl($target_items) ?? Url::fromRoute('<none>');
71      }
72    }
73
74    return Url::fromRoute('<none>');
75  }
76
77  /**
78   * Gets the current entity URL when the entity can be viewed.
79   *
80   * @param \Drupal\Core\Entity\EntityInterface $parent
81   *   The entity that owns the name field.
82   *
83   * @return \Drupal\Core\Url|null
84   *   The entity URL, or NULL when it cannot be linked.
85   */
86  private function resolveEntitySelfUrl(EntityInterface $parent): ?Url {
87    $cannot_link = ($parent->isNew() || !$parent->access('view'));
88    if ($cannot_link) {
89      return NULL;
90    }
91
92    try {
93      return $parent->toUrl();
94    }
95    catch (UndefinedLinkTemplateException) {
96      return NULL;
97    }
98  }
99
100  /**
101   * Gets a URL from a supported link target field item list.
102   *
103   * @param \Drupal\Core\Field\FieldItemListInterface $target_items
104   *   The field item list configured as the link target.
105   *
106   * @return \Drupal\Core\Url|null
107   *   The resolved URL, or NULL when the field type is unsupported.
108   */
109  private function resolveFieldTargetUrl(FieldItemListInterface $target_items): ?Url {
110    return match ($target_items->getFieldDefinition()->getType()) {
111      'entity_reference' => $this->resolveEntityReferenceUrl($target_items),
112      'link' => $this->resolveLinkFieldUrl($target_items),
113      default => NULL,
114    };
115  }
116
117  /**
118   * Gets the first accessible referenced entity URL.
119   *
120   * @param \Drupal\Core\Field\FieldItemListInterface $target_items
121   *   The entity reference field item list.
122   *
123   * @return \Drupal\Core\Url|null
124   *   The referenced entity URL, or NULL when no target can be linked.
125   */
126  private function resolveEntityReferenceUrl(FieldItemListInterface $target_items): ?Url {
127    foreach ($target_items as $item) {
128      $entity_cannot_link = (
129        empty($item->entity)
130        || $item->entity->isNew()
131        || !$item->entity->access('view')
132      );
133      if ($entity_cannot_link) {
134        continue;
135      }
136      try {
137        return $item->entity->toUrl();
138      }
139      catch (UndefinedLinkTemplateException) {
140        continue;
141      }
142    }
143
144    return NULL;
145  }
146
147  /**
148   * Gets the first URL from a link field item list.
149   *
150   * @param \Drupal\Core\Field\FieldItemListInterface $target_items
151   *   The link field item list.
152   *
153   * @return \Drupal\Core\Url|null
154   *   The link field URL, or NULL when no URL is available.
155   */
156  private function resolveLinkFieldUrl(FieldItemListInterface $target_items): ?Url {
157    foreach ($target_items as $item) {
158      try {
159        $url = $item->getUrl();
160        if ($url) {
161          return $url;
162        }
163      }
164      catch (UndefinedLinkTemplateException) {
165        continue;
166      }
167    }
168
169    return NULL;
170  }
171
172}