You are here

views_argument_substitutions.module in Views Argument Substitutions 7

Hook definitions for Views Argument Substitutions.

Allow fields and contextual arguments in filters and table arguments.

File

views_argument_substitutions.module
View source
<?php

/**
 * @file
 * Hook definitions for Views Argument Substitutions.
 *
 * Allow fields and contextual arguments in filters and table arguments.
 */

/**
 * Implements hook_help().
 */
function views_argument_substitutions_help($path, $arg) {
  if ($path == 'admin/help#views_argument_substitutions') {
    $output = file_get_contents(drupal_get_path('module', 'views_argument_substitutions') . '/README.txt');
    return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>' . check_plain($output) . '</pre>';
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add a note about substitutions to the Value field on filter configuration
 * forms.
 */
function views_argument_substitutions_form_views_ui_config_item_form_alter(&$form, &$form_state, $form_id) {
  $has_value_field = !empty($form['options']) && !empty($form['options']['value']);
  if ($has_value_field) {
    $output = '';
    if (!empty($form_state['view'])) {
      $view = $form_state['view'];

      // Gather all fields that have been added to the view.
      $options = array();
      foreach ($view->display_handler
        ->get_handlers('field') as $field => $handler) {

        // Display the field's machine name as well as it's UI name.
        $display_name = method_exists($handler, 'ui_name') ? $handler
          ->ui_name() : t('Unknown');
        $options[t('Fields')]["***!{$field}***"] = $display_name;
        $options[t('Fields')]["***~{$field}***"] = t('REGEX any words in @display_name', array(
          '@display_name' => $display_name,
        ));
      }

      // Display all available fields in a nice list.
      foreach (array_keys($options) as $type) {
        if (!empty($options[$type])) {
          $items = array();
          foreach ($options[$type] as $key => $value) {
            $items[] = $key . ' == ' . check_plain($value);
          }
          try {
            $output .= theme('item_list', array(
              'items' => $items,
              'type' => $type,
            ));
          } catch (Exception $e) {
            watchdog('views_argument_substitutions', 'Exception thrown in theme("item_list"): @message', array(
              '@message' => $e
                ->getMessage(),
            ));
          }
        }
      }
    }
    $description = t('The Views Argument Substitutions module allows either field names or contextual arguments to be used for this value.');
    if (!empty($form['options']['value']['#description'])) {
      $description .= '<br />' . $form['options']['value']['#description'];
    }
    $form['options']['argument_substitutions'] = array(
      '#type' => 'fieldset',
      '#title' => t('Views Argument Substitutions'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#dependency' => array(
        'edit-options-alter-make-link' => array(
          1,
        ),
        'edit-options-alter-alter-text' => array(
          1,
        ),
        'edit-options-alter-more-link' => array(
          1,
        ),
      ),
      '#description' => $description,
    );
    $form['options']['argument_substitutions']['fields'] = array(
      '#type' => 'fieldset',
      '#title' => t('Replacement Patterns'),
      '#value' => $output,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#dependency' => array(
        'edit-options-alter-make-link' => array(
          1,
        ),
        'edit-options-alter-alter-text' => array(
          1,
        ),
        'edit-options-alter-more-link' => array(
          1,
        ),
      ),
      '#description' => t('For contextual arguments, you may insert "***%1***" to use the title or "***!1***" to use the raw input from the first contextual argument, "***!2***" for the raw input of the second argument, etc.  The special "***~1***" replacement is a regular expression/REGEX pattern matching <em>any</em> words (separated by whitespace) from the input. Arguments will be substituted as strings, which may yield unexpected results for "contains any word" or "contains all words" filters - you may wish to use a regex filter with the special REGEX replacement instead.<br/>If you would like to use a field name, please use one of the following:'),
    );
  }
}

/**
 * Implements hook_views_query_substitutions().
 *
 * Provide contextual argument substitutions for usage in filters and table
 * arguments.
 *
 * @see views_query_views_alter()
 * @see _views_query_tag_alter_condition()
 * @see SelectQuery::arguments()
 */
function views_argument_substitutions_views_query_substitutions($obj) {

  // This hook is invoked with different classes as the $obj argument
  // (views_plugin_query_default and view). We will translate that to a
  // consistent view object we can work with.
  // @see views_plugin_query_default
  // @see view
  $view = !empty($obj->view) ? $obj->view : $obj;
  $query_substitutions = array();

  // Check if the Contextual Arguments ($view->argument) and exposed Filters
  // ($view->exposed_raw_input) are empty, in which case bail early.
  $all_arguments_empty = empty($view->argument) && empty($view->exposed_raw_input);
  if (!is_object($view) || $all_arguments_empty || empty($view->build_info)) {
    return $query_substitutions;
  }

  // Process any Contextual Arguments.
  if (!empty($view->argument)) {

    // We use the build_info['substitutions'] array because there is a ton of
    // logic in _build_arguments() that pieces together the title and value
    // based on present values, default settings, and exceptions.  Instead of
    // duplicating that logic, we'll try to use the values already determined.
    // @see view::_build_arguments()
    // @see views.inc
    $view_substitutions =& $view->build_info['substitutions'];

    // Sometimes there are more contextual filters on the View than there are
    // arguments actually supplied. We fill in the rest with NULL to let the
    // default filter behavior (generally, match all) work.
    foreach ($view->argument as $name => $arg) {
      $position = $arg->position + 1;

      // If an argument isn't actually supplied, its $value may be "all" (or a
      // custom exception value if one has been specified), or NULL. In that
      // case, we set $value and $title to NULL.
      $is_exception = method_exists($arg, 'is_exception') && $arg
        ->is_exception();
      $is_null = method_exists($arg, 'get_value') && $arg
        ->get_value() === NULL;
      $empty_arg = $is_exception || $is_null;
      $title = NULL;
      $value = NULL;
      $values = NULL;
      if (!$empty_arg) {
        if (!empty($view_substitutions["%{$position}"])) {
          $title = db_like($view_substitutions["%{$position}"]);
        }
        if (!empty($view_substitutions["!{$position}"])) {
          $value = db_like($view_substitutions["!{$position}"]);
          $values = preg_split('/\\s+/', trim($view_substitutions["!{$position}"]) . '/', 0, PREG_SPLIT_NO_EMPTY);
        }
      }

      // Always add the substitutions, even if they're NULL.  Without this, any
      // queries referring to an argument that wasn't supplied will never work.
      $query_substitutions["***\\%{$position}***"] = $title;
      $query_substitutions["***!{$position}***"] = $value;
      $query_substitutions["***~{$position}***"] = NULL;

      // Add any OR queries. Patch this via REGEX (because
      // hook_views_query_substitutions() can't return arrays/objects - we need
      // to fake db_and()/db_or()).
      if (!empty($values)) {
        $regex_values = array_map('preg_quote', $values, array_fill(0, count($values), '/'));
        $regex_any = '/' . implode('|', $regex_values) . '/i';
        $query_substitutions["***~{$position}***"] = $regex_any;
      }
    }
    unset($view_substitutions);
  }

  // Process any exposed Filters.
  if (!empty($view->exposed_raw_input)) {
    foreach ($view->exposed_raw_input as $field_name => $input) {
      $value = NULL;
      $values = NULL;
      if (!empty($input)) {
        if (is_array($input)) {
          if (isset($input['value'])) {
            $value = db_like($input['value']);
            $values = preg_split('/\\s+/', trim($input['value']) . '/', 0, PREG_SPLIT_NO_EMPTY);
          }
        }
        else {
          $value = db_like($input);
          $values = preg_split('/\\s+/', trim($input) . '/', 0, PREG_SPLIT_NO_EMPTY);
        }
      }
      $field_name = db_like($field_name);
      $query_substitutions["***!{$field_name}***"] = $value;
      $query_substitutions["***~{$field_name}***"] = NULL;

      // Add any OR queries. Patch this via REGEX (because
      // hook_views_query_substitutions() can't return arrays/objects - we need
      // to fake db_and()/db_or()).
      if (!empty($values)) {
        $regex_values = array_map('preg_quote', $values, array_fill(0, count($values), '/'));
        $regex_any = '/' . implode('|', $regex_values) . '/i';
        $query_substitutions["***~{$field_name}***"] = $regex_any;
      }
    }
  }
  return $query_substitutions;
}