1<?php
2/**
3 * @defgroup views_field_handlers Views' field handlers
4 * @{
5 * Handlers to tell Views how to build and display fields.
6 *
7 */
8
9/**
10 * Base field handler that has no options and renders an unformatted field.
11 *
12 * Definition terms:
13 * - additional fields: An array of fields that should be added to the query
14 *                      for some purpose. The array is in the form of:
15 *                      array('identifier' => array('table' => tablename,
16 *                      'field' => fieldname); as many fields as are necessary
17 *                      may be in this array.
18 * - click sortable: If TRUE, this field may be click sorted.
19 */
20class views_handler_field extends views_handler {
21  var $field_alias = 'unknown';
22  var $aliases = array();
23
24  /**
25   * Construct a new field handler.
26   */
27  function construct() {
28    parent::construct();
29
30    $this->additional_fields = array();
31    if (!empty($this->definition['additional fields'])) {
32      $this->additional_fields = $this->definition['additional fields'];
33    }
34
35    if (!isset($this->options['exclude'])) {
36      $this->options['exclude'] = '';
37    }
38  }
39
40  /**
41   * Determine if this field can allow advanced rendering.
42   *
43   * Fields can set this to FALSE if they do not wish to allow
44   * token based rewriting or link-making.
45   */
46  function allow_advanced_render() {
47    return TRUE;
48  }
49
50  function init(&$view, $options) {
51    parent::init($view, $options);
52
53    $this->options += array(
54      'exclude' => FALSE,
55    );
56  }
57
58  /**
59   * Called to add the field to a query.
60   */
61  function query() {
62    $this->ensure_my_table();
63    // Add the field.
64    $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field);
65
66    $this->add_additional_fields();
67  }
68
69  /**
70   * Add 'additional' fields to the query.
71   *
72   * @param $fields
73   * An array of fields. The key is an identifier used to later find the
74   * field alias used. The value is either a string in which case it's
75   * assumed to be a field on this handler's table; or it's an array in the
76   * form of
77   * @code array('table' => $tablename, 'field' => $fieldname) @endcode
78   */
79  function add_additional_fields($fields = NULL) {
80    if (!isset($fields)) {
81      // notice check
82      if (empty($this->additional_fields)) {
83        return;
84      }
85      $fields = $this->additional_fields;
86    }
87    if (!empty($fields) && is_array($fields)) {
88      foreach ($fields as $identifier => $info) {
89        if (is_array($info)) {
90          if (isset($info['table'])) {
91            $table_alias = $this->query->ensure_table($info['table'], $this->relationship);
92          }
93          else {
94            $table_alias = $this->table_alias;
95          }
96          if (empty($table_alias)) {
97            vpr(t('Handler @handler tried to add additional_field @identifier but @table could not be added!', array('@handler' => $this->definition['handler'], '@identifier' => $identifier, '@table' => $info['table'])));
98            $this->aliases[$identifier] = 'broken';
99            continue;
100          }
101          $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field']);
102        }
103        else {
104          $this->aliases[$info] = $this->query->add_field($this->table_alias, $info);
105        }
106      }
107    }
108  }
109
110  /**
111   * Called to determine what to tell the clicksorter.
112   */
113  function click_sort($order) {
114    $this->query->add_orderby($this->table_alias, $this->real_field, $order, $this->field_alias);
115  }
116
117  /**
118   * Determine if this field is click sortable.
119   */
120  function click_sortable() {
121    return !empty($this->definition['click sortable']);
122  }
123
124  /**
125   * Get this field's label.
126   */
127  function label() {
128    if (!isset($this->options['label'])) {
129      return '';
130    }
131    return $this->options['label'];
132  }
133
134  /**
135   * Return DIV or SPAN based upon the field's element type.
136   */
137  function element_type() {
138    if (isset($this->definition['element type'])) {
139      return $this->definition['element type'];
140    }
141
142    return 'span';
143  }
144
145  function option_definition() {
146    $options = parent::option_definition();
147
148    $options['label'] = array('default' => $this->definition['title'], 'translatable' => TRUE);
149    $options['alter'] = array(
150      'contains' => array(
151        'alter_text' => array('default' => FALSE),
152        'text' => array('default' => '', 'translatable' => TRUE),
153        'make_link' => array('default' => FALSE),
154        'path' => array('default' => '', 'translatable' => TRUE),
155        'absolute' => array('default' => '', 'translatable' => FALSE),
156        'alt' => array('default' => '', 'translatable' => TRUE),
157        'rel' => array('default' => ''),
158        'link_class' => array('default' => ''),
159        'prefix' => array('default' => '', 'translatable' => TRUE),
160        'suffix' => array('default' => '', 'translatable' => TRUE),
161        'target' => array('default' => '', 'translatable' => TRUE),
162        'trim' => array('default' => FALSE),
163        'max_length' => array('default' => ''),
164        'word_boundary' => array('default' => TRUE),
165        'ellipsis' => array('default' => TRUE),
166        'strip_tags' => array('default' => FALSE),
167        'html' => array('default' => FALSE),
168      ),
169    );
170    $options['empty'] = array('default' => '', 'translatable' => TRUE);
171    $options['hide_empty'] = array('default' => FALSE);
172    $options['empty_zero'] = array('default' => FALSE);
173    $options['hide_alter_empty'] = array('default' => TRUE);
174
175    return $options;
176  }
177
178  /**
179   * Default options form that provides the label widget that all fields
180   * should have.
181   */
182  function options_form(&$form, &$form_state) {
183    $form['label'] = array(
184      '#type' => 'textfield',
185      '#title' => t('Label'),
186      '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
187      '#description' => t('The label for this field that will be displayed to end users if the style requires it.'),
188    );
189    $form['exclude'] = array(
190      '#type' => 'checkbox',
191      '#title' => t('Exclude from display'),
192      '#default_value' => $this->options['exclude'],
193      '#description' => t('Check this box to not display this field, but still load it in the view.  Use this option to not show a grouping field in each record, or when doing advanced theming.'),
194    );
195
196    if ($this->allow_advanced_render()) {
197      $form['alter']['#tree'] = TRUE;
198      $form['alter']['alter_text'] = array(
199        '#type' => 'checkbox',
200        '#title' => t('Rewrite the output of this field'),
201        '#description' => t('If checked, you can alter the output of this field by specifying a string of text with replacement tokens that can use any existing field output.'),
202        '#default_value' => $this->options['alter']['alter_text'],
203      );
204
205      $form['alter']['text'] = array(
206        '#title' => t('Text'),
207        '#type' => 'textarea',
208        '#default_value' => $this->options['alter']['text'],
209        '#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.'),
210        '#process' => array('views_process_dependency'),
211        '#dependency' => array(
212          'edit-options-alter-alter-text' => array(1)
213        ),
214      );
215
216      $form['alter']['make_link'] = array(
217        '#type' => 'checkbox',
218        '#title' => t('Output this field as a link'),
219        '#description' => t('If checked, this field will be made into a link. The destination must be given below.'),
220        '#default_value' => $this->options['alter']['make_link'],
221      );
222      $form['alter']['path'] = array(
223        '#title' => t('Link path'),
224        '#type' => 'textfield',
225        '#default_value' => $this->options['alter']['path'],
226        '#description' => t('The Drupal path or absolute URL for this link. You may enter data from this view as per the "Replacement patterns" below.'),
227        '#process' => array('views_process_dependency'),
228        '#dependency' => array(
229          'edit-options-alter-make-link' => array(1)
230        ),
231        '#maxlength' => 255,
232      );
233       $form['alter']['absolute'] = array(
234         '#type' => 'checkbox',
235         '#title' => t('Use absolute path'),
236         '#default_value' => $this->options['alter']['absolute'],
237         '#process' => array('views_process_dependency'),
238         '#dependency' => array(
239           'edit-options-alter-make-link' => array(1)
240         ),
241       );
242
243      $form['alter']['link_class'] = array(
244        '#title' => t('Link class'),
245        '#type' => 'textfield',
246        '#default_value' => $this->options['alter']['link_class'],
247        '#description' => t('The CSS class to apply to the link.'),
248        '#process' => array('views_process_dependency'),
249        '#dependency' => array(
250          'edit-options-alter-make-link' => array(1)
251        ),
252      );
253      $form['alter']['alt'] = array(
254        '#title' => t('Alt text'),
255        '#type' => 'textfield',
256        '#default_value' => $this->options['alter']['alt'],
257        '#description' => t('Text to place as "alt" text which most browsers display as a tooltip when hovering over the link.'),
258        '#process' => array('views_process_dependency'),
259        '#dependency' => array(
260          'edit-options-alter-make-link' => array(1)
261        ),
262      );
263      $form['alter']['rel'] = array(
264        '#title' => t('Rel Text'),
265        '#type' => 'textfield',
266        '#default_value' => $this->options['alter']['rel'],
267        '#description' => t('Include Rel attribute for use in lightbox2 or other javascript utility.'),
268        '#process' => array('views_process_dependency'),
269        '#dependency' => array(
270          'edit-options-alter-make-link' => array(1)
271        ),
272      );
273      $form['alter']['prefix'] = array(
274        '#title' => t('Prefix text'),
275        '#type' => 'textfield',
276        '#default_value' => $this->options['alter']['prefix'],
277        '#description' => t('Any text to display before this link. You may include HTML.'),
278        '#process' => array('views_process_dependency'),
279        '#dependency' => array(
280          'edit-options-alter-make-link' => array(1)
281        ),
282      );
283      $form['alter']['suffix'] = array(
284        '#title' => t('Suffix text'),
285        '#type' => 'textfield',
286        '#default_value' => $this->options['alter']['suffix'],
287        '#description' => t('Any text to display after this link. You may include HTML.'),
288        '#process' => array('views_process_dependency'),
289        '#dependency' => array(
290          'edit-options-alter-make-link' => array(1)
291        ),
292      );
293      $form['alter']['target'] = array(
294        '#title' => t('Target'),
295        '#type' => 'textfield',
296        '#default_value' => $this->options['alter']['target'],
297        '#description' => t("Target of the link, such as _blank, _parent or an iframe's name. This field is rarely used."),
298        '#process' => array('views_process_dependency'),
299        '#dependency' => array(
300          'edit-options-alter-make-link' => array(1)
301        ),
302      );
303
304
305      // Get a list of the available fields and arguments for token replacement.
306      $options = array();
307      foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
308        $options[t('Fields')]["[$field]"] = $handler->ui_name();
309        // We only use fields up to (and including) this one.
310        if ($field == $this->options['id']) {
311          break;
312        }
313      }
314      $count = 0; // This lets us prepare the key as we want it printed.
315      foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
316        $options[t('Arguments')]['%' . ++$count] = t('@argument title', array('@argument' => $handler->ui_name()));
317        $options[t('Arguments')]['!' . $count] = t('@argument input', array('@argument' => $handler->ui_name()));
318      }
319
320      $this->document_self_tokens($options[t('Fields')]);
321
322      // Default text.
323      $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>');
324      // We have some options, so make a list.
325      if (!empty($options)) {
326        $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.
327If you would like to have the characters %5B and %5D please use the html entity codes \'%5B\' or  \'%5D\' or they will get replaced with empty space.</p>');
328        foreach (array_keys($options) as $type) {
329          if (!empty($options[$type])) {
330            $items = array();
331            foreach ($options[$type] as $key => $value) {
332              $items[] = $key . ' == ' . $value;
333            }
334            $output .= theme('item_list', $items, $type);
335          }
336        }
337      }
338      // This construct uses 'hidden' and not markup because process doesn't
339      // run. It also has an extra div because the dependency wants to hide
340      // the parent in situations like this, so we need a second div to
341      // make this work.
342      $form['alter']['help'] = array(
343        '#type' => 'hidden',
344        '#id' => 'views-tokens-help',
345        '#prefix' => '<div><fieldset id="views-tokens-help"><legend>' . t('Replacement patterns') . '</legend>' . $output . '</fieldset></div>',
346        '#process' => array('views_process_dependency'),
347        '#dependency' => array(
348          'edit-options-alter-make-link' => array(1),
349          'edit-options-alter-alter-text' => array(1),
350        ),
351      );
352
353      $form['alter']['trim'] = array(
354        '#type' => 'checkbox',
355        '#title' => t('Trim this field to a maximum length'),
356        '#description' => t('If checked, this field be trimmed to a maximum length in characters.'),
357        '#default_value' => $this->options['alter']['trim'],
358      );
359
360      $form['alter']['max_length'] = array(
361        '#title' => t('Maximum length'),
362        '#type' => 'textfield',
363        '#default_value' => $this->options['alter']['max_length'],
364        '#description' => t('The maximum number of characters this field can be.'),
365        '#process' => array('views_process_dependency'),
366        '#dependency' => array(
367          'edit-options-alter-trim' => array(1)
368        ),
369      );
370
371      $form['alter']['word_boundary'] = array(
372        '#type' => 'checkbox',
373        '#title' => t('Trim only on a word boundary'),
374        '#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.'),
375        '#default_value' => $this->options['alter']['word_boundary'],
376        '#process' => array('views_process_dependency'),
377        '#dependency' => array(
378          'edit-options-alter-trim' => array(1)
379        ),
380      );
381
382      $form['alter']['ellipsis'] = array(
383        '#type' => 'checkbox',
384        '#title' => t('Add an ellipsis'),
385        '#description' => t('If checked, a "..." will be added if a field was trimmed.'),
386        '#default_value' => $this->options['alter']['ellipsis'],
387        '#process' => array('views_process_dependency'),
388        '#dependency' => array(
389          'edit-options-alter-trim' => array(1)
390        ),
391      );
392
393      $form['alter']['html'] = array(
394        '#type' => 'checkbox',
395        '#title' => t('Field can contain HTML'),
396        '#description' => t('If checked, HTML corrector will be run to ensure tags are properly closed after trimming.'),
397        '#default_value' => $this->options['alter']['html'],
398        '#process' => array('views_process_dependency'),
399        '#dependency' => array(
400          'edit-options-alter-trim' => array(1)
401        ),
402      );
403
404      $form['alter']['strip_tags'] = array(
405        '#type' => 'checkbox',
406        '#title' => t('Strip HTML tags'),
407        '#description' => t('If checked, all HTML tags will be stripped.'),
408        '#default_value' => $this->options['alter']['strip_tags'],
409        '#process' => array('views_process_dependency'),
410      );
411    }
412
413    $form['empty'] = array(
414      '#type' => 'textfield',
415      '#title' => t('Empty text'),
416      '#default_value' => $this->options['empty'],
417      '#description' => t('If the field is empty, display this text instead.'),
418    );
419
420    $form['empty_zero'] = array(
421      '#type' => 'checkbox',
422      '#title' => t('Count the number 0 as empty'),
423      '#default_value' => $this->options['empty_zero'],
424      '#description' => t('If the field contains the number zero, display the empty text instead'),
425    );
426
427    $form['hide_empty'] = array(
428      '#type' => 'checkbox',
429      '#title' => t('Hide if empty'),
430      '#default_value' => $this->options['hide_empty'],
431      '#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.'),
432    );
433
434    $form['hide_alter_empty'] = array(
435      '#type' => 'checkbox',
436      '#title' => t('Do not rewrite if empty'),
437      '#default_value' => $this->options['hide_alter_empty'],
438    );
439  }
440
441  /**
442   * Provide extra data to the administration form
443   */
444  function admin_summary() {
445    return $this->label();
446  }
447
448  /**
449   * Run before any fields are rendered.
450   *
451   * This gives the handlers some time to set up before any handler has
452   * been rendered.
453   *
454   * @param $values
455   *   An array of all objects returned from the query.
456   */
457  function pre_render(&$values) { }
458
459  /**
460   * Render the field.
461   *
462   * @param $values
463   *   The values retrieved from the database.
464   */
465  function render($values) {
466    $value = $values->{$this->field_alias};
467    return check_plain($value);
468  }
469
470  /**
471   * Render a field using advanced settings.
472   *
473   * This renders a field normally, then decides if render-as-link and
474   * text-replacement rendering is necessary.
475   */
476  function advanced_render($values) {
477    if ($this->allow_advanced_render() && method_exists($this, 'render_item')) {
478      $raw_items = $this->get_items($values);
479    }
480    else {
481      $this->last_render = $value = $this->render($values);
482      $this->original_value = $value;
483    }
484
485    if ($this->allow_advanced_render()) {
486      $tokens = NULL;
487      if (method_exists($this, 'render_item')) {
488        $items = array();
489        foreach ($raw_items as $count => $item) {
490          $this->last_render = $this->render_item($count, $item);
491          $this->original_value = $this->last_render;
492
493          $alter = $item + $this->options['alter'];
494          $items[] = $this->render_text($alter);
495        }
496
497        $value = $this->render_items($items);
498      }
499      else {
500        $value = $this->render_text($this->options['alter']);
501      }
502
503      // This happens here so that render_as_link can get the unaltered value of
504      // this field as a token rather than the altered value.
505      $this->last_render = $value;
506    }
507
508    if (empty($this->last_render)) {
509      if (($this->last_render !== 0 && $this->last_render !== '0') || !empty($this->options['empty_zero'])) {
510        $alter = $this->options['alter'];
511        $alter['alter_text'] = 1;
512        $alter['text'] = $this->options['empty'];
513        $this->last_render = $this->render_text($alter);
514      }
515    }
516
517    return $this->last_render;
518  }
519
520  /**
521   * Perform an advanced text render for the item.
522   *
523   * This is separated out as some fields may render lists, and this allows
524   * each item to be handled individually.
525   */
526  function render_text($alter) {
527    $value = trim($this->last_render);
528
529    if (!empty($alter['alter_text']) && $alter['text'] !== '') {
530      $tokens = $this->get_render_tokens($alter);
531      $value = $this->render_altered($alter, $tokens);
532    }
533
534    if ((($this->options['hide_empty'] && empty($value)) || ($this->options['hide_alter_empty'] && empty($this->original_value))) && ($value !== 0 || $this->options['empty_zero'])) {
535      return '';
536    }
537
538    if (!empty($alter['strip_tags'])) {
539      $value = strip_tags($value);
540    }
541
542    if (!empty($alter['trim']) && !empty($alter['max_length'])) {
543      $value = $this->render_trim_text($alter, $value);
544    }
545
546    if (!empty($alter['make_link']) && !empty($alter['path'])) {
547      if (!isset($tokens)) {
548       $tokens = $this->get_render_tokens($alter);
549      }
550      $value = $this->render_as_link($alter, $value, $tokens);
551    }
552
553    return $value;
554  }
555
556  /**
557   * Render this field as altered text, from a fieldset set by the user.
558   */
559  function render_altered($alter, $tokens) {
560    // Filter this right away as our substitutions are already sanitized.
561    $value = filter_xss_admin($alter['text']);
562    $value = strtr($value, $tokens);
563
564    return $value;
565  }
566
567  /**
568   * Trim the field down to the specified length.
569   */
570  function render_trim_text($alter, $value) {
571    if (!empty($alter['strip_tags'])) {
572      // NOTE: It's possible that some external fields might override the
573      // element type so if someone from, say, CCK runs into a bug here,
574      // this may be why =)
575      $this->definition['element type'] = 'span';
576    }
577    return views_trim_text($alter, $value);
578  }
579
580  /**
581   * Render this field as a link, with the info from a fieldset set by
582   * the user.
583   */
584  function render_as_link($alter, $text, $tokens) {
585    $value = '';
586
587    if (!empty($alter['prefix'])) {
588      $value .= filter_xss_admin(strtr($alter['prefix'], $tokens));
589    }
590
591    $options = array(
592      'html' => TRUE,
593      'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
594    );
595
596    // $path will be run through check_url() by l() so we do not need to
597    // sanitize it ourselves.
598    $path = $alter['path'];
599
600    // html_entity_decode removes <front>, so check whether its different to front.
601    if ($path != '<front>') {
602      // Use strip tags as there should never be HTML in the path.
603      // However, we need to preserve special characters like " that
604      // were removed by check_plain().
605      $path = strip_tags(html_entity_decode(strtr($path, $tokens)));
606    }
607
608    // If the path is empty do not build a link around the given text and return
609    // it as is.
610    if (empty($path)) {
611      return $text;
612    }
613
614    // Parse the URL and move any query and fragment parameters out of the path.
615    $url = parse_url($path);
616    if (isset($url['query'])) {
617      $path = strtr($path, array('?' . $url['query'] => ''));
618      $options['query'] = $url['query'];
619    }
620    if (isset($url['fragment'])) {
621      $path = strtr($path, array('#' . $url['fragment'] => ''));
622      // If the path is empty we want to have a fragment for the current site.
623      if ($path == '') {
624        $options['external'] = TRUE;
625      }
626      $options['fragment'] = $url['fragment'];
627    }
628
629    $alt = strtr($alter['alt'], $tokens);
630    // Set the title attribute of the link only if it improves accessibility
631    if ($alt && $alt != $text) {
632      $options['attributes']['title'] = html_entity_decode($alt, ENT_QUOTES);
633    }
634
635    $class = strtr($alter['link_class'], $tokens);
636    if ($class) {
637      $options['attributes']['class'] = $class;
638    }
639
640    if (!empty($alter['rel']) && $rel = strtr($alter['rel'], $tokens)) {
641      $options['attributes']['rel'] = $rel;
642    }
643
644    $target = check_plain(trim(strtr($alter['target'],$tokens)));
645    if (!empty($target)) {
646      $options['attributes']['target'] = $target;
647    }
648
649    // Allow the addition of arbitrary attributes to links. Additional attributes
650    // currently can only be altered in preprocessors and not within the UI.
651    if (isset($alter['link_attributes']) && is_array($alter['link_attributes'])) {
652      foreach ($alter['link_attributes'] as $key => $attribute) {
653        if (!isset($options['attributes'][$key])) {
654          $options['attributes'][$key] = strtr($attribute, $tokens);
655        }
656      }
657    }
658
659    // If the query and fragment were programatically assigned overwrite any
660    // parsed values.
661    if (isset($alter['query'])) {
662      $options['query'] = strtr($alter['query'], $tokens);
663    }
664    if (isset($alter['alias'])) {
665      // Alias is a boolean field, so no token.
666      $options['alias'] = $alter['alias'];
667    }
668    if (isset($alter['fragment'])) {
669      $options['fragment'] = strtr($alter['fragment'], $tokens);
670    }
671    if (isset($this->options['alter']['language'])) {
672      $options['language'] = $this->options['alter']['language'];
673    }
674
675    $value .= l($text, $path, $options);
676
677    if (!empty($alter['suffix'])) {
678      $value .= filter_xss_admin(strtr($alter['suffix'], $tokens));
679    }
680
681    return $value;
682  }
683
684  /**
685   * Get the 'render' tokens to use for advanced rendering.
686   *
687   * This runs through all of the fields and arguments that
688   * are available and gets their values. This will then be
689   * used in one giant str_replace().
690   */
691  function get_render_tokens($item) {
692    $tokens = array();
693    if (!empty($this->view->build_info['substitutions'])) {
694      $tokens = $this->view->build_info['substitutions'];
695    }
696    $count = 0;
697    foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) {
698      $token = '%' . ++$count;
699      if (!isset($tokens[$token])) {
700        $tokens[$token] = '';
701      }
702
703      // Use strip tags as there should never be HTML in the path.
704      // However, we need to preserve special characters like " that
705      // were removed by check_plain().
706      $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(html_entity_decode($this->view->args[$count - 1])) : '';
707    }
708
709    // Now add replacements for our fields.
710    foreach ($this->view->display_handler->get_handlers('field') as $field => $handler) {
711      if (isset($handler->last_render)) {
712        $tokens["[$field]"] = $handler->last_render;
713      }
714      else {
715        $tokens["[$field]"] = '';
716      }
717      $this->add_self_tokens($tokens, $item);
718
719      // We only use fields up to (and including) this one.
720      if ($field == $this->options['id']) {
721        break;
722      }
723    }
724
725    return $tokens;
726  }
727
728  /**
729   * Add any special tokens this field might use for itself.
730   *
731   * This method is intended to be overridden by items that generate
732   * fields as a list. For example, the field that displays all terms
733   * on a node might have tokens for the tid and the term.
734   *
735   * By convention, tokens should follow the format of [token-subtoken]
736   * where token is the field ID and subtoken is the field. If the
737   * field ID is terms, then the tokens might be [terms-tid] and [terms-name].
738   */
739  function add_self_tokens(&$tokens, $item) { }
740
741  /**
742   * Document any special tokens this field might use for itself.
743   *
744   * @see add_self_tokens() for details.
745   */
746  function document_self_tokens(&$tokens) { }
747
748  /**
749   * Call out to the theme() function, which probably just calls render() but
750   * allows sites to override output fairly easily.
751   */
752  function theme($values) {
753    return theme($this->theme_functions(), $this->view, $this, $values);
754  }
755
756  function theme_functions() {
757    $themes = array();
758    $hook = 'views_view_field';
759
760    $display = $this->view->display[$this->view->current_display];
761
762    if (!empty($display)) {
763      $themes[] = $hook . '__' . $this->view->name  . '__' . $display->id . '__' . $this->options['id'];
764      $themes[] = $hook . '__' . $this->view->name  . '__' . $display->id;
765      $themes[] = $hook . '__' . $display->id . '__' . $this->options['id'];
766      $themes[] = $hook . '__' . $display->id;
767      if ($display->id != $display->display_plugin) {
768        $themes[] = $hook . '__' . $this->view->name  . '__' . $display->display_plugin . '__' . $this->options['id'];
769        $themes[] = $hook . '__' . $this->view->name  . '__' . $display->display_plugin;
770        $themes[] = $hook . '__' . $display->display_plugin . '__' . $this->options['id'];
771        $themes[] = $hook . '__' . $display->display_plugin;
772      }
773    }
774    $themes[] = $hook . '__' . $this->view->name . '__' . $this->options['id'];
775    $themes[] = $hook . '__' . $this->view->name;
776    $themes[] = $hook . '__' . $this->options['id'];
777    $themes[] = $hook;
778
779    return $themes;
780  }
781}
782
783/**
784 * A special handler to take the place of missing or broken handlers.
785 */
786class views_handler_field_broken extends views_handler_field {
787  function ui_name($short = FALSE) {
788    return t('Broken/missing handler');
789  }
790
791  function ensure_my_table() { /* No table to ensure! */ }
792  function query() { /* No query to run */ }
793  function options_form(&$form, &$form_state) {
794    $form['markup'] = array(
795      '#prefix' => '<div class="form-item description">',
796      '#value' => 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.'),
797    );
798  }
799
800  /**
801   * Determine if the handler is considered 'broken'
802   */
803  function broken() { return TRUE; }
804}
805
806/**
807 * Render a numeric value as a size.
808 */
809class views_handler_field_file_size extends views_handler_field {
810  function option_definition() {
811    $options = parent::option_definition();
812
813    $options['file_size_display'] = array('default' => 'formatted');
814
815    return $options;
816  }
817
818  function options_form(&$form, &$form_state) {
819    parent::options_form($form, $form_state);
820    $form['file_size_display'] = array(
821      '#title' => t('File size display'),
822      '#type' => 'select',
823      '#options' => array(
824        'formatted' => t('Formatted (in KB or MB)'),
825        'bytes' => t('Raw bytes'),
826      ),
827    );
828  }
829
830  function render($values) {
831    if ($values->{$this->field_alias}) {
832      switch ($this->options['file_size_display']) {
833        case 'bytes':
834          return $values->{$this->field_alias};
835        case 'formatted':
836        default:
837          return format_size($values->{$this->field_alias});
838      }
839    }
840    else {
841      return '';
842    }
843  }
844}
845
846/**
847 * A handler to run a field through simple XSS filtering
848 */
849class views_handler_field_xss extends views_handler_field {
850  function render($values) {
851    $value = $values->{$this->field_alias};
852    return filter_xss($value);
853  }
854}
855
856/**
857 * @}
858 */
859
860