updated search_api, search_api_solr_override, imce
This commit is contained in:
@@ -20,11 +20,23 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
*/
|
||||
protected $reductionType;
|
||||
|
||||
/**
|
||||
* A separator to use when the aggregation type is 'fulltext'.
|
||||
*
|
||||
* Used to temporarily store a string separator when the aggregation type is
|
||||
* "fulltext", for use in SearchApiAlterAddAggregation::reduce() with
|
||||
* array_reduce().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fulltextReductionSeparator;
|
||||
|
||||
public function configurationForm() {
|
||||
$form['#attached']['css'][] = drupal_get_path('module', 'search_api') . '/search_api.admin.css';
|
||||
|
||||
$fields = $this->index->getFields(FALSE);
|
||||
$field_options = array();
|
||||
$field_properties = array();
|
||||
foreach ($fields as $name => $field) {
|
||||
$field_options[$name] = check_plain($field['name']);
|
||||
$field_properties[$name] = array(
|
||||
@@ -79,9 +91,23 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['fields'][$name]['type_descriptions'] = $type_descriptions;
|
||||
$type_selector = ':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]';
|
||||
foreach (array_keys($types) as $type) {
|
||||
$form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]']['value'] = $type;
|
||||
$form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][$type_selector]['value'] = $type;
|
||||
}
|
||||
$form['fields'][$name]['separator'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Fulltext separator'),
|
||||
'#description' => t('For aggregation type "Fulltext", set the text that should be used to separate the aggregated field values. Use "\t" for tabs and "\n" for newline characters.'),
|
||||
'#default_value' => addcslashes(isset($field['separator']) ? $field['separator'] : "\n\n", "\0..\37\\"),
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
$type_selector => array(
|
||||
'value' => 'fulltext',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
$form['fields'][$name]['fields'] = array_merge($field_properties, array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Contained fields'),
|
||||
@@ -125,11 +151,12 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
return;
|
||||
}
|
||||
foreach ($values['fields'] as $name => $field) {
|
||||
$fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
|
||||
unset($values['fields'][$name]['actions']);
|
||||
$fields = $values['fields'][$name]['fields'] = array_values(array_filter($field['fields']));
|
||||
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.'));
|
||||
}
|
||||
$values['fields'][$name]['separator'] = stripcslashes($field['separator']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +203,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
$values = $this->flattenArray($values);
|
||||
|
||||
$this->reductionType = $field['type'];
|
||||
$this->fulltextReductionSeparator = isset($field['separator']) ? $field['separator'] : "\n\n";
|
||||
$item->$name = array_reduce($values, array($this, 'reduce'), NULL);
|
||||
if ($field['type'] == 'count' && !$item->$name) {
|
||||
$item->$name = 0;
|
||||
@@ -192,7 +220,7 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
public function reduce($a, $b) {
|
||||
switch ($this->reductionType) {
|
||||
case 'fulltext':
|
||||
return isset($a) ? $a . "\n\n" . $b : $b;
|
||||
return isset($a) ? $a . $this->fulltextReductionSeparator . $b : $b;
|
||||
case 'sum':
|
||||
return $a + $b;
|
||||
case 'count':
|
||||
@@ -300,10 +328,10 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
'count' => 'integer',
|
||||
'max' => 'integer',
|
||||
'min' => 'integer',
|
||||
'first' => 'string',
|
||||
'first_char' => 'string',
|
||||
'last' => 'string',
|
||||
'list' => 'list<string>',
|
||||
'first' => 'token',
|
||||
'first_char' => 'token',
|
||||
'last' => 'token',
|
||||
'list' => 'list<token>',
|
||||
);
|
||||
case 'description':
|
||||
return array(
|
||||
|
@@ -108,7 +108,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
$this->extractHierarchy($child, $prop, $values[$key]);
|
||||
}
|
||||
foreach ($values as $key => $value) {
|
||||
$item->$key = $value;
|
||||
$item->$key = array_values($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiAlterAddUserContent.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds the nodes created by the indexed user for indexing.
|
||||
*/
|
||||
class SearchApiAlterAddUserContent extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
return $index->getEntityType() === 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function propertyInfo() {
|
||||
return array(
|
||||
'search_api_user_content' => array(
|
||||
'label' => t('User content'),
|
||||
'description' => t('The nodes created by this user'),
|
||||
'type' => 'list<node>',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
$uids = array();
|
||||
foreach ($items as $item) {
|
||||
$uids[] = $item->uid;
|
||||
}
|
||||
|
||||
$sql = 'SELECT nid, uid FROM {node} WHERE uid IN (:uids)';
|
||||
$nids = db_query($sql, array(':uids' => $uids));
|
||||
$user_nodes = array();
|
||||
foreach ($nids as $row) {
|
||||
$user_nodes[$row->uid][] = $row->nid;
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->search_api_user_content = array();
|
||||
if (!empty($user_nodes[$item->uid])) {
|
||||
$item->search_api_user_content = $user_nodes[$item->uid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -315,99 +315,141 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
* @param array $keys
|
||||
* Search keywords entered by the user.
|
||||
*
|
||||
* @return string
|
||||
* A string containing HTML for the excerpt.
|
||||
* @return string|null
|
||||
* A string containing HTML for the excerpt, or NULL if none could be
|
||||
* created.
|
||||
*/
|
||||
protected function createExcerpt($text, array $keys) {
|
||||
// Prepare text by stripping HTML tags and decoding HTML entities.
|
||||
$text = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
|
||||
$text = ' ' . decode_entities($text);
|
||||
$text = decode_entities($text);
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
$text = trim($text, ' ');
|
||||
$text_length = strlen($text);
|
||||
|
||||
// Extract fragments around keywords.
|
||||
// First we collect ranges of text around each keyword, starting/ending
|
||||
// at spaces, trying to get to the requested length.
|
||||
// If the sum of all fragments is too short, we look for second occurrences.
|
||||
// Try to reach the requested excerpt length with about two fragments (each
|
||||
// with a keyword and some context).
|
||||
$ranges = array();
|
||||
$included = array();
|
||||
$length = 0;
|
||||
$work_keys = $keys;
|
||||
while ($length < $this->options['excerpt_length'] && $work_keys) {
|
||||
foreach ($work_keys as $k => $key) {
|
||||
if ($length >= $this->options['excerpt_length']) {
|
||||
$look_start = array();
|
||||
$remaining_keys = $keys;
|
||||
|
||||
// Get the set excerpt length from the configuration. If the length is too
|
||||
// small, only use one fragment.
|
||||
$excerpt_length = $this->options['excerpt_length'];
|
||||
$context_length = round($excerpt_length / 4) - 3;
|
||||
if ($context_length < 32) {
|
||||
$context_length = round($excerpt_length / 2) - 1;
|
||||
}
|
||||
|
||||
while ($length < $excerpt_length && !empty($remaining_keys)) {
|
||||
$found_keys = array();
|
||||
foreach ($remaining_keys as $key) {
|
||||
if ($length >= $excerpt_length) {
|
||||
break;
|
||||
}
|
||||
// Remember occurrence of key so we can skip over it if more occurrences
|
||||
// are desired.
|
||||
if (!isset($included[$key])) {
|
||||
$included[$key] = 0;
|
||||
|
||||
// Remember where we last found $key, in case we are coming through a
|
||||
// second time.
|
||||
if (!isset($look_start[$key])) {
|
||||
$look_start[$key] = 0;
|
||||
}
|
||||
// Locate a keyword (position $p, always >0 because $text starts with a
|
||||
// space).
|
||||
$p = 0;
|
||||
if (empty($this->options['highlight_partial'])) {
|
||||
$regex = '/' . self::$boundary . preg_quote($key, '/') . self::$boundary . '/iu';
|
||||
if (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
|
||||
$p = $match[0][1];
|
||||
|
||||
// See if we can find $key after where we found it the last time. Since
|
||||
// we are requiring a match on a word boundary, make sure $text starts
|
||||
// and ends with a space.
|
||||
$matches = array();
|
||||
|
||||
if (!$this->options['highlight_partial']) {
|
||||
$found_position = FALSE;
|
||||
$regex = '/' . static::$boundary . preg_quote($key, '/') . static::$boundary . '/iu';
|
||||
if (preg_match($regex, ' ' . $text . ' ', $matches, PREG_OFFSET_CAPTURE, $look_start[$key])) {
|
||||
$found_position = $matches[0][1];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$p = stripos($text, $key, $included[$key]);
|
||||
$found_position = stripos($text, $key, $look_start[$key]);
|
||||
}
|
||||
// Now locate a space in front (position $q) and behind it (position $s),
|
||||
// leaving about 60 characters extra before and after for context.
|
||||
// Note that a space was added to the front and end of $text above.
|
||||
if ($p) {
|
||||
if (($q = strpos(' ' . $text, ' ', max(0, $p - 61))) !== FALSE) {
|
||||
$end = substr($text . ' ', $p, 80);
|
||||
if (($s = strrpos($end, ' ')) !== FALSE) {
|
||||
// Account for the added spaces.
|
||||
$q = max($q - 1, 0);
|
||||
$s = min($s, strlen($end) - 1);
|
||||
$ranges[$q] = $p + $s;
|
||||
$length += $p + $s - $q;
|
||||
$included[$key] = $p + 1;
|
||||
continue;
|
||||
if ($found_position !== FALSE) {
|
||||
$look_start[$key] = $found_position + 1;
|
||||
// Keep track of which keys we found this time, in case we need to
|
||||
// pass through again to find more text.
|
||||
$found_keys[] = $key;
|
||||
|
||||
// Locate a space before and after this match, leaving some context on
|
||||
// each end.
|
||||
if ($found_position > $context_length) {
|
||||
$before = strpos($text, ' ', $found_position - $context_length);
|
||||
if ($before !== FALSE) {
|
||||
++$before;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$before = 0;
|
||||
}
|
||||
if ($before !== FALSE && $before <= $found_position) {
|
||||
if ($text_length > $found_position + $context_length) {
|
||||
$after = strrpos(substr($text, 0, $found_position + $context_length), ' ', $found_position);
|
||||
}
|
||||
else {
|
||||
$after = $text_length;
|
||||
}
|
||||
if ($after !== FALSE && $after > $found_position) {
|
||||
if ($before < $after) {
|
||||
// Save this range.
|
||||
$ranges[$before] = $after;
|
||||
$length += $after - $before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unless we got a match above, we don't need to look for this key any
|
||||
// more.
|
||||
unset($work_keys[$k]);
|
||||
}
|
||||
// Next time through this loop, only look for keys we found this time,
|
||||
// if any.
|
||||
$remaining_keys = $found_keys;
|
||||
}
|
||||
|
||||
if (count($ranges) == 0) {
|
||||
// We didn't find any keyword matches, so just return NULL.
|
||||
if (!$ranges) {
|
||||
// We didn't find any keyword matches, return NULL.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Sort the text ranges by starting position.
|
||||
ksort($ranges);
|
||||
|
||||
// Now we collapse overlapping text ranges into one. The sorting makes it O(n).
|
||||
// Collapse overlapping text ranges into one. The sorting makes it O(n).
|
||||
$newranges = array();
|
||||
$from1 = $to1 = NULL;
|
||||
foreach ($ranges as $from2 => $to2) {
|
||||
if (!isset($from1)) {
|
||||
if ($from1 === NULL) {
|
||||
// This is the first time through this loop: initialize.
|
||||
$from1 = $from2;
|
||||
$to1 = $to2;
|
||||
continue;
|
||||
}
|
||||
if ($from2 <= $to1) {
|
||||
// The ranges overlap: combine them.
|
||||
$to1 = max($to1, $to2);
|
||||
}
|
||||
else {
|
||||
// The ranges do not overlap: save the working range and start a new
|
||||
// one.
|
||||
$newranges[$from1] = $to1;
|
||||
$from1 = $from2;
|
||||
$to1 = $to2;
|
||||
}
|
||||
}
|
||||
// Save the remaining working range.
|
||||
$newranges[$from1] = $to1;
|
||||
|
||||
// Fetch text
|
||||
// Fetch text within the combined ranges we found.
|
||||
$out = array();
|
||||
foreach ($newranges as $from => $to) {
|
||||
$out[] = substr($text, $from, $to - $from);
|
||||
}
|
||||
if (!$out) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Let translators have the ... separator text as one chunk.
|
||||
$dots = explode('!excerpt', t('... !excerpt ... !excerpt ...'));
|
||||
|
@@ -24,12 +24,11 @@ class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
|
||||
$form = parent::configurationForm();
|
||||
|
||||
$args = array(
|
||||
'!algorithm' => url('https://github.com/markfullmer/porter2'),
|
||||
'!exclusions' => url('https://github.com/markfullmer/porter2#user-content-custom-exclusions'),
|
||||
'@algorithm' => url('http://snowball.tartarus.org/algorithms/english/stemmer.html'),
|
||||
);
|
||||
$form += array(
|
||||
'help' => array(
|
||||
'#markup' => '<p>' . t('Optionally, provide an exclusion list to override the stemmer algorithm. Read about the <a href="@algorithm">algorithm</a> and <a href="@exclusions">exclusions</a>.', $args) . '</p>',
|
||||
'#markup' => '<p>' . t('Optionally, provide an exclusion list to override the stemmer algorithm. (<a href="@algorithm">Read about the algorithm</a>.)', $args) . '</p>',
|
||||
),
|
||||
'exceptions' => array(
|
||||
'#type' => 'textarea',
|
||||
@@ -66,7 +65,7 @@ class SearchApiPorterStemmer extends SearchApiAbstractProcessor {
|
||||
$stemmed[] = $word;
|
||||
}
|
||||
}
|
||||
$value = implode('', $stemmed);
|
||||
$value = implode(' ', $stemmed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user