You are here

protected function views_handler_filter_selective::get_oids in Views Selective Filters 7

Get list of options for current view, only at runtime.

1 call to views_handler_filter_selective::get_oids()
views_handler_filter_selective::get_value_options in ./views_handler_filter_selective.inc
Child classes should be used to override this function and set the 'value options', unless 'options callback' is defined as a valid function or static public method to generate these values.

File

./views_handler_filter_selective.inc, line 265
Views Filter Selective Handler Overrides.

Class

views_handler_filter_selective
Views filter handler for selective values.

Code

protected function get_oids() {

  // Parameters that we will be using during the process.
  $base_field = $this->definition['field_base'];
  $ui_name = $this->options['ui_name'];
  $signature = $this
    ->getSignature();

  // Prevent same filters from being recalculated.
  if (empty(self::$results[$signature])) {

    // We don't want a badly configured selective filter
    // to return thousands of possible values.
    $max_items = (int) $this->options['selective_items_limit'];

    // Clone the view (so it works while editting) and get all results.
    $view_copy = $this->view
      ->clone_view();
    if (!$view_copy) {
      return NULL;
    }

    // Store a flag so that we can know from other places
    // that this view is being used to obtain selective data.
    $view_copy->selective_oids = TRUE;

    // Store information about what filter is this view being used for.
    $view_copy->selective_handler_signature = $signature;

    // If this filter is configured to *not* continue to filter the options
    // list as new exposed values are applied, make sure to explicitly set any
    // exposed input values to null and remove contextual filters (args).
    if (isset($this->options['selective_options_ignore_exposed_data']) && $this->options['selective_options_ignore_exposed_data'] === 1) {
      if (!empty($this->view->exposed_input) && is_array($this->view->exposed_input)) {
        $new_exposed_input = array();
        foreach ($this->view->exposed_input as $key => $val) {
          $new_exposed_input[$key] = NULL;
        }
        $view_copy
          ->set_exposed_input($new_exposed_input);
      }
    }
    else {
      $view_copy
        ->set_exposed_input($this->view->exposed_input);
    }

    // Transfer contextual information to cloned view.
    $view_copy
      ->set_arguments($this->view->args);

    // Mess up with the field used for distinct have thousands of elements.
    // Limit result set to 100: anything above is not user friendly at all.
    $view_copy
      ->set_items_per_page($max_items);

    // Remove paging, and page number from context.
    if (isset($_GET['items_per_page'])) {
      $items_per_page = $_GET['items_per_page'];
      unset($_GET['items_per_page']);
    }
    if (isset($_GET['page'])) {
      $exposed_page = $_GET['page'];
      unset($_GET['page']);
    }

    // Manipulate display + default: don't know if fields are overriden.
    $display = $view_copy->display[$this->view->current_display];
    $display_default = $view_copy->display['default'];

    // Initialize the current display handler.
    $display->handler = views_get_plugin('display', $view_copy->display[$this->view->current_display]->display_plugin);
    $display_default->handler =& $display->handler;

    // Remove any exposed form configuration. This showed up with BEF module!
    unset($display->display_options['exposed_form']);
    unset($display_default->display_options['exposed_form']);

    // Also disable attachments.
    $display->handler->definition['accept attachments'] = FALSE;
    $display_default->handler->definition['accept attachments'] = FALSE;

    // If we are using fields from default or current display.
    if (isset($display->display_options['fields'])) {
      $display_options_fields =& $display->display_options['fields'];
    }
    else {
      $display_options_fields =& $display_default->display_options['fields'];
    }

    // Original implementation based field matching on ui_name matches
    // so we need to preserve backwards compatibility.
    $field_to_keep = $this->options['selective_display_field'];
    if (empty($field_to_keep)) {
      foreach ($display_options_fields as $key => $value) {
        if (isset($value['ui_name']) && $value['ui_name'] == $ui_name) {
          $field_to_keep = $key;
          break;
        }
      }
    }

    // Remove all fields but the one used to display and aggregate.
    foreach ($display_options_fields as $key => $value) {
      if ($key != $field_to_keep) {
        unset($display_options_fields[$key]);
      }
      else {

        // If there is a group column on the field, remove it so
        // Field Collections will work.
        // https://www.drupal.org/node/2333065
        unset($display_options_fields[$key]['group_column']);
      }
    }

    // Check to see if the user remembered to add the field.
    if (empty($display_options_fields)) {
      drupal_set_message(t('Selective query filter must have corresponding field added to view with Administrative Name set to "@name" and Base Type "@type"', array(
        '@name' => $ui_name,
        '@type' => $base_field,
      )), 'error');
      return array();
    }

    // Get ID of field that will be used for rendering.
    $display_field = reset($display_options_fields);

    // Get field Id.
    $display_field_id = $display_field['id'];

    // Check that relationships are coherent between Field and Filter.
    $no_display_field_relationship = empty($display_field['relationship']) || $display_field['relationship'] === 'none';
    $no_filter_relationship = empty($this->options['relationship']) || $this->options['relationship'] === 'none';
    $equal = $no_display_field_relationship === TRUE && $no_filter_relationship === TRUE || $display_field['relationship'] === $this->options['relationship'];
    if (!$equal) {
      drupal_set_message(t('Selective filter "@name": relationship of field and filter must match.', array(
        '@name' => $ui_name,
        '@type' => $base_field,
      )), 'error');
      return array();
    }

    // If main field is excluded from presentation, bring it back.
    // Set group type for handler to populate database relationships in query.
    $display_field['exclude'] = 0;
    $display_field['group_type'] = 'group';

    // Remove all sorting: sorts must be added to aggregate fields.
    unset($display->display_options['sorts']);
    unset($display_default->display_options['sorts']);

    // Turn this into an aggregate query.
    $display->display_options['group_by'] = 1;
    $display->handler->options['group_by'] = 1;
    $display_default->display_options['group_by'] = 1;
    $display_default->handler->options['group_by'] = 1;

    // Aggregate is incompatible with distinct and pure distinct.
    // At least it does not make sense as it is implemented now.
    unset($display_default->display_options['query']['options']['distinct']);
    unset($display_default->display_options['query']['options']['pure_distinct']);
    unset($display->display_options['query']['options']['distinct']);
    unset($display->display_options['query']['options']['pure_distinct']);

    // Make sure we are not using a pager to prevent unnecessary count(*)
    // queries.
    $display->display_options['pager'] = unserialize('a:2:{s:4:"type";s:4:"none";s:7:"options";a:1:{s:6:"offset";s:1:"0";}}');
    $display_default->display_options['pager'] = unserialize('a:2:{s:4:"type";s:4:"none";s:7:"options";a:1:{s:6:"offset";s:1:"0";}}');

    // Some style plugins can affect the built query, make sure
    // we use a reliable field based style plugin.
    $display->display_options['style_plugin'] = 'default';
    $display->display_options['style_options'] = unserialize('a:4:{s:9:"row_class";s:0:"";s:17:"default_row_class";i:1;s:17:"row_class_special";i:1;s:11:"uses_fields";i:0;}');
    $display->display_options['row_plugin'] = 'fields';
    $display->display_options['row_options'] = unserialize('s:6:"fields";');

    // Run View.
    $view_copy
      ->execute($this->view->current_display);

    // Restore context parameters for real View.
    if (isset($items_per_page)) {
      $_GET['items_per_page'] = $items_per_page;
    }
    if (isset($exposed_page)) {
      $_GET['page'] = $exposed_page;
    }

    // Get Handler after execution.
    $display_field_handler = $view_copy->field[$display_field_id];

    // We show human-readable values when case.
    if (method_exists($display_field_handler, 'get_value_options')) {
      $display_field_handler
        ->get_value_options();
    }

    // Create array of objects for selector.
    $oids = array();
    $field_alias_original = isset($display_field_handler->aliases[$display_field_handler->real_field]) ? $display_field_handler->aliases[$display_field_handler->real_field] : $display_field_handler->table_alias . '_' . $display_field_handler->real_field;

    // views_plugin_query_default::add_field() truncates aliases to 60
    // characters.
    $field_alias = substr($field_alias_original, 0, 60);
    foreach ($view_copy->result as $index => $row) {

      // $key = $display_field_handler->get_value($row) should be more robust
      // but values are sometimes nested arrays, and we need scalar values
      // for the filters.
      $key = $display_field_handler
        ->get_value($row);
      if (!is_scalar($key)) {
        $key = $row->{$field_alias};
      }
      $value = strip_tags($view_copy
        ->render_field($display_field_id, $index));
      $oids[$key] = empty($value) ? t('Empty (@key)', array(
        '@key' => empty($key) ? json_encode($key) : $key,
      )) : $value;
    }

    // Sort values.
    $sort_option = $this->options['selective_display_sort'];
    switch ($sort_option) {
      case 'ASC':
        asort($oids);
        break;
      case 'DESC':
        arsort($oids);
        break;
      case 'KASC':
        ksort($oids);
        break;
      case 'KDESC':
        krsort($oids);
        break;
      case 'ORIG':
        $oids = self::filterOriginalOptions($this
          ->getOriginalOptions(), array_keys($oids));
        break;
      case 'NONE':
        break;
      default:
        asort($oids);
    }
    drupal_alter('views_filters_selective_sort', $oids, $this);

    // If limit exceeded this field is not good for being "selective".
    if (!empty($max_items) && count($oids) == $max_items) {
      drupal_set_message(t('Selective filter "@field" has limited the amount of total results. Please, review you query configuration.', array(
        '@field' => $ui_name,
      )), 'warning');
    }
    self::$results[$signature] = $oids;
    $view_copy
      ->destroy();
  }
  return self::$results[$signature];
}