Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / views / src / Entity / Render / EntityFieldRenderer.php
1 <?php
2
3 namespace Drupal\views\Entity\Render;
4
5 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
6 use Drupal\Core\Entity\Entity\EntityViewDisplay;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\EntityTypeInterface;
9 use Drupal\Core\Language\LanguageManagerInterface;
10 use Drupal\views\Plugin\views\field\EntityField;
11 use Drupal\views\Plugin\views\query\QueryPluginBase;
12 use Drupal\views\ResultRow;
13 use Drupal\views\ViewExecutable;
14
15 /**
16  * Renders entity fields.
17  *
18  * This is used to build render arrays for all entity field values of a view
19  * result set sharing the same relationship. An entity translation renderer is
20  * used internally to handle entity language properly.
21  */
22 class EntityFieldRenderer extends RendererBase {
23   use EntityTranslationRenderTrait;
24   use DependencySerializationTrait;
25
26   /**
27    * The relationship being handled.
28    *
29    * @var string
30    */
31   protected $relationship;
32
33   /**
34    * The entity manager.
35    *
36    * @var \Drupal\Core\Entity\EntityManagerInterface
37    */
38   protected $entityManager;
39
40   /**
41    * A list of indexes of rows whose fields have already been rendered.
42    *
43    * @var int[]
44    */
45   protected $processedRows = [];
46
47   /**
48    * Constructs an EntityFieldRenderer object.
49    *
50    * @param \Drupal\views\ViewExecutable $view
51    *   The view whose fields are being rendered.
52    * @param string $relationship
53    *   The relationship to be handled.
54    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
55    *   The language manager.
56    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
57    *   The entity type.
58    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
59    *   The entity manager.
60    */
61   public function __construct(ViewExecutable $view, $relationship, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) {
62     parent::__construct($view, $language_manager, $entity_type);
63     $this->relationship = $relationship;
64     $this->entityManager = $entity_manager;
65   }
66
67   /**
68    * {@inheritdoc}
69    */
70   public function getCacheContexts() {
71     return $this->getEntityTranslationRenderer()->getCacheContexts();
72   }
73
74   /**
75    * {@inheritdoc}
76    */
77   public function getEntityTypeId() {
78     return $this->entityType->id();
79   }
80
81   /**
82    * {@inheritdoc}
83    */
84   protected function getEntityManager() {
85     return $this->entityManager;
86   }
87
88   /**
89    * {@inheritdoc}
90    */
91   protected function getLanguageManager() {
92     return $this->languageManager;
93   }
94
95   /**
96    * {@inheritdoc}
97    */
98   protected function getView() {
99     return $this->view;
100   }
101
102   /**
103    * {@inheritdoc}
104    */
105   public function query(QueryPluginBase $query, $relationship = NULL) {
106     $this->getEntityTranslationRenderer()->query($query, $relationship);
107   }
108
109   /**
110    * Renders entity field data.
111    *
112    * @param \Drupal\views\ResultRow $row
113    *   A single row of the query result.
114    * @param \Drupal\views\Plugin\views\field\EntityField $field
115    *   (optional) A field to be rendered.
116    *
117    * @return array
118    *   A renderable array for the entity data contained in the result row.
119    */
120   public function render(ResultRow $row, EntityField $field = NULL) {
121     // The method is called for each field in each result row. In order to
122     // leverage multiple-entity building of formatter output, we build the
123     // render arrays for all fields in all rows on the first call.
124     if (!isset($this->build)) {
125       $this->build = $this->buildFields($this->view->result);
126     }
127
128     if (isset($field)) {
129       $field_id = $field->options['id'];
130       // Pick the render array for the row / field we are being asked to render,
131       // and remove it from $this->build to free memory as we progress.
132       if (isset($this->build[$row->index][$field_id])) {
133         $build = $this->build[$row->index][$field_id];
134         unset($this->build[$row->index][$field_id]);
135       }
136       elseif (isset($this->build[$row->index])) {
137         // In the uncommon case where a field gets rendered several times
138         // (typically through direct Views API calls), the pre-computed render
139         // array was removed by the unset() above. We have to manually rebuild
140         // the render array for the row.
141         $build = $this->buildFields([$row])[$row->index][$field_id];
142       }
143       else {
144         // In case the relationship is optional, there might not be any fields
145         // to render for this row.
146         $build = [];
147       }
148     }
149     else {
150       // Same logic as above, in the case where we are being called for a whole
151       // row.
152       if (isset($this->build[$row->index])) {
153         $build = $this->build[$row->index];
154         unset($this->build[$row->index]);
155       }
156       else {
157         $build = $this->buildFields([$row])[$row->index];
158       }
159     }
160
161     return $build;
162   }
163
164   /**
165    * Builds the render arrays for all fields of all result rows.
166    *
167    * The output is built using EntityViewDisplay objects to leverage
168    * multiple-entity building and ensure a common code path with regular entity
169    * view.
170    * - Each relationship is handled by a separate EntityFieldRenderer instance,
171    *   since it operates on its own set of entities. This also ensures different
172    *   entity types are handled separately, as they imply different
173    *   relationships.
174    * - Within each relationship, the fields to render are arranged in unique
175    *   sets containing each field at most once (an EntityViewDisplay can
176    *   only process a field once with given display options, but a View can
177    *   contain the same field several times with different display options).
178    * - For each set of fields, entities are processed by bundle, so that
179    *   formatters can operate on the proper field definition for the bundle.
180    *
181    * @param \Drupal\views\ResultRow[] $values
182    *   An array of all ResultRow objects returned from the query.
183    *
184    * @return array
185    *   A renderable array for the fields handled by this renderer.
186    *
187    * @see \Drupal\Core\Entity\Entity\EntityViewDisplay
188    */
189   protected function buildFields(array $values) {
190     $build = [];
191
192     if ($values && ($field_ids = $this->getRenderableFieldIds())) {
193       $entity_type_id = $this->getEntityTypeId();
194
195       // Collect the entities for the relationship, fetch the right translation,
196       // and group by bundle. For each result row, the corresponding entity can
197       // be obtained from any of the fields handlers, so we arbitrarily use the
198       // first one.
199       $entities_by_bundles = [];
200       $field = $this->view->field[current($field_ids)];
201       foreach ($values as $result_row) {
202         if ($entity = $field->getEntity($result_row)) {
203           $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
204         }
205       }
206
207       // Determine unique sets of fields that can be processed by the same
208       // display. Fields that appear several times in the View open additional
209       // "overflow" displays.
210       $display_sets = [];
211       foreach ($field_ids as $field_id) {
212         $field = $this->view->field[$field_id];
213         $field_name = $field->definition['field_name'];
214         $index = 0;
215         while (isset($display_sets[$index]['field_names'][$field_name])) {
216           $index++;
217         }
218         $display_sets[$index]['field_names'][$field_name] = $field;
219         $display_sets[$index]['field_ids'][$field_id] = $field;
220       }
221
222       // For each set of fields, build the output by bundle.
223       foreach ($display_sets as $display_fields) {
224         foreach ($entities_by_bundles as $bundle => $bundle_entities) {
225           // Create the display, and configure the field display options.
226           $display = EntityViewDisplay::create([
227             'targetEntityType' => $entity_type_id,
228             'bundle' => $bundle,
229             'status' => TRUE,
230           ]);
231           foreach ($display_fields['field_ids'] as $field) {
232             $display->setComponent($field->definition['field_name'], [
233               'type' => $field->options['type'],
234               'settings' => $field->options['settings'],
235             ]);
236           }
237           // Let the display build the render array for the entities.
238           $display_build = $display->buildMultiple($bundle_entities);
239           // Collect the field render arrays and index them using our internal
240           // row indexes and field IDs.
241           foreach ($display_build as $row_index => $entity_build) {
242             foreach ($display_fields['field_ids'] as $field_id => $field) {
243               $build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : [];
244             }
245           }
246         }
247       }
248     }
249
250     return $build;
251   }
252
253   /**
254    * Returns a list of names of entity fields to be rendered.
255    *
256    * @return string[]
257    *   An associative array of views fields.
258    */
259   protected function getRenderableFieldIds() {
260     $field_ids = [];
261     foreach ($this->view->field as $field_id => $field) {
262       if ($field instanceof EntityField && $field->relationship == $this->relationship) {
263         $field_ids[] = $field_id;
264       }
265     }
266     return $field_ids;
267   }
268
269 }