Projet

Général

Profil

Paste
Télécharger (58,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / views / handlers / views_handler_field.inc @ 4003efde

1
<?php
2

    
3
/**
4
 * @file
5
 * Definition of views_handler_field.
6
 */
7

    
8
/**
9
 * @defgroup views_field_handlers Views field handlers
10
 * @{
11
 * Handlers to tell Views how to build and display fields.
12
 */
13

    
14
/**
15
 * Indicator of the render_text() method for rendering a single item.
16
 *
17
 * If no render_item() is present.
18
 */
19
define('VIEWS_HANDLER_RENDER_TEXT_PHASE_SINGLE_ITEM', 0);
20

    
21
/**
22
 * Indicator of the render_text() method for rendering the whole element.
23
 *
24
 * if no render_item() method is available.
25
 */
26
define('VIEWS_HANDLER_RENDER_TEXT_PHASE_COMPLETELY', 1);
27

    
28
/**
29
 * Indicator of the render_text() method for rendering the empty text.
30
 */
31
define('VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY', 2);
32

    
33
/**
34
 * Base field handler that has no options and renders an unformatted field.
35
 *
36
 * Definition terms:
37
 * - additional fields: An array of fields that should be added to the query
38
 *   for some purpose. The array is in the form of:
39
 *   array(
40
 *     'identifier' => array(
41
 *       'table' => tablename,
42
 *       'field' => fieldname,
43
 *     )
44
 *   );
45
 *   with as many fields as are necessary may be in this array.
46
 * - click sortable: If TRUE, this field may be click sorted.
47
 *
48
 * @ingroup views_field_handlers
49
 */
50
class views_handler_field extends views_handler {
51

    
52
  /**
53
   *
54
   */
55
  public $field_alias = 'unknown';
56

    
57
  /**
58
   *
59
   */
60
  public $aliases = array();
61

    
62
  /**
63
   * The field value prior to any rewriting.
64
   *
65
   * @var mixed
66
   */
67
  public $original_value = NULL;
68

    
69
  /**
70
   * @var array
71
   * Stores additional fields which get added to the query.
72
   * The generated aliases are stored in $aliases.
73
   */
74
  public $additional_fields = array();
75

    
76
  /**
77
   * Construct a new field handler.
78
   */
79
  public function construct() {
80
    parent::construct();
81

    
82
    $this->additional_fields = array();
83
    if (!empty($this->definition['additional fields'])) {
84
      $this->additional_fields = $this->definition['additional fields'];
85
    }
86

    
87
    if (!isset($this->options['exclude'])) {
88
      $this->options['exclude'] = '';
89
    }
90
  }
91

    
92
  /**
93
   * Determine if this field can allow advanced rendering.
94
   *
95
   * Fields can set this to FALSE if they do not wish to allow token based
96
   * rewriting or link-making.
97
   */
98
  public function allow_advanced_render() {
99
    return TRUE;
100
  }
101

    
102
  /**
103
   * {@inheritdoc}
104
   */
105
  public function init(&$view, &$options) {
106
    parent::init($view, $options);
107
  }
108

    
109
  /**
110
   * Called to add the field to a query.
111
   */
112
  public function query() {
113
    $this->ensure_my_table();
114
    // Add the field.
115
    $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
116
    $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field, NULL, $params);
117

    
118
    $this->add_additional_fields();
119
  }
120

    
121
  /**
122
   * Add 'additional' fields to the query.
123
   *
124
   * @param array $fields
125
   *   An array of fields. The key is an identifier used to later find the field
126
   *   alias used. The value is either a string in which case it's assumed to be
127
   *   a field on this handler's table; or it's an array in the form of
128
   *   @code array('table' => $tablename, 'field' => $fieldname) @endcode
129
   */
130
  public function add_additional_fields($fields = NULL) {
131
    if (!isset($fields)) {
132
      // Notice check.
133
      if (empty($this->additional_fields)) {
134
        return;
135
      }
136
      $fields = $this->additional_fields;
137
    }
138

    
139
    $group_params = array();
140
    if ($this->options['group_type'] != 'group') {
141
      $group_params = array(
142
        'function' => $this->options['group_type'],
143
      );
144
    }
145

    
146
    if (!empty($fields) && is_array($fields)) {
147
      foreach ($fields as $identifier => $info) {
148
        if (is_array($info)) {
149
          if (isset($info['table'])) {
150
            $table_alias = $this->query->ensure_table($info['table'], $this->relationship);
151
          }
152
          else {
153
            $table_alias = $this->table_alias;
154
          }
155

    
156
          if (empty($table_alias)) {
157
            $t_args = array(
158
              '@handler' => $this->definition['handler'],
159
              '@identifier' => $identifier,
160
              '@table' => $info['table'],
161
            );
162
            debug(t('Handler @handler tried to add additional field @identifier but @table could not be added.', $t_args));
163
            $this->aliases[$identifier] = 'broken';
164
            continue;
165
          }
166

    
167
          $params = array();
168
          if (!empty($info['params'])) {
169
            $params = $info['params'];
170
          }
171

    
172
          $params += $group_params;
173
          $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field'], NULL, $params);
174
        }
175
        else {
176
          $this->aliases[$info] = $this->query->add_field($this->table_alias, $info, NULL, $group_params);
177
        }
178
      }
179
    }
180
  }
181

    
182
  /**
183
   * Called to determine what to tell the clicksorter.
184
   */
185
  public function click_sort($order) {
186
    if (isset($this->field_alias)) {
187
      // Since fields should always have themselves already added, just
188
      // add a sort on the field.
189
      $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
190
      $this->query->add_orderby(NULL, NULL, $order, $this->field_alias, $params);
191
    }
192
  }
193

    
194
  /**
195
   * Determine if this field is click sortable.
196
   */
197
  public function click_sortable() {
198
    return !empty($this->definition['click sortable']);
199
  }
200

    
201
  /**
202
   * Get this field's label.
203
   */
204
  public function label() {
205
    if (!isset($this->options['label'])) {
206
      return '';
207
    }
208
    return $this->options['label'];
209
  }
210

    
211
  /**
212
   * Return an HTML element based upon the field's element type.
213
   */
214
  public function element_type($none_supported = FALSE, $default_empty = FALSE, $inline = FALSE) {
215
    if ($none_supported) {
216
      if ($this->options['element_type'] === '0') {
217
        return '';
218
      }
219
    }
220
    if ($this->options['element_type']) {
221
      return check_plain($this->options['element_type']);
222
    }
223

    
224
    if ($default_empty) {
225
      return '';
226
    }
227

    
228
    if ($inline) {
229
      return 'span';
230
    }
231

    
232
    if (isset($this->definition['element type'])) {
233
      return $this->definition['element type'];
234
    }
235

    
236
    return 'span';
237
  }
238

    
239
  /**
240
   * Return an HTML element for the label based upon the field's element type.
241
   */
242
  public function element_label_type($none_supported = FALSE, $default_empty = FALSE) {
243
    if ($none_supported) {
244
      if ($this->options['element_label_type'] === '0') {
245
        return '';
246
      }
247
    }
248
    if ($this->options['element_label_type']) {
249
      return check_plain($this->options['element_label_type']);
250
    }
251

    
252
    if ($default_empty) {
253
      return '';
254
    }
255

    
256
    return 'span';
257
  }
258

    
259
  /**
260
   * Return an HTML element for the wrapper based upon the field's element type.
261
   */
262
  public function element_wrapper_type($none_supported = FALSE, $default_empty = FALSE) {
263
    if ($none_supported) {
264
      if ($this->options['element_wrapper_type'] === '0') {
265
        return 0;
266
      }
267
    }
268
    if ($this->options['element_wrapper_type']) {
269
      return check_plain($this->options['element_wrapper_type']);
270
    }
271

    
272
    if ($default_empty) {
273
      return '';
274
    }
275

    
276
    return 'div';
277
  }
278

    
279
  /**
280
   * Provide a list of elements valid for field HTML.
281
   *
282
   * This function can be overridden by fields that want more or fewer elements
283
   * available, though this seems like it would be an incredibly rare occurence.
284
   */
285
  public function get_elements() {
286
    static $elements = NULL;
287
    if (!isset($elements)) {
288
      $elements = variable_get('views_field_rewrite_elements', array(
289
        '' => t('- Use default -'),
290
        '0' => t('- None -'),
291
        'div' => 'DIV',
292
        'span' => 'SPAN',
293
        'h1' => 'H1',
294
        'h2' => 'H2',
295
        'h3' => 'H3',
296
        'h4' => 'H4',
297
        'h5' => 'H5',
298
        'h6' => 'H6',
299
        'p' => 'P',
300
        'strong' => 'STRONG',
301
        'em' => 'EM',
302
      ));
303
    }
304

    
305
    return $elements;
306
  }
307

    
308
  /**
309
   * Return the class of the field.
310
   */
311
  public function element_classes($row_index = NULL) {
312
    $classes = explode(' ', $this->options['element_class']);
313
    foreach ($classes as &$class) {
314
      $class = $this->tokenize_value($class, $row_index);
315
      $class = views_clean_css_identifier($class);
316
    }
317
    return implode(' ', $classes);
318
  }
319

    
320
  /**
321
   * Replace a value with tokens from the last field.
322
   *
323
   * This function actually figures out which field was last and uses its
324
   * tokens so they will all be available.
325
   */
326
  public function tokenize_value($value, $row_index = NULL) {
327
    if (strpos($value, '[') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) {
328
      $fake_item = array(
329
        'alter_text' => TRUE,
330
        'text' => $value,
331
      );
332

    
333
      // Use isset() because empty() will trigger on 0 and 0 is the first row.
334
      if (isset($row_index) && isset($this->view->style_plugin->render_tokens[$row_index])) {
335
        $tokens = $this->view->style_plugin->render_tokens[$row_index];
336
      }
337
      else {
338
        // Get tokens from the last field.
339
        $last_field = end($this->view->field);
340
        if (isset($last_field->last_tokens)) {
341
          $tokens = $last_field->last_tokens;
342
        }
343
        else {
344
          $tokens = $last_field->get_render_tokens($fake_item);
345
        }
346
      }
347

    
348
      $value = strip_tags($this->render_altered($fake_item, $tokens));
349
      if (!empty($this->options['alter']['trim_whitespace'])) {
350
        $value = trim($value);
351
      }
352
    }
353

    
354
    return $value;
355
  }
356

    
357
  /**
358
   * Return the class of the field's label.
359
   */
360
  public function element_label_classes($row_index = NULL) {
361
    $classes = explode(' ', $this->options['element_label_class']);
362
    foreach ($classes as &$class) {
363
      $class = $this->tokenize_value($class, $row_index);
364
      $class = views_clean_css_identifier($class);
365
    }
366
    return implode(' ', $classes);
367
  }
368

    
369
  /**
370
   * Return the class of the field's wrapper.
371
   */
372
  public function element_wrapper_classes($row_index = NULL) {
373
    $classes = explode(' ', $this->options['element_wrapper_class']);
374
    foreach ($classes as &$class) {
375
      $class = $this->tokenize_value($class, $row_index);
376
      $class = views_clean_css_identifier($class);
377
    }
378
    return implode(' ', $classes);
379
  }
380

    
381
  /**
382
   * Get the value that's supposed to be rendered.
383
   *
384
   * This api exists so that other modules can easy set the values of the field
385
   * without having the need to change the render method as well.
386
   *
387
   * @param object $values
388
   *   An object containing all retrieved values.
389
   * @param string $field
390
   *   Optional name of the field where the value is stored.
391
   */
392
  public function get_value($values, $field = NULL) {
393
    $alias = isset($field) ? $this->aliases[$field] : $this->field_alias;
394
    if (isset($values->{$alias})) {
395
      return $values->{$alias};
396
    }
397
  }
398

    
399
  /**
400
   * Determines if this field will be available as an option to group the result
401
   * by in the style settings.
402
   *
403
   * @return bool
404
   *   TRUE if this field handler is groupable, otherwise FALSE.
405
   */
406
  public function use_string_group_by() {
407
    return TRUE;
408
  }
409

    
410
  /**
411
   * {@inheritdoc}
412
   */
413
  public function option_definition() {
414
    $options = parent::option_definition();
415

    
416
    $options['label'] = array('default' => $this->definition['title'], 'translatable' => TRUE);
417
    $options['exclude'] = array('default' => FALSE, 'bool' => TRUE);
418
    $options['alter'] = array(
419
      'contains' => array(
420
        'alter_text' => array('default' => FALSE, 'bool' => TRUE),
421
        'text' => array('default' => '', 'translatable' => TRUE),
422
        'make_link' => array('default' => FALSE, 'bool' => TRUE),
423
        'path' => array('default' => ''),
424
        'absolute' => array('default' => FALSE, 'bool' => TRUE),
425
        'external' => array('default' => FALSE, 'bool' => TRUE),
426
        'replace_spaces' => array('default' => FALSE, 'bool' => TRUE),
427
        'unwanted_characters' => array('default' => ''),
428
        'path_case' => array('default' => 'none', 'translatable' => FALSE),
429
        'trim_whitespace' => array('default' => FALSE, 'bool' => TRUE),
430
        'alt' => array('default' => '', 'translatable' => TRUE),
431
        'rel' => array('default' => ''),
432
        'link_class' => array('default' => ''),
433
        'prefix' => array('default' => '', 'translatable' => TRUE),
434
        'suffix' => array('default' => '', 'translatable' => TRUE),
435
        'target' => array('default' => ''),
436
        'nl2br' => array('default' => FALSE, 'bool' => TRUE),
437
        'max_length' => array('default' => ''),
438
        'word_boundary' => array('default' => TRUE, 'bool' => TRUE),
439
        'ellipsis' => array('default' => TRUE, 'bool' => TRUE),
440
        'more_link' => array('default' => FALSE, 'bool' => TRUE),
441
        'more_link_text' => array('default' => '', 'translatable' => TRUE),
442
        'more_link_path' => array('default' => ''),
443
        'strip_tags' => array('default' => FALSE, 'bool' => TRUE),
444
        'trim' => array('default' => FALSE, 'bool' => TRUE),
445
        'preserve_tags' => array('default' => ''),
446
        'html' => array('default' => FALSE, 'bool' => TRUE),
447
      ),
448
    );
449
    $options['element_type'] = array('default' => '');
450
    $options['element_class'] = array('default' => '');
451

    
452
    $options['element_label_type'] = array('default' => '');
453
    $options['element_label_class'] = array('default' => '');
454
    $options['element_label_colon'] = array('default' => TRUE, 'bool' => TRUE);
455

    
456
    $options['element_wrapper_type'] = array('default' => '');
457
    $options['element_wrapper_class'] = array('default' => '');
458

    
459
    $options['element_default_classes'] = array('default' => TRUE, 'bool' => TRUE);
460

    
461
    $options['empty'] = array('default' => '', 'translatable' => TRUE);
462
    $options['hide_empty'] = array('default' => FALSE, 'bool' => TRUE);
463
    $options['empty_zero'] = array('default' => FALSE, 'bool' => TRUE);
464
    $options['hide_alter_empty'] = array('default' => TRUE, 'bool' => TRUE);
465

    
466
    return $options;
467
  }
468

    
469
  /**
470
   * Performs some cleanup tasks on the options array before saving it.
471
   */
472
  public function options_submit(&$form, &$form_state) {
473
    $options = &$form_state['values']['options'];
474
    $types = array(
475
      'element_type',
476
      'element_label_type',
477
      'element_wrapper_type',
478
    );
479
    $base_types = array(
480
      'element_class',
481
      'element_label_class',
482
      'element_wrapper_class',
483
    );
484
    $classes = array_combine($base_types, $types);
485

    
486
    foreach ($types as $type) {
487
      if (!$options[$type . '_enable']) {
488
        $options[$type] = '';
489
      }
490
    }
491

    
492
    foreach ($classes as $class => $type) {
493
      if (!$options[$class . '_enable'] || !$options[$type . '_enable']) {
494
        $options[$class] = '';
495
      }
496
    }
497

    
498
    if (empty($options['custom_label'])) {
499
      $options['label'] = '';
500
      $options['element_label_colon'] = FALSE;
501
    }
502
  }
503

    
504
  /**
505
   * Default options form provides the label widget that all fields should have.
506
   */
507
  public function options_form(&$form, &$form_state) {
508
    parent::options_form($form, $form_state);
509

    
510
    $label = $this->label();
511
    $form['custom_label'] = array(
512
      '#type' => 'checkbox',
513
      '#title' => t('Create a label'),
514
      '#description' => t('Enable to create a label for this field.'),
515
      '#default_value' => $label !== '',
516
      '#weight' => -103,
517
    );
518
    $form['label'] = array(
519
      '#type' => 'textfield',
520
      '#title' => t('Label'),
521
      '#default_value' => $label,
522
      '#dependency' => array(
523
        'edit-options-custom-label' => array(1),
524
      ),
525
      '#weight' => -102,
526
    );
527
    $form['element_label_colon'] = array(
528
      '#type' => 'checkbox',
529
      '#title' => t('Place a colon after the label'),
530
      '#default_value' => $this->options['element_label_colon'],
531
      '#dependency' => array(
532
        'edit-options-custom-label' => array(1),
533
      ),
534
      '#weight' => -101,
535
    );
536

    
537
    $form['exclude'] = array(
538
      '#type' => 'checkbox',
539
      '#title' => t('Exclude from display'),
540
      '#default_value' => $this->options['exclude'],
541
      '#description' => t('Enable to load this field as hidden. Often used to group fields, or to use as token in another field.'),
542
      '#weight' => -100,
543
    );
544

    
545
    $form['style_settings'] = array(
546
      '#type' => 'fieldset',
547
      '#title' => t('Style settings'),
548
      '#collapsible' => TRUE,
549
      '#collapsed' => TRUE,
550
      '#weight' => 99,
551
    );
552

    
553
    $form['element_type_enable'] = array(
554
      '#type' => 'checkbox',
555
      '#title' => t('Customize field HTML'),
556
      '#default_value' => !empty($this->options['element_type']) || (string) $this->options['element_type'] == '0' || !empty($this->options['element_class']) || (string) $this->options['element_class'] == '0',
557
      '#fieldset' => 'style_settings',
558
    );
559
    $form['element_type'] = array(
560
      '#title' => t('HTML element'),
561
      '#options' => $this->get_elements(),
562
      '#type' => 'select',
563
      '#default_value' => $this->options['element_type'],
564
      '#description' => t('Choose the HTML element to wrap around this field, e.g. H1, H2, etc.'),
565
      '#dependency' => array(
566
        'edit-options-element-type-enable' => array(1),
567
      ),
568
      '#fieldset' => 'style_settings',
569
    );
570

    
571
    $form['element_class_enable'] = array(
572
      '#type' => 'checkbox',
573
      '#title' => t('Create a CSS class'),
574
      '#dependency' => array(
575
        'edit-options-element-type-enable' => array(1),
576
      ),
577
      '#default_value' => !empty($this->options['element_class']) || (string) $this->options['element_class'] == '0',
578
      '#fieldset' => 'style_settings',
579
    );
580
    $form['element_class'] = array(
581
      '#title' => t('CSS class'),
582
      '#description' => t('You may use token substitutions from the rewriting section in this class.'),
583
      '#type' => 'textfield',
584
      '#default_value' => $this->options['element_class'],
585
      '#dependency' => array(
586
        'edit-options-element-class-enable' => array(1),
587
        'edit-options-element-type-enable' => array(1),
588
      ),
589
      '#dependency_count' => 2,
590
      '#fieldset' => 'style_settings',
591
    );
592

    
593
    $form['element_label_type_enable'] = array(
594
      '#type' => 'checkbox',
595
      '#title' => t('Customize label HTML'),
596
      '#default_value' => !empty($this->options['element_label_type']) || (string) $this->options['element_label_type'] == '0' || !empty($this->options['element_label_class']) || (string) $this->options['element_label_class'] == '0',
597
      '#fieldset' => 'style_settings',
598
    );
599
    $form['element_label_type'] = array(
600
      '#title' => t('Label HTML element'),
601
      '#options' => $this->get_elements(FALSE),
602
      '#type' => 'select',
603
      '#default_value' => $this->options['element_label_type'],
604
      '#description' => t('Choose the HTML element to wrap around this label, e.g. H1, H2, etc.'),
605
      '#dependency' => array(
606
        'edit-options-element-label-type-enable' => array(1),
607
      ),
608
      '#fieldset' => 'style_settings',
609
    );
610
    $form['element_label_class_enable'] = array(
611
      '#type' => 'checkbox',
612
      '#title' => t('Create a CSS class'),
613
      '#dependency' => array(
614
        'edit-options-element-label-type-enable' => array(1),
615
      ),
616
      '#default_value' => !empty($this->options['element_label_class']) || (string) $this->options['element_label_class'] == '0',
617
      '#fieldset' => 'style_settings',
618
    );
619
    $form['element_label_class'] = array(
620
      '#title' => t('CSS class'),
621
      '#description' => t('You may use token substitutions from the rewriting section in this class.'),
622
      '#type' => 'textfield',
623
      '#default_value' => $this->options['element_label_class'],
624
      '#dependency' => array(
625
        'edit-options-element-label-class-enable' => array(1),
626
        'edit-options-element-label-type-enable' => array(1),
627
      ),
628
      '#dependency_count' => 2,
629
      '#fieldset' => 'style_settings',
630
    );
631

    
632
    $form['element_wrapper_type_enable'] = array(
633
      '#type' => 'checkbox',
634
      '#title' => t('Customize field and label wrapper HTML'),
635
      '#default_value' => !empty($this->options['element_wrapper_type']) || (string) $this->options['element_wrapper_type'] == '0' || !empty($this->options['element_wrapper_class']) || (string) $this->options['element_wrapper_class'] == '0',
636
      '#fieldset' => 'style_settings',
637
    );
638
    $form['element_wrapper_type'] = array(
639
      '#title' => t('Wrapper HTML element'),
640
      '#options' => $this->get_elements(FALSE),
641
      '#type' => 'select',
642
      '#default_value' => $this->options['element_wrapper_type'],
643
      '#description' => t('Choose the HTML element to wrap around this field and label, e.g. H1, H2, etc. This may not be used if the field and label are not rendered together, such as with a table.'),
644
      '#dependency' => array(
645
        'edit-options-element-wrapper-type-enable' => array(1),
646
      ),
647
      '#fieldset' => 'style_settings',
648
    );
649

    
650
    $form['element_wrapper_class_enable'] = array(
651
      '#type' => 'checkbox',
652
      '#title' => t('Create a CSS class'),
653
      '#dependency' => array(
654
        'edit-options-element-wrapper-type-enable' => array(1),
655
      ),
656
      '#default_value' => !empty($this->options['element_wrapper_class']) || (string) $this->options['element_wrapper_class'] == '0',
657
      '#fieldset' => 'style_settings',
658
    );
659
    $form['element_wrapper_class'] = array(
660
      '#title' => t('CSS class'),
661
      '#description' => t('You may use token substitutions from the rewriting section in this class.'),
662
      '#type' => 'textfield',
663
      '#default_value' => $this->options['element_wrapper_class'],
664
      '#dependency' => array(
665
        'edit-options-element-wrapper-class-enable' => array(1),
666
        'edit-options-element-wrapper-type-enable' => array(1),
667
      ),
668
      '#dependency_count' => 2,
669
      '#fieldset' => 'style_settings',
670
    );
671

    
672
    $form['element_default_classes'] = array(
673
      '#type' => 'checkbox',
674
      '#title' => t('Add default classes'),
675
      '#default_value' => $this->options['element_default_classes'],
676
      '#description' => t('Use default Views classes to identify the field, field label and field content.'),
677
      '#fieldset' => 'style_settings',
678
    );
679

    
680
    $form['alter'] = array(
681
      '#title' => t('Rewrite results'),
682
      '#type' => 'fieldset',
683
      '#collapsible' => TRUE,
684
      '#collapsed' => TRUE,
685
      '#weight' => 100,
686
    );
687

    
688
    if ($this->allow_advanced_render()) {
689
      $form['alter']['#tree'] = TRUE;
690
      $form['alter']['alter_text'] = array(
691
        '#type' => 'checkbox',
692
        '#title' => t('Rewrite the output of this field'),
693
        '#description' => t('Enable to override the output of this field with custom text or replacement tokens.'),
694
        '#default_value' => $this->options['alter']['alter_text'],
695
      );
696

    
697
      $form['alter']['text'] = array(
698
        '#title' => t('Text'),
699
        '#type' => 'textarea',
700
        '#default_value' => $this->options['alter']['text'],
701
        '#description' => t('The text to display for this field. You may include HTML. You may enter data from this view as per the "Replacement patterns" below.'),
702
        '#dependency' => array(
703
          'edit-options-alter-alter-text' => array(1),
704
        ),
705
      );
706

    
707
      $form['alter']['make_link'] = array(
708
        '#type' => 'checkbox',
709
        '#title' => t('Output this field as a link'),
710
        '#description' => t('If checked, this field will be made into a link. The destination must be given below.'),
711
        '#default_value' => $this->options['alter']['make_link'],
712
      );
713
      $form['alter']['path'] = array(
714
        '#title' => t('Link path'),
715
        '#type' => 'textfield',
716
        '#default_value' => $this->options['alter']['path'],
717
        '#description' => t('The Drupal path or absolute URL for this link. You may enter data from this view as per the "Replacement patterns" below.'),
718
        '#dependency' => array(
719
          'edit-options-alter-make-link' => array(1),
720
        ),
721
        '#maxlength' => 255,
722
      );
723
      $form['alter']['absolute'] = array(
724
        '#type' => 'checkbox',
725
        '#title' => t('Use absolute path'),
726
        '#default_value' => $this->options['alter']['absolute'],
727
        '#dependency' => array(
728
          'edit-options-alter-make-link' => array(1),
729
        ),
730
      );
731
      $form['alter']['replace_spaces'] = array(
732
        '#type' => 'checkbox',
733
        '#title' => t('Replace spaces with dashes'),
734
        '#default_value' => $this->options['alter']['replace_spaces'],
735
        '#dependency' => array(
736
          'edit-options-alter-make-link' => array(1),
737
        ),
738
      );
739
      $form['alter']['external'] = array(
740
        '#type' => 'checkbox',
741
        '#title' => t('External server URL'),
742
        '#default_value' => $this->options['alter']['external'],
743
        '#description' => t("Links to an external server using a full URL: e.g. 'http://www.example.com' or 'www.example.com'."),
744
        '#dependency' => array(
745
          'edit-options-alter-make-link' => array(1),
746
        ),
747
      );
748
      $form['alter']['unwanted_characters'] = array(
749
        '#type' => 'textfield',
750
        '#title' => t('Remove unwanted characters'),
751
        '#description' => t('Space-separated list of characters to remove from the URL path'),
752
        '#default_value' => $this->options['alter']['unwanted_characters'],
753
        '#dependency' => array(
754
          'edit-options-alter-make-link' => array(1)
755
        ),
756
        '#maxlength' => 255,
757
      );
758
      $form['alter']['path_case'] = array(
759
        '#type' => 'select',
760
        '#title' => t('Transform the case'),
761
        '#description' => t('When printing url paths, how to transform the case of the filter value.'),
762
        '#dependency' => array(
763
          'edit-options-alter-make-link' => array(1),
764
        ),
765
        '#options' => array(
766
          'none' => t('No transform'),
767
          'upper' => t('Upper case'),
768
          'lower' => t('Lower case'),
769
          'ucfirst' => t('Capitalize first letter'),
770
          'ucwords' => t('Capitalize each word'),
771
        ),
772
        '#default_value' => $this->options['alter']['path_case'],
773
      );
774
      $form['alter']['link_class'] = array(
775
        '#title' => t('Link class'),
776
        '#type' => 'textfield',
777
        '#default_value' => $this->options['alter']['link_class'],
778
        '#description' => t('The CSS class to apply to the link.'),
779
        '#dependency' => array(
780
          'edit-options-alter-make-link' => array(1),
781
        ),
782
      );
783
      $form['alter']['alt'] = array(
784
        '#title' => t('Title text'),
785
        '#type' => 'textfield',
786
        '#default_value' => $this->options['alter']['alt'],
787
        '#description' => t('Text to place as "title" text which most browsers display as a tooltip when hovering over the link.'),
788
        '#dependency' => array(
789
          'edit-options-alter-make-link' => array(1),
790
        ),
791
      );
792
      $form['alter']['rel'] = array(
793
        '#title' => t('Rel Text'),
794
        '#type' => 'textfield',
795
        '#default_value' => $this->options['alter']['rel'],
796
        '#description' => t('Include Rel attribute for use in lightbox2 or other JavaScript utility.'),
797
        '#dependency' => array(
798
          'edit-options-alter-make-link' => array(1),
799
        ),
800
      );
801
      $form['alter']['prefix'] = array(
802
        '#title' => t('Prefix text'),
803
        '#type' => 'textfield',
804
        '#default_value' => $this->options['alter']['prefix'],
805
        '#description' => t('Any text to display before this link. You may include HTML.'),
806
        '#dependency' => array(
807
          'edit-options-alter-make-link' => array(1),
808
        ),
809
      );
810
      $form['alter']['suffix'] = array(
811
        '#title' => t('Suffix text'),
812
        '#type' => 'textfield',
813
        '#default_value' => $this->options['alter']['suffix'],
814
        '#description' => t('Any text to display after this link. You may include HTML.'),
815
        '#dependency' => array(
816
          'edit-options-alter-make-link' => array(1),
817
        ),
818
      );
819
      $form['alter']['target'] = array(
820
        '#title' => t('Target'),
821
        '#type' => 'textfield',
822
        '#default_value' => $this->options['alter']['target'],
823
        '#description' => t("Target of the link, such as _blank, _parent or an iframe's name. This field is rarely used."),
824
        '#dependency' => array(
825
          'edit-options-alter-make-link' => array(1),
826
        ),
827
      );
828

    
829

    
830
      // Get a list of the available fields and arguments for token replacement.
831
      $options = array();
832
      foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
833
        $options[t('Fields')]["[$field]"] = $handler->ui_name();
834
        // We only use fields up to (and including) this one.
835
        if ($field == $this->options['id']) {
836
          break;
837
        }
838
      }
839
      // This lets us prepare the key as we want it printed.
840
      $count = 0;
841
      foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
842
        $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
843
        $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
844
      }
845

    
846
      $this->document_self_tokens($options[t('Fields')]);
847

    
848
      // Default text.
849
      $output = t('<p>You must add some additional fields to this display before using this field. These fields may be marked as <em>Exclude from display</em> if you prefer. Note that due to rendering order, you cannot use fields that come after this field; if you need a field not listed here, rearrange your fields.</p>');
850
      // We have some options, so make a list.
851
      if (!empty($options)) {
852
        $output = t('<p>The following tokens are available for this field. Note that due to rendering order, you cannot use fields that come after this field; if you need a field not listed here, rearrange your fields.
853
If you would like to have the characters \'[\' and \']\' please use the html entity codes \'%5B\' or  \'%5D\' or they will get replaced with empty space.</p>');
854
        foreach (array_keys($options) as $type) {
855
          if (!empty($options[$type])) {
856
            $items = array();
857
            foreach ($options[$type] as $key => $value) {
858
              $items[] = $key . ' == ' . check_plain($value);
859
            }
860
            $output .= theme('item_list',
861
              array(
862
                'items' => $items,
863
                'type' => $type,
864
              ));
865
          }
866
        }
867
      }
868
      // This construct uses 'hidden' and not markup because process doesn't
869
      // run. It also has an extra div because the dependency wants to hide the
870
      // parent in situations like this, so we need a second div to make this
871
      // work.
872
      $form['alter']['help'] = array(
873
        '#type' => 'fieldset',
874
        '#title' => t('Replacement patterns'),
875
        '#collapsible' => TRUE,
876
        '#collapsed' => TRUE,
877
        '#value' => $output,
878
        '#dependency' => array(
879
          'edit-options-alter-make-link' => array(1),
880
          'edit-options-alter-alter-text' => array(1),
881
          'edit-options-alter-more-link' => array(1),
882
        ),
883
      );
884

    
885
      $form['alter']['trim'] = array(
886
        '#type' => 'checkbox',
887
        '#title' => t('Trim this field to a maximum length'),
888
        '#description' => t('Enable to trim the field to a maximum length of characters'),
889
        '#default_value' => $this->options['alter']['trim'],
890
      );
891

    
892
      $form['alter']['max_length'] = array(
893
        '#title' => t('Maximum length'),
894
        '#type' => 'textfield',
895
        '#default_value' => $this->options['alter']['max_length'],
896
        '#description' => t('The maximum number of characters this field can be.'),
897
        '#dependency' => array(
898
          'edit-options-alter-trim' => array(1),
899
        ),
900
      );
901

    
902
      $form['alter']['word_boundary'] = array(
903
        '#type' => 'checkbox',
904
        '#title' => t('Trim only on a word boundary'),
905
        '#description' => t('If checked, this field be trimmed only on a word boundary. This is guaranteed to be the maximum characters stated or less. If there are no word boundaries this could trim a field to nothing.'),
906
        '#default_value' => $this->options['alter']['word_boundary'],
907
        '#dependency' => array(
908
          'edit-options-alter-trim' => array(1),
909
        ),
910
      );
911

    
912
      $form['alter']['ellipsis'] = array(
913
        '#type' => 'checkbox',
914
        '#title' => t('Add an ellipsis'),
915
        '#description' => t('If checked, a "..." will be added if a field was trimmed.'),
916
        '#default_value' => $this->options['alter']['ellipsis'],
917
        '#dependency' => array(
918
          'edit-options-alter-trim' => array(1),
919
        ),
920
      );
921

    
922
      $form['alter']['more_link'] = array(
923
        '#type' => 'checkbox',
924
        '#title' => t('Add a read-more link if output is trimmed.'),
925
        '#description' => t('If checked, a read-more link will be added at the end of the trimmed output'),
926
        '#default_value' => $this->options['alter']['more_link'],
927
        '#dependency' => array(
928
          'edit-options-alter-trim' => array(1),
929
        ),
930
      );
931

    
932
      $form['alter']['more_link_text'] = array(
933
        '#type' => 'textfield',
934
        '#title' => t('More link text'),
935
        '#default_value' => $this->options['alter']['more_link_text'],
936
        '#description' => t('The text which will be displayed on the more link. You may enter data from this view as per the "Replacement patterns" above.'),
937
        '#dependency_count' => 2,
938
        '#dependency' => array(
939
          'edit-options-alter-trim' => array(1),
940
          'edit-options-alter-more-link' => array(1),
941
        ),
942
      );
943
      $form['alter']['more_link_path'] = array(
944
        '#type' => 'textfield',
945
        '#title' => t('More link path'),
946
        '#default_value' => $this->options['alter']['more_link_path'],
947
        '#description' => t('The path which is used for the more link. You may enter data from this view as per the "Replacement patterns" above.'),
948
        '#dependency_count' => 2,
949
        '#dependency' => array(
950
          'edit-options-alter-trim' => array(1),
951
          'edit-options-alter-more-link' => array(1),
952
        ),
953
      );
954

    
955
      $form['alter']['html'] = array(
956
        '#type' => 'checkbox',
957
        '#title' => t('Field can contain HTML'),
958
        '#description' => t('If checked, HTML corrector will be run to ensure tags are properly closed after trimming.'),
959
        '#default_value' => $this->options['alter']['html'],
960
        '#dependency' => array(
961
          'edit-options-alter-trim' => array(1),
962
        ),
963
      );
964

    
965
      $form['alter']['strip_tags'] = array(
966
        '#type' => 'checkbox',
967
        '#title' => t('Strip HTML tags'),
968
        '#description' => t('If checked, all HTML tags will be stripped.'),
969
        '#default_value' => $this->options['alter']['strip_tags'],
970
      );
971

    
972
      $form['alter']['preserve_tags'] = array(
973
        '#type' => 'textfield',
974
        '#title' => t('Preserve certain tags'),
975
        '#description' => t('List the tags that need to be preserved during the stripping process. example &quot;&lt;p&gt; &lt;br&gt;&quot; which will preserve all p and br elements'),
976
        '#default_value' => $this->options['alter']['preserve_tags'],
977
        '#dependency' => array(
978
          'edit-options-alter-strip-tags' => array(1),
979
        ),
980
      );
981

    
982
      $form['alter']['trim_whitespace'] = array(
983
        '#type' => 'checkbox',
984
        '#title' => t('Remove whitespace'),
985
        '#description' => t('If checked, all whitespaces at the beginning and the end of the output will be removed.'),
986
        '#default_value' => $this->options['alter']['trim_whitespace'],
987
      );
988

    
989
      $form['alter']['nl2br'] = array(
990
        '#type' => 'checkbox',
991
        '#title' => t('Convert newlines to HTML &lt;br&gt; tags'),
992
        '#description' => t('If checked, all newlines chars (e.g. \n) are converted into HTML &lt;br&gt; tags.'),
993
        '#default_value' => $this->options['alter']['nl2br'],
994
      );
995
    }
996

    
997
    $form['empty_field_behavior'] = array(
998
      '#type' => 'fieldset',
999
      '#title' => t('No results behavior'),
1000
      '#collapsible' => TRUE,
1001
      '#collapsed' => TRUE,
1002
      '#weight' => 100,
1003
    );
1004

    
1005
    $form['empty'] = array(
1006
      '#type' => 'textarea',
1007
      '#title' => t('No results text'),
1008
      '#default_value' => $this->options['empty'],
1009
      '#description' => t('Provide text to display if this field contains an empty result. You may include HTML. You may enter data from this view as per the "Replacement patterns" in the "Rewrite Results" section below.'),
1010
      '#fieldset' => 'empty_field_behavior',
1011
    );
1012

    
1013
    $form['empty_zero'] = array(
1014
      '#type' => 'checkbox',
1015
      '#title' => t('Count the number 0 as empty'),
1016
      '#default_value' => $this->options['empty_zero'],
1017
      '#description' => t('Enable to display the "no results text" if the field contains the number 0.'),
1018
      '#fieldset' => 'empty_field_behavior',
1019
    );
1020

    
1021
    $form['hide_empty'] = array(
1022
      '#type' => 'checkbox',
1023
      '#title' => t('Hide if empty'),
1024
      '#default_value' => $this->options['hide_empty'],
1025
      '#description' => t('Enable to hide this field if it is empty. Note that the field label or rewritten output may still be displayed. To hide labels, check the style or row style settings for empty fields. To hide rewritten content, check the "Hide rewriting if empty" checkbox.'),
1026
      '#fieldset' => 'empty_field_behavior',
1027
    );
1028

    
1029
    $form['hide_alter_empty'] = array(
1030
      '#type' => 'checkbox',
1031
      '#title' => t('Hide rewriting if empty'),
1032
      '#default_value' => $this->options['hide_alter_empty'],
1033
      '#description' => t('Do not display rewritten content if this field is empty.'),
1034
      '#fieldset' => 'empty_field_behavior',
1035
    );
1036
  }
1037

    
1038
  /**
1039
   * Provide extra data to the administration form
1040
   */
1041
  public function admin_summary() {
1042
    return $this->label();
1043
  }
1044

    
1045
  /**
1046
   * Run before any fields are rendered.
1047
   *
1048
   * This gives the handlers some time to set up before any handler has been
1049
   * rendered.
1050
   *
1051
   * @param array $values
1052
   *   An array of all objects returned from the query.
1053
   */
1054
  public function pre_render(&$values) {
1055
  }
1056

    
1057
  /**
1058
   * Render the field.
1059
   *
1060
   * @param array $values
1061
   *   The values retrieved from the database.
1062
   */
1063
  public function render($values) {
1064
    $value = $this->get_value($values);
1065
    return $this->sanitize_value($value);
1066
  }
1067

    
1068
  /**
1069
   * Render a field using advanced settings.
1070
   *
1071
   * This renders a field normally, then decides if render-as-link and
1072
   * text-replacement rendering is necessary.
1073
   */
1074
  public function advanced_render($values) {
1075
    if ($this->allow_advanced_render() && method_exists($this, 'render_item')) {
1076
      $raw_items = $this->get_items($values);
1077
      // If there are no items, set the original value to NULL.
1078
      if (empty($raw_items)) {
1079
        $this->original_value = NULL;
1080
      }
1081
    }
1082
    else {
1083
      $value = $this->render($values);
1084
      if (is_array($value)) {
1085
        $value = drupal_render($value);
1086
      }
1087
      $this->last_render = $value;
1088
      $this->original_value = $value;
1089
    }
1090

    
1091
    if ($this->allow_advanced_render()) {
1092
      $tokens = NULL;
1093
      if (method_exists($this, 'render_item')) {
1094
        $items = array();
1095
        foreach ($raw_items as $count => $item) {
1096
          $value = $this->render_item($count, $item);
1097
          if (is_array($value)) {
1098
            $value = drupal_render($value);
1099
          }
1100
          $this->last_render = $value;
1101
          $this->original_value = $this->last_render;
1102

    
1103
          $alter = $item + $this->options['alter'];
1104
          $alter['phase'] = VIEWS_HANDLER_RENDER_TEXT_PHASE_SINGLE_ITEM;
1105
          $items[] = $this->render_text($alter);
1106
        }
1107

    
1108
        $value = $this->render_items($items);
1109
      }
1110
      else {
1111
        $alter = array('phase' => VIEWS_HANDLER_RENDER_TEXT_PHASE_COMPLETELY) + $this->options['alter'];
1112
        $value = $this->render_text($alter);
1113
      }
1114

    
1115
      if (is_array($value)) {
1116
        $value = drupal_render($value);
1117
      }
1118
      // This happens here so that render_as_link can get the unaltered value of
1119
      // this field as a token rather than the altered value.
1120
      $this->last_render = $value;
1121
    }
1122

    
1123
    if (empty($this->last_render)) {
1124
      if ($this->is_value_empty($this->last_render, $this->options['empty_zero'], FALSE)) {
1125
        $alter = $this->options['alter'];
1126
        $alter['alter_text'] = 1;
1127
        $alter['text'] = $this->options['empty'];
1128
        $alter['phase'] = VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY;
1129
        $this->last_render = $this->render_text($alter);
1130
      }
1131
    }
1132

    
1133
    return $this->last_render;
1134
  }
1135

    
1136
  /**
1137
   * Checks if a field value is empty.
1138
   *
1139
   * @param mixed $value
1140
   *   The field value.
1141
   * @param bool $empty_zero
1142
   *   Whether or not this field is configured to consider 0 as empty.
1143
   * @param bool $no_skip_empty
1144
   *   Whether or not to use empty() to check the value.
1145
   *
1146
   * @return bool
1147
   *   TRUE if the value is considered empty, FALSE otherwise.
1148
   */
1149
  public function is_value_empty($value, $empty_zero, $no_skip_empty = TRUE) {
1150
    if (!isset($value)) {
1151
      $empty = TRUE;
1152
    }
1153
    else {
1154
      $empty = ($empty_zero || ($value !== 0 && $value !== '0'));
1155
    }
1156

    
1157
    if ($no_skip_empty) {
1158
      $empty = empty($value) && $empty;
1159
    }
1160
    return $empty;
1161
  }
1162

    
1163
  /**
1164
   * Perform an advanced text render for the item.
1165
   *
1166
   * This is separated out as some fields may render lists, and this allows
1167
   * each item to be handled individually.
1168
   */
1169
  public function render_text($alter) {
1170
    $value = $this->last_render;
1171

    
1172
    if (!empty($alter['alter_text']) && $alter['text'] !== '') {
1173
      $tokens = $this->get_render_tokens($alter);
1174
      $value = $this->render_altered($alter, $tokens);
1175
    }
1176

    
1177
    if (!empty($this->options['alter']['trim_whitespace'])) {
1178
      $value = trim($value);
1179
    }
1180

    
1181
    // Check if there should be no further rewrite for empty values.
1182
    $no_rewrite_for_empty = $this->options['hide_alter_empty'] && $this->is_value_empty($this->original_value, $this->options['empty_zero']);
1183

    
1184
    // Check whether the value is empty and return nothing, so the field isn't
1185
    // rendered. First check whether the field should be hidden if the
1186
    // value(hide_alter_empty = TRUE) /the rewrite is empty (hide_alter_empty =
1187
    // FALSE). For numeric values you can specify whether "0"/0 should be empty.
1188
    if ((($this->options['hide_empty'] && empty($value))
1189
        || ($alter['phase'] != VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty))
1190
      && $this->is_value_empty($value, $this->options['empty_zero'], FALSE)) {
1191
      return '';
1192
    }
1193
    // Only in empty phase.
1194
    if ($alter['phase'] == VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) {
1195
      // If we got here then $alter contains the value of "No results text"
1196
      // and so there is nothing left to do.
1197
      return $value;
1198
    }
1199

    
1200
    if (!empty($alter['strip_tags'])) {
1201
      $value = strip_tags($value, $alter['preserve_tags']);
1202
    }
1203

    
1204
    $suffix = '';
1205
    if (!empty($alter['trim']) && !empty($alter['max_length'])) {
1206
      $length = strlen($value);
1207
      $value = $this->render_trim_text($alter, $value);
1208
      if ($this->options['alter']['more_link'] && strlen($value) < $length) {
1209
        $tokens = $this->get_render_tokens($alter);
1210
        $more_link_text = $this->options['alter']['more_link_text'] ? $this->options['alter']['more_link_text'] : t('more');
1211
        $more_link_text = strtr(filter_xss_admin($more_link_text), $tokens);
1212
        $more_link_path = $this->options['alter']['more_link_path'];
1213
        $more_link_path = strip_tags(decode_entities(strtr($more_link_path, $tokens)));
1214

    
1215
        // Take sure that paths which was runned through url() does work as
1216
        // well.
1217
        $base_path = base_path();
1218
        // Checks whether the path starts with the base_path.
1219
        if (strpos($more_link_path, $base_path) === 0) {
1220
          $more_link_path = drupal_substr($more_link_path, drupal_strlen($base_path));
1221
        }
1222

    
1223
        $more_link = l($more_link_text, $more_link_path, array('attributes' => array('class' => array('views-more-link'))));
1224

    
1225
        $suffix .= " " . $more_link;
1226
      }
1227
    }
1228

    
1229
    if (!empty($alter['nl2br'])) {
1230
      $value = nl2br($value);
1231
    }
1232
    $this->last_render_text = $value;
1233

    
1234
    if (!empty($alter['make_link']) && !empty($alter['path'])) {
1235
      if (!isset($tokens)) {
1236
        $tokens = $this->get_render_tokens($alter);
1237
      }
1238
      $value = $this->render_as_link($alter, $value, $tokens);
1239
    }
1240

    
1241
    return $value . $suffix;
1242
  }
1243

    
1244
  /**
1245
   * Render this field as altered text, from a fieldset set by the user.
1246
   */
1247
  public function render_altered($alter, $tokens) {
1248
    // We trust admins so we allow any tag content. This is important for
1249
    // displays such as XML where we should not mess with tags.
1250
    $value = $alter['text'];
1251
    $value = strtr($value, $tokens);
1252
    // User might already used '%5B' and '%5D' instead of literal [ and ].
1253
    // After token replacements, we need to convert those codes to literal
1254
    // square bracket characters. Otherwise problems like comment #5 and #6 of
1255
    // https://www.drupal.org/node/578772 will happen.
1256
    // We could have used rawurldecode() also, but not sure about the consequences.
1257
    $value = strtr($value, array('%5B' => '[', '%5D' => ']'));
1258

    
1259
    return $value;
1260
  }
1261

    
1262
  /**
1263
   * Trim the field down to the specified length.
1264
   */
1265
  public function render_trim_text($alter, $value) {
1266
    if (!empty($alter['strip_tags'])) {
1267
      // NOTE: It's possible that some external fields might override the
1268
      // element type so if someone from, say, CCK runs into a bug here, this
1269
      // may be why =)
1270
      $this->definition['element type'] = 'span';
1271
    }
1272
    return views_trim_text($alter, $value);
1273
  }
1274

    
1275
  /**
1276
   * Render this field as a link, with info from a fieldset set by the user.
1277
   */
1278
  public function render_as_link($alter, $text, $tokens) {
1279
    $value = '';
1280

    
1281
    if (!empty($alter['prefix'])) {
1282
      $value .= filter_xss_admin(strtr($alter['prefix'], $tokens));
1283
    }
1284

    
1285
    $options = array(
1286
      'html' => TRUE,
1287
      'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
1288
    );
1289

    
1290
    // $path will be run through check_url() by l() so we do not need to
1291
    // sanitize it ourselves.
1292
    $path = $alter['path'];
1293

    
1294
    // strip_tags() removes <front>, so check whether its different to front.
1295
    if ($path != '<front>') {
1296
      // Use strip tags as there should never be HTML in the path. However, we
1297
      // need to preserve special characters like " that were removed by
1298
      // check_plain().
1299
      $path = strip_tags(decode_entities(strtr($path, $tokens)));
1300

    
1301
      if (!empty($alter['path_case']) && $alter['path_case'] != 'none') {
1302
        $path = $this->case_transform($path, $this->options['alter']['path_case']);
1303
      }
1304

    
1305
      if (!empty($alter['replace_spaces'])) {
1306
        $path = str_replace(' ', '-', $path);
1307
      }
1308

    
1309
      if (!empty($alter['unwanted_characters'])) {
1310
        foreach (explode(' ', $alter['unwanted_characters']) as $unwanted) {
1311
          $path = str_replace($unwanted, '', $path);
1312
        }
1313
      }
1314
    }
1315

    
1316
    // Parse the URL and move any query and fragment parameters out of the path.
1317
    $url = parse_url($path);
1318

    
1319
    // Seriously malformed URLs may return FALSE or empty arrays.
1320
    if (empty($url)) {
1321
      return $text;
1322
    }
1323

    
1324
    // If the path is empty do not build a link around the given text and return
1325
    // it as is. http://www.example.com URLs will not have a $url['path'], so
1326
    // check host as well.
1327
    if (empty($url['path']) && empty($url['host']) && empty($url['fragment'])) {
1328
      return $text;
1329
    }
1330

    
1331
    // If no scheme is provided in the $path, assign the default 'http://'.
1332
    // This allows a url of 'www.example.com' to be converted to
1333
    // 'http://www.example.com'. Only do this on for external URLs.
1334
    if ($alter['external']) {
1335
      if (!isset($url['scheme'])) {
1336
        // There is no scheme, add the default 'http://' to the $path.
1337
        $path = "http://$path";
1338
        // Reset the $url array to include the new scheme.
1339
        $url = parse_url($path);
1340
      }
1341
    }
1342

    
1343
    if (isset($url['query'])) {
1344
      $path = strtr($path, array('?' . $url['query'] => ''));
1345
      $query = drupal_get_query_array($url['query']);
1346
      // Remove query parameters that were assigned a query string replacement
1347
      // token for which there is no value available.
1348
      foreach ($query as $param => $val) {
1349
        if ($val == '%' . $param) {
1350
          unset($query[$param]);
1351
        }
1352
      }
1353
      $options['query'] = $query;
1354
    }
1355
    if (isset($url['fragment'])) {
1356
      $path = strtr($path, array('#' . $url['fragment'] => ''));
1357
      // If the path is empty we want to have a fragment for the current site.
1358
      if ($path == '') {
1359
        $options['external'] = TRUE;
1360
      }
1361
      $options['fragment'] = $url['fragment'];
1362
    }
1363

    
1364
    $alt = strtr($alter['alt'], $tokens);
1365
    // Set the title attribute of the link only if it improves accessibility
1366
    if ($alt && $alt != $text) {
1367
      $options['attributes']['title'] = decode_entities($alt);
1368
    }
1369

    
1370
    $class = strtr($alter['link_class'], $tokens);
1371
    if ($class) {
1372
      $options['attributes']['class'] = array($class);
1373
    }
1374

    
1375
    if (!empty($alter['rel']) && $rel = strtr($alter['rel'], $tokens)) {
1376
      $options['attributes']['rel'] = $rel;
1377
    }
1378

    
1379
    $target = check_plain(trim(strtr($alter['target'], $tokens)));
1380
    if (!empty($target)) {
1381
      $options['attributes']['target'] = $target;
1382
    }
1383

    
1384
    // Allow the addition of arbitrary attributes to links. Additional
1385
    // attributes currently can only be altered in preprocessors and not within
1386
    // the UI.
1387
    if (isset($alter['link_attributes']) && is_array($alter['link_attributes'])) {
1388
      foreach ($alter['link_attributes'] as $key => $attribute) {
1389
        if (!isset($options['attributes'][$key])) {
1390
          $options['attributes'][$key] = strtr($attribute, $tokens);
1391
        }
1392
      }
1393
    }
1394

    
1395
    // If the query and fragment were programatically assigned overwrite any
1396
    // parsed values.
1397
    if (isset($alter['query'])) {
1398
      // Convert the query to a string, perform token replacement, and then
1399
      // convert back to an array form for l().
1400
      $options['query'] = drupal_http_build_query($alter['query']);
1401
      $options['query'] = strtr($options['query'], $tokens);
1402
      $options['query'] = drupal_get_query_array($options['query']);
1403
    }
1404
    if (isset($alter['alias'])) {
1405
      // Alias is a boolean field, so no token.
1406
      $options['alias'] = $alter['alias'];
1407
    }
1408
    if (isset($alter['fragment'])) {
1409
      $options['fragment'] = strtr($alter['fragment'], $tokens);
1410
    }
1411
    if (isset($alter['language'])) {
1412
      $options['language'] = $alter['language'];
1413
    }
1414

    
1415
    // If the url came from entity_uri(), pass along the required options.
1416
    if (isset($alter['entity'])) {
1417
      $options['entity'] = $alter['entity'];
1418
    }
1419
    if (isset($alter['entity_type'])) {
1420
      $options['entity_type'] = $alter['entity_type'];
1421
    }
1422

    
1423
    $value .= l($text, $path, $options);
1424

    
1425
    if (!empty($alter['suffix'])) {
1426
      $value .= filter_xss_admin(strtr($alter['suffix'], $tokens));
1427
    }
1428

    
1429
    return $value;
1430
  }
1431

    
1432
  /**
1433
   * Get the 'render' tokens to use for advanced rendering.
1434
   *
1435
   * This runs through all of the fields and arguments that are available and
1436
   * gets their values. This will then be used in one giant str_replace().
1437
   */
1438
  public function get_render_tokens($item) {
1439
    $tokens = array();
1440
    if (!empty($this->view->build_info['substitutions'])) {
1441
      $tokens = $this->view->build_info['substitutions'];
1442
    }
1443
    $count = 0;
1444
    foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
1445
      $token = '%' . ++$count;
1446
      if (!isset($tokens[$token])) {
1447
        $tokens[$token] = '';
1448
      }
1449

    
1450
      // Use strip tags as there should never be HTML in the path. However, we
1451
      // need to preserve special characters like " that were removed by
1452
      // check_plain().
1453
      $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(decode_entities($this->view->args[$count - 1])) : '';
1454
    }
1455

    
1456
    // Get flattened set of tokens for any array depth in $_GET parameters.
1457
    $tokens += $this->get_token_values_recursive($_GET);
1458

    
1459
    // Now add replacements for our fields.
1460
    foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
1461
      if (isset($handler->last_render)) {
1462
        $tokens["[$field]"] = $handler->last_render;
1463
      }
1464
      else {
1465
        $tokens["[$field]"] = '';
1466
      }
1467
      if (!empty($item)) {
1468
        $this->add_self_tokens($tokens, $item);
1469
      }
1470

    
1471
      // We only use fields up to (and including) this one.
1472
      if ($field == $this->options['id']) {
1473
        break;
1474
      }
1475
    }
1476

    
1477
    // Store the tokens for the row so we can reference them later if necessary.
1478
    $this->view->style_plugin->render_tokens[$this->view->row_index] = $tokens;
1479
    $this->last_tokens = $tokens;
1480

    
1481
    return $tokens;
1482
  }
1483

    
1484
  /**
1485
   * Recursive function to add replacements for nested query string parameters.
1486
   *
1487
   * E.g. if you pass in the following array:
1488
   *   array(
1489
   *     'foo' => array(
1490
   *       'a' => 'value',
1491
   *       'b' => 'value',
1492
   *     ),
1493
   *     'bar' => array(
1494
   *       'a' => 'value',
1495
   *       'b' => array(
1496
   *         'c' => value,
1497
   *       ),
1498
   *     ),
1499
   *   );
1500
   *
1501
   * Would yield the following array of tokens:
1502
   *   array(
1503
   *     '%foo_a' => 'value'
1504
   *     '%foo_b' => 'value'
1505
   *     '%bar_a' => 'value'
1506
   *     '%bar_b_c' => 'value'
1507
   *   );
1508
   *
1509
   * @param array $array
1510
   *   An array of values.
1511
   * @param array $parent_keys
1512
   *   An array of parent keys. This will represent the array depth.
1513
   *
1514
   * @return array
1515
   *   An array of available tokens, with nested keys representative of the
1516
   *   array structure.
1517
   */
1518
  public function get_token_values_recursive(array $array, array $parent_keys = array()) {
1519
    $tokens = array();
1520

    
1521
    foreach ($array as $param => $val) {
1522
      if (is_array($val)) {
1523
        // Copy parent_keys array, so we don't afect other elements of this
1524
        // iteration.
1525
        $child_parent_keys = $parent_keys;
1526
        $child_parent_keys[] = $param;
1527
        // Get the child tokens.
1528
        $child_tokens = $this->get_token_values_recursive($val, $child_parent_keys);
1529
        // Add them to the current tokens array.
1530
        $tokens += $child_tokens;
1531
      }
1532
      else {
1533
        // Create a token key based on array element structure.
1534
        $token_string = !empty($parent_keys) ? implode('_', $parent_keys) . '_' . $param : $param;
1535
        $tokens['%' . $token_string] = strip_tags(decode_entities($val));
1536
      }
1537
    }
1538

    
1539
    return $tokens;
1540
  }
1541

    
1542
  /**
1543
   * Add any special tokens this field might use for itself.
1544
   *
1545
   * This method is intended to be overridden by items that generate fields as a
1546
   * list. For example, the field that displays all terms on a node might have
1547
   * tokens for the tid and the term.
1548
   *
1549
   * By convention, tokens should follow the format of [token-subtoken] where
1550
   * token is the field ID and subtoken is the field. If the field ID is terms,
1551
   * then the tokens might be [terms-tid] and [terms-name].
1552
   */
1553
  public function add_self_tokens(&$tokens, $item) {
1554
  }
1555

    
1556
  /**
1557
   * Document any special tokens this field might use for itself.
1558
   *
1559
   * @see add_self_tokens()
1560
   */
1561
  public function document_self_tokens(&$tokens) {
1562
  }
1563

    
1564
  /**
1565
   * Call out to the theme() function.
1566
   *
1567
   * It probably just calls render() but allows sites to override output fairly
1568
   * easily.
1569
   */
1570
  public function theme($values) {
1571
    return theme($this->theme_functions(),
1572
      array(
1573
        'view' => $this->view,
1574
        'field' => $this,
1575
        'row' => $values,
1576
      ));
1577
  }
1578

    
1579
  /**
1580
   * Build a list of suitable theme functions for this view.
1581
   */
1582
  public function theme_functions() {
1583
    $themes = array();
1584
    $hook = 'views_view_field';
1585

    
1586
    $display = $this->view->display[$this->view->current_display];
1587

    
1588
    if (!empty($display)) {
1589
      $themes[] = $hook . '__' . $this->view->name . '__' . $display->id . '__' . $this->options['id'];
1590
      $themes[] = $hook . '__' . $this->view->name . '__' . $display->id;
1591
      $themes[] = $hook . '__' . $display->id . '__' . $this->options['id'];
1592
      $themes[] = $hook . '__' . $display->id;
1593
      if ($display->id != $display->display_plugin) {
1594
        $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin . '__' . $this->options['id'];
1595
        $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin;
1596
        $themes[] = $hook . '__' . $display->display_plugin . '__' . $this->options['id'];
1597
        $themes[] = $hook . '__' . $display->display_plugin;
1598
      }
1599
    }
1600
    $themes[] = $hook . '__' . $this->view->name . '__' . $this->options['id'];
1601
    $themes[] = $hook . '__' . $this->view->name;
1602
    $themes[] = $hook . '__' . $this->options['id'];
1603
    $themes[] = $hook;
1604

    
1605
    return $themes;
1606
  }
1607

    
1608
  /**
1609
   * {@inheritdoc}
1610
   */
1611
  public function ui_name($short = FALSE) {
1612
    return $this->get_field(parent::ui_name($short));
1613
  }
1614

    
1615
}
1616

    
1617
/**
1618
 * A special handler to take the place of missing or broken handlers.
1619
 *
1620
 * @ingroup views_field_handlers
1621
 */
1622
class views_handler_field_broken extends views_handler_field {
1623

    
1624
  /**
1625
   * {@inheritdoc}
1626
   */
1627
  public function ui_name($short = FALSE) {
1628
    return t('Broken/missing handler');
1629
  }
1630

    
1631
  /**
1632
   * {@inheritdoc}
1633
   */
1634
  public function ensure_my_table() {
1635
    // No table to ensure!
1636
  }
1637

    
1638
  /**
1639
   * {@inheritdoc}
1640
   */
1641
  public function query($group_by = FALSE) {
1642
    // No query to run.
1643
  }
1644

    
1645
  /**
1646
   * {@inheritdoc}
1647
   */
1648
  public function options_form(&$form, &$form_state) {
1649
    $form['markup'] = array(
1650
      '#markup' => '<div class="form-item description">' . t('The handler for this item is broken or missing and cannot be used. If a module provided the handler and was disabled, re-enabling the module may restore it. Otherwise, you should probably delete this item.') . '</div>',
1651
    );
1652
  }
1653

    
1654
  /**
1655
   * {@inheritdoc}
1656
   */
1657
  public function broken() {
1658
    return TRUE;
1659
  }
1660

    
1661
}
1662

    
1663
/**
1664
 * Render a numeric value as a size.
1665
 *
1666
 * @ingroup views_field_handlers
1667
 */
1668
class views_handler_field_file_size extends views_handler_field {
1669

    
1670
  /**
1671
   * {@inheritdoc}
1672
   */
1673
  public function option_definition() {
1674
    $options = parent::option_definition();
1675

    
1676
    $options['file_size_display'] = array('default' => 'formatted');
1677

    
1678
    return $options;
1679
  }
1680

    
1681
  /**
1682
   * {@inheritdoc}
1683
   */
1684
  public function options_form(&$form, &$form_state) {
1685
    parent::options_form($form, $form_state);
1686
    $form['file_size_display'] = array(
1687
      '#title' => t('File size display'),
1688
      '#type' => 'select',
1689
      '#options' => array(
1690
        'formatted' => t('Formatted (in KB or MB)'),
1691
        'bytes' => t('Raw bytes'),
1692
      ),
1693
    );
1694
  }
1695

    
1696
  /**
1697
   * {@inheritdoc}
1698
   */
1699
  public function render($values) {
1700
    $value = $this->get_value($values);
1701
    if ($value) {
1702
      switch ($this->options['file_size_display']) {
1703
        case 'bytes':
1704
          return $value;
1705
        case 'formatted':
1706
        default:
1707
          return format_size($value);
1708
      }
1709
    }
1710
    else {
1711
      return '';
1712
    }
1713
  }
1714

    
1715
}
1716

    
1717
/**
1718
 * A handler to run a field through simple XSS filtering.
1719
 *
1720
 * @ingroup views_field_handlers
1721
 */
1722
class views_handler_field_xss extends views_handler_field {
1723

    
1724
  /**
1725
   * {@inheritdoc}
1726
   */
1727
  public function render($values) {
1728
    $value = $this->get_value($values);
1729
    return $this->sanitize_value($value, 'xss');
1730
  }
1731

    
1732
}
1733

    
1734
/**
1735
 * @}
1736
 */