Project

General

Profile

Paste
Download (57.9 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / views / handlers / views_handler_field.inc @ 8be7bf84

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

    
1253
    return $value;
1254
  }
1255

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

    
1269
  /**
1270
   * Render this field as a link, with info from a fieldset set by the user.
1271
   */
1272
  public function render_as_link($alter, $text, $tokens) {
1273
    $value = '';
1274

    
1275
    if (!empty($alter['prefix'])) {
1276
      $value .= filter_xss_admin(strtr($alter['prefix'], $tokens));
1277
    }
1278

    
1279
    $options = array(
1280
      'html' => TRUE,
1281
      'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
1282
    );
1283

    
1284
    // $path will be run through check_url() by l() so we do not need to
1285
    // sanitize it ourselves.
1286
    $path = $alter['path'];
1287

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

    
1295
      if (!empty($alter['path_case']) && $alter['path_case'] != 'none') {
1296
        $path = $this->case_transform($path, $this->options['alter']['path_case']);
1297
      }
1298

    
1299
      if (!empty($alter['replace_spaces'])) {
1300
        $path = str_replace(' ', '-', $path);
1301
      }
1302

    
1303
      if (!empty($alter['unwanted_characters'])) {
1304
        foreach (explode(' ', $alter['unwanted_characters']) as $unwanted) {
1305
          $path = str_replace($unwanted, '', $path);
1306
        }
1307
      }
1308
    }
1309

    
1310
    // Parse the URL and move any query and fragment parameters out of the path.
1311
    $url = parse_url($path);
1312

    
1313
    // Seriously malformed URLs may return FALSE or empty arrays.
1314
    if (empty($url)) {
1315
      return $text;
1316
    }
1317

    
1318
    // If the path is empty do not build a link around the given text and return
1319
    // it as is. http://www.example.com URLs will not have a $url['path'], so
1320
    // check host as well.
1321
    if (empty($url['path']) && empty($url['host']) && empty($url['fragment'])) {
1322
      return $text;
1323
    }
1324

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

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

    
1358
    $alt = strtr($alter['alt'], $tokens);
1359
    // Set the title attribute of the link only if it improves accessibility
1360
    if ($alt && $alt != $text) {
1361
      $options['attributes']['title'] = decode_entities($alt);
1362
    }
1363

    
1364
    $class = strtr($alter['link_class'], $tokens);
1365
    if ($class) {
1366
      $options['attributes']['class'] = array($class);
1367
    }
1368

    
1369
    if (!empty($alter['rel']) && $rel = strtr($alter['rel'], $tokens)) {
1370
      $options['attributes']['rel'] = $rel;
1371
    }
1372

    
1373
    $target = check_plain(trim(strtr($alter['target'], $tokens)));
1374
    if (!empty($target)) {
1375
      $options['attributes']['target'] = $target;
1376
    }
1377

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

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

    
1409
    // If the url came from entity_uri(), pass along the required options.
1410
    if (isset($alter['entity'])) {
1411
      $options['entity'] = $alter['entity'];
1412
    }
1413
    if (isset($alter['entity_type'])) {
1414
      $options['entity_type'] = $alter['entity_type'];
1415
    }
1416

    
1417
    $value .= l($text, $path, $options);
1418

    
1419
    if (!empty($alter['suffix'])) {
1420
      $value .= filter_xss_admin(strtr($alter['suffix'], $tokens));
1421
    }
1422

    
1423
    return $value;
1424
  }
1425

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

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

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

    
1453
    // Now add replacements for our fields.
1454
    foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
1455
      if (isset($handler->last_render)) {
1456
        $tokens["[$field]"] = $handler->last_render;
1457
      }
1458
      else {
1459
        $tokens["[$field]"] = '';
1460
      }
1461
      if (!empty($item)) {
1462
        $this->add_self_tokens($tokens, $item);
1463
      }
1464

    
1465
      // We only use fields up to (and including) this one.
1466
      if ($field == $this->options['id']) {
1467
        break;
1468
      }
1469
    }
1470

    
1471
    // Store the tokens for the row so we can reference them later if necessary.
1472
    $this->view->style_plugin->render_tokens[$this->view->row_index] = $tokens;
1473
    $this->last_tokens = $tokens;
1474

    
1475
    return $tokens;
1476
  }
1477

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

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

    
1533
    return $tokens;
1534
  }
1535

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

    
1550
  /**
1551
   * Document any special tokens this field might use for itself.
1552
   *
1553
   * @see add_self_tokens()
1554
   */
1555
  public function document_self_tokens(&$tokens) {
1556
  }
1557

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

    
1573
  /**
1574
   * Build a list of suitable theme functions for this view.
1575
   */
1576
  public function theme_functions() {
1577
    $themes = array();
1578
    $hook = 'views_view_field';
1579

    
1580
    $display = $this->view->display[$this->view->current_display];
1581

    
1582
    if (!empty($display)) {
1583
      $themes[] = $hook . '__' . $this->view->name . '__' . $display->id . '__' . $this->options['id'];
1584
      $themes[] = $hook . '__' . $this->view->name . '__' . $display->id;
1585
      $themes[] = $hook . '__' . $display->id . '__' . $this->options['id'];
1586
      $themes[] = $hook . '__' . $display->id;
1587
      if ($display->id != $display->display_plugin) {
1588
        $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin . '__' . $this->options['id'];
1589
        $themes[] = $hook . '__' . $this->view->name . '__' . $display->display_plugin;
1590
        $themes[] = $hook . '__' . $display->display_plugin . '__' . $this->options['id'];
1591
        $themes[] = $hook . '__' . $display->display_plugin;
1592
      }
1593
    }
1594
    $themes[] = $hook . '__' . $this->view->name . '__' . $this->options['id'];
1595
    $themes[] = $hook . '__' . $this->view->name;
1596
    $themes[] = $hook . '__' . $this->options['id'];
1597
    $themes[] = $hook;
1598

    
1599
    return $themes;
1600
  }
1601

    
1602
  /**
1603
   * {@inheritdoc}
1604
   */
1605
  public function ui_name($short = FALSE) {
1606
    return $this->get_field(parent::ui_name($short));
1607
  }
1608

    
1609
}
1610

    
1611
/**
1612
 * A special handler to take the place of missing or broken handlers.
1613
 *
1614
 * @ingroup views_field_handlers
1615
 */
1616
class views_handler_field_broken extends views_handler_field {
1617

    
1618
  /**
1619
   * {@inheritdoc}
1620
   */
1621
  public function ui_name($short = FALSE) {
1622
    return t('Broken/missing handler');
1623
  }
1624

    
1625
  /**
1626
   * {@inheritdoc}
1627
   */
1628
  public function ensure_my_table() {
1629
    // No table to ensure!
1630
  }
1631

    
1632
  /**
1633
   * {@inheritdoc}
1634
   */
1635
  public function query($group_by = FALSE) {
1636
    // No query to run.
1637
  }
1638

    
1639
  /**
1640
   * {@inheritdoc}
1641
   */
1642
  public function options_form(&$form, &$form_state) {
1643
    $form['markup'] = array(
1644
      '#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>',
1645
    );
1646
  }
1647

    
1648
  /**
1649
   * {@inheritdoc}
1650
   */
1651
  public function broken() {
1652
    return TRUE;
1653
  }
1654

    
1655
}
1656

    
1657
/**
1658
 * Render a numeric value as a size.
1659
 *
1660
 * @ingroup views_field_handlers
1661
 */
1662
class views_handler_field_file_size extends views_handler_field {
1663

    
1664
  /**
1665
   * {@inheritdoc}
1666
   */
1667
  public function option_definition() {
1668
    $options = parent::option_definition();
1669

    
1670
    $options['file_size_display'] = array('default' => 'formatted');
1671

    
1672
    return $options;
1673
  }
1674

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

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

    
1709
}
1710

    
1711
/**
1712
 * A handler to run a field through simple XSS filtering.
1713
 *
1714
 * @ingroup views_field_handlers
1715
 */
1716
class views_handler_field_xss extends views_handler_field {
1717

    
1718
  /**
1719
   * {@inheritdoc}
1720
   */
1721
  public function render($values) {
1722
    $value = $this->get_value($values);
1723
    return $this->sanitize_value($value, 'xss');
1724
  }
1725

    
1726
}
1727

    
1728
/**
1729
 * @}
1730
 */