diff -u b/core/modules/search/search.module b/core/modules/search/search.module --- b/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -855,40 +855,61 @@ $words[] = array('', strlen($text)); $num_words = count($words); - // Find the smallest segment of complete words in $text that we can simplify - // to match $simplified_key. - $start_position = 0; - $word_end = 0; - for ($word_index = 0; $word_index < $num_words; $word_index++) { - // See if we can move the starting position out from our previously-saved - // best position to here and still have a match. - $trial_position = $words[$word_index][1]; - if ($trial_position < strlen($text)) { - $candidate = substr($text, $trial_position); - $test_text = trim(search_simplify($candidate, $langcode)); - if (strpos($test_text, $simplified_key) !== FALSE) { - $start_position = $trial_position; - $word_end = $trial_position + strlen($words[$word_index][0]); - continue; - } + // Using a binary search, find the earliest possible ending position in + // $text where it will still match the keyword after applying + // search_simplify(). + $start_index = 0; + $start_pos = $words[$start_index][1]; + $min_end_index = 1; + $max_end_index = count($words) - 1; + while ($max_end_index > $min_end_index) { + // Check the index half way between min and max. See if we ended there, + // if we would still have a match. + $proposed_end_index = floor(($max_end_index + $min_end_index) * 0.5); + $proposed_end_pos = $words[$proposed_end_index][1]; + // Since the split was done with preg_split(), the positions are byte counts + // not character counts, so use substr() not Unicode::substr() here. + $trial_text = trim(search_simplify(substr($text, $start_pos, $proposed_end_pos - $start_pos), $langcode)); + if (strpos($trial_text, $simplified_key) !== FALSE) { + // The proposed endpoint is fine, text still matches. + $max_end_index = $proposed_end_index; } - - // See if we can end at our currently-saved word-ending position and still - // match, in which case this is the minimal matching string. - if ($word_end > $start_position) { - $candidate = substr($text, $start_position, $word_end - $start_position); - $test_text = trim(search_simplify($candidate, $langcode)); - if (strpos($test_text, $simplified_key) !== FALSE) { - return $candidate; - } + else { + // The proposed endpoint is too early, so the earliest possible + // OK ending would be the next word. + $min_end_index = $proposed_end_index + 1; } + } - // Save the end position of this word for the next time through this loop. - $word_end = $trial_position + strlen($words[$word_index][0]); + // Now do the same for the starting position: using a binary search, find the + // latest possible starting position in $text where it will still match the + // keyword after applying search_simplify(). + $end_index = $min_end_index; + $end_pos = $words[$end_index][1]; + $min_start_index = 0; + $max_start_index = $end_index - 1; + while ($max_start_index > $min_start_index) { + // Check the index half way between min and max. See if we started there, + // if we would still have a match. + $proposed_start_index = ceil(($max_start_index + $min_start_index) * 0.5); + $proposed_start_pos = $words[$proposed_start_index][1]; + // Since the split was done with preg_split(), the positions are byte counts + // not character counts, so use substr() not Unicode::substr() here. + $trial_text = trim(search_simplify(substr($text, $proposed_start_pos, $end_pos - $proposed_start_pos), $langcode)); + if (strpos($trial_text, $simplified_key) !== FALSE) { + // The proposed start point is fine, text still matches. + $min_start_index = $proposed_start_index; + } + else { + // The proposed start point is too late, so the latest possible + // OK starting point would be the previous word. + $max_start_index = $proposed_start_index - 1; + } } + $start_index = $max_start_index; - // If we get here, we couldn't find a match. - return NULL; + // Return the matching text. + return trim(substr($text, $words[$start_index][1], $words[$end_index][1] - $words[$start_index][1])); } /**