Projet

Général

Profil

Paste
Télécharger (57,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / views / handlers / views_handler_field.inc @ 5d12d676

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's 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
        'path_case' => array('default' => 'none', 'translatable' => FALSE),
428
        'trim_whitespace' => array('default' => FALSE, 'bool' => TRUE),
429
        'alt' => array('default' => '', 'translatable' => TRUE),
430
        'rel' => array('default' => ''),
431
        'link_class' => array('default' => ''),
432
        'prefix' => array('default' => '', 'translatable' => TRUE),
433
        'suffix' => array('default' => '', 'translatable' => TRUE),
434
        'target' => array('default' => ''),
435
        'nl2br' => array('default' => FALSE, 'bool' => TRUE),
436
        'max_length' => array('default' => ''),
437
        'word_boundary' => array('default' => TRUE, 'bool' => TRUE),
438
        'ellipsis' => array('default' => TRUE, 'bool' => TRUE),
439
        'more_link' => array('default' => FALSE, 'bool' => TRUE),
440
        'more_link_text' => array('default' => '', 'translatable' => TRUE),
441
        'more_link_path' => array('default' => ''),
442
        'strip_tags' => array('default' => FALSE, 'bool' => TRUE),
443
        'trim' => array('default' => FALSE, 'bool' => TRUE),
444
        'preserve_tags' => array('default' => ''),
445
        'html' => array('default' => FALSE, 'bool' => TRUE),
446
      ),
447
    );
448
    $options['element_type'] = array('default' => '');
449
    $options['element_class'] = array('default' => '');
450

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

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

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

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

    
465
    return $options;
466
  }
467

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

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

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

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

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

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

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

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

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

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

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

    
631
    $form['element_wrapper_type_enable'] = array(
632
      '#type' => 'checkbox',
633
      '#title' => t('Customize field and label wrapper HTML'),
634
      '#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',
635
      '#fieldset' => 'style_settings',
636
    );
637
    $form['element_wrapper_type'] = array(
638
      '#title' => t('Wrapper HTML element'),
639
      '#options' => $this->get_elements(FALSE),
640
      '#type' => 'select',
641
      '#default_value' => $this->options['element_wrapper_type'],
642
      '#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.'),
643
      '#dependency' => array(
644
        'edit-options-element-wrapper-type-enable' => array(1),
645
      ),
646
      '#fieldset' => 'style_settings',
647
    );
648

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

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

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

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

    
696
      $form['alter']['text'] = array(
697
        '#title' => t('Text'),
698
        '#type' => 'textarea',
699
        '#default_value' => $this->options['alter']['text'],
700
        '#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.'),
701
        '#dependency' => array(
702
          'edit-options-alter-alter-text' => array(1),
703
        ),
704
      );
705

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

    
818

    
819
      // Get a list of the available fields and arguments for token replacement.
820
      $options = array();
821
      foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
822
        $options[t('Fields')]["[$field]"] = $handler->ui_name();
823
        // We only use fields up to (and including) this one.
824
        if ($field == $this->options['id']) {
825
          break;
826
        }
827
      }
828
      // This lets us prepare the key as we want it printed.
829
      $count = 0;
830
      foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
831
        $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
832
        $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
833
      }
834

    
835
      $this->document_self_tokens($options[t('Fields')]);
836

    
837
      // Default text.
838
      $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>');
839
      // We have some options, so make a list.
840
      if (!empty($options)) {
841
        $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.
842
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>');
843
        foreach (array_keys($options) as $type) {
844
          if (!empty($options[$type])) {
845
            $items = array();
846
            foreach ($options[$type] as $key => $value) {
847
              $items[] = $key . ' == ' . check_plain($value);
848
            }
849
            $output .= theme('item_list',
850
              array(
851
                'items' => $items,
852
                'type' => $type,
853
              ));
854
          }
855
        }
856
      }
857
      // This construct uses 'hidden' and not markup because process doesn't
858
      // run. It also has an extra div because the dependency wants to hide the
859
      // parent in situations like this, so we need a second div to make this
860
      // work.
861
      $form['alter']['help'] = array(
862
        '#type' => 'fieldset',
863
        '#title' => t('Replacement patterns'),
864
        '#collapsible' => TRUE,
865
        '#collapsed' => TRUE,
866
        '#value' => $output,
867
        '#dependency' => array(
868
          'edit-options-alter-make-link' => array(1),
869
          'edit-options-alter-alter-text' => array(1),
870
          'edit-options-alter-more-link' => array(1),
871
        ),
872
      );
873

    
874
      $form['alter']['trim'] = array(
875
        '#type' => 'checkbox',
876
        '#title' => t('Trim this field to a maximum length'),
877
        '#description' => t('Enable to trim the field to a maximum length of characters'),
878
        '#default_value' => $this->options['alter']['trim'],
879
      );
880

    
881
      $form['alter']['max_length'] = array(
882
        '#title' => t('Maximum length'),
883
        '#type' => 'textfield',
884
        '#default_value' => $this->options['alter']['max_length'],
885
        '#description' => t('The maximum number of characters this field can be.'),
886
        '#dependency' => array(
887
          'edit-options-alter-trim' => array(1),
888
        ),
889
      );
890

    
891
      $form['alter']['word_boundary'] = array(
892
        '#type' => 'checkbox',
893
        '#title' => t('Trim only on a word boundary'),
894
        '#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.'),
895
        '#default_value' => $this->options['alter']['word_boundary'],
896
        '#dependency' => array(
897
          'edit-options-alter-trim' => array(1),
898
        ),
899
      );
900

    
901
      $form['alter']['ellipsis'] = array(
902
        '#type' => 'checkbox',
903
        '#title' => t('Add an ellipsis'),
904
        '#description' => t('If checked, a "..." will be added if a field was trimmed.'),
905
        '#default_value' => $this->options['alter']['ellipsis'],
906
        '#dependency' => array(
907
          'edit-options-alter-trim' => array(1),
908
        ),
909
      );
910

    
911
      $form['alter']['more_link'] = array(
912
        '#type' => 'checkbox',
913
        '#title' => t('Add a read-more link if output is trimmed.'),
914
        '#description' => t('If checked, a read-more link will be added at the end of the trimmed output'),
915
        '#default_value' => $this->options['alter']['more_link'],
916
        '#dependency' => array(
917
          'edit-options-alter-trim' => array(1),
918
        ),
919
      );
920

    
921
      $form['alter']['more_link_text'] = array(
922
        '#type' => 'textfield',
923
        '#title' => t('More link text'),
924
        '#default_value' => $this->options['alter']['more_link_text'],
925
        '#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.'),
926
        '#dependency_count' => 2,
927
        '#dependency' => array(
928
          'edit-options-alter-trim' => array(1),
929
          'edit-options-alter-more-link' => array(1),
930
        ),
931
      );
932
      $form['alter']['more_link_path'] = array(
933
        '#type' => 'textfield',
934
        '#title' => t('More link path'),
935
        '#default_value' => $this->options['alter']['more_link_path'],
936
        '#description' => t('The path which is used for 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

    
944
      $form['alter']['html'] = array(
945
        '#type' => 'checkbox',
946
        '#title' => t('Field can contain HTML'),
947
        '#description' => t('If checked, HTML corrector will be run to ensure tags are properly closed after trimming.'),
948
        '#default_value' => $this->options['alter']['html'],
949
        '#dependency' => array(
950
          'edit-options-alter-trim' => array(1),
951
        ),
952
      );
953

    
954
      $form['alter']['strip_tags'] = array(
955
        '#type' => 'checkbox',
956
        '#title' => t('Strip HTML tags'),
957
        '#description' => t('If checked, all HTML tags will be stripped.'),
958
        '#default_value' => $this->options['alter']['strip_tags'],
959
      );
960

    
961
      $form['alter']['preserve_tags'] = array(
962
        '#type' => 'textfield',
963
        '#title' => t('Preserve certain tags'),
964
        '#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'),
965
        '#default_value' => $this->options['alter']['preserve_tags'],
966
        '#dependency' => array(
967
          'edit-options-alter-strip-tags' => array(1),
968
        ),
969
      );
970

    
971
      $form['alter']['trim_whitespace'] = array(
972
        '#type' => 'checkbox',
973
        '#title' => t('Remove whitespace'),
974
        '#description' => t('If checked, all whitespaces at the beginning and the end of the output will be removed.'),
975
        '#default_value' => $this->options['alter']['trim_whitespace'],
976
      );
977

    
978
      $form['alter']['nl2br'] = array(
979
        '#type' => 'checkbox',
980
        '#title' => t('Convert newlines to HTML &lt;br&gt; tags'),
981
        '#description' => t('If checked, all newlines chars (e.g. \n) are converted into HTML &lt;br&gt; tags.'),
982
        '#default_value' => $this->options['alter']['nl2br'],
983
      );
984
    }
985

    
986
    $form['empty_field_behavior'] = array(
987
      '#type' => 'fieldset',
988
      '#title' => t('No results behavior'),
989
      '#collapsible' => TRUE,
990
      '#collapsed' => TRUE,
991
      '#weight' => 100,
992
    );
993

    
994
    $form['empty'] = array(
995
      '#type' => 'textarea',
996
      '#title' => t('No results text'),
997
      '#default_value' => $this->options['empty'],
998
      '#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.'),
999
      '#fieldset' => 'empty_field_behavior',
1000
    );
1001

    
1002
    $form['empty_zero'] = array(
1003
      '#type' => 'checkbox',
1004
      '#title' => t('Count the number 0 as empty'),
1005
      '#default_value' => $this->options['empty_zero'],
1006
      '#description' => t('Enable to display the "no results text" if the field contains the number 0.'),
1007
      '#fieldset' => 'empty_field_behavior',
1008
    );
1009

    
1010
    $form['hide_empty'] = array(
1011
      '#type' => 'checkbox',
1012
      '#title' => t('Hide if empty'),
1013
      '#default_value' => $this->options['hide_empty'],
1014
      '#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.'),
1015
      '#fieldset' => 'empty_field_behavior',
1016
    );
1017

    
1018
    $form['hide_alter_empty'] = array(
1019
      '#type' => 'checkbox',
1020
      '#title' => t('Hide rewriting if empty'),
1021
      '#default_value' => $this->options['hide_alter_empty'],
1022
      '#description' => t('Do not display rewritten content if this field is empty.'),
1023
      '#fieldset' => 'empty_field_behavior',
1024
    );
1025
  }
1026

    
1027
  /**
1028
   * Provide extra data to the administration form
1029
   */
1030
  public function admin_summary() {
1031
    return $this->label();
1032
  }
1033

    
1034
  /**
1035
   * Run before any fields are rendered.
1036
   *
1037
   * This gives the handlers some time to set up before any handler has been
1038
   * rendered.
1039
   *
1040
   * @param array $values
1041
   *   An array of all objects returned from the query.
1042
   */
1043
  public function pre_render(&$values) {
1044
  }
1045

    
1046
  /**
1047
   * Render the field.
1048
   *
1049
   * @param array $values
1050
   *   The values retrieved from the database.
1051
   */
1052
  public function render($values) {
1053
    $value = $this->get_value($values);
1054
    return $this->sanitize_value($value);
1055
  }
1056

    
1057
  /**
1058
   * Render a field using advanced settings.
1059
   *
1060
   * This renders a field normally, then decides if render-as-link and
1061
   * text-replacement rendering is necessary.
1062
   */
1063
  public function advanced_render($values) {
1064
    if ($this->allow_advanced_render() && method_exists($this, 'render_item')) {
1065
      $raw_items = $this->get_items($values);
1066
      // If there are no items, set the original value to NULL.
1067
      if (empty($raw_items)) {
1068
        $this->original_value = NULL;
1069
      }
1070
    }
1071
    else {
1072
      $value = $this->render($values);
1073
      if (is_array($value)) {
1074
        $value = drupal_render($value);
1075
      }
1076
      $this->last_render = $value;
1077
      $this->original_value = $value;
1078
    }
1079

    
1080
    if ($this->allow_advanced_render()) {
1081
      $tokens = NULL;
1082
      if (method_exists($this, 'render_item')) {
1083
        $items = array();
1084
        foreach ($raw_items as $count => $item) {
1085
          $value = $this->render_item($count, $item);
1086
          if (is_array($value)) {
1087
            $value = drupal_render($value);
1088
          }
1089
          $this->last_render = $value;
1090
          $this->original_value = $this->last_render;
1091

    
1092
          $alter = $item + $this->options['alter'];
1093
          $alter['phase'] = VIEWS_HANDLER_RENDER_TEXT_PHASE_SINGLE_ITEM;
1094
          $items[] = $this->render_text($alter);
1095
        }
1096

    
1097
        $value = $this->render_items($items);
1098
      }
1099
      else {
1100
        $alter = array('phase' => VIEWS_HANDLER_RENDER_TEXT_PHASE_COMPLETELY) + $this->options['alter'];
1101
        $value = $this->render_text($alter);
1102
      }
1103

    
1104
      if (is_array($value)) {
1105
        $value = drupal_render($value);
1106
      }
1107
      // This happens here so that render_as_link can get the unaltered value of
1108
      // this field as a token rather than the altered value.
1109
      $this->last_render = $value;
1110
    }
1111

    
1112
    if (empty($this->last_render)) {
1113
      if ($this->is_value_empty($this->last_render, $this->options['empty_zero'], FALSE)) {
1114
        $alter = $this->options['alter'];
1115
        $alter['alter_text'] = 1;
1116
        $alter['text'] = $this->options['empty'];
1117
        $alter['phase'] = VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY;
1118
        $this->last_render = $this->render_text($alter);
1119
      }
1120
    }
1121

    
1122
    return $this->last_render;
1123
  }
1124

    
1125
  /**
1126
   * Checks if a field value is empty.
1127
   *
1128
   * @param mixed $value
1129
   *   The field value.
1130
   * @param bool $empty_zero
1131
   *   Whether or not this field is configured to consider 0 as empty.
1132
   * @param bool $no_skip_empty
1133
   *   Whether or not to use empty() to check the value.
1134
   *
1135
   * @return bool
1136
   *   TRUE if the value is considered empty, FALSE otherwise.
1137
   */
1138
  public function is_value_empty($value, $empty_zero, $no_skip_empty = TRUE) {
1139
    if (!isset($value)) {
1140
      $empty = TRUE;
1141
    }
1142
    else {
1143
      $empty = ($empty_zero || ($value !== 0 && $value !== '0'));
1144
    }
1145

    
1146
    if ($no_skip_empty) {
1147
      $empty = empty($value) && $empty;
1148
    }
1149
    return $empty;
1150
  }
1151

    
1152
  /**
1153
   * Perform an advanced text render for the item.
1154
   *
1155
   * This is separated out as some fields may render lists, and this allows
1156
   * each item to be handled individually.
1157
   */
1158
  public function render_text($alter) {
1159
    $value = $this->last_render;
1160

    
1161
    if (!empty($alter['alter_text']) && $alter['text'] !== '') {
1162
      $tokens = $this->get_render_tokens($alter);
1163
      $value = $this->render_altered($alter, $tokens);
1164
    }
1165

    
1166
    if (!empty($this->options['alter']['trim_whitespace'])) {
1167
      $value = trim($value);
1168
    }
1169

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

    
1173
    // Check whether the value is empty and return nothing, so the field isn't
1174
    // rendered. First check whether the field should be hidden if the
1175
    // value(hide_alter_empty = TRUE) /the rewrite is empty (hide_alter_empty =
1176
    // FALSE). For numeric values you can specify whether "0"/0 should be empty.
1177
    if ((($this->options['hide_empty'] && empty($value))
1178
        || ($alter['phase'] != VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty))
1179
      && $this->is_value_empty($value, $this->options['empty_zero'], FALSE)) {
1180
      return '';
1181
    }
1182
    // Only in empty phase.
1183
    if ($alter['phase'] == VIEWS_HANDLER_RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) {
1184
      // If we got here then $alter contains the value of "No results text"
1185
      // and so there is nothing left to do.
1186
      return $value;
1187
    }
1188

    
1189
    if (!empty($alter['strip_tags'])) {
1190
      $value = strip_tags($value, $alter['preserve_tags']);
1191
    }
1192

    
1193
    $suffix = '';
1194
    if (!empty($alter['trim']) && !empty($alter['max_length'])) {
1195
      $length = strlen($value);
1196
      $value = $this->render_trim_text($alter, $value);
1197
      if ($this->options['alter']['more_link'] && strlen($value) < $length) {
1198
        $tokens = $this->get_render_tokens($alter);
1199
        $more_link_text = $this->options['alter']['more_link_text'] ? $this->options['alter']['more_link_text'] : t('more');
1200
        $more_link_text = strtr(filter_xss_admin($more_link_text), $tokens);
1201
        $more_link_path = $this->options['alter']['more_link_path'];
1202
        $more_link_path = strip_tags(decode_entities(strtr($more_link_path, $tokens)));
1203

    
1204
        // Take sure that paths which was runned through url() does work as
1205
        // well.
1206
        $base_path = base_path();
1207
        // Checks whether the path starts with the base_path.
1208
        if (strpos($more_link_path, $base_path) === 0) {
1209
          $more_link_path = drupal_substr($more_link_path, drupal_strlen($base_path));
1210
        }
1211

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

    
1214
        $suffix .= " " . $more_link;
1215
      }
1216
    }
1217

    
1218
    if (!empty($alter['nl2br'])) {
1219
      $value = nl2br($value);
1220
    }
1221
    $this->last_render_text = $value;
1222

    
1223
    if (!empty($alter['make_link']) && !empty($alter['path'])) {
1224
      if (!isset($tokens)) {
1225
        $tokens = $this->get_render_tokens($alter);
1226
      }
1227
      $value = $this->render_as_link($alter, $value, $tokens);
1228
    }
1229

    
1230
    return $value . $suffix;
1231
  }
1232

    
1233
  /**
1234
   * Render this field as altered text, from a fieldset set by the user.
1235
   */
1236
  public function render_altered($alter, $tokens) {
1237
    // Filter this right away as our substitutions are already sanitized.
1238
    $value = filter_xss_admin($alter['text']);
1239
    $value = strtr($value, $tokens);
1240

    
1241
    return $value;
1242
  }
1243

    
1244
  /**
1245
   * Trim the field down to the specified length.
1246
   */
1247
  public function render_trim_text($alter, $value) {
1248
    if (!empty($alter['strip_tags'])) {
1249
      // NOTE: It's possible that some external fields might override the
1250
      // element type so if someone from, say, CCK runs into a bug here, this
1251
      // may be why =)
1252
      $this->definition['element type'] = 'span';
1253
    }
1254
    return views_trim_text($alter, $value);
1255
  }
1256

    
1257
  /**
1258
   * Render this field as a link, with info from a fieldset set by the user.
1259
   */
1260
  public function render_as_link($alter, $text, $tokens) {
1261
    $value = '';
1262

    
1263
    if (!empty($alter['prefix'])) {
1264
      $value .= filter_xss_admin(strtr($alter['prefix'], $tokens));
1265
    }
1266

    
1267
    $options = array(
1268
      'html' => TRUE,
1269
      'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
1270
    );
1271

    
1272
    // $path will be run through check_url() by l() so we do not need to
1273
    // sanitize it ourselves.
1274
    $path = $alter['path'];
1275

    
1276
    // strip_tags() removes <front>, so check whether its different to front.
1277
    if ($path != '<front>') {
1278
      // Use strip tags as there should never be HTML in the path. However, we
1279
      // need to preserve special characters like " that were removed by
1280
      // check_plain().
1281
      $path = strip_tags(decode_entities(strtr($path, $tokens)));
1282

    
1283
      if (!empty($alter['path_case']) && $alter['path_case'] != 'none') {
1284
        $path = $this->case_transform($path, $this->options['alter']['path_case']);
1285
      }
1286

    
1287
      if (!empty($alter['replace_spaces'])) {
1288
        $path = str_replace(' ', '-', $path);
1289
      }
1290
    }
1291

    
1292
    // Parse the URL and move any query and fragment parameters out of the path.
1293
    $url = parse_url($path);
1294

    
1295
    // Seriously malformed URLs may return FALSE or empty arrays.
1296
    if (empty($url)) {
1297
      return $text;
1298
    }
1299

    
1300
    // If the path is empty do not build a link around the given text and return
1301
    // it as is. http://www.example.com URLs will not have a $url['path'], so
1302
    // check host as well.
1303
    if (empty($url['path']) && empty($url['host']) && empty($url['fragment'])) {
1304
      return $text;
1305
    }
1306

    
1307
    // If no scheme is provided in the $path, assign the default 'http://'.
1308
    // This allows a url of 'www.example.com' to be converted to
1309
    // 'http://www.example.com'. Only do this on for external URLs.
1310
    if ($alter['external']) {
1311
      if (!isset($url['scheme'])) {
1312
        // There is no scheme, add the default 'http://' to the $path.
1313
        $path = "http://$path";
1314
        // Reset the $url array to include the new scheme.
1315
        $url = parse_url($path);
1316
      }
1317
    }
1318

    
1319
    if (isset($url['query'])) {
1320
      $path = strtr($path, array('?' . $url['query'] => ''));
1321
      $query = drupal_get_query_array($url['query']);
1322
      // Remove query parameters that were assigned a query string replacement
1323
      // token for which there is no value available.
1324
      foreach ($query as $param => $val) {
1325
        if ($val == '%' . $param) {
1326
          unset($query[$param]);
1327
        }
1328
      }
1329
      $options['query'] = $query;
1330
    }
1331
    if (isset($url['fragment'])) {
1332
      $path = strtr($path, array('#' . $url['fragment'] => ''));
1333
      // If the path is empty we want to have a fragment for the current site.
1334
      if ($path == '') {
1335
        $options['external'] = TRUE;
1336
      }
1337
      $options['fragment'] = $url['fragment'];
1338
    }
1339

    
1340
    $alt = strtr($alter['alt'], $tokens);
1341
    // Set the title attribute of the link only if it improves accessibility
1342
    if ($alt && $alt != $text) {
1343
      $options['attributes']['title'] = decode_entities($alt);
1344
    }
1345

    
1346
    $class = strtr($alter['link_class'], $tokens);
1347
    if ($class) {
1348
      $options['attributes']['class'] = array($class);
1349
    }
1350

    
1351
    if (!empty($alter['rel']) && $rel = strtr($alter['rel'], $tokens)) {
1352
      $options['attributes']['rel'] = $rel;
1353
    }
1354

    
1355
    $target = check_plain(trim(strtr($alter['target'], $tokens)));
1356
    if (!empty($target)) {
1357
      $options['attributes']['target'] = $target;
1358
    }
1359

    
1360
    // Allow the addition of arbitrary attributes to links. Additional
1361
    // attributes currently can only be altered in preprocessors and not within
1362
    // the UI.
1363
    if (isset($alter['link_attributes']) && is_array($alter['link_attributes'])) {
1364
      foreach ($alter['link_attributes'] as $key => $attribute) {
1365
        if (!isset($options['attributes'][$key])) {
1366
          $options['attributes'][$key] = strtr($attribute, $tokens);
1367
        }
1368
      }
1369
    }
1370

    
1371
    // If the query and fragment were programatically assigned overwrite any
1372
    // parsed values.
1373
    if (isset($alter['query'])) {
1374
      // Convert the query to a string, perform token replacement, and then
1375
      // convert back to an array form for l().
1376
      $options['query'] = drupal_http_build_query($alter['query']);
1377
      $options['query'] = strtr($options['query'], $tokens);
1378
      $options['query'] = drupal_get_query_array($options['query']);
1379
    }
1380
    if (isset($alter['alias'])) {
1381
      // Alias is a boolean field, so no token.
1382
      $options['alias'] = $alter['alias'];
1383
    }
1384
    if (isset($alter['fragment'])) {
1385
      $options['fragment'] = strtr($alter['fragment'], $tokens);
1386
    }
1387
    if (isset($alter['language'])) {
1388
      $options['language'] = $alter['language'];
1389
    }
1390

    
1391
    // If the url came from entity_uri(), pass along the required options.
1392
    if (isset($alter['entity'])) {
1393
      $options['entity'] = $alter['entity'];
1394
    }
1395
    if (isset($alter['entity_type'])) {
1396
      $options['entity_type'] = $alter['entity_type'];
1397
    }
1398

    
1399
    $value .= l($text, $path, $options);
1400

    
1401
    if (!empty($alter['suffix'])) {
1402
      $value .= filter_xss_admin(strtr($alter['suffix'], $tokens));
1403
    }
1404

    
1405
    return $value;
1406
  }
1407

    
1408
  /**
1409
   * Get the 'render' tokens to use for advanced rendering.
1410
   *
1411
   * This runs through all of the fields and arguments that are available and
1412
   * gets their values. This will then be used in one giant str_replace().
1413
   */
1414
  public function get_render_tokens($item) {
1415
    $tokens = array();
1416
    if (!empty($this->view->build_info['substitutions'])) {
1417
      $tokens = $this->view->build_info['substitutions'];
1418
    }
1419
    $count = 0;
1420
    foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
1421
      $token = '%' . ++$count;
1422
      if (!isset($tokens[$token])) {
1423
        $tokens[$token] = '';
1424
      }
1425

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

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

    
1435
    // Now add replacements for our fields.
1436
    foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
1437
      if (isset($handler->last_render)) {
1438
        $tokens["[$field]"] = $handler->last_render;
1439
      }
1440
      else {
1441
        $tokens["[$field]"] = '';
1442
      }
1443
      if (!empty($item)) {
1444
        $this->add_self_tokens($tokens, $item);
1445
      }
1446

    
1447
      // We only use fields up to (and including) this one.
1448
      if ($field == $this->options['id']) {
1449
        break;
1450
      }
1451
    }
1452

    
1453
    // Store the tokens for the row so we can reference them later if necessary.
1454
    $this->view->style_plugin->render_tokens[$this->view->row_index] = $tokens;
1455
    $this->last_tokens = $tokens;
1456

    
1457
    return $tokens;
1458
  }
1459

    
1460
  /**
1461
   * Recursive function to add replacements for nested query string parameters.
1462
   *
1463
   * E.g. if you pass in the following array:
1464
   *   array(
1465
   *     'foo' => array(
1466
   *       'a' => 'value',
1467
   *       'b' => 'value',
1468
   *     ),
1469
   *     'bar' => array(
1470
   *       'a' => 'value',
1471
   *       'b' => array(
1472
   *         'c' => value,
1473
   *       ),
1474
   *     ),
1475
   *   );
1476
   *
1477
   * Would yield the following array of tokens:
1478
   *   array(
1479
   *     '%foo_a' => 'value'
1480
   *     '%foo_b' => 'value'
1481
   *     '%bar_a' => 'value'
1482
   *     '%bar_b_c' => 'value'
1483
   *   );
1484
   *
1485
   * @param array $array
1486
   *   An array of values.
1487
   * @param array $parent_keys
1488
   *   An array of parent keys. This will represent the array depth.
1489
   *
1490
   * @return array
1491
   *   An array of available tokens, with nested keys representative of the
1492
   *   array structure.
1493
   */
1494
  public function get_token_values_recursive(array $array, array $parent_keys = array()) {
1495
    $tokens = array();
1496

    
1497
    foreach ($array as $param => $val) {
1498
      if (is_array($val)) {
1499
        // Copy parent_keys array, so we don't afect other elements of this
1500
        // iteration.
1501
        $child_parent_keys = $parent_keys;
1502
        $child_parent_keys[] = $param;
1503
        // Get the child tokens.
1504
        $child_tokens = $this->get_token_values_recursive($val, $child_parent_keys);
1505
        // Add them to the current tokens array.
1506
        $tokens += $child_tokens;
1507
      }
1508
      else {
1509
        // Create a token key based on array element structure.
1510
        $token_string = !empty($parent_keys) ? implode('_', $parent_keys) . '_' . $param : $param;
1511
        $tokens['%' . $token_string] = strip_tags(decode_entities($val));
1512
      }
1513
    }
1514

    
1515
    return $tokens;
1516
  }
1517

    
1518
  /**
1519
   * Add any special tokens this field might use for itself.
1520
   *
1521
   * This method is intended to be overridden by items that generate fields as a
1522
   * list. For example, the field that displays all terms on a node might have
1523
   * tokens for the tid and the term.
1524
   *
1525
   * By convention, tokens should follow the format of [token-subtoken] where
1526
   * token is the field ID and subtoken is the field. If the field ID is terms,
1527
   * then the tokens might be [terms-tid] and [terms-name].
1528
   */
1529
  public function add_self_tokens(&$tokens, $item) {
1530
  }
1531

    
1532
  /**
1533
   * Document any special tokens this field might use for itself.
1534
   *
1535
   * @see add_self_tokens()
1536
   */
1537
  public function document_self_tokens(&$tokens) {
1538
  }
1539

    
1540
  /**
1541
   * Call out to the theme() function.
1542
   *
1543
   * It probably just calls render() but allows sites to override output fairly
1544
   * easily.
1545
   */
1546
  public function theme($values) {
1547
    return theme($this->theme_functions(),
1548
      array(
1549
        'view' => $this->view,
1550
        'field' => $this,
1551
        'row' => $values,
1552
      ));
1553
  }
1554

    
1555
  /**
1556
   * Build a list of suitable theme functions for this view.
1557
   */
1558
  public function theme_functions() {
1559
    $themes = array();
1560
    $hook = 'views_view_field';
1561

    
1562
    $display = $this->view->display[$this->view->current_display];
1563

    
1564
    if (!empty($display)) {
1565
      $themes[] = $hook . '__' . $this->view->name . '__' . $display->id . '__' . $this->options['id'];
1566
      $themes[] = $hook . '__' . $this->view->name . '__' . $display->id;
1567
      $themes[] = $hook . '__' . $display->id . '__' . $this->options['id'];
1568
      $themes[] = $hook . '__' . $display->id;
1569
      if ($display->id != $display->display_plugin) {
1570
        $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin . '__' . $this->options['id'];
1571
        $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin;
1572
        $themes[] = $hook . '__' . $display->display_plugin . '__' . $this->options['id'];
1573
        $themes[] = $hook . '__' . $display->display_plugin;
1574
      }
1575
    }
1576
    $themes[] = $hook . '__' . $this->view->name . '__' . $this->options['id'];
1577
    $themes[] = $hook . '__' . $this->view->name;
1578
    $themes[] = $hook . '__' . $this->options['id'];
1579
    $themes[] = $hook;
1580

    
1581
    return $themes;
1582
  }
1583

    
1584
  /**
1585
   * {@inheritdoc}
1586
   */
1587
  public function ui_name($short = FALSE) {
1588
    return $this->get_field(parent::ui_name($short));
1589
  }
1590

    
1591
}
1592

    
1593
/**
1594
 * A special handler to take the place of missing or broken handlers.
1595
 *
1596
 * @ingroup views_field_handlers
1597
 */
1598
class views_handler_field_broken extends views_handler_field {
1599

    
1600
  /**
1601
   * {@inheritdoc}
1602
   */
1603
  public function ui_name($short = FALSE) {
1604
    return t('Broken/missing handler');
1605
  }
1606

    
1607
  /**
1608
   * {@inheritdoc}
1609
   */
1610
  public function ensure_my_table() {
1611
    // No table to ensure!
1612
  }
1613

    
1614
  /**
1615
   * {@inheritdoc}
1616
   */
1617
  public function query($group_by = FALSE) {
1618
    // No query to run.
1619
  }
1620

    
1621
  /**
1622
   * {@inheritdoc}
1623
   */
1624
  public function options_form(&$form, &$form_state) {
1625
    $form['markup'] = array(
1626
      '#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>',
1627
    );
1628
  }
1629

    
1630
  /**
1631
   * {@inheritdoc}
1632
   */
1633
  public function broken() {
1634
    return TRUE;
1635
  }
1636

    
1637
}
1638

    
1639
/**
1640
 * Render a numeric value as a size.
1641
 *
1642
 * @ingroup views_field_handlers
1643
 */
1644
class views_handler_field_file_size extends views_handler_field {
1645

    
1646
  /**
1647
   * {@inheritdoc}
1648
   */
1649
  public function option_definition() {
1650
    $options = parent::option_definition();
1651

    
1652
    $options['file_size_display'] = array('default' => 'formatted');
1653

    
1654
    return $options;
1655
  }
1656

    
1657
  /**
1658
   * {@inheritdoc}
1659
   */
1660
  public function options_form(&$form, &$form_state) {
1661
    parent::options_form($form, $form_state);
1662
    $form['file_size_display'] = array(
1663
      '#title' => t('File size display'),
1664
      '#type' => 'select',
1665
      '#options' => array(
1666
        'formatted' => t('Formatted (in KB or MB)'),
1667
        'bytes' => t('Raw bytes'),
1668
      ),
1669
    );
1670
  }
1671

    
1672
  /**
1673
   * {@inheritdoc}
1674
   */
1675
  public function render($values) {
1676
    $value = $this->get_value($values);
1677
    if ($value) {
1678
      switch ($this->options['file_size_display']) {
1679
        case 'bytes':
1680
          return $value;
1681
        case 'formatted':
1682
        default:
1683
          return format_size($value);
1684
      }
1685
    }
1686
    else {
1687
      return '';
1688
    }
1689
  }
1690

    
1691
}
1692

    
1693
/**
1694
 * A handler to run a field through simple XSS filtering.
1695
 *
1696
 * @ingroup views_field_handlers
1697
 */
1698
class views_handler_field_xss extends views_handler_field {
1699

    
1700
  /**
1701
   * {@inheritdoc}
1702
   */
1703
  public function render($values) {
1704
    $value = $this->get_value($values);
1705
    return $this->sanitize_value($value, 'xss');
1706
  }
1707

    
1708
}
1709

    
1710
/**
1711
 * @}
1712
 */