You are here

finder.module in Finder 7

Same filename and directory in other branches
  1. 6 finder.module
  2. 7.2 finder.module

The finder module.

Finder allows Drupal site administrators to create flexible search forms to find objects such as Nodes or Users based on the values of a field.

File

finder.module
View source
<?php

// $Id: finder.module,v 1.7.2.58 2011/02/12 06:55:18 danielb Exp $

/**
 * @file
 * The finder module.
 *
 * Finder allows Drupal site administrators to create flexible search forms to
 * find objects such as Nodes or Users based on the values of a field.
 */

/**
 * Implements hook_menu().
 *
 * @see hook_menu()
 */
function finder_menu() {
  $items = array();
  $items['finder_ajax'] = array(
    'title' => 'Finder Ajax',
    'page callback' => 'finder_ajax',
    'access arguments' => array(
      'use finder',
    ),
    'type' => MENU_CALLBACK,
  );
  $finders = finder_load_multiple(NULL, array(), TRUE);
  if (is_array($finders)) {
    foreach ($finders as $finder) {
      $items[$finder->path] = array(
        'title' => $finder->title,
        'page callback' => 'finder_page',
        'page arguments' => array(
          $finder->finder_id,
        ),
        'access arguments' => array(
          'use finder',
        ),
        'type' => MENU_CALLBACK,
        'description' => $finder->description,
      );
    }
  }
  $admin_item = array(
    'file' => 'finder.admin.inc',
    'file path' => finder_inc_path(),
  );
  $items['admin/structure/finder'] = $admin_item + array(
    'title' => 'Finder',
    'page callback' => 'finder_admin_list',
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 0,
    'type' => MENU_NORMAL_ITEM,
    'description' => 'Finders are configurable forms to allow users to find objects in the system.',
  );
  $items['admin/structure/finder/list'] = $admin_item + array(
    'title' => 'Finder list',
    'weight' => 1,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/structure/finder/import'] = $admin_item + array(
    'title' => 'Import finder',
    'page callback' => 'finder_admin_import',
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 2,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/structure/finder/%finder'] = $admin_item + array(
    'title' => 'Edit finder',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'finder_admin_edit',
      3,
    ),
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 3,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/structure/finder/%finder/edit'] = $admin_item + array(
    'title' => 'Finder settings',
    'weight' => 3,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/structure/finder/%finder/delete'] = $admin_item + array(
    'title' => 'Delete finder',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'finder_admin_delete',
      3,
    ),
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 4,
    'type' => MENU_CALLBACK,
  );
  $items['admin/structure/finder/%finder/export'] = $admin_item + array(
    'title' => 'Export finder',
    'page callback' => 'finder_admin_export',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/structure/finder/%finder/%finder_arg_optional'] = $admin_item + array(
    'title' => 'Finder element settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'finder_admin_element_edit',
      3,
      4,
    ),
    'access arguments' => array(
      4,
    ),
    'access callback' => 'finder_menu_element_allowed',
    'weight' => 6,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/structure/finder/%finder/%finder_arg_optional/delete'] = $admin_item + array(
    'title' => 'Delete element',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'finder_admin_element_delete',
      3,
      4,
    ),
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 7,
    'type' => MENU_CALLBACK,
  );
  $items['admin/structure/finder/custom_matching'] = $admin_item + array(
    'title' => 'Finder custom matching',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'finder_admin_custom_matching',
    ),
    'access arguments' => array(
      'administer finder',
    ),
    'weight' => 8,
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * A to_arg() function is used to provide a default for the arg in the
 * menu wildcard.
 *
 * @param $arg
 *   The arg (URL fragment) to be tested.
 */
function finder_arg_optional_to_arg($arg) {
  return empty($arg) ? '' : $arg;
}

/**
 * Menu item access callback to hide the 'Finder element settings' tab when it
 * shouldn't be shown (when the element ID arg isn't present).
 *
 * @param $arg
 *   The arg (URL fragment) to be tested.
 */
function finder_menu_element_allowed($arg) {
  return !empty($arg) && user_access('administer finder');
}

/**
 * Implements hook_permission().
 *
 * @see hook_permission()
 */
function finder_permission() {
  return array(
    'administer finder' => array(
      'title' => t('Administer finder'),
      'description' => t('Create and edit finders.'),
    ),
    'administer finder PHP settings' => array(
      'title' => t('Administer finder PHP settings'),
      'description' => t('Allow access to PHP settings when administering finders.'),
    ),
    'use finder' => array(
      'title' => t('Use finder'),
      'description' => t('Use finder forms.'),
    ),
  );
}

/**
 * Implements hook_theme().
 *
 * @see hook_theme()
 */
function finder_theme() {
  return array(
    'finder_admin_edit_elements_table' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'form' => NULL,
      ),
    ),
    'finder_admin_links' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'finder' => NULL,
      ),
    ),
    'finder_links' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'finder' => NULL,
      ),
    ),
    'finder_page' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'finder' => NULL,
      ),
    ),
    'finder_block' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'finder' => NULL,
      ),
    ),
    'finder_view' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'finder' => NULL,
        'display' => NULL,
        'output_array' => NULL,
      ),
    ),
    'finder_results' => array(
      'file' => 'finder.theme.inc',
      'path' => finder_inc_path(),
      'variables' => array(
        'results' => NULL,
        'finder' => NULL,
        'keywords' => NULL,
        'pager' => NULL,
        'params' => NULL,
        'form_state' => NULL,
        'no_results' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_forms().
 *
 * @see hook_forms()
 */
function finder_forms($form_id, $args) {
  if (strpos($form_id, 'finder_form_') === 0) {
    $forms[$form_id] = array(
      'callback' => 'finder_form',
      'callback arguments' => $args,
    );
    return $forms;
  }
}

/**
 * Invoke hook_finderapi().
 *
 * @param &$object
 *   The finder or finder flement.
 * @param $op
 *   The operation, indicates where/when this is being invoked.
 * @param $a3, $a4
 *   Arguments to pass to the hook implementation.
 * @return
 *   The returned value of the invoked hooks.
 */
function finder_invoke_finderapi(&$object, $op, $a3 = NULL, $a4 = NULL) {
  $return = array();
  foreach (module_implements('finderapi') as $name) {
    $function = $name . '_finderapi';
    $result = $function($object, $op, $a3, $a4);
    if (isset($result) && is_array($result)) {
      $return = array_merge($return, $result);
    }
    elseif (isset($result)) {
      $return[] = $result;
    }
  }
  return $return;
}

/**
 * Load objects from the database.
 *
 * This is intended as a reverse 'drupal_write_record' based on the code from
 * node_load_multiple() in Drupal 7.  It can be used to read records from any
 * table assuming they are keyed by a serial field named {table}_id.  Also
 * supports serialized fields.
 *
 * @param $load
 *   The name of the table, and thus the type of object to load.
 * @param $ids
 *   An array of IDs, if selecting by ID.
 * @param $conditions
 *   An array of conditions on the table in the form 'field' => $value.
 * @param $reset
 *   Whether to reset the internal cache for this type of $load object.
 * @return
 *   An array of loaded objects indexed by ID.
 */
function finder_load_objects($load, $ids = NULL, $conditions = array(), $reset = FALSE) {
  static $object_cache = array();
  if ($reset) {
    $object_cache[$load] = array();
  }
  if (!isset($object_cache[$load])) {
    $object_cache[$load] = array();
  }
  $objects = array();
  $id_key = $load . '_id';

  // Create a new variable which is either a prepared version of the $ids
  // array for later comparison with the finder cache, or FALSE if no $ids were
  // passed. The $ids array is reduced as items are loaded from cache, and we
  // need to know if it's empty for this reason to avoid querying the database
  // when all requested objects are loaded from cache.
  $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;

  // Load any available objects from the internal cache.
  if ($object_cache[$load]) {
    if (!empty($ids)) {
      $objects += array_intersect_key($object_cache[$load], $passed_ids);

      // If any objects were loaded, remove them from the $ids still to load.
      $ids = array_keys(array_diff_key($passed_ids, $objects));
    }
    elseif ($conditions) {
      $objects = $object_cache[$load];
    }
  }

  // Exclude any objects loaded from cache if they don't match $conditions.
  // This ensures the same behavior whether loading from memory or database.
  if (!empty($conditions)) {
    foreach ($objects as $object) {
      $object_values = (array) $object;
      if (array_diff_assoc($conditions, $object_values)) {
        unset($objects[$object->{$id_key}]);
      }
    }
  }

  // Load objects from the database. This is the case if there are
  // any $ids left to load, if $conditions was passed without $ids,
  // or if $ids and $conditions were intentionally left blank.
  if (!empty($ids) || $conditions && !$passed_ids || $ids === NULL && $conditions === array()) {
    $query = array();

    // build query
    $query = db_select($load)
      ->fields($load);
    if (!empty($ids)) {
      $query
        ->condition($id_key, $ids, 'IN');
    }
    if (!empty($conditions)) {
      foreach ($conditions as $field => $value) {
        $query
          ->condition($field, $value);
      }
    }
    if ($load == 'finder_element') {
      $query
        ->orderBy('weight');
    }
    $queried_objects = $query
      ->execute()
      ->fetchAll();
  }

  // Pass all objects loaded from the database through the finder type specific
  // callbacks and hook_finderapi(), then add them to the internal cache.
  if (!empty($queried_objects)) {
    foreach ($queried_objects as $q_key => $q_object) {

      // unserialize settings
      if (isset($q_object->settings)) {
        $queried_objects[$q_key]->settings = (array) unserialize($q_object->settings);
      }

      // add elements if object is a finder.
      // this code is here in lieu of a hook_finderapi ($op='finder_load') implementation.
      if ($load == 'finder') {
        $queried_objects[$q_key]->elements = finder_element_load_multiple(array(), array(
          $id_key => $q_object->{$id_key},
        ));
        if (!empty($queried_objects[$q_key]->elements)) {
          foreach ($queried_objects[$q_key]->elements as $position => $element) {
            $queried_objects[$q_key]->elements_index[$element->finder_element_id] = $position;
          }
        }
        finder_load_base_handler($queried_objects[$q_key]);
        finder_load_element_handler($queried_objects[$q_key]);
        finder_load_links($queried_objects[$q_key]);
      }

      // invoke finderapi so modules can make changes
      finder_invoke_finderapi($queried_objects[$q_key], $load . '_load');
    }
    $objects += $queried_objects;

    // Add objects to the cache.
    foreach ($queried_objects as $queried_object) {
      $object_cache[$load][$queried_object->{$id_key}] = $queried_object;
    }
  }

  // Ensure that the returned array is ordered the same as the original $ids
  // array if this was passed in and remove any invalid ids.
  if ($passed_ids) {

    // Remove any invalid ids from the array.
    $passed_ids = array_intersect_key($passed_ids, $objects);
    foreach ($objects as $object) {
      $passed_ids[$object->{$id_key}] = $object;
    }
    $objects = $passed_ids;
  }
  return $objects;
}

/**
 * Load a finder object from the database.
 *
 * @param $finder_id
 *   The finder ID.
 * @param $reset
 *   Whether to reset the internal cache for finder objects.
 * @return
 *   The loaded finder object, or FALSE on failure.
 */
function finder_load($finder_id, $reset = FALSE) {
  $finders = finder_load_multiple(array(
    $finder_id,
  ), array(), $reset);
  return $finders ? $finders[$finder_id] : FALSE;
}

/**
 * Load finder objects from the database.
 *
 * @param $ids
 *   An array of finder IDs if selecting by IDs.
 * @param $conditions
 *   An array of conditions on the table in the form 'field' => $value.
 * @param $reset
 *   Whether to reset the internal cache for finder objects.
 * @return
 *   An array of loaded finder objects indexed by ID.
 */
function finder_load_multiple($ids = NULL, $conditions = array(), $reset = FALSE) {
  return finder_load_objects('finder', $ids, $conditions, $reset);
}

/**
 * Save changes to a finder or add a new finder.
 *
 * @param &$finder
 *   The finder object.
 */
function finder_save(&$finder) {
  finder_invoke_finderapi($finder, 'finder_presave');
  $update = array();
  $op = 'finder_insert';
  if (!empty($finder->finder_id)) {
    $update[] = 'finder_id';
    $op = 'finder_update';
  }
  drupal_write_record('finder', $finder, $update);
  finder_invoke_finderapi($finder, $op);
}

/**
 * Delete a finder and it's finder elements.
 *
 * @param $finder_id
 *   The finder ID.
 */
function finder_delete($finder_id) {
  $finder = finder_load($finder_id);
  db_delete('finder_element')
    ->condition('finder_id', $finder_id)
    ->execute();
  db_delete('finder')
    ->condition('finder_id', $finder_id)
    ->execute();
  finder_invoke_finderapi($finder, 'finder_delete');
  watchdog('finder', 'Finder %title deleted.', array(
    '%title' => $finder->title,
  ));
  drupal_set_message(t('Finder %title has been deleted.', array(
    '%title' => $finder->title,
  )));
}

/**
 * Load a finder element object from the database.
 *
 * @param $finder_element_id
 *   The finder element ID.
 * @param $reset
 *   Whether to reset the internal cache for finder element objects.
 * @return
 *   The loaded finder element object, or FALSE on failure.
 */
function finder_element_load($finder_element_id, $reset = FALSE) {
  $finder_elements = finder_element_load_multiple(array(
    $finder_element_id,
  ), array(), $reset);
  return $finder_elements ? $finder_elements[$finder_element_id] : FALSE;
}

/**
 * Load finder element objects from the database.
 *
 * @param $ids
 *   An array of finder element IDs if selecting by IDs.
 * @param $conditions
 *   An array of conditions on the table in the form 'field' => $value.
 * @param $reset
 *   Whether to reset the internal cache for finder element objects.
 * @return
 *   An array of loaded finder element objects indexed by ID.
 */
function finder_element_load_multiple($ids = NULL, $conditions = array(), $reset = FALSE) {
  return finder_load_objects('finder_element', $ids, $conditions, $reset);
}

/**
 * Save changes to a finder element or add a new finder element.
 *
 * @param &$finder_element
 *   The finder element object.
 */
function finder_element_save(&$finder_element) {
  finder_invoke_finderapi($finder_element, 'finder_element_presave');
  $update = array();
  $op = 'finder_element_insert';
  if (!empty($finder_element->finder_element_id)) {
    $update[] = 'finder_element_id';
    $op = 'finder_element_update';
  }
  drupal_write_record('finder_element', $finder_element, $update);
  finder_invoke_finderapi($finder_element, $op);
}

/**
 * Delete a finder element.
 *
 * @param $finder_element_id
 *   The finder element ID.
 */
function finder_element_delete($finder_element_id) {
  $finder_element = finder_element_load($finder_element_id);
  db_delete('finder_element')
    ->condition('finder_element_id', $finder_element_id)
    ->execute();
  finder_invoke_finderapi($finder_element, 'finder_element_delete');
  watchdog('finder', 'Finder element %title deleted.', array(
    '%title' => $finder_element->title,
  ));
  drupal_set_message(t('Finder element %title has been deleted.', array(
    '%title' => $finder_element->title,
  )));
}

/**
 * Write a finder into the database as a new finder.
 *
 * @param $old_finder
 *   The finder object to clone.
 * @return
 *   The new finder object.
 */
function finder_clone($old_finder) {
  $finder = clone $old_finder;
  finder_invoke_finderapi($finder, 'finder_clone');
  unset($finder->finder_id);
  finder_save($finder);
  foreach ($finder->elements as $key => $finder_element) {
    unset($finder_element->finder_element_id);
    $finder_element->finder_id = $finder->finder_id;
    finder_element_save($finder_element);
    $finder->elements[$key] = $finder_element;
  }
  return $finder;
}

/**
 * Menu callback; view a finder page.
 *
 * @param $finder_id
 *   The finder ID.
 * @return
 *   Themed output of a finder page.
 */
function finder_page($finder_id) {
  $finder = finder_load($finder_id);
  finder_invoke_finderapi($finder, 'finder_page');
  return theme('finder_page', array(
    'finder' => $finder,
  ));
}

/**
 * Generate display of a given finder.
 *
 * @param $finder
 *   The finder object to generate the output for.
 * @param $display
 *   The type of display ('page', or 'block').
 * @param $ajax
 *   Whether in the context of an ajax request.
 * @return
 *   Themed output of a finder.
 *
 * @todo
 *   Take better advantage of Drupal 7's render array.
 */
function finder_view($finder, $display, $ajax = FALSE) {
  finder_inc('form');
  finder_invoke_finderapi($finder, 'finder_view', $display);
  $output_array = array();
  $finder->finder_view_build_id = 'finder-' . $display . '-' . $finder->finder_id . '-wrapper';
  $finder->finder_view_build_display = $display;

  // Always get the form in order to populate the form_state in case there are results we need to present.
  // The form building function will not spend resources building elements if it doesn't need to.
  // to do: this non form_on_page get_form may only be needed when hiding url args?
  $form = drupal_get_form('finder_form_' . $finder->finder_id, $finder);
  if ($finder->settings['advanced']['show_admin_links'] && user_access('administer finder')) {
    $output['admin_links'] = theme('finder_admin_links', array(
      'finder' => $finder,
    ));
  }
  if ($display != 'page' || $display == 'page' && $finder->settings['form_on_page']) {
    $output['form'] = drupal_render($form);
  }
  if ($finder->settings['advanced']['show_links']) {
    $output['links'] = theme('finder_links', array(
      'finder' => $finder,
    ));
  }
  if ($display != 'block' || $display == 'block' && $ajax) {
    $output['results'] = finder_results($finder);
  }
  $rendered = '';
  $rendered .= $ajax ? '' : '<div id="' . $finder->finder_view_build_id . '" class="finder-view-wrapper">';
  $rendered .= theme('finder_view', array(
    'finder' => $finder,
    'display' => $display,
    'output_array' => $output,
  ));
  $rendered .= $ajax ? '' : '</div>';
  return $rendered;
}

/**
 * Menu callback; get finder ajax output.
 *
 * @param $finder_id
 *   The finder ID.
 * @param $display
 *   The type of display ('page', or 'block).
 * @param $path
 *   Any number of additional params to pass to $_GET['q'].
 * @return
 *   Finder ajax output.
 */
function finder_ajax($finder_id, $display) {
  if ($finder_id) {
    $finder = finder_load($finder_id);
    if ($finder) {

      // fix the path for any scripts that might call $_GET['q']
      $path = func_get_args();
      array_shift($path);
      array_shift($path);
      $_GET['q'] = implode('/', $path);

      // force the json'd finder output to hide_args
      $finder->settings['advanced']['hide_args'] = 1;
      drupal_json_output(array(
        'status' => TRUE,
        'data' => finder_view($finder, $display, TRUE),
      ));
      exit;
    }
  }
  drupal_json_output(array(
    'data' => '',
  ));
  exit;
}

/**
 * Create finder results output.
 *
 * @param $finder
 *   The finder object.
 * @return
 *   Themed output of finder results.
 */
function finder_results($finder) {
  $output = '';
  $finder_form_state = finder_form_state($finder->finder_id);
  finder_invoke_finderapi($finder, 'finder_results', $finder_form_state);
  if ($finder_form_state && $finder_form_state['storage']['finished'] || $finder->settings['advanced']['filter']) {

    // I can't remember what this is for...
    foreach ($_REQUEST as $k => $v) {
      unset($_REQUEST[$k]);
    }
    $keywords = array();
    $pager =& $finder->settings['advanced']['pager'];
    foreach ($finder->elements as $element) {
      $match =& $element->settings['advanced']['match'];
      $keyword = array();
      if (!is_null($finder_form_state['values'][$element->finder_element_id])) {
        $keyword = (array) $finder_form_state['values'][$element->finder_element_id];
        if (!empty($element->settings['advanced']['delimit'])) {

          // This enables escaped characters to work in delimiters.
          $delimiter = stripcslashes($element->settings['advanced']['delimit']);
          foreach ($keyword as $k => $v) {
            unset($keyword[$k]);
            $exploded = explode($delimiter, $v);
            foreach ($exploded as $e) {
              $keyword[] = trim($e);
            }
          }
        }
      }
      $keywords[$element->finder_element_id] = $keyword;
    }
    $goto =& $finder->settings['advanced']['goto'];
    if (isset($_GET['go']) && $_GET['go'] || isset($finder_form_state['clicked_button']) && $finder_form_state['clicked_button']['#name'] == 'go' && $finder_form_state['storage']['finished'] || $goto == 'always') {
      $finder->go = TRUE;
    }
    $result = finder_find($finder, $keywords, 'results', $match, $pager);
    if ($finder->settings['advanced']['hide_args']) {
      if ($pager && !isset($finder_form_state['storage']['pager_token'])) {
        $token = drupal_get_token();
        $finder_form_state['storage']['pager_token'] = $token;
        $_SESSION['finder'][$token] = $finder_form_state;
      }
    }
    $base_module =& $finder->base_handler['#module'];
    if (isset($finder->go) && $finder->go && count($result) || $goto == 'best' && count($result) === 1 && empty($_GET['page'])) {
      drupal_alter('finder_goto', $result, $finder);
      $current_result = current($result);
      module_invoke($base_module, 'finder_goto', $finder, $current_result);
    }
    $results = module_invoke($base_module, 'finder_result', $finder, $keywords, $result, $finder_form_state);
    $params = array();
    if (!empty($finder_form_state['storage']['pager_token'])) {
      $params['finder'] = $finder_form_state['storage']['pager_token'];
    }
    if (!empty($finder->settings['advanced']['no_results']['no_results'])) {
      $variables = array(
        'finder' => $finder,
        'keywords' => $keywords,
        'form_state' => $finder_form_state,
      );
      $no_results = finder_eval($finder->settings['advanced']['no_results']['no_results'], $variables);
    }
    else {

      // Default text.
      $no_results = t('There are no results to display');
    }
    $output .= theme('finder_results', array(
      'results' => $results,
      'finder' => $finder,
      'keywords' => $keywords,
      'pager' => $pager,
      'params' => $params,
      'form_state' => $finder_form_state,
      'no_results' => $no_results,
    ));
  }
  return $output;
}

/**
 * Implements hook_block_info().
 *
 * @see hook_block_info()
 */
function finder_block_info() {
  $finders = finder_load_multiple(NULL, array(
    'block' => 1,
  ));
  $blocks = array();
  foreach ($finders as $finder) {
    $blocks['finder_' . $finder->finder_id] = array(
      'info' => t('Finder') . ': ' . check_plain($finder->title),
      'cache' => 'BLOCK_NO_CACHE',
    );
  }
  return $blocks;
}

/**
 * Implements hook_block_view().
 *
 * @see hook_block_view()
 */
function finder_block_view($delta = '') {
  if (user_access('use finder')) {
    $finder_id = str_replace('finder_', '', $delta);
    $finder = finder_load($finder_id);
    if ($finder) {
      finder_invoke_finderapi($finder, 'finder_block');
      $block = array(
        'subject' => check_plain($finder->title),
        'content' => array(
          '#theme' => 'finder_block',
          '#finder' => $finder,
        ),
      );
      return $block;
    }
  }
}

/**
 * Get a list of choices for form or results.
 *
 * This will invoke the base handler module's hook_finder_find()
 * implementation.  It also allows for hooks to alter the keywords, finder, and
 * choices/results.
 *
 * @param $finder
 *   The finder object.
 * @param $keywords
 *   An array keyed by finder_element_id, where the values are any
 *   str/num/bool/null or an array of such values to be OR'd together.
 * @param $mode
 *   'choices' or 'results' depending on what we are fetching.
 * @param $match
 *   The match method, see finder_condition_args().
 * @param $pager
 *   Used to limit choices or results per page.
 * @param $finder_element_id
 *   If $mode is 'choices', this is the finder element id to get choices for.
 * @param $reset
 *   Reset the cached return value for this set of parameters.
 * @return
 *   An array of choices/results.
 * @see hook_finder_find()
 */
function finder_find($finder, $keywords, $mode = 'choices', $match = 'e', $pager = 0, $finder_element_id = NULL, $reset = FALSE) {
  static $finder_find_cache = array();

  // For a 'choices' find we need a main element to focus our query around
  // normally calling finder_find() you would not specify the $finder_element_id
  // but it would be interpreted as the index of the last $keywords element
  // though some modules may need to specify a main element other than the last
  // so that parameter is available.  This value is best left 'NULL' for 'results'.
  if ($mode == 'choices' && is_null($finder_element_id)) {

    // no $finder_element_id was passed as the current element we're doing
    // so let's assume the last array key in $keywords is current finder_element_id
    $finder_element_id = end(array_keys($keywords));
  }

  // Create an ID using the function params so we can cache the return value.
  $id = ($mode == 'choices' ? 'e' . $finder_element_id : 'f' . $finder->finder_id) . '|';
  $id_length = strlen($id);
  $cache_id = ($match != 'e' ? $match : '') . ($pager ? $pager . (isset($_GET['page']) ? '.' . $_GET['page'] : '') : '') . serialize($keywords);
  $cache_id_length = strlen($cache_id);
  if ($cache_id_length + $id_length > 255) {

    // For ID's that (will) exceed 255 we will try to represent them a unique way and pray for the best :/
    $cache_id = md5($cache_id) . $cache_id_length . substr($cache_id, 0, 223 - strlen($cache_id_length) - $id_length);

    // 223 = 255 - 32
  }
  $cache_id = $id . $cache_id;
  if (isset($finder_find_cache[$cache_id])) {

    // Use the static data.
    $options = $finder_find_cache[$cache_id];
  }
  elseif ($finder->settings['advanced']['cache_finder_find'] && !$reset && ($cache = cache_get($cache_id, 'cache_finder_find')) && !empty($cache->data)) {

    // Use the cached data.
    $options = $cache->data;
  }
  else {

    // Calculate the values from the database.
    // Figure out which module is the base module (the module that tells us the options)
    $module =& $finder->base_handler['#module'];

    // Allow other modules to intefere with the keywords.
    drupal_alter('finder_find_keywords', $keywords, $finder, $finder_element_id, $mode, $match, $pager);

    // Allow other modules to react to and alter the finder.
    finder_invoke_finderapi($finder, 'finder_find', $mode, $finder_element_id);
    $options = module_invoke($module, 'finder_find', $finder, $finder_element_id, $keywords, $mode, $match, $pager);

    // If mode is choices, we want to conduct some extra processing on this list.
    if ($mode == 'choices') {
      $options = finder_find_choices($finder, $finder_element_id, $options, $keywords[$finder_element_id], $match);
    }

    // Allow other modules to intefere with the options.
    drupal_alter('finder_find_options', $options, $finder, $finder_element_id, $keywords, $mode, $match, $pager);

    // Add the resulting $options to the drupal cache.
    if ($finder->settings['advanced']['cache_finder_find']) {
      cache_set($cache_id, $options, 'cache_finder_find', REQUEST_TIME + $finder->settings['advanced']['cache_finder_find']);
    }

    // Add the resulting $options to the static internal load cache.
    $finder_find_cache[$cache_id] = $options;
  }
  return $options;
}

/**
 * Build basic finder query arrays.
 *
 * This is a helper function that can be leveraged by basic hook_finder_find()
 * implementations.  It takes care of the common tasks that need to be done to
 * build the $query array correctly, and allows the base handler modules to
 * focus on their specialties.
 *
 * @param $query
 *   The query array to modify.
 * @param $finder
 *   The finder object.
 * @param $finder_element_id
 *   If $mode is 'choices', this is the finder element id to get choices for.
 * @param $keywords
 *   An array keyed by finder_element_id, where the values are any
 *   str/num/bool/null or an array of such values to be OR'd together.
 * @param $mode
 *   'choices' or 'results' depending on what we are fetching.
 * @param $match
 *   The match method, see finder_condition_args().
 * @param $pager
 *   Used to limit choices or results per page.
 * @param $join_ons
 *   Join information in the form of an array where the key is the table string
 *   as returned when a value returned from hook_finder_fields() is put through
 *   finder_split_info() and the value is an array where the keys are the names
 *   of real tables to join, and the values are the 'ON condition' that follows
 *   the ON keyword in SQL.
 * @param $base_table
 *   The name of the base table to query.
 * @param $base_field
 *   The primary key of the base table.
 * @return
 *   The modified query array.
 */
function finder_find_query($prequery, $finder, $finder_element_id, $keywords, $mode, $match, $pager, $join_ons, $base_table, $base_field) {
  $options = array();
  $query = db_select($base_table);
  if ($pager) {
    $query = $query
      ->extend('PagerDefault')
      ->limit($pager);
  }
  if ($mode == 'results') {
    $base = $query
      ->addField($base_table, $base_field);
    $query
      ->addExpression("'" . $base_field . "'", 'base_field');
    $query
      ->addExpression("'" . $base_table . "'", 'base_table');
    $query
      ->groupBy($base);
  }
  foreach ($keywords as $feid => $keyword_array) {
    $element =& finder_element($finder, $feid);
    $fields[$feid] =& $element->settings['choices']['field'];
    foreach ($fields[$feid] as $key => $field) {
      $field_info[$feid][$key] = finder_split_field($field);
    }
    $sort[$feid] =& $element->settings['choices']['sort'];
    if ($mode == 'choices' && $feid == $finder_element_id && $element->settings['choices']['per_result']) {
      $base = $query
        ->addField($base_table, $base_field);
      $query
        ->addExpression('"' . $base_field . '"', 'base_field');
      $query
        ->addExpression('"' . $base_table . '"', 'base_table');
      $query
        ->groupBy($base);
    }
    foreach ($fields[$feid] as $key => $field) {
      $field_alias = finder_field_alias($feid, $field_info[$feid][$key]['table'], $field_info[$feid][$key]['field']);
      $field_alias = $query
        ->addField($field_info[$feid][$key]['table'], $field_info[$feid][$key]['field'], $field_alias);
      if ($mode == 'choices' && $feid == $finder_element_id) {
        $query
          ->groupBy($field_alias);
      }
    }

    // join tables if needed
    foreach ($fields[$feid] as $key => $field) {
      if (in_array($field_info[$feid][$key]['table'], array_keys($join_ons))) {
        $join_on = $join_ons[$field_info[$feid][$key]['table']];
        foreach ($join_on as $table => $on) {
          $query
            ->innerJoin($table, $table, $on);
        }
      }
    }
    $field_operator = $element->settings['advanced']['field_combination'] ? 'AND' : 'OR';
    $value_operator = $element->settings['advanced']['value_combination'] ? 'AND' : 'OR';
    $nesting_order =& $element->settings['advanced']['nesting_order'];
    $outer_operator = $nesting_order ? $field_operator : $value_operator;
    $inner_operator = $nesting_order ? $value_operator : $field_operator;

    // Restrict by keywords on field.
    $keyword_array = (array) $keyword_array;
    $keyword_array = array_filter($keyword_array, 'finder_empty_keyword');
    if (!empty($keyword_array)) {
      $prequery['conditions']['outer']['#operator'] = $outer_operator;
      foreach ($keyword_array as $keyword_index => $keyword) {
        if ($feid == $finder_element_id) {
          foreach ($fields[$feid] as $key => $field) {
            $inner_key = $nesting_order ? $key : $keyword_index;
            if (!isset($prequery['conditions']['outer']['inner' . $inner_key]['#operator'])) {
              $prequery['conditions']['outer']['inner' . $inner_key]['#operator'] = $inner_operator;
            }
            $prequery['conditions']['outer']['inner' . $inner_key][] = finder_condition_args($field, $keyword, $match);
          }
        }
        else {
          $prequery['conditions']['restrictions'][$feid]['outer']['#operator'] = $outer_operator;
          foreach ($fields[$feid] as $key => $field) {
            $inner_key = $nesting_order ? $key : $keyword_index;
            if (!isset($prequery['conditions']['restrictions'][$feid]['outer']['inner' . $inner_key]['#operator'])) {
              $prequery['conditions']['restrictions'][$feid]['outer']['inner' . $inner_key]['#operator'] = $inner_operator;
            }
            $prequery['conditions']['restrictions'][$feid]['outer']['inner' . $inner_key][] = finder_condition_args($field, $keyword, $element->settings['advanced']['match']);
          }
        }
      }
    }
  }

  // for additional elements wheres group
  if (!empty($prequery['conditions']['restrictions'])) {
    $prequery['conditions']['restrictions']['#operator'] = $finder->settings['advanced']['element_combination'] ? 'OR' : 'AND';
  }

  // if this is a choices list add a sort if there is only one field
  if ($mode == 'choices' && $sort[$finder_element_id] && count($fields[$finder_element_id]) === 1) {
    $query
      ->orderBy(end($fields[$finder_element_id]));
  }

  // Restrict to one result if $finder->go is TRUE.
  if ($mode == 'results' && isset($finder->go) && $finder->go) {
    $query
      ->range(0, 1);
  }
  if (!empty($prequery['conditions'])) {
    $conditions = finder_wheres($prequery['conditions']);
    $query
      ->where($conditions['sql'], $conditions['args']);
  }
  if (!empty($prequery['joins'])) {
    foreach ($prequery['joins'] as $join_func => $joins) {
      foreach ($joins as $join) {
        call_user_func_array(array(
          $query,
          $join_func,
        ), $join);
      }
    }
  }
  return $query
    ->execute()
    ->fetchAll();
}

/*
 * Recursively process SQL 'wheres'.
 */
function finder_wheres($wheres) {
  static $placeholders = 0;
  $return = array(
    'sql' => '',
    'args' => array(),
  );
  $operator = isset($wheres['#operator']) ? $wheres['#operator'] : 'AND';
  unset($wheres['#operator']);
  foreach ($wheres as $key => $where) {
    if (is_array($where)) {
      $finder_wheres = finder_wheres($where);
      if ($finder_wheres['sql']) {
        $wheres[$key] = '(' . $finder_wheres['sql'] . ')';
        if ($finder_wheres['args']) {
          $return['args'] = array_merge($return['args'], $finder_wheres['args']);
        }
      }
      else {
        unset($wheres[$key]);
      }
    }
    elseif (is_object($where)) {
      $placeholder = ':finder_' . $placeholders++;
      $return['args'][$placeholder] = $where->value;
      if (is_array($where->value)) {
        $placeholder = '(' . $placeholder . ')';
      }
      $wheres[$key] = $where->field . ' ' . $where->match . ' ' . $placeholder;
    }
  }
  if (!empty($wheres)) {
    $return['sql'] = implode(' ' . $operator . ' ', $wheres);
  }
  return $return;
}

/**
 * Get a list of possible element types.
 *
 * @return
 *   An array of element handlers from hook implementations.
 * @see hook_finder_element_handlers()
 */
function finder_element_handlers() {
  static $element_handlers;
  if (empty($element_handlers)) {
    $element_handlers = module_invoke_all('finder_element_handlers');
  }
  return $element_handlers;
}

/**
 * Attach element handler data to the finder.
 *
 * @param &$finder
 *   The finder object.
 */
function finder_load_element_handler(&$finder) {
  $element_handlers = finder_element_handlers();
  if (!empty($finder->elements)) {
    foreach ($finder->elements as $key => $element) {
      $finder->elements[$key]->element_handler = $element_handlers[$element->element];
    }
  }
}

/**
 * Get a list of findable Drupal objects.
 *
 * @return
 *   An array of base handlers from hook implementations.
 * @see hook_finder_base_handlers()
 */
function finder_base_handlers() {
  static $base_handlers;
  if (empty($base_handlers)) {
    $base_handlers = module_invoke_all('finder_base_handlers');
  }
  return $base_handlers;
}

/**
 * Attach base handler data to the finder.
 *
 * @param &$finder
 *   The finder object.
 */
function finder_load_base_handler(&$finder) {
  $base_handlers = finder_base_handlers();
  $finder->base_handler = $base_handlers[$finder->base];
}

/**
 * Load a module include file according to finder's naming convention.
 *
 * Finder's naming convention suggests includes be put in a directory called
 * 'includes' within the module's directory, and named like so:
 * module-name.inc-string.inc
 *
 * @param $inc_string
 *   If the file is finder.foo.inc then the $inc_string to specify is 'foo'.
 * @param $module
 *   The name of the module.
 */
function finder_inc($inc_string, $module = 'finder') {
  return module_load_include('inc', $module, 'includes/' . $module . '.' . $inc_string);
}

/**
 * Returns the path to a module's includes directory according to finder's
 * naming convention.
 *
 * Finder's naming convention suggests includes be put in a directory called
 * 'includes' within the module's directory.
 *
 * @param $module
 *   The name of the module.
 */
function finder_inc_path($module = 'finder') {
  static $inc_path;
  if (empty($inc_path[$module])) {
    $inc_path[$module] = drupal_get_path('module', $module) . '/includes';
  }
  return $inc_path[$module];
}

/**
 * Return info about a field.
 *
 * Finder stores information about fields as "table-name.field-name", this is
 * just a simple function to split the field string into the two parts for
 * convenience.
 *
 * @param $field
 *   A field value as given in the array keys returned from
 *   hook_finder_fields() implementations.
 * @return
 *   An array with keys 'field' and 'table'.
 */
function finder_split_field($field) {
  $field_parts = explode('.', $field);
  $field_info['field'] = $field_parts[1];
  $field_info['table'] = $field_parts[0];
  return $field_info;
}

/**
 * Get data about finder match methods.
 *
 * @param $field
 *   The field to match.
 * @param $value
 *   The value to match.
 * @param $match
 *   The match method according to finder's settings.
 * @return
 *   An object cast from an array with keys field/value/match if $match is set.
 *   Otherwise returns the full array of data about all match methods including
 *   operators and descriptions.
 */
function finder_condition_args($field = NULL, $value = NULL, $match = NULL) {
  static $operators;
  if (empty($operators)) {

    // Operators use abbreviated key names because they need to be tiny in cache ID's.
    $operators = finder_condition_args_default();
    $operators = array_merge($operators, variable_get('finder_custom_matching', array()));
    drupal_alter('finder_condition_args', $operators);
  }
  if (!is_null($match)) {
    if ($operators[$match]['value_prefix']) {
      $value = $operators[$match]['value_prefix'] . $value;
    }
    if ($operators[$match]['value_suffix']) {
      $value = $value . $operators[$match]['value_suffix'];
    }
    return (object) array(
      'field' => $field,
      'value' => $value,
      'match' => $operators[$match]['operator'],
    );
  }
  return $operators;
}

/**
 * Get preconfigured condition match methods.
 */
function finder_condition_args_default() {
  return array(
    'c' => array(
      'name' => t('Contains'),
      'description' => t('!matches <em>contain</em> the !keywords.'),
      'operator' => 'LIKE',
      'value_prefix' => '%',
      'value_suffix' => '%',
    ),
    'cw' => array(
      'name' => t('Contains word'),
      'description' => t('!matches <em>contain</em> the !keywords as whole words.'),
      'operator' => 'REGEXP',
      'value_prefix' => '[[:<:]]',
      'value_suffix' => '[[:>:]]',
    ),
    'e' => array(
      'name' => t('Equals'),
      'description' => t('!matches must match the !keywords <em>exactly</em>.'),
      'operator' => '=',
      'value_prefix' => '',
      'value_suffix' => '',
    ),
    'sw' => array(
      'name' => t('Starts with'),
      'description' => t('!matches must <em>start with</em> the !keywords.'),
      'operator' => 'LIKE',
      'value_prefix' => '%',
      'value_suffix' => '',
    ),
    'ew' => array(
      'name' => t('Ends with'),
      'description' => t('!matches must <em>end with</em> the !keywords.'),
      'operator' => 'LIKE',
      'value_prefix' => '',
      'value_suffix' => '%',
    ),
    'lt' => array(
      'name' => t('Less than'),
      'description' => t('!matches must be <em>less than</em> the !keywords.'),
      'operator' => '<',
      'value_prefix' => '',
      'value_suffix' => '',
    ),
    'lte' => array(
      'name' => t('Less than or equals'),
      'description' => t('!matches must be <em>less than or equal to</em> the !keywords.'),
      'operator' => '<=',
      'value_prefix' => '',
      'value_suffix' => '',
    ),
    'gt' => array(
      'name' => t('Greater than'),
      'description' => t('!matches must be <em>greater than</em> the !keywords.'),
      'operator' => '>',
      'value_prefix' => '',
      'value_suffix' => '',
    ),
    'gte' => array(
      'name' => t('Greater than or equals'),
      'description' => t('!matches must be <em>greater than or equal to</em> the !keywords.'),
      'operator' => '>=',
      'value_prefix' => '',
      'value_suffix' => '',
    ),
    'nc' => array(
      'name' => t("Doesn't contain"),
      'description' => t("!matches <em>don't contain</em> the !keywords."),
      'operator' => 'NOT LIKE',
      'value_prefix' => '%',
      'value_suffix' => '%',
    ),
    'ncw' => array(
      'name' => t("Doesn't Contain word"),
      'description' => t("!matches <em>don't contain</em> the !keywords as whole words."),
      'operator' => 'NOT REGEXP',
      'value_prefix' => '[[:<:]]',
      'value_suffix' => '[[:>:]]',
    ),
    'ne' => array(
      'name' => t('Not equals'),
      'description' => t('!matches must <em>not</em> match the !keywords.'),
      'operator' => '<>',
      'value_prefix' => '',
      'value_suffix' => '',
    ),
  );
}

/**
 * Get label for a condition args entry.
 *
 * @param $entry
 *   A condition args entry.
 * @param $matches
 *  The translated string to replace !matches with.
 * @param $keywords
 *  The translated string to replace !keywords with.
 * @return
 *  The formatted label.
 */
function finder_condition_args_label($entry, $matches, $keywords) {
  return drupal_ucfirst($entry['name'] . ' - ' . strtr($entry['description'], array(
    '!matches' => $matches,
    '!keywords' => $keywords,
  )));
}

/**
 * Get an element from a finder.
 *
 * Finder stores it's elements in an indexed array, as well as tracking an
 * array that maps finder element IDs to the index position.  This can be
 * awkward to use when dealing with a particular element's settings especially
 * when the element variable needs to be a reference to an element in the
 * finder variable.  This function conveniently allows us to pull a finder
 * element into a reference.  If called by reference, for example,
 * $element = &finder_element($finder, $finder_element_id);
 * this function will return a reference to a finder element from the supplied
 * finder as identified by the supplied finder element id, if not called by
 * reference it will return a copy of the element.
 *
 * @param &$finder
 *   The finder object from which to get the element.
 * @param &$finder_element_id
 *   The ID of the finder element required.
 * @return
 *   The finder element, or reference to the finder element.
 */
function &finder_element(&$finder, &$finder_element_id) {
  $key =& $finder->elements_index[$finder_element_id];
  return $finder->elements[$key];
}

/**
 * Attach 'links' data and 'admin links' data to the finder.
 *
 * @param &$finder
 *   The finder object.
 */
function finder_load_links(&$finder) {

  // create admin links
  $finder->admin_links = array();
  $finder->admin_links[$finder->path] = t('View "Path"');
  if (!isset($finder->settings['programmatic']) || !$finder->settings['programmatic']) {
    $finder->admin_links['admin/structure/finder/' . $finder->finder_id . '/edit'] = t('Edit');
  }

  // create links
  $finder->links = array();
}

/**
 * Build finder code string recursively.
 */
function finder_export($var, $iteration = 0) {
  $tab = '';
  for ($i = 0; $i < $iteration; $i++) {
    $tab = $tab . '  ';
  }
  $iteration++;
  if (is_object($var)) {
    $var = (array) $var;
    $var['#_finder_object'] = '1';
  }
  if (is_array($var)) {
    $empty = empty($var);
    $code = "array(" . ($empty ? '' : "\n");
    foreach ($var as $key => $value) {
      $out = $tab . "  '" . $key . "' => " . finder_export($value, $iteration) . ",\n";
      drupal_alter('finder_export', $out, $tab, $key, $value, $iteration);
      $code .= $out;
    }
    $code .= ($empty ? '' : $tab) . ")";
    return $code;
  }
  else {
    if (is_string($var)) {
      return "'" . addslashes($var) . "'";
    }
    elseif (is_numeric($var)) {
      return $var;
    }
    elseif (is_bool($var)) {
      return $var ? 'TRUE' : 'FALSE';
    }
    else {
      return 'NULL';
    }
  }
}

/**
 * Evaluate and return decoded string.
 */
function finder_import($string) {
  $array = eval('return ' . $string . ';');
  $return = finder_import_objects($array);
  return $return;
}

/**
 * Recursively converts arrays back into objects.
 */
function finder_import_objects($array) {
  foreach ($array as $k => $v) {
    if (is_array($v)) {
      $array[$k] = finder_import_objects($v);
    }
    if (is_string($v)) {
      $array[$k] = stripslashes($v);
    }
  }
  if ($array['#_finder_object']) {
    unset($array['#_finder_object']);
    $array = (object) $array;
  }
  return $array;
}

/**
 * Postprocessing for returned finder_find options when mode is choices.
 *
 * TO DO: some of the added properties here could probably just be "selected"
 *        in the query.
 */
function finder_find_choices($finder, $finder_element_id, $options, $keywords, $match) {
  if ($options) {

    // If there are options, fetch some info about the field names.
    $element =& finder_element($finder, $finder_element_id);
    $fields =& $element->settings['choices']['field'];
    foreach ($fields as $key => $field) {
      $field_info[$key] = finder_split_field($field);
      $field_names[$key] = finder_field_alias($finder_element_id, $field_info[$key]['table'], $field_info[$key]['field']);
    }

    // Create a new array where we will put the options to return.
    $new_options = array();

    // Iterate through the options.
    foreach ($options as $option) {

      // Simply case - there is only one field.
      if (count($fields) === 1) {

        // Add the field name to the option.
        $option->field_name = end($field_names);

        // Add the field name to the display_field property of the option too.
        $option->display_field = $option->field_name;

        // If doing "per_result" then switch the field name to the base field.
        if (isset($element->settings['choices']['per_result']) && $element->settings['choices']['per_result']) {
          $option->field_name = $option->base_field;
        }

        // Append this option to the array.
        $new_options[] = $option;
      }
      elseif (count($fields) > 1) {
        if (is_null($keywords)) {
          $matching_names = $field_names;
        }
        else {
          $operator = str_replace('%%', '%', str_replace("'", "", finder_condition_args($match)));
          $matching_names = array();
          foreach ($field_names as $field_name) {
            $expression = str_replace('%s', $keywords, $operator);
            if (strpos($expression, 'LIKE') !== FALSE) {
              $expression = str_replace(' LIKE ', '', $expression);

              // Added strtolower() because some people were reporting
              // case-sensitivity even with the i modifier.
              $like_matches = preg_grep("/^" . str_replace('%', '(.*?)', preg_quote(drupal_strtolower($expression))) . "\$/si", array(
                drupal_strtolower($option->{$field_name}),
              ));
              if (!empty($like_matches)) {
                $matching_names[] = $field_name;
              }
            }
            elseif (eval('return ' . $option->{$field_name} . $expression . ';')) {
              $matching_names[] = $field_name;
            }
          }
        }
        if (count($matching_names) === 1) {
          $option->field_name = end($matching_names);
          $option->display_field = $option->field_name;
          $new_options[] = $option;
        }
        elseif (!empty($matching_names)) {
          foreach ($matching_names as $matching_name) {
            $new_option = clone $option;
            $new_option->field_name = $matching_name;
            $new_option->display_field = $new_option->field_name;
            $new_options[] = $new_option;
          }
        }
      }
    }
    return $new_options;
  }
  return $options;
}

/**
 * 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;
}

/**
 * Modify a PHP setting element.
 *
 * Used for security reasons to prevent an unauthorized user editing the
 * field.  Also makes variables available for the PHP input.
 *
 * @param $element
 *   The original array for the element.
 * @param $variables
 *   Array where keys are variable names (without the $) to make available in
 *   the PHP, and the values are descriptions already passed through t().
 */
function finder_php_setting($element, $variables = array()) {
  if (user_access('administer finder PHP settings')) {
    $var_list = array();
    foreach ($variables as $variable => $description) {
      $var_list[] = '<em>$' . $variable . '</em> - ' . $description;
    }
    if (!empty($var_list)) {
      $element['#description'] = (isset($element['#description']) ? $element['#description'] : '') . '<div>' . t('Available variables') . ':' . theme('item_list', array(
        'items' => $var_list,
      )) . '</div>';
    }
  }
  else {
    $element['#disabled'] = TRUE;
    $element['#prefix'] = '<div class="messages warning">' . t("You don't have permission to modify <em>!setting</em>.", array(
      '!setting' => $element['#title'],
    )) . '</div>' . (isset($element['#prefix']) ? $element['#prefix'] : '');
    $element['#value'] = $element['#default_value'];
  }
  return $element;
}

/**
 * Callback for array_filter to remove empty keywords.
 */
function finder_empty_keyword($keyword) {
  if ($keyword !== '' && $keyword !== NULL) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Build table alias in consistent manner.
 *
 * Deprecated.  Use finder_alias('table', ...) instead.
 */
function finder_table_alias($feid, $table = NULL, $field = NULL, $delta = NULL) {
  return finder_alias('table', $feid, $table, $field, $delta);
}

/**
 * Build field alias in consistent manner.
 *
 * Deprecated.  Use finder_alias('field', ...) instead.
 */
function finder_field_alias($feid, $table = NULL, $field = NULL, $delta = NULL) {
  return finder_alias('field', $feid, $table, $field, $delta);
}

/**
 * Build aliases in consistent manner.
 *
 * Only the $type and $feid 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 $feid
 *  The finder element id.
 * @param $table
 *  The raw table name.
 * @param $field
 *  The raw field name.
 * @param $delta
 *  A meaningful extra value.
 */
function finder_alias($type, $feid, $table = NULL, $field = NULL, $delta = NULL) {
  static $aliases = array();
  static $alias_count = 0;
  $alias =& $aliases[$type][$feid][$table][$field][$delta];
  if (empty($alias)) {
    $alias = ++$alias_count;
  }
  return 'finder_' . $type . '_' . $alias;
}

/**
 * Callback for array filter, removes NULL/FALSE but keeps 0.
 *
 * @todo: Deprecated?
 */
function finder_alias_filter($var) {
  return $var !== FALSE && $var !== NULL;
}

// TO DO: add multifield element sorts into finder_find_choices.
// TO DO: reduce duplicate choices returned from queries - distinct doesn't
//        work because nid's are selected too. - but it doesn't hurt to add
//        it to prevent superfluous rows??
// TO DO: theme functions all over this project are incorrectly documented in
//        doxygen comments.  Need to reassess how themes are used in general.

Functions

Namesort descending Description
finder_ajax Menu callback; get finder ajax output.
finder_alias Build aliases in consistent manner.
finder_alias_filter Callback for array filter, removes NULL/FALSE but keeps 0.
finder_arg_optional_to_arg A to_arg() function is used to provide a default for the arg in the menu wildcard.
finder_base_handlers Get a list of findable Drupal objects.
finder_block_info Implements hook_block_info().
finder_block_view Implements hook_block_view().
finder_clone Write a finder into the database as a new finder.
finder_condition_args Get data about finder match methods.
finder_condition_args_default Get preconfigured condition match methods.
finder_condition_args_label Get label for a condition args entry.
finder_delete Delete a finder and it's finder elements.
finder_element Get an element from a finder.
finder_element_delete Delete a finder element.
finder_element_handlers Get a list of possible element types.
finder_element_load Load a finder element object from the database.
finder_element_load_multiple Load finder element objects from the database.
finder_element_save Save changes to a finder element or add a new finder element.
finder_empty_keyword Callback for array_filter to remove empty keywords.
finder_eval Evaluate a string of PHP code.
finder_export Build finder code string recursively.
finder_field_alias Build field alias in consistent manner.
finder_find Get a list of choices for form or results.
finder_find_choices Postprocessing for returned finder_find options when mode is choices.
finder_find_query Build basic finder query arrays.
finder_forms Implements hook_forms().
finder_import Evaluate and return decoded string.
finder_import_objects Recursively converts arrays back into objects.
finder_inc Load a module include file according to finder's naming convention.
finder_inc_path Returns the path to a module's includes directory according to finder's naming convention.
finder_invoke_finderapi Invoke hook_finderapi().
finder_load Load a finder object from the database.
finder_load_base_handler Attach base handler data to the finder.
finder_load_element_handler Attach element handler data to the finder.
finder_load_links Attach 'links' data and 'admin links' data to the finder.
finder_load_multiple Load finder objects from the database.
finder_load_objects Load objects from the database.
finder_menu Implements hook_menu().
finder_menu_element_allowed Menu item access callback to hide the 'Finder element settings' tab when it shouldn't be shown (when the element ID arg isn't present).
finder_page Menu callback; view a finder page.
finder_permission Implements hook_permission().
finder_php_setting Modify a PHP setting element.
finder_results Create finder results output.
finder_save Save changes to a finder or add a new finder.
finder_split_field Return info about a field.
finder_table_alias Build table alias in consistent manner.
finder_theme Implements hook_theme().
finder_view Generate display of a given finder.
finder_wheres