You are here

class views_many_to_one_helper in Views (for Drupal 7) 7.3

Same name and namespace in other branches
  1. 6.3 includes/handlers.inc \views_many_to_one_helper
  2. 6.2 includes/handlers.inc \views_many_to_one_helper

This many to one helper object is used on both arguments and filters.

@todo This requires extensive documentation on how this class is to be used. For now, look at the arguments and filters that use it. Lots of stuff is just pass-through but there are definitely some interesting areas where they interact.

Any handler that uses this can have the following possibly additional definition terms:

  • numeric: If true, treat this field as numeric, using %d instead of %s in queries.

Hierarchy

Expanded class hierarchy of views_many_to_one_helper

File

includes/handlers.inc, line 810
Defines the various handler objects to help build and display views.

View source
class views_many_to_one_helper {

  /**
   * Contains possible existing placeholders used by the query.
   *
   * @var array
   */
  public $placeholders = array();

  /**
   * {@inheritdoc}
   */
  public function __construct(&$handler) {
    $this->handler =& $handler;
  }

  /**
   * {@inheritdoc}
   */
  static function option_definition(&$options) {
    $options['reduce_duplicates'] = array(
      'default' => FALSE,
      'bool' => TRUE,
    );
  }

  /**
   * {@inheritdoc}
   */
  public function options_form(&$form, &$form_state) {
    $form['reduce_duplicates'] = array(
      '#type' => 'checkbox',
      '#title' => t('Reduce duplicates'),
      '#description' => t('This filter can cause items that have more than one of the selected options to appear as duplicate results. If this filter causes duplicate results to occur, this checkbox can reduce those duplicates; however, the more terms it has to search for, the less performant the query will be, so use this with caution. Shouldn\'t be set on single-value fields, as it may cause values to disappear from display, if used on an incompatible field.'),
      '#default_value' => !empty($this->handler->options['reduce_duplicates']),
      '#weight' => 4,
    );
  }

  /**
   * Provide an option to use a formula.
   *
   * If it wants us to do this, it must set $helper->formula = TRUE and
   * implement handler->get_formula();.
   */
  public function get_field() {
    if (!empty($this->formula)) {
      return $this->handler
        ->get_formula();
    }
    else {
      return $this->handler->table_alias . '.' . $this->handler->real_field;
    }
  }

  /**
   * Add a table to the query.
   *
   * This is an advanced concept; not only does it add a new instance of the
   * table, but it follows the relationship path all the way down to the
   * relationship link point and adds *that* as a new relationship and then adds
   * the table to the relationship, if necessary.
   */
  public function add_table($join = NULL, $alias = NULL) {

    // This is used for lookups in the many_to_one table.
    $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
    if (empty($join)) {
      $join = $this
        ->get_join();
    }

    // See if there's a chain between us and the base relationship. If so, we
    // need to create a new relationship to use.
    $relationship = $this->handler->relationship;

    // Determine the primary table to seek.
    if (empty($this->handler->query->relationships[$relationship])) {
      $base_table = $this->handler->query->base_table;
    }
    else {
      $base_table = $this->handler->query->relationships[$relationship]['base'];
    }

    // Cycle through the joins. This isn't as error-safe as the normal
    // ensure_path logic. Perhaps it should be.
    $r_join = clone $join;
    while (!empty($r_join) && $r_join->left_table != $base_table) {
      $r_join = views_get_table_join($r_join->left_table, $base_table);
    }

    // If we found that there are tables in between, add the relationship.
    if ($r_join->table != $join->table) {
      $relationship = $this->handler->query
        ->add_relationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
    }

    // And now add our table, using the new relationship if one was used.
    $alias = $this->handler->query
      ->add_table($this->handler->table, $relationship, $join, $alias);

    // Store what values are used by this table chain so that other chains can
    // automatically discard those values.
    if (empty($this->handler->view->many_to_one_tables[$field])) {
      $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
    }
    else {
      $this->handler->view->many_to_one_tables[$field] = array_merge($this->handler->view->many_to_one_tables[$field], $this->handler->value);
    }
    return $alias;
  }

  /**
   * {@inheritdoc}
   */
  public function get_join() {
    return $this->handler
      ->get_join();
  }

  /**
   * Provide the proper join for summary queries.
   *
   * This is important in part because it will cooperate with other arguments if
   * possible.
   */
  public function summary_join() {
    $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
    $join = $this
      ->get_join();

    // Shortcuts.
    $options = $this->handler->options;
    $view =& $this->handler->view;
    $query =& $this->handler->query;
    if (!empty($options['require_value'])) {
      $join->type = 'INNER';
    }
    if (empty($options['add_table']) || empty($view->many_to_one_tables[$field])) {
      return $query
        ->ensure_table($this->handler->table, $this->handler->relationship, $join);
    }
    else {
      if (!empty($view->many_to_one_tables[$field])) {
        foreach ($view->many_to_one_tables[$field] as $value) {
          $join->extra = array(
            array(
              'field' => $this->handler->real_field,
              'operator' => '!=',
              'value' => $value,
              'numeric' => !empty($this->definition['numeric']),
            ),
          );
        }
      }
      return $this
        ->add_table($join);
    }
  }

  /**
   * Override ensure_my_table so we can control how this joins in.
   *
   * The operator actually has influence over joining.
   */
  public function ensure_my_table() {
    if (!isset($this->handler->table_alias)) {

      // Case 1: Operator is an 'or' and we're not reducing duplicates.
      // We hence get the absolute simplest.
      $field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
      if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
        if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {

          // Query optimization, INNER joins are slightly faster, so use them
          // when we know we can.
          $join = $this
            ->get_join();
          if (isset($join)) {
            $join->type = 'INNER';
          }
          $this->handler->table_alias = $this->handler->query
            ->ensure_table($this->handler->table, $this->handler->relationship, $join);
          $this->handler->view->many_to_one_tables[$field] = $this->handler->value;
        }
        else {
          $join = $this
            ->get_join();
          $join->type = 'LEFT';
          if (!empty($this->handler->view->many_to_one_tables[$field])) {
            foreach ($this->handler->view->many_to_one_tables[$field] as $value) {
              $join->extra = array(
                array(
                  'field' => $this->handler->real_field,
                  'operator' => '!=',
                  'value' => $value,
                  'numeric' => !empty($this->handler->definition['numeric']),
                ),
              );
            }
          }
          $this->handler->table_alias = $this
            ->add_table($join);
        }
        return $this->handler->table_alias;
      }

      // Case 2: it's anything but an 'or'.
      // We do one join per selected value.
      // Clone the join for each table:
      $this->handler->table_aliases = array();
      $values = $this->handler->operator === 'not' ? array(
        $this->handler->value,
      ) : $this->handler->value;
      foreach ($values as $value) {
        $join = $this
          ->get_join();
        if ($this->handler->operator == 'and') {
          $join->type = 'INNER';
        }
        if (empty($join->extra)) {
          $join->extra = array();
        }
        $join->extra[] = array(
          'field' => $this->handler->real_field,
          'value' => $value,
          'numeric' => !empty($this->handler->definition['numeric']),
        );
        if ($this->handler
          ->is_a_group() && is_array($value) || $this->handler->operator === 'not') {
          $value = serialize($value);
        }

        // The table alias needs to be unique to this value across the
        // multiple times the filter or argument is called by the view.
        if (!isset($this->handler->view->many_to_one_aliases[$field][$value])) {
          if (!isset($this->handler->view->many_to_one_count[$this->handler->table])) {
            $this->handler->view->many_to_one_count[$this->handler->table] = 0;
          }
          $this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . $this->handler->view->many_to_one_count[$this->handler->table]++;
          $alias = $this->handler->table_aliases[$value] = $this
            ->add_table($join, $this->handler->view->many_to_one_aliases[$field][$value]);

          // and set table_alias to the first of these.
          if (empty($this->handler->table_alias)) {
            $this->handler->table_alias = $alias;
          }
        }
        else {
          $this->handler->table_aliases[$value] = $this->handler->view->many_to_one_aliases[$field][$value];
        }
      }
    }
    return $this->handler->table_alias;
  }

  /**
   * Provides a unique placeholders for handlers.
   */
  public function placeholder() {
    return $this->handler->query
      ->placeholder($this->handler->options['table'] . '_' . $this->handler->options['field']);
  }

  /**
   *
   */
  public function add_filter() {
    if (empty($this->handler->value)) {
      return;
    }
    $this->handler
      ->ensure_my_table();

    // Shorten some variables.
    $field = $this
      ->get_field();
    $options = $this->handler->options;
    $operator = $this->handler->operator;
    $formula = !empty($this->formula);
    $value = $this->handler->value;
    if (empty($options['group'])) {
      $options['group'] = 0;
    }

    // Determine whether a single expression is enough(FALSE) or the conditions
    // should be added via an db_or()/db_and() (TRUE).
    $add_condition = TRUE;
    if ($operator == 'or' && empty($options['reduce_duplicates'])) {
      if (count($value) > 1) {
        $operator = 'IN';
      }
      else {
        $value = is_array($value) ? array_pop($value) : $value;
        if (is_array($value) && count($value) > 1) {
          $operator = 'IN';
        }
        else {
          $operator = '=';
        }
      }
      $add_condition = FALSE;
    }
    if (!$add_condition) {
      if ($formula) {
        $placeholder = $this
          ->placeholder();
        if ($operator == 'IN') {
          $operator = "{$operator} IN({$placeholder})";
        }
        else {
          $operator = "{$operator} {$placeholder}";
        }
        $placeholders = array(
          $placeholder => $value,
        ) + $this->placeholders;
        $this->handler->query
          ->add_where_expression($options['group'], "{$field} {$operator}", $placeholders);
      }
      else {
        $this->handler->query
          ->add_where($options['group'], $field, $value, $operator);
      }
    }
    if ($add_condition) {
      $field = $this->handler->real_field;
      $clause = $operator == 'or' ? db_or() : db_and();
      foreach ($this->handler->table_aliases as $value => $alias) {
        if ($operator == 'not') {
          $value = NULL;
        }
        $clause
          ->condition("{$alias}.{$field}", $value);
      }

      // Implode on either AND or OR.
      $this->handler->query
        ->add_where($options['group'], $clause);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
views_many_to_one_helper::$placeholders public property Contains possible existing placeholders used by the query.
views_many_to_one_helper::add_filter public function
views_many_to_one_helper::add_table public function Add a table to the query.
views_many_to_one_helper::ensure_my_table public function Override ensure_my_table so we can control how this joins in.
views_many_to_one_helper::get_field public function Provide an option to use a formula.
views_many_to_one_helper::get_join public function
views_many_to_one_helper::options_form public function
views_many_to_one_helper::option_definition static function
views_many_to_one_helper::placeholder public function Provides a unique placeholders for handlers.
views_many_to_one_helper::summary_join public function Provide the proper join for summary queries.
views_many_to_one_helper::__construct public function