views.theme.inc 39.3 KB
Newer Older
<
merlinofchaos's avatar
merlinofchaos committed
1 2 3 4 5 6 7
<?php

/**
 * @file
 * Preprocessors and helper functions to make theming easier.
 */

8
use Drupal\Component\Utility\Html;
9
use Drupal\Component\Utility\SafeMarkup;
10
use Drupal\Component\Utility\String;
11
use Drupal\Component\Utility\Xss;
12
use Drupal\Core\Template\Attribute;
13
use Drupal\Core\Url;
14

merlinofchaos's avatar
merlinofchaos committed
15
/**
16 17 18 19
 * Prepares variables for view templates.
 *
 * Default template: views-view.html.twig.
 *
20
 * @param array $variables
21 22
 *   An associative array containing:
 *   - view: The ViewExecutable object.
merlinofchaos's avatar
merlinofchaos committed
23
 */
24 25
function template_preprocess_views_view(&$variables) {
  $view = $variables['view'];
26
  $id = $view->storage->id();
merlinofchaos's avatar
merlinofchaos committed
27

28 29
  $variables['css_name'] = Html::cleanCssIdentifier($id);
  $variables['id'] = $id;
30
  $variables['display_id'] = $view->current_display;
31 32 33 34 35
  // Override the title to be empty by default. For example, if viewing a page
  // view, 'title' will already be populated in $variables. This can still be
  // overridden to use a title when needed. See views_ui_preprocess_views_view()
  // for an example of this.
  $variables['title'] = '';
merlinofchaos's avatar
merlinofchaos committed
36

37
  // Basic classes.
38 39
  $variables['attributes']['class'] = array();
  $variables['attributes']['class'][] = 'view';
40
  $variables['attributes']['class'][] = 'view-' . Html::cleanCssIdentifier($variables['id']);
41 42
  $variables['attributes']['class'][] = 'view-id-' . $variables['id'];
  $variables['attributes']['class'][] = 'view-display-id-' . $variables['display_id'];
merlinofchaos's avatar
merlinofchaos committed
43

44
  $css_class = $view->display_handler->getOption('css_class');
merlinofchaos's avatar
merlinofchaos committed
45
  if (!empty($css_class)) {
46 47
    $variables['css_class'] = preg_replace('/[^a-zA-Z0-9- ]/', '-', $css_class);
    $variables['attributes']['class'][] = $variables['css_class'];
merlinofchaos's avatar
merlinofchaos committed
48 49 50 51 52
  }

  // Attachments are always updated with the outer view, never by themselves,
  // so they do not have dom ids.
  if (empty($view->is_attachment)) {
53 54
    // Our JavaScript needs to have some means to find the HTML belonging to
    // this view.
merlinofchaos's avatar
merlinofchaos committed
55 56 57 58
    //
    // It is true that the DIV wrapper has classes denoting the name of the view
    // and its display ID, but this is not enough to unequivocally match a view
    // with its HTML, because one view may appear several times on the page. So
59 60
    // we set up a hash with the current time, $dom_id, to issue a "unique"
    // identifier for each view. This identifier is written to both
61
    // drupalSettings and the DIV wrapper.
62 63
    $variables['dom_id'] = $view->dom_id;
    $variables['attributes']['class'][] = 'view-dom-id-' . $variables['dom_id'];
merlinofchaos's avatar
merlinofchaos committed
64 65 66 67
  }
}

/**
68 69 70 71
 * Prepares variables for views fields templates.
 *
 * Default template: views-view-fields.html.twig.
 *
72
 * @param array $variables
73 74 75 76 77 78 79 80 81 82 83 84
 *   An associative array containing:
 *   - view: The view object.
 *   - options: An array of options. Each option contains:
 *     - inline: An array that contains the fields that are to be
 *       displayed inline.
 *     - default_field_elements: If default field wrapper
 *       elements are to be provided.
 *     - hide_empty: Whether the field is to be hidden if empty.
 *     - element_default_classes: If the default classes are to be added.
 *     - separator: A string to be placed between inline fields to keep them
 *       visually distinct.
 *    - row: An array containing information about the current row.
merlinofchaos's avatar
merlinofchaos committed
85
 */
86 87
function template_preprocess_views_view_fields(&$variables) {
  $view = $variables['view'];
merlinofchaos's avatar
merlinofchaos committed
88 89 90

  // Loop through the fields for this view.
  $previous_inline = FALSE;
91
  $variables['fields'] = array(); // ensure it's at least an empty array.
92 93
  /** @var \Drupal\views\ResultRow $row */
  $row = $variables['row'];
merlinofchaos's avatar
merlinofchaos committed
94 95
  foreach ($view->field as $id => $field) {
    // render this even if set to exclude so it can be used elsewhere.
96
    $field_output = $view->style_plugin->getField($row->index, $id);
97
    $empty = $field->isValueEmpty($field_output, $field->options['empty_zero']);
98
    if (empty($field->options['exclude']) && (!$empty || (empty($field->options['hide_empty']) && empty($variables['options']['hide_empty'])))) {
merlinofchaos's avatar
merlinofchaos committed
99
      $object = new stdClass();
100
      $object->handler = $view->field[$id];
101
      $object->inline = !empty($variables['options']['inline'][$id]);
merlinofchaos's avatar
merlinofchaos committed
102

103
      $object->element_type = $object->handler->elementType(TRUE, !$variables['options']['default_field_elements'], $object->inline);
merlinofchaos's avatar
merlinofchaos committed
104
      if ($object->element_type) {
105
        $attributes = array();
merlinofchaos's avatar
merlinofchaos committed
106
        if ($object->handler->options['element_default_classes']) {
107
          $attributes['class'][] = 'field-content';
merlinofchaos's avatar
merlinofchaos committed
108 109
        }

110
        if ($classes = $object->handler->elementClasses($row->index)) {
111
          $attributes['class'][] = $classes;
merlinofchaos's avatar
merlinofchaos committed
112
        }
113
        $attributes = new Attribute($attributes);
merlinofchaos's avatar
merlinofchaos committed
114 115

        $pre = '<' . $object->element_type;
116
        $pre .= $attributes;
merlinofchaos's avatar
merlinofchaos committed
117 118 119 120 121 122 123 124 125 126
        $field_output = $pre . '>' . $field_output . '</' . $object->element_type . '>';
      }

      // Protect ourself somewhat for backward compatibility. This will prevent
      // old templates from producing invalid HTML when no element type is selected.
      if (empty($object->element_type)) {
        $object->element_type = 'span';
      }

      $object->content = $field_output;
127 128
      if (isset($view->field[$id]->field_alias) && isset($row->{$view->field[$id]->field_alias})) {
        $object->raw = $row->{$view->field[$id]->field_alias};
merlinofchaos's avatar
merlinofchaos committed
129 130 131 132 133
      }
      else {
        $object->raw = NULL; // make sure it exists to reduce NOTICE
      }

134
      if (!empty($variables['options']['separator']) && $previous_inline && $object->inline && $object->content) {
135
        $object->separator = Xss::filterAdmin($variables['options']['separator']);
merlinofchaos's avatar
merlinofchaos committed
136 137
      }

138
      $object->class = Html::cleanCssIdentifier($id);
merlinofchaos's avatar
merlinofchaos committed
139 140

      $previous_inline = $object->inline;
141
      $object->inline_html = $object->handler->elementWrapperType(TRUE, TRUE);
142
      if ($object->inline_html === '' && $variables['options']['default_field_elements']) {
merlinofchaos's avatar
merlinofchaos committed
143 144 145 146 147 148 149 150
        $object->inline_html = $object->inline ? 'span' : 'div';
      }

      // Set up the wrapper HTML.
      $object->wrapper_prefix = '';
      $object->wrapper_suffix = '';

      if ($object->inline_html) {
151
        $attributes = array();
merlinofchaos's avatar
merlinofchaos committed
152
        if ($object->handler->options['element_default_classes']) {
153 154
          $attributes['class'][] = 'views-field';
          $attributes['class'][] = 'views-field-' . $object->class;
merlinofchaos's avatar
merlinofchaos committed
155 156
        }

157
        if ($classes = $object->handler->elementWrapperClasses($row->index)) {
158
          $attributes['class'][] = $classes;
merlinofchaos's avatar
merlinofchaos committed
159
        }
160
        $attributes = new Attribute($attributes);
merlinofchaos's avatar
merlinofchaos committed
161 162

        $object->wrapper_prefix = '<' . $object->inline_html;
163
        $object->wrapper_prefix .= $attributes;
merlinofchaos's avatar
merlinofchaos committed
164 165 166 167 168 169
        $object->wrapper_prefix .= '>';
        $object->wrapper_suffix = '</' . $object->inline_html . '>';
      }

      // Set up the label for the value and the HTML to make it easier
      // on the template.
170
      $object->label = String::checkPlain($view->field[$id]->label());
merlinofchaos's avatar
merlinofchaos committed
171 172 173 174 175 176 177
      $object->label_html = '';
      if ($object->label) {
        $object->label_html .= $object->label;
        if ($object->handler->options['element_label_colon']) {
          $object->label_html .= ': ';
        }

178
        $object->elementLabelType = $object->handler->elementLabelType(TRUE, !$variables['options']['default_field_elements']);
179
        if ($object->elementLabelType) {
180
          $attributes = array();
merlinofchaos's avatar
merlinofchaos committed
181
          if ($object->handler->options['element_default_classes']) {
182 183
            $attributes['class'][] = 'views-label';
            $attributes['class'][] = 'views-label-' . $object->class;
merlinofchaos's avatar
merlinofchaos committed
184 185
          }

186
          $element_label_class = $object->handler->elementLabelClasses($row->index);
merlinofchaos's avatar
merlinofchaos committed
187
          if ($element_label_class) {
188
            $attributes['class'][] = $element_label_class;
merlinofchaos's avatar
merlinofchaos committed
189
          }
190
          $attributes = new Attribute($attributes);
merlinofchaos's avatar
merlinofchaos committed
191

192
          $pre = '<' . $object->elementLabelType;
193
          $pre .= $attributes;
merlinofchaos's avatar
merlinofchaos committed
194 195
          $pre .= '>';

196
          $object->label_html = $pre . $object->label_html . '</' . $object->elementLabelType . '>';
merlinofchaos's avatar
merlinofchaos committed
197 198 199
        }
      }

200
      $variables['fields'][$id] = $object;
merlinofchaos's avatar
merlinofchaos committed
201 202 203 204 205
    }
  }

}

206
/**
207 208 209 210 211 212 213 214 215 216 217 218 219
 * Returns HTML for multiple views fields.
 *
 * @param $variables
 *   An associative array containing:
 *   - fields: An array of field objects. Each field object contains:
 *     - separator: A string that separates the fields.
 *     - wrapper_suffix: A string added to the beginning of the fields.
 *     - label_html: An HTML string that labels the fields.
 *     - content: The fields.
 *     - wrapper_suffix: A string added to the end of the fields.
 *
 * @see template_preprocess_views_view_fields()
 */
220 221 222 223
function theme_views_view_fields($variables) {
  $fields = $variables['fields'];
  $output = '';

224
  foreach ($fields as $field) {
225 226 227 228 229 230 231 232 233 234 235 236 237 238
    if (!empty($field->separator)) {
      $output .= $field->separator;
    }

    $output .= $field->wrapper_prefix;
    $output .= $field->label_html;
    $output .= $field->content;

    $output .= $field->wrapper_suffix;
  }

  return $output;
}

merlinofchaos's avatar
merlinofchaos committed
239
/**
240 241 242 243
 * Prepares variables for views single grouping templates.
 *
 * Default template: views-view-grouping.html.twig.
 *
244
 * @param array $variables
245 246 247 248 249 250 251
 *   An associative array containing:
 *   - view: The view object.
 *   - rows: The rows returned from the view.
 *   - grouping_level: Integer indicating the hierarchical level of the
 *     grouping.
 *   - content: The content to be grouped.
 *   - title: The group heading.
252
 */
253 254
function template_preprocess_views_view_grouping(&$variables) {
  $variables['content'] = $variables['view']->style_plugin->renderGroupingSets($variables['rows'], $variables['grouping_level']);
merlinofchaos's avatar
merlinofchaos committed
255 256 257 258 259 260 261 262 263
}

/**
 * Display a single views field.
 *
 * Interesting bits of info:
 * $field->field_alias says what the raw value in $row will be. Reach it like
 * this: @code { $row->{$field->field_alias} @endcode
 */
264 265
function theme_views_view_field($variables) {
  return $variables['output'];
merlinofchaos's avatar
merlinofchaos committed
266 267 268
}

/**
269
 * Prepares variables for views field templates.
merlinofchaos's avatar
merlinofchaos committed
270
 *
271 272
 * Default template: views-view-field.html.twig.
 *
273
 * @param array $variables
274 275 276 277 278
 *   An associative array containing:
 *   - field: The field handler object for the current field.
 *   - row: Object representing the raw result of the SQL query for the current
 *     field.
 *   - view: Instance of the ViewExecutable object for the parent view.
merlinofchaos's avatar
merlinofchaos committed
279
 */
280 281
function template_preprocess_views_view_field(&$variables) {
  $variables['output'] = $variables['field']->advancedRender($variables['row']);
merlinofchaos's avatar
merlinofchaos committed
282 283 284
}

/**
285 286 287 288 289 290
 * Prepares variables for views summary templates.
 *
 * The summary prints a single record from a row, with fields.
 *
 * Default template: views-view-summary.html.twig.
 *
291
 * @param array $variables
292 293 294
 *   An associative array containing:
 *   - view: A ViewExecutable object.
 *   - rows: The raw row data.
merlinofchaos's avatar
merlinofchaos committed
295
 */
296 297
function template_preprocess_views_view_summary(&$variables) {
  $view = $variables['view'];
merlinofchaos's avatar
merlinofchaos committed
298 299 300 301 302 303 304 305
  $argument = $view->argument[$view->build_info['summary_level']];

  $url_options = array();

  if (!empty($view->exposed_raw_input)) {
    $url_options['query'] = $view->exposed_raw_input;
  }

306
  $active_urls = array(
307 308
    // Force system path.
    \Drupal::url('<current>', [], ['alias' => TRUE]),
309 310
    // Force system path.
    Url::fromRouteMatch(\Drupal::routeMatch())->setOption('alias', TRUE)->toString(),
311 312
    // Could be an alias.
    \Drupal::url('<current>'),
313 314
    // Could be an alias.
    Url::fromRouteMatch(\Drupal::routeMatch())->toString(),
315 316
  );
  $active_urls = array_combine($active_urls, $active_urls);
merlinofchaos's avatar
merlinofchaos committed
317

318 319 320
  // Collect all arguments foreach row, to be able to alter them for example
  // by the validator. This is not done per single argument value, because this
  // could cause performance problems.
merlinofchaos's avatar
merlinofchaos committed
321 322
  $row_args = array();

323
  foreach ($variables['rows'] as $id => $row) {
324
    $row_args[$id] = $argument->summaryArgument($row);
merlinofchaos's avatar
merlinofchaos committed
325
  }
326
  $argument->processSummaryArguments($row_args);
merlinofchaos's avatar
merlinofchaos committed
327

328
  foreach ($variables['rows'] as $id => $row) {
329
    $variables['rows'][$id]->attributes = array();
330
    $variables['rows'][$id]->link = $argument->summaryName($row);
merlinofchaos's avatar
merlinofchaos committed
331 332 333 334 335 336 337
    $args = $view->args;
    $args[$argument->position] = $row_args[$id];

    $base_path = NULL;
    if (!empty($argument->options['summary_options']['base_path'])) {
      $base_path = $argument->options['summary_options']['base_path'];
    }
338
    $variables['rows'][$id]->url = _url($view->getUrl($args, $base_path), $url_options);
339 340
    $variables['rows'][$id]->count = intval($row->{$argument->count_alias});
    if (isset($active_urls[$variables['rows'][$id]->url])) {
341
      $variables['rows'][$id]->attributes['class'][] = 'active';
merlinofchaos's avatar
merlinofchaos committed
342
    }
343
    $variables['rows'][$id]->attributes = new Attribute($variables['rows'][$id]->attributes);
merlinofchaos's avatar
merlinofchaos committed
344 345 346 347
  }
}

/**
348 349 350 351
 * Prepares variables for unformatted summary view templates.
 *
 * Default template: views-view-summary-unformatted.html.twig.
 *
352
 * @param array $variables
353 354 355 356 357 358
 *   An associative array containing:
 *   - view: A ViewExecutable object.
 *   - rows: The raw row data.
 *   - options: An array of options. Each option contains:
 *     - separator: A string to be placed between inline fields to keep them
 *       visually distinct.
merlinofchaos's avatar
merlinofchaos committed
359
 */
360 361
function template_preprocess_views_view_summary_unformatted(&$variables) {
  $view = $variables['view'];
merlinofchaos's avatar
merlinofchaos committed
362 363 364 365 366 367 368 369 370
  $argument = $view->argument[$view->build_info['summary_level']];

  $url_options = array();

  if (!empty($view->exposed_raw_input)) {
    $url_options['query'] = $view->exposed_raw_input;
  }

  $count = 0;
371
  $active_urls = array(
372
    // Force system path.
373
    \Drupal::url('<current>', [], ['alias' => TRUE]),
374
    // Could be an alias.
375
    \Drupal::url('<current>'),
376 377
  );
  $active_urls = array_combine($active_urls, $active_urls);
merlinofchaos's avatar
merlinofchaos committed
378

379 380 381
  // Collect all arguments for each row, to be able to alter them for example
  // by the validator. This is not done per single argument value, because
  // this could cause performance problems.
merlinofchaos's avatar
merlinofchaos committed
382
  $row_args = array();
383
  foreach ($variables['rows'] as $id => $row) {
384
    $row_args[$id] = $argument->summaryArgument($row);
merlinofchaos's avatar
merlinofchaos committed
385
  }
386
  $argument->processSummaryArguments($row_args);
merlinofchaos's avatar
merlinofchaos committed
387

388
  foreach ($variables['rows'] as $id => $row) {
389
    // Only false on first time.
merlinofchaos's avatar
merlinofchaos committed
390
    if ($count++) {
391
      $variables['rows'][$id]->separator = Xss::filterAdmin($variables['options']['separator']);
merlinofchaos's avatar
merlinofchaos committed
392
    }
393
    $variables['rows'][$id]->attributes = array();
394
    $variables['rows'][$id]->link = $argument->summaryName($row);
merlinofchaos's avatar
merlinofchaos committed
395 396 397 398 399 400 401
    $args = $view->args;
    $args[$argument->position] = $row_args[$id];

    $base_path = NULL;
    if (!empty($argument->options['summary_options']['base_path'])) {
      $base_path = $argument->options['summary_options']['base_path'];
    }
402
    $variables['rows'][$id]->url = _url($view->getUrl($args, $base_path), $url_options);
403 404
    $variables['rows'][$id]->count = intval($row->{$argument->count_alias});
    if (isset($active_urls[$variables['rows'][$id]->url])) {
405
      $variables['rows'][$id]->attributes['class'][] = 'active';
merlinofchaos's avatar
merlinofchaos committed
406
    }
407
    $variables['rows'][$id]->attributes = new Attribute($variables['rows'][$id]->attributes);
merlinofchaos's avatar
merlinofchaos committed
408 409 410 411
  }
}

/**
412 413 414 415
 * Prepares variables for views table templates.
 *
 * Default template: views-view-table.html.twig.
 *
416
 * @param array $variables
417 418 419
 *   An associative array containing:
 *   - view: A ViewExecutable object.
 *   - rows: The raw row data.
merlinofchaos's avatar
merlinofchaos committed
420
 */
421 422
function template_preprocess_views_view_table(&$variables) {
  $view = $variables['view'];
merlinofchaos's avatar
merlinofchaos committed
423

424
  // We need the raw data for this grouping, which is passed in
425
  // as $variables['rows'].
merlinofchaos's avatar
merlinofchaos committed
426
  // However, the template also needs to use for the rendered fields.  We
427
  // therefore swap the raw data out to a new variable and reset $variables['rows']
merlinofchaos's avatar
merlinofchaos committed
428 429
  // so that it can get rebuilt.
  // Store rows so that they may be used by further preprocess functions.
430 431 432
  $result = $variables['result'] = $variables['rows'];
  $variables['rows'] = array();
  $variables['header'] = array();
merlinofchaos's avatar
merlinofchaos committed
433

434 435
  $options = $view->style_plugin->options;
  $handler = $view->style_plugin;
merlinofchaos's avatar
merlinofchaos committed
436

437
  $fields = &$view->field;
438
  $columns = $handler->sanitizeColumns($options['columns'], $fields);
merlinofchaos's avatar
merlinofchaos committed
439

440 441
  $active = !empty($handler->active) ? $handler->active : '';
  $order = !empty($handler->order) ? $handler->order : 'asc';
merlinofchaos's avatar
merlinofchaos committed
442

443 444 445
  // A boolean variable which stores whether the table has a responsive class.
  $responsive = FALSE;

446
  $query = tablesort_get_query_parameters();
merlinofchaos's avatar
merlinofchaos committed
447 448 449 450
  if (isset($view->exposed_raw_input)) {
    $query += $view->exposed_raw_input;
  }

451 452
  // A boolean to store whether the table's header has any labels.
  $has_header_labels = FALSE;
merlinofchaos's avatar
merlinofchaos committed
453
  foreach ($columns as $field => $column) {
454 455
    // Create a second variable so we can easily find what fields we have and
    // what the CSS classes should be.
456
    $variables['fields'][$field] = Html::cleanCssIdentifier($field);
merlinofchaos's avatar
merlinofchaos committed
457
    if ($active == $field) {
458
      $variables['fields'][$field] .= ' active';
merlinofchaos's avatar
merlinofchaos committed
459 460
    }

461
    // Render the header labels.
merlinofchaos's avatar
merlinofchaos committed
462
    if ($field == $column && empty($fields[$field]->options['exclude'])) {
463
      $label = String::checkPlain(!empty($fields[$field]) ? $fields[$field]->label() : '');
464
      if (empty($options['info'][$field]['sortable']) || !$fields[$field]->clickSortable()) {
465
        $variables['header'][$field]['content'] = $label;
merlinofchaos's avatar
merlinofchaos committed
466 467 468 469 470 471 472 473 474 475
      }
      else {
        $initial = !empty($options['info'][$field]['default_sort_order']) ? $options['info'][$field]['default_sort_order'] : 'asc';

        if ($active == $field) {
          $initial = ($order == 'asc') ? 'desc' : 'asc';
        }

        $title = t('sort by @s', array('@s' => $label));
        if ($active == $field) {
476 477 478 479
          $tablesort_indicator = array(
            '#theme' => 'tablesort_indicator',
            '#style' => $initial,
          );
480 481
          $markup = drupal_render($tablesort_indicator);
          $label = SafeMarkup::set($label . $markup);
merlinofchaos's avatar
merlinofchaos committed
482 483 484 485 486
        }

        $query['order'] = $field;
        $query['sort'] = $initial;
        $link_options = array(
487
          'html' => TRUE,
merlinofchaos's avatar
merlinofchaos committed
488 489 490
          'attributes' => array('title' => $title),
          'query' => $query,
        );
491
        $variables['header'][$field]['content'] = \Drupal::l($label, new Url('<current>', [], $link_options));
merlinofchaos's avatar
merlinofchaos committed
492 493 494
      }

      // Set up the header label class.
495
      $variables['header'][$field]['attributes'] = array();
merlinofchaos's avatar
merlinofchaos committed
496
      if ($fields[$field]->options['element_default_classes']) {
497 498
        $variables['header'][$field]['attributes']['class'][] = 'views-field';
        $variables['header'][$field]['attributes']['class'][] = 'views-field-' . $variables['fields'][$field];
merlinofchaos's avatar
merlinofchaos committed
499
      }
500
      $class = $fields[$field]->elementLabelClasses(0);
merlinofchaos's avatar
merlinofchaos committed
501
      if ($class) {
502
        $variables['header'][$field]['attributes']['class'][] = $class;
merlinofchaos's avatar
merlinofchaos committed
503
      }
504 505
      // Add responsive header classes.
      if (!empty($options['info'][$field]['responsive'])) {
506
        $variables['header'][$field]['attributes']['class'][] = $options['info'][$field]['responsive'];
507 508
        $responsive = TRUE;
      }
509
      // Add a CSS align class to each field if one was set.
merlinofchaos's avatar
merlinofchaos committed
510
      if (!empty($options['info'][$field]['align'])) {
511
        $variables['header'][$field]['attributes']['class'][] = Html::cleanCssIdentifier($options['info'][$field]['align']);
merlinofchaos's avatar
merlinofchaos committed
512 513
      }
      // Add a header label wrapper if one was selected.
514
      if ($variables['header'][$field]['content']) {
515
        $element_label_type = $fields[$field]->elementLabelType(TRUE, TRUE);
merlinofchaos's avatar
merlinofchaos committed
516
        if ($element_label_type) {
517
          $variables['header'][$field]['content'] = '<' . $element_label_type . '>' . $variables['header'][$field]['content'] . '</' . $element_label_type . '>';
merlinofchaos's avatar
merlinofchaos committed
518
        }
519
        // Improves accessibility of complex tables.
520
        $variables['header'][$field]['attributes']['id'] = drupal_html_id('view-' . $field . '-table-column');
merlinofchaos's avatar
merlinofchaos committed
521
      }
522 523 524 525 526 527
      // Check if header label is not empty.
      if (!empty($variables['header'][$field]['content'])) {
        $has_header_labels = TRUE;
      }

      $variables['header'][$field]['attributes'] = new Attribute($variables['header'][$field]['attributes']);
merlinofchaos's avatar
merlinofchaos committed
528 529
    }

530
    // Add a CSS align class to each field if one was set.
merlinofchaos's avatar
merlinofchaos committed
531
    if (!empty($options['info'][$field]['align'])) {
532
      $variables['fields'][$field] .= ' ' . Html::cleanCssIdentifier($options['info'][$field]['align']);
merlinofchaos's avatar
merlinofchaos committed
533 534 535 536
    }

    // Render each field into its appropriate column.
    foreach ($result as $num => $row) {
537 538 539 540 541 542 543 544 545 546

      // Skip building the attributes and content if the field is to be excluded
      // from the display.
      if (!empty($fields[$field]->options['exclude'])) {
        continue;
      }

      // Reference to the column in the loop to make the code easier to read.
      $column_reference =& $variables['rows'][$num]['columns'][$column];

547
      // Add field classes.
548 549 550
      if (!isset($column_reference['attributes'])) {
        $column_reference['attributes'] = array();
      }
merlinofchaos's avatar
merlinofchaos committed
551
      if ($fields[$field]->options['element_default_classes']) {
552 553
        $column_reference['attributes']['class'][] = 'views-field';
        $column_reference['attributes']['class'][] = 'views-field-' . $variables['fields'][$field];
merlinofchaos's avatar
merlinofchaos committed
554 555
      }

556
      if ($classes = $fields[$field]->elementClasses($num)) {
557
        $column_reference['attributes']['class'][] = $classes;
merlinofchaos's avatar
merlinofchaos committed
558
      }
559

560 561
      // Add responsive header classes.
      if (!empty($options['info'][$field]['responsive'])) {
562
        $column_reference['attributes']['class'][] = $options['info'][$field]['responsive'];
563 564
      }

565
      // Improves accessibility of complex tables.
566
      if (isset($variables['header'][$field]['attributes']['id'])) {
567
        $column_reference['attributes']['headers'] = array($variables['header'][$field]['attributes']['id']);
568 569
      }

570
      if (!empty($fields[$field])) {
571
        $field_output = $handler->getField($num, $field);
572
        $element_type = $fields[$field]->elementType(TRUE, TRUE);
merlinofchaos's avatar
merlinofchaos committed
573
        if ($element_type) {
574
          $field_output = SafeMarkup::set('<' . $element_type . '>' .  SafeMarkup::escape($field_output) . '</' . $element_type . '>');
merlinofchaos's avatar
merlinofchaos committed
575 576
        }

577
        // Only bother with separators and stuff if the field shows up.
578
        if (!empty($field_output)) {
579
          // Place the field into the column, along with an optional separator.
580
          if (!empty($column_reference['content'])) {
581
            if (!empty($options['info'][$column]['separator'])) {
582 583 584
              $safe_content = SafeMarkup::escape($column_reference['content']);
              $safe_separator = Xss::filterAdmin($options['info'][$column]['separator']);
              $column_reference['content'] = SafeMarkup::set($safe_content . $safe_separator);
585
            }
merlinofchaos's avatar
merlinofchaos committed
586
          }
587
          else {
588
            $column_reference['content'] = '';
589
          }
590 591 592
          $safe_content = SafeMarkup::escape($column_reference['content']);
          $safe_field_output = SafeMarkup::escape($field_output);
          $column_reference['content'] = SafeMarkup::set($safe_content . $safe_field_output);
merlinofchaos's avatar
merlinofchaos committed
593 594
        }
      }
595
      $column_reference['attributes'] = new Attribute($column_reference['attributes']);
merlinofchaos's avatar
merlinofchaos committed
596 597
    }

598 599
    // Remove columns if the option is hide empty column is checked and the
    // field is not empty.
merlinofchaos's avatar
merlinofchaos committed
600 601
    if (!empty($options['info'][$field]['empty_column'])) {
      $empty = TRUE;
602
      foreach ($variables['rows'] as $columns) {
merlinofchaos's avatar
merlinofchaos committed
603 604 605
        $empty &= empty($columns[$column]);
      }
      if ($empty) {
606
        foreach ($variables['rows'] as &$column_items) {
merlinofchaos's avatar
merlinofchaos committed
607
          unset($column_items[$column]);
608
          unset($variables['header'][$column]);
merlinofchaos's avatar
merlinofchaos committed
609 610 611 612 613 614
        }
      }
    }
  }

  // Hide table header if all labels are empty.
615
  if (!$has_header_labels) {
616
    $variables['header'] = array();
merlinofchaos's avatar
merlinofchaos committed
617 618
  }

619
  foreach ($variables['rows'] as $num => $row) {
620
    $variables['rows'][$num]['attributes'] = array();
621
    if ($row_class = $handler->getRowClass($num)) {
622
      $variables['rows'][$num]['attributes']['class'][] = $row_class;
merlinofchaos's avatar
merlinofchaos committed
623
    }
624
    $variables['rows'][$num]['attributes'] = new Attribute($variables['rows'][$num]['attributes']);
merlinofchaos's avatar
merlinofchaos committed
625 626
  }

627 628 629
  $variables['attributes']['class'][] = 'views-table';
  $variables['attributes']['class'][] = 'views-view-table';
  if (empty($variables['rows']) && !empty($options['empty_table'])) {
630
    $build = $view->display_handler->renderArea('empty');
631 632
    $variables['rows'][0]['columns'][0]['content'] = drupal_render($build);
    $variables['rows'][0]['attributes'] = new Attribute(array('class' => 'odd'));
merlinofchaos's avatar
merlinofchaos committed
633
    // Calculate the amounts of rows with output.
634
    $variables['rows'][0]['columns'][0]['attributes'] = new Attribute(array(
635
      'colspan' => count($variables['header']),
636 637
      'class' => 'views-empty',
    ));
merlinofchaos's avatar
merlinofchaos committed
638 639 640
  }

  if (!empty($options['sticky'])) {
641
    $variables['view']->element['#attached']['library'][] = 'core/drupal.tableheader';
642
    $variables['attributes']['class'][] = "sticky-enabled";
merlinofchaos's avatar
merlinofchaos committed
643
  }
644
  $variables['attributes']['class'][] = 'cols-' . count($variables['header']);
merlinofchaos's avatar
merlinofchaos committed
645

646 647
  // Add the caption to the list if set.
  if (!empty($handler->options['caption'])) {
648 649
    $variables['caption'] = Xss::filterAdmin($handler->options['caption']);
    $variables['caption_needed'] = TRUE;
merlinofchaos's avatar
merlinofchaos committed
650
  }
651
  else {
652 653
    $variables['caption'] = '';
    $variables['caption_needed'] = FALSE;
654 655
  }

656 657 658
  $variables['summary'] = $handler->options['summary'];
  $variables['description'] = $handler->options['description'];
  $variables['caption_needed'] |= !empty($variables['summary']) || !empty($variables['description']);
659

660 661 662
  // If the table has headers and it should react responsively to columns hidden
  // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
  // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
663
  if (isset($variables['header']) && $responsive) {
664
    $variables['view']->element['#attached']['library'][] = 'core/drupal.tableresponsive';
665 666
    // Add 'responsive-enabled' class to the table to identify it for JS.
    // This is needed to target tables constructed by this function.
667
    $variables['attributes']['class'][] = 'responsive-enabled';
668
  }
merlinofchaos's avatar
merlinofchaos committed
669 670 671
}

/**
672 673 674 675
 * Prepares variables for views grid style templates.
 *
 * Default template: views-view-grid.html.twig.
 *
676
 * @param array $variables
677 678 679
 *   An associative array containing:
 *   - view: The view object.
 *   - rows: An array of row items. Each row is an array of content.
merlinofchaos's avatar
merlinofchaos committed
680
 */
681
function template_preprocess_views_view_grid(&$variables) {
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
  $options = $variables['options'] = $variables['view']->style_plugin->options;
  $horizontal = ($options['alignment'] === 'horizontal');

  $variables['attributes']['class'] = array(
    'views-view-grid',
    $options['alignment'],
    'cols-' . $options['columns'],
    'clearfix',
  );

  $col = 0;
  $row = 0;
  $items = array();
  $remainders = count($variables['rows']) % $options['columns'];
  $num_rows = floor(count($variables['rows']) / $options['columns']);

  // Iterate over each rendered views result row.
699
  foreach ($variables['rows'] as $result_index => $item) {
700 701 702 703 704 705 706 707

    // Add the item.
    if ($horizontal) {
      $items[$row]['content'][$col]['content'] = $item;
    }
    else {
      $items[$col]['content'][$row]['content'] = $item;
    }
merlinofchaos's avatar
merlinofchaos committed
708

709 710 711 712 713 714 715 716 717 718 719 720
    // Create attributes for rows.
    if (!$horizontal || ($horizontal && empty($items[$row]['attributes']))) {
      $row_attributes = array('class' => array());
      // Add default views row classes.
      if ($options['row_class_default']) {
        $row_attributes['class'][] = 'views-row';
        $row_attributes['class'][] = 'row-' . ($row + 1);
        if ($horizontal) {
          $row_attributes['class'][] = 'clearfix';
        }
      }
      // Add custom row classes.
721
      $row_class = array_filter(explode(' ', $variables['view']->style_plugin->getCustomClass($result_index, 'row')));
722 723 724 725 726 727 728 729 730
      if (!empty($row_class)) {
        $row_attributes['class'] = array_merge($row_attributes['class'], $row_class);
      }
      // Add row attributes to the item.
      if ($horizontal) {
        $items[$row]['attributes'] = new Attribute($row_attributes);
      }
      else {
        $items[$col]['content'][$row]['attributes'] = new Attribute($row_attributes);
merlinofchaos's avatar
merlinofchaos committed
731 732
      }
    }
733 734 735 736 737 738 739 740 741 742

    // Create attributes for columns.
    if ($horizontal || (!$horizontal && empty($items[$col]['attributes']))) {
      $col_attributes = array('class' => array());
      // Add default views column classes.
      if ($options['col_class_default']) {
        $col_attributes['class'][] = 'views-col';
        $col_attributes['class'][] = 'col-' . ($col + 1);
        if (!$horizontal) {
          $col_attributes['class'][] = 'clearfix';
merlinofchaos's avatar
merlinofchaos committed
743 744
        }
      }
745
      // Add custom column classes.
746
      $col_class = array_filter(explode(' ', $variables['view']->style_plugin->getCustomClass($result_index, 'col')));
747 748 749 750 751 752 753 754 755 756 757 758 759 760
      if (!empty($col_class)) {
        $col_attributes['class'] = array_merge($col_attributes['class'], $col_class);
      }
      // Add automatic width for columns.
      if ($options['automatic_width']) {
        $col_attributes['style'] = 'width: ' . (100 / $options['columns']) . '%;';
      }
      // Add column attributes to the item.
      if ($horizontal) {
        $items[$row]['content'][$col]['attributes'] = new Attribute($col_attributes);
      }
      else {
        $items[$col]['attributes'] = new Attribute($col_attributes);
      }
merlinofchaos's avatar
merlinofchaos committed
761 762
    }

763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
    // Increase, decrease or reset appropriate integers.
    if ($horizontal) {
      if ($col == 0 && $col != ($options['columns'] - 1)) {
        $col++;
      }
      elseif ($col >= ($options['columns'] - 1)) {
        $col = 0;
        $row++;
      }
      else {
        $col++;
      }
    }
    else {
      $row++;
merlinofchaos's avatar
merlinofchaos committed
778 779 780 781 782 783 784 785 786 787 788 789
      if (!$remainders && $row == $num_rows) {
        $row = 0;
        $col++;
      }
      elseif ($remainders && $row == $num_rows + 1) {
        $row = 0;
        $col++;
        $remainders--;
      }
    }
  }

790 791
  // Add items to the variables array.
  $variables['items'] = $items;
merlinofchaos's avatar
merlinofchaos committed
792 793 794
}

/**
795 796 797 798
 * Prepares variables for views unformatted rows templates.
 *
 * Default template: views-view-unformatted.html.twig.
 *
799
 * @param array $variables
800 801 802
 *   An associative array containing:
 *   - view: The view object.
 *   - rows: An array of row items. Each row is an array of content.
merlinofchaos's avatar
merlinofchaos committed
803
 */
804 805 806
function template_preprocess_views_view_unformatted(&$variables) {
  $view = $variables['view'];
  $rows = $variables['rows'];
merlinofchaos's avatar
merlinofchaos committed
807 808 809 810 811
  $style = $view->style_plugin;
  $options = $style->options;

  $default_row_class = isset($options['default_row_class']) ? $options['default_row_class'] : FALSE;
  foreach ($rows as $id => $row) {
812 813 814
    $variables['rows'][$id] = array();
    $variables['rows'][$id]['content'] = $row;
    $variables['rows'][$id]['attributes'] = array();
merlinofchaos's avatar
merlinofchaos committed
815
    if ($default_row_class) {
816
      $variables['rows'][$id]['attributes']['class'][] = 'views-row';
merlinofchaos's avatar
merlinofchaos committed
817
    }
818
    if ($row_class = $view->style_plugin->getRowClass($id)) {
819
      $variables['rows'][$id]['attributes']['class'][] = $row_class;
merlinofchaos's avatar
merlinofchaos committed
820
    }
821
    $variables['rows'][$id]['attributes'] = new Attribute($variables['rows'][$id]['attributes']);
merlinofchaos's avatar
merlinofchaos committed
822 823 824 825
  }
}

/**
826 827 828 829 830 831 832
 * Prepares variables for Views HTML list templates.
 *
 * Default template: views-view-list.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - view: A View object.
merlinofchaos's avatar
merlinofchaos committed
833
 */
834 835
function template_preprocess_views_view_list(&$variables) {
  $handler  = $variables['view']->style_plugin;
merlinofchaos's avatar
merlinofchaos committed
836

837
  // Fetch classes from handler options.
838 839
  if ($handler->options['class']) {
    $class = explode(' ', $handler->options['class']);
840
    $class = array_map('\Drupal\Component\Utility\Html::cleanCssIdentifier', $class);
merlinofchaos's avatar
merlinofchaos committed
841

842 843 844
    // Initialize a new attribute class for $class.
    $variables['list']['attributes'] = new Attribute(array('class' => $class));
  }
merlinofchaos's avatar
merlinofchaos committed
845

846 847 848
  // Fetch wrapper classes from handler options.
  if ($handler->options['wrapper_class']) {
    $wrapper_class = explode(' ', $handler->options['wrapper_class']);
849
    $variables['attributes']['class'] = array_map('\Drupal\Component\Utility\Html::cleanCssIdentifier', $wrapper_class);
850 851
  }

852
  $variables['list']['type'] = $handler->options['type'];
853

854
  template_preprocess_views_view_unformatted($variables);
merlinofchaos's avatar
merlinofchaos committed
855 856 857
}

/**
858 859 860 861
 * Prepares variables for RSS feed templates.
 *
 * Default template: views-view-rss.html.twig.
 *
862
 * @param array $variables
863 864 865
 *   An associative array containing:
 *   - view: A ViewExecutable object.
 *   - rows: The raw row data.
866
 */
867
function template_preprocess_views_view_rss(&$variables) {
868
  $view  = $variables['view'];
869
  $items = $variables['rows'];
870
  $style = $view->style_plugin;
merlinofchaos's avatar
merlinofchaos committed
871

872
  $config = \Drupal::config('system.site');
873

merlinofchaos's avatar
merlinofchaos committed
874 875 876
  // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
  // We strip all HTML tags, but need to prevent double encoding from properly
  // escaped source data (such as &amp becoming &amp;amp;).
877
  $variables['description'] = String::checkPlain(String::decodeEntities(strip_tags($style->getDescription())));
merlinofchaos's avatar
merlinofchaos committed
878

879
  if ($view->display_handler->getOption('sitename_title')) {
880
    $title = $config->get('name');
881
    if ($slogan = $config->get('slogan')) {
merlinofchaos's avatar
merlinofchaos committed
882 883 884 885
      $title .= ' - ' . $slogan;
    }
  }
  else {
886
    $title = $view->getTitle();
merlinofchaos's avatar
merlinofchaos committed
887
  }
888
  $variables['title'] = String::checkPlain($title);
merlinofchaos's avatar
merlinofchaos committed
889

890 891
  // Figure out which display which has a path we're using for this feed. If
  // there isn't one, use the global $base_url
892
  $link_display_id = $view->display_handler->getLinkDisplay();
893 894
  if ($link_display_id && $display = $view->displayHandlers->get($link_display_id)) {
    $path = $view->displayHandlers->get($link_display_id)->getPath();
merlinofchaos's avatar
merlinofchaos committed
895 896 897
  }

  if ($path) {
898
    $path = $view->getUrl(NULL, $path);
merlinofchaos's avatar
merlinofchaos committed
899 900 901 902 903
    $url_options = array('absolute' => TRUE);
    if (!empty($view->exposed_raw_input)) {
      $url_options['query'] = $view->exposed_raw_input;
    }

904 905
    // Compare the link to the default home page; if it's the default home page,
    // just use $base_url.
906
    if ($path == $config->get('page.front')) {
merlinofchaos's avatar
merlinofchaos committed
907 908 909
      $path = '';
    }

910
    $variables['link'] = check_url(_url($path, $url_options));
merlinofchaos's avatar
merlinofchaos committed
911 912
  }

913
  $variables['langcode'] = String::checkPlain(\Drupal::languageManager()->getCurrentLanguage()->getId());
914 915 916
  $variables['namespaces'] = new Attribute($style->namespaces);
  $variables['items'] = $items;
  $variables['channel_elements'] = format_xml_elements($style->channel_elements);
merlinofchaos's avatar
merlinofchaos committed
917 918 919

  // During live preview we don't want to output the header since the contents
  // of the feed are being displayed inside a normal HTML page.
920 921
  if (empty($variables['view']->live_preview)) {
    $variables['view']->getResponse()->headers->set('Content-Type', 'application/rss+xml; charset=utf-8');
merlinofchaos's avatar
merlinofchaos committed
922 923 924 925
  }
}

/**
926 927 928 929
 * Prepares variables for views RSS item templates.
 *
 * Default template: views-view-row-rss.html.twig.
 *
930
 * @param array $variables
931 932
 *   An associative array containing:
 *   - row: The raw results rows.
merlinofchaos's avatar
merlinofchaos committed
933
 */
934 935
function template_preprocess_views_view_row_rss(&$variables) {
  $item = $variables['row'];
merlinofchaos's avatar
merlinofchaos committed
936

937
  $variables['title'] = String::checkPlain($item->title);
938
  $variables['link'] = check_url($item->link);
939
  $variables['description'] = String::checkPlain($item->description);
940
  $variables['item_elements'] = empty($item->elements) ? '' : format_xml_elements($item->elements);
merlinofchaos's avatar
merlinofchaos committed
941 942
}

943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993
/**
 * Prepares variables for OPML feed templates.
 *
 * Default template: views-view-opml.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - view: A ViewExecutable object.
 *   - rows: The raw row data.
 */
function template_preprocess_views_view_opml(&$variables) {
  $view  = $variables['view'];
  $items = $variables['rows'];

  $config = \Drupal::config('system.site');

  if ($view->display_handler->getOption('sitename_title')) {
    $title = $config->get('name');
    if ($slogan = $config->get('slogan')) {
      $title .= ' - ' . $slogan;
    }
  }
  else {
    $title = $view->getTitle();
  }
  $variables['title'] = String::checkPlain($title);
  $variables['items'] = $items;
  $variables['updated'] = gmdate(DATE_RFC2822, REQUEST_TIME);

  // During live preview we don't want to output the header since the contents
  // of the feed are being displayed inside a normal HTML page.
  if (empty($variables['view']->live_preview)) {
    $variables['view']->getResponse()->headers->set('Content-Type', 'text/xml; charset=utf-8');
  }
}

/**
 * Prepares variables for views OPML item templates.
 *
 * Default template: views-view-row-opml.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - row: The raw results rows.
 */
function template_preprocess_views_view_row_opml(&$variables) {
  $item = $variables['row'];

  $variables['attributes'] = new Attribute($item);
}

merlinofchaos's avatar
merlinofchaos committed
994
/**
995 996 997 998
 * Prepares variables for views exposed form templates.
 *
 * Default template: views-exposed-form.html.twig.
 *
999
 * @param array $variables
1000 1001
 *   An associative array containing:
 *   - form: A render element representing the form.
merlinofchaos's avatar
merlinofchaos committed
1002
 */
1003 1004
function template_preprocess_views_exposed_form(&$variables) {
  $form = &$variables['form'];
merlinofchaos's avatar
merlinofchaos committed
1005 1006

  if (!empty($form['q'])) {
1007
    $variables['q'] = $form['q'];
merlinofchaos's avatar
merlinofchaos committed
1008 1009
  }

1010
  // Include basic theming for exposed forms.
1011
  $form['#attached']['library'][] = 'views/views.exposed-form';
1012

1013
  foreach ($form['#info'] as $info) {
merlinofchaos's avatar
merlinofchaos committed
1014
    if (!empty($info['label'])) {
1015
      $form[$info['value']]['#title'] = $info['label'];
merlinofchaos's avatar
merlinofchaos committed
1016 1017 1018 1019
    }
  }
}

1020
/**
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
 * Prepares variables for views mini-pager templates.
 *
 * Default template: views-mini-pager.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - tags: Provides link text for the next/previous links.
 *   - element: The pager's id.
 *   - parameters: Any extra GET parameters that should be retained, such as
 *     exposed input.
1031
 */
1032
function template_preprocess_views_mini_pager(&$variables) {
merlinofchaos's avatar
merlinofchaos committed
1033 1034
  global $pager_page_array, $pager_total;

1035 1036 1037
  $tags = &$variables['tags'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
merlinofchaos's avatar
merlinofchaos committed
1038

1039
  // Current is the page we are currently paged to.
1040
  $variables['items']['current'] = $pager_page_array[$element] + 1;
merlinofchaos's avatar
merlinofchaos committed
1041

1042
  if ($pager_total[$element] > 1 && $pager_page_array[$element] > 0) {
1043 1044
    $options = array(
      'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
merlinofchaos's avatar
merlinofchaos committed
1045
    );
1046 1047 1048 1049 1050
    $variables['items']['previous']['href'] = \Drupal::url('<current>', [], $options);
    if (isset($tags[1])) {
      $variables['items']['previous']['text'] = $tags[1];
    }
    $variables['items']['previous']['attributes'] = new Attribute();
1051