You are here

class content_handler_field_multiple in Content Construction Kit (CCK) 6.3

Same name and namespace in other branches
  1. 6.2 includes/views/handlers/content_handler_field_multiple.inc \content_handler_field_multiple

@file An extended subclass for field handling that adds multiple field grouping.

Fields that want multiple value grouping options in addition to basic field and formatter handling can extend this class.

Hierarchy

Expanded class hierarchy of content_handler_field_multiple

1 string reference to 'content_handler_field_multiple'
content_views_field_views_data in includes/views/content.views.inc

File

includes/views/handlers/content_handler_field_multiple.inc, line 10
An extended subclass for field handling that adds multiple field grouping.

View source
class content_handler_field_multiple extends content_handler_field {
  var $defer_query;
  function init(&$view, $options) {
    $field = $this->content_field;
    parent::init($view, $options);
    $this->defer_query = !empty($options['multiple']['group']) && $field['multiple'];
    if ($this->defer_query) {

      // Grouped field: ditch the existing additional_fields (field columns + delta).
      // In the main query we'll only need:
      // - vid, which will be used to retrieve the actual values in pre_render,
      // - node type and nid, which wil be used in the pseudo-node used when
      // rendering.
      $this->additional_fields = array(
        'type' => array(
          'table' => 'node',
          'field' => 'type',
        ),
        'nid' => array(
          'table' => 'node',
          'field' => 'nid',
        ),
      );
      if ($view->base_table == 'node_revisions') {
        $this->additional_fields['vid'] = array(
          'table' => 'node_revisions',
          'field' => 'vid',
        );
      }
      else {
        $this->additional_fields['vid'] = array(
          'table' => 'node',
          'field' => 'vid',
        );
      }
    }
  }
  function option_definition() {
    $options = parent::option_definition();
    $options['multiple'] = array(
      'contains' => array(
        'group' => array(
          'default' => TRUE,
        ),
        'multiple_number' => array(
          'default' => '',
        ),
        'multiple_from' => array(
          'default' => '',
        ),
        'multiple_reversed' => array(
          'default' => FALSE,
        ),
      ),
    );
    return $options;
  }

  /**
   * Provide 'group multiple values' option.
   */
  function options_form(&$form, &$form_state) {
    parent::options_form($form, $form_state);
    $field = $this->content_field;
    $options = $this->options;
    $form['multiple'] = array(
      '#access' => $field['multiple'],
      '#weight' => 1,
    );
    $form['multiple']['group'] = array(
      '#title' => t('Group multiple values'),
      '#type' => 'checkbox',
      '#default_value' => $options['multiple']['group'],
      '#description' => t('If unchecked, each item in the field will create a new row, which may appear to cause duplicates. This setting is not compatible with click-sorting in table displays.'),
    );

    // Make the string translatable by keeping it as a whole rather than
    // translating prefix and suffix separately.
    list($prefix, $suffix) = explode('@count', t('Show @count value(s)'));
    $form['multiple']['multiple_number'] = array(
      '#type' => 'textfield',
      '#size' => 5,
      '#field_prefix' => $prefix,
      '#field_suffix' => $suffix,
      '#default_value' => $options['multiple']['multiple_number'],
      '#prefix' => '<div class="container-inline">',
      '#process' => array(
        'views_process_dependency',
      ),
      '#dependency' => array(
        'edit-options-multiple-group' => array(
          TRUE,
        ),
      ),
    );
    list($prefix, $suffix) = explode('@count', t('starting from @count'));
    $form['multiple']['multiple_from'] = array(
      '#type' => 'textfield',
      '#size' => 5,
      '#field_prefix' => $prefix,
      '#field_suffix' => $suffix,
      '#default_value' => $options['multiple']['multiple_from'],
      '#process' => array(
        'views_process_dependency',
      ),
      '#dependency' => array(
        'edit-options-multiple-group' => array(
          TRUE,
        ),
      ),
      '#description' => t('(first item is 0)'),
    );
    $form['multiple']['multiple_reversed'] = array(
      '#title' => t('Reversed'),
      '#type' => 'checkbox',
      '#default_value' => $options['multiple']['multiple_reversed'],
      '#suffix' => '</div>',
      '#process' => array(
        'views_process_dependency',
      ),
      '#dependency' => array(
        'edit-options-multiple-group' => array(
          TRUE,
        ),
      ),
      '#description' => t('(start from last values)'),
    );
  }

  /**
   * Determine if this field is click sortable.
   */
  function click_sortable() {
    $field = $this->content_field;
    $options = $this->options;

    // Grouped fields are not click-sortable.
    return !empty($this->definition['click sortable']) && !$this->defer_query;
  }
  function query() {

    // If this is not a grouped field, use the generic query().
    if (!$this->defer_query) {
      return parent::query();
    }

    // Grouped field: do NOT call ensure_my_table, only add additional fields.
    $this
      ->add_additional_fields();
    $this->field_alias = $this->aliases['vid'];
  }
  function pre_render($values) {

    // If there are no values to render (displaying a summary, or query returned no results),
    // or if this is not a grouped field, do nothing specific.
    if (isset($this->view->build_info['summary']) || empty($values) || !$this->defer_query) {
      return parent::pre_render($values);
    }
    $field = $this->content_field;
    $db_info = content_database_info($field);
    $options = $this->options;

    // Build the list of vids to retrieve.
    // TODO: try fetching from cache_content first ??
    $vids = array();
    $this->field_values = array();
    foreach ($values as $result) {
      if (isset($result->{$this->field_alias})) {
        $vids[] = $result->{$this->field_alias};
      }
    }

    // It may happend that the multiple values field is related to a non
    // required relation for which no node data related to the field being
    // processed here is available.
    if (empty($vids)) {
      return parent::pre_render($values);
    }

    // List columns to retrieve.
    $alias = content_views_tablename($field);

    // Prefix aliases with '_' to avoid clashing with field columns names.
    $query_columns = array(
      'vid AS _vid',
      "delta as _delta",
      // nid is needed to generate the links for 'link to node' option.
      'nid AS _nid',
    );

    // The actual field columns.
    foreach ($db_info['columns'] as $column => $attributes) {
      $query_columns[] = "{$attributes['column']} AS {$column}";
    }
    $query = 'SELECT ' . implode(', ', $query_columns) . ' FROM {' . $db_info['table'] . "}" . " WHERE vid IN (" . implode(',', $vids) . ')' . " ORDER BY _nid ASC, _delta " . ($options['multiple']['multiple_reversed'] ? 'DESC' : 'ASC');
    $result = db_query($query);
    while ($item = db_fetch_array($result)) {

      // Clean up the $item from vid and delta. We keep nid for now.
      $vid = $item['_vid'];
      unset($item['_vid']);
      $delta = !empty($item['_delta']) ? $item['_delta'] : 0;
      $item['#delta'] = $item['_delta'];
      unset($item['_delta']);
      $this->field_values[$vid][$delta] = $item;
    }
  }

  /**
   * Return DIV or SPAN based upon the field's element type.
   *
   * Fields rendered with the 'group multiple' option use <div> markers,
   * and thus shouldn't be wrapped in a <span>.
   */
  function element_type($none_supported = FALSE, $default_empty = FALSE) {

    // If this is not a grouped field, use the parent method.
    if (!$this->defer_query) {
      return parent::element_type($none_supported, $default_empty);
    }

    // The 'element_type' property denotes Views 3.x ('semantic views'
    // functionnality). If the property is set, and not set to '' ("default"),
    // let the generic method handle the output.
    if (isset($this->options['element_type']) && $this->options['element_type'] !== '') {
      return parent::element_type($none_supported, $default_empty);
    }
    if ($default_empty) {
      return '';
    }
    if (isset($this->definition['element type'])) {
      return $this->definition['element type'];
    }
    return 'div';
  }
  function render($values) {

    // If this is not a grouped field, use content_handler_field::render().
    if (!$this->defer_query) {
      return parent::render($values);
    }

    // We're down to a single node here, so we can retrieve the actual field
    // definition for the node type being considered.
    $field = content_fields($this->content_field['field_name'], $values->{$this->aliases['type']});

    // If the field does not appear in the node type, then we have no value
    // to display, and can just return.
    if (empty($field)) {
      return '';
    }
    $options = $this->options;
    $vid = $values->{$this->field_alias};
    if (isset($this->field_values[$vid])) {

      // Gather items, respecting the 'Display n values starting from m' settings.
      $count_skipped = 0;
      $items = array();
      foreach ($this->field_values[$vid] as $item) {
        if (empty($options['multiple']['multiple_from']) || $count_skipped >= $options['multiple']['multiple_from']) {
          if (empty($options['multiple']['multiple_number']) || count($items) < $options['multiple']['multiple_number']) {

            // Grab the nid - needed for render_link().
            $nid = $item['_nid'];
            unset($item['_nid']);
            $items[] = $item;
          }
          else {
            break;
          }
        }
        $count_skipped++;
      }

      // Build a pseudo-node from the retrieved values.
      $node = drupal_clone($values);

      // content_format and formatters will need a 'type'.
      $node->type = $values->{$this->aliases['type']};
      $node->nid = $values->{$this->aliases['nid']};
      $node->vid = $values->{$this->aliases['vid']};

      // Some formatters need to behave differently depending on the build_mode
      // (for instance: preview), so we provide one.
      $node->build_mode = NODE_BUILD_NORMAL;

      // Render items.
      $formatter_name = $options['format'];
      if ($items && ($formatter = _content_get_formatter($formatter_name, $field['type']))) {
        $rendered = array();
        if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) {

          // Single-value formatter.
          foreach ($items as $item) {
            $output = content_format($field, $item, $formatter_name, $node);
            if (!empty($output)) {
              $rendered[] = $this
                ->render_link($output, (object) array(
                'nid' => $nid,
              ));
            }
          }
        }
        else {

          // Multiple values formatter.
          $output = content_format($field, $items, $formatter_name, $values);
          if (!empty($output)) {
            $rendered[] = $this
              ->render_link($output, (object) array(
              'nid' => $nid,
            ));
          }
        }
        if (count($rendered) > 1) {

          // TODO: could we use generic field display ?
          return theme('content_view_multiple_field', $rendered, $field, $values);
        }
        elseif ($rendered) {
          return $rendered[0];
        }
      }
    }
    return '';
  }
  function render_link($data, $values) {
    if (!$this->defer_query) {
      return parent::render_link($data, $values);
    }
    if (!empty($this->options['link_to_node']) && $data !== NULL && $data !== '') {
      if (method_exists('render_as_link', 'views_handler_field')) {

        // Views 2.3+
        $this->options['alter']['make_link'] = TRUE;
        $this->options['alter']['path'] = "node/" . $values->{$this->aliases['nid']};
      }
      else {

        // Views up to 2.2
        return l($data, "node/" . $values->nid, array(
          'html' => TRUE,
        ));
      }
    }
    else {
      return $data;
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
content_handler_field::$content_field property
content_handler_field::admin_summary function Provide text for the administrative summary
content_handler_field::construct function
content_handler_field::label function @TODO Now that we save the label in the submit process above we could get rid of this function. Leave it here for now to be sure the label works for fields that haven't been updated since this change was made, since…
content_handler_field::options_submit function Make sure some value is stored as a label.
content_handler_field::options_validate function
content_handler_field_multiple::$defer_query property
content_handler_field_multiple::click_sortable function Determine if this field is click sortable.
content_handler_field_multiple::element_type function Return DIV or SPAN based upon the field's element type. Overrides content_handler_field::element_type
content_handler_field_multiple::init function Overrides content_handler_field::init
content_handler_field_multiple::options_form function Provide 'group multiple values' option. Overrides content_handler_field::options_form
content_handler_field_multiple::option_definition function Overrides content_handler_field::option_definition
content_handler_field_multiple::pre_render function
content_handler_field_multiple::query function
content_handler_field_multiple::render function Overrides content_handler_field::render
content_handler_field_multiple::render_link function