Index: project.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/project.module,v
retrieving revision 1.361
diff -u -p -r1.361 project.module
--- project.module	23 Jul 2010 04:12:12 -0000	1.361
+++ project.module	18 Aug 2010 00:13:03 -0000
@@ -554,6 +554,26 @@ function project_node_load($arg) {
 }
 
 /**
+ * Menu loader callback to load a project type.
+ *
+ * @param $arg
+ *   The menu argument to attempt to load a project type from.
+ *
+ * @return
+ *   The taxonomy term representing the project type, or FALSE.
+ */
+function project_type_load($arg) {
+  // Get all of the top-level terms in the project type vocabulary.
+  $tree = taxonomy_get_tree(_project_get_vid(), 0, -1, 1);
+  foreach ($tree as $project_type_term) {
+    if (strcasecmp($project_type_term->name, $arg) == 0) {
+      return $project_type_term;
+    }
+  }
+  return FALSE;
+}
+
+/**
  * Menu loader callback to load a project node if the edit tab needs subtabs.
  *
  * Load a project_project node if the given nid is a project_project node and
@@ -1337,3 +1357,49 @@ function project_token_values($type = 'a
   }
 }
 
+/**
+ * Render the HTML for a given block.
+ *
+ * This function takes a module and a delta, loads the block info from the DB,
+ * invokes the function to compute the block content, and then renders the
+ * block to HTML via theme('block'). This is basically a heavily pared back
+ * implementation of block_list() for a single block.
+ *
+ * @param string $module
+ *   The module that defines the block we're rendering.
+ * 
+ * @param string $delta
+ *   The delta of the block that we're rendering.
+ * 
+ * @return
+ *   The rendered HTML for the given block.
+ * 
+ * @see block_list()
+ * @see theme_block()
+ */
+function project_block_render($module, $delta) {
+  // Load the title, since that can be modified administratively.  
+  $title = db_result(db_query("SELECT title FROM {blocks} WHERE module = '%s' AND delta = '%s'", $module, $delta));
+
+  $block_output = module_invoke($module, 'block', 'view', $delta);
+  foreach ($block_output as $k => $v) {
+    $block->$k = $v;
+  }
+
+  // Set the module and delta so that the blocks contain
+  // at least the minimum of expected information.
+  $block->module = $module;
+  $block->delta = $delta;
+
+  if (isset($block->content) && $block->content) {
+    // Override default block title if a custom display title is present.
+    if ($title) {
+      // Check plain admin-generated titles, but not titles set in code.
+      $block->subject = $title == '<none>' ? '' : check_plain($title);
+    }
+    if (!isset($block->subject)) {
+      $block->subject = '';
+    }
+  }
+  return theme('block', $block);
+}
Index: solr/project_solr.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/solr/project_solr.module,v
retrieving revision 1.76
diff -u -p -r1.76 project_solr.module
--- solr/project_solr.module	16 Aug 2010 21:28:25 -0000	1.76
+++ solr/project_solr.module	18 Aug 2010 00:13:04 -0000
@@ -25,6 +25,13 @@ function project_solr_menu() {
     'access arguments' => array('access content'),
     'type' => MENU_NORMAL_ITEM,
   );
+  $items['project/%project_type/categories'] = array(
+    'title' => 'Categories',
+    'page callback' => 'project_solr_category_page',
+    'page arguments' => array(1),
+    'access arguments' => array('access content'),
+    'type' => MENU_NORMAL_ITEM,
+  );
   return $items;
 }
 
@@ -42,6 +49,13 @@ function project_solr_theme() {
         'num_found' => NULL,
       ),
     ),
+    'project_solr_category_page' => array(
+      'template' => 'project-solr-category-page',
+      'variables' => array(
+        'project_type' => NULL,
+        'categories' => array(),
+      ),
+    ),
   );
 }
 
@@ -367,6 +381,37 @@ function project_solr_browse_page($term_
   return $output;
 }
 
+/**
+ * Page callback for the listing of per-type categories.
+ */
+function project_solr_category_page($project_type) {
+  $tree = taxonomy_get_tree(_project_get_vid(), $project_type->tid);
+  if (empty($tree)) {
+    return drupal_not_found();
+  }
+  $categories = array();
+  foreach ($tree as $category_term) {
+    $categories[$category_term->tid] = $category_term->name;
+  }
+  return theme('project_solr_category_page', $project_type, $categories);
+}
+
+/**
+ * Implementation of template_preprocess_project_solr_category_page().
+ */
+function project_solr_preprocess_project_solr_category_page(&$variables) {
+  $project_type = $variables['project_type'];
+  $categories = $variables['categories'];
+
+  foreach ($categories as $tid => $category_name) {
+    $delta = 'project_solr_category_block_' . $tid;
+    $variables[$delta] = project_block_render('project_solr', $delta);
+  }
+
+  $search_path = 'project/' . drupal_strlower($project_type->name) . '/categories';
+  $variables['version_form'] = drupal_get_form('project_solr_version_form', $search_path);
+}
+
 //----------------------------------------
 // Blocks
 //----------------------------------------
@@ -376,7 +421,7 @@ function project_solr_browse_page($term_
  */
 function project_solr_block($op = 'list', $delta = 0, $edit = array()) {
   if ($op == 'list') {
-    return array(
+    $blocks = array(
       'project_solr_categories' => array(
         'info' => t('Project: categories'),
         'cache' => BLOCK_CACHE_PER_PAGE,
@@ -390,6 +435,17 @@ function project_solr_block($op = 'list'
         'cache' => BLOCK_CACHE_PER_PAGE,
       ),
     );
+    $vid = _project_get_vid();
+    $tree = taxonomy_get_tree($vid, 0);
+    foreach ($tree as $term) {
+      if ($term->depth > 0) {
+        $blocks['project_solr_category_' . $term->tid] = array(
+          'info' => t('Project Solr: Project category %category', array('%category' => $term->name)),
+          'cache' => BLOCK_CACHE_GLOBAL,
+        );
+      }
+    }
+    return $blocks;
   }
 
   if ($op == 'view' && apachesolr_has_searched() && ($response = apachesolr_static_response_cache()) && ($query = apachesolr_current_query())) {
@@ -529,6 +585,104 @@ function project_solr_block($op = 'list'
       );
     }
   }
+  $matches = array();
+  if ($op == 'view' && preg_match('/project_solr_category_(\d+)/', $delta)) {
+    return project_solr_view_category_block($matches[1]);
+  }
+}
+
+/**
+ * Gather the content for a give project_solr category block.
+ *
+ * @tid
+ *   The taxonomy term ID for the category to fetch the results of.
+ */
+function project_solr_view_category_block($tid) {
+  $term = taxonomy_get_term($tid);
+  $project_type = taxonomy_get_term($term->parent);
+
+  // Build a simple query.
+  $base_path = 'project/' . drupal_strtolower($project_type->name) . '/categories';
+  
+  $filters = isset($_GET['filters']) ? $_GET['filters'] : '';  
+  $query = apachesolr_drupal_query('', $filters, '', $base_path);  
+
+  $params = array(
+    // The fields to return.
+    'fl' => 'id,nid,title',
+    'start' => 0,
+    'rows' => 5,  // TODO: make this configurable.
+    // We need to be able to facet on the project vid value
+    // so we can pull project categories.
+    'facet.field' => array(
+      'type',
+      'im_vid_' . _project_get_vid(),
+      'im_project_release_api_tids',
+    ),
+    // Filters are set below via explicit calls to add_filter() rather than 
+    // here in the $params array. This is because any filters passed to 
+    // apachesolr_drupal_query() will override filters set via the $params
+    // array during the prepare_query() invocation.
+    'fq' => array(),
+    'facet' => 'true',
+    'facet.mincount' => 1,
+    'facet.sort' => 'true',
+    'sort' => variable_get('project_solr_default_sort', 'sort_title asc'),
+  );
+
+  apachesolr_search_add_facet_params($params, $query);
+
+  // Allow modules to alter the query prior to statically caching it.
+  // This can e.g. be used to add available sorts.
+  foreach (module_implements('apachesolr_prepare_query') as $module) {
+    $function_name = $module . '_apachesolr_prepare_query';
+    $function_name($query, $params, 'project_solr_view_category_block');
+  }
+
+  // We add our fields after the prepare_query() because prepare_query()
+  // generates a call to parse_filters() which destroys anything that
+  // does not get passed into the query on construction.
+  // We explicitly filter the blocks to only show projects with the right
+  // category. These values are not passed in the url, so we add them here
+  // ourselves to ensure we only get the content we want.
+  $query->add_filter('type', 'project_project');
+  $query->add_filter('im_vid_' . _project_get_vid(), $tid);
+
+  // Cache the built query. Since all the built queries go through
+  // this process, all the hook_invocations will happen later.
+  apachesolr_current_query($query);
+
+  // This hook allows modules to modify the query and params objects.
+  apachesolr_modify_query($query, $params, 'drupalorg_search_execute_base_query');
+
+  $solr = apachesolr_get_solr();
+  $response = $solr->search($query->get_query_basic(), $params['start'], $params['rows'], $params);
+
+  apachesolr_static_response_cache($response);
+  apachesolr_has_searched(TRUE);
+
+  $items = array();
+  if ($response->response->numFound > 0) {
+    foreach ($response->response->docs as $doc) {
+      $items[] = l($doc->title, 'node/' . $doc->nid);
+    }
+  }
+
+  if ($items) {
+    // Add the "more" link.
+    // TODO
+/*
+    $items[] = array(
+      'data' => l(t('More @title', array('@title' => $facet['info'])), $order_query->get_path(), array('query' => $query_values)),
+      'class' => 'all',
+    );
+*/
+    return array(
+      'subject' => check_plain($term->name),
+      'content' => theme('item_list', $items),
+    );
+  }
+  return array();
 }
 
 /**
@@ -854,3 +1008,62 @@ function theme_project_solr_no_count_fac
   $options['attributes']['class'] = implode(' ', $options['attributes']['class']);
   return apachesolr_l($facet_text,  $path, $options);
 }
+
+//----------------------------------------
+// Version-selector form
+//----------------------------------------
+
+/**
+ * This generates a form containing version selection and a submit button.
+ * 
+ * Generate a form with a version selection to allow filtering page content
+ * based on the API compatibility version. Also includes path to allow the
+ * form to potentially submit to other urls if desired.
+ * 
+ * @param string $path
+ *   The base path to which the version form will redirect.
+ */
+function project_solr_version_form(&$form_state, $path) {
+  $query = apachesolr_current_query();
+
+  $form = array(
+    '#attributes' => array('class' => 'clear-block'),
+  );
+
+  // Add version select field to our form.
+  $version_alias = variable_get('project_solr_project_release_api_tids_alias', 'api_version');
+
+  $form[$version_alias] = project_solr_get_api_version_field($query, t('Select version'));
+
+  $form['path'] = array(
+    '#type' => 'value',
+    '#value' => $path,
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Search'),
+  );
+  return $form;
+}
+
+/**
+ * Create a query with the right version filter and redirect to the right page.
+ * 
+ * Create a new query, add any version filtering if it was selected in the
+ * form, and redirect back to the relevant page with the appropriate filter
+ * string.
+ */
+function project_solr_version_form_submit($form, &$form_state) {
+  // We create a new query with our base path so that we don't need to remove
+  // any existing drupal_core selection, and so that the implict type and 
+  // module tid filters don't end up in the url string.
+  $query = apachesolr_drupal_query('', '', '', $form_state['values']['path']);
+
+  $version_alias = variable_get('project_solr_project_release_api_tids_alias', 'api_version');
+
+  if (!empty($form_state['values'][$version_alias])) {
+    $query->add_filter($version_alias, $form_state['values'][$version_alias]);
+  }
+
+  $form_state['redirect'] = array($query->get_path(), $query->get_url_queryvalues());
+}
