You are here

build.inc in Finder 7.2

The finder build functions.

File

includes/build.inc
View source
<?php

/**
 * @file
 * The finder build functions.
 */

// @todo: Remove this once update.php can use the registry [#1611106]

//views_include('base');

/**
 * Get the raw link to an object given the table and id.
 *
 * @param $table
 *   Base table for this type of object.
 * @param $id
 *   The value of the primary key for this record.
 * @return
 *   A raw path that can be put into url() or l() that can be used to link to
 *   or redirect to the object.
 */
function finder_path($table, $id) {
  switch ($table) {
    case 'node':
      $path = 'node/' . $id;
      break;
    case 'users':
      $path = 'user/' . $id;
      break;
    case 'taxonomy_term_data':
      $path = 'taxonomy/term/' . $id;
      break;
    case 'node_revisions':
      $revision = node_load(NULL, $id);
      $path = 'node/' . $revision->nid . '/revisions/' . $id . '/view';
      break;
    case 'files':
      $path = file_load($id)->uri;
      break;
  }
  drupal_alter('finder_path', $path, $table, $id);
  return $path;
}

/**
 * Convert finder arguments text field entry to an array of arguments.
 *
 * @param $string
 *   The typed string of arguments, can include PHP code.
 * @param $variables
 *   Any variables that should be available to any PHP provided in the input.
 * @return
 *   The array of views arguments.
 */
function finder_get_args($string, $variables) {
  $args = array();
  $arguments = finder_eval($string, $variables);
  if ($arguments) {
    $args = explode('/', $arguments);
    foreach ($args as $k => $v) {
      $args[$k] = trim($v);
    }
  }
  return $args;
}

/**
 * Does stuff during the display handler's query.
 *
 * @param &$display_handler
 *  A reference to the views display handler object.
 */
function finder_build_display_query(&$display_handler) {
  $finder = $display_handler
    ->get_option('finder');
  foreach (array_keys($finder->find) as $key) {
    ${$key} =& $finder->find[$key];
  }
  if ($mode == 'choices') {

    // We set distinct to reduce results sets somewhat if possible.
    $display_handler->view->query
      ->set_distinct(TRUE, TRUE);
  }
  if (!empty($keywords)) {
    $placeholder_count = 0;

    // Create an array to track wheres, we'll add them to views later.
    $wheres = array();

    // Create a where group called 'finder', for element combination.
    $element_combination = $finder
      ->setting('element_logic');
    $display_handler->view->query
      ->set_where_group($element_combination, 'finder');

    // @todo create a where group for each container element
    if ($mode == 'choices') {
      $display_handler->view->query
        ->set_where_group('OR', 'finder_choices');
    }
    foreach ($keywords as $eid => $keyword_array) {
      $finder_element =& $finder->elements[$eid];
      $element_fields = $finder
        ->esetting($finder_element, 'fields');
      if ($mode == 'results') {

        // For choices these have to be passed in manually through $finder->find, but for results we get them like this:
        $field_logic = $finder
          ->esetting($finder_element, 'field_logic');
        $value_logic = $finder
          ->esetting($finder_element, 'value_logic');
        $nesting_order = $finder
          ->esetting($finder_element, 'nesting_order');

        //$match = $finder->esetting($finder_element, 'match');
      }
      if (!empty($keyword_array)) {
        $clauses = array();
        $clause_args = array();
        foreach ($element_fields as $key => $field) {
          $field_info[$eid][$key] = $field;
          foreach (array_values($keyword_array) as $keyword_position => $keyword) {

            // Get the info we need to add the table/field.
            $delta = $value_logic == 'AND' && count($keyword_array) > 1 ? $keyword_position : 0;
            $field->table_alias[$delta] = finder_alias('table', $eid, $field->table, $field->field, $delta);
            $field->field_alias[$delta] = finder_alias('field', $eid, $field->table, $field->field, $delta);
            $relationship = NULL;
            $join = NULL;
            if (!empty($field->relationship)) {
              $relationship = $display_handler->view->relationship[$field->relationship]->alias;
            }

            // This chunk of code will fix the join directly to the left.  Should we actually be recursing through all the left tables right back to the base table?
            if (empty($relationship)) {
              $relationship = $display_handler->view->query->base_table;
            }
            $join = $display_handler->view->query
              ->get_join_data($field->table, $display_handler->view->query->relationships[$relationship]['base']);
            if (!empty($join)) {
              $join = $display_handler->view->query
                ->adjust_join($join, $relationship);
            }
            if (!empty($join->left_table) && $join->left_table != $display_handler->view->query->base_table) {

              // Check for the long-chain join case that we probably screwed up, and plead for assistance.
              $left_table_join_data = $display_handler->view->query
                ->get_join_data($join->left_table, $display_handler->view->query->relationships[$relationship]['base']);
              if (!empty($left_table_join_data->left_table) && $left_table_join_data->left_table != $display_handler->view->query->base_table && user_access('administer finder')) {

                // Purposefully not enclosed in t() because this is not a typical UI string.
                drupal_set_message("Views join configuration not supported by finder.  Please post in the <a href=\"http://drupal.org/node/add/project-issue/finder\">finder issue queue</a> and attach an export of your finder, or explain what fields you are using.  We need your help to solve this problem.", 'error');
              }

              // Table name supplied here is a combo of the table joined here, and the table we ultimately want to join.
              $join_alias = finder_alias('table', $eid, $field->table . "_" . $join->left_table, $field->field, $delta);
              $join_alias = $display_handler->view->query
                ->add_table($join->left_table, NULL, NULL, $join_alias);
              $join->left_table = $join_alias;
            }
            $join = $join ? $join : $display_handler->view->query
              ->get_join_data($field->table_alias[$delta], $display_handler->view->base_table);
            $table = isset($join->table) ? $join->table : $field->table;
            if ($table == $display_handler->view->base_table) {

              // Don't alias base table here.
              // @todo: rather than 'undoing' stuff here... don't do it in the first place.
              $field->table_alias[$delta] = $display_handler->view->base_table;
            }
            $display_handler->view->query
              ->add_table($field->table, $relationship, $join, $field->table_alias[$delta]);

            // Select the field.
            if ($mode == 'results' && $finder
              ->setting('results_style') == 'custom' || $mode == 'choices' && $eid == $element->id) {

              // If we're doing a custom results page or a choices list, grab the field value, and store the alias.
              $field->field_alias[$delta] = $display_handler->view->query
                ->add_field($field->table_alias[$delta], $field->field, $field->field_alias[$delta]);

              // This if-statement also assumes that $eid == $element->id, but it is not needed to specify this because of the outer if-statement.
              if ($mode == 'choices') {
                $groups[] = $field->field_alias[$delta];
                $wheres['finder_choices'][$eid][] = array(
                  'snippet' => $field->table_alias[$delta] . '.' . $field->field . ' IS NOT NULL',
                  'args' => array(),
                );
              }
            }

            // Add the field to where clauses.
            if ($keyword !== '' && $keyword !== NULL) {
              list($field_name, $value, $op) = array_values((array) $finder
                ->match_args($field->table_alias[$delta] . '.' . $field->field, $keyword, $finder->find['matches'][$eid]['match'], $finder->find['matches'][$eid]['match_x']));
              $outer_key = $nesting_order ? $key : $keyword_position;
              $placeholder = ':finder_keyword_' . $placeholder_count++;
              $expression = $field_name . ' ' . $op . ' ' . $placeholder;
              $clauses[$outer_key][] = $expression;
              $clause_args[$outer_key][$placeholder] = $value;

              // Using same conditions as for when we add a field, because there's no point in adding the match field if not selecting the field.
              if ($mode == 'results' && $finder
                ->setting('results_style') == 'custom' || $mode == 'choices' && $eid == $element->id) {
                $field->field_matched[$delta] = $field->field_alias[$delta] . '_matched';
                $display_handler->view->query
                  ->add_field(NULL, $expression, $field->field_matched[$delta]);
              }
            }
          }
        }

        // Convert where clauses into sql strings and add to view with add_where().
        if (!empty($clauses)) {
          $inner_operator = $nesting_order ? $value_logic : $field_logic;
          $outer_operator = $nesting_order ? $field_logic : $value_logic;
          $inner_clauses = array();
          $inner_clause_args = array();
          foreach ($clauses as $clause) {
            if (!empty($clause)) {
              $inner_clauses[] = finder_implode_wheres($inner_operator, $clause);
            }
          }
          foreach ($clause_args as $clause_arg) {
            foreach ($clause_arg as $inner_clause_arg_key => $inner_clause_arg) {
              $inner_clause_args[$inner_clause_arg_key] = $inner_clause_arg;
            }
          }
          $wheres['finder'][$eid][] = array(
            'snippet' => finder_implode_wheres($outer_operator, $inner_clauses),
            'args' => $inner_clause_args,
          );
        }
      }
    }
    foreach ($wheres as $wheres_group_name => $wheres_group) {
      $formatted_wheres = finder_format_wheres($finder, $finder
        ->root_elements(), $wheres_group);
      foreach ($formatted_wheres as $formatted_where) {

        // ditching empty snippets now falls to finder_format_wheres().

        //if (trim($formatted_where['snippet'])) {
        $display_handler->view->query
          ->add_where_expression($wheres_group_name, $formatted_where['snippet'], $formatted_where['args']);

        //}
      }
    }
  }

  // Add groups.
  foreach ($groups as $group) {
    $display_handler->view->query
      ->add_groupby($group);
  }
}

/**
 * Format the wheres.
 */
function finder_format_wheres($finder, $element_ids, $wheres) {
  $formatted_wheres = array();
  foreach ($element_ids as $element_id) {
    $element = $finder->elements[$element_id];
    $children = $finder
      ->element_children($element);
    if (!empty($children)) {
      $operator = $element->settings['element_logic'];
      $child_wheres = finder_format_wheres($finder, $children, $wheres);
      $child_where_snippets = array();
      $child_where_args = array();
      foreach ($child_wheres as $child_where) {
        if (!empty($child_where['snippet'])) {
          $child_where_snippets[] = $child_where['snippet'];
        }
        foreach ($child_where['args'] as $child_where_arg_key => $child_where_arg_val) {
          $child_where_args[$child_where_arg_key] = $child_where_arg_val;
        }
      }

      //$formatted_wheres[] = array(

      //  'snippet' => !empty($child_where_snippets) ? finder_implode_wheres($operator, $child_where_snippets) : '',
      //  'args' => $child_where_args,

      //);
      if (!empty($child_where_snippets)) {
        $formatted_wheres[] = array(
          'snippet' => finder_implode_wheres($operator, $child_where_snippets),
          'args' => $child_where_args,
        );
      }
    }
    if (!empty($wheres[$element_id])) {
      foreach ($wheres[$element_id] as $where) {
        $formatted_wheres[] = $where;
      }
    }
  }
  return $formatted_wheres;
}

/**
 * Implode some where clauses.
 */
function finder_implode_wheres($operator, $wheres) {
  $prefix = '';
  $suffix = '';
  if (count($wheres) > 1) {
    $prefix = '(';
    $suffix = ')';
  }
  return $prefix . implode(' ' . $operator . ' ', $wheres) . $suffix;
}

/**
 * Does stuff during the style plugin's render.
 *
 * @param &$style_plugin
 *  A reference to the views style plugin object.
 */
function finder_build_style_render(&$style_plugin) {
  $results = array();

  // Group the rows according to the grouping field, if specified.
  $sets = $style_plugin
    ->render_grouping($style_plugin->view->result, $style_plugin->options['grouping']);
  $finder = $style_plugin->display->display_options['finder'];
  foreach (array_keys($finder->find) as $key) {
    ${$key} =& $finder->find[$key];
  }
  $style_plugin->view->row_index = 0;
  foreach ($sets as $title => $records) {
    foreach ($records as $label => $row) {
      if ($mode == 'choices') {
        foreach ($field_info[$element->id] as $key => $field) {
          $format = isset($field->format) ? $field->format : 'filter_xss';
          foreach (array_keys($field->field_alias) as $delta) {

            // If there is no match field, or if there is and it's set.
            if (!isset($field->field_matched[$delta]) || !empty($field->field_matched[$delta]) && !empty($row->{$field->field_matched[$delta]})) {
              if (!empty($row->{$field->field_alias[$delta]}) && !isset($results[$row->{$field->field_alias[$delta]}])) {
                $value = $row->{$field->field_alias[$delta]};
                if ($finder
                  ->esetting($element, 'choice_display', 'field') == 'rendered') {
                  $display = $style_plugin->row_plugin
                    ->render($row);
                }
                else {
                  $display = $row->{$field->field_alias[$delta]};
                }
                if ($finder
                  ->esetting($element, 'choices_rewrite')) {
                  $variables = array(
                    'element' => $element,
                    'value' => $value,
                    'display' => $display,
                    'row' => $row,
                    'field' => $field,
                    'delta' => $delta,
                  );
                  $display = finder_eval($finder
                    ->esetting($element, 'choices_rewrite'), $variables);
                }
                if ($finder
                  ->esetting($element, 'value_rewrite')) {
                  $variables = array(
                    'element' => $element,
                    'value' => $value,
                    'display' => $display,
                    'row' => $row,
                    'field' => $field,
                    'delta' => $delta,
                  );
                  $value = finder_eval($finder
                    ->esetting($element, 'value_rewrite'), $variables);
                }
                $results[$value] = finder_sanitize($display, $format);
              }
            }
          }
        }
      }
      else {
        if ($finder
          ->setting('results_style') == 'views' || $finder
          ->setting('results_style') == 'custom' && $finder
          ->setting('custom_results_style_render')) {
          $row->render = $style_plugin->row_plugin
            ->render($row);

          // This is code to get rid of extra whitespace, could be useful for some future feature?

          //$row->render = preg_replace('/\s+/', ' ', trim($row->render));
          $row->display_key = 'render';
        }
        $results[] = $row;
      }
      $style_plugin->view->row_index++;
    }
  }
  unset($style_plugin->view->row_index);
  if ($mode == 'results' && empty($results)) {
    return $style_plugin->view->display_handler
      ->render_empty();
  }
  elseif ($mode == 'choices' || $mode == 'results' && $finder
    ->setting('results_style') == 'custom' && !$finder
    ->setting('custom_results_style_render')) {
    return $results;
  }
  else {
    return "[%FINDER_RESULTS%]";
  }
}

/**
 * Evaluate a string of PHP code.
 *
 * This is a wrapper around PHP's eval(). It uses output buffering to capture
 * both returned value and printed text. Allows to use variables with the given
 * code.
 * Using this wrapper also ensures that the PHP code which is evaluated can not
 * overwrite any variables in the calling code, unlike a regular eval() call.
 * In other words, we evaluate the code with independent variable scope.
 *
 * @param $code
 *   The code to evaluate.
 * @param $variables
 *   Variables to import to local variable scope.
 * @return
 *   A string containing the printed output of the code, followed by the
 *   returned output of the code.
 */
function finder_eval($code, $variables = array()) {
  global $theme_path, $theme_info, $conf;

  // Store current theme path.
  $old_theme_path = $theme_path;

  // Restore theme_path to the theme, as long as drupal_eval() executes,
  // so code evaluted will not see the caller module as the current theme.
  // If theme info is not initialized get the path from theme_default.
  if (!isset($theme_info)) {
    $theme_path = drupal_get_path('theme', $conf['theme_default']);
  }
  else {
    $theme_path = dirname($theme_info->filename);
  }
  extract($variables);
  ob_start();
  print eval('?>' . $code);
  $output = ob_get_contents();
  ob_end_clean();

  // Recover original theme path.
  $theme_path = $old_theme_path;
  return $output;
}

/**
 * Build aliases in consistent manner.
 *
 * Only the $type and $eid are required, but you can supply as many params as
 * is needed, as long as your usage is consistent between places where it needs
 * to be.
 *
 * @param $type
 *  The type of alias ('table', or 'field').
 * @param $eid
 *  The finder element id.
 * @param $table
 *  The raw table name.
 * @param $field
 *  The raw field name.
 * @param $delta
 *  A meaningful extra value.
 * @return
 *  The alias string.
 */
function finder_alias($type, $eid, $table = NULL, $field = NULL, $delta = NULL) {
  static $aliases = array();
  static $alias_count = array();
  if (!isset($alias_count[$type])) {
    $alias_count[$type] = 0;
  }
  $alias =& $aliases[$type][$eid][$table][$field][$delta];
  if (empty($alias)) {
    $alias = ++$alias_count[$type];
  }
  return 'finder_' . $type . '_' . $alias;
}

/**
 * Sanitize a finder choice.
 *
 * @param $option
 *  The option object with unsanitized display field.
 * @param $filter
 *  The filter to use.
 * @return
 *  The option object with sanitized display field.
 */
function finder_sanitize($option, $filter = 'filter_xss') {
  switch ($filter) {
    case 'filter_xss':
    case 'filter_xss_admin':
    case 'check_plain':
    case 'check_url':
    case 'strip_tags':
      $option = $filter($option);
      break;
    default:
      $option = check_markup($option, $filter, FALSE);
  }
  return $option;
}

Functions

Namesort descending Description
finder_alias Build aliases in consistent manner.
finder_build_display_query Does stuff during the display handler's query.
finder_build_style_render Does stuff during the style plugin's render.
finder_eval Evaluate a string of PHP code.
finder_format_wheres Format the wheres.
finder_get_args Convert finder arguments text field entry to an array of arguments.
finder_implode_wheres Implode some where clauses.
finder_path Get the raw link to an object given the table and id.
finder_sanitize Sanitize a finder choice.