5 * Preprocessors and helper functions to make theming easier.
8 use Drupal\Component\Utility\Html;
9 use Drupal\Component\Utility\Xss;
10 use Drupal\Core\Template\Attribute;
14 * Prepares variables for view templates.
16 * Default template: views-view.html.twig.
18 * @param array $variables
19 * An associative array containing:
20 * - view: The ViewExecutable object.
22 function template_preprocess_views_view(&$variables) {
23 $view = $variables['view'];
24 $id = $view->storage->id();
26 $variables['css_name'] = Html::cleanCssIdentifier($id);
27 $variables['id'] = $id;
28 $variables['display_id'] = $view->current_display;
29 // Override the title to be empty by default. For example, if viewing a page
30 // view, 'title' will already be populated in $variables. This can still be
31 // overridden to use a title when needed. See views_ui_preprocess_views_view()
32 // for an example of this.
33 $variables['title'] = '';
35 $css_class = $view->display_handler->getOption('css_class');
36 if (!empty($css_class)) {
37 $variables['css_class'] = preg_replace('/[^a-zA-Z0-9- ]/', '-', $css_class);
38 $variables['attributes']['class'][] = $variables['css_class'];
41 // contextual_preprocess() only works on render elements, and since this theme
42 // hook is not for a render element, contextual_preprocess() falls back to the
43 // first argument and checks if that is a render element. The first element is
44 // view_array. However, view_array does not get set anywhere, but since we do
45 // have access to the View object, we can also access the View object's
46 // element, which is a render element that does have #contextual_links set if
47 // the display supports it. Doing this allows contextual_preprocess() to
48 // access this theme hook's render element, and therefore allows this template
49 // to have contextual links.
51 $variables['view_array'] = $variables['view']->element;
53 // Attachments are always updated with the outer view, never by themselves,
54 // so they do not have dom ids.
55 if (empty($view->is_attachment)) {
56 // Our JavaScript needs to have some means to find the HTML belonging to
59 // It is true that the DIV wrapper has classes denoting the name of the view
60 // and its display ID, but this is not enough to unequivocally match a view
61 // with its HTML, because one view may appear several times on the page. So
62 // we set up a hash with the current time, $dom_id, to issue a "unique"
63 // identifier for each view. This identifier is written to both
64 // drupalSettings and the DIV wrapper.
65 $variables['dom_id'] = $view->dom_id;
70 * Prepares variables for views fields templates.
72 * Default template: views-view-fields.html.twig.
74 * @param array $variables
75 * An associative array containing:
76 * - view: The view object.
77 * - options: An array of options. Each option contains:
78 * - inline: An array that contains the fields that are to be
80 * - default_field_elements: If default field wrapper
81 * elements are to be provided.
82 * - hide_empty: Whether the field is to be hidden if empty.
83 * - element_default_classes: If the default classes are to be added.
84 * - separator: A string to be placed between inline fields to keep them
86 * - row: An array containing information about the current row.
88 function template_preprocess_views_view_fields(&$variables) {
89 $view = $variables['view'];
91 // Loop through the fields for this view.
92 $previous_inline = FALSE;
93 // Ensure it's at least an empty array.
94 $variables['fields'] = [];
95 /** @var \Drupal\views\ResultRow $row */
96 $row = $variables['row'];
97 foreach ($view->field as $id => $field) {
98 // render this even if set to exclude so it can be used elsewhere.
99 $field_output = $view->style_plugin->getField($row->index, $id);
100 $empty = $field->isValueEmpty($field_output, $field->options['empty_zero']);
101 if (empty($field->options['exclude']) && (!$empty || (empty($field->options['hide_empty']) && empty($variables['options']['hide_empty'])))) {
102 $object = new stdClass();
103 $object->handler = $view->field[$id];
104 $object->inline = !empty($variables['options']['inline'][$id]);
105 // Set up default value of the flag that indicates whether to display a
106 // colon after the label.
107 $object->has_label_colon = FALSE;
109 $object->element_type = $object->handler->elementType(TRUE, !$variables['options']['default_field_elements'], $object->inline);
110 if ($object->element_type) {
112 if ($object->handler->options['element_default_classes']) {
113 $attributes['class'][] = 'field-content';
116 if ($classes = $object->handler->elementClasses($row->index)) {
117 $attributes['class'][] = $classes;
119 $object->element_attributes = new Attribute($attributes);
122 $object->content = $field_output;
123 if (isset($view->field[$id]->field_alias) && isset($row->{$view->field[$id]->field_alias})) {
124 $object->raw = $row->{$view->field[$id]->field_alias};
127 // Make sure it exists to reduce NOTICE.
131 if (!empty($variables['options']['separator']) && $previous_inline && $object->inline && $object->content) {
132 $object->separator = Xss::filterAdmin($variables['options']['separator']);
135 $object->class = Html::cleanCssIdentifier($id);
137 $previous_inline = $object->inline;
138 // Set up field wrapper element.
139 $object->wrapper_element = $object->handler->elementWrapperType(TRUE, TRUE);
140 if ($object->wrapper_element === '' && $variables['options']['default_field_elements']) {
141 $object->wrapper_element = $object->inline ? 'span' : 'div';
144 // Set up field wrapper attributes if field wrapper was set.
145 if ($object->wrapper_element) {
147 if ($object->handler->options['element_default_classes']) {
148 $attributes['class'][] = 'views-field';
149 $attributes['class'][] = 'views-field-' . $object->class;
152 if ($classes = $object->handler->elementWrapperClasses($row->index)) {
153 $attributes['class'][] = $classes;
155 $object->wrapper_attributes = new Attribute($attributes);
158 // Set up field label
159 $object->label = $view->field[$id]->label();
161 // Set up field label wrapper and its attributes.
162 if ($object->label) {
163 // Add a colon in a label suffix.
164 if ($object->handler->options['element_label_colon']) {
165 $object->label_suffix = ': ';
166 $object->has_label_colon = TRUE;
169 // Set up label HTML element.
170 $object->label_element = $object->handler->elementLabelType(TRUE, !$variables['options']['default_field_elements']);
172 // Set up label attributes.
173 if ($object->label_element) {
175 if ($object->handler->options['element_default_classes']) {
176 $attributes['class'][] = 'views-label';
177 $attributes['class'][] = 'views-label-' . $object->class;
180 // Set up field label.
181 $element_label_class = $object->handler->elementLabelClasses($row->index);
182 if ($element_label_class) {
183 $attributes['class'][] = $element_label_class;
185 $object->label_attributes = new Attribute($attributes);
189 $variables['fields'][$id] = $object;
196 * Prepares variables for views single grouping templates.
198 * Default template: views-view-grouping.html.twig.
200 * @param array $variables
201 * An associative array containing:
202 * - view: The view object.
203 * - rows: The rows returned from the view.
204 * - grouping_level: Integer indicating the hierarchical level of the
206 * - content: The content to be grouped.
207 * - title: The group heading.
209 function template_preprocess_views_view_grouping(&$variables) {
210 $variables['content'] = $variables['view']->style_plugin->renderGroupingSets($variables['rows'], $variables['grouping_level']);
214 * Prepares variables for views field templates.
216 * Default template: views-view-field.html.twig.
218 * @param array $variables
219 * An associative array containing:
220 * - field: The field handler object for the current field.
221 * - row: Object representing the raw result of the SQL query for the current
223 * - view: Instance of the ViewExecutable object for the parent view.
225 function template_preprocess_views_view_field(&$variables) {
226 $variables['output'] = $variables['field']->advancedRender($variables['row']);
230 * Prepares variables for views summary templates.
232 * The summary prints a single record from a row, with fields.
234 * Default template: views-view-summary.html.twig.
236 * @param array $variables
237 * An associative array containing:
238 * - view: A ViewExecutable object.
239 * - rows: The raw row data.
241 function template_preprocess_views_view_summary(&$variables) {
242 /** @var \Drupal\views\ViewExecutable $view */
243 $view = $variables['view'];
244 $argument = $view->argument[$view->build_info['summary_level']];
248 if (!empty($view->exposed_raw_input)) {
249 $url_options['query'] = $view->exposed_raw_input;
253 // Force system path.
254 \Drupal::url('<current>', [], ['alias' => TRUE]),
255 // Force system path.
256 Url::fromRouteMatch(\Drupal::routeMatch())->setOption('alias', TRUE)->toString(),
257 // Could be an alias.
258 \Drupal::url('<current>'),
259 // Could be an alias.
260 Url::fromRouteMatch(\Drupal::routeMatch())->toString(),
262 $active_urls = array_combine($active_urls, $active_urls);
264 // Collect all arguments foreach row, to be able to alter them for example
265 // by the validator. This is not done per single argument value, because this
266 // could cause performance problems.
269 foreach ($variables['rows'] as $id => $row) {
270 $row_args[$id] = $argument->summaryArgument($row);
272 $argument->processSummaryArguments($row_args);
274 foreach ($variables['rows'] as $id => $row) {
275 $variables['rows'][$id]->attributes = [];
276 $variables['rows'][$id]->link = $argument->summaryName($row);
278 $args[$argument->position] = $row_args[$id];
280 if (!empty($argument->options['summary_options']['base_path'])) {
281 $base_path = $argument->options['summary_options']['base_path'];
282 $tokens = $view->getDisplay()->getArgumentsTokens();
283 $base_path = $argument->globalTokenReplace($base_path, $tokens);
284 // @todo Views should expect and store a leading /. See:
285 // https://www.drupal.org/node/2423913
286 $url = Url::fromUserInput('/' . $base_path);
288 /** @var \Symfony\Component\Routing\Route $route */
289 $route_name = $url->getRouteName();
290 $route = \Drupal::service('router.route_provider')->getRouteByName($route_name);
292 $route_variables = $route->compile()->getVariables();
293 $parameters = $url->getRouteParameters();
295 foreach ($route_variables as $variable_name) {
296 $parameters[$variable_name] = array_shift($args);
299 $url->setRouteParameters($parameters);
301 catch (Exception $e) {
302 // If the given route doesn't exist, default to <front>
303 $url = Url::fromRoute('<front>');
307 $url = $view->getUrl($args)->setOptions($url_options);
309 $variables['rows'][$id]->url = $url->toString();
310 $variables['rows'][$id]->count = intval($row->{$argument->count_alias});
311 $variables['rows'][$id]->attributes = new Attribute($variables['rows'][$id]->attributes);
312 $variables['rows'][$id]->active = isset($active_urls[$variables['rows'][$id]->url]);
317 * Prepares variables for unformatted summary view templates.
319 * Default template: views-view-summary-unformatted.html.twig.
321 * @param array $variables
322 * An associative array containing:
323 * - view: A ViewExecutable object.
324 * - rows: The raw row data.
325 * - options: An array of options. Each option contains:
326 * - separator: A string to be placed between inline fields to keep them
329 function template_preprocess_views_view_summary_unformatted(&$variables) {
330 /** @var \Drupal\views\ViewExecutable $view */
331 $view = $variables['view'];
332 $argument = $view->argument[$view->build_info['summary_level']];
336 if (!empty($view->exposed_raw_input)) {
337 $url_options['query'] = $view->exposed_raw_input;
342 // Force system path.
343 \Drupal::url('<current>', [], ['alias' => TRUE]),
344 // Could be an alias.
345 \Drupal::url('<current>'),
347 $active_urls = array_combine($active_urls, $active_urls);
349 // Collect all arguments for each row, to be able to alter them for example
350 // by the validator. This is not done per single argument value, because
351 // this could cause performance problems.
353 foreach ($variables['rows'] as $id => $row) {
354 $row_args[$id] = $argument->summaryArgument($row);
356 $argument->processSummaryArguments($row_args);
358 foreach ($variables['rows'] as $id => $row) {
359 // Only false on first time.
361 $variables['rows'][$id]->separator = Xss::filterAdmin($variables['options']['separator']);
363 $variables['rows'][$id]->attributes = [];
364 $variables['rows'][$id]->link = $argument->summaryName($row);
366 $args[$argument->position] = $row_args[$id];
368 if (!empty($argument->options['summary_options']['base_path'])) {
369 $base_path = $argument->options['summary_options']['base_path'];
370 $tokens = $view->getDisplay()->getArgumentsTokens();
371 $base_path = $argument->globalTokenReplace($base_path, $tokens);
372 // @todo Views should expect and store a leading /. See:
373 // https://www.drupal.org/node/2423913
374 $url = Url::fromUserInput('/' . $base_path);
376 /** @var \Symfony\Component\Routing\Route $route */
377 $route = \Drupal::service('router.route_provider')->getRouteByName($url->getRouteName());
378 $route_variables = $route->compile()->getVariables();
379 $parameters = $url->getRouteParameters();
381 foreach ($route_variables as $variable_name) {
382 $parameters[$variable_name] = array_shift($args);
385 $url->setRouteParameters($parameters);
387 catch (Exception $e) {
388 // If the given route doesn't exist, default to <front>
389 $url = Url::fromRoute('<front>');
393 $url = $view->getUrl($args)->setOptions($url_options);
395 $variables['rows'][$id]->url = $url->toString();
396 $variables['rows'][$id]->count = intval($row->{$argument->count_alias});
397 $variables['rows'][$id]->active = isset($active_urls[$variables['rows'][$id]->url]);
398 $variables['rows'][$id]->attributes = new Attribute($variables['rows'][$id]->attributes);
403 * Prepares variables for views table templates.
405 * Default template: views-view-table.html.twig.
407 * @param array $variables
408 * An associative array containing:
409 * - view: A ViewExecutable object.
410 * - rows: The raw row data.
412 function template_preprocess_views_view_table(&$variables) {
413 $view = $variables['view'];
415 // We need the raw data for this grouping, which is passed in
416 // as $variables['rows'].
417 // However, the template also needs to use for the rendered fields. We
418 // therefore swap the raw data out to a new variable and reset $variables['rows']
419 // so that it can get rebuilt.
420 // Store rows so that they may be used by further preprocess functions.
421 $result = $variables['result'] = $variables['rows'];
422 $variables['rows'] = [];
423 $variables['header'] = [];
425 $options = $view->style_plugin->options;
426 $handler = $view->style_plugin;
428 $fields = &$view->field;
429 $columns = $handler->sanitizeColumns($options['columns'], $fields);
431 $active = !empty($handler->active) ? $handler->active : '';
432 $order = !empty($handler->order) ? $handler->order : 'asc';
434 // A boolean variable which stores whether the table has a responsive class.
437 // For the actual site we want to not render full URLs, because this would
438 // make pagers cacheable per URL, which is problematic in blocks, for example.
439 // For the actual live preview though the javascript relies on properly
441 $route_name = !empty($view->live_preview) ? '<current>' : '<none>';
443 $query = tablesort_get_query_parameters();
444 if (isset($view->exposed_raw_input)) {
445 $query += $view->exposed_raw_input;
448 // A boolean to store whether the table's header has any labels.
449 $has_header_labels = FALSE;
450 foreach ($columns as $field => $column) {
451 // Create a second variable so we can easily find what fields we have and
452 // what the CSS classes should be.
453 $variables['fields'][$field] = Html::cleanCssIdentifier($field);
454 if ($active == $field) {
455 $variables['fields'][$field] .= ' is-active';
458 // Render the header labels.
459 if ($field == $column && empty($fields[$field]->options['exclude'])) {
460 $label = !empty($fields[$field]) ? $fields[$field]->label() : '';
461 if (empty($options['info'][$field]['sortable']) || !$fields[$field]->clickSortable()) {
462 $variables['header'][$field]['content'] = $label;
465 $initial = !empty($options['info'][$field]['default_sort_order']) ? $options['info'][$field]['default_sort_order'] : 'asc';
467 if ($active == $field) {
468 $initial = ($order == 'asc') ? 'desc' : 'asc';
471 $title = t('sort by @s', ['@s' => $label]);
472 if ($active == $field) {
473 $variables['header'][$field]['sort_indicator'] = [
474 '#theme' => 'tablesort_indicator',
475 '#style' => $initial,
479 $query['order'] = $field;
480 $query['sort'] = $initial;
484 $url = new Url($route_name, [], $link_options);
485 $variables['header'][$field]['url'] = $url->toString();
486 $variables['header'][$field]['content'] = $label;
487 $variables['header'][$field]['title'] = $title;
490 $variables['header'][$field]['default_classes'] = $fields[$field]->options['element_default_classes'];
491 // Set up the header label class.
492 $variables['header'][$field]['attributes'] = [];
493 $class = $fields[$field]->elementLabelClasses(0);
495 $variables['header'][$field]['attributes']['class'][] = $class;
497 // Add responsive header classes.
498 if (!empty($options['info'][$field]['responsive'])) {
499 $variables['header'][$field]['attributes']['class'][] = $options['info'][$field]['responsive'];
502 // Add a CSS align class to each field if one was set.
503 if (!empty($options['info'][$field]['align'])) {
504 $variables['header'][$field]['attributes']['class'][] = Html::cleanCssIdentifier($options['info'][$field]['align']);
506 // Add a header label wrapper if one was selected.
507 if ($variables['header'][$field]['content']) {
508 $element_label_type = $fields[$field]->elementLabelType(TRUE, TRUE);
509 if ($element_label_type) {
510 $variables['header'][$field]['wrapper_element'] = $element_label_type;
512 // Improves accessibility of complex tables.
513 $variables['header'][$field]['attributes']['id'] = Html::getUniqueId('view-' . $field . '-table-column');
515 // Check if header label is not empty.
516 if (!empty($variables['header'][$field]['content'])) {
517 $has_header_labels = TRUE;
520 $variables['header'][$field]['attributes'] = new Attribute($variables['header'][$field]['attributes']);
523 // Add a CSS align class to each field if one was set.
524 if (!empty($options['info'][$field]['align'])) {
525 $variables['fields'][$field] .= ' ' . Html::cleanCssIdentifier($options['info'][$field]['align']);
528 // Render each field into its appropriate column.
529 foreach ($result as $num => $row) {
531 // Skip building the attributes and content if the field is to be excluded
533 if (!empty($fields[$field]->options['exclude'])) {
537 // Reference to the column in the loop to make the code easier to read.
538 $column_reference =& $variables['rows'][$num]['columns'][$column];
540 $column_reference['default_classes'] = $fields[$field]->options['element_default_classes'];
542 // Set the field key to the column so it can be used for adding classes
544 $column_reference['fields'][] = $variables['fields'][$field];
546 // Add field classes.
547 if (!isset($column_reference['attributes'])) {
548 $column_reference['attributes'] = [];
551 if ($classes = $fields[$field]->elementClasses($num)) {
552 $column_reference['attributes']['class'][] = $classes;
555 // Add responsive header classes.
556 if (!empty($options['info'][$field]['responsive'])) {
557 $column_reference['attributes']['class'][] = $options['info'][$field]['responsive'];
560 // Improves accessibility of complex tables.
561 if (isset($variables['header'][$field]['attributes']['id'])) {
562 $column_reference['attributes']['headers'] = [$variables['header'][$field]['attributes']['id']];
565 if (!empty($fields[$field])) {
566 $field_output = $handler->getField($num, $field);
567 $column_reference['wrapper_element'] = $fields[$field]->elementType(TRUE, TRUE);
568 if (!isset($column_reference['content'])) {
569 $column_reference['content'] = [];
572 // Only bother with separators and stuff if the field shows up.
573 // Place the field into the column, along with an optional separator.
574 if (trim($field_output) != '') {
575 if (!empty($column_reference['content']) && !empty($options['info'][$column]['separator'])) {
576 $column_reference['content'][] = [
577 'separator' => ['#markup' => $options['info'][$column]['separator']],
578 'field_output' => ['#markup' => $field_output]
582 $column_reference['content'][] = [
583 'field_output' => ['#markup' => $field_output]
588 $column_reference['attributes'] = new Attribute($column_reference['attributes']);
591 // Remove columns if the "empty_column" option is checked and the
593 if (!empty($options['info'][$field]['empty_column'])) {
595 foreach ($variables['rows'] as $columns) {
596 $empty &= empty($columns['columns'][$column]['content']);
599 foreach ($variables['rows'] as &$column_items) {
600 unset($column_items['columns'][$column]);
602 unset($variables['header'][$column]);
607 // Hide table header if all labels are empty.
608 if (!$has_header_labels) {
609 $variables['header'] = [];
612 foreach ($variables['rows'] as $num => $row) {
613 $variables['rows'][$num]['attributes'] = [];
614 if ($row_class = $handler->getRowClass($num)) {
615 $variables['rows'][$num]['attributes']['class'][] = $row_class;
617 $variables['rows'][$num]['attributes'] = new Attribute($variables['rows'][$num]['attributes']);
620 if (empty($variables['rows']) && !empty($options['empty_table'])) {
621 $build = $view->display_handler->renderArea('empty');
622 $variables['rows'][0]['columns'][0]['content'][0]['field_output'] = $build;
623 $variables['rows'][0]['attributes'] = new Attribute(['class' => ['odd']]);
624 // Calculate the amounts of rows with output.
625 $variables['rows'][0]['columns'][0]['attributes'] = new Attribute([
626 'colspan' => count($variables['header']),
627 'class' => ['views-empty'],
631 $variables['sticky'] = FALSE;
632 if (!empty($options['sticky'])) {
633 $variables['view']->element['#attached']['library'][] = 'core/drupal.tableheader';
634 $variables['sticky'] = TRUE;
637 // Add the caption to the list if set.
638 if (!empty($handler->options['caption'])) {
639 $variables['caption'] = ['#markup' => $handler->options['caption']];
640 $variables['caption_needed'] = TRUE;
642 elseif (!empty($variables['title'])) {
643 $variables['caption'] = ['#markup' => $variables['title']];
644 $variables['caption_needed'] = TRUE;
647 $variables['caption'] = '';
648 $variables['caption_needed'] = FALSE;
651 $variables['summary'] = $handler->options['summary'];
652 $variables['description'] = $handler->options['description'];
653 $variables['caption_needed'] |= !empty($variables['summary']) || !empty($variables['description']);
655 $variables['responsive'] = FALSE;
656 // If the table has headers and it should react responsively to columns hidden
657 // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
658 // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
659 if (isset($variables['header']) && $responsive) {
660 $variables['view']->element['#attached']['library'][] = 'core/drupal.tableresponsive';
661 // Add 'responsive-enabled' class to the table to identify it for JS.
662 // This is needed to target tables constructed by this function.
663 $variables['responsive'] = TRUE;
668 * Prepares variables for views grid style templates.
670 * Default template: views-view-grid.html.twig.
672 * @param array $variables
673 * An associative array containing:
674 * - view: The view object.
675 * - rows: An array of row items. Each row is an array of content.
677 function template_preprocess_views_view_grid(&$variables) {
678 $options = $variables['options'] = $variables['view']->style_plugin->options;
679 $horizontal = ($options['alignment'] === 'horizontal');
684 $remainders = count($variables['rows']) % $options['columns'];
685 $num_rows = floor(count($variables['rows']) / $options['columns']);
687 // Iterate over each rendered views result row.
688 foreach ($variables['rows'] as $result_index => $item) {
692 $items[$row]['content'][$col]['content'] = $item;
695 $items[$col]['content'][$row]['content'] = $item;
698 // Create attributes for rows.
699 if (!$horizontal || ($horizontal && empty($items[$row]['attributes']))) {
700 $row_attributes = ['class' => []];
701 // Add custom row classes.
702 $row_class = array_filter(explode(' ', $variables['view']->style_plugin->getCustomClass($result_index, 'row')));
703 if (!empty($row_class)) {
704 $row_attributes['class'] = array_merge($row_attributes['class'], $row_class);
706 // Add row attributes to the item.
708 $items[$row]['attributes'] = new Attribute($row_attributes);
711 $items[$col]['content'][$row]['attributes'] = new Attribute($row_attributes);
715 // Create attributes for columns.
716 if ($horizontal || (!$horizontal && empty($items[$col]['attributes']))) {
717 $col_attributes = ['class' => []];
718 // Add default views column classes.
719 // Add custom column classes.
720 $col_class = array_filter(explode(' ', $variables['view']->style_plugin->getCustomClass($result_index, 'col')));
721 if (!empty($col_class)) {
722 $col_attributes['class'] = array_merge($col_attributes['class'], $col_class);
724 // Add automatic width for columns.
725 if ($options['automatic_width']) {
726 $col_attributes['style'] = 'width: ' . (100 / $options['columns']) . '%;';
728 // Add column attributes to the item.
730 $items[$row]['content'][$col]['attributes'] = new Attribute($col_attributes);
733 $items[$col]['attributes'] = new Attribute($col_attributes);
737 // Increase, decrease or reset appropriate integers.
739 if ($col == 0 && $col != ($options['columns'] - 1)) {
742 elseif ($col >= ($options['columns'] - 1)) {
752 if (!$remainders && $row == $num_rows) {
756 elseif ($remainders && $row == $num_rows + 1) {
764 // Add items to the variables array.
765 $variables['items'] = $items;
769 * Prepares variables for views unformatted rows templates.
771 * Default template: views-view-unformatted.html.twig.
773 * @param array $variables
774 * An associative array containing:
775 * - view: The view object.
776 * - rows: An array of row items. Each row is an array of content.
778 function template_preprocess_views_view_unformatted(&$variables) {
779 $view = $variables['view'];
780 $rows = $variables['rows'];
781 $style = $view->style_plugin;
782 $options = $style->options;
784 $variables['default_row_class'] = !empty($options['default_row_class']);
785 foreach ($rows as $id => $row) {
786 $variables['rows'][$id] = [];
787 $variables['rows'][$id]['content'] = $row;
788 $variables['rows'][$id]['attributes'] = new Attribute();
789 if ($row_class = $view->style_plugin->getRowClass($id)) {
790 $variables['rows'][$id]['attributes']->addClass($row_class);
796 * Prepares variables for Views HTML list templates.
798 * Default template: views-view-list.html.twig.
800 * @param array $variables
801 * An associative array containing:
802 * - view: A View object.
804 function template_preprocess_views_view_list(&$variables) {
805 $handler = $variables['view']->style_plugin;
807 // Fetch classes from handler options.
808 if ($handler->options['class']) {
809 $class = explode(' ', $handler->options['class']);
810 $class = array_map('\Drupal\Component\Utility\Html::cleanCssIdentifier', $class);
812 // Initialize a new attribute class for $class.
813 $variables['list']['attributes'] = new Attribute(['class' => $class]);
816 // Fetch wrapper classes from handler options.
817 if ($handler->options['wrapper_class']) {
818 $wrapper_class = explode(' ', $handler->options['wrapper_class']);
819 $variables['attributes']['class'] = array_map('\Drupal\Component\Utility\Html::cleanCssIdentifier', $wrapper_class);
822 $variables['list']['type'] = $handler->options['type'];
824 template_preprocess_views_view_unformatted($variables);
828 * Prepares variables for RSS feed templates.
830 * Default template: views-view-rss.html.twig.
832 * @param array $variables
833 * An associative array containing:
834 * - view: A ViewExecutable object.
835 * - rows: The raw row data.
837 function template_preprocess_views_view_rss(&$variables) {
838 $view = $variables['view'];
839 $items = $variables['rows'];
840 $style = $view->style_plugin;
842 $config = \Drupal::config('system.site');
844 // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
845 // We strip all HTML tags, but need to prevent double encoding from properly
846 // escaped source data (such as & becoming &amp;).
847 $variables['description'] = Html::decodeEntities(strip_tags($style->getDescription()));
849 if ($view->display_handler->getOption('sitename_title')) {
850 $title = $config->get('name');
851 if ($slogan = $config->get('slogan')) {
852 $title .= ' - ' . $slogan;
856 $title = $view->getTitle();
858 $variables['title'] = $title;
860 // Figure out which display which has a path we're using for this feed. If
861 // there isn't one, use the global $base_url
862 $link_display_id = $view->display_handler->getLinkDisplay();
863 /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase $display */
864 if ($link_display_id && ($display = $view->displayHandlers->get($link_display_id)) && $display->isEnabled()) {
865 $url = $view->getUrl(NULL, $link_display_id);
868 /** @var \Drupal\Core\Url $url */
870 $url_options = ['absolute' => TRUE];
871 if (!empty($view->exposed_raw_input)) {
872 $url_options['query'] = $view->exposed_raw_input;
875 // Compare the link to the default home page; if it's the default home page,
876 // just use $base_url.
877 $url_string = $url->setOptions($url_options)->toString();
878 if ($url_string === Url::fromUserInput($config->get('page.front'))->toString()) {
879 $url_string = Url::fromRoute('<front>')->setAbsolute()->toString();
882 $variables['link'] = $url_string;
885 $variables['langcode'] = \Drupal::languageManager()->getCurrentLanguage()->getId();
886 $variables['namespaces'] = new Attribute($style->namespaces);
887 $variables['items'] = $items;
888 $variables['channel_elements'] = \Drupal::service('renderer')->render($style->channel_elements);
890 // During live preview we don't want to output the header since the contents
891 // of the feed are being displayed inside a normal HTML page.
892 if (empty($variables['view']->live_preview)) {
893 $variables['view']->getResponse()->headers->set('Content-Type', 'application/rss+xml; charset=utf-8');
898 * Prepares variables for views RSS item templates.
900 * Default template: views-view-row-rss.html.twig.
902 * @param array $variables
903 * An associative array containing:
904 * - row: The raw results rows.
906 function template_preprocess_views_view_row_rss(&$variables) {
907 $item = $variables['row'];
908 $variables['title'] = $item->title;
909 $variables['link'] = $item->link;
911 // The description is the only place where we should find HTML.
912 // @see https://validator.w3.org/feed/docs/rss2.html#hrelementsOfLtitemgt
913 // If we have a render array, render it here and pass the result to the
914 // template, letting Twig autoescape it.
915 if (isset($item->description) && is_array($item->description)) {
916 $variables['description'] = (string) \Drupal::service('renderer')->render($item->description);
919 $variables['item_elements'] = [];
920 foreach ($item->elements as $element) {
921 if (isset($element['attributes']) && is_array($element['attributes'])) {
922 $element['attributes'] = new Attribute($element['attributes']);
924 $variables['item_elements'][] = $element;
929 * Prepares variables for OPML feed templates.
931 * Default template: views-view-opml.html.twig.
933 * @param array $variables
934 * An associative array containing:
935 * - view: A ViewExecutable object.
936 * - rows: The raw row data.
938 function template_preprocess_views_view_opml(&$variables) {
939 $view = $variables['view'];
940 $items = $variables['rows'];
942 $config = \Drupal::config('system.site');
944 if ($view->display_handler->getOption('sitename_title')) {
945 $title = $config->get('name');
946 if ($slogan = $config->get('slogan')) {
947 $title .= ' - ' . $slogan;
951 $title = $view->getTitle();
953 $variables['title'] = $title;
954 $variables['items'] = $items;
955 $variables['updated'] = gmdate(DATE_RFC2822, REQUEST_TIME);
957 // During live preview we don't want to output the header since the contents
958 // of the feed are being displayed inside a normal HTML page.
959 if (empty($variables['view']->live_preview)) {
960 $variables['view']->getResponse()->headers->set('Content-Type', 'text/xml; charset=utf-8');
965 * Prepares variables for views OPML item templates.
967 * Default template: views-view-row-opml.html.twig.
969 * @param array $variables
970 * An associative array containing:
971 * - row: The raw results rows.
973 function template_preprocess_views_view_row_opml(&$variables) {
974 $item = $variables['row'];
976 $variables['attributes'] = new Attribute($item);
980 * Prepares variables for views exposed form templates.
982 * Default template: views-exposed-form.html.twig.
984 * @param array $variables
985 * An associative array containing:
986 * - form: A render element representing the form.
988 function template_preprocess_views_exposed_form(&$variables) {
989 $form = &$variables['form'];
991 if (!empty($form['q'])) {
992 $variables['q'] = $form['q'];
995 foreach ($form['#info'] as $info) {
996 if (!empty($info['label'])) {
997 $form[$info['value']]['#title'] = $info['label'];
999 if (!empty($info['description'])) {
1000 $form[$info['value']]['#description'] = $info['description'];
1006 * Prepares variables for views mini-pager templates.
1008 * Default template: views-mini-pager.html.twig.
1010 * @param array $variables
1011 * An associative array containing:
1012 * - tags: Provides link text for the next/previous links.
1013 * - element: The pager's id.
1014 * - parameters: Any extra GET parameters that should be retained, such as
1017 function template_preprocess_views_mini_pager(&$variables) {
1018 global $pager_page_array, $pager_total;
1020 $tags = &$variables['tags'];
1021 $element = $variables['element'];
1022 $parameters = $variables['parameters'];
1024 // Current is the page we are currently paged to.
1025 $variables['items']['current'] = $pager_page_array[$element] + 1;
1027 if ($pager_total[$element] > 1 && $pager_page_array[$element] > 0) {
1029 'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
1031 $variables['items']['previous']['href'] = \Drupal::url('<current>', [], $options);
1032 if (isset($tags[1])) {
1033 $variables['items']['previous']['text'] = $tags[1];
1035 $variables['items']['previous']['attributes'] = new Attribute();
1038 if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
1040 'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
1042 $variables['items']['next']['href'] = \Drupal::url('<current>', [], $options);
1043 if (isset($tags[3])) {
1044 $variables['items']['next']['text'] = $tags[3];
1046 $variables['items']['next']['attributes'] = new Attribute();
1049 // This is based on the entire current query string. We need to ensure
1050 // cacheability is affected accordingly.
1051 $variables['#cache']['contexts'][] = 'url.query_args';
1055 * @defgroup views_templates Views template files
1057 * Describes various views templates & overriding options.
1059 * All views templates can be overridden with a variety of names, using
1060 * the view, the display ID of the view, the display type of the view,
1061 * or some combination thereof.
1063 * For each view, there will be a minimum of two templates used. The first
1064 * is used for all views: views-view.html.twig.
1066 * The second template is determined by the style selected for the view. Note
1067 * that certain aspects of the view can also change which style is used; for
1068 * example, arguments which provide a summary view might change the style to
1069 * one of the special summary styles.
1071 * The default style for all views is views-view-unformatted.html.twig.
1073 * Many styles will then farm out the actual display of each row to a row
1074 * style; the default row style is views-view-fields.html.twig.
1076 * Here is an example of all the templates that will be tried in the following
1079 * View, named foobar. Style: unformatted. Row style: Fields. Display: Page.
1081 * - views-view--foobar--page.html.twig
1082 * - views-view--page.html.twig
1083 * - views-view--foobar.html.twig
1084 * - views-view.html.twig
1086 * - views-view-unformatted--foobar--page.html.twig
1087 * - views-view-unformatted--page.html.twig
1088 * - views-view-unformatted--foobar.html.twig
1089 * - views-view-unformatted.html.twig
1091 * - views-view-fields--foobar--page.html.twig
1092 * - views-view-fields--page.html.twig
1093 * - views-view-fields--foobar.html.twig
1094 * - views-view-fields.html.twig
1096 * Important! When adding a new template to your theme, be sure to flush the
1097 * theme registry cache!
1099 * @ingroup views_overview
1100 * @see \Drupal\views\ViewExecutable::buildThemeFunctions()