'Project summary',
'description' => '',
'page callback' => 'project_solr_browse_summary_page',
'access arguments' => array('access content'),
'type' => MENU_NORMAL_ITEM,
);
$items['project/%'] = array(
'title' => 'Project summary',
'description' => '',
'page callback' => 'project_solr_browse_page',
'page arguments' => array(1),
'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;
}
/**
* Implementation of hook_theme().
*/
function project_solr_theme() {
return array(
'project_solr_no_count_facet_link' => array(
'arguments' => array(
'facet_text' => NULL,
'path' => '',
'options' => '',
'active' => FALSE,
'num_found' => NULL,
),
),
'project_solr_category_page' => array(
'arguments' => array(
'project_type' => NULL,
'categories' => array(),
'version_form' => NULL,
),
),
'project_solr_category_list' => array(
'arguments' => array(
'items' => array(),
),
),
);
}
/**
* Implementation of hook_nodeapi().
*
* Whenever a release node is edited or submitted, if the node is now
* published, reindex the project node associated with that release.
*/
function project_solr_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'update':
case 'insert':
if ($node->type == 'project_release' && $node->status) {
apachesolr_mark_node($node->project_release['pid']);
}
break;
}
}
/**
* Implementation of hook_form_alter().
*/
function project_solr_form_apachesolr_delete_index_form_alter(&$form, $form_state) {
$form['reindex_project'] = array(
'#type' => 'submit',
'#value' => t('Re-index all projects'),
'#submit' => array('project_solr_reindex_projects'),
);
$form['reindex_project_desc'] = array(
'#type' => 'item',
'#description' => t('This will only re-index the project content on your site.'),
);
}
function project_solr_reindex_projects($form, $form_state) {
db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {node} WHERE type = 'project_project')", time());
drupal_set_message(t('Marked all project content to be reindexed by Apache Solr.'));
}
//----------------------------------------
// Solr-related hooks
//----------------------------------------
/**
* Implementation of hook_apachesolr_facets().
*/
function project_solr_apachesolr_facets() {
$facets = array();
if (module_exists('project_usage')) {
$facets = array(
'sort_most_installed' => array(
'info' => t('Most installed'),
'facet_field' => 'sis_project_release_usage',
'direction' => 'desc',
),
);
}
return $facets;
}
/**
* Implementation of hook_apachesolr_update_index().
*
* This adds information about releases for the project to the Solr document
* so we can facet on releases (API compatibility terms, usage, etc), along
* with other project-specific metadata (e.g. shortname/uri).
*
* Beware that this hook is invoked for all nodes, so we should be careful in
* here to check that we're really dealing with a project node before trying
* to access any project-specifc data.
*/
function project_solr_apachesolr_update_index(&$document, $node) {
if (!empty($node->project['uri'])) {
$document->ss_project_uri = $node->project['uri'];
}
if (!empty($node->project)) {
// The initial "bs" is for boolean/single.
$document->bs_project_sandbox = !empty($node->project['sandbox']) ? '1' : '0';
}
if (module_exists('project_release') && !empty($node->project_release['releases'])) {
$document->bs_project_has_releases = '1';
$max_filetime = 0;
$max_official_filetime = 0;
$term_query = db_query("SELECT DISTINCT(tn.tid) FROM {node} n INNER JOIN {project_release_nodes} prn ON n.nid = prn.nid INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {term_data} td ON tn.tid = td.tid WHERE prn.pid = %d AND td.vid = %d", $node->nid, _project_release_get_api_vid());
while ($term = db_fetch_object($term_query)) {
$document->setMultiValue('im_project_release_api_tids', $term->tid);
$latest_activity = db_fetch_object(db_query_range("SELECT f.timestamp, prn.rebuild FROM {node} n INNER JOIN {project_release_nodes} prn ON n.nid = prn.nid INNER JOIN {project_release_file} prf ON prn.nid = prf.nid INNER JOIN {term_node} tn ON prn.nid = tn.nid INNER JOIN {files} f ON prf.fid = f.fid WHERE n.status = 1 AND tn.tid = %d AND prn.pid = %d ORDER BY f.timestamp DESC", $term->tid, $node->nid, 0, 1));
$filetime = $latest_activity->timestamp;
$key = 'ds_project_latest_activity_'. $term->tid;
$document->$key = apachesolr_date_iso($filetime);
if ($filetime > $max_filetime) {
$max_filetime = $filetime;
}
// Now, look for the most recent official release for this API version.
$key = 'ds_project_latest_release_'. $term->tid;
if ($latest_activity->rebuild == 0) {
// The latest activity is official, we're done.
$document->$key = apachesolr_date_iso($filetime);
if ($filetime > $max_official_filetime) {
$max_official_filetime = $filetime;
}
}
else {
$filetime = db_result(db_query_range("SELECT f.timestamp FROM {node} n INNER JOIN {project_release_nodes} prn ON n.nid = prn.nid INNER JOIN {project_release_file} prf ON prn.nid = prf.nid INNER JOIN {term_node} tn ON prn.nid = tn.nid INNER JOIN {files} f ON prf.fid = f.fid WHERE n.status = 1 AND prn.rebuild = 0 AND tn.tid = %d AND prn.pid = %d ORDER BY f.timestamp DESC", $term->tid, $node->nid, 0, 1));
if (!empty($filetime)) {
$document->$key = apachesolr_date_iso($filetime);
}
if ($filetime > $max_official_filetime) {
$max_official_filetime = $filetime;
}
}
}
// Set Latest Activity to be the last file update time for the newest release on the project.
$document->ds_project_latest_activity = apachesolr_date_iso($max_filetime);
if (!empty($max_official_filetime)) {
// Set Latest Release to be the last file update time for an official release on the project.
$document->ds_project_latest_release = apachesolr_date_iso($max_official_filetime);
}
if (module_exists('project_usage')) {
$weeks = variable_get('project_usage_active_weeks', array());
$week = reset($weeks);
$total_usage = 0;
$query = db_query("SELECT * FROM {project_usage_week_project} WHERE nid = %d AND timestamp = %d", $node->nid, $week);
while ($usage = db_fetch_object($query)) {
$key = 'sis_project_release_usage_'. $usage->tid;
$document->$key = $usage->count;
$total_usage += $usage->count;
}
$document->sis_project_release_usage = $total_usage;
}
}
}
/**
* Implementation of hook_apachesolr_modify_query().
*
* Extract all of the project-type-related filters, which will be present
* in the url in the format im_vid_X:Y im_vid_X:Z, and turn them into a
* relevant subquery of the format (im_vid_X:Y OR im_vid_X:Z). This is done to
* allow searching for results matching any of Y or Z inside of vocabulary X.
* e.g. Return all themes that are Fixed OR Fluid.
*/
function project_solr_apachesolr_modify_query(&$query, &$params) {
$filter_set = array();
// Grab vids for all project-type-related vocabularies
$vocabs = project_get_related_tids_map(TRUE);
$project_type_related_vids = array_keys($vocabs);
foreach ($query->get_filters() as $filter) {
// Grab any project-type-related filters (im_vid_X filters).
if (preg_match('/^im_vid_(.*)$/', $filter['#name'], $matches)) {
if (in_array($matches[1], $project_type_related_vids)) {
$filter_set[$filter['#name']][] = $filter['#value'];
$query->remove_filter($filter['#name'], $filter['#value']);
}
}
}
// Add the project-type-related filters back as an OR list.
foreach ($filter_set as $name => $filters) {
$tid_query = apachesolr_drupal_query();
foreach ($filters as $value) {
$tid_query->add_filter($name, $value);
}
$query->add_subquery($tid_query, 'OR');
}
// If we don't have any explicit filter for the sandbox status projects,
// add an explicit exclusion for sandbox projects or their related content
// (e.g. issues from sandbox projects). We want this for all solr queries.
if (!$query->get_filters('bs_project_sandbox')) {
// If not defined, we want to *hide* sandboxes (to match the
// #default_value for the exposed filter), hence 1, with the third
// 'exclude' parameter set to TRUE (inverts the sense of the filter).
$query->add_filter('bs_project_sandbox', '1', TRUE);
// As we're building the query for the project browsing pages, we try to
// add this filter to hide projects without releases, but only if the
// query already has a filter that hides sandboxes. The default filter
// above to exclude sandbox content won't have been added yet, so we won't
// have the filter for releases, either, unless we add it here ourselves
// (but only if we're building a query for project nodes).
if ($query->has_filter('type', 'project_project') && module_exists('project_release')) {
$query->add_filter('bs_project_has_releases', '1');
}
}
}
/**
* Implementation of hook_apachesolr_prepare_query().
*
* This just adds an alias to allow the im_project_release_api_tids to present
* itself as something different in the url. In the default case, instead of
* im_project_release_api_tids displaying in the url, api_version will display
* in the url, making the url more user-friendly.
*/
function project_solr_apachesolr_prepare_query(&$query, &$params) {
if (module_exists('project_release')) {
// Add a human-readable alias for the release filter.
$query->add_field_aliases(array('im_project_release_api_tids' => variable_get('project_solr_project_release_api_tids_alias', 'api_version')));
}
// Only add the project-specific sort if the query is filtering on projects.
if ($query->has_filter('type', 'project_project')) {
project_solr_add_sorts($query);
}
}
//----------------------------------------
// Page callbacks
//----------------------------------------
/**
* Summary project browsing page.
*/
function project_solr_browse_summary_page() {
$items = array();
$project_types = project_get_project_types();
foreach ($project_types as $term) {
$items[] = theme('project_type', $term);
}
drupal_set_title(t('Project types'));
return theme('item_list', $items);
}
function project_solr_browse_page($term_name) {
try {
$output = '';
$project_type = 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));
if (!$project_type) {
// XXX: this is the Drupal 5 way...
return drupal_not_found();
}
drupal_set_title(check_plain($project_type->name));
if (!empty($project_type->description)) {
$output .= theme('project_type_description', $project_type);
}
$text_query = isset($_GET['text']) ? $_GET['text'] : '';
$filters = isset($_GET['filters']) ? $_GET['filters'] : '';
$sort = isset($_GET['solrsort']) ? check_plain($_GET['solrsort']) : '';
// Validate sort parameter.
if ((!isset($sort) || !preg_match('/^([a-z0-9_]+ (asc|desc)(,)?)+$/i', $sort)) && empty($text_query)) {
$sort = variable_get('project_solr_default_sort', 'sort_title asc');
}
include_once drupal_get_path('module', 'project_solr') .'/ProjectSolrQuery.php';
$query = new ProjectSolrQuery(apachesolr_get_solr(), $text_query, $filters, $sort, 'project/' . drupal_strtolower($project_type->name));
if (is_null($query)) {
throw new Exception(t('Could not construct a Solr query.'));
}
$params = array(
'fl' => 'id,nid,title,body,format,comment_count,type,created,changed,score,url,uid,name,sis_project_release_usage,ds_project_latest_release,ds_project_latest_activity',
'rows' => variable_get('apachesolr_rows', 10),
'facet' => 'true',
'facet.mincount' => 1,
'facet.sort' => 'true',
'facet.field' => array(
'im_vid_'. _project_get_vid(),
'im_project_release_api_tids',
),
'facet.limit' => 200,
);
$page = isset($_GET['page']) ? $_GET['page'] : 0;
$params['start'] = $page * $params['rows'];
// This is the object that does the communication with the solr server.
$solr = apachesolr_get_solr();
// We add add_filter() parameters here to include all the constant
// filters for the query -- project nodes of the given top-level type that
// have releases (if project_release is enabled).
// We use add_filter() rather than $params['fq'] so that our filters
// are correctly passed to anything that uses our cached query.
$query->add_filter('type', 'project_project');
$query->add_filter('im_vid_'. _project_get_vid(), $project_type->tid);
// We can only filter on bs_project_has_releases for just official projects,
// since sandbox projects can never have releases.
if (module_exists('project_release') && $query->has_filter('bs_project_sandbox', '0')) {
$query->add_filter('bs_project_has_releases', '1');
}
// 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_browse_page');
}
// 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, 'project_solr');
if (!$query) {
return array();
}
// Force sort to be by the corresponding core compatibility if filtered.
$sort = $query->get_solrsort();
if (in_array($sort['#name'], array('ds_project_latest_release', 'ds_project_latest_activity'))
&& ($api_filters = $query->get_filters('im_project_release_api_tids'))) {
$first_filter = reset($api_filters);
$params['sort'] = $sort['#name'] .'_'. $first_filter['#value'] .' '. $sort['#direction'];
}
$response = $solr->search($query->get_query_basic(), $params['start'], $params['rows'], $params);
// The response is cached so that it is accessible to the blocks and anything
// else that needs it beyond the initial search.
$total = $response->response->numFound;
apachesolr_static_response_cache($response);
apachesolr_has_searched(TRUE);
// Set breadcrumb.
$breadcrumb = menu_get_active_breadcrumb();
drupal_set_breadcrumb($breadcrumb);
$output .= '
';
pager_query("SELECT %d", $params['rows'], 0, NULL, $total);
if ($total > 0) {
foreach ($response->response->docs as $doc) {
$doc->created = strtotime($doc->created);
$doc->changed = strtotime($doc->changed);
$output .= project_solr_render_search_result($doc);
}
}
else {
$output .= t('No projects found in this category.');
}
$output .= '
'; // id="project-overview"
$output .= theme('pager', NULL, $params['rows'], 0);
$output = drupal_get_form('project_solr_browse_projects_form', $project_type, 'project/' . drupal_strtolower($project_type->name)) . $output;
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
apachesolr_failure(t('Solr search'), is_null($query) ? $keys : $query->get_query_basic());
}
return $output;
}
//----------------------------------------
// Blocks
//----------------------------------------
/**
* Implementation of hook_block().
*
* @see project_solr_block_info()
* @see project_solr_block_view()
*/
function project_solr_block($op = 'list', $delta = 0, $edit = array()) {
// Instead of a monolithic function, split out this code into separate
// functions for each operation (which we're going to need when we port to
// D7, anyway).
switch ($op) {
case 'list':
return project_solr_block_info();
case 'view':
return project_solr_block_view($delta);
}
}
/**
* Declare all the blocks provided by project_solr.
*
* @see project_solr_block()
*/
function project_solr_block_info() {
$blocks = array(
'project_solr_categories' => array(
'info' => t('Project Solr: categories'),
'cache' => BLOCK_CACHE_PER_PAGE,
),
'project_solr_text' => array(
'info' => t('Project Solr: text search'),
'cache' => BLOCK_CACHE_PER_PAGE,
),
);
if (module_exists('project_release')) {
$blocks['project_solr_compability'] = array(
'info' => t('Project Solr: core compatibility'),
'cache' => BLOCK_CACHE_PER_PAGE,
);
}
return $blocks;
}
/**
* Render the content for the requested project_solr block.
*
* @param $delta
* The block delta for the block to render (the key from the array returned
* by project_solr_block_info()).
*
* @see project_solr_block()
* @see project_solr_block_info()
*/
function project_solr_block_view($delta) {
// None of these blocks make sense unless there's been a solr search.
if (!apachesolr_has_searched()) {
return;
}
$response = apachesolr_static_response_cache();
$query = apachesolr_current_query();
switch ($delta) {
case '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) {
$project_type = $term;
break;
}
}
}
}
if (!isset($project_type)) {
// This facet cannot process generic queries.
return;
}
$facet = 'im_vid_'. _project_get_vid();
$terms = array();
// Get the terms at the current depth.
$current_tid = $project_type->tid;
foreach ($query->get_filters() as $filter) {
if ($filter['#name'] == 'tid') {
$current_tid = $filter['#value'];
break;
}
}
$current_level_terms = array();
$tree = taxonomy_get_tree(_project_get_vid(), $current_tid, -1, 1);
foreach ($tree as $term) {
$current_level_terms[$term->tid] = $term;
}
foreach ($response->facet_counts->facet_fields->$facet as $tid => $count) {
$active = $query->has_filter('tid', $tid);
if (!isset($current_level_terms[$tid]) && (!$active || $tid != $current_tid)) {
continue;
}
$unclick_link = '';
$term = taxonomy_get_term($tid);
$new_query = clone $query;
$path = $new_query->get_path();
$options = array();
if ($active) {
$new_query->remove_filter('tid', $term->tid);
$options['query'] = $new_query->get_url_queryvalues();
$link = theme('apachesolr_unclick_link', $term->name, $path, $options);
}
else {
$new_query->add_filter('tid', $term->tid);
$options['query'] = $new_query->get_url_queryvalues();
$link = theme('apachesolr_facet_link', $term->name, $path, $options, $count, $active, $response->numFound);
}
$countsort = $count == 0 ? '' : 1 / $count;
// if numdocs == 1 and !active, don't add.
if ($response->numFound == 1 && !$active) {
// skip
}
else {
$terms[$active ? $countsort . $term->name : 1 + $countsort . $term->name] = $link;
}
}
$vocab = taxonomy_vocabulary_load(_project_get_vid());
if (!empty($terms)) {
ksort($terms);
// The currently selected term should be first.
if (isset($terms[$current_tid])) {
$current_term = $terms[$current_tid];
unset($terms[$current_tid]);
$terms = array_merge(array($current_tid => $current_term), $terms);
}
return array(
'subject' => $vocab->name,
'content' => theme('apachesolr_facet_list', $terms, 200),
);
}
break;
case 'project_solr_compability':
if (module_exists('project_release')) {
$facet = 'im_project_release_api_tids';
$terms = array();
$active_terms = project_release_compatibility_list();
$active_term_counts = array();
if (isset($response->facet_counts->facet_fields->$facet)) {
foreach ($response->facet_counts->facet_fields->$facet as $tid => $count) {
if (!empty($active_terms[$tid])) {
$active_term_counts[$tid] = $count;
}
}
}
foreach ($active_terms as $tid => $term_name) {
if (!empty($active_term_counts[$tid])) {
$active = $query->has_filter('im_project_release_api_tids', $tid);
$new_query = clone $query;
$path = $new_query->get_path();
$new_query->remove_filter('im_project_release_api_tids', $term->tid);
$options = array();
if ($active) {
$options['query'] = $new_query->get_url_queryvalues();
$link = theme('apachesolr_unclick_link', $term_name, $path, $options);
}
else {
$new_query->add_filter('im_project_release_api_tids', $tid);
$options['query'] = project_solr_append_api_term($new_query->get_url_queryvalues(), $tid);
$link = theme('project_solr_no_count_facet_link', $term_name, $path, $options, $active, $response->response->numFound);
}
$terms[$term_name] = $link;
}
}
if (!empty($terms)) {
return array(
'subject' => t('Filter by compatibility'),
'content' => theme('apachesolr_facet_list', $terms, 200),
);
}
}
break;
case 'project_solr_text':
return array(
'subject' => t('Search @project_type', array('@project_type' => $query->get_query_basic())),
'content' => drupal_get_form('project_sort_freetext', $query->get_path()),
);
}
}
/**
* Build project browsing navigation form.
*/
function project_solr_browse_projects_form(&$form_state, $project_type, $path) {
drupal_add_css(drupal_get_path('module', 'project_solr') .'/project_solr.css');
$response = apachesolr_static_response_cache();
$query = apachesolr_current_query();
$text = $query->get_keys();
// Get the terms at the current depth.
$current_tid = '';
foreach ($query->get_filters() as $field) {
if ($field['#name'] == 'tid') {
$current_tid = $field['#value'];
break;
}
}
// Create a drop-down for sub-terms of primary project terms.
$tree = taxonomy_get_tree(_project_get_vid(), $project_type->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('' => t('- Any -')) + $terms;
$form['tid'] = array(
'#type' => 'select',
'#options' => $terms,
'#title' => t('@type categories', array('@type' => $project_type->name)),
'#default_value' => $current_tid,
);
}
// Add the version selection field to our form.
$form['api_version'] = project_solr_get_api_version_field($query);
// Retrieve all the vocabularies related to project so that the facets can be built.
$vocabs = project_get_related_tids_map(TRUE);
foreach ($vocabs as $vid => $vocab) {
if ($project_type->tid == variable_get('project_type_associated_tid_' . $vid, NULL)) {
$selected = array();
// Extract selected values from our filters.
foreach ($query->get_filters() as $filter) {
if ($filter['#name'] == 'im_vid_'. $vid) {
$selected[] = $filter['#value'];
}
}
// Build checkbox items for all terms in related vocabularies.
$terms = array();
$project_types = project_get_project_types();
foreach ($project_types as $term) {
$terms[$term->tid] = $term->name;
}
asort($terms);
$form['im_vid_' . $vid] = array(
'#title' => t($vocab->name),
'#type' => 'checkboxes',
'#options' => $terms,
'#default_value' => $selected,
);
}
}
// Add the project sandbox fields. Solr allows use of 0 and 1 to match false and true.
$form['bs_project_sandbox'] = array(
'#title' => t('Status'),
'#type' => 'select',
'#options' => array(
'0' => t('Full projects'),
'[* TO *]' => t('All projects'),
'1' => t('Only sandbox projects'),
),
'#default_value' => '0',
);
// Extract the project sandbox value from our filters.
if ($filters = $query->get_filters('bs_project_sandbox')) {
// There can be only one.
$filter = end($filters);
$form['bs_project_sandbox']['#default_value'] = $filter['#value'];
}
$form['bs_project_sandbox']['#access'] = variable_get('project_solr_show_sandbox_ui', FALSE);
$form['text'] = array(
'#title' => t('Search @project_type', array('@project_type' => $project_type->name)),
'#type' => 'textfield',
'#default_value' => $text,
'#size' => 20,
);
// Add the sort selection field to our form.
$form['solrsort'] = project_solr_get_solrsort_field();
$form['path'] = array(
'#type' => 'value',
'#value' => $path,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Search'),
);
return $form;
}
/**
* Return a Form API array for a sort selector field.
*
* @param string $label
* Optional form label for the sort selector field.
*
* @return
* Form array for the sort selector field.
*/
function project_solr_get_solrsort_field($label = NULL) {
// Pull the current query, so that we can default the sorts
// and provide an accurate list of available sorting methods.
$query = apachesolr_current_query();
// Pull the currently selected sort.
$solrsort = $query->get_solrsort();
// Pull all of the available sorts and create an options array.
$options = array();
$available_sorts = $query->get_available_sorts();
foreach ($available_sorts as $name => $field) {
$options[$name . ' ' . $field['default']] = $field['title'];
}
if (!isset($label)) {
$label = t('Sort by');
}
return array(
'#title' => $label,
'#type' => 'select',
'#default_value' => $solrsort['#name'] . ' ' . $solrsort['#direction'],
'#options' => $options,
);
}
function project_solr_browse_projects_form_submit($form, &$form_state) {
include_once drupal_get_path('module', 'project_solr') .'/ProjectSolrQuery.php';
$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'])) {
$query->add_filter(variable_get('project_solr_project_release_api_tids_alias', 'api_version'), $form_state['values']['api_version']);
}
// Loop over all project-related vocabularies and create filters
// for any values that have been posted.
$vocabs = project_get_related_tids_map(TRUE);
foreach ($vocabs as $vid => $vocab) {
$values = array_filter($form_state['values']['im_vid_' . $vid]);
if (!empty($values)) {
foreach($values as $value) {
$query->add_filter('im_vid_'. $vid, $value);
}
}
}
// Rewrite solrsort parameter with API version tid, if necessary.
$solrsort = explode(' ', $form_state['values']['solrsort']);
if (module_exists('project_release') && !empty($form_state['values']['api_version'])) {
if ($solrsort[0] == 'ds_project_latest_release' || $solrsort[0] == 'ds_project_latest_activity') {
$solrsort[0] .= '_'. $form_state['values']['api_version'];
}
}
if ($solrsort[0] == '' && empty($form_state['values']['text'])) {
drupal_set_message(t('No search text submitted for a relevancy sort. The default sorting method was used instead.'));
}
// Add the project sandbox filter.
$query->add_filter('bs_project_sandbox', $form_state['values']['bs_project_sandbox']);
// Add all project-specific sorts so that the sort set in set_solrsort() will
// work as intended.
project_solr_add_sorts($query, variable_get('project_solr_project_release_api_tids_alias', 'api_version'));
$query->set_solrsort($solrsort[0], $solrsort[1]);
$query_values = $query->get_url_queryvalues();
if (!empty($form_state['values']['text'])) {
$query_values['text'] = $query->get_query_basic();
}
// By setting this as an array, FAPI is going to hand it to drupal_goto().
// drupal_goto() itself doesn't care about the keys, but we define them here
// to make the code more self-documenting, especially if people are trying
// to alter this form (e.g. in drupalorg_search.module).
$form_state['redirect'] = array(
'path' => $query->get_path(),
'query' => $query_values,
);
}
/**
* Append the API tid to selected fields that might be in the string.
*/
function project_solr_append_api_term($values, $tid) {
$api_fields = array('ds_project_latest_release', 'sis_project_release_usage', 'ds_project_latest_activity');
foreach($values as $k => $v) {
if (in_array($k, $api_fields)) {
unset($values[$k]);
$values[$k . '_' . $tid] = $v;
}
}
return $values;
}
/**
* Form callback; display a free text form.
*/
function project_sort_freetext(&$form_state, $base_path) {
$form = array();
$form['text'] = array(
'#type' => 'textfield',
'#default_value' => $_GET['text'],
'#size' => 20,
);
$form['path'] = array(
'#type' => 'value',
'#value' => $base_path,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Submit handler for project_sort_freetext().
*/
function project_sort_freetext_submit($form, &$form_state) {
if ($query = apachesolr_current_query()) {
$queryvalues = $query->get_url_queryvalues();
}
else {
$queryvalues = array();
}
$queryvalues['text'] = $form_state['values']['text'];
unset($queryvalues['solrsort']);
$form_state['redirect'] = array($form_state['values']['path'], $queryvalues);
}
//----------------------------------------
// Public helper methods
//----------------------------------------
/**
* Build and execute a Solr query to find project nodes.
*
* @param string $base_path
* The base path to use for the Solr query.
* @param array $filters
* Optional array of Solr filters to add to the query.
*
* @return
* An ApacheSolr $query object.
*/
function project_solr_run_project_query($base_path, $filters = array()) {
$filterstring = isset($_GET['filters']) ? $_GET['filters'] : '';
$query = apachesolr_drupal_query('', $filterstring, '', $base_path);
// Figure out all the fields we need to use as facets.
// First handle all the implicit filters we're going to add ourselves.
$facet_fields = array(
'type',
'im_project_release_api_tids',
);
// Now add any fields used for optional filters from our caller.
if (!empty($filters)) {
foreach ($filters as $filter) {
$facet_fields[] = $filter['key'];
}
}
$params = array(
// The fields to return.
'fl' => 'id,nid,title',
'start' => 0,
'rows' => 5, // TODO: make this configurable.
'facet.field' => $facet_fields,
// 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_run_project_query');
}
// 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');
if (!empty($filters)) {
foreach ($filters as $filter) {
$query->add_filter($filter['key'], $filter['value']);
}
}
// 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, 'project_solr_run_project_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);
return $query;
}
/**
* Return a Form API array for an API version selector field.
*
* @param object $query
* An existing query object that we can check for existing filters.
* @param string $label
* Optional form label for the version selector field.
*
* @return
* Form array for the API version selector field.
*/
function project_solr_get_api_version_field($query, $label = NULL) {
if (module_exists('project_release')) {
$current_tid = '';
$terms = array();
$active_terms = project_release_compatibility_list();
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)) {
$terms = array('' => t('- Any -')) + $terms;
if (!isset($label)) {
$label = t('Filter by compatibility');
}
return array(
'#title' => $label,
'#type' => 'select',
'#options' => $terms,
'#default_value' => $current_tid,
);
}
}
}
/**
* Adds project-specific sorts to the query object
*
* Adds project-specific sorts to the query object to allow all queries to
* leverage project-specific sorting if relevant. This is necessary to allow
* site-wide search to sort by any project-specific sorting method. This is
* called by hook_prepare_query() to add the sorts into all queries, but
* separated out so that a hook_prepare_query() invocation is not necessary
* if only the project sorts are desired. We filter the list of sorts to only
* provide meaningful options for Latest release and Recent activity so that
* we don't end up with multiple sorts with the same name. This is particularly
* relevant for how the sorts display in the apachesolr generated sort block.
*
* @param object &$query
* An existing query object to add sorts into.
* @param string $api_filter
* A string representing the name of the filter where our version tids will
* be located. This is necessary since, at times, both the base string name
* im_project_release_api_tids and the alias are passed in.
*
* @see apachesolr_block
*/
function project_solr_add_sorts(&$query, $api_filter = 'im_project_release_api_tids') {
if (module_exists('project_release')) {
// Pull any existing filter on version.
$versions = $query->get_filters($api_filter);
$tids = array();
foreach ($versions as $version) {
$tids[] = $version['#value'];
}
// If we have no version selected, only present the base sorts.
if (empty($tids)) {
$query->set_available_sort('ds_project_latest_release', array('title' => t('Last release'), 'default' => 'desc'));
$query->set_available_sort('ds_project_latest_activity', array('title' => t('Last build'), 'default' => 'desc'));
}
else {
// If we have versions selected, only present sorts for our selected
// versions. Traditionally, this will only be one version, but this
// allows for multiple potential versions to be selected.
$active_terms = project_release_compatibility_list();
foreach ($tids as $tid) {
if (isset($active_terms[$tid])) {
$query->set_available_sort('ds_project_latest_release_' . $tid, array('title' => t('Last release'), 'default' => 'desc'));
$query->set_available_sort('ds_project_latest_activity_' . $tid, array('title' => t('Last build'), 'default' => 'desc'));
}
}
}
}
if (module_exists('project_usage')) {
$query->set_available_sort('sis_project_release_usage', array('title' => t('Most installed'), 'default' => 'desc'));
}
}
//----------------------------------------
// Theme-related functions
//----------------------------------------
/**
* Perform the business logic to render search results for project-related searches.
*/
function project_solr_render_search_result($result) {
$project = node_load($result->nid);
$project = node_build_content($project, TRUE, FALSE);
$project->body = $project->teaser;
$project->solr_result = $result;
if (!empty($project->project_release['releases'])) {
$project->download_table = project_release_table($project, 'recommended', 'all', t('Version'), FALSE, FALSE);
}
$project->links = array();
$project->links['read_more'] = array(
'title' => t('Find out more'),
'href' => "node/$project->nid",
);
if (!empty($project->project_issue['issues'])) {
$project->links['issues'] = array(
'title' => t('Bugs and feature requests'),
'href' => 'project/issues/'. $project->project['uri'],
);
}
return theme('project_summary', $project);
}
function theme_project_solr_no_count_facet_link($facet_text, $path, $options = array(), $active = FALSE, $num_found = NULL) {
$options['attributes']['class'][] = 'apachesolr-facet';
if ($active) {
$options['attributes']['class'][] = 'active';
}
$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.
* @param string $label
* An optional label for the form element.
*/
function project_solr_version_form(&$form_state, $path, $label = NULL) {
$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, $label);
$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());
}
//----------------------------------------
// Category page and related functions
//----------------------------------------
/**
* Page callback for the listing of per-type categories.
*
* @param $project_type
* Fully-loaded taxonomy term object for the project type.
*
* @return
* Rendered page output for the project/%project_type/categories pages.
*/
function project_solr_category_page($project_type) {
$tree = taxonomy_get_tree(_project_get_vid(), $project_type->tid);
if (empty($tree)) {
return drupal_not_found();
}
drupal_set_title(t('@project_type categories', array('@project_type' => $project_type->name)));
$categories = array();
foreach ($tree as $category_term) {
$items = project_solr_fetch_category_items($project_type, $category_term);
if (!empty($items)) {
$categories[$category_term->tid] = array(
'title' => check_plain($category_term->name),
'items' => $items,
);
}
}
$search_path = 'project/' . drupal_strtolower($project_type->name) . '/categories';
$version_form = drupal_get_form('project_solr_version_form', $search_path);
return theme('project_solr_category_page', $project_type, $categories, $version_form);
}
/**
* Render the markup for the per-project type categories landing pages.
*
* @param $project_type
* Fully-loaded taxonomy term object for the project type.
* @param $categories
* Nested array of information about categories and projects. The keys are
* the taxonomy term IDs (tids) of each category for the given project_type
* that has projects associtated with it. The values are arrays with the
* keys 'title' for the human-readable (and sanitized) titles of each
* category, and 'items', which is an array of project links from the
* projects in the category.
* @param $version_form
* Optional rendered HTML of a version selector form to restrict results to
* a given API compatibility term.
*/
function theme_project_solr_category_page($project_type, $categories, $version_form = '') {
$output = '';
if (!empty($version_form)) {
$output .= $version_form;
}
// TODO: This could probably use more slickness, a grid with multiple
// columns, etc. http://drupal.org/node/888144
foreach ($categories as $tid => $category) {
$output .= '' . $category['title'] . '
';
$output .= theme('project_solr_category_list', $category['items']);
}
return $output;
}
/**
* Gather the items for a given project category.
*
* @param $project_type
* The fully-loaded taxonomy term for the project type.
* @param $category_term
* The fully-loaded taxonomy term for the category.
*
* @return
* Array of projects from the given category.
*/
function project_solr_fetch_category_items($project_type, $category_term) {
$filters = array();
$filters[] = array(
'key' => 'tid',
'value' => $category_term->tid,
);
$base_path = 'project/' . drupal_strtolower($project_type->name);
$query = project_solr_run_project_query($base_path, $filters);
$query_values = $query->get_url_queryvalues();
$response = apachesolr_static_response_cache();
$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.
$items[] = array(
'data' => l(t('More @category', array('@category' => $category_term->name)), $query->get_path(), array('query' => $query_values)),
'class' => 'more',
);
}
return $items;
}
/**
* Render the final markup for a list of projects from a given category.
*
* @param array $items
* Array of links to projects in a given category.
*
* @return
* Formatted HTML markup for the list of project in a category.
*
* @see theme_project_solr_category_page()
* @see theme_item_list()
*/
function theme_project_solr_category_list($items = array()) {
$output = '';
if (!empty($items)) {
$output .= theme('item_list', $items);
}
return $output;
}