740ef532311f495a8c9e8f755eeb3c664bb56262
[yaffs-website] / web / core / modules / views / src / Plugin / views / style / StylePluginBase.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\style;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\Xss;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\views\Plugin\views\display\DisplayPluginBase;
9 use Drupal\views\Plugin\views\PluginBase;
10 use Drupal\views\Plugin\views\wizard\WizardInterface;
11 use Drupal\views\Render\ViewsRenderPipelineMarkup;
12 use Drupal\views\ViewExecutable;
13
14 /**
15  * @defgroup views_style_plugins Views style plugins
16  * @{
17  * Plugins that control how the collection of results is rendered in a view.
18  *
19  * Style plugins control how a view is displayed. For the most part, they are
20  * object wrappers around theme templates. Examples of styles include HTML
21  * lists, tables, full or teaser content views, etc.
22  *
23  * Many (but not all) style plugins have an optional row plugin, which
24  * displays a single record. Not all style plugins use row plugins, so it is
25  * up to the style plugin to set this up and call the row plugin. See the
26  * @link views_row_plugins Views row plugins topic @endlink for more
27  * information.
28  *
29  * Style plugins extend \Drupal\views\Plugin\views\style\StylePluginBase. They
30  * must be annotated with \Drupal\views\Annotation\ViewsStyle
31  * annotation, and they must be in namespace directory Plugin\views\style.
32  *
33  * @ingroup views_plugins
34  * @see plugin_api
35  */
36
37 /**
38  * Base class for views style plugins.
39  */
40 abstract class StylePluginBase extends PluginBase {
41
42   /**
43    * {@inheritdoc}
44    */
45   protected $usesOptions = TRUE;
46
47   /**
48    * Store all available tokens row rows.
49    */
50   protected $rowTokens = [];
51
52   /**
53    * Whether or not this style uses a row plugin.
54    *
55    * @var bool
56    */
57   protected $usesRowPlugin = FALSE;
58
59   /**
60    * Does the style plugin support custom css class for the rows.
61    *
62    * @var bool
63    */
64   protected $usesRowClass = FALSE;
65
66   /**
67    * Does the style plugin support grouping of rows.
68    *
69    * @var bool
70    */
71   protected $usesGrouping = TRUE;
72
73   /**
74    * Does the style plugin for itself support to add fields to its output.
75    *
76    * This option only makes sense on style plugins without row plugins, like
77    * for example table.
78    *
79    * @var bool
80    */
81   protected $usesFields = FALSE;
82
83   /**
84    * Stores the rendered field values, keyed by the row index and field name.
85    *
86    * @see \Drupal\views\Plugin\views\style\StylePluginBase::renderFields()
87    * @see \Drupal\views\Plugin\views\style\StylePluginBase::getField()
88    *
89    * @var array|null
90    */
91   protected $rendered_fields;
92
93   /**
94    * The theme function used to render the grouping set.
95    *
96    * Plugins may override this attribute if they wish to use some other theme
97    * function to render the grouping set.
98    *
99    * @var string
100    *
101    * @see StylePluginBase::renderGroupingSets()
102    */
103   protected $groupingTheme = 'views_view_grouping';
104
105   /**
106    * Should field labels be enabled by default.
107    *
108    * @var bool
109    */
110   protected $defaultFieldLabels = FALSE;
111
112   /**
113    * Overrides \Drupal\views\Plugin\views\PluginBase::init().
114    *
115    * The style options might come externally as the style can be sourced from at
116    * least two locations. If it's not included, look on the display.
117    */
118   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
119     parent::init($view, $display, $options);
120
121     if ($this->usesRowPlugin() && $display->getOption('row')) {
122       $this->view->rowPlugin = $display->getPlugin('row');
123     }
124
125     $this->options += [
126       'grouping' => [],
127     ];
128
129   }
130
131   /**
132    * {@inheritdoc}
133    */
134   public function destroy() {
135     parent::destroy();
136
137     if (isset($this->view->rowPlugin)) {
138       $this->view->rowPlugin->destroy();
139     }
140   }
141
142   /**
143    * Returns the usesRowPlugin property.
144    *
145    * @return bool
146    */
147   public function usesRowPlugin() {
148     return $this->usesRowPlugin;
149
150   }
151
152   /**
153    * Returns the usesRowClass property.
154    *
155    * @return bool
156    */
157   public function usesRowClass() {
158     return $this->usesRowClass;
159   }
160
161   /**
162    * Returns the usesGrouping property.
163    *
164    * @return bool
165    */
166   public function usesGrouping() {
167     return $this->usesGrouping;
168   }
169
170   /**
171    * Return TRUE if this style also uses fields.
172    *
173    * @return bool
174    */
175   public function usesFields() {
176     // If we use a row plugin, ask the row plugin. Chances are, we don't
177     // care, it does.
178     $row_uses_fields = FALSE;
179     if ($this->usesRowPlugin() && ($row_plugin = $this->displayHandler->getPlugin('row'))) {
180       $row_uses_fields = $row_plugin->usesFields();
181     }
182     // Otherwise, check the definition or the option.
183     return $row_uses_fields || $this->usesFields || !empty($this->options['uses_fields']);
184   }
185
186   /**
187    * Return TRUE if this style uses tokens.
188    *
189    * Used to ensure we don't fetch tokens when not needed for performance.
190    */
191   public function usesTokens() {
192     if ($this->usesRowClass()) {
193       $class = $this->options['row_class'];
194       if (strpos($class, '{{') !== FALSE) {
195         return TRUE;
196       }
197     }
198   }
199
200   /**
201    * Return TRUE if this style enables field labels by default.
202    *
203    * @return bool
204    */
205   public function defaultFieldLabels() {
206     return $this->defaultFieldLabels;
207   }
208
209   /**
210    * Return the token replaced row class for the specified row.
211    */
212   public function getRowClass($row_index) {
213     if ($this->usesRowClass()) {
214       $class = $this->options['row_class'];
215       if ($this->usesFields() && $this->view->field) {
216         $class = strip_tags($this->tokenizeValue($class, $row_index));
217       }
218
219       $classes = explode(' ', $class);
220       foreach ($classes as &$class) {
221         $class = Html::cleanCssIdentifier($class);
222       }
223       return implode(' ', $classes);
224     }
225   }
226
227   /**
228    * Take a value and apply token replacement logic to it.
229    */
230   public function tokenizeValue($value, $row_index) {
231     if (strpos($value, '{{') !== FALSE) {
232       // Row tokens might be empty, for example for node row style.
233       $tokens = isset($this->rowTokens[$row_index]) ? $this->rowTokens[$row_index] : [];
234       if (!empty($this->view->build_info['substitutions'])) {
235         $tokens += $this->view->build_info['substitutions'];
236       }
237
238       $value = $this->viewsTokenReplace($value, $tokens);
239     }
240     else {
241       // ::viewsTokenReplace() will run Xss::filterAdmin on the
242       // resulting string. We do the same here for consistency.
243       $value = Xss::filterAdmin($value);
244     }
245     return $value;
246   }
247
248   /**
249    * Should the output of the style plugin be rendered even if it's a empty view.
250    */
251   public function evenEmpty() {
252     return !empty($this->definition['even empty']);
253   }
254
255   /**
256    * {@inheritdoc}
257    */
258   protected function defineOptions() {
259     $options = parent::defineOptions();
260     $options['grouping'] = ['default' => []];
261     if ($this->usesRowClass()) {
262       $options['row_class'] = ['default' => ''];
263       $options['default_row_class'] = ['default' => TRUE];
264     }
265     $options['uses_fields'] = ['default' => FALSE];
266
267     return $options;
268   }
269
270   /**
271    * {@inheritdoc}
272    */
273   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
274     parent::buildOptionsForm($form, $form_state);
275     // Only fields-based views can handle grouping.  Style plugins can also exclude
276     // themselves from being groupable by setting their "usesGrouping" property
277     // to FALSE.
278     // @TODO: Document "usesGrouping" in docs.php when docs.php is written.
279     if ($this->usesFields() && $this->usesGrouping()) {
280       $options = ['' => $this->t('- None -')];
281       $field_labels = $this->displayHandler->getFieldLabels(TRUE);
282       $options += $field_labels;
283       // If there are no fields, we can't group on them.
284       if (count($options) > 1) {
285         // This is for backward compatibility, when there was just a single
286         // select form.
287         if (is_string($this->options['grouping'])) {
288           $grouping = $this->options['grouping'];
289           $this->options['grouping'] = [];
290           $this->options['grouping'][0]['field'] = $grouping;
291         }
292         if (isset($this->options['group_rendered']) && is_string($this->options['group_rendered'])) {
293           $this->options['grouping'][0]['rendered'] = $this->options['group_rendered'];
294           unset($this->options['group_rendered']);
295         }
296
297         $c = count($this->options['grouping']);
298         // Add a form for every grouping, plus one.
299         for ($i = 0; $i <= $c; $i++) {
300           $grouping = !empty($this->options['grouping'][$i]) ? $this->options['grouping'][$i] : [];
301           $grouping += ['field' => '', 'rendered' => TRUE, 'rendered_strip' => FALSE];
302           $form['grouping'][$i]['field'] = [
303             '#type' => 'select',
304             '#title' => $this->t('Grouping field Nr.@number', ['@number' => $i + 1]),
305             '#options' => $options,
306             '#default_value' => $grouping['field'],
307             '#description' => $this->t('You may optionally specify a field by which to group the records. Leave blank to not group.'),
308           ];
309           $form['grouping'][$i]['rendered'] = [
310             '#type' => 'checkbox',
311             '#title' => $this->t('Use rendered output to group rows'),
312             '#default_value' => $grouping['rendered'],
313             '#description' => $this->t('If enabled the rendered output of the grouping field is used to group the rows.'),
314             '#states' => [
315               'invisible' => [
316                 ':input[name="style_options[grouping][' . $i . '][field]"]' => ['value' => ''],
317               ],
318             ],
319           ];
320           $form['grouping'][$i]['rendered_strip'] = [
321             '#type' => 'checkbox',
322             '#title' => $this->t('Remove tags from rendered output'),
323             '#default_value' => $grouping['rendered_strip'],
324             '#states' => [
325               'invisible' => [
326                 ':input[name="style_options[grouping][' . $i . '][field]"]' => ['value' => ''],
327               ],
328             ],
329           ];
330         }
331       }
332     }
333
334     if ($this->usesRowClass()) {
335       $form['row_class'] = [
336         '#title' => $this->t('Row class'),
337         '#description' => $this->t('The class to provide on each row.'),
338         '#type' => 'textfield',
339         '#default_value' => $this->options['row_class'],
340       ];
341
342       if ($this->usesFields()) {
343         $form['row_class']['#description'] .= ' ' . $this->t('You may use field tokens from as per the "Replacement patterns" used in "Rewrite the output of this field" for all fields.');
344       }
345
346       $form['default_row_class'] = [
347         '#title' => $this->t('Add views row classes'),
348         '#description' => $this->t('Add the default row classes like @classes to the output. You can use this to quickly reduce the amount of markup the view provides by default, at the cost of making it more difficult to apply CSS.', ['@classes' => 'views-row']),
349         '#type' => 'checkbox',
350         '#default_value' => $this->options['default_row_class'],
351       ];
352     }
353
354     if (!$this->usesFields() || !empty($this->options['uses_fields'])) {
355       $form['uses_fields'] = [
356         '#type' => 'checkbox',
357         '#title' => $this->t('Force using fields'),
358         '#description' => $this->t('If neither the row nor the style plugin supports fields, this field allows to enable them, so you can for example use groupby.'),
359         '#default_value' => $this->options['uses_fields'],
360       ];
361     }
362   }
363
364   /**
365    * {@inheritdoc}
366    */
367   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
368     // Don't run validation on style plugins without the grouping setting.
369     if ($form_state->hasValue(['style_options', 'grouping'])) {
370       // Don't save grouping if no field is specified.
371       $groupings = $form_state->getValue(['style_options', 'grouping']);
372       foreach ($groupings as $index => $grouping) {
373         if (empty($grouping['field'])) {
374           $form_state->unsetValue(['style_options', 'grouping', $index]);
375         }
376       }
377     }
378   }
379
380   /**
381    * Provide a form in the views wizard if this style is selected.
382    *
383    * @param array $form
384    *   An associative array containing the structure of the form.
385    * @param \Drupal\Core\Form\FormStateInterface $form_state
386    *   The current state of the form.
387    * @param string $type
388    *   The display type, either block or page.
389    */
390   public function wizardForm(&$form, FormStateInterface $form_state, $type) {
391   }
392
393   /**
394    * Alter the options of a display before they are added to the view.
395    *
396    * @param array $form
397    *   An associative array containing the structure of the form.
398    * @param \Drupal\Core\Form\FormStateInterface $form_state
399    *   The current state of the form.
400    * @param \Drupal\views\Plugin\views\wizard\WizardInterface $wizard
401    *   The current used wizard.
402    * @param array $display_options
403    *   The options which will be used on the view. The style plugin should
404    *   alter this to its own needs.
405    * @param string $display_type
406    *   The display type, either block or page.
407    */
408   public function wizardSubmit(&$form, FormStateInterface $form_state, WizardInterface $wizard, &$display_options, $display_type) {
409   }
410
411   /**
412    * Called by the view builder to see if this style handler wants to
413    * interfere with the sorts. If so it should build; if it returns
414    * any non-TRUE value, normal sorting will NOT be added to the query.
415    */
416   public function buildSort() {
417     return TRUE;
418   }
419
420   /**
421    * Called by the view builder to let the style build a second set of
422    * sorts that will come after any other sorts in the view.
423    */
424   public function buildSortPost() {}
425
426   /**
427    * Allow the style to do stuff before each row is rendered.
428    *
429    * @param $result
430    *   The full array of results from the query.
431    */
432   public function preRender($result) {
433     if (!empty($this->view->rowPlugin)) {
434       $this->view->rowPlugin->preRender($result);
435     }
436   }
437
438   /**
439    * Renders a group of rows of the grouped view.
440    *
441    * @param array $rows
442    *   The result rows rendered in this group.
443    *
444    * @return array
445    *   The render array containing the single group theme output.
446    */
447   protected function renderRowGroup(array $rows = []) {
448     return [
449       '#theme' => $this->themeFunctions(),
450       '#view' => $this->view,
451       '#rows' => $rows,
452     ];
453   }
454
455   /**
456    * Render the display in this style.
457    */
458   public function render() {
459     if ($this->usesRowPlugin() && empty($this->view->rowPlugin)) {
460       trigger_error('Drupal\views\Plugin\views\style\StylePluginBase: Missing row plugin', E_WARNING);
461       return [];
462     }
463
464     // Group the rows according to the grouping instructions, if specified.
465     $sets = $this->renderGrouping(
466       $this->view->result,
467       $this->options['grouping'],
468       TRUE
469     );
470
471     return $this->renderGroupingSets($sets);
472   }
473
474   /**
475    * Render the grouping sets.
476    *
477    * Plugins may override this method if they wish some other way of handling
478    * grouping.
479    *
480    * @param $sets
481    *   An array keyed by group content containing the grouping sets to render.
482    *   Each set contains the following associative array:
483    *   - group: The group content.
484    *   - level: The hierarchical level of the grouping.
485    *   - rows: The result rows to be rendered in this group..
486    *
487    * @return array
488    *   Render array of grouping sets.
489    */
490   public function renderGroupingSets($sets) {
491     $output = [];
492     $theme_functions = $this->view->buildThemeFunctions($this->groupingTheme);
493     foreach ($sets as $set) {
494       $level = isset($set['level']) ? $set['level'] : 0;
495
496       $row = reset($set['rows']);
497       // Render as a grouping set.
498       if (is_array($row) && isset($row['group'])) {
499         $single_output = [
500           '#theme' => $theme_functions,
501           '#view' => $this->view,
502           '#grouping' => $this->options['grouping'][$level],
503           '#rows' => $set['rows'],
504         ];
505       }
506       // Render as a record set.
507       else {
508         if ($this->usesRowPlugin()) {
509           foreach ($set['rows'] as $index => $row) {
510             $this->view->row_index = $index;
511             $set['rows'][$index] = $this->view->rowPlugin->render($row);
512           }
513         }
514
515         $single_output = $this->renderRowGroup($set['rows']);
516       }
517
518       $single_output['#grouping_level'] = $level;
519       $single_output['#title'] = $set['group'];
520       $output[] = $single_output;
521     }
522     unset($this->view->row_index);
523     return $output;
524   }
525
526   /**
527    * Group records as needed for rendering.
528    *
529    * @param $records
530    *   An array of records from the view to group.
531    * @param $groupings
532    *   An array of grouping instructions on which fields to group. If empty, the
533    *   result set will be given a single group with an empty string as a label.
534    * @param $group_rendered
535    *   Boolean value whether to use the rendered or the raw field value for
536    *   grouping. If set to NULL the return is structured as before
537    *   Views 7.x-3.0-rc2. After Views 7.x-3.0 this boolean is only used if
538    *   $groupings is an old-style string or if the rendered option is missing
539    *   for a grouping instruction.
540    * @return
541    *   The grouped record set.
542    *   A nested set structure is generated if multiple grouping fields are used.
543    *
544    *   @code
545    *   array(
546    *     'grouping_field_1:grouping_1' => array(
547    *       'group' => 'grouping_field_1:content_1',
548    *       'level' => 0,
549    *       'rows' => array(
550    *         'grouping_field_2:grouping_a' => array(
551    *           'group' => 'grouping_field_2:content_a',
552    *           'level' => 1,
553    *           'rows' => array(
554    *             $row_index_1 => $row_1,
555    *             $row_index_2 => $row_2,
556    *             // ...
557    *           )
558    *         ),
559    *       ),
560    *     ),
561    *     'grouping_field_1:grouping_2' => array(
562    *       // ...
563    *     ),
564    *   )
565    *   @endcode
566    */
567   public function renderGrouping($records, $groupings = [], $group_rendered = NULL) {
568     // This is for backward compatibility, when $groupings was a string
569     // containing the ID of a single field.
570     if (is_string($groupings)) {
571       $rendered = $group_rendered === NULL ? TRUE : $group_rendered;
572       $groupings = [['field' => $groupings, 'rendered' => $rendered]];
573     }
574
575     // Make sure fields are rendered
576     $this->renderFields($this->view->result);
577     $sets = [];
578     if ($groupings) {
579       foreach ($records as $index => $row) {
580         // Iterate through configured grouping fields to determine the
581         // hierarchically positioned set where the current row belongs to.
582         // While iterating, parent groups, that do not exist yet, are added.
583         $set = &$sets;
584         foreach ($groupings as $level => $info) {
585           $field = $info['field'];
586           $rendered = isset($info['rendered']) ? $info['rendered'] : $group_rendered;
587           $rendered_strip = isset($info['rendered_strip']) ? $info['rendered_strip'] : FALSE;
588           $grouping = '';
589           $group_content = '';
590           // Group on the rendered version of the field, not the raw.  That way,
591           // we can control any special formatting of the grouping field through
592           // the admin or theme layer or anywhere else we'd like.
593           if (isset($this->view->field[$field])) {
594             $group_content = $this->getField($index, $field);
595             if ($this->view->field[$field]->options['label']) {
596               $group_content = $this->view->field[$field]->options['label'] . ': ' . $group_content;
597             }
598             if ($rendered) {
599               $grouping = (string) $group_content;
600               if ($rendered_strip) {
601                 $group_content = $grouping = strip_tags(htmlspecialchars_decode($group_content));
602               }
603             }
604             else {
605               $grouping = $this->getFieldValue($index, $field);
606               // Not all field handlers return a scalar value,
607               // e.g. views_handler_field_field.
608               if (!is_scalar($grouping)) {
609                 $grouping = hash('sha256', serialize($grouping));
610               }
611             }
612           }
613
614           // Create the group if it does not exist yet.
615           if (empty($set[$grouping])) {
616             $set[$grouping]['group'] = $group_content;
617             $set[$grouping]['level'] = $level;
618             $set[$grouping]['rows'] = [];
619           }
620
621           // Move the set reference into the row set of the group we just determined.
622           $set = &$set[$grouping]['rows'];
623         }
624         // Add the row to the hierarchically positioned row set we just determined.
625         $set[$index] = $row;
626       }
627     }
628     else {
629       // Create a single group with an empty grouping field.
630       $sets[''] = [
631         'group' => '',
632         'rows' => $records,
633       ];
634     }
635
636     // If this parameter isn't explicitly set, modify the output to be fully
637     // backward compatible to code before Views 7.x-3.0-rc2.
638     // @TODO Remove this as soon as possible e.g. October 2020
639     if ($group_rendered === NULL) {
640       $old_style_sets = [];
641       foreach ($sets as $group) {
642         $old_style_sets[$group['group']] = $group['rows'];
643       }
644       $sets = $old_style_sets;
645     }
646
647     return $sets;
648   }
649
650   /**
651    * Renders all of the fields for a given style and store them on the object.
652    *
653    * @param array $result
654    *   The result array from $view->result
655    */
656   protected function renderFields(array $result) {
657     if (!$this->usesFields()) {
658       return;
659     }
660
661     if (!isset($this->rendered_fields)) {
662       $this->rendered_fields = [];
663       $this->view->row_index = 0;
664       $field_ids = array_keys($this->view->field);
665
666       // Only tokens relating to field handlers preceding the one we invoke
667       // ::getRenderTokens() on are returned, so here we need to pick the last
668       // available field handler.
669       $render_tokens_field_id = end($field_ids);
670
671       // If all fields have a field::access FALSE there might be no fields, so
672       // there is no reason to execute this code.
673       if (!empty($field_ids)) {
674         $renderer = $this->getRenderer();
675         /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
676         $cache_plugin = $this->view->display_handler->getPlugin('cache');
677         $max_age = $cache_plugin->getCacheMaxAge();
678
679         /** @var \Drupal\views\ResultRow $row */
680         foreach ($result as $index => $row) {
681           $this->view->row_index = $index;
682
683           // Here we implement render caching for result rows. Since we never
684           // build a render array for single rows, given that style templates
685           // need individual field markup to support proper theming, we build
686           // a raw render array containing all field render arrays and cache it.
687           // This allows us to cache the markup of the various children, that is
688           // individual fields, which is then available for style template
689           // preprocess functions, later in the rendering workflow.
690           // @todo Fetch all the available cached row items in one single cache
691           //   get operation, once https://www.drupal.org/node/2453945 is fixed.
692           $data = [
693             '#pre_render' => [[$this, 'elementPreRenderRow']],
694             '#row' => $row,
695             '#cache' => [
696               'keys' => $cache_plugin->getRowCacheKeys($row),
697               'tags' => $cache_plugin->getRowCacheTags($row),
698               'max-age' => $max_age,
699             ],
700             '#cache_properties' => $field_ids,
701           ];
702           $renderer->addCacheableDependency($data, $this->view->storage);
703           // Views may be rendered both inside and outside a render context:
704           // - HTML views are rendered inside a render context: then we want to
705           //   use ::render(), so that attachments and cacheability are bubbled.
706           // - non-HTML views are rendered outside a render context: then we
707           //   want to use ::renderPlain(), so that no bubbling happens
708           if ($renderer->hasRenderContext()) {
709             $renderer->render($data);
710           }
711           else {
712             $renderer->renderPlain($data);
713           }
714
715           // Extract field output from the render array and post process it.
716           $fields = $this->view->field;
717           $rendered_fields = &$this->rendered_fields[$index];
718           $post_render_tokens = [];
719           foreach ($field_ids as $id) {
720             $rendered_fields[$id] = $data[$id]['#markup'];
721             $tokens = $fields[$id]->postRender($row, $rendered_fields[$id]);
722             if ($tokens) {
723               $post_render_tokens += $tokens;
724             }
725           }
726
727           // Populate row tokens.
728           $this->rowTokens[$index] = $this->view->field[$render_tokens_field_id]->getRenderTokens([]);
729
730           // Replace post-render tokens.
731           if ($post_render_tokens) {
732             $placeholders = array_keys($post_render_tokens);
733             $values = array_values($post_render_tokens);
734             foreach ($this->rendered_fields[$index] as &$rendered_field) {
735               // Placeholders and rendered fields have been processed by the
736               // render system and are therefore safe.
737               $rendered_field = ViewsRenderPipelineMarkup::create(str_replace($placeholders, $values, $rendered_field));
738             }
739           }
740         }
741       }
742
743       unset($this->view->row_index);
744     }
745   }
746
747   /**
748    * #pre_render callback for view row field rendering.
749    *
750    * @see self::render()
751    *
752    * @param array $data
753    *   The element to #pre_render
754    *
755    * @return array
756    *   The processed element.
757    */
758   public function elementPreRenderRow(array $data) {
759     // Render row fields.
760     foreach ($this->view->field as $id => $field) {
761       $data[$id] = ['#markup' => $field->theme($data['#row'])];
762     }
763     return $data;
764   }
765
766   /**
767    * Gets a rendered field.
768    *
769    * @param int $index
770    *   The index count of the row.
771    * @param string $field
772    *   The ID of the field.
773    *
774    * @return \Drupal\Component\Render\MarkupInterface|null
775    *   The output of the field, or NULL if it was empty.
776    */
777   public function getField($index, $field) {
778     if (!isset($this->rendered_fields)) {
779       $this->renderFields($this->view->result);
780     }
781
782     if (isset($this->rendered_fields[$index][$field])) {
783       return $this->rendered_fields[$index][$field];
784     }
785   }
786
787   /**
788    * Get the raw field value.
789    *
790    * @param $index
791    *   The index count of the row.
792    * @param $field
793    *   The id of the field.
794    */
795   public function getFieldValue($index, $field) {
796     $this->view->row_index = $index;
797     $value = $this->view->field[$field]->getValue($this->view->result[$index]);
798     unset($this->view->row_index);
799     return $value;
800   }
801
802   /**
803    * {@inheritdoc}
804    */
805   public function validate() {
806     $errors = parent::validate();
807
808     if ($this->usesRowPlugin()) {
809       $plugin = $this->displayHandler->getPlugin('row');
810       if (empty($plugin)) {
811         $errors[] = $this->t('Style @style requires a row style but the row plugin is invalid.', ['@style' => $this->definition['title']]);
812       }
813       else {
814         $result = $plugin->validate();
815         if (!empty($result) && is_array($result)) {
816           $errors = array_merge($errors, $result);
817         }
818       }
819     }
820     return $errors;
821   }
822
823   /**
824    * {@inheritdoc}
825    */
826   public function query() {
827     parent::query();
828     if (isset($this->view->rowPlugin)) {
829       $this->view->rowPlugin->query();
830     }
831   }
832
833 }
834
835 /**
836  * @}
837  */