diff --git a/contrib/search_api_facetapi/search_api_facetapi.module b/contrib/search_api_facetapi/search_api_facetapi.module index db52816..22ed183 100644 --- a/contrib/search_api_facetapi/search_api_facetapi.module +++ b/contrib/search_api_facetapi/search_api_facetapi.module @@ -103,11 +103,7 @@ function search_api_facetapi_facetapi_facet_info(array $searcher_info) { // Iterate through the indexed fields to set the facetapi settings for // each one. - foreach ($index->options['fields'] as $key => $field) { - if (!$field['indexed']) { - continue; - } - + foreach ($index->getFields() as $key => $field) { $field['key'] = $key; // Determine which, if any, of the field type-specific options will be // used for this field. diff --git a/contrib/search_api_facets/search_api_facets.admin.inc b/contrib/search_api_facets/search_api_facets.admin.inc index 9d30b3d..9f8a8c6 100644 --- a/contrib/search_api_facets/search_api_facets.admin.inc +++ b/contrib/search_api_facets/search_api_facets.admin.inc @@ -161,9 +161,6 @@ function search_api_facets_index_select(array $form, array &$form_state, SearchA $types = search_api_field_types(); $entity_types = entity_get_info(); foreach ($index->options['fields'] as $key => $field) { - if (!$field['indexed']) { - continue; - } if (isset($field['entity_type']) && isset($entity_types[$field['entity_type']]['label'])) { $type = $entity_types[$field['entity_type']]['label']; } diff --git a/contrib/search_api_facets/search_api_facets.module b/contrib/search_api_facets/search_api_facets.module index 49360ed..b97115c 100644 --- a/contrib/search_api_facets/search_api_facets.module +++ b/contrib/search_api_facets/search_api_facets.module @@ -752,7 +752,7 @@ function search_api_facets_search_api_index_update(SearchApiIndex $index) { $fields = $options['fields']; $facets = search_api_facet_load_multiple(FALSE, array('index_id' => $index->machine_name, 'enabled' => 1)); foreach ($facets as $facet) { - if (empty($fields[$facet->field]['indexed'])) { + if (empty($fields[$facet->field])) { $facet->enabled = FALSE; $facet->save(); } diff --git a/contrib/search_api_views/includes/display_facet_block.inc b/contrib/search_api_views/includes/display_facet_block.inc index 683e4ee..5d637c3 100644 --- a/contrib/search_api_views/includes/display_facet_block.inc +++ b/contrib/search_api_views/includes/display_facet_block.inc @@ -127,9 +127,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block { $this->field_options = array(); if (!empty($index->options['fields'])) { foreach ($index->options['fields'] as $key => $field) { - if ($field['indexed']) { - $this->field_options[$key] = $field['name']; - } + $this->field_options[$key] = $field['name']; } } } diff --git a/contrib/search_api_views/includes/handler_argument_more_like_this.inc b/contrib/search_api_views/includes/handler_argument_more_like_this.inc index be73ab0..533f8f1 100644 --- a/contrib/search_api_views/includes/handler_argument_more_like_this.inc +++ b/contrib/search_api_views/includes/handler_argument_more_like_this.inc @@ -25,9 +25,7 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg if (!empty($index->options['fields'])) { $fields = array(); foreach ($index->options['fields'] as $key => $field) { - if ($field['indexed']) { - $fields[$key] = $field['name']; - } + $fields[$key] = $field['name']; } } if (!empty($fields)) { @@ -65,9 +63,7 @@ class SearchApiViewsHandlerArgumentMoreLikeThis extends SearchApiViewsHandlerArg $fields = $this->options['fields'] ? $this->options['fields'] : array(); if (empty($fields)) { foreach($this->query->getIndex()->options['fields'] as $key => $field) { - if ($field['indexed']) { - $fields[] = $key; - } + $fields[] = $key; } } $mlt = array( diff --git a/contrib/search_api_views/search_api_views.views.inc b/contrib/search_api_views/search_api_views.views.inc index 7b38c9b..cd9cd32 100644 --- a/contrib/search_api_views/search_api_views.views.inc +++ b/contrib/search_api_views/search_api_views.views.inc @@ -94,7 +94,7 @@ function search_api_views_views_data() { } // If field is indexed, also add additional handlers. - if (!empty($fields[$field]['indexed'])) { + if (!empty($fields[$field])) { // Discern between original and indexed type $table[$key]['field']['type'] = $table[$key]['type']; $table[$key]['type'] = $fields[$field]['type']; @@ -120,9 +120,6 @@ function search_api_views_views_data() { // Add handlers for all indexed fields which weren't processed yet. $orig_wrapper = $index->entityWrapper(NULL, TRUE); foreach ($fields as $key => $field) { - if (empty($field['indexed'])) { - continue; - } $tmp = $orig_wrapper; $group = ''; $name = ''; diff --git a/includes/callback_add_aggregation.inc b/includes/callback_add_aggregation.inc index 54a90e0..0bb5835 100644 --- a/includes/callback_add_aggregation.inc +++ b/includes/callback_add_aggregation.inc @@ -8,8 +8,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { public function configurationForm() { $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css'; - $fields = empty($this->index->options['fields']) ? _search_api_admin_get_fields($this->index, $this->index->entityWrapper()) : $this->index->options; - $fields = $fields['fields']; + $fields = $this->index->getFields(FALSE); $field_options = array(); foreach ($fields as $name => $field) { $field_options[$name] = $field['name']; @@ -68,7 +67,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { '#type' => 'checkboxes', '#title' => t('Contained fields'), '#options' => $field_options, - '#default_value' => $field['fields'], + '#default_value' => drupal_map_assoc($field['fields']), '#attributes' => array('class' => array('search-api-alter-add-aggregation-fields')), '#required' => TRUE, ); @@ -102,11 +101,14 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { } public function configurationFormValidate(array $form, array &$values, array &$form_state) { + unset($values['actions']); if (empty($values['fields'])) { return; } foreach ($values['fields'] as $name => $field) { - if ($field['name'] && !$field['fields']) { + $fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields'])); + unset($values['fields'][$name]['actions']); + if ($field['name'] && !$fields) { form_error($form['fields'][$name]['fields'], t('You have to select at least one field to aggregate. If you want to remove an aggregated field, please delete its name.')); } } @@ -114,7 +116,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { public function configurationFormSubmit(array $form, array &$values, array &$form_state) { if (empty($values['fields'])) { - return; + return array(); } foreach ($values['fields'] as $name => $field) { if (!$field['name']) { @@ -134,21 +136,22 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { foreach ($items as $item) { $wrapper = $this->index->entityWrapper($item); foreach ($this->options['fields'] as $name => $field) { - $required_fields = array(); - foreach ($field['fields'] as $f) { - if (!isset($required_fields[$f])) { - $required_fields[$f]['type'] = $types[$field['type']]; + if ($field['name']) { + $required_fields = array(); + foreach ($field['fields'] as $f) { + if (!isset($required_fields[$f])) { + $required_fields[$f]['type'] = $types[$field['type']]; + } } - } - $fields = search_api_extract_fields($wrapper, $required_fields); - $values = array(); - foreach ($fields as $f) { - if (isset($f['value'])) { - $values[] = $f['value']; + $fields = search_api_extract_fields($wrapper, $required_fields); + $values = array(); + foreach ($fields as $f) { + if (isset($f['value'])) { + $values[] = $f['value']; + } } - } - $values = $this->flattenArray($values); - if ($field['name']) { + $values = $this->flattenArray($values); + $this->reductionType = $field['type']; $item->$name = array_reduce($values, array($this, 'reduce'), NULL); if ($field['type'] == 'count' && !$item->$name) { @@ -202,11 +205,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { public function propertyInfo() { $types = $this->getTypes('type'); $ret = array(); + $index_fields = $this->index->getFields(FALSE); if (isset($this->options['fields'])) { foreach ($this->options['fields'] as $name => $field) { $ret[$name] = array( 'label' => $field['name'], - 'description' => $this->fieldDescription($field), + 'description' => $this->fieldDescription($field, $index_fields), 'type' => $types[$field['type']], ); } @@ -217,12 +221,10 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { /** * Helper method for creating a field description. */ - protected function fieldDescription(array $field) { + protected function fieldDescription(array $field, array $index_fields) { $fields = array(); foreach ($field['fields'] as $f) { - if ($f !== 0) { - $fields[] = isset($this->index->options['fields'][$f]) ? $this->index->options['fields'][$f]['name'] : $f; - } + $fields[] = isset($index_fields[$f]) ? $index_fields[$f]['name'] : $f; } $type = $this->getTypes(); $type = $type[$field['type']]; @@ -232,8 +234,10 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback { /** * Helper method for getting all available aggregation types. * - * @param $descriptions - * If TRUE, get type descriptions instead of names. + * @param $info (optional) + * One of "name", "type" or "description", to indicate what values should be + * returned for the types. Defaults to "name". + * */ protected function getTypes($info = 'name') { switch ($info) { diff --git a/includes/callback_add_hierarchy.inc b/includes/callback_add_hierarchy.inc index 3accc56..98e832c 100644 --- a/includes/callback_add_hierarchy.inc +++ b/includes/callback_add_hierarchy.inc @@ -77,7 +77,6 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback { list($key, $prop) = explode(':', $field); if (empty($previous[$field]) && isset($fields[$key]['type'])) { $fields[$key]['type'] = 'list<' . search_api_extract_inner_type($fields[$key]['type']) . '>'; - $fields[$key]['indexed'] = 1; $change = TRUE; } } diff --git a/includes/callback_node_access.inc b/includes/callback_node_access.inc index fec6a28..002cc94 100644 --- a/includes/callback_node_access.inc +++ b/includes/callback_node_access.inc @@ -100,7 +100,7 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback { $new_status = !empty($form_state['values']['callbacks']['search_api_alter_node_access']['status']); if (!$old_status && $new_status) { - $form_state['index']->options['fields']['status']['indexed'] = TRUE; + $form_state['index']->options['fields']['status']['type'] = 'boolean'; } return $values; diff --git a/includes/index_entity.inc b/includes/index_entity.inc index 35f0769..60a8bfe 100644 --- a/includes/index_entity.inc +++ b/includes/index_entity.inc @@ -105,20 +105,20 @@ class SearchApiIndex extends Entity { * - cron_limit: The maximum number of items to be indexed per cron batch. * - index_directly: Boolean setting whether entities are indexed immediately * after they are created or updated. - * - fields: An array of all known fields for this index. Keys are the field + * - fields: An array of all indexed fields for this index. Keys are the field * identifiers, the values are arrays for specifying the field settings. The * structure of those arrays looks like this: - * - name: The human-readable name for the field. - * - indexed: Boolean indicating whether the field is indexed or not. * - type: The type set for this field. One of the types returned by * search_api_field_types(). - * - boost: A boost value for terms found in this field during searches. - * Usually only relevant for fulltext fields. + * - boost: (optional) A boost value for terms found in this field during + * searches. Usually only relevant for fulltext fields. Defaults to 1.0. * - entity_type (optional): If set, the type of this field is really an * entity. The "type" key will then contain "integer", meaning that * servers will ignore this and merely index the entity's ID. Components * displaying this field, though, are advised to use the entity label * instead of the ID. + * - additional fields: An associative array with keys and values being the + * field identifiers of related entities whose fields should be displayed. * - data_alter_callbacks: An array of all data alterations available. Keys * are the alteration identifiers, the values are arrays containing the * settings for that data alteration. The inner structure looks like this: @@ -414,17 +414,7 @@ class SearchApiIndex extends Entity { if (empty($this->options['fields'])) { throw new SearchApiException(t("Couldn't index values on '!name' index (no fields selected)", array('!name' => $this->name))); } - $fields = $this->options['fields']; - foreach ($fields as $field => $info) { - if (!$info['indexed']) { - unset($fields[$field]); - } - unset($fields[$field]['indexed']); - } - if (empty($fields)) { - throw new SearchApiException(t("Couldn't index values on '!name' index (no fields selected)", array('!name' => $this->name))); - } // Mark all items that are rejected as indexed. $ret = array_keys($items); @@ -663,7 +653,174 @@ class SearchApiIndex extends Entity { } /** - * Convenience method for getting all of this index' fulltext fields. + * Returns a list of all known fields for this index. + * + * @param $only_indexed (optional) + * Return only indexed fields, not all known fields. Defaults to TRUE. + * @param $get_additional (optional) + * Return not only known/indexed fields, but also related entities whose + * fields could additionally be added to the index. + * + * @return array + * An array of all known fields for this index. Keys are the field + * identifiers, the values are arrays for specifying the field settings. The + * structure of those arrays looks like this: + * - name: The human-readable name for the field. + * - description: A description of the field, if available. + * - indexed: Boolean indicating whether the field is indexed or not. + * - type: The type set for this field. One of the types returned by + * search_api_field_types(). + * - boost: A boost value for terms found in this field during searches. + * Usually only relevant for fulltext fields. + * - entity_type (optional): If set, the type of this field is really an + * entity. The "type" key will then contain "integer", meaning that + * servers will ignore this and merely index the entity's ID. Components + * displaying this field, though, are advised to use the entity label + * instead of the ID. + * If $get_additional is TRUE, this array is encapsulated in another + * associative array, which contains the above array under the "fields" key, + * and a list of related entities (field keys mapped to names) under the + * "additional fields" key. + */ + public function getFields($only_indexed = TRUE, $get_additional = FALSE) { + $fields = empty($this->options['fields']) ? array() : $this->options['fields']; + $wrapper = $this->entityWrapper(); + $additional = array(); + $entity_types = entity_get_info(); + + // First we need all already added prefixes. + $added = ($only_indexed || empty($this->options['additional fields'])) ? array() : $this->options['additional fields']; + foreach (array_keys($fields) as $key) { + $len = strlen($key) + 1; + $pos = $len; + // The third parameter ($offset) to strrpos has rather weird behaviour, + // necessitating this rather awkward code. It will iterate over all + // prefixes of each field, beginning with the longest, adding all of them + // to $added until one is encountered that was already added (which means + // all shorter ones will have already been added, too). + while ($pos = strrpos($key, ':', $pos - $len)) { + $prefix = substr($key, 0, $pos); + if (isset($added[$prefix])) { + break; + } + $added[$prefix] = $prefix; + } + } + + // Then we walk through all properties and look if they are already + // contained in one of the arrays. + // Since this uses an iterative instead of a recursive approach, it is a bit + // complicated, with three arrays tracking the current depth. + + // A wrapper for a specific field name prefix, e.g. 'user:' mapped to the user wrapper + $wrappers = array('' => $wrapper); + // Display names for the prefixes + $prefix_names = array('' => ''); + // The list nesting level for entities with a certain prefix + $nesting_levels = array('' => 0); + + $types = search_api_field_types(); + $flat = array(); + while ($wrappers) { + foreach ($wrappers as $prefix => $wrapper) { + $prefix_name = $prefix_names[$prefix]; + // Deal with lists of entities. + $nesting_level = $nesting_levels[$prefix]; + $type_prefix = str_repeat('list<', $nesting_level); + $type_suffix = str_repeat('>', $nesting_level); + if ($nesting_level) { + $info = $wrapper->info(); + // The real nesting level of the wrapper, not the accumulated one. + $level = search_api_list_nesting_level($info['type']); + for ($i = 0; $i < $level; ++$i) { + $wrapper = $wrapper[0]; + } + } + // Now look at all properties. + foreach ($wrapper as $property => $value) { + $info = $value->info(); + // We hide the complexity of multi-valued types from the user here. + $type = search_api_extract_inner_type($info['type']); + // Treat Entity API type "token" as our "string" type. + // Also let text fields with limited options be of type "string" by default. + if ($type == 'token' || ($type == 'text' && $value->optionsList('view'))) { + // Inner type is changed to "string". + $type = 'string'; + // Set the field type accordingly. + $info['type'] = search_api_nest_type('string', $info['type']); + } + $info['type'] = $type_prefix . $info['type'] . $type_suffix; + $key = $prefix . $property; + if (isset($types[$type]) || isset($entity_types[$type])) { + if ($only_indexed && empty($fields[$key])) { + continue; + } + if (isset($fields[$key])) { + // This field is already known in the index configuration. + $fields[$key] += array( + 'name' => $prefix_name . $info['label'], + 'description' => empty($info['description']) ? NULL : $info['description'], + 'boost' => '1.0', + 'indexed' => TRUE, + ); + $flat[$key] = $fields[$key]; + // Update its type. + if (isset($entity_types[$type])) { + // Always enforce the proper entity type. + $flat[$key]['type'] = $info['type']; + } + else { + // Else, only update the nesting level. + $flat[$key]['type'] = search_api_nest_type(search_api_extract_inner_type($flat[$key]['type']), $info['type']); + } + } + else { + $flat[$key] = array( + 'name' => $prefix_name . $info['label'], + 'description' => empty($info['description']) ? NULL : $info['description'], + 'type' => $info['type'], + 'boost' => '1.0', + 'indexed' => FALSE, + ); + } + } + if (empty($types[$type])) { + if (isset($added[$key])) { + // Visit this entity/struct in a later iteration. + $wrappers[$key . ':'] = $value; + $prefix_names[$key . ':'] = $prefix_name . $info['label'] . ' » '; + $nesting_levels[$key . ':'] = search_api_list_nesting_level($info['type']); + } + else { + $name = $prefix_name . $info['label']; + // Add machine names to discern fields with identical labels. + if (isset($used_names[$name])) { + if ($used_names[$name] !== FALSE) { + $additional[$used_names[$name]] .= ' [' . $used_names[$name] . ']'; + $used_names[$name] = FALSE; + } + $name .= ' [' . $key . ']'; + } + $additional[$key] = $name; + $used_names[$name] = $key; + } + } + } + unset($wrappers[$prefix]); + } + } + + if (!$get_additional) { + return $flat; + } + $options = array(); + $options['fields'] = $flat; + $options['additional fields'] = $additional; + return $options; + } + + /** + * Convenience method for getting all of this index's fulltext fields. * * @param boolean $only_indexed * If set to TRUE, only the indexed fulltext fields will be returned. @@ -676,11 +833,9 @@ class SearchApiIndex extends Entity { $i = $only_indexed ? 1 : 0; if (!isset($this->fulltext_fields[$i])) { $this->fulltext_fields[$i] = array(); - if (empty($this->options['fields'])) { - return array(); - } - foreach ($this->options['fields'] as $key => $field) { - if (search_api_is_text_type($field['type']) && (!$only_indexed || $field['indexed'])) { + $fields = $only_indexed ? $this->options['fields'] : $this->getFields(FALSE); + foreach ($fields as $key => $field) { + if (search_api_is_text_type($field['type'])) { $this->fulltext_fields[$i][] = $key; } } diff --git a/includes/processor.inc b/includes/processor.inc index 288e8f5..bd44980 100644 --- a/includes/processor.inc +++ b/includes/processor.inc @@ -160,16 +160,13 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface public function configurationForm() { $form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css'; - $fields = empty($this->index->options['fields']) ? _search_api_admin_get_fields($this->index, $this->index->entityWrapper()) : $this->index->options; - $fields = $fields['fields']; + $fields = $this->index->getFields(); $field_options = array(); $default_fields = isset($this->options['fields']) ? $this->options['fields'] : array(); foreach ($fields as $name => $field) { - if ($field['indexed']) { - $field_options[$name] = $field['name']; - if (!isset($this->options['fields']) && $this->testField($name, $field)) { - $default_fields[$name] = $name; - } + $field_options[$name] = $field['name']; + if (!isset($this->options['fields']) && $this->testField($name, $field)) { + $default_fields[$name] = $name; } } @@ -184,7 +181,13 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface return $form; } - public function configurationFormValidate(array $form, array &$values, array &$form_state) { } + public function configurationFormValidate(array $form, array &$values, array &$form_state) { + $fields = array_filter($values['fields']); + if ($fields) { + $fields = array_combine($fields, array_fill(0, count($fields), TRUE)); + } + $values['fields'] = $fields; + } public function configurationFormSubmit(array $form, array &$values, array &$form_state) { $this->options = $values; diff --git a/includes/processor_html_filter.inc b/includes/processor_html_filter.inc index 205723a..f636e2f 100644 --- a/includes/processor_html_filter.inc +++ b/includes/processor_html_filter.inc @@ -60,6 +60,8 @@ class SearchApiHtmlFilter extends SearchApiAbstractProcessor { } public function configurationFormValidate(array $form, array &$values, array &$form_state) { + parent::configurationFormValidate($form, $values, $form_state); + if (empty($values['tags'])) { return; } diff --git a/includes/processor_stopwords.inc b/includes/processor_stopwords.inc index 40c0d40..d8559ca 100644 --- a/includes/processor_stopwords.inc +++ b/includes/processor_stopwords.inc @@ -6,7 +6,9 @@ class SearchApiStopWords extends SearchApiAbstractProcessor { public function configurationForm() { - $form = array( + $form = parent::configurationForm(); + + $form += array( 'help' => array( '#markup' => '

' . t('Provide a stopwords file or enter the words in this form. If you do both, both will be used. Read about !stopwords.', array('!stopwords' => l(t('stop words'), "http://en.wikipedia.org/wiki/Stop_words"))) . '

', ), @@ -32,6 +34,8 @@ class SearchApiStopWords extends SearchApiAbstractProcessor { } public function configurationFormValidate(array $form, array &$values, array &$form_state) { + parent::configurationFormValidate($form, $values, $form_state); + $stopwords = trim($values['stopwords']); $uri = $values['file']; if (empty($stopwords) && empty($uri)) { diff --git a/includes/processor_tokenizer.inc b/includes/processor_tokenizer.inc index d3e3574..2a36dea 100644 --- a/includes/processor_tokenizer.inc +++ b/includes/processor_tokenizer.inc @@ -45,6 +45,8 @@ class SearchApiTokenizer extends SearchApiAbstractProcessor { } public function configurationFormValidate(array $form, array &$values, array &$form_state) { + parent::configurationFormValidate($form, $values, $form_state); + $spaces = str_replace('/', '\/', $values['spaces']); $ignorable = str_replace('/', '\/', $values['ignorable']); if (@preg_match('/(' . $spaces . ')+/u', '') === FALSE) { diff --git a/search_api.admin.inc b/search_api.admin.inc index 6854d83..9d35340 100644 --- a/search_api.admin.inc +++ b/search_api.admin.inc @@ -666,6 +666,7 @@ function search_api_admin_index_view(SearchApiIndex $index = NULL, $action = NUL '#enabled' => $index->enabled, '#server' => $index->server(), '#options' => $index->options, + '#fields' => $index->getFields(), '#status' => $index->status, '#read_only' => $index->read_only, ); @@ -686,6 +687,7 @@ function search_api_admin_index_view(SearchApiIndex $index = NULL, $action = NUL * - enabled: Boolean indicating whether the index is enabled. * - server: The server this index currently rests on, if any. * - options: The index' options, like cron limit. + * - fields: All indexed fields of the index. * - indexed_items: The number of items already indexed in their latest * version on this index. * - total_items: The total number of items that have to be indexed for this @@ -751,21 +753,19 @@ function theme_search_api_index(array $variables) { $output .= '
' . format_plural($options['cron_limit'], '1 item per cron batch.', '@count items per cron batch.') . '
' . "\n"; } - if (!empty($options['fields'])) { - $fields = array(); - foreach ($options['fields'] as $name => $field) { - if ($field['indexed']) { - if (search_api_is_text_type($field['type'])) { - $fields[] = t('@field (@boost x)', array('@field' => $field['name'], '@boost' => $field['boost'])); - } - else { - $fields[] = $field['name']; - } + if (!empty($fields)) { + $fields_list = array(); + foreach ($fields as $name => $field) { + if (search_api_is_text_type($field['type'])) { + $fields_list[] = t('@field (@boost x)', array('@field' => $field['name'], '@boost' => $field['boost'])); + } + else { + $fields_list[] = $field['name']; } } - if ($fields) { + if ($fields_list) { $output .= '
' . t('Indexed fields') . '
' . "\n"; - $output .= '
' . implode(', ', $fields) . '
' . "\n"; + $output .= '
' . implode(', ', $fields_list) . '
' . "\n"; } } @@ -1396,10 +1396,7 @@ function search_api_admin_index_workflow_submit(array $form, array &$form_state) } } $index->options['fields'][$key] = array( - 'name' => $field['label'], 'type' => $type, - 'boost' => '1.0', - 'indexed' => 1, ); } } @@ -1449,9 +1446,7 @@ function search_api_admin_element_compare($a, $b) { * The index to edit. */ function search_api_admin_index_fields(array $form, array &$form_state, SearchApiIndex $index) { - $wrapper = $index->entityWrapper(); - - $options = _search_api_admin_get_fields($index, $wrapper); + $options = $index->getFields(FALSE, TRUE); $fields = $options['fields']; $additional = $options['additional fields']; @@ -1478,10 +1473,6 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp } foreach ($fields as $key => $info) { $form['fields'][$key]['title']['#markup'] = check_plain($info['name']); - $form['fields'][$key]['name'] = array( - '#type' => 'value', - '#value' => $info['name'], - ); if (isset($info['description'])) { $form['fields'][$key]['description'] = array( '#type' => 'value', @@ -1597,13 +1588,13 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp ); } - // @todo Provide a way to remove a related entity's fields again? - return $form; } /** * Helper function for building the field list for an index. + * + * @deprecated Use SearchApiIndex::getFields() instead. */ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapper $wrapper) { $fields = empty($index->options['fields']) ? array() : $index->options['fields']; @@ -1681,7 +1672,7 @@ function _search_api_admin_get_fields(SearchApiIndex $index, EntityMetadataWrapp 'description' => empty($info['description']) ? NULL : $info['description'], 'type' => $info['type'], 'boost' => '1.0', - 'indexed' => 0, + 'indexed' => FALSE, ); } } @@ -1768,10 +1759,19 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) { $options = isset($index->options) ? $index->options : array(); if ($form_state['values']['op'] == t('Save changes')) { $fields = $form_state['values']['fields']; - foreach ($fields as $name => &$field) { - unset($field['description']); + foreach ($fields as $name => $field) { + if (empty($field['indexed'])) { + unset($fields[$name]); + } + else { + unset($fields[$name]['description'], $fields[$name]['indexed']); + if (!search_api_is_text_type($field['type']) || $field['boost'] == '1.0') { + unset($fields[$name]['boost']); + } + } } $options['fields'] = $fields; + unset($options['additional fields']); $ret = $index->update(array('options' => $options)); if ($ret) { @@ -1791,56 +1791,8 @@ function search_api_admin_index_fields_submit(array $form, array &$form_state) { return; } // Adding a related entity's fields. - $wrapper = $orig_wrapper = $index->entityWrapper(); $prefix = $form_state['values']['additional']['field']; - $types = search_api_field_types(); - $name_prefix = ''; - $type_prefix = $type_suffix = ''; - - foreach (explode(':', $prefix) as $element) { - if (!isset($wrapper->$element)) { - $wrapper = NULL; - break; - } - $wrapper = $wrapper->$element; - $wrapper_info = $wrapper->info(); - $level = search_api_list_nesting_level($wrapper_info['type']); - for ($i = 0; $i < $level; ++$i) { - $wrapper = $wrapper[0]; - } - // For each nesting level, wrap the field types in an additional "list<*>". - $type_prefix .= str_repeat('list<', $level); - $type_suffix .= str_repeat('>', $level); - $name_prefix .= $wrapper_info['label'] . ' » '; - } - if (!$wrapper) { - drupal_set_message(t("An error occurred while trying to add the related entity's fields."), 'error'); - return; - } - - if (!empty($options['fields'])) { - $fields = $options['fields']; - } - else { - $fields = _search_api_admin_get_fields($index, $orig_wrapper); - $fields = $fields['fields']; - } - - $prefix .= ':'; - foreach ($wrapper as $field => $value) { - $info = $value->info(); - $type = $type_prefix . $info['type'] . $type_suffix; - $key = $prefix . $field; - if (isset($types[$info['type']]) && empty($fields[$key])) { - $fields[$key] = array( - 'name' => $name_prefix . $info['label'], - 'type' => $type, - 'boost' => '1.0', - 'indexed' => 0, - ); - } - } - $options['fields'] = $fields; + $options['additional fields'][$prefix] = $prefix; $ret = $index->update(array('options' => $options)); if ($ret) { diff --git a/search_api.install b/search_api.install index 1fa87d2..b014c78 100644 --- a/search_api.install +++ b/search_api.install @@ -261,162 +261,40 @@ function search_api_install() { ), ), 'fields' => array( - 'nid' => array( - 'name' => 'Node ID', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'vid' => array( - 'name' => 'Revision ID', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'tnid' => array( - 'name' => 'Translation set ID', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'uid' => array( - 'name' => 'User ID', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'is_new' => array( - 'name' => 'Is new', - 'type' => 'boolean', - 'boost' => '1.0', - 'indexed' => 0, - ), 'type' => array( - 'name' => 'Content type', 'type' => 'string', - 'boost' => '1.0', - 'indexed' => 1, - ), - 'type_name' => array( - 'name' => 'Content type name', - 'type' => 'string', - 'boost' => '1.0', - 'indexed' => 0, ), 'title' => array( - 'name' => 'Title', 'type' => 'text', 'boost' => '5.0', - 'indexed' => 1, - ), - 'language' => array( - 'name' => 'Language', - 'type' => 'string', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'url' => array( - 'name' => 'URL', - 'type' => 'uri', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'edit_url' => array( - 'name' => 'Edit URL', - 'type' => 'uri', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'status' => array( - 'name' => 'Published', - 'type' => 'boolean', - 'boost' => '1.0', - 'indexed' => 1, ), 'promote' => array( - 'name' => 'Promoted to frontpage', 'type' => 'boolean', - 'boost' => '1.0', - 'indexed' => 1, ), 'sticky' => array( - 'name' => 'Sticky in lists', 'type' => 'boolean', - 'boost' => '1.0', - 'indexed' => 1, ), 'created' => array( - 'name' => 'Date created', 'type' => 'date', - 'boost' => '1.0', - 'indexed' => 1, ), 'changed' => array( - 'name' => 'Date changed', 'type' => 'date', - 'boost' => '1.0', - 'indexed' => 1, ), 'author' => array( - 'name' => 'Author', 'type' => 'integer', 'entity_type' => 'user', - 'boost' => '1.0', - 'indexed' => 1, - ), - 'log' => array( - 'name' => 'Revision log message', - 'type' => 'text', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'revision' => array( - 'name' => 'Creates revision', - 'type' => 'boolean', - 'boost' => '1.0', - 'indexed' => 0, - ), - 'comment' => array( - 'name' => 'Comments allowed', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, ), 'comment_count' => array( - 'name' => 'Comment count', 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 1, - ), - 'comment_count_new' => array( - 'name' => 'New comment count', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, ), 'search_api_language' => array( - 'name' => 'Item language', 'type' => 'string', - 'boost' => '1.0', - 'indexed' => 1, ), 'search_api_fulltext' => array( - 'name' => 'Fulltext', 'type' => 'text', - 'boost' => '1.0', - 'indexed' => 1, ), 'body:value' => array( - 'name' => 'Body » Text', 'type' => 'text', - 'boost' => '1.0', - 'indexed' => 1, - ), - 'body:format' => array( - 'name' => 'Body » Text format', - 'type' => 'integer', - 'boost' => '1.0', - 'indexed' => 0, ), ), ), @@ -844,3 +722,71 @@ function search_api_update_7112() { ); db_change_field('search_api_index', 'options', 'options', $spec); } + +/** + * Removes superfluous data from the stored index options. + */ +function search_api_update_7113() { + $indexes = db_select('search_api_index', 'i') + ->fields('i') + ->execute(); + foreach ($indexes as $index) { + $options = unserialize($index->options); + // Weed out fields settings. + if (!empty($options['fields'])) { + foreach ($options['fields'] as $key => $field) { + if (isset($field['indexed']) && !$field['indexed']) { + unset($options['fields'][$key]); + continue; + } + unset($options['fields'][$key]['name'], $options['fields'][$key]['indexed']); + if (isset($field['boost']) && $field['boost'] == '1.0') { + unset($options['fields'][$key]['boost']); + } + } + } + // Weed out processor settings. + if (!empty($options['processors'])) { + // Only weed out settings for our own processors. + $processors = array('search_api_case_ignore', 'search_api_html_filter', 'search_api_tokenizer', 'search_api_stopwords'); + foreach ($processors as $key) { + if (empty($options['processors'][$key])) { + continue; + } + $processor = $options['processors'][$key]; + if (empty($processor['settings']['fields'])) { + continue; + } + $fields = array_filter($processor['settings']['fields']); + if ($fields) { + $fields = array_combine($fields, array_fill(0, count($fields), TRUE)); + } + $options['processors'][$key]['settings']['fields'] = $fields; + } + } + // Weed out settings for the „Aggregated fields“ data alteration. + if (!empty($options['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields'])) { + unset($options['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['actions']); + $aggregated_fields = &$options['data_alter_callbacks']['search_api_alter_add_aggregation']['settings']['fields']; + foreach ($aggregated_fields as $key => $field) { + unset($aggregated_fields[$key]['actions']); + if (!empty($field['fields'])) { + $aggregated_fields[$key]['fields'] = array_values(array_filter($field['fields'])); + } + } + } + $options = serialize($options); + if ($options != $index->options) { + // Mark the entity as overridden, in case it has been defined in code + // only. + $index->status |= 0x01; + db_update('search_api_index') + ->fields(array( + 'options' => $options, + 'status' => $index->status, + )) + ->condition('id', $index->id) + ->execute(); + } + } +} diff --git a/search_api.module b/search_api.module index 69d551d..ac14109 100644 --- a/search_api.module +++ b/search_api.module @@ -181,6 +181,7 @@ function search_api_theme() { 'enabled' => NULL, 'server' => NULL, 'options' => array(), + 'fields' => array(), 'indexed_items' => 0, 'total_items' => 0, 'status' => ENTITY_CUSTOM, @@ -1253,10 +1254,10 @@ function search_api_get_processors() { */ function search_api_search_api_query_alter(SearchApiQueryInterface $query) { $index = $query->getIndex(); - // Only add node access if the "search_api_access_node" field is indexed for - // the index, and unless disabled explicitly by the query. + // Only add node access if the necessary fields are indexed in the index, and + // unless disabled explicitly by the query. $fields = $index->options['fields']; - if (!empty($fields['search_api_access_node']['indexed']) && !empty($fields['status']['indexed']) && !$query->getOption('search_api_bypass_access')) { + if (!empty($fields['search_api_access_node']) && !empty($fields['status']) && !$query->getOption('search_api_bypass_access')) { $account = $query->getOption('search_api_access_account', $GLOBALS['user']); if (is_numeric($account)) { $account = user_load($account); diff --git a/search_api.test b/search_api.test index f0923ad..c8795d4 100644 --- a/search_api.test +++ b/search_api.test @@ -414,18 +414,12 @@ class SearchApiUnitTest extends DrupalWebTestCase { 'fields' => array( 'name' => array( 'type' => 'text', - 'boost' => 1.0, - 'indexed' => 1, ), 'mail' => array( 'type' => 'string', - 'boost' => 1.0, - 'indexed' => 1, ), 'search_api_language' => array( 'type' => 'string', - 'boost' => 1.0, - 'indexed' => 1, ), ), ),