Hi David

I'm trying to integrate Faceted Search with the excellent Context module, by allowing searches to activate conditions. I was wondering if you could please give me a nudge in the right direction.

To integrate the two, I need to provide a keyed array of all options to the Context module. In the array, I need a unique ID as the key to identify each category, and the label of the category as the value. This is so that at a later stage I can find out which categories of which facet are in the current search and enable the right context.

So I could create this unique ID by combining the environment ID, the facet key and the category. Here's the outline of what I'm talking about (this is not real code but just to illustrate):

$options = array(
  '3-12-455' => 'Sport', // the "Sport" taxonomy term, a taxonomy_facet.
  '3-1-document' => 'Document', // the "Document" content type, a content_type_facet.
);
return $options;

The key '3-12-455' means "Environment ID = 3; Facet key (vocabulary) = 12; Term ID = 455".
The key '3-1-document' means "Environment ID =3; Facet key = 1; Content type = document".

So each category of each facet of each environment has a unique id.

The problem is that I can't seem to find a common interface to retrieve a category's id. I want to just call $category->get_id() or something similar, and a taxonomy_facet_category should return a term id, whereas a content_type_facet_category should return the machine-name of the content type.

Does anything like this exist? Perhaps I'm going about this in the wrong way? Here's an example of how this works with taxonomy: http://drupal.org/node/343110#comment-1533748

Of course, each term id is unique.

And here's what I've got so far:

// $Id$

/**
 * @file
 * Main module code for context - faceted search integration.
 */

/**
 * Implementation of hook_context_conditions().
 */
function context_facets_context_conditions() {
  $items = array();
	$environments = faceted_search_get_env_ids();

  // $all_options stores all facets from the current environment.
  $all_options = array();

  foreach ($environments as $env_id) {
    $env = faceted_search_env_load($env_id);
    $env->prepare();
    $env->execute();

    foreach ($env->get_filters() as $index => $facet) {
      if (!$facet->is_browsable()) {
        continue; // Not a facet.
      }

      $categories = $env->load_categories($facet, 0, NULL);
      $options = array();

      foreach ($categories as $category_index => $category) {
        // debugging 
        $id = $env_id . ':' . $facet->get_id() . ' : ' . $facet->get_text();
        $options[$category_index] = $id . ' : ' . $category->get_label(TRUE);
      }

      $items['facet_' . $env_id . '_' . $index] = array(
        '#title' => t('Faceted Search: ' . $env->name . ': ' . $facet->get_label()),
        '#description' => t('Set this context when a faceted search with these facets is executed.'),
        '#options' => $options,
        '#type' => 'checkboxes',
      );
    }
  }

  return $items;
}

/**
 * Implementation of hook_faceted_search_query_alter().
 */
function context_facets_faceted_search_query_alter($env, $query) {
  foreach ($env->get_filters() as $index => $facet) {
    if ($facet->is_active()) {
      foreach ($facet->get_active_path() as $category_index => $category) {
        // debugging
        //var_dump($facet->get_label() . $facet->get_key() . $facet->get_id());
        //var_dump($facet->get_text());
      }
    }
  }
}

Thanks for any tips!
Mark

Comments

Mark Theunissen’s picture

Here's a newer, working version of the code which supports taxonomy and content type facets:

http://drupal.org/node/450726

There's also a bug in there somewhere that I'm battling to track down.

David Lesieur’s picture

The way you currently determine the id (using get_id() and get_text()) is generic because all filters have those methods. You'll probably want to use get_key() as well, because that will distinguish classes of filters. Having to use both get_key() and get_id() to distinguish filters is something that annoys me, but so far I never got to clean this up.

About get_text(): Note that it can contain all sorts of characters, and that its format may vary depending on the facet class. I'm not sure what implications that can have on the Context module, which I have never used, unfortunately... (looks interesting though!)

Mark Theunissen’s picture

Thanks for the response. Maybe I'm missing something, or maybe I wasn't clear enough in my post, but I can't seem to find the generic function you are talking about. For example, let's look at the interfaces for two facet modules, content_type_facet.module and taxonomy_facets.module.

content_type_facet.module

class content_type_facet_category extends faceted_search_category {
  var $_type = NULL;
  var $_name = '';

  function content_type_facet_category($type, $name, $count = NULL);
  function get_label($html = FALSE);
  function build_results_query(&$query);
}

taxonomy_facets.module

/**
 * A category for non-hierarchical taxonomy-based facets.
 *
 * @see taxonomy_facet()
 */
class taxonomy_facet_category extends faceted_search_category {
  var $_tid = NULL;
  var $_name = '';

  function taxonomy_facet_category($tid, $name, $count = NULL);
  function get_label($html = FALSE);
  function build_results_query(&$query);
}

taxonomy_facet_category defines the variable $_tid and content_type_facet_category defines the variable $_type, and as far as I can see there is no generic base class method which will return one or the other depending on what type of facet it is.

Maybe we could add a base class method and then provide an override in each derived class? Is there another way to do this? Thanks!

David Lesieur’s picture

Well, the get_key() method I was mentioning is not in the faceted_search_category hierarchy. It comes from the faceted_search_filter class, which is the base class for all facet classes.

At the moment, the closest thing to what you're looking for is get_text(), but it belongs to the facet classes and can only return the text for the facet's active category. Since the facet classes always know what kind of category class they're dealing with, and since not all facets have the same key:value (search text) format, get_text() is implemented differently for each type of category and no generic accessor has been designed for category classes. I'm pretty sure we could imagine a generic interface at the category level (and this would be more elegant), but in your case that is really needed only if you need an id even for non-active categories. If you need an id only for active categories (i.e. categories used in the current search), then using get_text() as you currently do should be fine (but you'll want to include get_key() in the id as well).

Mark Theunissen’s picture

StatusFileSize
new58.43 KB

Yes, that's what I thought: get_text() only deals with the active search.

I think that I do need to get the inactive categories too. Basically, when a user creates a context, they need to set the conditions that trigger the context. I thus need to present a list of all facets and categories in a given environment, (none of which will be active because it's an admin form), so the user can select the ones they want.

Hmm, maybe there is another way to do this. I've looked at the source for the faceted search "guided" block and the admin form and I can't see anything.

Take a look at the screenshot I've attached of the context administration section. In it, I have clicked "Faceted Search: find: Research location" which means the environment is called "find" and the taxonomy vocab is called "Research location". Now I check the boxes on the right, each of which is a category and needs to have a unique id that identifies it's environment, facet and category.

David Lesieur’s picture

Title: Help with Faceted Search API » Help with Faceted Search API / Integration with the Context module

That would create an awful lot contexts... Wouldn't one context per facet be sufficient in most use cases? The context would be triggered when the facet becomes active.

How do you plan to deal with multiple active facets (or categories)? Which context would then be active? Can more than one context be active at the same time? (sorry, I still haven't tried the Context module :-))

Mark Theunissen’s picture

Wouldn't one context per facet be sufficient in most use cases? The context would be triggered when the facet becomes active.

Maybe, but not in my use case... ;)

I need a context to become active when a specific set of categories become active. For example, I may want to specify the following conditions:

1) Content type active search must be "document" types
2) Taxonomy active search must be "North America" from vocabulary "Region" and it must also be "Marketing" from "Company division".

So when a user has a faceted search with marketing documents from North America, I want to trigger the context.

How do you plan to deal with multiple active facets (or categories)? Which context would then be active? Can more than one context be active at the same time? (sorry, I still haven't tried the Context module :-))

Actually, I'm not sure how the context behaviour will handle this. I'm guessing that the behaviour will be undefined, as in, if you set two different contexts to trigger on the same active category, then it will be either the one or the other, but you can't count on anything.

The other problem I'm having is that I feel I'm mis-using the hook_faceted_search_query_alter(). I'm currently implementing this hook and then using it to determine what the active search is for the current page request, and setting the context in there.

Is there a more elegant solution to hook into this? For example, in setting a context based on taxonomy, we use hook_nodeapi(). In setting a context based on the path, we use hook_init(). But I tried using hook_init and I couldn't get the active search. Any other ideas?

Mark Theunissen’s picture

The other problem I'm having is a warning:

user warning: Table 'temp_faceted_search_results_1' already exists query: CREATE TEMPORARY TABLE temp_faceted_search_results_1 (nid int unsigned NOT NULL, PRIMARY KEY (nid)) Engine=HEAP SELECT n.nid AS nid, 5 * POW(2, .........

I suspect this is because I'm not using the API correctly.

David Lesieur’s picture

I'm not sure that hook_faceted_search_query_alter() is ideal since the search has not really been performed yet when this hook is invoked. I'd be open to invoking a new hook at the end of faceted_search::execute(). Patch welcome!

Regarding the warning: Are you calling faceted_search::execute() somewhere? If so, make sure that ready() is FALSE before calling execute().

Mark Theunissen’s picture

Yes, you're absolutely right. I did some debugging and it seems that in certain circumstances, faceted_search::execute() is being called twice, the second time ready() is TRUE.

The problem is related to the fact that I'm using the hook! Because I am executing a search in the hook, then in the menu callback faceted_search_ui_stage_results() it is being called again, even though ready() == TRUE, because the page handler doesn't check (it assumes that it's not ready).

So a proper hook invoke would solve two problems, in theory. I will write a patch. Thanks for all the help!

Mark Theunissen’s picture

Patch attached. My problem is almost sorted out, just need to fix a caching issue in Context module.