diff --git a/includes/SearchlightDatasource.inc b/includes/SearchlightDatasource.inc index 3d229be..44deac6 100644 --- a/includes/SearchlightDatasource.inc +++ b/includes/SearchlightDatasource.inc @@ -6,6 +6,7 @@ class SearchlightDatasource { var $base_table; var $base_field; var $fields; + var $relations; var $filters; var $options; @@ -22,6 +23,7 @@ class SearchlightDatasource { $this->fields = !empty($this->fields) ? $this->fields : array(); $this->filters = !empty($this->filters) ? $this->filters : array(); + $this->relations = !empty($this->relations) ? $this->relations : array(); $this->options = !empty($this->options) ? $this->options : array(); $this->options = $this->options + array( 'node_access' => TRUE, @@ -31,10 +33,8 @@ class SearchlightDatasource { } function setId() { - global $db_url; - $url = is_array($db_url) ? $db_url['default'] : $db_url; - $url = parse_url($url); - $this->id = trim(urldecode($url['path']), '/') .'_'. $this->name; + global $databases; + $this->id = $databases['default']['default']['database'] . '_' . $this->name; return $this; } @@ -46,6 +46,7 @@ class SearchlightDatasource { views_include('view'); $this->view = new view; $this->view->base_table = $this->base_table; + $this->view->base_field = $this->base_field; $this->view->api_version = '3.0-alpha1'; $this->view->new_display('default', 'Default', 'default'); $this->view->set_display('default'); @@ -67,18 +68,25 @@ class SearchlightDatasource { 'id' => $this->base_field, 'table' => $this->base_table, 'field' => $this->base_field, - 'relationship' => 'none' + 'relationship' => 'none', ); } foreach ($this->getFields() as $name => $field) { - $fields[$name] = array( - 'id' => $field['name'], - 'table' => $field['table'], - 'field' => $field['field'], - 'relationship' => 'none' - ); + $data = views_fetch_data($field['table']); + if (isset($data[$field['field']]['field'])) { + $relationship = empty($field['relationship']) ? 'none' : $field['relationship']; + $fields[$name] = array( + 'id' => $field['name'], + 'table' => $field['table'], + 'field' => $field['field'], + 'relationship' => $relationship, + ); + } } $handler->override_option('fields', $fields); + + $handler->override_option('relationships', $this->getRelations()); + return $this; } @@ -110,20 +118,30 @@ class SearchlightDatasource { $schema = drupal_get_schema($table); if ($schema && isset($field, $schema['fields'][$field])) { $class = $handler ? get_class($handler) : NULL; - + // Use handler defined in $data in case $data has been altered by another + // and requires for example another handler + if (isset($data[$table][$field]['field']['handler'])) { + $class = $data[$table][$field]['field']['handler']; + } // Get the datasource attribute type. // We use the handler class for special cases like timestamp where DB // column type is not enough information to determine the usage of the // field. $map = array( - 'serial' => 'int', - 'int' => 'int', + 'serial' => 'int', + 'int' => 'int', 'varchar' => 'text', - 'text' => 'text', - 'float' => 'float', + 'text' => 'text', + 'float' => 'float', + 'numeric' => 'float', + 'decimal' => 'float', ); if (isset($map[$schema['fields'][$field]['type']])) { $column_type = $map[$schema['fields'][$field]['type']]; + // Allow custom handlers to specity the column type in definition + if (isset($handler->definition['data_type'])) { + $column_type = $handler->definition['data_type']; + } if ($column_type === 'int' && strpos($class, 'date') !== FALSE) { return 'timestamp'; } @@ -163,6 +181,19 @@ class SearchlightDatasource { return $fields; } + /** + * Retrieve relationships currently enabled for this datasource. + */ + function getRelations() { + $relationships = array(); + foreach ($this->relations as $name => $relation) { + $relation['relationship'] = 'none'; + $relation['group_type'] = 'group'; + $relationships[$name] = $relation; + } + return $relationships; + } + /** * Retrieve all possible multivalue fields for this base table. */ @@ -182,7 +213,6 @@ class SearchlightDatasource { } } } - foreach ($usable as $view) { if ($view->name !== 'searchlight_node_access') { foreach ($view->display as $display) { @@ -219,6 +249,57 @@ class SearchlightDatasource { return $nodeaccess; } + + /** + * Finds all possible relations ships for a field. + * + * Orignal code resides in admin.inc of views. + */ + function getFieldRelationshipOptions($field_id) { + $relationship_options = array(); + $view = &$this->view; + $item = $view->get_item('default', 'field', $field_id); + +// // If relation is already set, return it +// if (!empty($item['relationship']) && $item['relationship'] != 'none') { +// $relationship_options[$item['relationship']] = $item['relationship']; +// return $relationship_options; +// } + // A whole bunch of code to figure out what relationships are valid for + // this item. + $relationships = $view->display_handler->get_option('relationships'); + $relationship_options = array(); + + foreach ($relationships as $relationship) { + $relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship'); + // ignore invalid/broken relationships. + if (empty($relationship_handler)) { + continue; + } + + // If this relationship is valid for this type, add it to the list. + $data = views_fetch_data($relationship['table']); + $base = $data[$relationship['field']]['relationship']['base']; + $base_fields = views_fetch_fields($base, 'field', $view->display_handler->use_group_by()); + if (isset($base_fields[$item['table'] . '.' . $item['field']])) { + $relationship_handler->init($view, $relationship); + $relationship_options[$relationship['id']] = $relationship_handler->options['field']; + //$relationship_options[$relationship['id']] = $relationship_handler->label(); + } + } + + if (!empty($relationship_options)) { + // Make sure the existing relationship is even valid. If not, force + // it to none. + $base_fields = views_fetch_fields($view->base_table, 'field', $view->display_handler->use_group_by()); + if (isset($base_fields[$item['table'] . '.' . $item['field']])) { + $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options); + } + } + + return $relationship_options; + } + /** * Provide the default form for setting options. */ @@ -226,26 +307,27 @@ class SearchlightDatasource { views_include('admin'); views_include('form'); + $this->viewInit()->viewSetHandlers(); + $base_tables = $this->view->get_base_tables(); + // Theme function will handle formatting of datasource information. $form['#theme'] = 'searchlight_admin_datasource'; $form['#datasource'] = $this; // Calculations of indexing percentage. - $result = db_query("SELECT count(id) AS count, status FROM {searchlight_search} WHERE type = '%s' GROUP BY status", $this->base_table); + $result = db_query("SELECT count(id) AS count, status FROM {searchlight_search} WHERE type = :type GROUP BY status", array(':type' => $this->base_table)); $items_indexed = 0; $items_total = 0; - while ($row = db_fetch_object($result)) { + foreach ($result as $row) { $items_indexed = $row->status == 1 ? $row->count : $items_indexed; $items_total = $items_total + $row->count; } $form['index']['#tree'] = TRUE; $form['index']['percent'] = array( - '#type' => 'markup', - '#value' => !empty($items_total) ? number_format(($items_indexed / $items_total * 100), 1) . '%' : '0%', + '#markup' => !empty($items_total) ? number_format(($items_indexed / $items_total * 100), 1) . '%' : '0%', ); $form['index']['counts'] = array( - '#type' => 'markup', - '#value' => t('@indexed of @total total', array( + '#markup' => t('@indexed of @total total', array( '@indexed' => $items_indexed, '@total' => $items_total, )), @@ -257,7 +339,7 @@ class SearchlightDatasource { $form['help'] = array( '#type' => 'item', - '#value' => t('Choose a usage type for each field in the search datasource. Content fields will be used to perform text searches. Attributes can be used to filter, sort or group the search results.') + '#markup' => t('Choose a usage type for each field in the search datasource. Content fields will be used to perform text searches. Attributes can be used to filter, sort or group the search results.'), ); $form['fields'] = array( @@ -270,7 +352,8 @@ class SearchlightDatasource { // Add fields $field_options = array(); - $fields = views_fetch_fields($this->base_table, 'field', TRUE); + $fields = views_fetch_fields(array_keys($base_tables), 'field', TRUE); + foreach ($fields as $field_id => $info) { $field_options[$info['group']][$field_id] = $info['title']; } @@ -279,15 +362,19 @@ class SearchlightDatasource { '#type' => 'select', '#options' => $field_options, ); + $form['fields']['new']['add'] = array( '#value' => t('Add field'), '#type' => 'submit', '#submit' => array('searchlight_admin_datasource_edit_submit'), - '#ahah' => array( - 'path' => 'admin/settings/search/datasource/ahah/edit-fields-new-add/fields', - 'wrapper' => 'datasource-fields', - 'method' => 'replace', - ), + '#limit_validation_errors' => array(array('fields', 'new')), + // @TODO: Determine why AJAX is failing. Possibly because the element to + // which the ajax behavior is attached is replaced by the ajax callback. + // '#ajax' => array( + // 'callback' => 'searchlight_admin_datasource_ajax_fields_add', + // 'wrapper' => 'datasource-fields', + // 'method' => 'replace', + // ), ); // Remove fields @@ -296,14 +383,14 @@ class SearchlightDatasource { '#value' => t('Remove selected fields'), '#type' => 'submit', '#submit' => array('searchlight_admin_datasource_edit_submit'), - '#ahah' => array( - 'path' => 'admin/settings/search/datasource/ahah/edit-fields-remove/fields', - 'wrapper' => 'datasource-fields', - 'method' => 'replace', - ), + '#limit_validation_errors' => array(array('fields', 'fields')), + // '#ajax' => array( + // 'callback' => 'searchlight_admin_datasource_ajax_fields_remove', + // 'wrapper' => 'datasource-fields', + // 'method' => 'replace', + // ), ); } - // Adjust existing fields $form['fields']['fields'] = array('#tree' => TRUE); foreach ($this->getFields() as $name => $info) { @@ -315,16 +402,34 @@ class SearchlightDatasource { '#default_value' => FALSE, ); + // Relation + if (count($relation_options = $this->getFieldRelationshipOptions($name)) > 1) { + + $form['fields']['fields'][$name]['relationship'] = array( + '#type' => 'select', + '#options' => $relation_options, + '#default_value' => ((!empty($info['relationship'])) ? $info['relationship'] : NULL), + ); + } + else { + $relation = $info['relationship']; + $form['fields']['fields'][$name]['relation_name'] = array( + '#markup' => ((empty($relation)) ? t('none') : $relation), + ); + $form['fields']['fields'][$name]['relationship'] = array( + '#type' => 'hidden', + '#value' => ((empty($relation)) ? t('none') : $relation), + ); + } + // Field label. $form['fields']['fields'][$name]['label'] = array( - '#type' => 'markup', - '#value' => $info['label'], + '#markup' => $info['label'], ); // Datatype $form['fields']['fields'][$name]['datatype'] = array( - '#type' => 'markup', - '#value' => "{$info['datatype']}", + '#markup' => "{$info['datatype']}", ); // Usage @@ -332,20 +437,15 @@ class SearchlightDatasource { $default_usage = isset($this->fields[$name]['usage']) ? $this->fields[$name]['usage'] : $default_usage; $form['fields']['fields'][$name]['usage'] = array( '#type' => 'select', - '#options' => array( 'content' => t('Content'), 'attribute' => t('Attribute')), + '#options' => array( + 'content' => t('Content'), + 'attribute' => t('Attribute'), + ), '#default_value' => $default_usage, ); } - // @TODO: Handle multivalues. - $form['multivalues'] = array( - '#tree' => TRUE, - '#theme' => 'searchlight_admin_datasource_fields', - '#title' => t('Multivalues'), - '#prefix' => "
", - '#suffix' => "
", - ); - + // Handle multivalues. // Add fields $multivalue_options = array(); foreach ($this->buildMultivalues() as $name => $info) { @@ -353,6 +453,13 @@ class SearchlightDatasource { $multivalue_options[$name] = $info['label']; } } + $form['multivalues'] = array( + '#tree' => TRUE, + '#theme' => 'searchlight_admin_datasource_fields', + '#title' => t('Multivalues'), + '#prefix' => "
", + '#suffix' => "
", + ); $form['multivalues']['new']['#tree'] = TRUE; $form['multivalues']['new']['field'] = array( '#type' => 'select', @@ -362,11 +469,12 @@ class SearchlightDatasource { '#value' => t('Add multivalue'), '#type' => 'submit', '#submit' => array('searchlight_admin_datasource_edit_submit'), - '#ahah' => array( - 'path' => 'admin/settings/search/datasource/ahah/edit-multivalues-new-add/multivalues', - 'wrapper' => 'datasource-multivalues', - 'method' => 'replace', - ), + '#limit_validation_errors' => array(array('multivalues', 'new')), + // '#ajax' => array( + // 'callback' => 'searchlight_admin_datasource_ajax_multivalues_add', + // 'wrapper' => 'datasource-multivalues', + // 'method' => 'replace', + // ), ); // Remove fields @@ -375,14 +483,15 @@ class SearchlightDatasource { '#value' => t('Remove selected multivalues'), '#type' => 'submit', '#submit' => array('searchlight_admin_datasource_edit_submit'), - '#ahah' => array( - 'path' => 'admin/settings/search/datasource/ahah/edit-multivalues-remove/multivalues', - 'wrapper' => 'datasource-multivalues', - 'method' => 'replace', - ), + '#limit_validation_errors' => array(array('multivalues', 'fields')), + // '#ajax' => array( + // 'callback' => 'searchlight_admin_datasource_ajax_multivalues_remove', + // 'wrapper' => 'datasource-multivalues', + // 'method' => 'replace', + // ), ); } - + $form['multivalues']['fields'] = array(); foreach ($this->getMultivalues() as $name => $info) { $form['multivalues']['fields'][$name] = array(); @@ -394,33 +503,144 @@ class SearchlightDatasource { // Field label. $form['multivalues']['fields'][$name]['label'] = array( - '#type' => 'markup', - '#value' => $info['label'], + '#markup' => $info['label'], ); // Datatype $form['multivalues']['fields'][$name]['datatype'] = array( - '#type' => 'markup', - '#value' => "{$info['datatype']}", + '#markup' => "{$info['datatype']}", ); $form['multivalues']['fields'][$name]['usage'] = array( - '#type' => 'markup', - '#value' => t('Multivalue'), + '#markup' => t('Multivalue'), + ); + } + + $form['relations'] = array( + '#prefix' => "
", + '#suffix' => "
", + '#tree' => TRUE, + '#theme' => 'searchlight_admin_datasource_relations', + '#title' => t('Relations'), + ); + + // Add relations + $relation_options = array(); + $fields = views_fetch_fields(array_keys($base_tables), 'relationship'); + foreach ($fields as $relation_id => $info) { + $relation_options[$info['group']][$relation_id] = $info['title']; + } + $form['relations']['new']['#tree'] = TRUE; + $form['relations']['new']['relation'] = array( + '#type' => 'select', + '#options' => $relation_options, + ); + $form['relations']['new']['add'] = array( + '#value' => t('Add relation'), + '#type' => 'submit', + '#submit' => array('searchlight_admin_datasource_edit_submit'), + '#limit_validation_errors' => array(array('relations', 'new')), + ); + + $relations = $this->getRelations(); + // Remove relations + if (count($relations)) { + $form['relations']['remove'] = array( + '#value' => t('Remove selected relations'), + '#type' => 'submit', + '#submit' => array('searchlight_admin_datasource_edit_submit'), + '#limit_validation_errors' => array(array('relations', 'relations')), ); } + // Adjust existing relations + $form['relations']['relations'] = array('#tree' => TRUE); + foreach ($relations as $name => $info) { + $form['relations']['relations'][$name] = array(); + + // Remove field checkbox. + $form['relations']['relations'][$name]['remove'] = array( + '#type' => 'checkbox', + '#default_value' => FALSE, + ); + + // Relation label. + $form['relations']['relations'][$name]['label'] = array( + '#type' => 'textfield', + '#default_value' => $info['label'], + '#field_suffix' => '(' . $info['table'] . '.' . $info['field'] . ')', + ); - $form['options']['#tree'] = TRUE; - // In reality, these are multivalue fields that are generated - // programmatically. - if ($this->base_table === 'node') { - $form['options']['node_access'] = array( - '#title' => t('Node access'), - '#description' => t('Include node access information in datasource'), + // Required relation. + $form['relations']['relations'][$name]['required'] = array( '#type' => 'checkbox', - '#default_value' => $this->options['node_access'], + '#default_value' => (int) $info['required'], ); } + + $backend = variable_get('searchlight_backend'); + $settings = variable_get('searchlight_backend_' . $backend); + if ($settings['grouping']) { + // Group + $form['groupfield'] = array( + '#tree' => TRUE, + '#theme' => 'searchlight_admin_datasource_fields', + '#title' => t('Group'), + '#prefix' => "
", + '#suffix' => "
", + ); + + // Add fields + $groupfield_options = array(); + foreach ($this->getFields() as $name => $info) { + $groupfield_options[$name] = $info['label']; + } + $form['groupfield']['new']['#tree'] = TRUE; + $form['groupfield']['new']['field'] = array( + '#type' => 'select', + '#options' => $groupfield_options, + ); + $form['groupfield']['new']['add'] = array( + '#value' => t('Add group field'), + '#type' => 'submit', + '#submit' => array('searchlight_admin_datasource_edit_submit'), + '#limit_validation_errors' => array(array('groupfield', 'new')), + ); + + // Remove field + if (isset($this->options['groupfield'])) { + $form['groupfield']['remove'] = array( + '#value' => t('Remove groupfield'), + '#type' => 'submit', + '#submit' => array('searchlight_admin_datasource_edit_submit'), + '#limit_validation_errors' => array(array('groupfield', 'fields')), + ); + } + $form['groupfield']['fields'] = array(); + if (isset($this->options['groupfield'])) { + $form['groupfield']['fields'][$this->options['groupfield']] = array(); + + // Remove field checkbox. + $form['groupfield']['fields'][$this->options['groupfield']]['remove'] = array( + '#type' => 'checkbox', + '#default_value' => FALSE, + ); + + // Field label. + $form['groupfield']['fields'][$this->options['groupfield']]['label'] = array( + '#markup' => $this->fields[$this->options['groupfield']]['label'], + ); + + // Datatype + $form['groupfield']['fields'][$this->options['groupfield']]['datatype'] = array( + '#markup' => "{$this->fields[$this->options['groupfield']]['datatype']}", + ); + + $form['groupfield']['fields'][$this->options['groupfield']]['usage'] = array( + '#markup' => t('Grouping'), + ); + } + } + return $form; } @@ -459,6 +679,31 @@ class SearchlightDatasource { } searchlight_datasource_save($this, TRUE); break; + case 'edit-groupfield-new-add': + $this->options['groupfield'] = $form_state['values']['groupfield']['new']['field']; + searchlight_datasource_save($this, TRUE); + break; + case 'edit-groupfield-remove': + foreach ($form_state['values']['groupfield']['fields'] as $name => $values) { + if (!empty($values['remove'])) { + unset ($this->options['groupfield']); + } + } + searchlight_datasource_save($this, TRUE); + break; + case 'edit-relations-new-add': + list($table, $field) = explode('.', $form_state['values']['relations']['new']['relation']); + $this->addRelation($table, $field); + searchlight_datasource_save($this, TRUE); + break; + case 'edit-relations-remove': + foreach ($form_state['values']['relations']['relations'] as $name => $values) { + if (!empty($values['remove'])) { + $this->removeRelation($name); + } + } + searchlight_datasource_save($this, TRUE); + break; case 'edit-save': // Save additional metadata from fields, multivalues. foreach (array('fields', 'multivalues') as $key) { @@ -472,34 +717,103 @@ class SearchlightDatasource { } } // Save options. - $this->options = $form_state['values']['options']; + $this->options = isset($form_state['values']['options']) ? $form_state['values']['options'] : $this->options; + + // Save relations + if (!empty($form_state['values']['relations']['relations'])) { + foreach ($form_state['values']['relations']['relations'] as $name => $values) { + $values = array_diff_key($values, array('remove' => NULL)); + if (isset($this->relations[$name])) { + $this->relations[$name] = array_merge($this->relations[$name], $values); + } + } + } // Save the datasource. searchlight_datasource_save($this); drupal_set_message(t('Datasource @datasource saved. The index for this datasource needs to be rebuilt.', array('@datasource' => $this->name))); break; + + case 'edit-relations-new-add': + $this->addRelation($form_state['values']['relations']['new']['table'], $form_state['values']['relations']['new']['field']); + searchlight_datasource_save($this, TRUE); + break; + case 'edit-relations-remove': + foreach ($form_state['values']['relations']['relations'] as $name => $values) { + if (!empty($values['remove'])) { + $this->removeRelation(NULL, NULL, $name); + } + } + searchlight_datasource_save($this, TRUE); + break; + } + } + + + /** + * Remove relation. + * @param string $relation + */ + function removeRelation($name) { + if (array_key_exists($name, $this->relations)) { + unset($this->relations[$name]); + } + return $this->viewInit()->viewSetHandlers(); + } + + /** + * Adds a new relation for the datasource. + * @param string $table + * @param string $field + * @param boolean $required + */ + function addRelation($table, $field, $label = NULL, $required = FALSE) { + $this->viewInit()->viewSetHandlers(); + + if (empty($label)) { + $label = $table . '.' . $field; } + + $options = array( + 'label' => $label, + 'required' => (bool) $required, + ); + $id = $this->view->add_item('default', 'relationship', $table, $field, $options); + + $this->relations = $this->view->display_handler->get_option('relationships'); } function addField($table, $field) { // Add the field to the view and build. This will give us an inited handler // with full aliases. $this->viewInit()->viewSetHandlers(); - $fields = $this->view->display_handler->get_option('fields'); + $fields = &$this->view->display_handler->get_option('fields'); $fields[$field] = array( 'id' => $field, 'table' => $table, 'field' => $field, 'relationship' => 'none', ); + $this->view->display_handler->set_option('fields', $fields); $this->view->build(); + + $relationship = 'none'; + foreach($this->getFieldRelationshipOptions($field) as $key => $val) { + $relationship = $val; + } + $fields[$field]['relationship'] = $relationship; // Retrieve field information for storage with datasource. $handler = isset($this->view->field[$field]) ? $this->view->field[$field] : NULL; + // Track fields added so we can provide a default usage based on datatype. + $fields_added = array(); + // Don't use alias for base field. if ($field === $this->view->base_field) { + $fields_added[] = $field; + $this->fields[$field] = array( 'label' => $field, 'datatype' => $this->getDatatype(NULL, $table, $field), @@ -509,13 +823,24 @@ class SearchlightDatasource { ); } // Use alias for all other fields. - if ($handler) { + if ($handler && $field != $this->view->base_field) { + // Generate unique id's + $alias_base = $handler->field_alias; + $count = 1; + while (!empty($this->fields[$handler->field_alias])) { + $handler->field_alias = $alias_base . '_' . $count++; + } + + $fields_added[] = $handler->field_alias; + $this->fields[$handler->field_alias] = array( + 'id' => $handler->field_alias, 'label' => $handler->ui_name() . " ({$handler->real_field})", 'datatype' => $this->getDatatype($handler, $handler->table, $handler->real_field), 'table' => $handler->table, 'field' => $handler->real_field, 'name' => $handler->field_alias, + 'relationship' => $relationship, ); } if (!empty($handler->additional_fields)) { @@ -528,17 +853,40 @@ class SearchlightDatasource { $table = isset($info['table']) ? $info['table'] : $table; $field = $info['field']; } - if ($field !== $handler->view->base_field || $table !== $handler->view->base_table) { + + // Check if we are dealing with a field API field handler. + // - Skip the actual field specified by the handler (entity_id). + // - Exclude field metadata in additional_fields (language, entity_type, delta, etc.) + $add_field = FALSE; + if (isset($handler->definition['field_info'], $handler->definition['entity_tables'])) { + $add_field = !in_array($field, array('delta', 'entity_type', 'language')) && strpos($field, '_format') === FALSE; + } + else { + $add_field = $field !== $handler->view->base_field || $table !== $handler->view->base_table; + } + if ($add_field) { + $fields_added[] = $handler->aliases[$field]; + $this->fields[$handler->aliases[$field]] = array( + 'id' => $handler->aliases[$field], 'label' => $handler->ui_name() . " ({$field})", 'datatype' => $this->getDatatype($handler, $table, $field), 'table' => $table, 'field' => $field, 'name' => $handler->aliases[$field], + 'relationship' => $relationship, ); } } } + + if (sizeof($fields_added)) { + foreach ($fields_added as $name) { + if (!isset($this->fields[$name]['usage'])) { + $this->fields[$name]['usage'] = ($this->fields[$name]['datatype'] === 'text') ? 'content' : 'attribute'; + } + } + } return $this->viewInit()->viewSetHandlers(); } @@ -548,7 +896,7 @@ class SearchlightDatasource { unset($this->fields[$name]); } } - else if (isset($table, $field)) { + elseif (isset($table, $field)) { foreach ($this->fields as $name => $field) { if ($field['table'] === $table && $field['field'] === $field) { unset($this->fields[$name]); diff --git a/includes/SearchlightEnvironment.inc b/includes/SearchlightEnvironment.inc index 67b977a..3774af1 100644 --- a/includes/SearchlightEnvironment.inc +++ b/includes/SearchlightEnvironment.inc @@ -97,14 +97,19 @@ class SearchlightEnvironment { $rendered = array(); foreach ($this->plugins as $name => $plugin) { foreach ($deltas as $delta) { - if ($render = $plugin->render(drupal_clone($this->query), $delta)) { + if ($render = $plugin->render(clone $this->query, $delta)) { // Theme each item $items = array(); foreach ($render as $item) { $items[] = $plugin->theme($item, $delta); } // Generate a label/items array suitable for theme('searchlight_facet') - $rendered[$name] = array('label' => $plugin->label($delta), 'items' => $items, 'delta' => $delta); + $rendered[$name] = array( + 'name' => $name, + 'label' => $plugin->label($delta), + 'items' => $items, + 'delta' => $delta, + ); } } } @@ -127,17 +132,26 @@ class SearchlightEnvironment { */ function getBlock($delta) { if ($delta === 'facets' && $this->initView()) { + // Provide an array with 'unthemed' data for modules implementing + // hook_block_view_alter() in order to have more theming flexibility + $data = array(); $output = ''; + $data['container']['name'] = $this->name; + $data['container']['view_name'] = $this->view->name; + $data['container']['facets'] = array(); foreach ($this->render() as $rendered) { - $output .= theme('searchlight_facet', $rendered); + $data['container']['facets'][] = $rendered; + // If we don't get back arrays for items, we directly them them + if (!is_array($rendered['items'][0])) { + $output .= theme('searchlight_facet', array('facet' => $rendered)); + } } if (!empty($output)) { $output = "
{$output}
"; - return array( - 'subject' => filter_xss_admin($this->options['facets_label']), - 'content' => $output, - ); + $data['label'] = filter_xss_admin($this->options['facets_label']); + $data['content'] = $output; } + return $data; } return array(); } @@ -149,6 +163,7 @@ class SearchlightEnvironment { // Adapted from searchlight_build_view. It should be possible to // retrieve a data source from a view without running it, untill then... $split = explode(':', $this->view_display); + if (count($split) === 2 && $view = views_get_view($split[0])) { return searchlight_get_datasource($view->base_table); } @@ -209,7 +224,10 @@ class SearchlightEnvironment { if ($datasource) { $form['facets'] = array('#tree' => TRUE); $fields = $datasource->fields; - $fields['search_query'] = array('label' => t('Search query')); + $fields['search_query'] = array( + 'label' => t('Search query'), + 'name' => 'search_query' + ); foreach ($fields as $name => $field) { if ($this->isValidFacet($name)) { $form['facets'][$name] = array( @@ -219,12 +237,11 @@ class SearchlightEnvironment { $form['facets'][$name]['enabled'] = array( '#title' => t('Enabled'), '#type' => 'checkbox', - '#default_value' => isset($this->facets[$name]['enabled']) ? $this->facets[$name]['enabled'] : TRUE, + '#default_value' => isset($this->facets[$name]['enabled']) ? $this->facets[$name]['enabled'] : FALSE, ); $form['facets'][$name]['ui_name'] = array( '#title' => t('Facet'), - '#type' => 'markup', - '#value' => $field['label'], + '#markup' => $field['label'], ); $form['facets'][$name]['weight'] = array( '#title' => t('Weight'), @@ -235,7 +252,8 @@ class SearchlightEnvironment { // We instantiate plugins here rather than using initView() as the // facets on this form may not already be enabled for this environment. $plugin = searchlight_get_facet($datasource, $name); - $plugin->construct($this, $datasource->fields[$name], $this->getValue($name), isset($this->facets[$name]) ? $this->facets[$name] : array()); + $plugin->construct($this, $field, $this->getValue($name), isset($this->facets[$name]) ? $this->facets[$name] : array()); + $plugin->optionsForm($form['facets'][$name], $form_state); $form['facets'][$name]['settings'] = array( @@ -265,6 +283,9 @@ class SearchlightEnvironment { unset($this->facets[$name]['settings']); $this->facets[$name] = array_merge($this->facets[$name], $options['settings']); } + if (empty($options['enabled'])) { + unset($this->facets[$name]); + } } $this->options = $form_state['values']['options']; @@ -301,8 +322,8 @@ class SearchlightEnvironment { * Get the URL options for the current set of active facets, adjusted using * one of the $op operations. * - * 'add': Add a facet value for the given key/value pair. - * 'remove': Add a facet value for the given key/value pair. + * 'add': Add a facet value or multiple facet values for the given key/value pair. + * 'remove': Add a facet value or multiple facet values for the given key/value pair. * 'active': Retain only active facets and drop any other query strings. */ function getURLOptions($op = 'add', $key = NULL, $value = NULL) { @@ -315,8 +336,17 @@ class SearchlightEnvironment { break; case 'remove': $modifier = $modifier + $this->active_values; - if (isset($modifier[$key])) { - unset($modifier[$key]); + if (is_array($modifier[$key]) && !empty($value)) { + foreach ($modifier[$key] as $k => $v) { + if ($v === $value) { + unset($modifier[$key][$k]); + } + } + } + else { + if (isset($modifier[$key])) { + unset($modifier[$key]); + } } break; case 'active': @@ -348,7 +378,7 @@ class SearchlightEnvironment { else if (empty($modifier)) { $exclude[] = $key; } - $options['query'] = drupal_query_string_encode($query, $exclude); + $options['query'] = drupal_get_query_parameters($query, $exclude); return $options; } diff --git a/libraries/SolrPhpClient/Apache/Solr/Service.php b/libraries/SolrPhpClient/Apache/Solr/Service.php index c9786b9..61791a0 100644 --- a/libraries/SolrPhpClient/Apache/Solr/Service.php +++ b/libraries/SolrPhpClient/Apache/Solr/Service.php @@ -327,6 +327,7 @@ protected function _sendRawGet($url, $timeout = FALSE) } //$http_response_header is set by file_get_contents + $http_response_header = ''; $response = new Apache_Solr_Response(@file_get_contents($url, false, $this->_getContext), $http_response_header, $this->_createDocuments, $this->_collapseSingleValueArrays); if ($response->getHttpStatus() != 200) diff --git a/plugins/SearchlightBackend.inc b/plugins/SearchlightBackend.inc index a287858..65dcbbf 100644 --- a/plugins/SearchlightBackend.inc +++ b/plugins/SearchlightBackend.inc @@ -88,8 +88,7 @@ abstract class SearchlightBackend { * * @param $datasource */ - function initClient($datasource) { - } + function initClient($datasource) { } /** * Execute a query using the search backend. Should return an array with the @@ -99,38 +98,32 @@ abstract class SearchlightBackend { * 'total': Total number of non-paged results. * 'raw': The raw result object/array/data from the search client. */ - function executeQuery(&$client, $datasource, $query = '') { - } + function executeQuery(&$client, $datasource, $query = '') { } /** * Set any custom options for the search backend. */ - function setOptions(&$client, $datasource, $options) { - } + function setOptions(&$client, $datasource, $options) { } /** * Set a filter parameter for the search backend. */ - function setFilter(&$client, $datasource, $filters) { - } + function setFilter(&$client, $datasource, $filters) { } /** * Set a sort parameter for the search backend. */ - function setSort(&$client, $datasource, $sorts) { - } - + function setSort(&$client, $datasource, $sorts) { } + /** * Set a pager/limit parameter for the search backend. */ - function setPager(&$client, $offset, $limit) { - } + function setPager(&$client, $offset, $limit) { } /** * Set node_access attribute filters. */ - function setNodeAccess(&$client) { - } + function setNodeAccess(&$client) { } /** * Utility date methods ===================================================== @@ -193,8 +186,7 @@ abstract class SearchlightBackend { /** * Invalidate the search index associated with this datasource. */ - function invalidateIndex($datasource) { - } + function invalidateIndex($datasource) { } /** * Drush methods ============================================================ @@ -203,36 +195,30 @@ abstract class SearchlightBackend { /** * Start a search backend daemon process through drush. */ - function drushSearchd($command = 'start') { - } + function drushSearchd($command = 'start') { } /** * Start a search backend indexing process through drush. */ - function drushIndex() { - } + function drushIndex() { } /** * Write search backend configuration files through drush. */ - function drushConf() { - } + function drushConf() { } /** * Execute functionality on a drush cron run. */ - function drushCron() { - } + function drushCron() { } /** * When a new site is installed via Aegir. */ - function drushAegirInstall() { - } + function drushAegirInstall() { } /** * When a site is migrated via Aegir. */ - function drushAegirDeploy() { - } + function drushAegirDeploy() { } } diff --git a/plugins/SearchlightBackendSolr.inc b/plugins/SearchlightBackendSolr.inc index 43f45e4..fa6e501 100644 --- a/plugins/SearchlightBackendSolr.inc +++ b/plugins/SearchlightBackendSolr.inc @@ -40,6 +40,12 @@ class SearchlightBackendSolr extends SearchlightBackend { '#default_value' => $this->settings['path'], '#size' => 60, ); + $form['jar_path'] = array( + '#title' => t('Solr jar path'), + '#type' => 'textfield', + '#default_value' => $this->settings['jar_path'], + '#size' => 60, + ); $form['item_limit'] = array( '#title' => t('Items to index per job'), '#type' => 'select', @@ -53,6 +59,11 @@ class SearchlightBackendSolr extends SearchlightBackend { 1000 => 1000, ), ); + $form['grouping'] = array( + '#title' => t('Supports Result Grouping / Field Collapsing (Solr4.0 and higher)'), + '#type' => 'checkbox', + '#default_value' => $this->settings['grouping'], + ); return $form; } @@ -61,7 +72,7 @@ class SearchlightBackendSolr extends SearchlightBackend { */ function initClient($datasource) { $this->solrInclude(); - $path = $this->settings['path'] .'/'. $datasource->id; + $path = $this->settings['path'] . '/' . $datasource->id; $client = new Apache_Solr_Service($this->settings['host'], (int) $this->settings['port'], $path); return $client; } @@ -76,29 +87,53 @@ class SearchlightBackendSolr extends SearchlightBackend { $options = array(); if (isset($client->searchlightSort)) { + $sort = ''; foreach ($client->searchlightSort as $field => $order) { $field = ($field == 'searchlight_weight' ? 'score' : $field); - $sort .= $field .' '. strtolower($order) .','; + $sort .= $field . ' ' . strtolower($order) . ','; } $options['sort'] = rtrim($sort, ','); } + + // Handle grouping + if (isset($datasource->options['groupfield'])) { + $groupfilter = array(); + $options['group'] = 'true'; + $options['group.field'] = $datasource->options['groupfield']; + } - try { + try { $response = $client->search($query, $offset, $limit, $options); } catch (Exception $e) { - drupal_set_message('Caught exception: '. $e->getMessage(), 'error'); + drupal_set_message('Caught exception: ' . $e->getMessage(), 'error'); return FALSE; } - - foreach ($response->response->docs as $doc) { - $ids[] = $doc->{$datasource->base_field}; + + if (!empty($datasource->options['groupfield']) && is_array($response->grouped->{$datasource->options['groupfield']}->groups)) { + foreach ($response->grouped->{$datasource->options['groupfield']}->groups as $group) { + if (is_array($group->doclist->docs)) { + foreach ($group->doclist->docs as $doc) { + $ids[] = $doc->{$datasource->base_field}; + } + } + } + } + else { + foreach ($response->response->docs as $doc) { + $ids[] = $doc->{$datasource->base_field}; + } } - if (!empty($ids)) { + if (!empty($datasource->options['groupfield'])) { + $count = $response->grouped->{$datasource->options['groupfield']}->matches; + } + else { + $count = $response->response->numFound; + } return array( - 'result' => $ids, - 'total' => $response->response->numFound, + 'result' => $ids, + 'total' => $count, 'raw' => $response, ); } @@ -115,29 +150,29 @@ class SearchlightBackendSolr extends SearchlightBackend { foreach ($filters as $params) { $field = $params['field']; $operator = $params['operator']; - $args = $params['args']; + $value = $params['args']; - switch($operator) { + switch ($operator) { case '<': case '<=': - $range_filters[$field]['max'] = $args[0]; + $range_filters[$field]['max'] = implode($value); break; case '>': case '>=': - $range_filters[$field]['min'] = $args[0]; + $range_filters[$field]['min'] = implode($value); break; case '=': - $client->searchlightFilters[] = $field .':"'. $args[0] .'"'; + $client->searchlightFilters[] = $field . ':"' . implode($value) . '"'; break; case '!=': case '<>': - $client->searchlightFilters[] = '-'. $field .':"'. $args[0] .'"'; + $client->searchlightFilters[] = '-' . $field . ':"' . implode($value) . '"'; break; case 'IN': - $client->searchlightFilters[] = "{$field}:(" . implode(' OR ', $args) .")"; + $client->searchlightFilters[] = "{$field}:(" . implode(' OR ', $value) . ")"; break; case 'NOT IN': - $client->searchlightFilters[] = "-{$field}:(" . implode(' OR ', $args) .")"; + $client->searchlightFilters[] = "-{$field}:(" . implode(' OR ', $value) . ")"; break; default: //dsm($params); @@ -159,7 +194,7 @@ class SearchlightBackendSolr extends SearchlightBackend { if (!empty($sorts)) { $client->searchlightSort = array(); foreach ($sorts as $sort) { - $client->searchlightSort[$sort['field']] = $sort['order']; + $client->searchlightSort[$sort['field']] = $sort['direction']; } } } @@ -200,7 +235,7 @@ class SearchlightBackendSolr extends SearchlightBackend { function facetBuild(&$client, $datasource, $query = '', $facets) { $query = $this->solrPrepareQuery($client, $datasource, $query); - $limit_options = $options; + $limit_options = array(); $options = array( 'facet' => 'true', 'facet.mincount' => '1', @@ -219,12 +254,13 @@ class SearchlightBackendSolr extends SearchlightBackend { $field = $datasource->fields[$facet['field']]; switch ($field['datatype']) { case 'timestamp': + case 'date': $date_facets[$facet['field']] = $facet; // TODO cache this value so it's not requested on every page load. $limits = array( - 'max' => $facet['field'] ." desc", - 'min' => $facet['field'] ." asc", + 'max' => $facet['field'] . " desc", + 'min' => $facet['field'] . " asc", ); foreach ($limits as $lim => $sort) { @@ -237,7 +273,7 @@ class SearchlightBackendSolr extends SearchlightBackend { $limits[$lim] = $response->response->docs[0]->{$facet['field']}; } catch (Exception $e) { - drupal_set_message('Caught exception: '. $e->getMessage(), 'error'); + drupal_set_message('Caught exception: ' . $e->getMessage(), 'error'); return; } } @@ -245,7 +281,7 @@ class SearchlightBackendSolr extends SearchlightBackend { $options['facet.date'][] = $facet['field']; // @TODO several issues here // - start/end will need to be adjusted to match an even started - // point for the gap so that dates all fit within the declared + // point for the gap so that dates all fit within the declared // gap, and we avoid "midnight" problems. // - the month/day/time is set to Jan 1 00:00:00 as granularity/gaps // are calculated as increments from the starting point. HOWEVER, @@ -257,7 +293,7 @@ class SearchlightBackendSolr extends SearchlightBackend { $options["f.{$facet['field']}.facet.date.start"] = $limits['min']; $options["f.{$facet['field']}.facet.date.end"] = $limits['max']; - switch ($facet['granularity']) { + switch (!empty($facet['granularity']) ? $facet['granularity'] : 'month') { case 'day': $gap = '+1DAY'; break; @@ -284,7 +320,7 @@ class SearchlightBackendSolr extends SearchlightBackend { $response = $client->search($query, 0, 1, $options); } catch (Exception $e) { - drupal_set_message('Caught exception: '. $e->getMessage(), 'error'); + drupal_set_message('Caught exception: ' . $e->getMessage(), 'error'); return; } @@ -295,8 +331,13 @@ class SearchlightBackendSolr extends SearchlightBackend { foreach ($v as $i => $j) { // '_empty_' is Solr speak for NULL or empty values. Convert that // string back into something more palatable in Drupal. - $i = $i === '_empty_' ? NULL : $i; - $built[$k][$i] = array('id' => $i, 'count' => $j); + $i = ($i === '_empty_' || empty($i)) ? NULL : $i; + if (!empty($i)) { + $built[$k][$i] = array( + 'id' => $i, + 'count' => $j, + ); + } } } } @@ -305,16 +346,19 @@ class SearchlightBackendSolr extends SearchlightBackend { foreach ($response->facet_counts->facet_dates as $k => $v) { foreach ($v as $i => $j) { // Filter out empty ranges. - if (!empty($j) && !in_array($i, array('gap', 'end'))) { + if (!empty($j) && !in_array($i, array('gap', 'end', 'start'))) { // $time = gmmktime($i); // $build[$k][$i]format_date($time); - $built[$k][$i] = array('id' => $i, 'count' => $j); + $built[$k][$i] = array( + 'id' => $i, + 'count' => $j, + ); } } - // Reverse-chronological sort the dates. + // Chronological sort the dates. // @TODO: unhardwire this and do handling of facets sorting in general. if (!empty($built[$k])) { - krsort($built[$k]); + ksort($built[$k]); // Hard limit the date facet as Solr returns all elements between start // and end times. if (!empty($date_facets[$k]['limit']) && count($built[$k]) > $date_facets[$k]['limit']) { @@ -338,7 +382,7 @@ class SearchlightBackendSolr extends SearchlightBackend { $solr->commit(); } catch (Exception $e) { - drupal_set_message('Caught exception: '. $e->getMessage(), 'error'); + drupal_set_message('Caught exception: ' . $e->getMessage(), 'error'); return; } } @@ -348,19 +392,38 @@ class SearchlightBackendSolr extends SearchlightBackend { * Start the Solr service. */ function drushSearchd() { - $file_path = conf_path() .'/solr'; - $solr_home = drush_locate_root() .'/'. conf_path() . '/solr'; - $log_dir = $solr_home .'/log'; - if (file_check_directory($log_dir, TRUE)) { - $opts .= '-Dsolr.solr.home='. $solr_home .' '; - $opts .= '-Djetty.logs='. $log_dir.' '; - $opts .= '-Djetty.home='. $this->settings['jar_path'] .' '; - $opts .= '-jar '. $this->settings['jar_path'] .'/start.jar'; - drush_op('drush_shell_exec', 'java '. $opts); + $file_path = conf_path() . '/solr'; + $solr_home = drush_locate_root() . '/' . conf_path() . '/solr'; + $log_dir = $solr_home . '/log'; + if (file_prepare_directory($log_dir, TRUE)) { + $opts .= '-Dsolr.solr.home=' . $solr_home . ' '; + $opts .= '-Djetty.logs=' . $log_dir . ' '; + $opts .= '-Djetty.home=' . $this->settings['jar_path'] . ' '; + $opts .= '-jar ' . $this->settings['jar_path'] . '/start.jar'; + drush_op('drush_shell_exec', 'java ' . $opts); } return drush_log("An error ocurred while starting the search daemon.", 'error'); } + /** + * Create Solr date format + */ + function createSolrDate($value, $type = 'date') { + if (!empty($value)) { + switch($type) { + case 'timestamp' && preg_match('~^[1-9][0-9]*$~', $value): + $date = gmdate('Y-m-d\\TH:i:s\\Z', $value); + break; + default: + $date_time = new DateTime($value, new DateTimeZone('UTC')); + $date = $date_time->format('Y-m-d\\TH:i:s\\Z'); + break; + } + return $date; + } + return $value; + } + /** * Override of drushIndex(). * Run an indexing job. Requires that the Solr service is available. @@ -442,7 +505,7 @@ class SearchlightBackendSolr extends SearchlightBackend { } $ids = implode(',', array_keys($items['datasource'])); - $view->query->add_where(0, "{$view->base_table}.{$view->base_field} IN ({$ids})"); + $view->query->add_where(0, "{$view->base_table}.{$view->base_field} IN ({$ids})", array(), 'formula'); } else { continue; @@ -477,26 +540,36 @@ class SearchlightBackendSolr extends SearchlightBackend { break; case 'multivalue': $mva_item = $raw_items[$field['name']][$id]; - // Ideally we would use the handler to discover the MVA field - // alias. For now just do it by convention. - $alias = "{$field['table']}_{$field['field']}"; - if (isset($mva_item->{$alias})) { - if ($field['datatype'] == 'timestamp') { - $document->{$field['name']} = gmdate('Y-m-d\\TH:i:s\\Z', $mva_item->{$alias}); - } - else { - $document->{$field['name']} = $mva_item->{$alias}; - } - } + $document->{$field['name']} = $mva_item->{$field['name']}; break; default: $raw_item = $raw_items['datasource'][$id]; - if (isset($raw_item->{$field['name']})) { - if ($field['datatype'] == 'timestamp') { - $document->{$field['name']} = gmdate('Y-m-d\\TH:i:s\\Z', $raw_item->{$field['name']}); + if (!empty($raw_item->{$field['name']})) { + switch($field['datatype']) { + case 'date': + case 'timestamp': + $document->{$field['name']} = $this->createSolrDate($raw_item->{$field['name']}, $field['datatype']); + break; + case 'int': + $document->{$field['name']} = intval($raw_item->{$field['name']}); + break; + default: + $document->{$field['name']} = $raw_item->{$field['name']}; + break; } - else { - $document->{$field['name']} = $raw_item->{$field['name']}; + } + elseif (!empty($item->{$field['name']})) { + switch($field['datatype']) { + case 'date': + case 'timestamp': + $document->{$field['name']} = $this->createSolrDate($item->{$field['name']}, $field['datatype']); + break; + case 'int': + $document->{$field['name']} = intval($item->{$field['name']}); + break; + default: + $document->{$field['name']} = $item->{$field['name']}; + break; } } break; @@ -511,10 +584,11 @@ class SearchlightBackendSolr extends SearchlightBackend { $solr->commit(); // $solr->optimize(); //merges multiple segments into one - $args = $ids; - array_unshift($args, $datasource->base_table); - db_query("UPDATE {searchlight_search} SET status = 1 WHERE type ='%s' - AND id IN (". rtrim(str_repeat('%d,', count($ids)), ',') .")", $args); + $query = db_update('searchlight_search') + ->fields(array('status' => 1)) + ->condition('type', $datasource->base_table) + ->condition('id', $ids, 'IN') + ->execute(); drush_log(dt('@datasource: Indexing completed for @count items.', array('@datasource' => $datasource->name, '@count' => count($ids))), 'success'); } @@ -523,9 +597,13 @@ class SearchlightBackendSolr extends SearchlightBackend { } // Deletion. - $result = db_query("SELECT * FROM {searchlight_search} WHERE status = -1 AND type = '%s'", $datasource->base_table); + $result = db_select('searchlight_search', 's') + ->fields('s') + ->condition('status', -1) + ->condition('type', $datasource->base_table) + ->execute(); $delete = array(); - while ($row = db_fetch_object($result)) { + foreach ($result as $row) { $delete[] = $row->id; } if (!empty($delete)) { @@ -536,11 +614,14 @@ class SearchlightBackendSolr extends SearchlightBackend { $solr->commit(); // Once updates are complete, remove the Drupal-side records. - db_query("DELETE FROM {searchlight_search} WHERE status = -1 AND type = '%s'", $datasource->base_table); + db_delete('searchlight_search') + ->condition('status', -1) + ->condition('type', $datasource->base_table) + ->execute(); drush_log(dt('@datasource: Deletion completed for @count items.', array('@datasource' => $datasource->name, '@count' => count($delete))), 'success'); } - variable_set('searchlight_solr_last', time()); + variable_set('searchlight_solr_last', REQUEST_TIME); } } @@ -549,23 +630,23 @@ class SearchlightBackendSolr extends SearchlightBackend { * Write the Solr configuration files. */ function drushConf() { - $file_path = conf_path() .'/solr'; - if (file_check_directory($file_path, TRUE)) { + $file_path = conf_path() . '/solr'; + if (file_prepare_directory($file_path, TRUE)) { // Collect configuration arrays for each datasource. $cores = array(); foreach (searchlight_get_datasource() as $datasource) { $datasource->init(); $cores[] = $datasource->id; - $core_path = $file_path .'/'. $datasource->id; - if (!file_check_directory($core_path, TRUE)) { + $core_path = $file_path . '/' . $datasource->id; + if (!file_prepare_directory($core_path, TRUE)) { return drush_log("/{$core_path} could not be written to.", 'error'); } $core_path .= '/conf'; - if (file_check_directory($core_path, TRUE)) { + if (file_prepare_directory($core_path, TRUE)) { // Generate configuration file from datasources. $schema = $this->solrDatasourceConf($datasource); $files = array( - 'schema.xml' => theme('searchlight_solr_schema', $schema), + 'schema.xml' => theme('searchlight_solr_schema', array('datasource' => $schema)), 'solrconfig.xml' => theme('searchlight_solr_config'), ); $this->solrWriteFiles($core_path, $files); @@ -575,7 +656,7 @@ class SearchlightBackendSolr extends SearchlightBackend { } } // Generate top level config. - $files = array('solr.xml' => theme('searchlight_solr_cores', $cores)); + $files = array('solr.xml' => theme('searchlight_solr_cores', array('cores' => $cores))); $this->solrWriteFiles($file_path, $files); } else { @@ -604,9 +685,9 @@ class SearchlightBackendSolr extends SearchlightBackend { * Assemble the query string. */ function solrPrepareQuery(&$client, $datasource, $query) { - $query = empty($query) ? '[* TO *]' : '"'. $query .'"'; // Is this really right? + $query = empty($query) ? '[* TO *]' : '"' . $query . '"'; // Is this really right? if (isset($client->searchlightFilters)) { - $query .= ' '. implode(' ', $client->searchlightFilters); + $query .= ' ' . implode(' ', $client->searchlightFilters); } return $query; } @@ -643,10 +724,14 @@ class SearchlightBackendSolr extends SearchlightBackend { case 'int': $conf['schema'][$f]['type'] = 'integer'; break; + case 'float': + $conf['schema'][$f]['type'] = 'sfloat'; + break; case 'text': $conf['schema'][$f]['type'] = 'string'; break; case 'timestamp': + case 'date': $conf['schema'][$f]['type'] = 'date'; } @@ -662,15 +747,15 @@ class SearchlightBackendSolr extends SearchlightBackend { * Write config to the filesystem */ function solrWriteFiles($path, $files) { - foreach($files as $name => $contents) { + foreach ($files as $name => $contents) { if ($contents) { $existing = ''; if (file_exists("{$path}/{$name}")) { $existing = file_get_contents("{$path}/{$name}"); } if ($contents !== $existing) { - file_put_contents("{$path}/{$name}", $contents); - drush_log("{$path}/{$name} was written successfully.", 'success'); + file_put_contents("{$path}/{$name}", $contents); + drush_log("{$path}/{$name} was written successfully.", 'success'); } else { drush_log("{$path}/{$name} is unchanged.", 'success'); diff --git a/plugins/SearchlightBackendSphinx.inc b/plugins/SearchlightBackendSphinx.inc index e043635..ac4e234 100644 --- a/plugins/SearchlightBackendSphinx.inc +++ b/plugins/SearchlightBackendSphinx.inc @@ -52,12 +52,12 @@ class SearchlightBackendSphinx extends SearchlightBackend { $form['index'] = array( '#type' => 'fieldset', '#title' => t('Index settings'), - '#description' => t('Advanced configuration options for Sphinx. Use these options to enable CJK or other character set handling for Sphinx. Otherwise, you can leave these blank.') + '#description' => t('Advanced configuration options for Sphinx. Use these options to enable CJK or other character set handling for Sphinx. Otherwise, you can leave these blank.'), ); $form['index']['morphology'] = array( '#title' => t('Morphology'), '#description' => t('Morphology processors to apply. See !link', array( - '!link' => l(t('Sphinx: morphology'), 'http://www.sphinxsearch.com/docs/current.html#conf-morphology') + '!link' => l(t('Sphinx: morphology'), 'http://www.sphinxsearch.com/docs/current.html#conf-morphology'), )), '#type' => 'textfield', '#default_value' => $this->settings['index']['morphology'], @@ -83,7 +83,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { '#title' => t('N-gram characters list'), '#description' => t('N-gram characters list for basic CJK support. See !link and !charset for CJK support', array( '!link' => l(t('Sphinx: ngram_chars'), 'http://www.sphinxsearch.com/docs/current.html#ngram_chars'), - '!charset' => l(t('CJK n-gram characters'), 'http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk_ngram_characters') + '!charset' => l(t('CJK n-gram characters'), 'http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk_ngram_characters'), )), '#type' => 'textfield', '#default_value' => $this->settings['index']['ngram_chars'], @@ -145,13 +145,13 @@ class SearchlightBackendSphinx extends SearchlightBackend { * Override of facetBuild(). */ function facetBuild(&$client, $datasource, $query = '', $facets) { - $client = drupal_clone($client); + $client = clone $client; foreach ($facets as $facet) { $field = $datasource->fields[$facet['field']]; $limit = $facet['limit']; - switch ($field['datatype']) { case 'timestamp': + case 'date': $groupby = array('day' => SPH_GROUPBY_DAY, 'month' => SPH_GROUPBY_MONTH, 'year' => SPH_GROUPBY_YEAR); $granularity = !empty($facet['granularity']) ? $facet['granularity'] : 'month'; $client->SetGroupBy($field['name'], $groupby[$granularity], '@group desc'); @@ -186,6 +186,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { $id = $this->sphinxGetOrdinal($datasource, $field['name'], $attr['@groupby']); break; case 'timestamp': + case 'date': $id = $this->sphinxGetTimestamp($attr['@groupby'], !empty($facet['granularity']) ? $facet['granularity'] : 'month'); break; default: @@ -209,8 +210,10 @@ class SearchlightBackendSphinx extends SearchlightBackend { variable_del('searchlight_sphinx_last'); $delete = array(); - $result = db_query("SELECT " . $datasource->base_field . " FROM {" . $datasource->base_table . "}"); - while ($row = db_fetch_object($result)) { + $result = db_select($datasource->base_table, 'b') + ->fields('b', array($datasource->base_field)) + ->execute(); + foreach ($result as $row) { $delete[$row->{$datasource->base_field}] = array(1); } @@ -283,6 +286,9 @@ class SearchlightBackendSphinx extends SearchlightBackend { case 'NOT IN': $this->sphinxSetFilter($client, $datasource, $field, $args, TRUE); break; + case 'IS NOT NULL': + $this->sphinxSetFilter($client, $datasource, $field, array(0), TRUE); + break; } } } @@ -305,10 +311,10 @@ class SearchlightBackendSphinx extends SearchlightBackend { $sphinx_sorts = array(); foreach ($sorts as $sort) { if ($sort['field'] === 'searchlight_weight') { - $sphinx_sorts[] = "@weight {$sort['order']}"; + $sphinx_sorts[] = "@weight {$sort['direction']}"; } else { - $sphinx_sorts[] = "{$sort['field']} {$sort['order']}"; + $sphinx_sorts[] = "{$sort['field']} {$sort['direction']}"; } } $client->setSortMode(SPH_SORT_EXTENDED, implode(', ', $sphinx_sorts)); @@ -347,7 +353,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { } } - + /** * Return a directory where the configuration files and indexes are stored. * @@ -371,13 +377,13 @@ class SearchlightBackendSphinx extends SearchlightBackend { $conf_path = $this->drushGetConfigPath(); $conf_file = $this->drushGetConfigFile(); - if (file_check_directory($conf_path, TRUE)) { + if (file_prepare_directory($conf_path, TRUE)) { $files = array(); // rerieve list of possible configuration file matches. $files[] = $conf_path . '/sphinx.conf'; - $matches = drush_scan_directory($conf_path . '/index.d/', '/^.*\.conf$/', array('.', '..') , 0, false, 'name'); + $matches = drush_scan_directory($conf_path . '/index.d/', '/^.*\.conf$/', array('.', '..'), 0, false, 'name'); foreach ($matches as $name => $file) { $files[] = $file->filename; @@ -393,7 +399,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { } } - // load the previous file + // load the previous file $old_config = (file_exists($conf_file)) ? file_get_contents($conf_file) : ''; if ($old_config != $string) { @@ -414,7 +420,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { $file_path = $this->drushGetConfigPath() . '/log'; $lock_file = "{$file_path}/searchd.lock"; - if (file_check_directory($file_path, TRUE)) { + if (file_prepare_directory($file_path, TRUE)) { $lock = file_exists($lock_file) ? file_get_contents($lock_file) : 0; if ((int) $lock == 1) { @@ -422,7 +428,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { } file_put_contents($lock_file, '1'); - if (file_check_directory($file_path, TRUE)) { + if (file_prepare_directory($file_path, TRUE)) { $pid_file = "{$file_path}/searchd.pid"; $pid = file_exists($pid_file) ? file_get_contents($pid_file) : ''; @@ -450,9 +456,9 @@ class SearchlightBackendSphinx extends SearchlightBackend { // Create the sphinx directory if it doesn't exist. $changed = $this->drushGenerateMergedConfig(); $file_path = $this->drushGetConfigPath() . '/indexes'; - if (file_check_directory($file_path, TRUE)) { + if (file_prepare_directory($file_path, TRUE)) { // Determine if we should update deltas only. - $delta = $this->settings['delta_ttl'] && (variable_get('searchlight_sphinx_last', 0) + $this->settings['delta_ttl']) > time(); + $delta = $this->settings['delta_ttl'] && (variable_get('searchlight_sphinx_last', 0) + $this->settings['delta_ttl']) > REQUEST_TIME; // Build list of this site's indexes. $indexes = array(); @@ -473,8 +479,12 @@ class SearchlightBackendSphinx extends SearchlightBackend { // Note that we must update both the main and delta indices. foreach (searchlight_get_datasource() as $datasource) { $delete = array(); - $result = db_query("SELECT * FROM {searchlight_search} WHERE status = -1 AND type = '%s'", $datasource->base_table); - while ($row = db_fetch_object($result)) { + $result = db_select('searchlight_search', 's') + ->fields('s') + ->condition('status', -1) + ->condition('type', $datasource->base_table) + ->execute(); + foreach ($result as $row) { $delete[$row->id] = array(1); } if (!empty($update)) { @@ -483,14 +493,20 @@ class SearchlightBackendSphinx extends SearchlightBackend { $client->UpdateAttributes($indexes_delta[$datasource->id], array('searchlight_deleted'), $delete); // Once updates are complete, remove the Drupal-side records. - db_query("DELETE FROM {searchlight_search} WHERE status = -1 AND type = '%s'", $datasource->base_table); + db_delete('searchlight_search') + ->condition('status', -1) + ->condition('type', $datasource->base_table) + ->execute(); } } // Clear caches so that sphinx id -> facet name mapping is updated. cache_clear_all('searchlight', 'cache', TRUE); - db_query('UPDATE {searchlight_search} SET status = 1 WHERE status = 0'); - variable_set('searchlight_sphinx_last', time()); + db_update('searchlight_search') + ->fields(array('status' => 1)) + ->condition('status', 0) + ->execute(); + variable_set('searchlight_sphinx_last', REQUEST_TIME); drush_log('Indexing complete.', 'success'); if ($changed === TRUE) { return $this->drushSearchd(); @@ -527,28 +543,28 @@ class SearchlightBackendSphinx extends SearchlightBackend { // variable_set('searchlight_config_changed', FALSE); // } - if (file_check_directory($file_path, TRUE) && drush_get_option('sl-sphinx-base-conf', TRUE)) { + if (file_prepare_directory($file_path, TRUE) && drush_get_option('sl-sphinx-base-conf', TRUE)) { $this->drushBaseConf(); } - if (file_check_directory($conf_path, TRUE)) { + if (file_prepare_directory($conf_path, TRUE)) { // Collect configuration arrays for each datasource. - foreach (searchlight_get_datasource() as $datasource) { - $source = $this->sphinxDatasourceConf($datasource); - - // Add delta index. + foreach (searchlight_datasource_load(NULL, TRUE) as $datasource) { + $conf = array(FALSE); if (!empty($this->settings['delta_ttl'])) { - $source = $this->sphinxDatasourceConf($source, TRUE); + $conf[] = TRUE; } - - $conf_file = "{$conf_path}/{$source['conf']['id']}.conf"; - - // Generate configuration file from datasource. - $sphinx_conf = theme('searchlight_sphinx_index_conf', $source); - $existing = file_exists($conf_file) ? file_get_contents($conf_file) : ''; - if ($sphinx_conf !== $existing) { - file_put_contents($conf_file, $sphinx_conf); - drush_log("{$conf_file} was written successfully.", 'success'); + foreach ($conf as $delta_ttl) { + $source = $this->sphinxDatasourceConf($datasource, $delta_ttl); + $conf_file = "{$conf_path}/{$source['conf']['id']}.conf"; + + // Generate configuration file from datasource. + $sphinx_conf = theme('searchlight_sphinx_index_conf', array('datasource' => $source)); + $existing = file_exists($conf_file) ? file_get_contents($conf_file) : ''; + if ($sphinx_conf !== $existing) { + file_put_contents($conf_file, $sphinx_conf); + drush_log("{$conf_file} was written successfully.", 'success'); + } } } return drush_log("Sphinx configuration files were written", 'success'); @@ -562,8 +578,8 @@ class SearchlightBackendSphinx extends SearchlightBackend { function drushBaseConf() { $file_path = $this->drushGetConfigPath(); $conf_file = "{$file_path}/sphinx.conf"; - if (file_check_directory($file_path, TRUE)) { - $sphinx_conf = theme('searchlight_sphinx_conf', $this->sphinxSearchdConf()); + if (file_prepare_directory($file_path, TRUE)) { + $sphinx_conf = theme('searchlight_sphinx_conf', array('searchd' => $this->sphinxSearchdConf())); if ($sphinx_conf) { $existing = file_exists($conf_file) ? file_get_contents($conf_file) : ''; if ($sphinx_conf === $existing) { @@ -618,7 +634,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { */ function sphinxSetFilter(&$client, $datasource, $attribute, $values, $exclude = FALSE) { $ordinals = array(); - foreach ($values as $arg) { + foreach ((array) $values as $arg) { $arg = trim($arg); if (is_numeric($arg)) { $ordinals[] = $arg; @@ -640,22 +656,24 @@ class SearchlightBackendSphinx extends SearchlightBackend { $datasource->init(); $datasource_id = $delta ? $datasource->id . '_delta' : $datasource->id; - $conf = array('conf' => array(), 'index' => array()); - - // Retrieve db info. - global $db_url, $db_type; - $url = is_array($db_url) ? $db_url['default'] : $db_url; - $url = parse_url($url); + $conf = array( + 'conf' => array(), + 'index' => array(), + ); // Configuration options. $conf['conf']['id'] = $datasource_id; - $conf['conf']['type'] = $db_type === 'mysqli' ? 'mysql' : $db_type; - $conf['conf']['sql_user'] = urldecode($url['user']); - $conf['conf']['sql_pass'] = isset($url['pass']) ? urldecode($url['pass']) : ''; - $conf['conf']['sql_host'] = urldecode($url['host']); - $conf['conf']['sql_db'] = trim(urldecode($url['path']), '/'); - $conf['conf']['sql_port'] = isset($url['port']) ? urldecode($url['port']) : '3306'; + // Use mysql stored database credentials. + if ($creds = drush_get_context('DRUSH_DB_CREDENTIALS')) { + $conf['conf']['type'] = $creds['driver'] === 'mysqli' ? 'mysql' : $creds['driver']; + $conf['conf']['sql_user'] = $creds['user']; + $conf['conf']['sql_pass'] = $creds['pass']; + $conf['conf']['sql_host'] = $creds['host']; + $conf['conf']['sql_db'] = $creds['name']; + $conf['conf']['sql_port'] = isset($creds['port']) ? $creds['port'] : '3306'; + + } // Check for optional sql_sock option. $sock = trim($this->settings['sql_sock']); if (!empty($sock)) { @@ -693,23 +711,24 @@ class SearchlightBackendSphinx extends SearchlightBackend { // Force utf8 when indexing. $conf['conf']['sql_query_pre'] = "SET NAMES utf8"; - $sql_query = drupal_clone($view->query); - $sql_query->add_where(0, "{$view->base_table}.{$view->base_field}" .' BETWEEN $start AND $end'); + $sql_query = clone $view->query; + + $sql_query->add_where(0, "{$view->base_table}.{$view->base_field}" . ' BETWEEN $start AND $end ', array(), 'formula'); $sql_query->add_field(NULL, '0', 'searchlight_deleted'); if ($delta) { $this->queryFresh($sql_query); } - $conf['conf']['sql_query'] = $this->sphinxWriteSQL($sql_query->query(), $sql_query->get_where_args()); + $conf['conf']['sql_query'] = $this->sphinxWriteSQL($sql_query); $conf['conf']['sql_query'] = str_replace("\n", " \\\n", trim($conf['conf']['sql_query'])); // Build the info query. - $sql_query_info = drupal_clone($view->query); - $sql_query_info->add_where(0, "{$view->base_table}.{$view->base_field}" .' = $id'); + $sql_query_info = clone $view->query; + $sql_query_info->add_where(0, "{$view->base_table}.{$view->base_field}" . ' = $id', array(), 'formula'); $sql_query_info->add_field(NULL, '0', 'searchlight_deleted'); if ($delta) { $this->queryFresh($sql_query_info); } - $conf['conf']['sql_query_info'] = $this->sphinxWriteSQL($sql_query_info->query(), $sql_query_info->get_where_args()); + $conf['conf']['sql_query_info'] = $this->sphinxWriteSQL($sql_query); $conf['conf']['sql_query_info'] = str_replace("\n", " \\\n", trim($conf['conf']['sql_query_info'])); // Assume serial ids on the base table and step by 1000. @@ -721,9 +740,9 @@ class SearchlightBackendSphinx extends SearchlightBackend { // Merge in attributes. $sql_attr = array(); $sphinx_type = array( - 'text' => 'sql_attr_str2ordinal', - 'int' => 'sql_attr_uint', - 'float' => 'sql_attr_float', + 'text' => 'sql_attr_str2ordinal', + 'int' => 'sql_attr_uint', + 'float' => 'sql_attr_float', 'timestamp' => 'sql_attr_timestamp', ); @@ -739,7 +758,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { } // Generate multivalue queries. else if ($field['usage'] === 'multivalue' && $mva_view = searchlight_build_view($field['view'])) { - $query = drupal_clone($mva_view->query); + $query = clone $mva_view->query; // Remove any fields that are not the id field or attribute field. foreach ($query->fields as $alias => $query_field) { if ($query_field['field'] === $mva_view->base_field && $query_field['table'] === $mva_view->base_table) { @@ -753,7 +772,7 @@ class SearchlightBackendSphinx extends SearchlightBackend { if ($delta) { $this->queryFresh($query); } - $mva_query = $this->sphinxWriteSQL($query->query(), $query->get_where_args()); + $mva_query = $this->sphinxWriteSQL($query); $mva_query = str_replace("\n", " \\\n", trim($mva_query)); $sql_attr[] = "sql_attr_multi = uint {$field['name']} from query; {$mva_query}"; } @@ -767,8 +786,8 @@ class SearchlightBackendSphinx extends SearchlightBackend { } /** - * Get the Sphinx searchd settings. - */ + * Get the Sphinx searchd settings. + */ function sphinxSearchdConf() { $searchd = array(); $searchd['log'] = $this->drushGetConfigPath() . '/log/searchd.log'; @@ -787,11 +806,75 @@ class SearchlightBackendSphinx extends SearchlightBackend { /** * Write a SQL query with fully prefixed tables and replaced arguments. */ - function sphinxWriteSQL($query, $args) { - _db_query_callback($args, TRUE); - $query = db_prefix_tables($query); - $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - return $query; + function sphinxWriteSQL($query) { + $connection = Database::getConnection('default'); + $sql = $connection->prepareQuery( (string) $query->query())->queryString; + + // Monstrous hack. Because PDO only allows access to prepared queries and + // not the full query with placeholders replaced with actual values we + // need to "parse" and replace the placeholders on our own. + // + // Assumptions: + // - Argument placeholder tokens are named as :db_condition_placeholder_[x] + // and Views where condition are in the same order and as the + // placeholders. + $replace = array(); + // Process SQL args in Views JOINs. + $i = 0; + foreach ($query->table_queue as $t) { + if (!empty($t['join']->extra) && is_array($t['join']->extra)) { + foreach ($t['join']->extra as $extra) { + if (!is_null($extra['value'])) { + $replace[":views_join_condition_{$i}"] = $this->sphinxPrepareSQLArg($extra['value']); + $i++; + } + } + } + } + // Process SQL args in Views WHEREs + $i = 0; + foreach ($query->where as $w) { + if (isset($w['conditions'])) { + foreach ($w['conditions'] as $condition) { + if (!empty($condition['value'])) { + $args = array(); + foreach ($condition['value'] as $arg) { + $args[] = $this->sphinxPrepareSQLArg($arg); + } + $replace[":db_condition_placeholder_{$i}"] = implode(',', $args); + $i++; + } + } + } + } + $sql = strtr($sql, $replace); + return $sql; + } + + /** + * Prepare an SQL argument for use in an SQL query. + * Do *NOT* use this unless you absolutely know how and why you might need + * it. It is only used in the Searchlight sphinx backend to generate select + * queries for use in the sphinx.conf file. + */ + function sphinxPrepareSQLArg($arg) { + $connection = Database::getConnection('default'); + switch ($connection->databaseType()) { + case 'mysql': + case 'pgsql': + // @TODO better handling of types. Currently numeric strings and actual + // number fields can be confused. + if (is_numeric($arg)) { + } + else { + // addslashes() is used here because mysql_real_escape_string() + // requires a mysql_connect() connection which is not used by PDO. + // @TODO: fix this stopgap with a real solution.. + $arg = addslashes($arg); + $arg = "'{$arg}'"; + } + return $arg; + } } /** @@ -856,14 +939,13 @@ class SearchlightBackendSphinx extends SearchlightBackend { if (!empty($result['matches']) && count($result['matches']) < 1000) { // Dispatch a Views query to retrieve the corresponding string. - $ids = implode(',', array_keys($result['matches'])); + $ids = array_keys($result['matches']); $view = $datasource->view->copy(); $view->build(); $view->set_items_per_page(0); $view->query->where = array(); - $view->query->add_where(0, "{$view->base_table}.{$view->base_field} IN ({$ids})"); + $view->query->add_where(0, "{$view->base_table}.{$view->base_field}", $ids, 'IN'); $view->build_info['query'] = $view->query->query(); - $view->build_info['query_args'] = $view->query->get_where_args(); $view->execute(); foreach ($view->result as $row) { $id = $row->{$view->base_field}; diff --git a/plugins/SearchlightFacet.inc b/plugins/SearchlightFacet.inc index af32d98..f1f6011 100644 --- a/plugins/SearchlightFacet.inc +++ b/plugins/SearchlightFacet.inc @@ -36,7 +36,16 @@ class SearchlightFacet { */ function query(&$query) { // Filter the query if there is an active facet value. + // Remove any existing filters for this same value in order to cleanly + // "replace" the filter condition. Note that this requires the Searchlight + // facets filter handler to be placed *after* any other Views filters that + // should be replaced when a facet is active. if (isset($this->value)) { + foreach ($query->search_filter as $key => $filter) { + if ($filter['field'] === $this->name) { + unset($query->search_filter[$key]); + } + } $query->search_filter[] = array( 'field' => $this->name, 'operator' => '=', @@ -93,7 +102,9 @@ class SearchlightFacet { break; } if (!empty($items) && $this->viewInit($query)) { - return $this->viewRenderItems($items); + $items = $this->viewRenderItems($items); + $this->sort($items); + return $items; } return array(); } @@ -114,12 +125,12 @@ class SearchlightFacet { $path = $this->environment->getURLPath(); $options = $this->environment->getURLOptions('remove', $this->name, $item['id']); $item['link'] = l(t('remove'), $path, $options); - return theme('searchlight_facet_active', $this->field, $item); + return theme('searchlight_facet_active', array('field' => $this->field, 'item' => $item)); case 'facets': $path = $this->environment->getURLPath(); $options = $this->environment->getURLOptions('add', $this->name, $item['id']); $item['link'] = l($item['title'], $path, $options); - return theme('searchlight_facet_link', $this->field, $item); + return theme('searchlight_facet_link', array('field' => $this->field, 'item' => $item)); } } @@ -140,8 +151,8 @@ class SearchlightFacet { else { $this->view = $query->datasource->view->copy(); $this->view->build(); - foreach ($this->view->field as $field_handler) { - if ($field_handler->field_alias === $this->name) { + foreach ($this->view->field as $name => $field_handler) { + if (isset($field_handler->aliases[$this->name]) || $name == $this->name) { $this->handler = $field_handler; } } @@ -155,25 +166,18 @@ class SearchlightFacet { */ function viewRenderItems($items) { $rows = array(); + $options = array(); // Multivalue fields must build a new Views query in order to // retrieve any secondary label field values. if ($this->field['usage'] === 'multivalue') { $ids = array_keys($items); - // Views 2.x - if (views_api_version() == '2.0') { - views_include('query'); - $query = new views_query($this->field['table'], $this->field['field']); - } - // Views 3.x - else { - $query = views_get_plugin('query', 'views_query'); - $query->init($this->field['table'], $this->field['field']); - } + $query = views_get_plugin('query', 'views_query'); + $query->init($this->field['table'], $this->field['field'], $options); // Add WHERE condition on item ids. - $query->add_where(0, "{$this->field['table']}.{$this->field['field']} IN (". db_placeholders($ids, 'int') .")", $ids); + $query->add_where(0, "{$this->field['table']}.{$this->field['field']}", $ids, 'IN'); // Add base field. $field_alias = $query->add_field($this->field['table'], $this->field['field']); @@ -189,15 +193,11 @@ class SearchlightFacet { $rows[$row->{$this->name}] = $row; } } - // For regular fields attempt to spoof rows with the appropriate field - // values for rendering by the field handler. - else { - $rows = array(); - foreach ($items as $item) { - $row = new stdClass(); - $row->{$this->name} = $item['id']; - $rows[$item['id']] = $row; - } + + foreach ($items as $item) { + $row = new stdClass(); + $row->{$this->handler->field_alias} = $item['id']; + $rows[$item['id']] = $row; } // Render item titles. @@ -215,6 +215,7 @@ class SearchlightFacet { return array( 'label' => '', 'items' => 5, + 'sort' => 'count' ); } @@ -234,6 +235,12 @@ class SearchlightFacet { '#default_value' => $this->options['items'], '#options' => drupal_map_assoc(range(1, 20)) + array(0 => t('Show all')), ); + $form['sort'] = array( + '#title' => t('Sort by'), + '#type' => 'select', + '#default_value' => $this->options['sort'], + '#options' => array('count' => t('Number of items'), 'name' => t('Alphabetical')), + ); } /** @@ -241,4 +248,34 @@ class SearchlightFacet { */ function extendedForm(&$form, $form_state) { } + + /** + * Sort built facet items. + */ + function sort(&$items) { + switch ($this->options['sort']) { + case 'count': + uasort($items, 'SearchlightFacet::sortCount'); + break; + case 'name': + uasort($items, 'SearchlightFacet::sortName'); + break; + default: + break; + } + } + + /** + * uasort callback, sort by count. + */ + function sortCount($a, $b) { + return $a['count'] < $b['count']; + } + + /** + * uasort callback, sort by name. + */ + function sortName($a, $b) { + return $a['title'] > $b['title']; + } } diff --git a/plugins/SearchlightFacetDatatypeTimestamp.inc b/plugins/SearchlightFacetDatatypeTimestamp.inc index e6ef57a..7b49261 100644 --- a/plugins/SearchlightFacetDatatypeTimestamp.inc +++ b/plugins/SearchlightFacetDatatypeTimestamp.inc @@ -9,6 +9,12 @@ class SearchlightFacetDatatypeTimestamp extends SearchlightFacet { */ function query(&$query) { if (isset($this->value)) { + foreach ($query->search_filter as $key => $filter) { + if ($filter['field'] === $this->name) { + unset($query->search_filter[$key]); + } + } + $range = $query->backend->dateRange($this->value, $this->options['granularity']); $query->search_filter[] = array( @@ -51,7 +57,7 @@ class SearchlightFacetDatatypeTimestamp extends SearchlightFacet { foreach ($items as $k => $item) { $format = $this->options['date_format']; $timestamp = is_numeric($item['id']) ? $item['id'] : strtotime($item['id']); - $items[$k]['title'] = format_date($timestamp, 'custom', $format, 0); + $items[$k]['title'] = format_date($timestamp, 'custom', $format, 'UTC'); } return $items; } @@ -65,9 +71,19 @@ class SearchlightFacetDatatypeTimestamp extends SearchlightFacet { $options = parent::optionsDefault(); $options['granularity'] = 'month'; $options['date_format'] = 'F, Y'; + $options['sort'] = 'date'; return $options; } + /** + * Provide an options form to be exposed in the Environment editor. + */ + function optionsForm(&$form, $form_state) { + parent::optionsForm($form, $form_state); + $form['sort']['#type'] = 'hidden'; + $form['sort']['#value'] = 'date'; + } + /** * Provide an options form to be exposed in the Environment editor. */ diff --git a/plugins/SearchlightFacetSearchQuery.inc b/plugins/SearchlightFacetSearchQuery.inc index e3807d8..5a0d8d8 100644 --- a/plugins/SearchlightFacetSearchQuery.inc +++ b/plugins/SearchlightFacetSearchQuery.inc @@ -44,7 +44,7 @@ class SearchlightFacetSearchQuery extends SearchlightFacet { $path = $this->environment->getURLPath(); $options = $this->environment->getURLOptions('active', $this->name, $item['id']); $item['link'] = l(t('remove'), $path, $options); - return theme('searchlight_facet_active', $this->field, $item); + return theme('searchlight_facet_active', array('field' => $this->field, 'item' => $item)); } } @@ -77,9 +77,9 @@ class SearchlightFacetSearchQuery extends SearchlightFacet { */ function optionsForm(&$form, $form_state) { parent::optionsForm($form, $form_state); - $form['items'] = array( - '#type' => 'value', - '#value' => 1, - ); + $form['items']['#type'] = 'hidden'; + $form['items']['#value'] = true; + $form['sort']['#type'] = 'hidden'; + $form['sort']['#value'] = 'count'; } } diff --git a/plugins/SearchlightFacetTerm.inc b/plugins/SearchlightFacetTerm.inc index fdb6344..7245d74 100644 --- a/plugins/SearchlightFacetTerm.inc +++ b/plugins/SearchlightFacetTerm.inc @@ -5,220 +5,18 @@ */ class SearchlightFacetTerm extends SearchlightFacet { /** - * Override of construct(). - * Do additional setup and provide information about the current active facet - * term, vocab and hierarchy. - */ - function construct($environment, $field, $value, $options) { - parent::construct($environment, $field, $value, $options); - - $this->term = taxonomy_get_term($this->value); - - // Attempt to parse the vocab from the field name. - $identifier = trim(array_pop(explode('term_data_tid_', $this->name))); - if (is_numeric($identifier)) { - $vid = $identifier; - } - else { - $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module = '%s' LIMIT 1", $identifier)); - } - $this->vocab = taxonomy_vocabulary_load($vid); - if (!empty($this->vocab->hierarchy)) { - $this->tree = array(); - foreach (taxonomy_get_tree($this->vocab->vid) as $term) { - $this->tree[$term->tid] = $term; - } - } - - $this->children = array(); - $this->parents = array(); - if (!empty($this->value) && $this->term) { - foreach (taxonomy_get_children($this->term->tid) as $term) { - $this->children[] = $term->tid; - } - foreach (taxonomy_get_parents($this->term->tid) as $term) { - $this->parents[] = $term->tid; - } - } - } - - /** - * Override of query(). - * Handle hierarchical taxonomies. - */ - function query(&$query) { - // Filter the query if there is an active facet value. - if (isset($this->value)) { - $query->search_filter[] = array( - 'field' => $this->name, - 'operator' => 'IN', - 'args' => array_merge(array($this->value), $this->children), - ); - } - - // Add this facet to be built by the backend. - // An extremely high limit is used so that we can process returned terms - // properly Drupalside. - // @TODO: This is probably a terrible idea. - if (!empty($this->vocab->hierarchy)) { - $limit = 1000; - } - else { - $limit = isset($this->options['items']) ? $this->options['items'] : 5; - } - $query->add_search_facet($this->name, $limit); - } - - /** - * Override of theme(). - */ - function theme($item, $delta) { - if ($this->vocab->hierarchy && $delta === 'active') { - if (isset($this->tree[$this->value], $this->tree[$item['id']])) { - $term_item = $this->tree[$item['id']]; - $term_current = $this->tree[$this->value]; - - // Add a class specifying the item's depth. - $item['class'] = "depth-{$term_item->depth}"; - - // Compare this item's depth vs. current value depth. - // If the item is deeper than the current depth it should be an option - // for further faceting drill-down. - if ($term_item->depth > $term_current->depth) { - return parent::theme($item, 'facets'); - } - // Otherwise, broaden the search by either linking to a parent term - // facet or (root terms) removing the term facets alltogether. - else { - if ($parent = reset($term_item->parents)) { - $path = $this->environment->getURLPath(); - $options = $this->environment->getURLOptions('add', $this->name, $parent); - $item['link'] = l(t('remove'), $path, $options); - } - else { - $path = $this->environment->getURLPath(); - $options = $this->environment->getURLOptions('remove', $this->name, $term_item->tid); - $item['link'] = l(t('remove'), $path, $options); - } - return theme('searchlight_facet_active', $this->field, $item); - } + * Override viewRenderItems to use a simple db_select(). + */ + function viewRenderItems($items) { + $result = db_select('taxonomy_term_data') + ->fields('taxonomy_term_data', array('name', 'tid')) + ->condition('tid', array_keys($items)) + ->execute(); + foreach ($result as $term) { + if (isset($items[$term->tid])) { + $items[$term->tid]['title'] = $term->name; } - return ''; } - return parent::theme($item, $delta); - } - - - /** - * Override of render(). - * Most of the complexity here is for smart handling of - * hierarchical taxonomies. - */ - function render($query, $delta) { - if ($this->viewInit($query)) { - switch ($delta) { - case 'active': - if (isset($this->value)) { - $items = array(); - // Add terms from current value all the way to root term. - $trail = $this->parents; - $trail[] = $this->value; - foreach ($trail as $num => $id) { - $items[] = array( - 'id' => $id, - 'title' => $this->getName($id), - ); - } - // Add direct children terms and get their counts. - if ($this->vocab->hierarchy && $this->term) { - $raw = $query->get_search_facet($this->name); - $terms = taxonomy_get_children($this->term->tid, $this->vocab->vid); - foreach ($terms as $term) { - $count = $this->getDeepCount($term->tid, $raw); - if ($count) { - $items[] = array('id' => $term->tid, 'title' => $term->name, 'count' => $count); - } - } - } - return $items; - } - break; - case 'facets': - $items = array(); - // If hierarchical, only show root terms and calculate their "deep" - // count values. - if ($this->vocab->hierarchy && empty($this->term)) { - $raw = $query->get_search_facet($this->name); - $terms = taxonomy_get_children(0, $this->vocab->vid); - foreach ($terms as $term) { - $count = $this->getDeepCount($term->tid, $raw); - if ($count) { - $items[] = array('id' => $term->tid, 'title' => $term->name, 'count' => $count, /*'link' => l($term->name, $_GET['q'], $options)*/); - } - } - } - // If non-hierarchical, simple flat list of terms. - else if (!isset($this->value)) { - foreach ($query->get_search_facet($this->name) as $item) { - $item['title'] = $this->getName($item['id']); - $items[] = $item; - } - } - return $items; - break; - } - } - return array(); - } - - /** - * Simple renderer for term names. - */ - function getName($tid) { - if ($term = taxonomy_get_term($tid)) { - return $term->name; - } - return NULL; - } - - /** - * Retrieve the full sum count (including all children) for a term. - * @TODO: This is wrong! If a node is tagged with both the parent term and - * a child term (or more) it will be counted multiple times. Fix this. - */ - function getDeepCount($tid, $raw) { - $count = isset($raw[$tid]['count']) ? $raw[$tid]['count'] : 0; - if ($this->options['count_method'] === 'deep') { - $children = taxonomy_get_children($tid); - $children = array_intersect_key($raw, $children); - if (!empty($children)) { - foreach (array_keys($children) as $tid) { - $count = $count + $this->getDeepCount($tid, $raw); - } - } - } - return $count; - } - - /** - * Provide default values for options. - */ - function optionsDefault() { - $options = parent::optionsDefault(); - $options['deep_count'] = 'yes'; - return $options; - } - - /** - * Provide an options form to be exposed in the Environment editor. - */ - function extendedForm(&$form, $form_state) { - parent::extendedForm($form, $form_state); - $form['count_method'] = array( - '#title' => t('Count mode'), - '#type' => 'select', - '#options' => array('shallow' => t('Shallow'), 'deep' => t('Deep')), - '#default_value' => $this->options['count_method'], - ); + return $items; } } diff --git a/searchlight.admin.inc b/searchlight.admin.inc index f916fec..dc1ca13 100644 --- a/searchlight.admin.inc +++ b/searchlight.admin.inc @@ -3,7 +3,7 @@ /** * Datasource management form. */ -function searchlight_admin_environment($form_state) { +function searchlight_admin_environment($form, $form_state) { $form = array( '#theme' => 'searchlight_admin_list', '#objects' => array(searchlight_environment_load(NULL, TRUE)), @@ -40,7 +40,7 @@ function searchlight_admin_environment_view_displays() { $usable = array(); foreach ($views as $view) { foreach ($view->display as $display) { - if ($display->display_plugin === 'page') { + if ($display->display_plugin === 'page' || $display->display_plugin === 'panel_pane') { $view->set_display($display->id); $filters = $view->display_handler->get_option('filters'); if (!empty($filters)) { @@ -65,7 +65,7 @@ function searchlight_admin_environment_validate_name($element, &$form_state) { form_set_error('name', t('The environment name can only consist of lowercase letters, dashes, underscores, and numbers.')); } // Check for name collision - else if ($exists = searchlight_environment_load($element['#value'], TRUE)) { + elseif ($exists = searchlight_environment_load($element['#value'], TRUE)) { form_set_error('name', t('A environment with this name already exists. Please choose another name or delete the existing environment before creating a new one.')); } } @@ -76,7 +76,7 @@ function searchlight_admin_environment_validate_name($element, &$form_state) { function searchlight_admin_environment_new(&$form, &$form_state) { $environment = searchlight_environment_new($form_state['values']['name'], $form_state['values']['view_display']); if (searchlight_environment_save($environment)) { - drupal_set_message(t('Saved environment %name.', array('%name' => $environment->name))); + drupal_set_message(t('Saved environment %name.', array('%name' => $environment->name))); } else { drupal_set_message(t('Could not save environment %name.', array('%name' => $environment->name)), 'error'); @@ -86,19 +86,21 @@ function searchlight_admin_environment_new(&$form, &$form_state) { /** * Edit form for environment. */ -function searchlight_admin_environment_edit($form_state, $environment) { - $form = array(); +function searchlight_admin_environment_edit($form, $form_state, $environment) { $form['#environment'] = $environment; $form['#environment_name'] = $environment->name; + $form['#attached']['js'][drupal_get_path('module', 'searchlight') . '/searchlight.admin.js'] = array('type' => 'file'); + $form['#attached']['css'][drupal_get_path('module', 'searchlight') . '/searchlight.admin.css'] = array('type' => 'file'); $environment->optionsForm($form, $form_state); - $form['buttons']['save'] = array( + $form['actions']['save'] = array( '#type' => 'submit', '#value' => t('Save'), ); - $form['buttons']['cancel'] = array( - '#type' => 'markup', - '#value' => l(t('Cancel'), 'admin/settings/search/environment'), + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => 'admin/config/search/settings/environment', ); return $form; } @@ -115,7 +117,7 @@ function searchlight_admin_environment_edit_submit(&$form, $form_state) { /** * Datasource management form. */ -function searchlight_admin_datasource($form_state) { +function searchlight_admin_datasource($form, $form_state) { views_include('admin'); $base_tables = views_fetch_base_tables(); @@ -171,7 +173,7 @@ function searchlight_admin_datasource($form_state) { ); $form['buttons']['cancel'] = array( '#type' => 'markup', - '#value' => l(t('Cancel'), 'admin/settings/search/datastore'), + '#value' => l(t('Cancel'), 'admin/config/search/settings/datastore'), ); return $form; } @@ -186,11 +188,11 @@ function searchlight_admin_datasource_new_validate(&$form, &$form_state) { form_set_error('name', t('!name field is required.', array('!name' => $form['new']['name']['#title']))); } // Check for string identifier sanity - else if (!preg_match('!^[a-z0-9_-]+$!', $name)) { + elseif (!preg_match('!^[a-z0-9_-]+$!', $name)) { form_set_error('name', t('The datasource name can only consist of lowercase letters, dashes, underscores, and numbers.')); } // Check for name collision - else if ($exists = searchlight_datasource_load($name, TRUE)) { + elseif ($exists = searchlight_datasource_load($name, TRUE)) { form_set_error('name', t('A datasource with this name already exists. Please choose another name or delete the existing datasource before creating a new one.')); } } @@ -201,7 +203,7 @@ function searchlight_admin_datasource_new_validate(&$form, &$form_state) { function searchlight_admin_datasource_new(&$form, &$form_state) { $datasource = searchlight_datasource_new($form_state['values']['name'], $form_state['values']['base_table']); if (searchlight_datasource_save($datasource)) { - drupal_set_message(t('Created datasource %name.', array('%name' => $datasource->name))); + drupal_set_message(t('Created datasource %name.', array('%name' => $datasource->name))); } else { drupal_set_message(t('Could not save datasource %name.', array('%name' => $datasource->name)), 'error'); @@ -214,8 +216,8 @@ function searchlight_admin_datasource_new(&$form, &$form_state) { function searchlight_admin_datasource_save(&$form, &$form_state) { foreach ($form_state['values']['active'] as $base_table => $datasource) { if (!empty($datasource)) { - $current = variable_get("searchlight_datasource_{$base_table}", "searchlight_{$base_table}"); - variable_set('searchlight_datasource_'. $base_table, $datasource); + $current = variable_get("searchlight_datasource_{$base_table}", "search_{$base_table}"); + variable_set('searchlight_datasource_' . $base_table, $datasource); drupal_set_message(t('New active datasource settings have been saved.'), 'status', FALSE); // If active datasource settings have changed a rebuild is necessary. @@ -234,20 +236,28 @@ function searchlight_admin_datasource_save(&$form, &$form_state) { /** * Edit form for datasource. */ -function searchlight_admin_datasource_edit($form_state, $datasource) { - $form = array(); +function searchlight_admin_datasource_edit($form, $form_state, $datasource) { $form['#datasource'] = $datasource; $form['#datasource_name'] = $datasource->name; + $form['#attached']['js']['misc/tableselect.js'] = array('type' => 'file'); + $datasource->init(); $datasource->optionsForm($form, $form_state); - $form['buttons']['save'] = array( + $form['actions']['save'] = array( '#type' => 'submit', '#value' => t('Save'), + '#limit_validation_errors' => array( + array('options'), + array('fields', 'fields'), + array('multivalues', 'fields'), + array('relations', 'relations'), + ), ); - $form['buttons']['cancel'] = array( - '#type' => 'markup', - '#value' => l(t('Cancel'), 'admin/settings/search/datastore'), + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => 'admin/config/search/settings/datastore', ); return $form; } @@ -255,45 +265,60 @@ function searchlight_admin_datasource_edit($form_state, $datasource) { /** * Submit handler for searchlight_admin_datasource_edit(). */ -function searchlight_admin_datasource_edit_submit(&$form, $form_state) { +function searchlight_admin_datasource_edit_submit($form, &$form_state) { if (!empty($form['#datasource'])) { $form['#datasource']->optionsSubmit($form, $form_state); } } /** - * AHAH callback & processor for datasource editor. + * AJAX callback & processor for datasource editor. */ -function searchlight_admin_datasource_ahah($clicked_id, $element = 'fields') { - $form_state = array(); +function searchlight_admin_datasource_ajax($form, $form_state, $clicked_id, $element = 'fields') { $form_state['clicked_button']['#id'] = $clicked_id; - $form_state['values'] = $_POST; - $form = form_get_cache($_POST['form_build_id'], $form_state); - if ($form) { - $datasource = searchlight_datasource_load($form['#datasource_name']); - $datasource->optionsSubmit($form, $form_state); - - $form['#datasource'] = $datasource; - $datasource->optionsForm($form, $form_state); - form_set_cache($_POST['form_build_id'], $form, $form_state); - - // Build and render the new element, then return it in JSON format. - $form_state = array(); - $form['#post'] = array(); - $form = form_builder($form['form_id']['#value'], $form, $form_state); - $output = drupal_render($form[$element]); - drupal_json(array('status' => TRUE, 'data' => $output)); - } - else { - drupal_json(array('status' => FALSE, 'data' => '')); - } - exit(); + + $datasource = searchlight_datasource_load($form['#datasource_name']); + $datasource->optionsSubmit($form, $form_state); + + $form['#datasource'] = $datasource; + $datasource->optionsForm($form, $form_state); + form_set_cache($_POST['form_build_id'], $form, $form_state); + + return $form[$element]; +} + +/** + * Form AJAX callback for adding fields. + */ +function searchlight_admin_datasource_ajax_fields_add($form, $form_state) { + return searchlight_admin_datasource_ajax($form, $form_state, 'edit-fields-new-add', 'fields'); +} + +/** + * Form AJAX callback for removing fields. + */ +function searchlight_admin_datasource_ajax_fields_remove($form, $form_state) { + return searchlight_admin_datasource_ajax($form, $form_state, 'edit-fields-remove', 'fields'); +} + +/** + * Form AJAX callback for adding MVA fields. + */ +function searchlight_admin_datasource_ajax_multivalues_add($form, $form_state) { + return searchlight_admin_datasource_ajax($form, $form_state, 'edit-multivalues-new-add', 'multivalues'); +} + +/** + * Form AJAX callback for removing MVA fields. + */ +function searchlight_admin_datasource_ajax_multivalues_remove($form, $form_state) { + return searchlight_admin_datasource_ajax($form, $form_state, 'edit-multivalues-remove', 'multivalues'); } /** * Confirmation form for datasource actions. */ -function searchlight_admin_confirm(&$form_state, $type, $object, $op) { +function searchlight_admin_confirm($form, &$form_state, $type, $object, $op) { switch ($type) { case 'searchlight_datasource': $type_name = t('datasource'); @@ -303,9 +328,18 @@ function searchlight_admin_confirm(&$form_state, $type, $object, $op) { break; } $form = array(); - $form['type'] = array('#type' => 'value', '#value' => $type); - $form['object'] = array('#type' => 'value', '#value' => $object); - $form['action'] = array('#type' => 'value', '#value' => $op); + $form['type'] = array( + '#type' => 'value', + '#value' => $type, + ); + $form['object'] = array( + '#type' => 'value', + '#value' => $object, + ); + $form['action'] = array( + '#type' => 'value', + '#value' => $op, + ); switch ($op) { case 'revert': $action = t('revert'); @@ -324,9 +358,10 @@ function searchlight_admin_confirm(&$form_state, $type, $object, $op) { $message = ''; break; } - $form = confirm_form($form, + $form = confirm_form( + $form, t('Are you sure you want to !action the @type %title?', array('%title' => $object->name, '@type' => $type_name, '!action' => $action)), - 'admin/settings/search', + 'admin/config/search/settings', $message, drupal_ucfirst($action), t('Cancel') ); @@ -346,15 +381,16 @@ function searchlight_admin_confirm_submit($form, &$form_state) { case 'searchlight_datasource': searchlight_datasource_delete($object); - // If reverting, display a message indicating that the index must be rebuilt. + // If reverting, display a message indicating that the index must be + // rebuilt. if ($form_state['values']['action'] === 'revert') { drupal_set_message(t('Datasource @datasource reverted. The index for this datasource needs to be rebuilt.', array('@datasource' => $object->name))); } - $form_state['redirect'] = 'admin/settings/search/datasource'; + $form_state['redirect'] = 'admin/config/search/settings/datasource'; break; case 'searchlight_environment': searchlight_environment_delete($object); - $form_state['redirect'] = 'admin/settings/search/environment'; + $form_state['redirect'] = 'admin/config/search/settings/environment'; break; } break; @@ -364,8 +400,8 @@ function searchlight_admin_confirm_submit($form, &$form_state) { /** * System settings form for Searchlight. */ -function searchlight_admin_backend($form_state) { - drupal_add_js(drupal_get_path('module', 'searchlight') .'/searchlight.admin.js'); +function searchlight_admin_backend($form, $form_state) { + drupal_add_js(drupal_get_path('module', 'searchlight') . '/searchlight.admin.js'); $form = array(); @@ -387,7 +423,7 @@ function searchlight_admin_backend($form_state) { '#type' => 'select', '#title' => t('Global search'), '#description' => t('Choose a View to power the global search block.'), - '#options' => array(FALSE => '<'. t("Don't replace global search") .'>') + searchlight_admin_global_search_views(), + '#options' => array(FALSE => '<' . t("Don't replace global search") . '>') + searchlight_admin_global_search_views(), '#default_value' => variable_get('searchlight_global_search', FALSE), ); @@ -400,10 +436,10 @@ function searchlight_admin_backend($form_state) { ); $form['backend']['searchlight_backend'] = array( '#type' => 'select', - '#options' => array(0 => '< '. t('Choose a backend') .' >'), + '#options' => array(0 => '< ' . t('Choose a backend') . ' >'), '#default_value' => variable_get('searchlight_backend', 'sphinx'), '#attributes' => array( - 'class' => 'searchlight-backend-select', + 'class' => array('searchlight-backend-select'), ), ); foreach (searchlight_registry('backend', TRUE) as $key => $info) { @@ -414,7 +450,7 @@ function searchlight_admin_backend($form_state) { $form["searchlight_backend_{$key}"]['#title'] = $info['title']; $form["searchlight_backend_{$key}"]['#type'] = 'fieldset'; $form["searchlight_backend_{$key}"]['#attributes'] = array( - 'class' => "searchlight-backend-settings searchlight-backend-{$key}", + 'class' => array("searchlight-backend-settings", "searchlight-backend-{$key}"), ); } $form = system_settings_form($form); diff --git a/searchlight.admin.js b/searchlight.admin.js index f740a8b..497156f 100644 --- a/searchlight.admin.js +++ b/searchlight.admin.js @@ -1,30 +1,34 @@ // $Id$ -Drupal.behaviors.searchlight = function(context) { - $('.searchlight-backend-select:not(.searchlight-processed)', context).each(function() { - $(this).change(function() { - var value = $(this).val(); - $('.searchlight-backend-settings').hide(); - $('.searchlight-backend-' + value).show(); - }); - $(this).change(); - }).addClass('searchlight-processed'); - $('.searchlight-admin-environment .environment-settings:not(.searchlight-processed)', context).each(function() { - $('a.environment-settings-link', this).click(function() { - if ($(this).is('.settings-active')) { - $('.searchlight-admin-environment .environment-settings-form').hide(); - $('a.environment-settings-link').removeClass('settings-active'); - } - else { - // Hide & show per-facet settings forms. - $('.searchlight-admin-environment .environment-settings-form').hide(); - var target = $(this).attr('href').split('#')[1]; - $('#' + target).show(); +(function ($) { + Drupal.behaviors.searchlight = { + attach: function(context, settiongs) { + $('.searchlight-backend-select:not(.searchlight-processed)', context).each(function() { + $(this).change(function() { + var value = $(this).val(); + $('.searchlight-backend-settings').hide(); + $('.searchlight-backend-' + value).show(); + }); + $(this).change(); + }).addClass('searchlight-processed'); + $('.searchlight-admin-environment .environment-settings:not(.searchlight-processed)', context).each(function() { + $('a.environment-settings-link', this).click(function() { + if ($(this).is('.settings-active')) { + $('.searchlight-admin-environment .environment-settings-form').hide(); + $('a.environment-settings-link').removeClass('settings-active'); + } + else { + // Hide & show per-facet settings forms. + $('.searchlight-admin-environment .environment-settings-form').hide(); + var target = $(this).attr('href').split('#')[1]; + $('#' + target).show(); - // Set link classes. - $('a.environment-settings-link').removeClass('settings-active'); - $(this).addClass('settings-active'); - } - }); - $(this).change(); - }).addClass('searchlight-processed'); -}; + // Set link classes. + $('a.environment-settings-link').removeClass('settings-active'); + $(this).addClass('settings-active'); + } + }); + $(this).change(); + }).addClass('searchlight-processed'); + } + } +})(jQuery); diff --git a/searchlight.drush.inc b/searchlight.drush.inc index 17c97a5..5af11e4 100644 --- a/searchlight.drush.inc +++ b/searchlight.drush.inc @@ -2,7 +2,7 @@ // $Id$ /** - * Implementation of hook_drush_command(). + * Implements hook_drush_command(). */ function searchlight_drush_command() { $items = array(); diff --git a/searchlight.info b/searchlight.info index 5a324c9..3ca6880 100644 --- a/searchlight.info +++ b/searchlight.info @@ -1,6 +1,46 @@ -core = "6.x" +core = 7.x dependencies[] = "ctools" dependencies[] = "views" description = "Views-driven search API with pluggable search backends." name = "Searchlight" package = "Search" + + +files[] = searchlight.admin.inc +files[] = searchlight.drush.inc +files[] = searchlight.install +files[] = searchlight.module +files[] = searchlight_provision.drush.inc +files[] = includes/SearchlightDatasource.inc +files[] = includes/SearchlightEnvironment.inc +files[] = libraries/sphinxapi.php +files[] = Solr/Document.php +files[] = Solr/Response.php +files[] = Solr/Service.php +files[] = Service/Balancer.php +files[] = plugins/SearchlightBackend.inc +files[] = plugins/SearchlightBackendSolr.inc +files[] = plugins/SearchlightBackendSphinx.inc +files[] = plugins/SearchlightFacet.inc +files[] = plugins/SearchlightFacetDatatypeTimestamp.inc +files[] = plugins/SearchlightFacetLanguage.inc +files[] = plugins/SearchlightFacetSearchQuery.inc +files[] = plugins/SearchlightFacetTerm.inc +files[] = theme/searchlight-admin-datasource.tpl.php +files[] = theme/searchlight-admin-environment.tpl.php +files[] = theme/searchlight-solr-config.tpl.php +files[] = theme/searchlight-solr-cores.tpl.php +files[] = theme/searchlight-solr-schema.tpl.php +files[] = theme/searchlight-sphinx-conf.tpl.php +files[] = theme/searchlight-sphinx-index-conf.tpl.php +files[] = theme/searchlight.theme.inc +files[] = views/searchlight.views.inc +files[] = views/searchlight_handler_argument_search.inc +files[] = views/searchlight_handler_field_facet_link.inc +files[] = views/searchlight_handler_field_node_access.inc +files[] = views/searchlight_handler_filter_facets.inc +files[] = views/searchlight_handler_filter_search.inc +files[] = views/searchlight_handler_sort_search.inc +files[] = views/searchlight_plugin_display_multivalue.inc +files[] = views/searchlight_plugin_display_solr.inc +files[] = views/searchlight_plugin_query_v3.inc diff --git a/searchlight.install b/searchlight.install index 7ece968..bd9f9e3 100644 --- a/searchlight.install +++ b/searchlight.install @@ -1,12 +1,18 @@ t('Storage for Searchlight datasource configuration.'), + 'description' => 'Storage for Searchlight datasource configuration.', 'export' => array( 'key' => 'name', 'object' => 'SearchlightDatasource', @@ -39,6 +45,11 @@ function searchlight_schema() { 'type' => 'text', 'serialize' => TRUE, ), + 'relations' => array( + 'description' => 'Serialized storage of datasource relations settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), 'filters' => array( 'description' => 'Serialized storage of datasource filter settings.', 'type' => 'text', @@ -53,7 +64,7 @@ function searchlight_schema() { 'primary key' => array('name'), ); $schema['searchlight_environment'] = array( - 'description' => t('Storage for Searchlight environment configuration.'), + 'description' => 'Storage for Searchlight environment configuration.', 'export' => array( 'key' => 'name', 'object' => 'SearchlightEnvironment', @@ -95,55 +106,61 @@ function searchlight_schema() { 'primary key' => array('name'), ); $schema['searchlight_search'] = array( - 'description' => t('Stores a record of an items status in the index.'), + 'description' => 'Stores a record of an items status in the index.', 'fields' => array( 'type' => array( - 'description' => t('The type of item.'), + 'description' => 'The type of item.', 'type' => 'varchar', 'length' => '128', - 'not null' => TRUE + 'not null' => TRUE, ), 'id' => array( - 'description' => t('The primary identifier for an item.'), + 'description' => 'The primary identifier for an item.', 'type' => 'int', 'unsigned' => TRUE, - 'not null' => TRUE + 'not null' => TRUE, ), 'status' => array( - 'description' => t('Boolean indicating whether the item should be available in the index.'), + 'description' => 'Boolean indicating whether the item should be available in the index.', 'type' => 'int', 'not null' => TRUE, - 'default' => 0 + 'default' => 0, ), ), 'indexes' => array( 'status' => array('status'), ), - 'primary key' => array('type, id'), + 'primary key' => array('type', 'id'), ); return $schema; } /** - * Implementation of hook_enable(). + * Implements hook_enable(). */ function searchlight_enable() { searchlight_invalidate_index(); } /** - * Implementation of hook_install(). + * Implements hook_install(). */ function searchlight_install() { - drupal_install_schema('searchlight'); + // TODO The drupal_(un)install_schema functions are called automatically in D7. + // drupal_install_schema('searchlight') } /** - * Implementation of hook_uninstall(). + * Implements hook_uninstall(). */ function searchlight_uninstall() { variable_del('searchlight_views'); variable_del('searchlight_backend'); - db_query("DELETE FROM {variable} WHERE name LIKE 'searchlight_backend_%'"); - drupal_uninstall_schema('searchlight'); + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("DELETE FROM {variable} WHERE name LIKE 'searchlight_backend_%'") */ + db_delete('variable') + ->condition('name', 'searchlight_backend_%', 'LIKE') + ->execute(); + // TODO The drupal_(un)install_schema functions are called automatically in D7. + // drupal_uninstall_schema('searchlight') } diff --git a/searchlight.js b/searchlight.js index 4952975..38c3f82 100644 --- a/searchlight.js +++ b/searchlight.js @@ -1,72 +1,8 @@ (function($) { - $.fn.drupalSearchlight = function(method, params) { - switch (method) { - case 'ajaxViewLink': - var settings = params.settings; - var view = params.view; - // If there are multiple views this might've ended up showing up multiple times. - var ajax_path = Drupal.settings.views.ajax_path; - if (ajax_path.constructor.toString().indexOf("Array") != -1) { - ajax_path = ajax_path[0]; - } - - $(this).click(function() { - var link = $(this); - var viewData = { 'js': 1 }; - - // Construct an object using the settings defaults and then overriding - // with data specific to the link. - $.extend( - viewData, - Drupal.Views.parseQueryString(link.attr('href')), - // Extract argument data from the URL. - Drupal.Views.parseViewArgs(link.attr('href'), settings.view_base_path), - // Settings must be used last to avoid sending url aliases to the server. - settings - ); - - // Show throbber. - link.addClass('views-throbbing'); - - // AJAX request. - $.ajax({ - url: ajax_path, - type: 'GET', - data: viewData, - success: function(response) { - link.removeClass('views-throbbing'); - - // Call all callbacks. - if (response.__callbacks) { - $.each(response.__callbacks, function(i, callback) { eval(callback)(view, response); }); - } - }, - error: function(xhr) { link.removeClass('views-throbbing'); Drupal.Views.Ajax.handleErrors(xhr, ajax_path); }, - dataType: 'json' - }); - - // Don't follow through link. - return false; - }); - break; - } - return this; - }; - $.fn.drupalSearchlight.replace = function(selector, data) { - if (data.searchlightData) { - for (var target in data.searchlightData) { - if ($(target).size()) { - $(target).replaceWith(data.searchlightData[target]); - Drupal.attachBehaviors($(target)); - } - } - } - }; -})(jQuery); - -Drupal.behaviors.searchlight = function(context) { - if (Drupal.settings.views && Drupal.settings.views.ajaxViews) { +Drupal.behaviors.searchlight = {}; +Drupal.behaviors.searchlight.attach = function(context, settings) { + if (Drupal.settings && Drupal.settings.views && Drupal.settings.views.ajaxViews) { $('.searchlight-environment:not(.searchlightProcessed)').each(function() { // Retrieve current page view's settings and DOM element. var settings = {}; @@ -74,8 +10,11 @@ Drupal.behaviors.searchlight = function(context) { var identifier = $(this).attr('class').split('searchlight-view-')[1].split('-'); for (var i in Drupal.settings.views.ajaxViews) { if ( - Drupal.settings.views.ajaxViews[i].view_name == identifier[0] && - Drupal.settings.views.ajaxViews[i].view_display_id == identifier[1] + Drupal.settings.views.ajaxViews[i].view_name == identifier[0] + // @TODO: Do a loose (view-name only) check for attaching AJAX + // view handlers to allow environments to be used with multiple + // displays in the same view. + // && Drupal.settings.views.ajaxViews[i].view_display_id == identifier[1] ) { settings = Drupal.settings.views.ajaxViews[i]; view = $('.view-dom-id-' + settings.view_dom_id); @@ -86,3 +25,61 @@ Drupal.behaviors.searchlight = function(context) { }).addClass('searchlightProcessed'); } }; + +$.fn.drupalSearchlight = function(method, params) { + switch (method) { + case 'ajaxViewLink': + var settings = params.settings; + var view = params.view; + + // If there are multiple views this might've ended up showing up multiple times. + var ajax_path = Drupal.settings.views.ajax_path; + if (ajax_path.constructor.toString().indexOf("Array") != -1) { + ajax_path = ajax_path[0]; + } + + $(this).each(function() { + var viewData = {}; + var link = $(this); + + // Construct an object using the settings defaults and then overriding + // with data specific to the link. + $.extend( + viewData, + Drupal.Views.parseQueryString(link.attr('href')), + // Extract argument data from the URL. + Drupal.Views.parseViewArgs(link.attr('href'), settings.view_base_path), + // Settings must be used last to avoid sending url aliases to the server. + settings + ); + + var ajax = new Drupal.ajax(false, link, { + url: ajax_path, + submit: viewData, + event: 'click', + selector: view + }); + + // Override HTTP method type and beforeSerialize method. + // See note above. + ajax.options.type = 'GET'; + ajax.beforeSerialize = function(element_settings, options) { return; }; + }); + break; + } + return this; +}; + +$.fn.drupalSearchlight.replace = function(selector, data) { + if (data.searchlightData) { + for (var target in data.searchlightData) { + if ($(target).size()) { + $(target).replaceWith(data.searchlightData[target]); + Drupal.attachBehaviors($(target)); + } + } + } +}; + +})(jQuery); + diff --git a/searchlight.module b/searchlight.module index 63d8455..206ab05 100644 --- a/searchlight.module +++ b/searchlight.module @@ -1,10 +1,10 @@ 'Datasource', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_datasource'), @@ -29,17 +29,17 @@ function searchlight_menu() { 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); - $items['admin/settings/search/datasource/list/%searchlight_datasource'] = array( + $items['admin/config/search/settings/datasource/%searchlight_datasource'] = array( 'title' => 'Datasource', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_datasource_edit', 5), 'access callback' => 'user_access', 'access arguments' => array('administer site configuration'), 'file' => 'searchlight.admin.inc', - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, 'weight' => -10, ); - $items['admin/settings/search/datasource/list/%searchlight_datasource/edit'] = array( + $items['admin/config/search/settings/datasource/%searchlight_datasource/edit'] = array( 'title' => 'Edit', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_datasource_edit', 5), @@ -49,7 +49,7 @@ function searchlight_menu() { 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); - $items['admin/settings/search/datasource/list/%searchlight_datasource/delete'] = array( + $items['admin/config/search/settings/datasource/%searchlight_datasource/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_confirm', 'searchlight_datasource', 5, 'revert'), @@ -58,7 +58,7 @@ function searchlight_menu() { 'file' => 'searchlight.admin.inc', 'type' => MENU_LOCAL_TASK, ); - $items['admin/settings/search/datasource/list/%searchlight_datasource/revert'] = array( + $items['admin/config/search/settings/datasource/%searchlight_datasource/revert'] = array( 'title' => 'Revert', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_confirm', 'searchlight_datasource', 5, 'revert'), @@ -67,16 +67,7 @@ function searchlight_menu() { 'file' => 'searchlight.admin.inc', 'type' => MENU_LOCAL_TASK, ); - $items['admin/settings/search/datasource/ahah/%/%'] = array( - 'page callback' => 'searchlight_admin_datasource_ahah', - 'page arguments' => array(5, 6), - 'access callback' => 'user_access', - 'access arguments' => array('administer site configuration'), - 'file' => 'searchlight.admin.inc', - 'type' => MENU_CALLBACK, - ); - - $items['admin/settings/search/environment'] = array( + $items['admin/config/search/settings/environment'] = array( 'title' => 'Environment', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_environment'), @@ -86,17 +77,17 @@ function searchlight_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => -5, ); - $items['admin/settings/search/environment/list/%searchlight_environment'] = array( + $items['admin/config/search/settings/environment/%searchlight_environment'] = array( 'title' => 'Environment', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_environment_edit', 5), 'access callback' => 'user_access', 'access arguments' => array('administer site configuration'), 'file' => 'searchlight.admin.inc', - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, 'weight' => -10, ); - $items['admin/settings/search/environment/list/%searchlight_environment/edit'] = array( + $items['admin/config/search/settings/environment/%searchlight_environment/edit'] = array( 'title' => 'Edit', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_environment_edit', 5), @@ -106,7 +97,7 @@ function searchlight_menu() { 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); - $items['admin/settings/search/environment/list/%searchlight_environment/delete'] = array( + $items['admin/config/search/settings/environment/%searchlight_environment/delete'] = array( 'title' => 'Delete', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_confirm', 'searchlight_environment', 5, 'revert'), @@ -115,7 +106,7 @@ function searchlight_menu() { 'file' => 'searchlight.admin.inc', 'type' => MENU_LOCAL_TASK, ); - $items['admin/settings/search/environment/list/%searchlight_environment/revert'] = array( + $items['admin/config/search/settings/environment/%searchlight_environment/revert'] = array( 'title' => 'Revert', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_confirm', 'searchlight_environment', 5, 'revert'), @@ -125,7 +116,7 @@ function searchlight_menu() { 'type' => MENU_LOCAL_TASK, ); - $items['admin/settings/search/backend'] = array( + $items['admin/config/search/settings/backend'] = array( 'title' => 'Backend', 'page callback' => 'drupal_get_form', 'page arguments' => array('searchlight_admin_backend'), @@ -138,27 +129,34 @@ function searchlight_menu() { } /** - * Implementation of hook_menu_alter(). + * Implements hook_menu_alter(). */ function searchlight_menu_alter(&$items) { - $items['admin/settings/search'] = $items['admin/settings/search/datasource']; - $items['admin/settings/search']['title'] = 'Search'; - $items['admin/settings/search']['type'] = MENU_NORMAL_ITEM; - unset($items['admin/settings/search']['weight']); + $items['admin/config/search/settings'] = $items['admin/config/search/settings/datasource']; + $items['admin/config/search/settings']['title'] = 'Search'; + $items['admin/config/search/settings']['description'] = 'Configure relevance settings for search and other indexing options.'; + $items['admin/config/search/settings']['type'] = MENU_NORMAL_ITEM; + unset($items['admin/config/search/settings']['weight']); } /** - * Implementation of hook_block(). + * Implements hook_block_info(). */ -function searchlight_block($op = 'list', $delta = 0, $edit = array()) { - if ($op === 'list') { +function searchlight_block_info() { + if (TRUE) { $list = array(); foreach (searchlight_environment_load() as $environment) { - $list['facets_'. $environment->name] = array('info' => t('@name: Search facets', array('@name' => $environment->name))); + $list['facets_' . $environment->name] = array('info' => t('@name: Search facets', array('@name' => $environment->name))); } return $list; } - else if ($op === 'view' && strpos($delta, '_') !== FALSE) { +} + +/** + * Implements hook_block_view(). + */ +function searchlight_block_view($delta) { + if (TRUE && strpos($delta, '_') !== FALSE) { list($delta, $name) = explode('_', $delta, 2); if (in_array($delta, array('facets'), TRUE) && $environment = searchlight_environment_load($name)) { return $environment->getBlock($delta); @@ -167,50 +165,92 @@ function searchlight_block($op = 'list', $delta = 0, $edit = array()) { } /** - * Implementation of hook_nodeapi(). + * Implements hook_node_insert(). */ -function searchlight_nodeapi($node, $op) { - switch ($op) { - case 'insert': - db_query("INSERT INTO {searchlight_search} (type, id, status) VALUES ('node', %d, %d)", $node->nid, 0); - break; - case 'update': - db_query("UPDATE {searchlight_search} SET status = 0 WHERE type='node' and id=%d", $node->nid); - break; - case 'delete': - db_query("UPDATE {searchlight_search} SET status = -1 WHERE type='node' and id=%d", $node->nid); - break; - } +function searchlight_node_insert($node) { + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("INSERT INTO {searchlight_search} (type, id, status) VALUES ('node', %d, %d)", $node->nid, 0) */ + $id = db_insert('searchlight_search') + ->fields(array( + 'type' => 'node', + 'id' => $node->nid, + 'status' => 0, + )) + ->execute(); } /** - * Implementation of hook_comment(). + * Implements hook_node_update(). */ -function searchlight_comment(&$a1, $op) { - $update_node = FALSE; - switch ($op) { - case 'insert': - db_query("INSERT INTO {searchlight_search} (type, id, status) VALUES ('comments', %d, %d)", $a1['cid'], 0); - $update_node = TRUE; - break; - case 'update': - db_query("UPDATE {searchlight_search} SET status = 0 WHERE type = 'comments' AND id = %d", $a1['cid']); - $update_node = TRUE; - break; - case 'delete': - db_query("UPDATE {searchlight_search} SET status = -1 WHERE type = 'comments' and id = %d", $a1['cid']); - $update_node = TRUE; - break; - } - if ($update_node) { - // Mark the parent node as needing an update. Changes in node comments - // can affect the node as well (e.g. node_comment_statistics). - db_query("UPDATE {searchlight_search} SET status = 0 WHERE type='node' and id=%d", $a1['nid']); - } +function searchlight_node_update($node) { + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("UPDATE {searchlight_search} SET status = 0 WHERE type='node' and id=%d", $node->nid) */ + db_update('searchlight_search') + ->fields(array( 'status' => 0,)) + ->condition('type', 'node') + ->condition('id', $node->nid) + ->execute(); } /** - * Implementation of hook_theme(). + * Implements hook_node_delete(). + */ +function searchlight_node_delete($node) { + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("UPDATE {searchlight_search} SET status = -1 WHERE type='node' and id=%d", $node->nid) */ + db_update('searchlight_search') + ->fields(array( 'status' => -1,)) + ->condition('type', 'node') + ->condition('id', $node->nid) + ->execute(); +} + +/** + * Implements hook_comment_insert(). + */ +function searchlight_comment_insert($comment) { + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("INSERT INTO {searchlight_search} (type, id, status) VALUES ('comments', %d, %d)", $a1['cid'], 0) */ + $id = db_insert('searchlight_search') + ->fields(array( + 'type' => 'comments', + 'id' => $comment->cid, + 'status' => 0, + )) + ->execute(); + $update_node = TRUE; +} + +/** + * Implements hook_comment_update(). + */ +function searchlight_comment_update($comment) { + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("UPDATE {searchlight_search} SET status = 0 WHERE type = 'comments' AND id = %d", $a1['cid']) */ + db_update('searchlight_search') + ->fields(array('status' => 0,)) + ->condition('type', 'comments') + ->condition('id', $comment->cid) + ->execute(); + $update_node = TRUE; +} + +/** + * Implements hook_comment_delete(). + */ +function searchlight_comment_delete($comment) { + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("UPDATE {searchlight_search} SET status = -1 WHERE type = 'comments' and id = %d", $a1['cid']) */ + db_update('searchlight_search') + ->fields(array('status' => -1, )) + ->condition('type', 'comments') + ->condition('id', $comment->cid) + ->execute(); + $update_node = TRUE; +} + +/** + * Implements hook_theme(). */ function searchlight_theme() { return array( @@ -220,78 +260,87 @@ function searchlight_theme() { ), 'searchlight_sphinx_conf' => array( 'template' => 'searchlight-sphinx-conf', - 'arguments' => array('searchd' => array()), + 'variables' => array('searchd' => array()), 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_sphinx_index_conf' => array( 'template' => 'searchlight-sphinx-index-conf', - 'arguments' => array('datasource' => array()), + 'variables' => array('datasource' => array()), 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_solr_schema' => array( 'template' => 'searchlight-solr-schema', - 'arguments' => array('datasource' => array()), + 'variables' => array('datasource' => array()), 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_solr_config' => array( 'template' => 'searchlight-solr-config', - //'arguments' => array('datasource' => array()), 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_solr_cores' => array( 'template' => 'searchlight-solr-cores', - 'arguments' => array('cores' => array()), + 'variables' => array('cores' => array()), 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_plugin_display_datasource' => array( - 'arguments' => array('form' => array()), + 'render element' => 'form', 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_facet' => array( 'path' => drupal_get_path('module', 'searchlight') . '/theme', + 'variables' => array('facet' => array()), 'file' => 'searchlight.theme.inc', ), 'searchlight_facet_link' => array( 'path' => drupal_get_path('module', 'searchlight') . '/theme', + 'variables' => array('field' => NULL, 'item' => array()), 'file' => 'searchlight.theme.inc', ), 'searchlight_facet_active' => array( 'path' => drupal_get_path('module', 'searchlight') . '/theme', + 'variables' => array('field' => NULL, 'item' => array()), 'file' => 'searchlight.theme.inc', ), 'searchlight_admin_list' => array( 'path' => drupal_get_path('module', 'searchlight') . '/theme', + 'render element' => 'form', 'file' => 'searchlight.theme.inc', ), 'searchlight_admin_datasource' => array( 'template' => 'searchlight-admin-datasource', - 'arguments' => array('form' => array()), + 'render element' => 'form', 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_admin_datasource_fields' => array( - 'arguments' => array('form' => array()), + 'render element' => 'form', + 'path' => drupal_get_path('module', 'searchlight') . '/theme', + 'file' => 'searchlight.theme.inc', + ), + 'searchlight_admin_datasource_relations' => array( + 'render element' => 'form', 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), 'searchlight_admin_environment' => array( 'template' => 'searchlight-admin-environment', - 'arguments' => array('form' => array()), + 'render element' => 'form', 'path' => drupal_get_path('module', 'searchlight') . '/theme', 'file' => 'searchlight.theme.inc', ), ); } + + /** * Preprocessor for theme('page'). - */ function searchlight_preprocess_page(&$vars) { if ($global_search = variable_get('searchlight_global_search', FALSE)) { list($path, $identifier) = explode(':', $global_search); @@ -303,10 +352,13 @@ function searchlight_preprocess_page(&$vars) { 'no_redirect' => TRUE, 'input' => array(), ); - $vars['search_box'] = drupal_build_form('searchlight_search_form', $form_state); + // $vars['search_box'] = drupal_build_form('searchlight_search_form', $form_state); } + } + */ + /** * Form builder for Searchlight GET form. Should be used with * drupal_build_form(), the function used in Views for exposed filter forms. @@ -317,7 +369,10 @@ function searchlight_search_form(&$form_state) { $form['#id'] = 'searchlight-search-form'; if (!variable_get('clean_url', FALSE)) { - $form['q'] = array('#type' => 'hidden', '#value' => $form_state['path']); + $form['q'] = array( + '#type' => 'hidden', + '#value' => $form_state['path'], + ); } $form[$form_state['identifier']] = array( @@ -329,23 +384,23 @@ function searchlight_search_form(&$form_state) { '#name' => '', // prevent from showing up in $_GET. '#type' => 'submit', '#value' => t('Search'), - '#id' => form_clean_id('edit-submit-searchlight-search-form'), + '#id' => drupal_clean_css_identifier('edit-submit-searchlight-search-form'), ); return $form; } /** - * Implementation of hook_views_api(). + * Implements hook_views_api(). */ function searchlight_views_api() { return array( - 'api' => 2, + 'api' => '3.0-alpha1', 'path' => drupal_get_path('module', 'searchlight') . '/views', ); } /** - * Implementation of hook_ctools_plugin_api(). + * Implements hook_ctools_plugin_api(). */ function searchlight_ctools_plugin_api($module = NULL, $api = NULL) { if ($module === 'searchlight') { @@ -361,31 +416,35 @@ function searchlight_ctools_plugin_api($module = NULL, $api = NULL) { } /** - * Implementation of hook_ctools_plugin_plugins(). + * Implements hook_ctools_plugin_plugins(). */ -function searchlight_ctools_plugin_plugins() { +function searchlight_ctools_plugin_type() { return array( - 'cache' => TRUE, - 'use hooks' => TRUE, + 'plugins' => array( + 'cache' => TRUE, + 'use hooks' => TRUE, + 'classes' => array('handler'), + ), ); } + /** - * Implementation of hook_searchlight_plugins(). + * Implements hook_searchlight_plugins(). * This is a CTools plugin API hook. */ function searchlight_searchlight_plugins() { return array( 'SearchlightBackend' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightBackend.inc', 'class' => 'SearchlightBackend', ), ), 'SearchlightBackendSphinx' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightBackendSphinx.inc', 'class' => 'SearchlightBackendSphinx', 'parent' => 'SearchlightBackend', @@ -393,7 +452,7 @@ function searchlight_searchlight_plugins() { ), 'SearchlightBackendSolr' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightBackendSolr.inc', 'class' => 'SearchlightBackendSolr', 'parent' => 'SearchlightBackend', @@ -401,14 +460,14 @@ function searchlight_searchlight_plugins() { ), 'SearchlightFacet' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightFacet.inc', 'class' => 'SearchlightFacet', ), ), 'SearchlightFacetSearchQuery' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightFacetSearchQuery.inc', 'class' => 'SearchlightFacetSearchQuery', 'parent' => 'SearchlightFacet', @@ -416,7 +475,7 @@ function searchlight_searchlight_plugins() { ), 'SearchlightFacetTerm' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightFacetTerm.inc', 'class' => 'SearchlightFacetTerm', 'parent' => 'SearchlightFacet', @@ -424,7 +483,7 @@ function searchlight_searchlight_plugins() { ), 'SearchlightFacetDatatypeTimestamp' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightFacetDatatypeTimestamp.inc', 'class' => 'SearchlightFacetDatatypeTimestamp', 'parent' => 'SearchlightFacet', @@ -432,7 +491,7 @@ function searchlight_searchlight_plugins() { ), 'SearchlightFacetLanguage' => array( 'handler' => array( - 'path' => drupal_get_path('module', 'searchlight') .'/plugins', + 'path' => drupal_get_path('module', 'searchlight') . '/plugins', 'file' => 'SearchlightFacetLanguage.inc', 'class' => 'SearchlightFacetLanguage', 'parent' => 'SearchlightFacet', @@ -442,7 +501,7 @@ function searchlight_searchlight_plugins() { } /** - * Implementation of hook_searchlight_registry(). + * Implements hook_searchlight_registry(). */ function searchlight_searchlight_registry() { return array( @@ -465,7 +524,7 @@ function searchlight_searchlight_registry() { 'title' => t('Search query'), 'plugin' => 'SearchlightFacetSearchQuery', ), - 'term_data_tid' => array( + 'taxonomy_index_tid' => array( 'title' => t('Taxonomy term'), 'plugin' => 'SearchlightFacetTerm', ), @@ -482,45 +541,33 @@ function searchlight_searchlight_registry() { } /** - * Implementation of hook_form_alter() for 'views_exposed_form'. + * Implements hook_form_alter() for 'views_exposed_form'(). * Ensure that Searchlight facet components are preserved when Views exposed * filters are submitted. */ function searchlight_form_views_exposed_form_alter(&$form, &$form_state) { $key = variable_get('searchlight_facet_key', 'sl'); if (!isset($form[$key]) && isset($_GET[$key])) { - $form[$key] = array('#type' => 'hidden', '#value' => $_GET[$key]); + $form[$key] = array( + '#type' => 'hidden', + '#value' => $_GET[$key], + ); } } /** - * Implementation of hook_ajax_data_alter(). - * - * This hook is called by Views (and some other modules) when returning AJAX - * data to the client. It provides an event for other modules to hook into to - * alter the AJAX data returned and register additional *javascript* callbacks - * to be used once the AJAX response is received. - */ -function searchlight_ajax_data_alter(&$object, $type, $metadata) { - if ($type === 'views') { - $view = $metadata; - // If this is a searchlight view with AJAX, add a callback for updating any - // associated facet blocks. - if (isset($view->query) && !empty($view->query->searchlight)) { - $environment = searchlight_environment_active(NULL, $view->name, $view->current_display); - if ($environment) { - $block = $environment->getBlock('facets'); - $object->searchlightData = array(".searchlight-environment-{$environment->name}" => $block['content']); - - // Add searchlight replace callback. - $object->__callbacks[] = '$().drupalSearchlight.replace'; - } - } + * Implements hook_ajax_render_alter(). + */ +function searchlight_ajax_render_alter(&$commands) { + $environment = searchlight_environment_active(); + if ($environment) { + $block = $environment->getBlock('facets'); + $commands[] = ajax_command_replace(".searchlight-environment-{$environment->name}", $block['content']); } } /** - * Implementation of hook_searchlight_node_access_realms_alter(). + * Implements hook_searchlight_node_access_realms_alter(). * Provide realms for commonly used node_access modules. */ function searchlight_searchlight_node_access_realms_alter(&$realms) { @@ -622,7 +669,7 @@ function searchlight_get_datasource($base_table = NULL) { views_include('admin'); $active_datasources = array(); foreach (array_keys(views_fetch_base_tables()) as $base_table) { - $name = variable_get("searchlight_datasource_{$base_table}", "searchlight_{$base_table}"); + $name = variable_get("searchlight_datasource_{$base_table}", "search_{$base_table}"); if (!empty($name) && $datasource = searchlight_datasource_load($name)) { $active_datasources[$name] = $datasource; } @@ -630,7 +677,7 @@ function searchlight_get_datasource($base_table = NULL) { return $active_datasources; } else { - $name = variable_get("searchlight_datasource_{$base_table}", "searchlight_{$base_table}"); + $name = variable_get("searchlight_datasource_{$base_table}", "search_{$base_table}"); if (!empty($name) && $datasource = searchlight_datasource_load($name)) { return $datasource; } @@ -641,11 +688,11 @@ function searchlight_get_datasource($base_table = NULL) { /** * Retrieve a new inited Searchlight query. */ -function searchlight_get_query($base_table, $base_field, $backend = NULL) { +function searchlight_get_query($base_table, $base_field, $options = array(), $backend = NULL) { $backend = !isset($backend) ? searchlight_get_backend() : $backend; if ($backend) { $query = views_get_plugin('query', 'searchlight'); - $query->init($base_table, $base_field, $backend); + $query->init($base_table, $base_field, $options, $backend); return $query; } return FALSE; @@ -754,7 +801,11 @@ function searchlight_datasource_save($datasource, $editing = FALSE) { */ function searchlight_datasource_delete($datasource) { if (isset($datasource->name) && ($datasource->export_type & EXPORT_IN_DATABASE)) { - db_query("DELETE FROM {searchlight_datasource} WHERE name = '%s'", $datasource->name); + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("DELETE FROM {searchlight_datasource} WHERE name = '%s'", $datasource->name) */ + db_delete('searchlight_datasource') + ->condition('name', $datasource->name) + ->execute(); searchlight_invalidate_cache(); return TRUE; } @@ -767,7 +818,17 @@ function searchlight_datasource_delete($datasource) { function searchlight_environment_pack($value) { $keyvals = array(); foreach ($value as $k => $v) { - $keyvals[] = "{$k}-{$v}"; + if (is_array($v)) { + $mv_string = ''; + foreach ($v as $mvkey => $mv) { + $mv_string .= "{$mv}|"; + } + $mv_string = substr($mv_string,0,-1); + $keyvals[] = "{$k}-{$mv_string}"; + } + else { + $keyvals[] = "{$k}-{$v}"; + } } return urlencode(implode(',', $keyvals)); } @@ -782,7 +843,13 @@ function searchlight_environment_unpack($value) { foreach ($split as $chunk) { $keyval = explode('-', $chunk, 2); if (count($keyval) === 2) { - $parsed[$keyval[0]] = $keyval[1]; + $multivalues = explode('|', $keyval[1]); + if (count($multivalues) > 1) { + $parsed[$keyval[0]] = $multivalues; + } + else { + $parsed[$keyval[0]] = $keyval[1]; + } } } return $parsed; @@ -829,13 +896,18 @@ function searchlight_environment_active($environment = NULL, $view = NULL, $disp } // Match both view & display. Note that a lazy matched environment is not // static cached as multiple may be active on the same page. + // EXCEPTION: In the context of an views AJAX response do static cache the + // active environment to allow the AJAX payload to be altered. + // See searchlight_ajax_render_alter(). else if (isset($view, $display)) { foreach (searchlight_environment_load() as $environment) { list($environment_view, $environment_display) = explode(':', $environment->view_display); if ($environment->view_display === "{$view}:{$display}") { + $active = isset($_REQUEST['view_name'], $_REQUEST['view_display_id']) ? $environment : $active; return $environment; } else if ($environment_view === $view) { + $active = isset($_REQUEST['view_name'], $_REQUEST['view_display_id']) ? $environment : $active; return $environment; } } @@ -917,7 +989,11 @@ function searchlight_environment_save($environment) { */ function searchlight_environment_delete($environment) { if (isset($environment->name) && ($environment->export_type & EXPORT_IN_DATABASE)) { - db_query("DELETE FROM {searchlight_environment} WHERE name = '%s'", $environment->name); + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("DELETE FROM {searchlight_environment} WHERE name = '%s'", $environment->name) */ + db_delete('searchlight_environment') + ->condition('name', $environment->name) + ->execute(); searchlight_invalidate_cache(); return TRUE; } @@ -970,10 +1046,15 @@ function searchlight_invalidate_index($base_table = NULL) { // Let the backend respond to a rebuild index command. $backend->invalidateIndex($datasource); - db_query("DELETE FROM {searchlight_search} WHERE type = '%s'", $datasource->base_table); + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("DELETE FROM {searchlight_search} WHERE type = '%s'", $datasource->base_table) */ + db_delete('searchlight_search') + ->condition('type', $datasource->base_table) + ->execute(); $views_data = views_fetch_data($datasource->base_table); $base_field = $views_data['table']['base']['field']; - db_query("INSERT INTO {searchlight_search} (type, id, status) SELECT '". $datasource->base_table ."', ". $base_field .", 0 FROM {". $datasource->base_table ."}"); + // TODO Please convert this statement to the D7 database API syntax. + db_query("INSERT INTO {searchlight_search} (type, id, status) SELECT '" . $datasource->base_table . "', " . $base_field . ", 0 FROM {" . $datasource->base_table . "}"); } } } diff --git a/searchlight_basic/searchlight_basic.defaults.inc b/searchlight_basic/searchlight_basic.defaults.inc index ef6ddb5..a238732 100644 --- a/searchlight_basic/searchlight_basic.defaults.inc +++ b/searchlight_basic/searchlight_basic.defaults.inc @@ -34,14 +34,6 @@ function _searchlight_basic_searchlight_default_datasources() { 'name' => 'node_title', 'usage' => 'content', ), - 'node_revisions_body' => array( - 'label' => 'Node: Body (body)', - 'datatype' => 'text', - 'table' => 'node_revisions', - 'field' => 'body', - 'name' => 'node_revisions_body', - 'usage' => 'content', - ), 'node_status' => array( 'label' => 'Node: Published (status)', 'datatype' => 'int', @@ -64,7 +56,7 @@ function _searchlight_basic_searchlight_default_datasources() { 'table' => 'users', 'field' => 'name', 'name' => 'users_name', - 'usage' => 'attribute', + 'usage' => 'content', ), 'users_uid' => array( 'label' => 'User: Name (uid)', @@ -82,6 +74,22 @@ function _searchlight_basic_searchlight_default_datasources() { 'name' => 'node_type', 'usage' => 'attribute', ), + 'field_data_body_entity_id' => array( + 'label' => 'Fields: body (entity_id)', + 'datatype' => 'int', + 'table' => 'field_data_body', + 'field' => 'entity_id', + 'name' => 'field_data_body_entity_id', + 'usage' => 'attribute', + ), + 'field_data_body_body_value' => array( + 'label' => 'Fields: body (body_value)', + 'datatype' => 'text', + 'table' => 'field_data_body', + 'field' => 'body_value', + 'name' => 'field_data_body_body_value', + 'usage' => 'content', + ), ); $searchlight_datasource->filters = array(); $searchlight_datasource->options = array( diff --git a/searchlight_basic/searchlight_basic.features.inc b/searchlight_basic/searchlight_basic.features.inc index 562d2d4..a5e0908 100644 --- a/searchlight_basic/searchlight_basic.features.inc +++ b/searchlight_basic/searchlight_basic.features.inc @@ -1,7 +1,7 @@ is_cacheable = FALSE; $view->api_version = '3.0-alpha1'; $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ - -/* Display: Defaults */ + + /* Display: Defaults */ $handler = $view->new_display('default', 'Defaults', 'default'); $handler->display->display_options['access']['type'] = 'none'; $handler->display->display_options['cache']['type'] = 'none'; @@ -77,19 +77,21 @@ function _searchlight_basic_views_default_views() { $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0; $handler->display->display_options['fields']['title']['alter']['html'] = 0; $handler->display->display_options['fields']['title']['link_to_node'] = 1; - /* Field: Node: Teaser */ - $handler->display->display_options['fields']['teaser']['id'] = 'teaser'; - $handler->display->display_options['fields']['teaser']['table'] = 'node_revisions'; - $handler->display->display_options['fields']['teaser']['field'] = 'teaser'; - $handler->display->display_options['fields']['teaser']['label'] = ''; - $handler->display->display_options['fields']['teaser']['alter']['alter_text'] = 0; - $handler->display->display_options['fields']['teaser']['alter']['make_link'] = 0; - $handler->display->display_options['fields']['teaser']['alter']['trim'] = 1; - $handler->display->display_options['fields']['teaser']['alter']['max_length'] = '150'; - $handler->display->display_options['fields']['teaser']['alter']['word_boundary'] = 1; - $handler->display->display_options['fields']['teaser']['alter']['ellipsis'] = 1; - $handler->display->display_options['fields']['teaser']['alter']['strip_tags'] = 1; - $handler->display->display_options['fields']['teaser']['alter']['html'] = 0; + /* Field: Fields: body */ + $handler->display->display_options['fields']['entity_id']['id'] = 'entity_id'; + $handler->display->display_options['fields']['entity_id']['table'] = 'field_data_body'; + $handler->display->display_options['fields']['entity_id']['field'] = 'entity_id'; + $handler->display->display_options['fields']['entity_id']['alter']['alter_text'] = 0; + $handler->display->display_options['fields']['entity_id']['alter']['make_link'] = 0; + $handler->display->display_options['fields']['entity_id']['alter']['absolute'] = 0; + $handler->display->display_options['fields']['entity_id']['alter']['trim'] = 0; + $handler->display->display_options['fields']['entity_id']['alter']['word_boundary'] = 1; + $handler->display->display_options['fields']['entity_id']['alter']['ellipsis'] = 1; + $handler->display->display_options['fields']['entity_id']['alter']['strip_tags'] = 0; + $handler->display->display_options['fields']['entity_id']['alter']['html'] = 0; + $handler->display->display_options['fields']['entity_id']['hide_empty'] = 0; + $handler->display->display_options['fields']['entity_id']['empty_zero'] = 0; + /* Sort criterion: Search: Searchlight */ $handler->display->display_options['sorts']['search']['id'] = 'search'; $handler->display->display_options['sorts']['search']['table'] = 'searchlight'; @@ -108,8 +110,8 @@ function _searchlight_basic_views_default_views() { $handler->display->display_options['filters']['facets']['id'] = 'facets'; $handler->display->display_options['filters']['facets']['table'] = 'searchlight'; $handler->display->display_options['filters']['facets']['field'] = 'facets'; - -/* Display: Page: search */ + + /* Display: Page: search */ $handler = $view->new_display('page', 'Page: search', 'page_1'); $handler->display->display_options['path'] = 'search'; diff --git a/searchlight_basic/searchlight_basic.info b/searchlight_basic/searchlight_basic.info index 03fe735..6c99cb8 100644 --- a/searchlight_basic/searchlight_basic.info +++ b/searchlight_basic/searchlight_basic.info @@ -1,4 +1,4 @@ -core = "6.x" +core = 7.x dependencies[] = "searchlight" description = "Basic default configuration for Searchlight indexing and views." features[ctools][] = "searchlight_datasource" @@ -8,3 +8,12 @@ features[searchlight_environment][] = "search_node" features[views][] = "search_node" name = "Searchlight basic" package = "Search" + + + + + +files[] = searchlight_basic.defaults.inc +files[] = searchlight_basic.features.inc +files[] = searchlight_basic.features.views.inc +files[] = searchlight_basic.module diff --git a/theme/searchlight-admin-datasource.tpl.php b/theme/searchlight-admin-datasource.tpl.php index 801c79c..6421834 100644 --- a/theme/searchlight-admin-datasource.tpl.php +++ b/theme/searchlight-admin-datasource.tpl.php @@ -1,5 +1,5 @@
-
+

@@ -18,5 +18,5 @@ - -
\ No newline at end of file + +
diff --git a/theme/searchlight-admin-environment.tpl.php b/theme/searchlight-admin-environment.tpl.php index caa2aff..18c579a 100644 --- a/theme/searchlight-admin-environment.tpl.php +++ b/theme/searchlight-admin-environment.tpl.php @@ -1,5 +1,5 @@
-
+

@@ -7,5 +7,5 @@ - -
\ No newline at end of file + +
diff --git a/theme/searchlight-solr-config.tpl.php b/theme/searchlight-solr-config.tpl.php index 628f203..71b0f6a 100644 --- a/theme/searchlight-solr-config.tpl.php +++ b/theme/searchlight-solr-config.tpl.php @@ -31,6 +31,15 @@ + + + + + standard + solrpingquery + all + + diff --git a/theme/searchlight.theme.inc b/theme/searchlight.theme.inc index 5d5ff93..4273bb3 100644 --- a/theme/searchlight.theme.inc +++ b/theme/searchlight.theme.inc @@ -25,11 +25,11 @@ function template_preprocess_searchlight_solr_schema(&$vars) { $defaults = array( 'indexed' => 'true', 'stored' => 'true', - 'multiValued' => 'false' + 'multiValued' => 'false', ); - foreach($vars['datasource']['schema'] as $id => $field) { + foreach ($vars['datasource']['schema'] as $id => $field) { // Populate defaults. - foreach($defaults as $k => $v) { + foreach ($defaults as $k => $v) { if (!isset($field[$k])) { $vars['datasource']['schema'][$id][$k] = $v; } @@ -47,7 +47,7 @@ function template_preprocess_searchlight_solr_schema(&$vars) { * Theme function for searchlight global search form. * As this is a GET form, FormAPI related data can be excluded. */ -function theme_searchlight_search_form($form) { +function theme_searchlight_search_form() { $output = ''; foreach (element_children($form) as $key) { if (!in_array($key, array('form_build_id', 'form_token', 'form_id'))) { @@ -60,7 +60,9 @@ function theme_searchlight_search_form($form) { /** * Theme function for searchlight datasource display form. */ -function theme_searchlight_plugin_display_datasource($form) { +function theme_searchlight_plugin_display_datasource($variables) { + $variables = $variables['form']; + $form = $variables['form']; $output = ''; $rows = array(); foreach (element_children($form) as $key) { @@ -68,27 +70,36 @@ function theme_searchlight_plugin_display_datasource($form) { $row[] = $form[$key]['#title']; foreach (element_children($form[$key]) as $cell) { if (isset($form[$key][$cell]['#type']) && !in_array($form[$key][$cell]['#type'], array('hidden', 'value'))) { - $row[] = drupal_render($form[$key][$cell]); + $row[] = drupal_render_children($form[$key][$cell]); } } $rows[] = $row; } - $output = theme('table', array(t('Field'), t('Datatype'), t('Usage'), t('Show facet'), ''), $rows); - $output .= drupal_render($form); + $output = theme('table', array('header' => array(t('Field'), t('Datatype'), t('Usage'), t('Show facet'), ''), + 'rows' => $rows, + )); + $output .= drupal_render_children($form); return $output; } /** * Theme function for a facet. */ -function theme_searchlight_facet($facet) { - return theme('item_list', $facet['items'], $facet['label'], 'ul', array('class' => 'searchlight-facet-'. $facet['delta'])); +function theme_searchlight_facet($variables) { + $facet = $variables['facet']; + return theme('item_list', array( + 'items' => $facet['items'], + 'title' => $facet['label'], + 'type' => 'ul', + 'attributes' => array('class' => array('searchlight-facet-' . $facet['delta'])), + )); } /** * Theme function for an individual (inactive) facet link. */ -function theme_searchlight_facet_link($field, $item) { +function theme_searchlight_facet_link($variables) { + $item = $variables['item']; $class = isset($item['class']) ? $item['class'] : ''; return "
{$item['link']} {$item['count']}
"; } @@ -96,23 +107,30 @@ function theme_searchlight_facet_link($field, $item) { /** * Theme function for an individual (active) facet link. */ -function theme_searchlight_facet_active($field, $item) { +function theme_searchlight_facet_active($variables) { + $item = $variables['item']; $class = isset($item['class']) ? $item['class'] : ''; - return "
{$item['title']} {$item['link']}
"; + $title = check_plain($item['title']); + return "
{$title} {$item['link']}
"; } /** * Generates the main datasource admin page. */ -function theme_searchlight_admin_list($form) { - drupal_add_css(drupal_get_path("module", "searchlight") ."/searchlight.admin.css"); +function theme_searchlight_admin_list($variables) { + $form = $variables['form']; + drupal_add_css(drupal_get_path("module", "searchlight") . "/searchlight.admin.css"); $rows = array(); // Generate listing of existing objects foreach ($form['#objects'] as $group => $objects) { if (!empty($group)) { - $rows[] = array(array('data' => check_plain($group), 'colspan' => 4, 'header' => TRUE)); + $rows[] = array(array( + 'data' => check_plain($group), + 'colspan' => 4, + 'header' => TRUE, + ),); } foreach ($objects as $object) { if ($object instanceof SearchlightDatasource) { @@ -122,7 +140,7 @@ function theme_searchlight_admin_list($form) { $name_label = t('Active datasource'); $extra_label = t('Base table'); } - else if ($object instanceof SearchlightEnvironment) { + elseif ($object instanceof SearchlightEnvironment) { $extra = $object->view_display; $type = 'environment'; $path = 'environment'; @@ -141,73 +159,124 @@ function theme_searchlight_admin_list($form) { if ($type === 'datasource') { $row[] = array( 'data' => drupal_render($form['active'][$object->base_table][$object->name]), - 'class' => 'object-name', + 'class' => array('object-name'), ); } else { - $row[] = array('data' => $object->name, 'class' => 'object-name'); + $row[] = array( + 'data' => $object->name, + 'class' => array('object-name'), + ); } - $row[] = array('data' => $extra, 'class' => 'object-extra'); - $row[] = array('data' => $storage, 'class' => 'object-storage'); + $row[] = array( + 'data' => $extra, + 'class' => array('object-extra'), + ); + $row[] = array( + 'data' => $storage, + 'class' => array('object-storage'), + ); // Actions $links = array( - 'edit' => l(t('Edit'), "admin/settings/search/{$path}/list/{$object->name}"), - 'delete' => l(t('Delete'), "admin/settings/search/{$path}/list/{$object->name}/delete"), - 'revert' => l(t('Revert'), "admin/settings/search/{$path}/list/{$object->name}/revert"), + 'edit' => l(t('Edit'), "admin/config/search/settings/{$path}/{$object->name}"), + 'delete' => l(t('Delete'), "admin/config/search/settings/{$path}/{$object->name}/delete"), + 'revert' => l(t('Revert'), "admin/config/search/settings/{$path}/{$object->name}/revert"), ); foreach (array_keys($links) as $key) { if (!searchlight_task_access($object, $key)) { unset($links[$key]); } } - $row[] = array('data' => implode(' | ', $links), 'class' => 'object-links'); + $row[] = array( + 'data' => implode(' | ', $links), + 'class' => array('object-links'), + ); $rows[] = $row; } } $row = array(); foreach (element_children($form['new']) as $key) { if ($form['new'][$key]['#type'] === 'submit') { - $row[] = array('data' => drupal_render($form['new'][$key]), 'colspan' => 2); + $row[] = array( + 'data' => drupal_render($form['new'][$key]), + 'colspan' => 2, + ); } else { $row[] = drupal_render($form['new'][$key]); } } $rows[] = $row; - $output = theme('table', array($name_label, $extra_label, t('Storage'), t('Operations')), $rows, array('class' => 'object-admin')); - $output .= drupal_render($form); + $output = theme('table', array( + 'header' => isset($name_label) ? array($name_label, $extra_label, t('Storage'), t('Operations')) : array(), + 'rows' => $rows, + 'attributes' => array( + 'class' => array('object-admin'), + ), + )); + $output .= drupal_render_children($form); return $output; } /** * Theme function for searchlight datasource display form. */ -function theme_searchlight_admin_datasource_fields($form) { +function theme_searchlight_admin_datasource_fields($variables) { +// $variables = $variables['form']; + $form = $variables['form']; $output = ''; $rows = array(); + drupal_add_js('misc/tableselect.js'); // New fields header. if (!empty($form['new'])) { - $rows[] = array(array('data' => t('Add field'), 'colspan' => 4, 'header' => TRUE)); + $rows[] = array(array( + 'data' => t('Add field'), + 'colspan' => 5, + 'header' => TRUE, + ),); $rows[] = array( - array('data' => drupal_render($form['new']['field']), 'colspan' => 3), + array( + 'data' => drupal_render($form['new']['field']), + 'colspan' => 4, + ), array('data' => drupal_render($form['new']['add'])), ); } - // Existing fields. + // TODO Please change this theme call to use an associative array for the + // $variables parameter. $rows[] = array( - theme('table_select_header_cell') + array('header' => TRUE), - array('data' => t('Field'), 'header' => TRUE), - array('data' => t('Datatype'), 'header' => TRUE), - array('data' => t('Usage'), 'header' => TRUE) + array( + 'class' => array('select-all'), + 'header' => TRUE, + ), + array( + 'data' => t('Relation'), + 'header' => TRUE, + ), + array( + 'data' => t('Field'), + 'header' => TRUE, + ), + array( + 'data' => t('Datatype'), + 'header' => TRUE, + ), + array( + 'data' => t('Usage'), + 'header' => TRUE, + ), ); foreach (element_children($form['fields']) as $key) { if ($key !== 'new') { $row = array(); foreach (element_children($form['fields'][$key]) as $cell) { - if (isset($form['fields'][$key][$cell]['#type']) && !in_array($form['fields'][$key][$cell]['#type'], array('hidden', 'value'))) { + if (isset($form['fields'][$key][$cell]['#markup']) || + (isset($form['fields'][$key][$cell]['#type']) && + !in_array($form['fields'][$key][$cell]['#type'], array('hidden', 'value')))) { + $row[] = drupal_render($form['fields'][$key][$cell]); } } @@ -215,25 +284,118 @@ function theme_searchlight_admin_datasource_fields($form) { } } if (!element_children($form['fields'])) { - $rows[] = array(array('data' => t('No fields found.'), 'colspan' => 4)); + $rows[] = array(array( + 'data' => t('No fields found.'), + 'colspan' => 5, + ),); + } + // Remove fields footer. + if (!empty($form['remove'])) { + $rows[] = array(array( + 'data' => drupal_render($form['remove']), + 'colspan' => 5, + 'header' => TRUE, + ),); } + $output = theme('table', array( + 'header' => array(), + 'rows' => $rows, + 'attributes' => array(), + 'caption' => $form['#title'], + )); + + $output .= drupal_render_children($form); + + return $output; +} + +/** + * Theme function for searchlight datasource display form. + */ +function theme_searchlight_admin_datasource_relations($variables) { + $form = $variables['form']; + $output = ''; + $rows = array(); + drupal_add_js('misc/tableselect.js'); + + // New fields header. + if (!empty($form['new'])) { + $rows[] = array(array( + 'data' => t('Add relation'), + 'colspan' => 3, + 'header' => TRUE, + ),); + $rows[] = array( + array( + 'data' => drupal_render($form['new']['relation']), + 'colspan' => 2, + ), + array('data' => drupal_render($form['new']['add'])), + ); + } + // Existing fields. + // TODO Please change this theme call to use an associative array for the + // $variables parameter. + $rows[] = array( + array( + 'class' => array('select-all'), + 'header' => TRUE, + ), + array( + 'data' => t('Relation'), + 'header' => TRUE, + ), + array( + 'data' => t('Required'), + 'header' => TRUE, + ), + ); + foreach (element_children($form['relations']) as $key) { + if ($key !== 'new') { + $row = array(); + foreach (element_children($form['relations'][$key]) as $cell) { + if (isset($form['relations'][$key][$cell]['#markup']) || + (isset($form['relations'][$key][$cell]['#type']) && + !in_array($form['relations'][$key][$cell]['#type'], array('hidden', 'value')))) { + + $row[] = drupal_render($form['relations'][$key][$cell]); + } + } + $rows[] = $row; + } + } + if (!element_children($form['relations'])) { + $rows[] = array(array( + 'data' => t('No relations found.'), + 'colspan' => 3, + ),); + } // Remove fields footer. if (!empty($form['remove'])) { - $rows[] = array(array('data' => drupal_render($form['remove']), 'colspan' => 4, 'header' => TRUE)); + $rows[] = array(array( + 'data' => drupal_render($form['remove']), + 'colspan' => 3, + 'header' => TRUE, + ),); } - $output = theme('table', array(), $rows, array(), $form['#title']); - $output .= drupal_render($form); + $output = theme('table', array( + 'header' => array(), + 'rows' => $rows, + 'attributes' => array(), + 'caption' => $form['#title'], + )); + + $output .= drupal_render_children($form); + return $output; } /** * Preprocessor for theme('searchlight_admin_environment'). */ -function template_preprocess_searchlight_admin_environment($vars) { - drupal_add_js(drupal_get_path('module', 'searchlight') .'/searchlight.admin.js'); - drupal_add_css(drupal_get_path("module", "searchlight") ."/searchlight.admin.css"); +function template_preprocess_searchlight_admin_environment(&$vars) { drupal_add_tabledrag('searchlight-environment', 'order', 'sibling', 'searchlight-environment-weight', NULL, NULL, TRUE); // Basic environment info. @@ -245,8 +407,14 @@ function template_preprocess_searchlight_admin_environment($vars) { $facets = ''; $rows = array(); $rows[] = array( - array('data' => drupal_render($vars['form']['view_display']['view_display']), 'colspan' => 3), - array('data' => drupal_render($vars['form']['view_display']['update']), 'colspan' => 3), + array( + 'data' => drupal_render($vars['form']['view_display']['view_display']), + 'colspan' => 3, + ), + array( + 'data' => drupal_render($vars['form']['view_display']['update']), + 'colspan' => 3, + ), ); // Build facets table. @@ -261,7 +429,10 @@ function template_preprocess_searchlight_admin_environment($vars) { // Handle element title as table header. if (empty($header_row[$cell])) { $header_cell = !empty($vars['form']['facets'][$key][$cell]['#title']) ? check_plain($vars['form']['facets'][$key][$cell]['#title']) : ''; - $header_row[$cell] = array('data' => $header_cell, 'header' => TRUE); + $header_row[$cell] = array( + 'data' => $header_cell, + 'header' => TRUE, + ); } if (isset($vars['form']['facets'][$key][$cell]['#title'])) { unset($vars['form']['facets'][$key][$cell]['#title']); @@ -269,7 +440,7 @@ function template_preprocess_searchlight_admin_environment($vars) { // Handling for specific elements. switch ($cell) { case 'weight': - $vars['form']['facets'][$key][$cell]['#attributes']['class'] = 'searchlight-environment-weight'; + $vars['form']['facets'][$key][$cell]['#attributes']['class'] = array('searchlight-environment-weight'); $row[] = drupal_render($vars['form']['facets'][$key][$cell]); break; case 'settings': @@ -277,7 +448,7 @@ function template_preprocess_searchlight_admin_environment($vars) { $element_settings_id = "searchlight-facet-{$key}"; if (!empty($element_settings)) { $element_settings_form = "
"; - $element_settings_form .= l(t('Edit'), $_GET['q'], array('attributes' => array('class' => 'environment-settings-link'), 'fragment' => $element_settings_id)); + $element_settings_form .= l(t('Edit'), $_GET['q'], array( 'attributes' => array('class' => array('environment-settings-link')),'fragment' => $element_settings_id)); $element_settings_form .= "
{$element_settings}
"; $element_settings_form .= "
"; $row[] = $element_settings_form; @@ -291,14 +462,20 @@ function template_preprocess_searchlight_admin_environment($vars) { break; } } - $facet_rows[] = array('data' => $row, 'class' => 'draggable'); + $facet_rows[] = array( + 'data' => $row, + 'class' => array('draggable'), + ); } $rows[] = $header_row; $rows = array_merge($rows, $facet_rows); - // Facet table. - $facets .= theme('table', array(), $rows, array('id' => 'searchlight-environment')); + $facets .= theme('table', array( + 'header' => array(), + 'rows' => $rows, + 'attributes' => array('id' => 'searchlight-environment'), + )); $vars['facets'] = $facets; // Per-facet settings. @@ -308,8 +485,8 @@ function template_preprocess_searchlight_admin_environment($vars) { /** * Preprocessor for theme('searchlight_admin_datasource'). */ -function template_preprocess_searchlight_admin_datasource($vars) { - drupal_add_css(drupal_get_path("module", "searchlight") ."/searchlight.admin.css"); +function template_preprocess_searchlight_admin_datasource(&$vars) { + drupal_add_css(drupal_get_path("module", "searchlight") . "/searchlight.admin.css"); $datasource = $vars['form']['#datasource']; $base_tables = views_fetch_base_tables(); diff --git a/views/searchlight.views.inc b/views/searchlight.views.inc index ac86cb0..62cbf57 100644 --- a/views/searchlight.views.inc +++ b/views/searchlight.views.inc @@ -1,25 +1,15 @@ searchlight)) { - $query->build($view); - $query->execute($view); - } -} /** - * Implementation of hook_views_plugins(). + * Implements hook_views_plugins(). */ function searchlight_views_plugins() { return array( 'query' => array( 'searchlight' => array( - 'file' => (views_api_version() == '2.0') ? 'searchlight_plugin_query_v2.inc' : 'searchlight_plugin_query_v3.inc', + 'file' => 'searchlight_plugin_query_v3.inc', 'path' => drupal_get_path('module', 'searchlight') . '/views', 'title' => t('Searchlight'), 'help' => t('Searchlight query plugin.'), @@ -57,11 +47,11 @@ function searchlight_views_plugins() { } /** - * Implementation of hook_views_handlers(). + * Implements hook_views_handlers(). */ function searchlight_views_handlers() { return array( - 'info' => array('path' => drupal_get_path('module', 'searchlight') .'/views'), + 'info' => array('path' => drupal_get_path('module', 'searchlight') . '/views'), 'handlers' => array( 'searchlight_handler_argument_search' => array('parent' => 'views_handler_argument'), 'searchlight_handler_filter_search' => array('parent' => 'views_handler_filter'), @@ -74,7 +64,7 @@ function searchlight_views_handlers() { } /** - * Implementation of hook_views_data(). + * Implements hook_views_data(). */ function searchlight_views_data() { $data = array(); @@ -103,7 +93,7 @@ function searchlight_views_data() { } /** - * Implementation of hook_views_data_alter(). + * Implements hook_views_data_alter(). */ function searchlight_views_data_alter(&$data) { if (!empty($data['node_access']['nid']) && empty($data['node_access']['nid']['field'])) { @@ -113,11 +103,27 @@ function searchlight_views_data_alter(&$data) { ); } + // A special handler for taxonomy_index table. Taxonomy views integration + // in D7 sometimes makes use of `taxonomy_term_data` but when used for + // arguments, filters, etc. directly queries against `taxonomy_index`. + if (module_exists('taxonomy')) { + $data['taxonomy_index']['searchlight_tid'] = array( + 'title' => t('Term ID (Searchlight)'), + 'help' => t('The taxonomy term ID'), + 'real field' => 'tid', + 'field' => array( + 'field' => 'tid', + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + ), + ); + } + // OG UID field handler. Needed to allow filtering directly against OG UID. if (module_exists('og_views')) { $data['og_uid']['nid']['field'] = array( 'title' => t('Group ID'), - 'handler' => 'views_handler_field' + 'handler' => 'views_handler_field', ); } @@ -163,15 +169,16 @@ function searchlight_views_init_query(&$view) { // Check that an active datasource can be found for this base table. if (searchlight_get_datasource($view->base_table)) { // Add Searchlight JS. - drupal_add_js(drupal_get_path('module', 'searchlight') .'/searchlight.js'); + drupal_add_js(drupal_get_path('module', 'searchlight') . '/searchlight.js'); - $view->query = searchlight_get_query($view->base_table, $view->base_field); + $query_options = $view->display_handler->get_option('query'); + $view->query = searchlight_get_query($view->base_table, $view->base_field, $query_options['options']); } } } /** - * Implementation of hook_views_default_views(). + * Implements hook_views_default_views(). */ function searchlight_views_default_views() { $views = array(); @@ -181,7 +188,7 @@ function searchlight_views_default_views() { // $realms = array(); $result = db_query("SELECT realm FROM {node_access} GROUP BY realm"); - while ($row = db_fetch_object($result)) { + foreach ($result as $row) { $realms[$row->realm] = $row->realm; } // Allow modules to declare their realms in an alter hook. This allows @@ -236,12 +243,44 @@ function searchlight_views_default_views() { $view->base_table = 'node'; $view->api_version = '3.0-alpha1'; $handler = $view->new_display('default', 'Default', 'default'); + /* Display: All terms */ + $handler = $view->new_display('searchlight_multivalue', 'all', 'searchlight_multivalue_all'); + $handler->override_option('arguments', array()); + $handler->override_option('relationships', array()); + $handler->override_option('sorts', array()); + $handler->override_option('fields', array( + 'nid' => array( + 'id' => 'nid', + 'table' => 'node', + 'field' => 'nid', + 'relationship' => 'none', + ), + 'seachlight_tid' => array( + 'id' => 'searchlight_tid', + 'table' => 'taxonomy_index', + 'field' => 'searchlight_tid', + 'relationship' => 'none', + 'label' => t('Term ID'), + ), + 'name' => array( + 'id' => 'name', + 'table' => 'taxonomy_term_data', + 'field' => 'name', + 'relationship' => 'none', + 'label' => t('All terms'), + ), + )); + $handler->override_option('searchlight_multivalue', array( + 'field' => 'taxonomy_index_tid', + 'label_field' => 'taxonomy_term_data_name', + 'override' => array( + 'name' => 'taxonomy_index_tid', + 'label' => t('Taxonomy (all)'), + ), + )); foreach (taxonomy_get_vocabularies() as $vocab) { - $identifier = !empty($vocab->module) && $vocab->module !== 'taxonomy' ? $vocab->module : $vocab->vid; - $identifier = strtr($identifier, ' -:', '___'); - /* Display: Vocabulary */ - $handler = $view->new_display('searchlight_multivalue', $vocab->name, 'searchlight_multivalue_'. $identifier); + $handler = $view->new_display('searchlight_multivalue', $vocab->name, 'searchlight_multivalue_' . $vocab->machine_name); $handler->override_option('arguments', array()); $handler->override_option('relationships', array()); $handler->override_option('sorts', array()); @@ -252,43 +291,34 @@ function searchlight_views_default_views() { 'field' => 'nid', 'relationship' => 'none', ), + 'seachlight_tid' => array( + 'id' => 'searchlight_tid', + 'table' => 'taxonomy_index', + 'field' => 'searchlight_tid', + 'relationship' => 'none', + 'label' => t('Term ID'), + ), 'name' => array( 'id' => 'name', - 'table' => 'term_data', + 'table' => 'taxonomy_term_data', 'field' => 'name', 'relationship' => 'none', 'label' => $vocab->name, ), )); - - // If Views can handle the Taxonomy vocab module field for supporting - // exportable vocabularies, use it. - $data = views_fetch_data('vocabulary'); - if (isset($data['module']) && !is_numeric($identifier)) { - $handler->override_option('filters', array( - 'module' => array( - 'id' => 'module', - 'table' => 'vocabulary', - 'field' => 'module', - 'value' => array($vocab->module => $vocab->module), - ), - )); - } - else { - $handler->override_option('filters', array( - 'vid' => array( - 'id' => 'vid', - 'table' => 'term_data', - 'field' => 'vid', - 'value' => array($vocab->vid => $vocab->vid), - ), - )); - } + $handler->override_option('filters', array( + 'machine_name' => array( + 'id' => 'machine_name', + 'table' => 'taxonomy_vocabulary', + 'field' => 'machine_name', + 'value' => array($vocab->machine_name => $vocab->machine_name), + ), + )); $handler->override_option('searchlight_multivalue', array( - 'field' => 'term_data_tid', - 'label_field' => 'term_data_name', + 'field' => 'taxonomy_index_tid', + 'label_field' => 'taxonomy_term_data_name', 'override' => array( - 'name' => 'term_data_tid_'. $identifier, + 'name' => 'taxonomy_index_tid_' . $vocab->machine_name, 'label' => $vocab->name, ), )); @@ -296,214 +326,5 @@ function searchlight_views_default_views() { $views[$view->name] = $view; } - // - // Content profile ========================================================== - // - if (module_exists('content_profile')) { - $view = new view; - $view->name = 'searchlight_content_profile'; - $view->tag = 'searchlight'; - $view->base_table = 'users'; - $view->api_version = '3.0-alpha1'; - $handler = $view->new_display('default', 'Default', 'default'); - foreach(array_keys(content_profile_get_types()) as $profile_type) { - foreach (taxonomy_get_vocabularies() as $vocab) { - if (isset($vocab->nodes[$profile_type])) { - $identifier = !empty($vocab->module) && $vocab->module !== 'taxonomy' ? $vocab->module : $vocab->vid; - $identifier = strtr($identifier, ' -:', '___'); - - /* Display: Vocabulary */ - $handler = $view->new_display('searchlight_multivalue', "{$profile_type}: {$vocab->name}", "searchlight_multivalue_{$profile_type}_{$identifier}"); - $handler->override_option('arguments', array()); - $handler->override_option('sorts', array()); - $handler->override_option('relationships', array( - 'content_profile_rel' => array( - 'required' => 1, - 'type' => $profile_type, - 'id' => 'content_profile_rel', - 'table' => 'users', - 'field' => 'content_profile_rel', - 'relationship' => 'none', - ), - )); - $handler->override_option('fields', array( - 'uid' => array( - 'id' => 'uid', - 'table' => 'users', - 'field' => 'uid', - 'relationship' => 'none', - ), - 'name' => array( - 'id' => 'name', - 'table' => 'term_data', - 'field' => 'name', - 'relationship' => 'content_profile_rel', - 'label' => $vocab->name, - ), - )); - - // If Views can handle the Taxonomy vocab module field for supporting - // exportable vocabularies, use it. - $data = views_fetch_data('vocabulary'); - if (isset($data['module']) && !is_numeric($identifier)) { - $handler->override_option('filters', array( - 'module' => array( - 'id' => 'module', - 'table' => 'vocabulary', - 'field' => 'module', - 'value' => array($vocab->module => $vocab->module), - 'relationship' => 'content_profile_rel', - ), - )); - } - else { - $handler->override_option('filters', array( - 'vid' => array( - 'id' => 'vid', - 'table' => 'term_data', - 'field' => 'vid', - 'value' => array($vocab->vid => $vocab->vid), - 'relationship' => 'content_profile_rel', - ), - )); - } - $handler->override_option('searchlight_multivalue', array( - 'field' => 'node_users__term_data_tid', - 'label_field' => 'node_users__term_data_name', - 'override' => array( - 'name' => "term_data_tid_{$profile_type}_{$identifier}", - 'label' => $vocab->name, - ), - )); - } - } - } - $views[$view->name] = $view; - } - - // - // Organic Groups =========================================================== - // - if (module_exists('og_views')) { - $view = new view; - $view->name = 'searchlight_og_ancestry'; - $view->tag = 'searchlight'; - $view->base_table = 'node'; - $view->api_version = '3.0-alpha1'; - $handler = $view->new_display('default', 'Default', 'default'); - - /* Display: Node's groups */ - $handler = $view->new_display('searchlight_multivalue', 'Organic Groups', 'searchlight_multivalue_og'); - $handler->override_option('arguments', array()); - $handler->override_option('sorts', array()); - $handler->override_option('relationships', array( - 'group_nid' => array( - 'required' => 1, - 'id' => 'group_nid', - 'table' => 'og_ancestry', - 'field' => 'group_nid', - 'relationship' => 'none', - ), - )); - $handler->override_option('fields', array( - 'nid' => array( - 'id' => 'nid', - 'table' => 'node', - 'field' => 'nid', - 'relationship' => 'none', - ), - 'title' => array( - 'id' => 'title', - 'table' => 'node', - 'field' => 'title', - 'label' => 'Groups', - 'relationship' => 'group_nid', - ), - )); - $handler->override_option('searchlight_multivalue', array( - 'field' => 'node_og_ancestry_nid', - 'label_field' => 'node_og_ancestry_title', - 'override' => array( - 'label' => 'Organic Groups', - 'table' => 'og_ancestry', - 'name' => 'group_nid', - ), - )); - $views[$view->name] = $view; - - $view = new view; - $view->name = 'searchlight_og_uid'; - $view->tag = 'searchlight'; - $view->base_table = 'users'; - $view->api_version = '3.0-alpha1'; - $handler = $view->new_display('default', 'Default', 'default'); - - /* Display: User's groups */ - $handler = $view->new_display('searchlight_multivalue', 'Organic Groups', 'searchlight_multivalue_og'); - $handler->override_option('arguments', array()); - $handler->override_option('sorts', array()); - $handler->override_option('relationships', array( - 'nid' => array( - 'required' => 1, - 'id' => 'nid', - 'table' => 'og_uid', - 'field' => 'nid', - 'relationship' => 'none', - ), - )); - $handler->override_option('fields', array( - 'uid' => array( - 'id' => 'uid', - 'table' => 'users', - 'field' => 'uid', - 'relationship' => 'none', - ), - 'title' => array( - 'id' => 'title', - 'table' => 'node', - 'field' => 'title', - 'label' => 'Groups', - 'relationship' => 'nid', - ), - )); - $handler->override_option('searchlight_multivalue', array( - 'field' => 'node_og_uid_nid', - 'label_field' => 'node_og_uid_title', - 'override' => array( - 'label' => 'Organic Groups', - 'table' => 'og_uid', - 'name' => 'nid', - ), - )); - - /* Display: User's groups */ - $handler = $view->new_display('searchlight_multivalue', 'Organic Groups (filter)', 'searchlight_multivalue_og_uid'); - $handler->override_option('arguments', array()); - $handler->override_option('sorts', array()); - $handler->override_option('relationships', array()); - $handler->override_option('fields', array( - 'uid' => array( - 'id' => 'uid', - 'table' => 'users', - 'field' => 'uid', - 'relationship' => 'none', - ), - 'nid' => array( - 'id' => 'nid', - 'table' => 'og_uid', - 'field' => 'nid', - 'label' => 'Groups', - ), - )); - $handler->override_option('searchlight_multivalue', array( - 'field' => 'og_uid_nid', - 'label_field' => 'og_uid_nid', - 'override' => array( - 'label' => 'Organic Groups (nid)', - ), - )); - $views[$view->name] = $view; - } - return $views; } diff --git a/views/searchlight_handler_field_facet_link.inc b/views/searchlight_handler_field_facet_link.inc index c94ece0..981fd54 100644 --- a/views/searchlight_handler_field_facet_link.inc +++ b/views/searchlight_handler_field_facet_link.inc @@ -1,15 +1,18 @@ array(0 => '---') + drupal_map_assoc(array_keys($this->get_render_tokens(array()))), '#default_value' => $this->options['field_token'], ); + $form['text_token'] = array( + '#title' => t('Display as link'), + '#type' => 'select', + '#options' => array(0 => '---') + drupal_map_assoc(array_keys($this->get_render_tokens(array()))), + '#default_value' => $this->options['text_token'], + ); $form['absolute'] = array( '#title' => t('Absolute URL'), '#type' => 'checkbox', '#default_value' => $this->options['absolute'], ); + $form['exclude'] = array( + '#title' => t('Exclude this field'), + '#type' => 'checkbox', + '#default_value' => $this->options['exclude'], + ); } function render($values) { @@ -52,7 +66,13 @@ class searchlight_handler_field_facet_link extends views_handler_field { $path = $environment->getURLPath(); $options = $environment->getURLOptions('add', $facet, $tokens[$this->options['field_token']]); $options['absolute'] = $this->options['absolute']; - return url($path, $options); + // TODO The second parameter to this function call should be an array. + if (!empty($this->options['text_token'])) { + return l(check_plain($tokens[$this->options['text_token']]), $path, $options); + } + else { + return url($path, $options); + } } } return ''; diff --git a/views/searchlight_handler_field_node_access.inc b/views/searchlight_handler_field_node_access.inc index 4b0a33b..3ff2f98 100644 --- a/views/searchlight_handler_field_node_access.inc +++ b/views/searchlight_handler_field_node_access.inc @@ -11,10 +11,13 @@ class searchlight_handler_field_node_access extends views_handler_field { parent::options_form($form, $form_state); $options = array(); $result = db_query("SELECT realm FROM {node_access} GROUP BY realm"); - while ($row = db_fetch_object($result)) { + foreach ($result as $row) { $options[$row->realm] = check_plain($row->realm); } - $form['realm'] = array('#type' => 'select', '#options' => $options); + $form['realm'] = array( + '#type' => 'select', + '#options' => $options, + ); } function construct() { @@ -26,8 +29,8 @@ class searchlight_handler_field_node_access extends views_handler_field { parent::query(); $table = $this->ensure_my_table(); if (!empty($this->options['realm'])) { - $this->query->add_where('AND', "$table.realm = '%s'", $this->options['realm']); - $this->query->add_where('AND', "$table.grant_view >= 1"); + $this->query->add_where('AND', "$table.realm = '" . $this->options['realm'] . "'", array(), 'formula'); + $this->query->add_where('AND', "$table.grant_view >= -1", array(), 'formula'); } } @@ -42,5 +45,7 @@ class searchlight_handler_field_node_access extends views_handler_field { } } - function allow_advanced_render() { return FALSE; } + function allow_advanced_render() { + return FALSE; + } } diff --git a/views/searchlight_handler_filter_facets.inc b/views/searchlight_handler_filter_facets.inc index a9305c0..0edff8f 100644 --- a/views/searchlight_handler_filter_facets.inc +++ b/views/searchlight_handler_filter_facets.inc @@ -21,5 +21,7 @@ class searchlight_handler_filter_facets extends views_handler_filter { } } - function can_expose() { return FALSE; } + function can_expose() { + return FALSE; + } } diff --git a/views/searchlight_handler_sort_search.inc b/views/searchlight_handler_sort_search.inc index 51e4e72..14016c8 100644 --- a/views/searchlight_handler_sort_search.inc +++ b/views/searchlight_handler_sort_search.inc @@ -9,9 +9,10 @@ class searchlight_handler_sort_search extends views_handler_sort { if (isset($this->query->fields['searchlight_weight'])) { unset($this->query->fields['searchlight_weight']); - $key = array_search('searchlight_weight '. $this->options['order'], $this->query->orderby); - if ($key !== FALSE) { - unset($this->query->orderby[$key]); + foreach ($this->query->orderby as $key => $info) { + if ($info['field'] == 'searchlight_weight') { + unset($this->query->orderby[$key]); + } } } } diff --git a/views/searchlight_plugin_display_multivalue.inc b/views/searchlight_plugin_display_multivalue.inc index 29f2500..4f338da 100644 --- a/views/searchlight_plugin_display_multivalue.inc +++ b/views/searchlight_plugin_display_multivalue.inc @@ -34,20 +34,26 @@ class searchlight_plugin_display_multivalue extends views_plugin_display { } } + $class = get_class($handler); + + // Allow custom handlers to specity the data_type in definition + if (isset($handler->definition['data_type'])) { + return $handler->definition['data_type']; + } + $schema = drupal_get_schema($table); if ($schema && isset($field, $schema['fields'][$field])) { - $class = get_class($handler); // Get the datasource attribute type. // We use the handler class for special cases like timestamp where DB // column type is not enough information to determine the usage of the // field. $map = array( - 'serial' => 'int', - 'int' => 'int', + 'serial' => 'int', + 'int' => 'int', 'varchar' => 'text', - 'text' => 'text', - 'float' => 'float', + 'text' => 'text', + 'float' => 'float', ); if (isset($map[$schema['fields'][$field]['type']])) { $column_type = $map[$schema['fields'][$field]['type']]; @@ -69,7 +75,6 @@ class searchlight_plugin_display_multivalue extends views_plugin_display { $clone->set_display($this->view->current_display); $clone->build(); $handlers = $clone->display_handler->get_handlers('field'); - $fields = array(); foreach ($handlers as $handler) { if (!empty($handler->relationship) || $handler->real_field !== $handler->view->base_field) { @@ -91,7 +96,8 @@ class searchlight_plugin_display_multivalue extends views_plugin_display { $table = isset($info['table']) ? $info['table'] : $table; $field = $info['field']; } - if (!empty($handler->relationship) || $field !== $handler->view->base_field || $table !== $handler->view->base_table) { + + if ((!empty($handler->relationship) || ($field !== $handler->view->base_field) || ($table !== $handler->view->base_table)) && !empty($handler->aliases[$field])) { $fields[$handler->aliases[$field]] = array( 'label' => $handler->ui_name() . " ({$field})", 'datatype' => $this->get_datatype($handler, $table, $field), @@ -118,17 +124,29 @@ class searchlight_plugin_display_multivalue extends views_plugin_display { ); $options = $this->get_option($this->my_name()); $fields = $this->get_fields(); + foreach ($fields as $name => $info) { - if ($options['field'] === $name) { - $info['usage'] = 'multivalue'; - $multivalue = array_merge($multivalue, $info); - } - else if ($options['label_field'] === $name) { + +// Why should fields without label_field not +// be added to the multivalue array ???????? +// +// if ($options['field'] === $name) { +// $info['usage'] = 'multivalue'; +// $multivalue = array_merge($multivalue, $info); +// } +// elseif ($options['label_field'] === $name) { +// $info['usage'] = 'multivalue'; +// $multivalue['label_field'] = $info; +// } + if ($options['label_field'] === $name) { $info['usage'] = 'multivalue'; $multivalue['label_field'] = $info; } + else { + $info['usage'] = 'multivalue'; + $multivalue = array_merge($multivalue, $info); + } } - // Override explicitly specified properties if available. if (!empty($options['override'])) { foreach ($options['override'] as $key => $value) { @@ -137,13 +155,14 @@ class searchlight_plugin_display_multivalue extends views_plugin_display { } } } + return $multivalue; } /** * Override of option_definition(). */ - function option_definition () { + function option_definition() { $options = parent::option_definition(); $options[$this->my_name()]['field'] = array('default' => NULL); $options[$this->my_name()]['label_field'] = array('default' => NULL); diff --git a/views/searchlight_plugin_display_solr.inc b/views/searchlight_plugin_display_solr.inc index f179d22..a647a5f 100644 --- a/views/searchlight_plugin_display_solr.inc +++ b/views/searchlight_plugin_display_solr.inc @@ -3,7 +3,7 @@ class searchlight_plugin_display_solr extends views_plugin_display { function render() { $return = array(); - foreach ($this->view->result as $row) { + foreach ($this->view->result as $row) { $render = array(); foreach ($this->view->field as $id => $field) { $render[$id] = $field->advanced_render($row); diff --git a/views/searchlight_plugin_query_v2.inc b/views/searchlight_plugin_query_v2.inc deleted file mode 100644 index 0be88e1..0000000 --- a/views/searchlight_plugin_query_v2.inc +++ /dev/null @@ -1,391 +0,0 @@ -pager = new searchlight_plugin_pager($view); - } - - /** - * Override of init(). - */ - function init($base_table = 'node', $base_field = 'nid', $backend) { - parent::views_query($base_table, $base_field); - - $this->datasource = searchlight_get_datasource($base_table); - $this->datasource->init(); - $this->datasource_id = $this->datasource->id; - - $this->backend = $backend; - $this->client = $backend->initClient($this->datasource); - - $this->search_buildmode = NULL; - $this->search_facets = array(); - $this->search_filter = array(); - $this->search_options = array(); - $this->search_result = array(); - $this->search_sort = array(); - $this->search_query = ''; - - // This flag lets others know we are a searchlight-based view. - $this->searchlight = TRUE; - } - - /** - * Override of add_where(). - */ - function add_where($group, $clause) { - $where_args = func_get_args(); - $where_args = $this->get_args($where_args); - - // Call the parent method to add the filter SQL side. - parent::add_where($group, $clause, $where_args); - - $split = preg_split('/[ ]([<>=!]*)|(\sIN\s)|(\sIS\s)|(\sNOT IN\s)|(\sIS NOT\s)/i', trim($clause), NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); - if (count($split) >= 2) { - $field = explode('.', trim(array_shift($split))); - $operator = strtoupper(trim(array_shift($split))); - $possible_args = count($split) ? array_shift($split) : ''; - if (count($field) === 2) { - $table = $field[0]; - $field = $field[1]; - foreach ($this->datasource->fields as $name => $info) { - if ($name === "{$table}_{$field}") { - // Get the arguments for this where clause. - $args = array(); - // If arguments have been specified using query placeholders, - // simply grab as many as there have been placeholders specified. - $argnum = substr_count($clause, '%'); - if ($argnum && count($where_args) >= $argnum) { - $args = array_slice($where_args, 0, $argnum); - } - // We may be working with a NULL check... - elseif ($operator == 'IS' && $possible_args == 'NULL') { - $operator = 'IN'; - $args[] = 0; // This may only work with numeric fields... - } - elseif ($operator == 'IS NOT' && $possible_args == 'NULL') { - $operator = 'IN'; - $args[] = 1; // This may only work with numeric fields... - } - // Otherwise, groan, breath deeply & try to retrieve arguments from - // the query. - else { - $matches = array(); - preg_match_all('/[A-Z*_]*|\w+|\d+/', $possible_args, $matches); - if (!empty($matches)) { - foreach ($matches as $match) { - foreach ($match as $chunk) { - if ($chunk !== '') { - $args[] = $chunk; - } - } - } - } - } - - // Done. - $this->search_filter[] = array('field' => $name, 'operator' => $operator, 'args' => $args); - return; - } - } - } - } - } - - function add_orderby($table, $field, $order, $alias = '', $params = array()) { - // Call the parent method to add the orderby SQL side. - parent::add_orderby($table, $field, $order, $alias, $params); - - if ($field === 'searchlight_weight') { - $this->search_sort[] = array('field' => 'searchlight_weight', 'order' => $order); // "@weight {$order}"; - } - else { - // Field is aliased. Use query fields to retrieve actual table, field. - if (!empty($alias) && isset($this->fields[$alias])) { - $table = $this->fields[$alias]['table']; - $field = $this->fields[$alias]['field']; - } - // Use table field info to retrieve datasource field name and add sort. - if (isset($table, $field)) { - foreach ($this->datasource->fields as $name => $info) { - if ($name === "{$table}_{$field}" && $info['usage'] !== 'content') { - $this->search_sort[] = array('field' => $name, 'order' => $order); // "{$name} {$order}"; - break; - } - } - } - // If no table was specified or found, this is probably a function. - // Use the alias directly if it is available. - else if (empty($table) && isset($this->datasource->fields[$alias]) && $this->datasource->fields[$alias]['usage'] !== 'content') { - $this->search_sort[] = array('field' => $alias, 'order' => $order); - } - } - } - - function add_search_facet($name, $limit = 5, $options = array()) { - $this->search_facets[] = array('field' => $name, 'limit' => $limit) + $options; - } - - /** - * Set the Search build mode behavior. - * - * @param string $mode - * One of the following modes: - * 'default': Build the Search for facets, etc. but use the View's default - * result set. - * 'search': Build the Search for facets, etc. and use the search backend - * to generate the result set. - * 'empty': Build the Search for facets, etc. but blank this View's - * result set. - * @param boolean $force - * Force the mode specified. Otherwise, will only set the build mode if no - * other mode has been set yet. - */ - function set_search_buildmode($mode = 'default', $force = FALSE) { - if (!isset($this->search_buildmode) || $force) { - $this->search_buildmode = $mode; - } - } - - function set_search_options($options) { - $this->search_options = $options; - } - - function set_search_query($query) { - $this->search_query = $query; - } - - function get_search_facet($facet) { - return isset($this->search_result_facets[$facet]) ? $this->search_result_facets[$facet] : array(); - } - - protected function get_args($args, $offset = 2) { - $args = array_slice($args, $offset); - if (count($args) == 1 && is_array(reset($args))) { - return current($args); - } - return $args; - } - - function build(&$view) { - $this->set_search_buildmode(); - - // Fail the rest of the build entirely if the hideEmpty option is true and - // there is no search query available. We don't exit right here in order - // to allow facet queries to run. - if (empty($this->fields) || $this->search_buildmode === 'empty') { - parent::add_where(0, "FALSE"); - } - - // Views pager initialization. - if ($this->search_buildmode === 'search') { - $this->init_pager($view); - if (!empty($this->pager)) { - // I'm not sure the consequences of collaping these two tests, nor can - // I think of a time the pager will be present but use_pager not set - // however, there are some things better left unknown, lest you end up - // like the Dwarves in Moria... - if ($this->pager->use_pager()) { - $this->pager->set_current_page($view->current_page); - } - // Let the pager modify the query to add limits. - $this->pager->query(); - } - } - - // Views query token replacements. - $replacements = module_invoke_all('views_query_substitutions', $view); - - // Do token replacement against filter fields & args. - if (!empty($replacements)) { - foreach ($this->search_filter as $j => $filter) { - $this->search_filter[$j]['field'] = str_replace(array_keys($replacements), $replacements, $this->search_filter[$j]['field']); - foreach ($this->search_filter[$j]['args'] as $k => $arg) { - $this->search_filter[$j]['args'][$k] = str_replace(array_keys($replacements), $replacements, $arg); - } - } - } - - // Set backend client options. - $this->backend->setOptions($this->client, $this->datasource, $this->search_options); - $this->backend->setSort($this->client, $this->datasource, $this->search_sort); - $this->backend->setFilter($this->client, $this->datasource, $this->search_filter); - $this->backend->setPager($this->client, $this->offset, $this->limit); - if ($this->base_table === 'node' && !user_access('administer nodes') && !empty($this->datasource->options['node_access'])) { - $this->backend->setNodeAccess($this->client, node_access_grants('view')); - } - - // Main query execution. - $this->search_result = $this->backend->executeQuery($this->client, $this->datasource, $this->search_query); - - // Build facets. - $this->search_result_facets = $this->backend->facetBuild($this->client, $this->datasource, $this->search_query, $this->search_facets); - - if ($this->search_buildmode === 'search') { - if ($this->search_result) { - $placeholders = db_placeholders($this->search_result['result']); - parent::add_where(0, "{$this->base_table}.{$this->base_field} IN ({$placeholders})", $this->search_result['result']); - - $view->build_info['query'] = $this->query(); - $view->build_info['count_query'] = "SELECT FALSE"; - $view->build_info['query_args'] = $this->get_where_args(); - $view->built = TRUE; - } - elseif (!empty($this->search_query)) { - parent::add_where(0, "FALSE"); - } - } - } - - function execute(&$view) { - if ($this->search_buildmode === 'search') { - // Store values prior to execute(). - $offset = $this->offset; - $limit = $this->limit; - $current_page = $this->pager->current_page; - $this->offset = $this->limit = 0; - - if (!empty($this->search_result)) { - $view->total_rows = $this->pager->total_items = $this->search_result['total']; - $this->pager->set_current_page($current_page); - $this->pager->update_page_info(); - } - - // Restore original values. - $this->offset = $offset; - $this->limit = $limit; - - if ($view->built && !empty($this->search_result)) { - // 2.x: Actually execute the View and process the result set order. - $view->execute(); - - // Ensure the order of the result set from Views matches that of the - // search backend. Don't attempt to do this for aggregate queries. - if (empty($this->has_aggregate)) { - $sortmap = array(); - $positions = array_flip(array_values($this->search_result['result'])); - $i = count($positions); - foreach ($view->result as $num => $row) { - $key = $row->{$view->base_field}; - // If in search results use its position in the resultset. - if (isset($positions[$key])) { - $sortmap[$num] = $positions[$key]; - } - // If not, move to the end of the stack. - else { - $sortmap[$num] = $i; - $i++; - } - } - array_multisort($sortmap, $view->result); - } - } - } - } -} - -/** - * Reimplementation of the Views 3 pager class. - * Many of the methods here are taken from views_plugin_pager_full. - */ -class searchlight_plugin_pager { - var $view; - var $options; - var $current_page; - - function __construct($view) { - $this->view = $view; - $this->options = $view->pager; - $this->options['id'] = $this->options['element']; - } - - function use_pager() { - return !empty($this->options['items_per_page']); - } - - function set_current_page($number = NULL) { - if (isset($number)) { - $this->current_page = $number; - return; - } - - // If the current page number was not prespecified, default to pulling it from 'page' - // based upon - global $pager_page_array; - // Extract the ['page'] info. - $pager_page_array = isset($_GET['page']) ? explode(',', $_GET['page']) : array(); - - $this->current_page = 0; - if (!empty($pager_page_array[$this->options['id']])) { - $this->current_page = intval($pager_page_array[$this->options['id']]); - } - } - - function get_items_per_page() { - return isset($this->options['items_per_page']) ? $this->options['items_per_page'] : 0; - } - - function update_page_info() { - if (!empty($this->options['total_pages'])) { - if (($this->options['total_pages'] * $this->options['items_per_page']) < $this->total_items) { - $this->total_items = $this->options['total_pages'] * $this->options['items_per_page']; - } - } - - // Don't set pager settings for items per page = 0. - $items_per_page = $this->get_items_per_page(); - if (!empty($items_per_page)) { - // Dump information about what we already know into the globals. - global $pager_page_array, $pager_total, $pager_total_items; - // Set the item count for the pager. - $pager_total_items[$this->options['id']] = $this->total_items; - // Calculate and set the count of available pages. - $pager_total[$this->options['id']] = ceil($pager_total_items[$this->options['id']] / $this->get_items_per_page()); - - // See if the requested page was within range: - if ($this->current_page < 0) { - $this->current_page = 0; - } - else if ($this->current_page >= $pager_total[$this->options['id']]) { - // Pages are numbered from 0 so if there are 10 pages, the last page is 9. - $this->current_page = $pager_total[$this->options['id']] - 1; - } - - // Put this number in to guarantee that we do not generate notices when the pager - // goes to look for it later. - $pager_page_array[$this->options['id']] = $this->current_page; - } - - // This prevents our pager information from being overwritten by - // $view->execute() after our work is done. - $this->view->pager['items_per_page'] = NULL; - } - - function query() { - $limit = $this->options['items_per_page']; - $offset = $this->current_page * $this->options['items_per_page'] + $this->options['offset']; - $this->view->query->limit = $limit; - $this->view->query->offset = $offset; - } -} - diff --git a/views/searchlight_plugin_query_v3.inc b/views/searchlight_plugin_query_v3.inc index 97d43e7..b2c07d8 100644 --- a/views/searchlight_plugin_query_v3.inc +++ b/views/searchlight_plugin_query_v3.inc @@ -17,8 +17,8 @@ class searchlight_plugin_query extends views_plugin_query_default { /** * Override of init(). */ - function init($base_table = 'node', $base_field = 'nid', $backend) { - parent::init($base_table, $base_field); + function init($base_table = 'node', $base_field = 'nid', $options = array(), $backend) { + parent::init($base_table, $base_field, $options); // Init the fields array on behalf of the parent. $this->fields = isset($this->fields) ? $this->fields : array(); @@ -30,6 +30,8 @@ class searchlight_plugin_query extends views_plugin_query_default { $this->backend = $backend; $this->client = $backend->initClient($this->datasource); + $this->offset = NULL; + $this->limit = NULL; $this->search_buildmode = NULL; $this->search_facets = array(); $this->search_filter = array(); @@ -41,66 +43,22 @@ class searchlight_plugin_query extends views_plugin_query_default { // This flag lets others know we are a searchlight-based view. $this->searchlight = TRUE; } - /** * Override of add_where(). */ - function add_where($group, $clause) { - $where_args = func_get_args(); - $where_args = $this->get_args($where_args); - + function add_where($group, $field, $value = NULL, $operator = NULL) { // Call the parent method to add the filter SQL side. - parent::add_where($group, $clause, $where_args); - - $split = preg_split('/[ ]([<>=!]*)|(\sIN\s)|(\sIS\s)|(\sNOT IN\s)|(\sIS NOT\s)/i', trim($clause), NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); - if (count($split) >= 2) { - $field = explode('.', trim(array_shift($split))); - $operator = strtoupper(trim(array_shift($split))); - $possible_args = count($split) ? array_shift($split) : ''; - if (count($field) === 2) { - $table = $field[0]; - $field = $field[1]; - foreach ($this->datasource->fields as $name => $info) { - if ($name === "{$table}_{$field}") { - // Get the arguments for this where clause. - $args = array(); - - // If arguments have been specified using query placeholders, - // simply grab as many as there have been placeholders specified. - $argnum = substr_count($clause, '%'); - if ($argnum && count($where_args) >= $argnum) { - $args = array_slice($where_args, 0, $argnum); - } - // We may be working with a NULL check... - elseif ($operator == 'IS' && $possible_args == 'NULL') { - $operator = 'IN'; - $args[] = 0; // This may only work with numeric fields... - } - elseif ($operator == 'IS NOT' && $possible_args == 'NULL') { - $operator = 'IN'; - $args[] = 1; // This may only work with numeric fields... - } - // Otherwise, groan, breath deeply & try to retrieve arguments from - // the query. - else { - $matches = array(); - preg_match_all('/[A-Z*_]*|\w+|\d+/', $possible_args, $matches); - if (!empty($matches)) { - foreach ($matches as $match) { - foreach ($match as $chunk) { - if ($chunk !== '') { - $args[] = $chunk; - } - } - } - } - } - - // Done. - $this->search_filter[] = array('field' => $name, 'operator' => $operator, 'args' => $args); - return; - } - } + parent::add_where($group, $field, $value, $operator); + + foreach ($this->datasource->fields as $name => $info) { + $match = str_replace(".", "_", $field); + if ($name === $match || "{$info['table']}_{$info['field']}" === $match) { + $this->search_filter[] = array( + 'field' => $name, + 'operator' => !empty($operator) ? strtoupper($operator) : 'IN', + 'args' => $value + ); + return; } } } @@ -110,7 +68,10 @@ class searchlight_plugin_query extends views_plugin_query_default { parent::add_orderby($table, $field, $order, $alias, $params); if ($field === 'searchlight_weight') { - $this->search_sort[] = array('field' => 'searchlight_weight', 'order' => $order); // "@weight {$order}"; + $this->search_sort[] = array( + 'field' => 'searchlight_weight', + 'direction' => $order, + ); } else { // Field is aliased. Use query fields to retrieve actual table, field. @@ -122,7 +83,10 @@ class searchlight_plugin_query extends views_plugin_query_default { if (isset($table, $field)) { foreach ($this->datasource->fields as $name => $info) { if ($name === "{$table}_{$field}" && $info['usage'] !== 'content') { - $this->search_sort[] = array('field' => $name, 'order' => $order); // "{$name} {$order}"; + $this->search_sort[] = array( + 'field' => $name, + 'direction' => $order, + );; break; } } @@ -130,13 +94,19 @@ class searchlight_plugin_query extends views_plugin_query_default { // If no table was specified or found, this is probably a function. // Use the alias directly if it is available. else if (empty($table) && isset($this->datasource->fields[$alias]) && $this->datasource->fields[$alias]['usage'] !== 'content') { - $this->search_sort[] = array('field' => $alias, 'order' => $order); + $this->search_sort[] = array( + 'field' => $alias, + 'direction' => $order, + ); } } } function add_search_facet($name, $limit = 5, $options = array()) { - $this->search_facets[] = array('field' => $name, 'limit' => $limit) + $options; + $this->search_facets[] = array( + 'field' => $name, + 'limit' => $limit, + ) + $options; } /** @@ -216,8 +186,13 @@ class searchlight_plugin_query extends views_plugin_query_default { if (!empty($replacements)) { foreach ($this->search_filter as $j => $filter) { $this->search_filter[$j]['field'] = str_replace(array_keys($replacements), $replacements, $this->search_filter[$j]['field']); - foreach ($this->search_filter[$j]['args'] as $k => $arg) { - $this->search_filter[$j]['args'][$k] = str_replace(array_keys($replacements), $replacements, $arg); + if (is_array($this->search_filter[$j]['args'])) { + foreach ($this->search_filter[$j]['args'] as $k => $arg) { + $this->search_filter[$j]['args'][$k] = str_replace(array_keys($replacements), $replacements, $arg); + } + } + else { + $this->search_filter[$j]['args'] = str_replace(array_keys($replacements), $replacements, $this->search_filter[$j]['args']); } } } @@ -236,15 +211,27 @@ class searchlight_plugin_query extends views_plugin_query_default { // Build facets. $this->search_result_facets = $this->backend->facetBuild($this->client, $this->datasource, $this->search_query, $this->search_facets); + + // Count the number of groups from the facet result to determine the total when using result grouping + if (!empty($this->datasource->options['groupfield'])) { + $this->search_result['total'] = count($this->search_result['result']); + } if ($this->search_buildmode === 'search') { - if ($this->search_result) { - $placeholders = db_placeholders($this->search_result['result']); - parent::add_where(0, "{$this->base_table}.{$this->base_field} IN ({$placeholders})", $this->search_result['result']); + if (isset($this->search_result, $this->search_result['result'])) { + // Make the query distinct if the option was set. + if (!empty($this->options['distinct'])) { + $this->set_distinct(); + } + + parent::add_where(0, "{$this->base_table}.{$this->base_field}", $this->search_result['result'], 'IN'); $view->build_info['query'] = $this->query(); - $view->build_info['count_query'] = "SELECT FALSE"; - $view->build_info['query_args'] = $this->get_where_args(); + $view->build_info['count_query'] = $this->query(); + } + else { + // we did not return any search results. Return no result. + $this->search_buildmode = 'empty'; } } elseif ($this->search_buildmode === 'default') { @@ -253,7 +240,10 @@ class searchlight_plugin_query extends views_plugin_query_default { } function execute(&$view) { - if ($this->search_buildmode === 'default') { + if ($this->search_buildmode === 'empty') { + // + } + elseif ($this->search_buildmode === 'default') { parent::execute($view); } elseif ($this->search_buildmode === 'search') { @@ -299,3 +289,4 @@ class searchlight_plugin_query extends views_plugin_query_default { } } } +