Drupal coder

A better content type facet for Drupal Search Lucene API when working with Panels

Drupal's Search Lucene API module is a nice alternative to Drupal's core search if you need more power and features (like facets, ...) but don't want to maintain a Solr installation. The Search Lucene API module comes with facet support out of the box.

One facet you will probably provide most of the time is the "Node type" facet. This allows your visitor to filter search results by content type. When you are using Panel nodes (provided with the Panels module), one of the options for your facet is "Panel". Unless all your visitors are Drupal people, they have no idea what a panel is (in this context). Most of the time, panel nodes can be considered as "pages". So conceptually, they are on the same level as "Page" nodes.

Using the hooks provided by Search Lucene API module we can add an extra field to our Lucene index, called "Clean type" and provide a facet for this field. This way we can add one filter option, called "Pages" which actually returns "page" and "panel" search results.

What follows is a snippet you can use in your own module.

Snippet

  • Using hook_luceneapi_result_alter we change the content type under the search result snippet to show "page" for "panel" nodes.
  • Using hook_luceneapi_document_alter we add the extra "clean type" field to our Lucene index.
  • Using hook_luceneapi_facet and hook_luceneapi_facet_postrender_alter we provide the actual facet.
  • hook_luceneapi_facet_empty provides us a way to handle the special case where there a no search results for a given query. We'll show all content types then.
function my_module_luceneapi_result_alter(&$result, $module, $type = NULL) {
  if ('node' == $type) {
    // setting clean node type
    $types = node_get_types('types');
    $clean_type = $result['node']->type;
    if ($clean_type == 'panel') $clean_type = 'page';
    $result['type'] = $types[$clean_type]->name;
  }
}

/**
 * Implementation of hook_luceneapi_postrender_alter().
 */
function my_module_luceneapi_facet_postrender_alter(&$items, $realm, $module, $type = NULL) {
  if ('block' == $realm) {

    // converts values pulled from index to human readable names
    $types = node_get_types('types');
    
    foreach ($items as $name => $item) {
      if ($name == 'clean_type') {
        foreach ($items[$name]['items'] as $type => $item) {
          $items[$name]['items'][$type]['text'] = $types[$type]->name;
        }
      }
    }
  }

  if ('fieldset' == $realm) {

    // clean content type facet
    if (isset($items['clean_type'])) {

      // gets content type minus the excluded ones
      $types = array_map('check_plain', node_get_types('names'));
      foreach (luceneapi_node_excluded_types_get() as $excluded) {
        unset($types[$excluded]);
      }
      
      unset($types['panel']);

      // if there are still content types, finalizes form element
      if (!empty($types)) {
        $items['clean_type'] = array_merge($items['clean_type'], array(
          '#title' => t('Only of the type(s)'),
          '#prefix' => '<div class="criterion">',
          '#suffix' => '</div>',
          '#options' => $types,
        ));
        unset($items['type']['#description']);
      }
      else {
        unset($items['type']);
      }
    }
  }
}

function my_module_luceneapi_document_alter($doc, $item, $module, $type = NULL) {
  if ('node' == $type) {
    $clean_type = drupal_strtolower($item->type);
    if ($clean_type == 'panel') {
      $clean_type = 'page';
    }
    luceneapi_field_add($doc, 'keyword', 'clean_type', $clean_type, TRUE);
  }
}

/**
 * Implementation of hook_luceneapi_facet().
 */
function my_module_luceneapi_facet($module, $type = NULL) {
  if ($type != 'node') {
    return;
  }
  $facets = array();

  // gets node types, defines content type facet
  $facets['clean_type'] = array(
    'title' => t('Type'),
    'element' => 'clean_type',
    'field' => 'clean_type',
    'type' => 'checkboxes',
    'callback' => 'luceneapi_facet_multiterm_callback',
    'callback arguments' => array(luceneapi_facet_value_get('clean_type', array()), 'clean_type'),
    'description' => t('Filter by clean content type.'),
  );

  return $facets;
}

/**
 * Implementation of hook_luceneapi_facet_empty().
 */
function my_module_luceneapi_facet_empty($facets, $realm, $module) {
  $type = luceneapi_index_type_get($module);
  if ($type != 'node') {
    return;
  }

  // gets "empty search" settings
  $variable = sprintf('luceneapi_facet:%s:empty', $module);
  $empty_settings = variable_get($variable, array());

  // gets "hard limit"
  $variable = sprintf('luceneapi_facet:%s:hard_limit', $module);
  $limit = variable_get($variable, 20);

  $items = array();
  foreach ($facets as $name => $facet) {
    if ('clean_type' == $name) {
      $sql = 'SELECT IF(n.type = \'panel\', \'page\', n.type) AS id, IF(t.name = \''. t('Panel') .'\', \''. t('Page') .'\', t.name), COUNT(*) AS num'
           .' FROM {node} n'
           .' LEFT JOIN {node_type} t ON n.type = t.type'
           .' WHERE n.status = 1'
           .' GROUP BY IF(n.type = \'panel\', \'page\', n.type)'
           .' ORDER BY num DESC';
      $sql = luceneapi_node_type_condition_add($sql);
    }
    else {
      continue;
    }

    // ads limit clause if one is set
    if ($limit) {
      $sql .= sprintf(' LIMIT %d', $limit);
    }

    // initializes array containing facet information
    $rows = array();
    if ($result = db_query(db_rewrite_sql($sql))) {
      while ($row = db_fetch_object($result)) {
        $path = sprintf('search/%s/%s:%s', $module, $facet['element'], $row->id);
        $rows[$row->id] = array(
          'function' => 'luceneapi_facet_link',
          'text' => $row->id,
          'path' => $path,
          'options' => array(),
        );
        if ($empty_settings['counts']) {
          $rows[$row->id]['count'] = $row->num;
        }
        else {
          $rows[$row->id]['count'] = 0;
        }
      }
    }

    // if there are items ($rows), appends to $items[$name]
    if (!empty($rows)) {
      $items[$name] = array(
        'title' => $facet['title'],
        'field' => $facet['field'],
        'element' => $facet['element'],
        'selected' => array(),
        'count' => array(),
        'items' => $rows,
      );
    }
  }

  return $items;
}
April 17, 2010Drupal, Lucene, Panels, search, Search Lucene API

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options