Index: solr/project_solr.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/solr/project_solr.module,v
retrieving revision 1.64
diff -u -r1.64 project_solr.module
--- solr/project_solr.module	10 Jul 2010 05:12:43 -0000	1.64
+++ solr/project_solr.module	16 Jul 2010 23:49:58 -0000
@@ -178,7 +178,7 @@
   try {
     $output = '';
 
-    $parent_term = db_fetch_object(db_query("SELECT t.tid, t.name, t.description FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) = LOWER('%s')", _project_get_vid(), $term_name));
+    $parent_term = project_solr_get_parent_term($term_name);
 
     if (!$parent_term) {
       // XXX: this is the Drupal 5 way...
@@ -202,10 +202,12 @@
     include_once drupal_get_path('module', 'project_solr') .'/ProjectSolrQuery.php';
 
     $query = new ProjectSolrQuery(apachesolr_get_solr(), $text_query, $filters, $sort, 'project/' . drupal_strtolower($parent_term->name));
+
     if (is_null($query)) {
       throw new Exception(t('Could not construct a Solr query.'));
     }
 
+    // @TODO: The drupal_core alias should be a site-specific setting.
     $query->add_field_aliases(array('im_project_release_api_tids' => 'drupal_core'));
 
     $params = array(
@@ -281,6 +283,7 @@
 
     $output .= '</div>'; // id="project-overview"
     $output .= theme('pager', NULL, $params['rows'], 0);
+    $output = drupal_get_form('project_solr_browse_form', $parent_term) . $output;
   }
   catch (Exception $e) {
     watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
@@ -318,19 +321,7 @@
   if ($op == 'view' && apachesolr_has_searched() && ($response = apachesolr_static_response_cache()) && ($query = apachesolr_current_query())) {
     if ($delta == 'project_solr_categories') {
       // Find the parent term for this query.
-      $project_vid = _project_get_vid();
-      if (isset($response->responseHeader->params->fq)) {
-        foreach ($response->responseHeader->params->fq as $query_snippet) {
-          if (preg_match('/^im_vid_'. _project_get_vid() .':(.*)$/', $query_snippet, $matches)) {
-            $tid = trim($matches[1]);
-            $term = taxonomy_get_term($tid);
-            if ($term->vid == $project_vid) {
-              $parent_term = $term;
-              break;
-            }
-          }
-        }
-      }
+      $parent_term = project_solr_get_parent_term();
 
       if (!isset($parent_term)) {
         // This facet cannot process generic queries.
@@ -456,6 +447,173 @@
 }
 
 /**
+ * Build project browsing navigation form.
+ */
+function project_solr_browse_form(&$form_state, $parent_term = NULL) {
+  drupal_add_js(drupal_get_path('module', 'project_solr') .'/project_solr.js', 'module', 'footer', TRUE);
+
+  $response = apachesolr_static_response_cache();
+  $query    = apachesolr_current_query();
+
+  // Get the terms at the current depth.
+  $current_tid = $parent_term->tid;
+  foreach ($query->get_filters() as $field) {
+    if ($field['#name'] == 'tid') {
+      $current_tid = $field['#value'];
+      break;
+    }
+  }
+
+  $tree = taxonomy_get_tree(_project_get_vid(), $parent_term->tid, -1, 1);
+  foreach ($tree as $term) {
+    $terms[$term->tid] = $term->name;
+  }
+  if (!empty($terms)) {
+    $vocab = taxonomy_vocabulary_load(_project_get_vid());
+    asort($terms);
+    $terms = array('' => '') + $terms;
+    $form['tid'] = array(
+      '#type' => 'select',
+      '#options' => $terms,
+      '#title' => $vocab->name,
+      '#default_value' => $current_tid,
+    );
+  }
+
+  if (module_exists('project_release')) {
+    $terms = array('' => '');
+    $active_terms = array_reverse(project_release_compatibility_list(FALSE), TRUE);
+    foreach ($active_terms as $tid => $term_name) {
+      $active = $query->has_filter('im_project_release_api_tids', $tid);
+      if ($active) {
+        $current_tid = $tid;
+      }
+      $terms[$tid] = $term_name;
+    }
+    if (!empty($terms)) {
+      $form['api_version'] = array(
+        '#title' => t('Filter by compatibility'),
+        '#type' => 'select',
+        '#options' => $terms,
+        '#default_value' => $current_tid,
+      );
+    }
+  }
+
+  // retrieve all the related vocabularies related to project so facets can be built
+  $vocabs = taxonomy_get_vocabularies('project_project');
+
+  // because the values don't automatically become filters, we need to retrieve 
+  // the pseudo filters from the url and extract them from the string.
+  $filters = $_GET['filters'] ? $_GET['filters'] : '';
+  foreach ($vocabs as $vid => $vocab) {
+    // Skip freetagging vocabularies, and the hard-coded project type
+    // vocabulary, since things will go crazy if you set this for itself.
+    if (!$vocab->tags && $vid != $project_vid && $parent_term->tid == variable_get('project_type_associated_tid_' . $vid, NULL)) {
+      // extract set values from our pseudo filters
+      $selected = $query->filter_extract($filters, 'vid_'. $vid);
+      $selected = !empty($selected[0]['#value']) ? explode(',', $selected[0]['#value']) : array();
+
+      // build checkbox items for all terms in related vocabularies
+      $tree     = taxonomy_get_tree($vid, 0, -1, 1);
+      $terms    = array();
+      foreach ($tree as $term) {
+        $terms[$term->tid] = $term->name;
+      }      
+      asort($terms);
+      $form['vid_' . $vid] = array(
+        '#title' => t($vocab->name),
+        '#type' => 'checkboxes',
+        '#options' => $terms,
+        '#default_value' => $selected,
+      );
+    }
+  }
+
+  $form['text'] = array(
+    '#title' => t('Search @project_type', array('@project_type' => drupal_strtolower($parent_term->name))),
+    '#type' => 'textfield',
+    '#default_value' => isset($_GET['text']) ? $_GET['text'] : '',
+    '#size' => 20,
+  );
+
+  $sorts = array(
+    'sort_title asc' => t('Title'),
+    'created desc' => t('Creation date'),
+  );
+  if (module_exists('project_release')) {
+    $sorts['ds_project_latest_release desc'] = t('Last release');
+    $sorts['ds_project_latest_activity desc'] = t('Recent activity');
+  }
+  if (module_exists('project_usage')) {
+    $sorts['sis_project_release_usage desc'] = t('Usage statistics');
+  }
+  $sorts[''] = ('Relevancy');
+
+  // Determine default value based on current value of solrsort.
+  $solrsort = isset($_GET['solrsort']) ? $_GET['solrsort'] : '';
+  $solrsort = explode(' ', $solrsort);
+  $solrsort[0] = preg_replace('/_[0-9]+$/', '', $solrsort[0]);
+  $solrsort = implode(' ', $solrsort);
+  if (!$solrsort && !($form['text']['#default_value'])) {
+    $solrsort = 'sort_title asc';
+  }
+
+  $form['solrsort'] = array(
+    '#title' => t('Sort by'),
+    '#type' => 'select',
+    '#default_value' => $solrsort,
+    '#options' => $sorts,
+  );
+
+  $form['path'] = array(
+    '#type' => 'value',
+    '#value' => 'project/' . drupal_strtolower($parent_term->name),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+  return $form;
+}
+
+function project_solr_browse_form_submit($form, &$form_state) {
+  $query = new ProjectSolrQuery(apachesolr_get_solr(), $form_state['values']['text'], '', '', $form_state['values']['path']);
+
+  if (!empty($form_state['values']['tid'])) {
+    $query->add_filter('tid', $form_state['values']['tid']);
+  }
+
+  if (!empty($form_state['values']['api_version'])) {
+    // @TODO: The drupal_core alias should be a site-specific setting.
+    $query->add_filter('drupal_core', $form_state['values']['api_version']);
+  }
+  
+  // loop over all project-related vocabularies and create filters
+  // for any values that have been posted. we utilize a pseudo-style
+  // filter since we can't place im_vid_X:X OR im_vid_X:X style
+  // subqueries in the URL
+  $vocabs = taxonomy_get_vocabularies('project_project');
+  foreach ($vocabs as $vid => $vocab) {
+    $values = array_filter($form_state['values']['vid_' . $vid]);
+    if (!empty($values)) {
+      $query->add_filter('vid_' . $vid, implode(',', $values));
+    }
+  }
+
+  // Rewrite solrsort parameter with API version tid, if necessary.
+  $solrsort = explode(' ', $form_state['values']['solrsort']);
+  if (module_exists('project_release')) {
+    if ($solrsort[0] == 'ds_project_latest_release' || $solrsort[0] == 'ds_project_latest_activity') {
+      if (!empty($form_state['values']['api_version'])) {
+        $solrsort[0] .= '_'. $form_state['values']['api_version'];
+      }
+    }
+  }
+  call_user_method_array('set_solrsort', $query, $solrsort);  
+  $form_state['redirect'] = array($query->get_path(), $query->get_url_queryvalues());  
+}
+/**
  * Append the API tid to selected fields that might be in the string.
  */
 function project_solr_append_api_term($values, $tid) {
@@ -545,3 +703,76 @@
   return apachesolr_l($facet_text,  $path, $options);
 }
 
+/**
+ * Retrieves and statically caches the parent term for a given page.
+ * Can retrieve based on a term name in the url (project pages) or based on the
+ * response object
+ */
+function project_solr_get_parent_term($term_name = NULL) {
+  $parent_term = NULL;
+  if (isset($term_name)) {
+    $parent_term = db_fetch_object(db_query("SELECT t.tid, t.name, t.description FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) = LOWER('%s')", _project_get_vid(), $term_name));
+  }
+  else {
+    $response = apachesolr_static_response_cache();
+    $query = apachesolr_current_query();    
+    $project_vid = _project_get_vid();
+
+    if (isset($response->responseHeader->params->fq)) {
+      foreach ($response->responseHeader->params->fq as $query_snippet) {
+        if (preg_match('/^im_vid_'. $project_vid .':(.*)$/', $query_snippet, $matches)) {
+          $tid = trim($matches[1]);
+          $term = taxonomy_get_term($tid);
+          if ($term->vid == $project_vid) {
+            $parent_term = $term;
+            break;
+          }
+        }
+      }
+    }
+  }
+  project_solr_parent_term_static_cache($parent_term);
+  return $parent_term;
+}
+
+/**
+ * Statically cache the parent term for use in areas where the term name or 
+ * response doesn't have all the necessary values.
+ */
+function project_solr_parent_term_static_cache($term = NULL) {
+  static $parent_term;
+  if ($term) {
+    $parent_term = $term;
+  }
+  return $parent_term;
+}
+
+/**
+ * Implementation of hook_apachesolr_modify_query().
+ */
+function project_solr_apachesolr_modify_query(&$query, &$params, $caller = '') {
+  // the parent term isn't directly accessible to us in this context
+  // so we pull it from the static cache
+  $parent_term  = project_solr_parent_term_static_cache();
+  $project_vid  = _project_get_vid();
+  $vocabs       = taxonomy_get_vocabularies('project_project');
+
+  // create (im_vid_X:X OR im_vid_X:X) style subqueries for all of the
+  // project-type specific facets.
+  $filterstring = isset($_GET['filters']) ? $_GET['filters'] : '';
+  foreach ($vocabs as $vid => $vocab) {
+    // Skip freetagging vocabularies, and the hard-coded project type
+    // vocabulary, since things will go crazy if you set this for itself.      
+    if (!$vocab->tags && $vid != $project_vid && $parent_term->tid == variable_get('project_type_associated_tid_' . $vid, NULL)) {
+      $filter = $query->filter_extract($filterstring, 'vid_'. $vid);
+      if (!empty($filter)) {
+        // pull apart our pseudo-style filter and create a true subquery out of it.
+        $tid_query = apachesolr_drupal_query();
+        foreach(explode(',', $filter[0]['#value']) as $value) {
+          $tid_query->add_filter('im_vid_'. $vid, $value);
+        }
+        $query->add_subquery($tid_query, 'OR');
+      }
+    }
+  }
+}
\ No newline at end of file
Index: solr/project_solr.js
===================================================================
RCS file: solr/project_solr.js
diff -N solr/project_solr.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ solr/project_solr.js	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,30 @@
+// $Id$
+
+Drupal.behaviors.projectSolrBrowseForm = function() {
+  // Disable relevancy option if no text is entered.
+  if (!$('#edit-text').val()) {
+    $('#edit-solrsort').children('[value=]').attr({'disabled':'disabled'});
+  }
+  // If length is zero as user starts typing text, select relevancy option.
+  $('#edit-text').keydown(function() {
+    if (($(this).val()).length == 0) {
+      $('#edit-solrsort').val("");
+    }
+  });
+  // If length is zero as user finishes typing text, deselect relevancy option.
+  $('#edit-text').keyup(function() {
+    if (($(this).val()).length == 0 && $('#edit-solrsort').val() == "") {
+      $('#edit-solrsort').val("sort_title asc");
+    }
+  });  
+  $('#edit-text').blur(function() {
+    // Enable relevancy option if text is entered.
+    if ($(this).val()) {
+      $('#edit-solrsort').children('[value=]').removeAttr('disabled');
+    }
+    // Disable relevancy option if no text is entered.
+    else {
+      $('#edit-solrsort').children('[value=]').attr({'disabled':'disabled'});
+    }
+  });  
+};
