updated contrib modules : pathauto, token, entity_api, search_api, redirect, features

This commit is contained in:
2018-09-12 15:05:44 +02:00
parent b8e8cc8cee
commit a4746432c7
281 changed files with 4839 additions and 2453 deletions
@@ -1,3 +1,62 @@
Search API 1.9 (2018-07-19):
----------------------------
- #2956012 by drunken monkey, DuaelFr: Added uncustomized view modes as options
when rendering items.
- #2980903 by drunken monkey, kristofferwiklund, borisson_: Fixed undefined
index notice in Views date filter.
- #2982693 by jhedstrom, borisson_, drunken monkey: Adapted tests to latest
Core changes.
- #2971033 by mErilainen, drunken monkey, borisson_: Added an option for prefix
matching to the database backend.
- #2973034 by idebr, drunken monkey: Removed outdated @todo comment.
- #2972510 by msankhala, drunken monkey: Replaced Unicode::* methods with mb_*
functions.
- #2973047 by idebr, drunken monkey: Fixed misnamed test config files.
- #2860167 by drunken monkey, borisson_, fenstrat: Added "orderby" to our
config schema definitions.
- #2948820 by capysara, drunken monkey, borisson_: Added a link to the "need to
reindex" message on the Filters tab.
- #2965803 by capysara, drunken monkey: Fixed left-over use of @url placeholder.
- #2957650 by drunken monkey, borisson_: Fixed two minor problems.
- #2939010 by nedjo, drunken monkey: Fixed deleted fields when index is created
during module install.
- #2931730 by drunken monkey, borisson_, chanderbhushan: Adapted to changes in
drupal_set_message().
- #2955607 by drunken monkey, borisson_: Removed our custom Query::__wakeup()
work-around.
- #2940138 by drunken monkey, borisson_: Switched to new Core tempstore service.
- #2936630 by drunken monkey, borisson_: Adapted override detection code to use
Config::hasOverrides().
- #2961537 by drunken monkey: Increased Core dependency to Drupal 8.5.
- #2959809 by drunken monkey, Rob230, mkalkbrenner, borisson_: Fixed small
coding mistake in the "URL field" processor.
- #2960931 by drunken monkey, borisson_: Fixed viewing of multiple content
entity items.
Search API 1.8 (2018-04-16):
----------------------------
- #2960854 by nedjo, drunken monkey: Fixed "Database search defaults" index.
- #2930839 by drunken monkey: Update tests according to latest PHPUnit
compatibility changes
- #2921022 by drunken monkey, marcvangend: Fixed automatic task execution with
Drush.
- #2955887 by drunken monkey, borisson_: Added a way to re-execute a query.
- #2925464 by drunken monkey, borisson_, .jch: Fixed problem with missing "NOT
NULL" attribute in database backend.
- #2953266 by drunken monkey, borisson_: Added a helper method for
"selected"/"default" checks.
- #2953267 by drunken monkey, borisson_: Fixed REST dependency of Views test
module.
- #2945870 by Dropa, drunken monkey: Fixed timezone of indexed date strings.
- #2948659 by drunken monkey, borisson_, bskibinski: Added support for HTML5
placeholder in Views exposed filters.
- #2951055 by bradjones1, borisson_, drunken monkey: Fixed documentation for
IndexInterface::addField().
- #2945560 by mkalkbrenner, drunken monkey, borisson_: Fixed handling of
existing highlighting data in Highlight processor.
- #2946386 by drunken monkey, borisson_: Fixed typo in Query::getAbortMessage().
- #2945675 by drunken monkey, borisson_: Unified use of self and static.
Search API 1.7 (2018-02-23):
----------------------------
- #2922525 by drunken monkey, borisson_: Changed "Index items immediately" to
@@ -12,6 +12,7 @@
selected:
type: sequence
label: 'The selected bundles'
orderby: value
sequence:
type: string
label: 'A bundle machine name'
@@ -25,6 +26,7 @@
selected:
type: sequence
label: 'The selected languages'
orderby: value
sequence:
type: string
label: 'A language code'
@@ -20,6 +20,7 @@ search_api.index.*:
field_settings:
type: sequence
label: 'Indexed fields'
orderby: key
sequence:
type: mapping
label: field
@@ -56,18 +57,21 @@ search_api.index.*:
datasource_settings:
type: sequence
label: 'Datasource settings'
orderby: key
sequence:
type: plugin.plugin_configuration.search_api_datasource.[%key]
label: 'The configuration for a single datasource'
processor_settings:
type: sequence
label: 'Processor settings'
orderby: key
sequence:
type: plugin.plugin_configuration.search_api_processor.[%key]
label: 'The configuration for a single processor'
tracker_settings:
type: sequence
label: 'Tracker settings'
orderby: key
sequence:
type: plugin.plugin_configuration.search_api_tracker.[%key]
label: 'The configuration for the tracker'
@@ -7,6 +7,7 @@ search_api.default_processor_configuration:
weights:
type: sequence
label: 'The processor''s weights for the different processing stages'
orderby: key
sequence:
type: integer
label: 'The processor''s weight for this stage'
@@ -21,6 +22,7 @@ search_api.fields_processor_configuration:
fields:
type: sequence
label: 'The selected fields'
orderby: value
sequence:
type: string
label: 'Selected field'
@@ -37,7 +39,8 @@ plugin.plugin_configuration.search_api_processor.hierarchy:
mapping:
fields:
type: sequence
label: 'Fields for which to add the hierarchy'
label: 'Fields for which to add the hierarchy, mapped to their hierarchical nested property'
orderby: key
sequence:
type: string
label: 'Field ID'
@@ -61,6 +64,7 @@ plugin.plugin_configuration.search_api_processor.highlight:
exclude_fields:
type: sequence
label: 'Fields excluded from excerpt'
orderby: value
sequence:
type: string
label: 'An excluded field''s ID'
@@ -84,6 +88,7 @@ plugin.plugin_configuration.search_api_processor.html_filter:
tags:
type: sequence
label: 'Tag boosts'
orderby: key
sequence:
type: integer
label: Boost
@@ -106,6 +111,7 @@ plugin.plugin_configuration.search_api_processor.ignore_character:
character_sets:
type: sequence
label: 'Configuration of the character sets'
orderby: key
sequence:
type: ignore
label: 'Character set'
@@ -117,6 +123,7 @@ plugin.plugin_configuration.search_api_processor.stemmer:
exceptions:
type: sequence
label: 'Stemming exceptions'
orderby: key
sequence:
type: string
label: Exception
@@ -131,6 +138,7 @@ plugin.plugin_configuration.search_api_processor.role_filter:
roles:
type: sequence
label: 'The selected roles'
orderby: value
sequence:
type: string
label: 'The role name'
@@ -141,7 +149,8 @@ plugin.plugin_configuration.search_api_processor.stopwords:
mapping:
stopwords:
type: sequence
label: 'entered stopwords'
label: 'Entered stopwords'
orderby: value
sequence:
type: string
label: Stopword
@@ -174,6 +183,7 @@ plugin.plugin_configuration.search_api_processor.type_boost:
boosts:
type: sequence
label: 'Boost settings'
orderby: key
sequence:
type: mapping
label: 'Datasource boost settings'
@@ -184,6 +194,7 @@ plugin.plugin_configuration.search_api_processor.type_boost:
bundle_boosts:
type: sequence
label: 'Bundle-specific boosts'
orderby: key
sequence:
type: float
label: 'The boost value for this bundle'
@@ -205,6 +216,7 @@ search_api.property_configuration.aggregated_field:
fields:
type: sequence
label: 'The properties to be aggregated'
orderby: value
sequence:
type: string
label: 'A property that should be part of the aggregation'
@@ -216,15 +228,18 @@ search_api.property_configuration.rendered_item:
roles:
type: sequence
label: 'The selected roles'
orderby: key
sequence:
type: string
label: 'The user roles which will be active when the entity is rendered'
view_mode:
type: sequence
label: 'The selected view modes for each datasource, by bundle'
orderby: key
sequence:
type: sequence
label: 'The selected view modes for the datasource, by bundle'
orderby: key
sequence:
type: string
label: 'The view mode used to render the entity for the specified bundle'
@@ -16,9 +16,11 @@ views.row.search_api:
view_modes:
type: sequence
label: View modes for each datasource
orderby: key
sequence:
type: sequence
label: A list of all the bundles and their configured view mode
orderby: key
sequence:
type: string
label: View mode for the specific bundle
@@ -70,6 +72,7 @@ views.argument.search_api_fulltext:
fields:
type: sequence
label: 'Fields'
orderby: value
sequence:
type: string
label: 'Field'
@@ -81,6 +84,7 @@ views.argument.search_api_more_like_this:
fields:
type: sequence
label: 'Fields'
orderby: value
sequence:
type: string
label: 'Field'
@@ -147,6 +151,7 @@ views.field.search_api_entity:
display_methods:
type: sequence
label: 'Display settings'
orderby: key
sequence:
type: mapping
label: 'Display settings for bundle'
@@ -216,9 +221,17 @@ views.filter.search_api_fulltext:
fields:
type: sequence
label: Fields to search on
orderby: value
sequence:
type: string
label: Field name
expose:
type: mapping
label: 'Exposed'
mapping:
placeholder:
type: label
label: 'Placeholder'
views.filter.search_api_language:
type: views.filter.language
@@ -269,6 +282,7 @@ views.filter_value.search_api_numeric:
views.filter_value.search_api_options:
type: sequence
orderby: value
sequence:
type: string
label: 'Value'
@@ -8,9 +8,9 @@ plugin.plugin_configuration.search_api_backend.search_api_db:
min_chars:
type: 'integer'
label: 'Minimum length of indexed words'
partial_matches:
type: 'boolean'
label: 'Whether to also search on parts of a word'
matching:
type: 'string'
label: 'The matching mode "words", "prefix" or "partial"'
autocomplete:
type: mapping
label: Autcomplete configuration
@@ -6,8 +6,8 @@ package: Search
dependencies:
- search_api:search_api
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -76,3 +76,34 @@ function search_api_db_update_8102() {
return t('Primary keys added to all denormalized index tables.');
}
/**
* Converts the old "partial_matches" option to the new "matching" option.
*/
function search_api_db_update_8103() {
// @see https://www.drupal.org/project/search_api/issues/2971033
$config_factory = \Drupal::configFactory();
$count = 0;
foreach ($config_factory->listAll('search_api.server.') as $server_id) {
$server = $config_factory->getEditable($server_id);
if ($server->get('backend') !== 'search_api_db') {
continue;
}
++$count;
$config = $server->get('backend_config') ?: [];
$config['matching'] = empty($config['partial_matches']) ? 'words' : 'partial';
unset($config['partial_matches']);
$server->set('backend_config', $config);
// Mark the resulting configuration as trusted data. This avoids issues
// with future schema changes.
$server->save(TRUE);
}
if ($count) {
return \Drupal::translation()
->formatPlural($count, 'Updated 1 server.', 'Updated @count servers.');
}
return NULL;
}
@@ -3,12 +3,33 @@ name: 'Default content index'
description: 'Default content index created by the Database Search Defaults module'
read_only: false
field_settings:
title:
label: Title
type: text
author:
label: 'Author name'
type: string
datasource_id: 'entity:node'
property_path: title
boost: 8
property_path: 'uid:entity:name'
changed:
label: Changed
type: date
datasource_id: 'entity:node'
property_path: changed
created:
label: 'Authored on'
type: date
datasource_id: 'entity:node'
property_path: created
field_tags:
label: Tags
type: integer
datasource_id: 'entity:node'
property_path: 'field_tags'
node_grants:
label: 'Node access information'
type: string
property_path: search_api_node_grants
indexed_locked: true
type_locked: true
hidden: true
rendered_item:
label: 'Rendered item'
type: text
@@ -20,57 +41,36 @@ field_settings:
'entity:node':
article: search_index
page: search_index
created:
label: 'Authored on'
type: date
datasource_id: 'entity:node'
property_path: created
changed:
label: Changed
type: date
datasource_id: 'entity:node'
property_path: changed
status:
label: 'Publishing status'
type: boolean
datasource_id: 'entity:node'
property_path: status
index_locked: true
indexed_locked: true
type_locked: true
sticky:
label: 'Sticky at top of lists'
type: boolean
datasource_id: 'entity:node'
property_path: sticky
field_tags:
label: Tags
type: integer
title:
label: Title
type: text
datasource_id: 'entity:node'
property_path: 'field_tags'
author:
label: 'Author name'
type: string
datasource_id: 'entity:node'
property_path: 'uid:entity:name'
uid:
label: 'Author ID'
type: integer
datasource_id: 'entity:node'
property_path: uid
index_locked: true
type_locked: true
node_grants:
label: 'Node access information'
type: string
property_path: search_api_node_grants
index_locked: true
type_locked: true
hidden: true
property_path: title
boost: 8
type:
label: 'Content type'
type: string
datasource_id: 'entity:node'
property_path: type
uid:
label: 'Author ID'
type: integer
datasource_id: 'entity:node'
property_path: uid
indexed_locked: true
type_locked: true
processor_settings:
add_url:
weights:
@@ -82,6 +82,9 @@ processor_settings:
weights:
preprocess_index: -6
preprocess_query: -4
entity_status:
weights:
preprocess_index: -10
html_filter:
weights:
preprocess_index: -3
@@ -103,9 +106,6 @@ processor_settings:
fields:
- rendered_item
- title
entity_status:
weights:
preprocess_index: -10
rendered_item:
weights:
add_properties: 0
@@ -171,8 +171,8 @@ processor_settings:
- rendered_item
- title
options:
index_directly: true
cron_limit: 50
index_directly: true
datasource_settings:
'entity:node': { }
tracker_settings:
@@ -6,7 +6,7 @@ backend: search_api_db
backend_config:
database: 'default:default'
min_chars: 3
partial_matches: false
matching: 'words'
autocomplete:
suggest_suffix: true
suggest_words: true
@@ -13,8 +13,8 @@ dependencies:
- drupal:views
- search_api:search_api_db
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -3,7 +3,6 @@
namespace Drupal\search_api_db\DatabaseCompatibility;
use Drupal\Component\Transliteration\TransliterationInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Connection;
/**
@@ -66,7 +65,7 @@ class GenericDatabase implements DatabaseCompatibilityHandlerInterface {
if ($type == 'text') {
return $value;
}
return Unicode::strtolower($this->transliterator->transliterate($value));
return mb_strtolower($this->transliterator->transliterate($value));
}
}
@@ -399,7 +399,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
return [
'database' => NULL,
'min_chars' => 1,
'partial_matches' => FALSE,
'matching' => 'words',
'autocomplete' => [
'suggest_suffix' => TRUE,
'suggest_words' => TRUE,
@@ -465,11 +465,15 @@ class Database extends BackendPluginBase implements PluginFormInterface {
'#default_value' => $this->configuration['min_chars'],
];
$form['partial_matches'] = [
'#type' => 'checkbox',
'#title' => $this->t('Search on parts of a word'),
'#description' => $this->t('Find keywords in parts of a word, too. (For example, find results with "database" when searching for "base"). <strong>Caution:</strong> This can make searches much slower on large sites!'),
'#default_value' => $this->configuration['partial_matches'],
$form['matching'] = [
'#type' => 'radios',
'#title' => $this->t('Partial matching'),
'#default_value' => $this->configuration['matching'],
'#options' => [
'words' => $this->t('Match whole words only'),
'partial' => $this->t('Match on parts of a word'),
'prefix' => $this->t('Match words starting with given keywords'),
],
];
if ($this->getModuleHandler()->moduleExists('search_api_autocomplete')) {
@@ -511,10 +515,17 @@ class Database extends BackendPluginBase implements PluginFormInterface {
'info' => $this->configuration['min_chars'],
];
}
$info[] = [
'label' => $this->t('Search on parts of a word'),
'info' => !empty($this->configuration['partial_matches']) ? $this->t('enabled') : $this->t('disabled'),
$labels = [
'words' => $this->t('Match whole words only'),
'partial' => $this->t('Match on parts of a word'),
'prefix' => $this->t('Match words starting with given keywords'),
];
$info[] = [
'label' => $this->t('Partial matching'),
'info' => $labels[$this->configuration['matching']],
];
if (!empty($this->configuration['autocomplete'])) {
$this->configuration['autocomplete'] += [
'suggest_suffix' => TRUE,
@@ -669,12 +680,12 @@ class Database extends BackendPluginBase implements PluginFormInterface {
// A DB prefix might further reduce the maximum length of the table name.
$max_bytes = 62;
if ($db_prefix = $this->database->tablePrefix()) {
// Use strlen() instead of Unicode::strlen() since we want to measure
// bytes, not characters.
// Use strlen() instead of mb_strlen() since we want to measure bytes, not
// characters.
$max_bytes -= strlen($db_prefix);
}
$base = $table = Unicode::truncateBytes($prefix . Unicode::strtolower(preg_replace('/[^a-z0-9]/i', '_', $name)), $max_bytes);
$base = $table = Unicode::truncateBytes($prefix . mb_strtolower(preg_replace('/[^a-z0-9]/i', '_', $name)), $max_bytes);
$i = 0;
while ($this->database->schema()->tableExists($table)) {
$suffix = '_' . ++$i;
@@ -703,7 +714,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
protected function findFreeColumn($table, $column) {
$maxbytes = 62;
$base = $name = Unicode::truncateBytes(Unicode::strtolower(preg_replace('/[^a-z0-9]/i', '_', $column)), $maxbytes);
$base = $name = Unicode::truncateBytes(mb_strtolower(preg_replace('/[^a-z0-9]/i', '_', $column)), $maxbytes);
// If the table does not exist yet, the initial name is not taken.
if ($this->database->schema()->tableExists($table)) {
$i = 0;
@@ -769,7 +780,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
$db_field += [
'description' => "The field's value for this item",
];
if ($new_table) {
if ($new_table || $type === 'field') {
$db_field['not null'] = TRUE;
}
$this->database->schema()->addField($db['table'], $column, $db_field);
@@ -924,8 +935,13 @@ class Database extends BackendPluginBase implements PluginFormInterface {
elseif ($this->sqlType($old_type) != $this->sqlType($new_type)) {
// There is a change in SQL type. We don't have to clear the index,
// since types can be converted.
$this->database->schema()->changeField($field['table'], 'value', 'value', $this->sqlType($new_type) + ['description' => "The field's value for this item"]);
$this->database->schema()->changeField($denormalized_table, $field['column'], $field['column'], $this->sqlType($new_type) + ['description' => "The field's value for this item"]);
$sql_spec = $this->sqlType($new_type);
$sql_spec += [
'description' => "The field's value for this item",
];
$this->database->schema()->changeField($denormalized_table, $field['column'], $field['column'], $sql_spec);
$sql_spec['not null'] = TRUE;
$this->database->schema()->changeField($field['table'], 'value', 'value', $sql_spec);
$reindex = TRUE;
}
elseif ($old_type == 'date' || $new_type == 'date') {
@@ -955,7 +971,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
}
$this->database->update($text_table)
->expression('score', $expression, $args)
->condition('field_name', self::getTextFieldName($field_id))
->condition('field_name', static::getTextFieldName($field_id))
->execute();
}
else {
@@ -1081,7 +1097,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
if ($this->getDataTypeHelper()->isTextType($field['type'])) {
// Remove data from the text table.
$this->database->delete($field['table'])
->condition('field_name', self::getTextFieldName($name))
->condition('field_name', static::getTextFieldName($name))
->execute();
}
elseif ($this->database->schema()->tableExists($field['table'])) {
@@ -1263,7 +1279,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
// Store the first 30 characters of the string as the denormalized
// value.
if (Unicode::strlen($denormalized_value) < 30) {
if (mb_strlen($denormalized_value) < 30) {
$denormalized_value .= $word . ' ';
}
@@ -1271,7 +1287,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
if (is_numeric($word)) {
$word = ltrim($word, '-0');
}
elseif (Unicode::strlen($word) < $this->configuration['min_chars']) {
elseif (mb_strlen($word) < $this->configuration['min_chars']) {
continue;
}
@@ -1295,12 +1311,13 @@ class Database extends BackendPluginBase implements PluginFormInterface {
$unique_tokens[$word_base_form]['score'] += $score;
}
}
$denormalized_values[$column] = Unicode::substr(trim($denormalized_value), 0, 30);
$denormalized_values[$column] = mb_substr(trim($denormalized_value), 0, 30);
if ($unique_tokens) {
$field_name = self::getTextFieldName($field_id);
$field_name = static::getTextFieldName($field_id);
$boost = $field_info['boost'];
foreach ($unique_tokens as $token) {
$score = round($token['score'] * $boost * self::SCORE_MULTIPLIER);
$score = $token['score'] * $boost * self::SCORE_MULTIPLIER;
$score = round($score);
// Take care that the score doesn't exceed the maximum value for
// the database column (2^32-1).
$score = min((int) $score, 4294967295);
@@ -1420,9 +1437,9 @@ class Database extends BackendPluginBase implements PluginFormInterface {
}
foreach (static::splitIntoWords($text) as $word) {
if ($word) {
if (Unicode::strlen($word) > 50) {
if (mb_strlen($word) > 50) {
$this->getLogger()->warning('An overlong word (more than 50 characters) was encountered while indexing: %word.<br />Since database search servers currently cannot index words of more than 50 characters, the word was truncated for indexing. If this should not be a single word, please make sure the "Tokenizer" processor is enabled and configured correctly for index %index.', ['%word' => $word, '%index' => $index->label()]);
$word = Unicode::substr($word, 0, 50);
$word = mb_substr($word, 0, 50);
}
$tokens[] = new TextToken($word);
}
@@ -1434,12 +1451,12 @@ class Database extends BackendPluginBase implements PluginFormInterface {
// Check for over-long tokens.
$score = $token->getBoost();
$word = $token->getText();
if (Unicode::strlen($word) > 50) {
if (mb_strlen($word) > 50) {
$new_tokens = [];
foreach (static::splitIntoWords($word) as $word) {
if (Unicode::strlen($word) > 50) {
if (mb_strlen($word) > 50) {
$this->getLogger()->warning('An overlong word (more than 50 characters) was encountered while indexing: %word.<br />Since database search servers currently cannot index words of more than 50 characters, the word was truncated for indexing. If this should not be a single word, please make sure the "Tokenizer" processor is enabled and configured correctly for index %index.', ['%word' => $word, '%index' => $index->label()]);
$word = Unicode::substr($word, 0, 50);
$word = mb_substr($word, 0, 50);
}
$new_tokens[] = new TextToken($word, $score);
}
@@ -1459,13 +1476,14 @@ class Database extends BackendPluginBase implements PluginFormInterface {
if ($original_type == 'date') {
return date('c', $value);
}
if (Unicode::strlen($value) > 255) {
$value = Unicode::substr($value, 0, 255);
if (mb_strlen($value) > 255) {
$value = mb_substr($value, 0, 255);
$this->getLogger()->warning('An overlong value (more than 255 characters) was encountered while indexing: %value.<br />Database search servers currently cannot index such values correctly the value was therefore trimmed to the allowed length.', ['%value' => $value]);
}
return $value;
case 'integer':
case 'date':
case 'duration':
case 'decimal':
return 0 + $value;
@@ -1473,12 +1491,6 @@ class Database extends BackendPluginBase implements PluginFormInterface {
case 'boolean':
return $value ? 1 : 0;
case 'date':
if (is_numeric($value) || !$value) {
return 0 + $value;
}
return strtotime($value);
default:
throw new SearchApiException("Unknown field type '$type'.");
}
@@ -1791,7 +1803,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
if (is_numeric($processed_keys)) {
return ltrim($processed_keys, '-0');
}
elseif (Unicode::strlen($processed_keys) < $this->configuration['min_chars']) {
elseif (mb_strlen($processed_keys) < $this->configuration['min_chars']) {
$this->ignored[$keys] = 1;
return NULL;
}
@@ -1886,8 +1898,9 @@ class Database extends BackendPluginBase implements PluginFormInterface {
$db_query = NULL;
$mul_words = FALSE;
$neg_nested = $neg && $conj == 'AND';
$match_parts = !empty($this->configuration['partial_matches']);
$match_parts = $this->configuration['matching'] !== 'words';
$keyword_hits = [];
$prefix_search = $this->configuration['matching'] === 'prefix';
foreach ($keys as $i => $key) {
if (!Element::child($i)) {
@@ -1944,7 +1957,9 @@ class Database extends BackendPluginBase implements PluginFormInterface {
}
foreach ($words as $i => $word) {
$db_or->condition('t.word', '%' . $this->database->escapeLike($word) . '%', 'LIKE');
$like = $this->database->escapeLike($word);
$like = $prefix_search ? "$like%" : "%$like%";
$db_or->condition('t.word', $like, 'LIKE');
// Add an expression for each keyword that shows whether the indexed
// word matches that particular keyword. That way we don't return a
@@ -2547,8 +2562,8 @@ class Database extends BackendPluginBase implements PluginFormInterface {
// Make the input lowercase as the indexed data is (usually) also all
// lowercase.
$incomplete_key = Unicode::strtolower($incomplete_key);
$user_input = Unicode::strtolower($user_input);
$incomplete_key = mb_strtolower($incomplete_key);
$user_input = mb_strtolower($user_input);
// Decide which methods we want to use.
if ($incomplete_key && $settings['suggest_suffix']) {
@@ -2578,12 +2593,12 @@ class Database extends BackendPluginBase implements PluginFormInterface {
$query->keys($user_input);
}
// To avoid suggesting incomplete words, we have to temporarily disable
// the "partial_matches" option. There should be no way we'll save the
// server during the createDbQuery() call, so this should be safe.
// partial matching. There should be no way we'll save the server during
// the createDbQuery() call, so this should be safe.
$configuration = $this->configuration;
$db_query = NULL;
try {
$this->configuration['partial_matches'] = FALSE;
$this->configuration['matching'] = 'words';
$db_query = $this->createDbQuery($query, $fields);
$this->configuration = $configuration;
@@ -8,8 +8,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -4,6 +4,7 @@ namespace Drupal\Tests\search_api_db\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Database\Database as CoreDatabase;
use Drupal\search_api\Entity\Index;
use Drupal\search_api\Entity\Server;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Item\ItemInterface;
@@ -73,6 +74,8 @@ class BackendTest extends BackendTestBase {
$this->checkMultiValuedInfo();
$this->editServerPartial();
$this->searchSuccessPartial();
$this->editServerStartsWith();
$this->searchSuccessStartsWith();
$this->editServerMinChars();
$this->searchSuccessMinChars();
$this->checkUnknownOperator();
@@ -89,6 +92,7 @@ class BackendTest extends BackendTestBase {
$this->regressionTest2846932();
$this->regressionTest2926733();
$this->regressionTest2938646();
$this->regressionTest2925464();
}
/**
@@ -205,14 +209,11 @@ class BackendTest extends BackendTestBase {
/**
* Edits the server to enable partial matches.
*
* @param bool $enable
* (optional) Whether partial matching should be enabled or disabled.
*/
protected function editServerPartial($enable = TRUE) {
protected function editServerPartial() {
$server = $this->getServer();
$backend_config = $server->getBackendConfig();
$backend_config['partial_matches'] = $enable;
$backend_config['matching'] = 'partial';
$server->setBackendConfig($backend_config);
$this->assertTrue((bool) $server->save(), 'The server was successfully edited.');
$this->resetEntityCache();
@@ -241,7 +242,7 @@ class BackendTest extends BackendTestBase {
$this->assertResults([], $results, 'Partial search for »foo nonexistent«');
$results = $this->buildSearch('bar nonexistent')->execute();
$this->assertResults([], $results, 'Partial search for »foo nonexistent«');
$this->assertResults([], $results, 'Partial search for »bar nonexistent«');
$keys = [
'#conjunction' => 'AND',
@@ -269,6 +270,69 @@ class BackendTest extends BackendTestBase {
$this->assertResults([1, 2, 3, 4], $results, 'Partial search with multi-field fulltext filter');
}
/**
* Edits the server to enable prefix matching.
*/
protected function editServerStartsWith() {
$server = $this->getServer();
$backend_config = $server->getBackendConfig();
$backend_config['matching'] = 'prefix';
$server->setBackendConfig($backend_config);
$this->assertTrue((bool) $server->save(), 'The server was successfully edited.');
$this->resetEntityCache();
}
/**
* Tests whether prefix matching works.
*/
protected function searchSuccessStartsWith() {
$results = $this->buildSearch('foobaz')->range(0, 1)->execute();
$this->assertResults([1], $results, 'Prefix search for »foobaz«');
$results = $this->buildSearch('foo', [], [], FALSE)
->sort('search_api_relevance', QueryInterface::SORT_DESC)
->sort('id')
->execute();
$this->assertResults([1, 2, 4, 3, 5], $results, 'Prefix search for »foo«');
$results = $this->buildSearch('foo tes')->execute();
$this->assertResults([1, 2, 3, 4], $results, 'Prefix search for »foo tes«');
$results = $this->buildSearch('oob est')->execute();
$this->assertResults([], $results, 'Prefix search for »oob est«');
$results = $this->buildSearch('foo nonexistent')->execute();
$this->assertResults([], $results, 'Prefix search for »foo nonexistent«');
$results = $this->buildSearch('bar nonexistent')->execute();
$this->assertResults([], $results, 'Prefix search for »bar nonexistent«');
$keys = [
'#conjunction' => 'AND',
'foob',
[
'#conjunction' => 'OR',
'tes',
'nonexistent',
],
];
$results = $this->buildSearch($keys)->execute();
$this->assertResults([1, 2, 3], $results, 'Prefix search for complex keys');
$results = $this->buildSearch('foo', ['category,item_category'], [], FALSE)
->sort('id', QueryInterface::SORT_DESC)
->execute();
$this->assertResults([2, 1], $results, 'Prefix search for »foo« with additional filter');
$query = $this->buildSearch();
$conditions = $query->createConditionGroup('OR');
$conditions->addCondition('name', 'test');
$conditions->addCondition('body', 'test');
$query->addConditionGroup($conditions);
$results = $query->execute();
$this->assertResults([1, 2, 3, 4], $results, 'Prefix search with multi-field fulltext filter');
}
/**
* Edits the server to change the "Minimum word length" setting.
*/
@@ -276,7 +340,7 @@ class BackendTest extends BackendTestBase {
$server = $this->getServer();
$backend_config = $server->getBackendConfig();
$backend_config['min_chars'] = 4;
$backend_config['partial_matches'] = FALSE;
$backend_config['matching'] = 'words';
$server->setBackendConfig($backend_config);
$success = (bool) $server->save();
$this->assertTrue($success, 'The server was successfully edited.');
@@ -556,6 +620,23 @@ class BackendTest extends BackendTestBase {
$this->assertEquals(2 * $old_score, $new_score);
}
/**
* Tests changing of field types.
*
* @see https://www.drupal.org/node/2925464
*/
protected function regressionTest2925464() {
$index = $this->getIndex();
$index->getField('category')->setType('integer');
$index->save();
$index->getField('category')->setType('string');
$index->save();
$this->indexItems($index->id());
}
/**
* {@inheritdoc}
*/
@@ -750,4 +831,59 @@ class BackendTest extends BackendTestBase {
$this->assertEquals($class, get_class($dbms_comp2));
}
/**
* Tests whether indexing of dates works correctly.
*/
public function testDateIndexing() {
// Load all existing entities.
$storage = \Drupal::entityTypeManager()
->getStorage('entity_test_mulrev_changed');
$storage->delete($storage->loadMultiple());
$index = Index::load('database_search_index');
$index->getField('name')->setType('date');
$index->save();
// Simulate date field creation in one timezone and indexing in another.
date_default_timezone_set('America/Chicago');
// Test different input values, similar to @dataProvider (but with less
// overhead).
$t = 1400000000;
$f = 'Y-m-d H:i:s';
$test_values = [
'null' => [NULL, NULL],
'timestamp' => [$t, $t],
'string timestamp' => ["$t", $t],
'float timestamp' => [$t + 0.12, $t],
'date string' => [gmdate($f, $t), $t],
'date string with timezone' => [date($f . 'P', $t), $t],
];
// Get storage information for quickly checking the indexed value.
$db_info = $this->getIndexDbInfo();
$table = $db_info['index_table'];
$column = $db_info['field_tables']['name']['column'];
$sql = "SELECT $column FROM {{$table}} WHERE item_id = :id";
$id = 0;
date_default_timezone_set('Asia/Seoul');
foreach ($test_values as $label => list($field_value, $expected)) {
$entity = $this->addTestEntity(++$id, [
'name' => $field_value,
'type' => 'item',
]);
$item_id = $this->getItemIds([$id])[0];
$index->indexSpecificItems([$item_id => $entity->getTypedData()]);
$args[':id'] = $item_id;
$indexed_value = \Drupal::database()->query($sql, $args)->fetchField();
if ($expected === NULL) {
$this->assertSame($expected, $indexed_value, "Indexing of date field with $label value.");
}
else {
$this->assertEquals($expected, $indexed_value, "Indexing of date field with $label value.");
}
}
}
}
@@ -11,8 +11,8 @@ dependencies:
- drupal:taxonomy
- drupal:views
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -6,7 +6,7 @@
<!-- Only include specific sniffs that pass. This ensures that, if new sniffs are added, HEAD does not fail.-->
<!-- Drupal sniffs -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Drupal.Array.DisallowLongArraySyntax"/>
<rule ref="Drupal.Classes.ClassCreateInstance"/>
<rule ref="Drupal.Classes.ClassDeclaration"/>
<rule ref="Drupal.Classes.FullyQualifiedNamespace"/>
@@ -241,7 +241,8 @@ function hook_search_api_index_items_alter(\Drupal\search_api\IndexInterface $in
'%index' => $index->label(),
'@ids' => implode(', ', array_keys($items)),
];
drupal_set_message(t('Indexing items on index %index with the following IDs: @ids', $arguments));
$message = t('Indexing items on index %index with the following IDs: @ids', $arguments);
\Drupal::messenger()->addStatus($message);
}
/**
@@ -257,7 +258,8 @@ function hook_search_api_items_indexed(\Drupal\search_api\IndexInterface $index,
// Note that this is just an example, and would only work if there are only
// nodes indexed in that index (and even then the printed IDs would probably
// not be as expected).
drupal_set_message(t('Nodes indexed: @ids.', implode(', ', $item_ids)));
$message = t('Nodes indexed: @ids.', implode(', ', $item_ids));
\Drupal::messenger()->addStatus($message);
}
}
@@ -5,10 +5,10 @@ package: Search
# core: 8.x
configure: search_api.overview
dependencies:
- system (>=8.4)
- system (>=8.5)
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -572,7 +572,8 @@ function search_api_view_insert(ViewEntityInterface $view) {
}
if ($change) {
drupal_set_message(\Drupal::translation()->translate('The selected caching mechanism does not work with views on Search API indexes. Please either use one of the Search API-specific caching options or "None". Caching was turned off for this view.'), 'warning');
$warning = t('The selected caching mechanism does not work with views on Search API indexes. Please either use one of the Search API-specific caching options or "None". Caching was turned off for this view.');
\Drupal::messenger()->addWarning($warning);
$view->set('display', $displays);
$view->save();
}
@@ -11,7 +11,7 @@ services:
paramconverter.search_api:
class: Drupal\search_api\ParamConverter\SearchApiConverter
arguments: ['@entity.manager', '@user.shared_tempstore', '@current_user']
arguments: ['@entity.manager', '@tempstore.shared', '@current_user']
tags:
- { name: paramconverter, priority: 10 }
lazy: true
@@ -72,7 +72,7 @@ services:
class: Drupal\search_api\Utility\PostRequestIndexing
arguments: ['@entity_type.manager']
tags:
- { name: event_subscriber }
- { name: needs_destruction }
search_api.query_helper:
class: Drupal\search_api\Utility\QueryHelper
@@ -86,7 +86,7 @@ services:
search_api.task_manager:
class: Drupal\search_api\Task\TaskManager
arguments: ['@entity_type.manager', '@event_dispatcher', '@string_translation']
arguments: ['@entity_type.manager', '@event_dispatcher', '@string_translation', '@messenger']
search_api.vbo_view_data_provider:
class: Drupal\search_api\Contrib\ViewsBulkOperationsEventSubscriber
@@ -2,6 +2,7 @@
namespace Drupal\search_api\Backend;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\search_api\Entity\Server;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\LoggerTrait;
@@ -11,6 +12,7 @@ use Drupal\search_api\IndexInterface;
use Drupal\search_api\Plugin\ConfigurablePluginBase;
use Drupal\search_api\ServerInterface;
use Drupal\search_api\Utility\FieldsHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a base class for backend plugins.
@@ -66,6 +68,13 @@ abstract class BackendPluginBase extends ConfigurablePluginBase implements Backe
*/
protected $fieldsHelper;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface|null
*/
protected $messenger;
/**
* {@inheritdoc}
*/
@@ -77,6 +86,19 @@ abstract class BackendPluginBase extends ConfigurablePluginBase implements Backe
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
/** @var static $plugin */
$plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$plugin->setFieldsHelper($container->get('search_api.fields_helper'));
$plugin->setMessenger($container->get('messenger'));
return $plugin;
}
/**
* Retrieves the fields helper.
*
@@ -100,6 +122,29 @@ abstract class BackendPluginBase extends ConfigurablePluginBase implements Backe
return $this;
}
/**
* Retrieves the messenger.
*
* @return \Drupal\Core\Messenger\MessengerInterface
* The messenger.
*/
public function getMessenger() {
return $this->messenger ?: \Drupal::service('messenger');
}
/**
* Sets the messenger.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The new messenger.
*
* @return $this
*/
public function setMessenger(MessengerInterface $messenger) {
$this->messenger = $messenger;
return $this;
}
/**
* {@inheritdoc}
*/
@@ -172,7 +217,7 @@ abstract class BackendPluginBase extends ConfigurablePluginBase implements Backe
'%server' => $this->getServer()->label(),
];
$this->logException($e, '%type while deleting items from server %server: @message in %function (line %line of %file).', $vars);
drupal_set_message($this->t('Deleting some of the items on the server failed. Check the logs for details. The server was still removed.'), 'error');
$this->getMessenger()->addError($this->t('Deleting some of the items on the server failed. Check the logs for details. The server was still removed.'));
}
}
@@ -127,7 +127,7 @@ interface BackendSpecificInterface {
* Notifies the server that an index attached to it has been changed.
*
* If any user action is necessary as a result of this, the method should
* use drupal_set_message() to notify the user.
* set a message to notify the user.
*
* @param \Drupal\search_api\IndexInterface $index
* The updated index.
@@ -35,7 +35,7 @@ class RowsOfMultiValueFields extends RowsOfFields implements RenderCellInterface
protected static function arrayToString(array $array) {
$elements = [];
foreach ($array as $element) {
$elements[] = is_array($element) ? '"' . self::arrayToString($element) . '"' : $element;
$elements[] = is_array($element) ? '"' . static::arrayToString($element) . '"' : $element;
}
return implode(',', $elements);
}
@@ -7,6 +7,7 @@ use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\SearchApiException;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -25,6 +26,13 @@ class IndexController extends ControllerBase {
*/
protected $requestStack;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface|null
*/
protected $messenger;
/**
* {@inheritdoc}
*/
@@ -33,6 +41,7 @@ class IndexController extends ControllerBase {
$controller = parent::create($container);
$controller->setRequestStack($container->get('request_stack'));
$controller->setMessenger($container->get('messenger'));
return $controller;
}
@@ -70,6 +79,29 @@ class IndexController extends ControllerBase {
return $this;
}
/**
* Retrieves the messenger.
*
* @return \Drupal\Core\Messenger\MessengerInterface
* The messenger.
*/
public function getMessenger() {
return $this->messenger ?: \Drupal::service('messenger');
}
/**
* Sets the messenger.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The new messenger.
*
* @return $this
*/
public function setMessenger(MessengerInterface $messenger) {
$this->messenger = $messenger;
return $this;
}
/**
* Displays information about a search index.
*
@@ -125,11 +157,11 @@ class IndexController extends ControllerBase {
// enabled if its server is not set or disabled.
if ($search_api_index->status()) {
// Notify the user about the status change.
drupal_set_message($this->t('The search index %name has been enabled.', ['%name' => $search_api_index->label()]));
$this->getMessenger()->addStatus($this->t('The search index %name has been enabled.', ['%name' => $search_api_index->label()]));
}
else {
// Notify the user that the status change did not succeed.
drupal_set_message($this->t('The search index %name could not be enabled. Check if its server is set and enabled.', ['%name' => $search_api_index->label()]));
$this->getMessenger()->addStatus($this->t('The search index %name could not be enabled. Check if its server is set and enabled.', ['%name' => $search_api_index->label()]));
}
// Redirect to the index's "View" page.
@@ -162,7 +194,7 @@ class IndexController extends ControllerBase {
}
catch (SearchApiException $e) {
$args['%field'] = $fields[$field_id]->getLabel();
drupal_set_message($this->t('The field %field is locked and cannot be removed.', $args), 'error');
$this->getMessenger()->addError($this->t('The field %field is locked and cannot be removed.', $args));
}
}
else {
@@ -4,13 +4,57 @@ namespace Drupal\search_api\Controller;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\search_api\ServerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides block routines for search server-specific routes.
*/
class ServerController extends ControllerBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface|null
*/
protected $messenger;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
/** @var static $controller */
$controller = parent::create($container);
$controller->setMessenger($container->get('messenger'));
return $controller;
}
/**
* Retrieves the messenger.
*
* @return \Drupal\Core\Messenger\MessengerInterface
* The messenger.
*/
public function getMessenger() {
return $this->messenger ?: \Drupal::service('messenger');
}
/**
* Sets the messenger.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The new messenger.
*
* @return $this
*/
public function setMessenger(MessengerInterface $messenger) {
$this->messenger = $messenger;
return $this;
}
/**
* Displays information about a search server.
*
@@ -65,7 +109,7 @@ class ServerController extends ControllerBase {
$search_api_server->setStatus(TRUE)->save();
// Notify the user about the status change.
drupal_set_message($this->t('The search server %name has been enabled.', ['%name' => $search_api_server->label()]));
$this->getMessenger()->addStatus($this->t('The search server %name has been enabled.', ['%name' => $search_api_server->label()]));
// Redirect to the server's "View" page.
$url = $search_api_server->toUrl('canonical');
@@ -18,7 +18,7 @@ use Drupal\search_api\SearchApiException;
use Drupal\search_api\ServerInterface;
use Drupal\search_api\Tracker\TrackerInterface;
use Drupal\search_api\Utility\Utility;
use Drupal\user\TempStoreException;
use Drupal\Core\TempStore\TempStoreException;
use Drupal\views\Views;
/**
@@ -89,6 +89,7 @@ use Drupal\views\Views;
*/
class Index extends ConfigEntityBase implements IndexInterface {
use InstallingTrait;
use LoggerTrait;
/**
@@ -914,15 +915,19 @@ class Index extends ConfigEntityBase implements IndexInterface {
if ($this->hasValidTracker() && !$this->isReadOnly()) {
$tracker = $this->getTrackerInstance();
$next_set = $tracker->getRemainingItems($limit, $datasource_id);
if (!$next_set) {
return 0;
}
$items = $this->loadItemsMultiple($next_set);
if ($items) {
try {
return count($this->indexSpecificItems($items));
}
catch (SearchApiException $e) {
$variables['%index'] = $this->label();
$this->logException($e, '%type while trying to index items on index %index: @message in %function (line %line of %file)', $variables);
}
if (!$items) {
return 0;
}
try {
return count($this->indexSpecificItems($items));
}
catch (SearchApiException $e) {
$variables['%index'] = $this->label();
$this->logException($e, '%type while trying to index items on index %index: @message in %function (line %line of %file)', $variables);
}
}
return 0;
@@ -1197,9 +1202,10 @@ class Index extends ConfigEntityBase implements IndexInterface {
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
// If we are in the process of syncing, we shouldn't change any entity
// If we are in the process of syncing, or in the process of installing
// configuration from an extension, we shouldn't change any entity
// properties (or other configuration).
if ($this->isSyncing()) {
if ($this->isSyncing() || $this->isInstallingFromExtension()) {
parent::preSave($storage);
return;
}
@@ -1559,8 +1565,8 @@ class Index extends ConfigEntityBase implements IndexInterface {
\Drupal::cache('discovery')->delete('views:wizard');
}
/** @var \Drupal\user\SharedTempStore $temp_store */
$temp_store = \Drupal::service('user.shared_tempstore')->get('search_api_index');
/** @var \Drupal\Core\TempStore\SharedTempStore $temp_store */
$temp_store = \Drupal::service('tempstore.shared')->get('search_api_index');
foreach ($entities as $entity) {
try {
$temp_store->delete($entity->id());
@@ -0,0 +1,38 @@
<?php
namespace Drupal\search_api\Entity;
/**
* Provides a helper method for checking a saved config entity's source.
*/
trait InstallingTrait {
/**
* Determines if this config entity is being installed from an extension.
*
* @return bool
* TRUE if the item is being installed from an extension; FALSE otherwise.
*/
protected function isInstallingFromExtension() {
// A configuration item is being installed from an extension if both of the
// following are true:
// - It is marked as new.
// - It has the _core property set.
// @see \Drupal\core\Config\ConfigInstaller::installConfiguration().
return ($this->isNew() && !empty($this->_core));
}
/**
* Determines whether the entity is new.
*
* Usually an entity is new if no ID exists for it yet. However, entities may
* be enforced to be new with existing IDs too.
*
* @return bool
* TRUE if the entity is new, or FALSE if the entity has already been saved.
*
* @see \Drupal\Core\Entity\EntityInterface::isNew()
*/
abstract public function isNew();
}
@@ -62,6 +62,7 @@ use Drupal\search_api\Utility\Utility;
*/
class Server extends ConfigEntityBase implements ServerInterface {
use InstallingTrait;
use LoggerTrait;
/**
@@ -500,6 +501,7 @@ class Server extends ConfigEntityBase implements ServerInterface {
// If the server is being disabled, also disable all its indexes.
if (!$this->isSyncing()
&& !$this->isInstallingFromExtension()
&& !isset($overrides['status'])
&& !$this->status()
&& $this->original->status()) {
@@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\search_api\Processor\ConfigurablePropertyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -44,11 +45,11 @@ class FieldConfigurationForm extends EntityForm {
protected $formIdAttribute;
/**
* {@inheritdoc}
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
public function getFormId() {
return 'search_api_field_config';
}
protected $messenger;
/**
* Constructs a FieldConfigurationForm object.
@@ -61,12 +62,15 @@ class FieldConfigurationForm extends EntityForm {
* The date formatter.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, DateFormatterInterface $date_formatter, RequestStack $request_stack) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, DateFormatterInterface $date_formatter, RequestStack $request_stack, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->renderer = $renderer;
$this->dateFormatter = $date_formatter;
$this->requestStack = $request_stack;
$this->messenger = $messenger;
}
/**
@@ -77,8 +81,23 @@ class FieldConfigurationForm extends EntityForm {
$renderer = $container->get('renderer');
$date_formatter = $container->get('date.formatter');
$request_stack = $container->get('request_stack');
$messenger = $container->get('messenger');
return new static($entity_type_manager, $renderer, $date_formatter, $request_stack);
return new static($entity_type_manager, $renderer, $date_formatter, $request_stack, $messenger);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'search_api_field_config';
}
/**
@@ -179,7 +198,7 @@ class FieldConfigurationForm extends EntityForm {
$property = $field->getDataDefinition();
$property->submitConfigurationForm($field, $form, $form_state);
drupal_set_message($this->t('The field configuration was successfully saved.'));
$this->messenger->addStatus($this->t('The field configuration was successfully saved.'));
if ($this->getRequest()->query->get('modal_redirect')) {
$url = $this->entity->toUrl('add-fields-ajax')
->setOption('query', [
@@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
@@ -75,18 +76,11 @@ class IndexAddFieldsForm extends EntityForm {
protected $formIdAttribute;
/**
* {@inheritdoc}
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
public function getFormId() {
return 'search_api_index_add_fields';
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return NULL;
}
protected $messenger;
/**
* Constructs an IndexAddFieldsForm object.
@@ -101,15 +95,18 @@ class IndexAddFieldsForm extends EntityForm {
* The renderer to use.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
* @param array $parameters
* The parameters for this page request.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldsHelperInterface $fields_helper, DataTypeHelperInterface $data_type_helper, RendererInterface $renderer, DateFormatterInterface $date_formatter, array $parameters) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldsHelperInterface $fields_helper, DataTypeHelperInterface $data_type_helper, RendererInterface $renderer, DateFormatterInterface $date_formatter, MessengerInterface $messenger, array $parameters) {
$this->entityTypeManager = $entity_type_manager;
$this->fieldsHelper = $fields_helper;
$this->dataTypeHelper = $data_type_helper;
$this->renderer = $renderer;
$this->dateFormatter = $date_formatter;
$this->messenger = $messenger;
$this->parameters = $parameters;
}
@@ -123,9 +120,24 @@ class IndexAddFieldsForm extends EntityForm {
$renderer = $container->get('renderer');
$date_formatter = $container->get('date.formatter');
$request_stack = $container->get('request_stack');
$messenger = $container->get('messenger');
$parameters = $request_stack->getCurrentRequest()->query->all();
return new static($entity_type_manager, $fields_helper, $data_type_helper, $renderer, $date_formatter, $parameters);
return new static($entity_type_manager, $fields_helper, $data_type_helper, $renderer, $date_formatter, $messenger, $parameters);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'search_api_index_add_fields';
}
/**
@@ -480,7 +492,7 @@ class IndexAddFieldsForm extends EntityForm {
}
$args['%label'] = $field->getLabel();
drupal_set_message($this->t('Field %label was added to the index.', $args));
$this->messenger->addStatus($this->t('Field %label was added to the index.', $args));
}
}
@@ -5,8 +5,9 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\user\SharedTempStoreFactory;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -14,10 +15,17 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class IndexBreakLockForm extends EntityConfirmFormBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The shared temporary storage for unsaved search indexes.
*
* @var \Drupal\user\SharedTempStore
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
@@ -38,28 +46,32 @@ class IndexBreakLockForm extends EntityConfirmFormBase {
/**
* Constructs an IndexBreakLockForm object.
*
* @param \Drupal\user\SharedTempStoreFactory $temp_store_factory
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The factory for shared temporary storages.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The Entity manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer to use.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer) {
public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, MessengerInterface $messenger) {
$this->tempStore = $temp_store_factory->get('search_api_index');
$this->entityTypeManager = $entity_type_manager;
$this->renderer = $renderer;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$temp_store_factory = $container->get('user.shared_tempstore');
$temp_store_factory = $container->get('tempstore.shared');
$entity_type_manager = $container->get('entity_type.manager');
$renderer = $container->get('renderer');
$messenger = $container->get('messenger');
return new static($temp_store_factory, $entity_type_manager, $renderer);
return new static($temp_store_factory, $entity_type_manager, $renderer, $messenger);
}
/**
@@ -120,7 +132,7 @@ class IndexBreakLockForm extends EntityConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->tempStore->delete($this->entity->id());
$form_state->setRedirectUrl($this->entity->toUrl('fields'));
drupal_set_message($this->t('The lock has been broken. You may now edit this search index.'));
$this->messenger->addStatus($this->t('The lock has been broken. You may now edit this search index.'));
}
}
@@ -7,12 +7,24 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Drupal\search_api\SearchApiException;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for clearing an index.
*/
class IndexClearConfirmForm extends EntityConfirmFormBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$form = parent::create($container);
$form->setMessenger($container->get('messenger'));
return $form;
}
/**
* {@inheritdoc}
*/
@@ -43,10 +55,11 @@ class IndexClearConfirmForm extends EntityConfirmFormBase {
try {
$index->clear();
drupal_set_message($this->t('All items were successfully deleted from search index %name.', ['%name' => $index->label()]));
$this->messenger()
->addStatus($this->t('All items were successfully deleted from search index %name.', ['%name' => $index->label()]));
}
catch (SearchApiException $e) {
drupal_set_message($this->t('Failed to clear the search index %name.', ['%name' => $index->label()]), 'error');
$this->messenger->addError($this->t('Failed to clear the search index %name.', ['%name' => $index->label()]));
$message = '%type while trying to clear the index %name: @message in %function (line %line of %file)';
$variables = [
'%name' => $index->label(),
@@ -4,13 +4,41 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for deleting an index.
*/
class IndexDeleteConfirmForm extends EntityDeleteForm {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs an IndexDeleteConfirmForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* {@inheritdoc}
*/
@@ -37,7 +65,7 @@ class IndexDeleteConfirmForm extends EntityDeleteForm {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->delete();
drupal_set_message($this->t('The search index %name has been removed.', ['%name' => $this->entity->label()]));
$this->messenger->addStatus($this->t('The search index %name has been removed.', ['%name' => $this->entity->label()]));
$form_state->setRedirect('search_api.overview');
}
@@ -4,13 +4,41 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for disabling an index.
*/
class IndexDisableConfirmForm extends EntityConfirmFormBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs an IndexDisableConfirmForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* {@inheritdoc}
*/
@@ -47,7 +75,7 @@ class IndexDisableConfirmForm extends EntityConfirmFormBase {
$entity = $this->entity;
$entity->setStatus(FALSE)->save();
drupal_set_message($this->t('The search index %name has been disabled.', ['%name' => $this->entity->label()]));
$this->messenger->addStatus($this->t('The search index %name has been disabled.', ['%name' => $this->entity->label()]));
$form_state->setRedirect('search_api.overview');
}
@@ -8,6 +8,7 @@ use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Drupal\search_api\DataType\DataTypePluginManager;
@@ -16,7 +17,7 @@ use Drupal\search_api\SearchApiException;
use Drupal\search_api\UnsavedConfigurationInterface;
use Drupal\search_api\Utility\DataTypeHelperInterface;
use Drupal\search_api\Utility\FieldsHelperInterface;
use Drupal\user\SharedTempStoreFactory;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -36,7 +37,7 @@ class IndexFieldsForm extends EntityForm {
/**
* The shared temporary storage for unsaved search indexes.
*
* @var \Drupal\user\SharedTempStore
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
@@ -62,23 +63,16 @@ class IndexFieldsForm extends EntityForm {
protected $fieldsHelper;
/**
* {@inheritdoc}
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
public function getFormId() {
return 'search_api_index_fields';
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return NULL;
}
protected $messenger;
/**
* Constructs an IndexFieldsForm object.
*
* @param \Drupal\user\SharedTempStoreFactory $temp_store_factory
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The factory for shared temporary storages.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
@@ -92,8 +86,10 @@ class IndexFieldsForm extends EntityForm {
* The data type helper.
* @param \Drupal\search_api\Utility\FieldsHelperInterface $fields_helper
* The fields helper.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, DataTypePluginManager $data_type_plugin_manager, RendererInterface $renderer, DateFormatterInterface $date_formatter, DataTypeHelperInterface $data_type_helper, FieldsHelperInterface $fields_helper) {
public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, DataTypePluginManager $data_type_plugin_manager, RendererInterface $renderer, DateFormatterInterface $date_formatter, DataTypeHelperInterface $data_type_helper, FieldsHelperInterface $fields_helper, MessengerInterface $messenger) {
$this->tempStore = $temp_store_factory->get('search_api_index');
$this->entityTypeManager = $entity_type_manager;
$this->dataTypePluginManager = $data_type_plugin_manager;
@@ -101,21 +97,37 @@ class IndexFieldsForm extends EntityForm {
$this->dateFormatter = $date_formatter;
$this->dataTypeHelper = $data_type_helper;
$this->fieldsHelper = $fields_helper;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$temp_store_factory = $container->get('user.shared_tempstore');
$temp_store_factory = $container->get('tempstore.shared');
$entity_type_manager = $container->get('entity_type.manager');
$data_type_plugin_manager = $container->get('plugin.manager.search_api.data_type');
$renderer = $container->get('renderer');
$date_formatter = $container->get('date.formatter');
$data_type_helper = $container->get('search_api.data_type_helper');
$fields_helper = $container->get('search_api.fields_helper');
$messenger = $container->get('messenger');
return new static($temp_store_factory, $entity_type_manager, $data_type_plugin_manager, $renderer, $date_formatter, $data_type_helper, $fields_helper);
return new static($temp_store_factory, $entity_type_manager, $data_type_plugin_manager, $renderer, $date_formatter, $data_type_helper, $fields_helper, $messenger);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'search_api_index_fields';
}
/**
@@ -155,7 +167,7 @@ class IndexFieldsForm extends EntityForm {
],
];
$form['description']['#markup'] = $this->t('<p>The data type of a field determines how it can be used for searching and filtering. The boost is used to give additional weight to certain fields, for example titles or tags.</p> <p>For information about the data types available for indexing, see the <a href="@url">data types table</a> at the bottom of the page.</p>', ['@url' => '#search-api-data-types-table']);
$form['description']['#markup'] = $this->t('<p>The data type of a field determines how it can be used for searching and filtering. The boost is used to give additional weight to certain fields, for example titles or tags.</p> <p>For information about the data types available for indexing, see the <a href=":url">data types table</a> at the bottom of the page.</p>', [':url' => '#search-api-data-types-table']);
if ($fields = $index->getFieldsByDatasource(NULL)) {
$form['_general'] = $this->buildFieldsTable($fields);
@@ -216,7 +228,9 @@ class IndexFieldsForm extends EntityForm {
if ($fallback_types) {
foreach ($fields as $field) {
if (isset($fallback_types[$field->getType()])) {
drupal_set_message($this->t("Some of the used data types aren't supported by the server's backend. See the <a href=\":url\">data types table</a> to find out which types are supported.", [':url' => '#search-api-data-types-table']), 'warning');
$args = [':url' => '#search-api-data-types-table'];
$warning = $this->t("Some of the used data types aren't supported by the server's backend. See the <a href=\":url\">data types table</a> to find out which types are supported.", $args);
$this->messenger->addWarning($warning);
break;
}
}
@@ -274,6 +288,7 @@ class IndexFieldsForm extends EntityForm {
],
];
uasort($fields, [$this->fieldsHelper, 'compareFieldLabels']);
foreach ($fields as $key => $field) {
$build['fields'][$key]['#access'] = !$field->isHidden();
@@ -444,7 +459,7 @@ class IndexFieldsForm extends EntityForm {
foreach ($field_values as $field_id => $new_settings) {
if (!isset($fields[$field_id])) {
$args['%field_id'] = $field_id;
drupal_set_message($this->t('The field with ID %field_id does not exist anymore.', $args), 'warning');
$this->messenger->addWarning($this->t('The field with ID %field_id does not exist anymore.', $args));
continue;
}
$field = $fields[$field_id];
@@ -471,9 +486,9 @@ class IndexFieldsForm extends EntityForm {
$index->save();
}
drupal_set_message($this->t('The changes were successfully saved.'));
$this->messenger->addStatus($this->t('The changes were successfully saved.'));
if ($this->entity->isReindexing()) {
drupal_set_message($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
$this->messenger->addStatus($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
}
return SAVED_UPDATED;
@@ -4,13 +4,14 @@ namespace Drupal\search_api\Form;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Utility\Error;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\SearchApiException;
use Drupal\search_api\Utility\PluginHelperInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -26,6 +27,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class IndexForm extends EntityForm {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The entity type manager.
*
@@ -54,10 +62,13 @@ class IndexForm extends EntityForm {
* The entity type manager.
* @param \Drupal\search_api\Utility\PluginHelperInterface $plugin_helper
* The plugin helper.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, PluginHelperInterface $plugin_helper) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, PluginHelperInterface $plugin_helper, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->pluginHelper = $plugin_helper;
$this->messenger = $messenger;
}
/**
@@ -66,7 +77,9 @@ class IndexForm extends EntityForm {
public static function create(ContainerInterface $container) {
$entity_type_manager = $container->get('entity_type.manager');
$plugin_helper = $container->get('search_api.plugin_helper');
return new static($entity_type_manager, $plugin_helper);
$messenger = $container->get('messenger');
return new static($entity_type_manager, $plugin_helper, $messenger);
}
/**
@@ -358,7 +371,7 @@ class IndexForm extends EntityForm {
// If the user changed the datasources and there is at least one datasource
// config form, show a message telling the user to configure it.
if ($selected_datasources && $show_message) {
drupal_set_message($this->t('Please configure the used datasources.'), 'warning');
$this->messenger->addWarning($this->t('Please configure the used datasources.'));
}
}
@@ -382,7 +395,7 @@ class IndexForm extends EntityForm {
// Only notify the user of a missing tracker plugin if we're editing an
// existing index.
elseif (!$index->isNew()) {
drupal_set_message($this->t('The tracker plugin is missing or invalid.'), 'error');
$this->messenger->addError($this->t('The tracker plugin is missing or invalid.'));
}
}
else {
@@ -412,7 +425,7 @@ class IndexForm extends EntityForm {
// If the user changed the tracker and the new one has a config form, show
// a message telling the user to configure it.
if ($selected_tracker && $selected_tracker != $tracker->getPluginId()) {
drupal_set_message($this->t('Please configure the used tracker.'), 'warning');
$this->messenger->addWarning($this->t('Please configure the used tracker.'));
}
}
}
@@ -598,7 +611,7 @@ class IndexForm extends EntityForm {
/** @var \Drupal\search_api\IndexInterface $index */
$index = $this->getEntity();
$index->save();
drupal_set_message($this->t('The index was successfully saved.'));
$this->messenger->addStatus($this->t('The index was successfully saved.'));
$button = $form_state->getTriggeringElement();
if (!empty($button['#redirect_to_url'])) {
$form_state->setRedirectUrl($index->toUrl($button['#redirect_to_url']));
@@ -607,14 +620,14 @@ class IndexForm extends EntityForm {
$form_state->setRedirect('entity.search_api_index.canonical', ['search_api_index' => $index->id()]);
}
}
catch (SearchApiException $e) {
catch (EntityStorageException $e) {
$form_state->setRebuild();
$message = '%type: @message in %function (line %line of %file).';
$variables = Error::decodeException($e);
$this->getLogger('search_api')->error($message, $variables);
drupal_set_message($this->t('The index could not be saved.'), 'error');
$this->messenger->addError($this->t('The index could not be saved.'));
}
}
}
@@ -6,6 +6,7 @@ use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Form\SubformState;
use Drupal\search_api\Processor\ProcessorInterface;
@@ -18,6 +19,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class IndexProcessorsForm extends EntityForm {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The index being configured.
*
@@ -55,11 +63,14 @@ class IndexProcessorsForm extends EntityForm {
* The processor plugin manager.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ProcessorPluginManager $processor_plugin_manager, LoggerInterface $logger) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, ProcessorPluginManager $processor_plugin_manager, LoggerInterface $logger, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->processorPluginManager = $processor_plugin_manager;
$this->logger = $logger;
$this->messenger = $messenger;
}
/**
@@ -69,8 +80,9 @@ class IndexProcessorsForm extends EntityForm {
$entity_type_manager = $container->get('entity_type.manager');
$processor_plugin_manager = $container->get('plugin.manager.search_api.processor');
$logger = $container->get('logger.channel.search_api');
$messenger = $container->get('messenger');
return new static($entity_type_manager, $processor_plugin_manager, $logger);
return new static($entity_type_manager, $processor_plugin_manager, $logger, $messenger);
}
/**
@@ -322,13 +334,14 @@ class IndexProcessorsForm extends EntityForm {
public function save(array $form, FormStateInterface $form_state) {
if ($form_state->get('processors_changed')) {
$save_status = parent::save($form, $form_state);
drupal_set_message($this->t('The indexing workflow was successfully edited.'));
$this->messenger->addStatus($this->t('The indexing workflow was successfully edited.'));
if ($this->entity->isReindexing()) {
drupal_set_message($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
$url = $this->entity->toUrl();
$this->messenger->addStatus($this->t('All content was scheduled for <a href=":url">reindexing</a> so the new settings can take effect.', [':url' => $url->toString()]));
}
}
else {
drupal_set_message($this->t('No values were changed.'));
$this->messenger->addStatus($this->t('No values were changed.'));
$save_status = SAVED_UPDATED;
}
@@ -5,12 +5,24 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for clearing an index.
*/
class IndexRebuildTrackerConfirmForm extends EntityConfirmFormBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$form = parent::create($container);
$form->setMessenger($container->get('messenger'));
return $form;
}
/**
* {@inheritdoc}
*/
@@ -39,7 +51,8 @@ class IndexRebuildTrackerConfirmForm extends EntityConfirmFormBase {
/** @var \Drupal\search_api\IndexInterface $index */
$index = $this->getEntity();
$index->rebuildTracker();
drupal_set_message($this->t('The tracking information for search index %name will be rebuilt.', ['%name' => $index->label()]));
$this->messenger()
->addStatus($this->t('The tracking information for search index %name will be rebuilt.', ['%name' => $index->label()]));
$form_state->setRedirect('entity.search_api_index.canonical', ['search_api_index' => $index->id()]);
}
@@ -4,15 +4,43 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Drupal\search_api\SearchApiException;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for reindexing an index.
*/
class IndexReindexConfirmForm extends EntityConfirmFormBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs an IndexReindexConfirmForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* {@inheritdoc}
*/
@@ -43,10 +71,10 @@ class IndexReindexConfirmForm extends EntityConfirmFormBase {
try {
$index->reindex();
drupal_set_message($this->t('The search index %name was successfully reindexed.', ['%name' => $index->label()]));
$this->messenger->addStatus($this->t('The search index %name was successfully reindexed.', ['%name' => $index->label()]));
}
catch (SearchApiException $e) {
drupal_set_message($this->t('Failed to reindex items for the search index %name.', ['%name' => $index->label()]), 'error');
$this->messenger->addError($this->t('Failed to reindex items for the search index %name.', ['%name' => $index->label()]));
$message = '%type while trying to reindex items on index %name: @message in %function (line %line of %file)';
$variables = [
'%name' => $index->label(),
@@ -2,19 +2,46 @@
namespace Drupal\search_api\Form;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\search_api\IndexBatchHelper;
use Drupal\search_api\SearchApiException;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Task\IndexTaskManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for indexing, clearing, etc., an index.
*/
class IndexStatusForm extends FormBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs an IndexStatusForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* The index task manager.
*
@@ -184,12 +211,12 @@ class IndexStatusForm extends FormBase {
if ($form_state->getTriggeringElement()['#name'] === 'index_now') {
$values = $form_state->getValues();
// Get the translated "all" value and lowercase it for comparison.
$all_value = Unicode::strtolower($values['all']);
$all_value = mb_strtolower($values['all']);
foreach (['limit', 'batch_size'] as $field) {
// Trim and lowercase the value so we correctly identify "all" values,
// even if not matching exactly.
$value = Unicode::strtolower(trim($values[$field]));
$value = mb_strtolower(trim($values[$field]));
if ($value === $all_value) {
$value = -1;
@@ -221,7 +248,7 @@ class IndexStatusForm extends FormBase {
IndexBatchHelper::create($index, $values['batch_size'], $values['limit']);
}
catch (SearchApiException $e) {
drupal_set_message($this->t('Failed to create a batch, please check the batch size and limit.'), 'warning');
$this->messenger->addWarning($this->t('Failed to create a batch, please check the batch size and limit.'));
}
break;
@@ -4,15 +4,43 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Drupal\search_api\SearchApiException;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for clearing a server.
*/
class ServerClearConfirmForm extends EntityConfirmFormBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a ServerClearConfirmForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* {@inheritdoc}
*/
@@ -43,10 +71,10 @@ class ServerClearConfirmForm extends EntityConfirmFormBase {
try {
$server->deleteAllItems();
drupal_set_message($this->t('All indexed data was successfully deleted from the server.'));
$this->messenger->addStatus($this->t('All indexed data was successfully deleted from the server.'));
}
catch (SearchApiException $e) {
drupal_set_message($this->t('Indexed data could not be cleared for some indexes. Check the logs for details.'), 'error');
$this->messenger->addError($this->t('Indexed data could not be cleared for some indexes. Check the logs for details.'));
}
$failed_reindexing = [];
@@ -74,7 +102,7 @@ class ServerClearConfirmForm extends EntityConfirmFormBase {
$args = [
'@indexes' => implode(', ', $failed_reindexing),
];
drupal_set_message($this->t('Failed to mark the following indexes for reindexing: @indexes. Check the logs for details.', $args), 'warning');
$this->messenger->addWarning($this->t('Failed to mark the following indexes for reindexing: @indexes. Check the logs for details.', $args));
}
$form_state->setRedirect('entity.search_api_server.canonical', ['search_api_server' => $server->id()]);
@@ -4,13 +4,41 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for deleting a server.
*/
class ServerDeleteConfirmForm extends EntityDeleteForm {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a ServerDeleteConfirmForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* {@inheritdoc}
*/
@@ -44,7 +72,7 @@ class ServerDeleteConfirmForm extends EntityDeleteForm {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->delete();
drupal_set_message($this->t('The search server %name has been deleted.', ['%name' => $this->entity->label()]));
$this->messenger->addStatus($this->t('The search server %name has been deleted.', ['%name' => $this->entity->label()]));
$form_state->setRedirect('search_api.overview');
}
@@ -4,13 +4,41 @@ namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a confirm form for disabling a server.
*/
class ServerDisableConfirmForm extends EntityConfirmFormBase {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a ServerDisableConfirmForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$messenger = $container->get('messenger');
return new static($messenger);
}
/**
* {@inheritdoc}
*/
@@ -47,7 +75,7 @@ class ServerDisableConfirmForm extends EntityConfirmFormBase {
$server = $this->entity;
$server->setStatus(FALSE)->save();
drupal_set_message($this->t('The search server %name has been disabled.', ['%name' => $this->entity->label()]));
$this->messenger->addStatus($this->t('The search server %name has been disabled.', ['%name' => $this->entity->label()]));
$form_state->setRedirect('search_api.overview');
}
@@ -3,13 +3,14 @@
namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Drupal\search_api\Backend\BackendPluginManager;
use Drupal\search_api\SearchApiException;
use Drupal\search_api\ServerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -25,21 +26,34 @@ class ServerForm extends EntityForm {
*/
protected $backendPluginManager;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a ServerForm object.
*
* @param \Drupal\search_api\Backend\BackendPluginManager $backend_plugin_manager
* The backend plugin manager.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(BackendPluginManager $backend_plugin_manager) {
public function __construct(BackendPluginManager $backend_plugin_manager, MessengerInterface $messenger) {
$this->backendPluginManager = $backend_plugin_manager;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('plugin.manager.search_api.backend'));
$backend_plugin_manager = $container->get('plugin.manager.search_api.backend');
$messenger = $container->get('messenger');
return new static($backend_plugin_manager, $messenger);
}
/**
@@ -154,7 +168,10 @@ class ServerForm extends EntityForm {
$form['backend'] += $descriptions;
}
else {
drupal_set_message($this->t('There are no backend plugins available for the Search API. Please install a <a href=":url">module that provides a backend plugin</a> to proceed.', [':url' => Url::fromUri('https://www.drupal.org/node/1254698')->toString()]), 'error');
$url = 'https://www.drupal.org/node/1254698';
$args[':url'] = Url::fromUri($url)->toString();
$error = $this->t('There are no backend plugins available for the Search API. Please install a <a href=":url">module that provides a backend plugin</a> to proceed.', $args);
$this->messenger->addError($error);
$form = [];
}
}
@@ -176,7 +193,7 @@ class ServerForm extends EntityForm {
$form_state->set('backend', $backend->getPluginId());
if ($backend instanceof PluginFormInterface) {
if ($form_state->isRebuilding()) {
drupal_set_message($this->t('Please configure the selected backend.'), 'warning');
$this->messenger->addWarning($this->t('Please configure the selected backend.'));
}
// Attach the backend plugin configuration form.
$backend_form_state = SubformState::createForSubform($form['backend_config'], $form, $form_state);
@@ -191,7 +208,7 @@ class ServerForm extends EntityForm {
// Only notify the user of a missing backend plugin if we're editing an
// existing server.
elseif (!$server->isNew()) {
drupal_set_message($this->t('The backend plugin is missing or invalid.'), 'error');
$this->messenger->addError($this->t('The backend plugin is missing or invalid.'));
return;
}
$form['backend_config'] += [
@@ -282,17 +299,17 @@ class ServerForm extends EntityForm {
try {
$server = $this->getEntity();
$server->save();
drupal_set_message($this->t('The server was successfully saved.'));
$this->messenger->addStatus($this->t('The server was successfully saved.'));
$form_state->setRedirect('entity.search_api_server.canonical', ['search_api_server' => $server->id()]);
}
catch (SearchApiException $e) {
catch (EntityStorageException $e) {
$form_state->setRebuild();
$message = '%type: @message in %function (line %line of %file).';
$variables = Error::decodeException($e);
$this->getLogger('search_api')->error($message, $variables);
drupal_set_message($this->t('The server could not be saved.'), 'error');
$this->messenger->addError($this->t('The server could not be saved.'));
}
}
}
@@ -187,23 +187,23 @@ class IndexBatchHelper {
// Build the indexed message.
$indexed_message = static::formatPlural($results['indexed'], 'Successfully indexed 1 item.', 'Successfully indexed @count items.');
// Notify user about indexed items.
drupal_set_message($indexed_message);
\Drupal::messenger()->addStatus($indexed_message);
// Display the number of items not indexed.
if (!empty($results['not indexed'])) {
// Build the not indexed message.
$not_indexed_message = static::formatPlural($results['not indexed'], '1 item could not be indexed. Check the logs for details.', '@count items could not be indexed. Check the logs for details.');
// Notify user about not indexed items.
drupal_set_message($not_indexed_message, 'warning');
\Drupal::messenger()->addWarning($not_indexed_message);
}
}
else {
// Notify user about failure to index items.
drupal_set_message(static::t("Couldn't index items. Check the logs for details."), 'error');
\Drupal::messenger()->addError(static::t("Couldn't index items. Check the logs for details."));
}
}
else {
// Notify user about batch job failure.
drupal_set_message(static::t('An error occurred while trying to index items. Check the logs for details.'), 'error');
\Drupal::messenger()->addError(static::t('An error occurred while trying to index items. Check the logs for details.'));
}
}
@@ -399,18 +399,15 @@ interface IndexInterface extends ConfigEntityInterface {
/**
* Adds a field to this index.
*
* If the field is already present (with the same datasource and property
* path) its settings will be updated.
*
* @param \Drupal\search_api\Item\FieldInterface $field
* The field to add, or update.
* The field to add.
*
* @return $this
*
* @throws \Drupal\search_api\SearchApiException
* Thrown if the field could not be added, either because a different field
* with the same field ID would be overwritten, or because the field
* identifier is one of the pseudo-fields that can be used in search
* Thrown if the field could not be added, either because a field with the
* same field ID already exists, or because the field identifier is one of
* the reserved field IDs of pseudo-fields that can be used in search
* queries.
*/
public function addField(FieldInterface $field);
@@ -8,7 +8,7 @@ use Drupal\Core\ParamConverter\EntityConverter;
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\search_api\UnsavedIndexConfiguration;
use Drupal\user\SharedTempStoreFactory;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Symfony\Component\Routing\Route;
/**
@@ -24,7 +24,7 @@ class SearchApiConverter extends EntityConverter implements ParamConverterInterf
/**
* The shared temporary storage factory.
*
* @var \Drupal\user\SharedTempStoreFactory
* @var \Drupal\Core\TempStore\SharedTempStoreFactory
*/
protected $tempStoreFactory;
@@ -40,7 +40,7 @@ class SearchApiConverter extends EntityConverter implements ParamConverterInterf
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\user\SharedTempStoreFactory $temp_store_factory
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The factory for the temp store object.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
@@ -11,7 +11,7 @@ namespace Drupal\search_api\Plugin\DataType;
*
* @DataType(
* id = "search_api_html",
* label = @Translation("Text (Search API)")
* label = @Translation("HTML text (Search API)")
* )
*/
class Html extends Text {}
@@ -16,4 +16,19 @@ use Drupal\search_api\DataType\DataTypePluginBase;
*/
class DateDataType extends DataTypePluginBase {
/**
* {@inheritdoc}
*/
public function getValue($value) {
if ((string) $value === '') {
return NULL;
}
if (is_numeric($value)) {
return (int) $value;
}
$timezone = new \DateTimezone('UTC');
$date = new \Datetime($value, $timezone);
return $date->getTimestamp();
}
}
@@ -809,12 +809,8 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
* {@inheritdoc}
*/
public function getViewModes($bundle = NULL) {
if (isset($bundle) && $this->hasBundles()) {
return $this->getEntityDisplayRepository()->getViewModeOptionsByBundle($this->getEntityTypeId(), $bundle);
}
else {
return $this->getEntityDisplayRepository()->getViewModeOptions($this->getEntityTypeId());
}
return $this->getEntityDisplayRepository()
->getViewModeOptions($this->getEntityTypeId());
}
/**
@@ -841,7 +837,8 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
*/
public function viewMultipleItems(array $items, $view_mode, $langcode = NULL) {
try {
$view_builder = $this->getEntityTypeManager()->getViewBuilder($this->getEntityTypeId());
$view_builder = $this->getEntityTypeManager()
->getViewBuilder($this->getEntityTypeId());
// Langcode passed, use that for viewing.
if (isset($langcode)) {
$entities = [];
@@ -858,8 +855,8 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
// Otherwise, separate the items by language, keeping the keys.
$items_by_language = [];
foreach ($items as $i => $item) {
if ($item instanceof EntityInterface) {
$items_by_language[$item->language()->getId()][$i] = $item;
if ($entity = $this->getEntity($item)) {
$items_by_language[$entity->language()->getId()][$i] = $entity;
}
}
// Then build the items for each language. We initialize $build beforehand
@@ -969,7 +966,7 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
$indexes = Index::loadMultiple();
foreach ($indexes as $index_id => $index) {
// Filter our indexes that don't contain the datasource in question.
// Filter out indexes that don't contain the datasource in question.
if (!$index->isValidDatasource($datasource_id)) {
unset($indexes[$index_id]);
}
@@ -977,9 +974,7 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
// If the entity type supports bundles, we also have to filter out
// indexes that exclude the entity's bundle.
$config = $index->getDatasource($datasource_id)->getConfiguration();
$default = !empty($config['bundles']['default']);
$bundle_set = in_array($entity_bundle, $config['bundles']['selected']);
if ($default == $bundle_set) {
if (!Utility::matches($entity_bundle, $config['bundles'])) {
unset($indexes[$index_id]);
}
}
@@ -1017,8 +1012,6 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
if (!isset($config['languages']['selected'])) {
return $item_ids;
}
$default = !empty($config['languages']['default']);
$selected = $config['languages']['selected'];
$always_valid = [
LanguageInterface::LANGCODE_NOT_SPECIFIED,
LanguageInterface::LANGCODE_NOT_APPLICABLE,
@@ -1031,7 +1024,7 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
continue;
}
$langcode = substr($item_id, $pos + 1);
if ($default != in_array($langcode, $selected)
if (Utility::matches($langcode, $config['languages'])
|| in_array($langcode, $always_valid)) {
$valid_ids[] = $item_id;
}
@@ -55,8 +55,8 @@ class ContentEntityTaskManager implements EventSubscriberInterface {
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events['search_api.task.' . static::INSERT_ITEMS_TASK_TYPE][] = ['processEvent'];
$events['search_api.task.' . static::DELETE_ITEMS_TASK_TYPE][] = ['processEvent'];
$events['search_api.task.' . self::INSERT_ITEMS_TASK_TYPE][] = ['processEvent'];
$events['search_api.task.' . self::DELETE_ITEMS_TASK_TYPE][] = ['processEvent'];
return $events;
}
@@ -120,7 +120,7 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface {
$property_label = $property->getLabel();
$property = $this->getFieldsHelper()->getInnerProperty($property);
if ($property instanceof EntityDataDefinitionInterface) {
$options = self::findHierarchicalProperties($property, $property_label);
$options = static::findHierarchicalProperties($property, $property_label);
if ($options) {
$field_options += [$field_id => []];
$field_options[$field_id] += $options;
@@ -49,8 +49,9 @@ class AddURL extends ProcessorPluginBase {
$url = $item->getDatasource()->getItemUrl($item->getOriginalObject());
if ($url) {
$url = $url->toString();
$fields = $item->getFields(FALSE);
$fields = $this->getFieldsHelper()
->filterForPropertyPath($item->getFields(), NULL, 'search_api_url');
->filterForPropertyPath($fields, NULL, 'search_api_url');
foreach ($fields as $field) {
$field->addValue($url);
}
@@ -63,14 +63,14 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
if (!isset(self::$boundary)) {
if (!isset(static::$boundary)) {
$cjk = '\x{1100}-\x{11FF}\x{3040}-\x{309F}\x{30A1}-\x{318E}' .
'\x{31A0}-\x{31B7}\x{31F0}-\x{31FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FCF}' .
'\x{A000}-\x{A48F}\x{A4D0}-\x{A4FD}\x{A960}-\x{A97F}\x{AC00}-\x{D7FF}' .
'\x{F900}-\x{FAFF}\x{FF21}-\x{FF3A}\x{FF41}-\x{FF5A}\x{FF66}-\x{FFDC}' .
'\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}';
self::$boundary = '(?:(?<=[' . Unicode::PREG_CLASS_WORD_BOUNDARY . $cjk . '])|(?=[' . Unicode::PREG_CLASS_WORD_BOUNDARY . $cjk . ']))';
self::$split = '/[' . Unicode::PREG_CLASS_WORD_BOUNDARY . ']+/iu';
static::$boundary = '(?:(?<=[' . Unicode::PREG_CLASS_WORD_BOUNDARY . $cjk . '])|(?=[' . Unicode::PREG_CLASS_WORD_BOUNDARY . $cjk . ']))';
static::$split = '/[' . Unicode::PREG_CLASS_WORD_BOUNDARY . ']+/iu';
}
}
@@ -257,9 +257,6 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
$highlighted_fields = $this->highlightFields($result_items, $keys);
foreach ($highlighted_fields as $item_id => $item_fields) {
$item = $result_items[$item_id];
// Maybe the backend or some other processor has already set highlighted
// field values.
$item_fields += $item->getExtraData('highlighted_fields', []);
$item->setExtraData('highlighted_fields', $item_fields);
}
}
@@ -303,19 +300,28 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
* highlighted versions of the values for that field.
*/
protected function highlightFields(array $results, array $keys) {
$item_fields = $this->getFulltextFields($results, NULL, $this->configuration['highlight'] == 'always');
$highlighted_fields = [];
foreach ($results as $item_id => $item) {
// Maybe the backend or some other processor has already set highlighted
// field values.
$highlighted_fields[$item_id] = $item->getExtraData('highlighted_fields', []);
}
$load = $this->configuration['highlight'] == 'always';
$item_fields = $this->getFulltextFields($results, NULL, $load);
foreach ($item_fields as $item_id => $fields) {
foreach ($fields as $field_id => $values) {
$change = FALSE;
foreach ($values as $i => $value) {
$values[$i] = $this->highlightField($value, $keys);
if ($values[$i] !== $value) {
$change = TRUE;
if (empty($highlighted_fields[$item_id][$field_id])) {
$change = FALSE;
foreach ($values as $i => $value) {
$values[$i] = $this->highlightField($value, $keys);
if ($values[$i] !== $value) {
$change = TRUE;
}
}
if ($change) {
$highlighted_fields[$item_id][$field_id] = $values;
}
}
if ($change) {
$highlighted_fields[$item_id][$field_id] = $values;
}
}
}
@@ -373,7 +379,7 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
return $this->flattenKeysArray($keys);
}
$keywords_in = preg_split(self::$split, $keys);
$keywords_in = preg_split(static::$split, $keys);
// Assure there are no duplicates. (This is actually faster than
// array_unique() by a factor of 3 to 4.)
// Remove quotes from keywords.
@@ -474,7 +480,7 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
if (!$this->configuration['highlight_partial']) {
$found_position = FALSE;
$regex = '/' . self::$boundary . preg_quote($key, '/') . self::$boundary . '/iu';
$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];
}
@@ -602,7 +608,7 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
$keys = implode('|', array_map('preg_quote', $keys, array_fill(0, count($keys), '/')));
// If "Highlight partial matches" is disabled, we only want to highlight
// matches that are complete words. Otherwise, we want all of them.
$boundary = !$this->configuration['highlight_partial'] ? self::$boundary : '';
$boundary = !$this->configuration['highlight_partial'] ? static::$boundary : '';
$regex = '/' . $boundary . '(?:' . $keys . ')' . $boundary . '/iu';
$replace = $this->configuration['prefix'] . '\0' . $this->configuration['suffix'];
$text = preg_replace($regex, $replace, ' ' . $text . ' ');
@@ -2,7 +2,6 @@
namespace Drupal\search_api\Plugin\search_api\processor;
use Drupal\Component\Utility\Unicode;
use Drupal\search_api\Item\FieldInterface;
use Drupal\search_api\Plugin\search_api\data_type\value\TextValueInterface;
use Drupal\search_api\Processor\FieldsProcessorPluginBase;
@@ -42,7 +41,7 @@ class IgnoreCase extends FieldsProcessorPluginBase {
protected function process(&$value) {
// We don't touch integers, NULL values or the like.
if (is_string($value)) {
$value = Unicode::strtolower($value);
$value = mb_strtolower($value);
}
}
@@ -205,7 +205,7 @@ class Tokenizer extends FieldsProcessorPluginBase {
$value = [];
foreach ($arr as $token) {
if (is_numeric($token) || Unicode::strlen($token) >= $this->configuration['minimum_word_size']) {
if (is_numeric($token) || mb_strlen($token) >= $this->configuration['minimum_word_size']) {
$value[] = Utility::createTextToken($token);
}
}
@@ -277,7 +277,7 @@ class Tokenizer extends FieldsProcessorPluginBase {
protected function expandCjk(array $matches) {
$min = $this->configuration['minimum_word_size'];
$str = $matches[0];
$length = Unicode::strlen($str);
$length = mb_strlen($str);
// If the text is shorter than the minimum word size, don't tokenize it.
if ($length <= $min) {
return ' ' . $str . ' ';
@@ -287,7 +287,7 @@ class Tokenizer extends FieldsProcessorPluginBase {
$chars = [];
for ($i = 0; $i < $length; $i++) {
// Add the next character off the beginning of the string to the queue.
$current = Unicode::substr($str, 0, 1);
$current = mb_substr($str, 0, 1);
$str = substr($str, strlen($current));
$chars[] = $current;
if ($i >= $min - 1) {
@@ -271,7 +271,7 @@ class Basic extends TrackerPluginBase implements PluginFormInterface {
// (Otherwise, an item that's regularly being updated might never get
// indexed.)
if ($this->configuration['indexing_order'] === 'fifo') {
$update->condition('status', static::STATUS_INDEXED);
$update->condition('status', self::STATUS_INDEXED);
}
$update->execute();
}
@@ -78,14 +78,9 @@ class SearchApiDate extends Date {
];
}
// Store this because it will get overwritten by the grandparent, and the
// parent doesn't always restore it correctly.
$type = $this->value['type'];
$return = parent::acceptExposedInput($input);
if (!$return) {
// If the parent returns FALSE, it doesn't restore the "type" key.
$this->value['type'] = $type;
// Override for the "(not) empty" operators.
$operators = $this->operators();
if ($operators[$this->operator]['values'] == 0) {
@@ -2,7 +2,6 @@
namespace Drupal\search_api\Plugin\views\filter;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\search_api\Entity\Index;
@@ -123,11 +122,11 @@ class SearchApiFulltext extends FilterPluginBase {
public function defineOptions() {
$options = parent::defineOptions();
$options['parse_mode'] = ['default' => 'terms'];
$options['operator']['default'] = 'and';
$options['min_length']['default'] = '';
$options['fields']['default'] = [];
$options['parse_mode'] = ['default' => 'terms'];
$options['min_length'] = ['default' => ''];
$options['fields'] = ['default' => []];
$options['expose']['contains']['placeholder'] = ['default' => ''];
return $options;
}
@@ -188,6 +187,21 @@ class SearchApiFulltext extends FilterPluginBase {
];
}
/**
* {@inheritdoc}
*/
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
$form['expose']['placeholder'] = [
'#type' => 'textfield',
'#default_value' => $this->options['expose']['placeholder'],
'#title' => $this->t('Placeholder'),
'#size' => 40,
'#description' => $this->t('Hint text that appears inside the field when empty.'),
];
}
/**
* {@inheritdoc}
*/
@@ -200,6 +214,9 @@ class SearchApiFulltext extends FilterPluginBase {
'#size' => 30,
'#default_value' => $this->value,
];
if (!empty($this->options['expose']['placeholder'])) {
$form['value']['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
}
}
/**
@@ -254,7 +271,7 @@ class SearchApiFulltext extends FilterPluginBase {
$words = preg_split('/\s+/', $input);
foreach ($words as $i => $word) {
if (Unicode::strlen($word) < $this->options['min_length']) {
if (mb_strlen($word) < $this->options['min_length']) {
unset($words[$i]);
}
}
@@ -19,7 +19,7 @@ class SearchApiLanguage extends LanguageFilter {
* {@inheritdoc}
*/
public function query() {
$substitutions = self::queryLanguageSubstitutions();
$substitutions = static::queryLanguageSubstitutions();
foreach ($this->value as $i => $value) {
if (isset($substitutions[$value])) {
$this->value[$i] = $substitutions[$value];
@@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\search_api\Entity\Index;
use Drupal\search_api\LoggerTrait;
@@ -115,6 +116,13 @@ class SearchApiQuery extends QueryPluginBase {
*/
protected $moduleHandler;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface|null
*/
protected $messenger;
/**
* {@inheritdoc}
*/
@@ -123,6 +131,7 @@ class SearchApiQuery extends QueryPluginBase {
$plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$plugin->setModuleHandler($container->get('module_handler'));
$plugin->setMessenger($container->get('messenger'));
$plugin->setLogger($container->get('logger.channel.search_api'));
return $plugin;
@@ -208,6 +217,29 @@ class SearchApiQuery extends QueryPluginBase {
return $this;
}
/**
* Retrieves the messenger.
*
* @return \Drupal\Core\Messenger\MessengerInterface
* The messenger.
*/
public function getMessenger() {
return $this->messenger ?: \Drupal::service('messenger');
}
/**
* Sets the messenger.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The new messenger.
*
* @return $this
*/
public function setMessenger(MessengerInterface $messenger) {
$this->messenger = $messenger;
return $this;
}
/**
* {@inheritdoc}
*/
@@ -427,7 +459,7 @@ class SearchApiQuery extends QueryPluginBase {
if ($this->shouldAbort()) {
if (error_displayable()) {
foreach ($this->errors as $msg) {
drupal_set_message(Html::escape($msg), 'error');
$this->getMessenger()->addError(Html::escape($msg));
}
}
$view->result = [];
@@ -427,9 +427,9 @@ abstract class FieldsProcessorPluginBase extends ProcessorPluginBase implements
// value can't be in the index but that's irrelevant for (NOT)
// BETWEEN conditions, as any value between the two bounds could still
// be included. We therefore never remove a (NOT) BETWEEN condition
// and also ignore it when one of the two values got removed. (Note
// that this check will also catch empty strings.) Processors who need
// different behavior have to override this method.
// and also ignore it when one of the two values got removed.
// Processors who need different behavior have to override this
// method.
$between_operator = in_array($condition->getOperator(), ['BETWEEN', 'NOT BETWEEN']);
if ($between_operator && (!is_array($value) || count($value) < 2)) {
continue;
@@ -176,6 +176,13 @@ class Query implements QueryInterface {
*/
protected $queryHelper;
/**
* The original query before preprocessing.
*
* @var static|null
*/
protected $originalQuery;
/**
* Constructs a Query object.
*
@@ -465,7 +472,7 @@ class Query implements QueryInterface {
* {@inheritdoc}
*/
public function getAbortMessage() {
return is_bool($this->aborted) ? $this->aborted : NULL;
return !is_bool($this->aborted) ? $this->aborted : NULL;
}
/**
@@ -511,6 +518,9 @@ class Query implements QueryInterface {
if (!$this->wasAborted() && $this->languages !== []) {
return FALSE;
}
if (!$this->originalQuery) {
$this->originalQuery = clone $this;
}
$this->postExecute();
return TRUE;
}
@@ -521,9 +531,15 @@ class Query implements QueryInterface {
public function preExecute() {
// Make sure to only execute this once per query, and not for queries with
// the "none" processing level.
if (!$this->preExecuteRan && $this->processingLevel != self::PROCESSING_NONE) {
if (!$this->preExecuteRan) {
$this->originalQuery = clone $this;
$this->originalQuery->executed = FALSE;
$this->preExecuteRan = TRUE;
if ($this->processingLevel == self::PROCESSING_NONE) {
return;
}
// Preprocess query.
$this->index->preprocessSearchQuery($this);
@@ -673,12 +689,22 @@ class Query implements QueryInterface {
return $this->tags;
}
/**
* {@inheritdoc}
*/
public function getOriginalQuery() {
return $this->originalQuery ?: clone $this;
}
/**
* {@inheritdoc}
*/
public function __clone() {
$this->results = $this->getResults()->getCloneForQuery($this);
$this->conditionGroup = clone $this->conditionGroup;
if ($this->originalQuery) {
$this->originalQuery = clone $this->originalQuery;
}
if ($this->parseMode) {
$this->parseMode = clone $this->parseMode;
}
@@ -707,19 +733,6 @@ class Query implements QueryInterface {
$this->indexId = NULL;
}
// Sanitize the service IDs saved by the serialization trait to guard
// against incomplete service containers. Doesn't need to happen when the
// trait's __wakeup() method will return early anyways, though.
// @todo Remove once #2909164 gets fixed in Core (and we depend on that Core
// version).
if (!isset($GLOBALS['__PHPUNIT_BOOTSTRAP']) || \Drupal::hasContainer()) {
$container = \Drupal::getContainer();
foreach ($this->_serviceIds as $key => $service_id) {
if (!$container->has($service_id)) {
unset($this->_serviceIds[$key]);
}
}
}
$this->traitWakeup();
}
@@ -286,6 +286,11 @@ interface QueryInterface extends ConditionSetInterface {
/**
* Executes this search query.
*
* The results of the search will be cached on the query, so subsequent calls
* of this method will always return the same result set (even if conditions
* were changed in between). If you want to re-execute a query, use
* getOriginalQuery().
*
* @return \Drupal\search_api\Query\ResultSetInterface
* The results of the search.
*
@@ -520,4 +525,14 @@ interface QueryInterface extends ConditionSetInterface {
*/
public function &getTags();
/**
* Retrieves the original version of the query, before preprocessing occurred.
*
* Will be a clone of this query if preprocessing has not already run.
*
* @return \Drupal\search_api\Query\Query
* The original, unpreprocessed version of this query.
*/
public function getOriginalQuery();
}
@@ -54,7 +54,7 @@ class IndexTaskManager implements IndexTaskManagerInterface, EventSubscriberInte
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events['search_api.task.' . static::TRACK_ITEMS_TASK_TYPE][] = ['trackItems'];
$events['search_api.task.' . self::TRACK_ITEMS_TASK_TYPE][] = ['trackItems'];
return $events;
}
@@ -161,7 +161,7 @@ class IndexTaskManager implements IndexTaskManagerInterface, EventSubscriberInte
if ($reschedule) {
++$data['page'];
$this->taskManager->addTask(static::TRACK_ITEMS_TASK_TYPE, NULL, $index, $data);
$this->taskManager->addTask(self::TRACK_ITEMS_TASK_TYPE, NULL, $index, $data);
}
}
@@ -178,7 +178,7 @@ class IndexTaskManager implements IndexTaskManagerInterface, EventSubscriberInte
'datasource' => $datasource_id,
'page' => 0,
];
$this->taskManager->addTask(static::TRACK_ITEMS_TASK_TYPE, NULL, $index, $data);
$this->taskManager->addTask(self::TRACK_ITEMS_TASK_TYPE, NULL, $index, $data);
}
}
@@ -193,7 +193,7 @@ class IndexTaskManager implements IndexTaskManagerInterface, EventSubscriberInte
*/
protected function getTaskConditions(IndexInterface $index) {
return [
'type' => static::TRACK_ITEMS_TASK_TYPE,
'type' => self::TRACK_ITEMS_TASK_TYPE,
'index_id' => $index->id(),
];
}
@@ -5,6 +5,7 @@ namespace Drupal\search_api\Task;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\search_api\IndexInterface;
@@ -58,6 +59,13 @@ class TaskManager implements TaskManagerInterface {
*/
protected $eventDispatcher;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a TaskManager object.
*
@@ -67,11 +75,14 @@ class TaskManager implements TaskManagerInterface {
* The event dispatcher.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The string translation service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, TranslationInterface $translation) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, TranslationInterface $translation, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->eventDispatcher = $event_dispatcher;
$this->setStringTranslation($translation);
$this->messenger = $messenger;
}
/**
@@ -267,6 +278,9 @@ class TaskManager implements TaskManagerInterface {
];
// Schedule the batch.
batch_set($batch_definition);
if (function_exists('drush_backend_batch_process')) {
drush_backend_batch_process();
}
}
/**
@@ -335,11 +349,11 @@ class TaskManager implements TaskManagerInterface {
'Successfully executed @count pending task.',
'Successfully executed @count pending tasks.'
);
drupal_set_message($message);
$this->messenger->addStatus($message);
}
else {
// Notify the user about the batch job failure.
drupal_set_message($this->t('An error occurred while trying to execute tasks. Check the logs for details.'), 'error');
$this->messenger->addError($this->t('An error occurred while trying to execute tasks. Check the logs for details.'));
}
}
@@ -14,7 +14,7 @@ use Drupal\search_api\Processor\ProcessorInterface;
use Drupal\search_api\Query\QueryInterface;
use Drupal\search_api\Query\ResultSetInterface;
use Drupal\search_api\Tracker\TrackerInterface;
use Drupal\user\SharedTempStore;
use Drupal\Core\TempStore\SharedTempStore;
/**
* Represents a configuration of an index that was not yet permanently saved.
@@ -31,7 +31,7 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI
/**
* The shared temporary storage to use.
*
* @var \Drupal\user\SharedTempStore
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
@@ -61,7 +61,7 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI
*
* @param \Drupal\search_api\IndexInterface $index
* The index to proxy.
* @param \Drupal\user\SharedTempStore $temp_store
* @param \Drupal\Core\TempStore\SharedTempStore $temp_store
* The shared temporary storage to use.
* @param int|string $current_user_id
* Either the UID of the currently logged-in user, or the session ID (for
@@ -490,4 +490,11 @@ class FieldsHelper implements FieldsHelperInterface {
return $fieldId;
}
/**
* {@inheritdoc}
*/
public function compareFieldLabels(FieldInterface $a, FieldInterface $b) {
return strnatcasecmp($a->getLabel(), $b->getLabel());
}
}
@@ -257,4 +257,18 @@ interface FieldsHelperInterface {
*/
public function getNewFieldId(IndexInterface $index, $propertyPath);
/**
* Compares two fields for alphabetic sorting according to their labels.
*
* @param \Drupal\search_api\Item\FieldInterface $a
* The first field.
* @param \Drupal\search_api\Item\FieldInterface $b
* The second field.
*
* @return int
* -1, 0 or 1 if the first field should, respectively, be considered less
* than, equal to or greater than the second.
*/
public function compareFieldLabels(FieldInterface $a, FieldInterface $b);
}
@@ -3,16 +3,15 @@
namespace Drupal\search_api\Utility;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\search_api\LoggerTrait;
use Drupal\search_api\SearchApiException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Provides a service for indexing items at the end of the page request.
*/
class PostRequestIndexing implements PostRequestIndexingInterface, EventSubscriberInterface {
class PostRequestIndexing implements PostRequestIndexingInterface, DestructableInterface {
use LoggerTrait;
@@ -46,20 +45,7 @@ class PostRequestIndexing implements PostRequestIndexingInterface, EventSubscrib
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::TERMINATE][] = ['onKernelTerminate'];
return $events;
}
/**
* Indexes all items that were registered for indexing in this page request.
*
* Invoked by the terminate kernel event.
*
* @see \Symfony\Component\HttpKernel\Event\PostResponseEvent
*/
public function onKernelTerminate() {
public function destruct() {
foreach ($this->operations as $index_id => $item_ids) {
try {
$storage = $this->entityTypeManager->getStorage('search_api_index');
@@ -3,12 +3,11 @@
namespace Drupal\search_api\Utility;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Plugin\search_api\data_type\value\TextToken;
use Symfony\Component\DependencyInjection\TaggedContainerInterface;
/**
* Contains utility methods for the Search API.
@@ -167,31 +166,43 @@ class Utility {
}
$config_key = $entity_type->getConfigPrefix() . '.' . $entity->id();
$overrides = [];
// Overrides from tagged services.
$container = \Drupal::getContainer();
if ($container instanceof TaggedContainerInterface) {
$tags = $container->findTaggedServiceIds('config.factory.override');
foreach (array_keys($tags) as $service_id) {
$override = $container->get($service_id);
if ($override instanceof ConfigFactoryOverrideInterface) {
$service_overrides = $override->loadOverrides([$config_key]);
if (!empty($service_overrides[$config_key])) {
// Existing overrides take precedence since these will have been
// added by events with a higher priority.
$arrays = [$service_overrides[$config_key], $overrides];
$overrides = NestedArray::mergeDeepArray($arrays, TRUE);
}
}
}
$config = \Drupal::config($config_key);
if (!$config->hasOverrides()) {
return [];
}
// Overrides from settings.php. (This takes precedence over overrides from
// services.)
if (isset($GLOBALS['config'][$config_key])) {
$arrays = [$overrides, $GLOBALS['config'][$config_key]];
$overrides = NestedArray::mergeDeepArray($arrays, TRUE);
return static::collectOverrides($config, $config->get());
}
/**
* Collects overrides from a config object.
*
* @param \Drupal\Core\Config\Config $config
* The config object.
* @param array $values
* The array of values for the given $prefix.
* @param array $overrides
* (optional) The overrides collected so far. Internal use only.
* @param string $prefix
* (optional) The config key prefix for the current call level. Internal
* use only.
*
* @return array
* An associative array mapping property names to their overridden values.
*/
protected static function collectOverrides(Config $config, array $values, array $overrides = [], $prefix = '') {
foreach ($values as $key => $value) {
$key = "$prefix$key";
if (!$config->hasOverrides($key)) {
continue;
}
if (is_array($value)) {
NestedArray::setValue($overrides, explode('.', $key), []);
$overrides = static::collectOverrides($config, $value, $overrides, "$key.");
}
else {
NestedArray::setValue($overrides, explode('.', $key), $value);
}
}
return $overrides;
@@ -207,4 +218,34 @@ class Utility {
return php_sapi_name() === 'cli';
}
/**
* Checks whether a certain value matches the configuration.
*
* This unifies checking for matches with the common configuration pattern of
* having one "All except those selected"/"Only the selected" option
* ("default") and a list of options to select.
*
* @param mixed $value
* The value to check.
* @param array $settings
* The settings to check against, as an associative array with the following
* keys:
* - default: Boolean defining the default for not-selected items. TRUE
* means "All except those selected", FALSE means "Only the selected".
* Defaults to TRUE.
* - selected: A numerically indexed array of the selected options. Defaults
* to an empty array.
*
* @return bool
* TRUE if the value matches according to the configuration, FALSE
* otherwise.
*/
public static function matches($value, array $settings) {
$settings += [
'default' => TRUE,
'selected' => [],
];
return in_array($value, $settings['selected']) != $settings['default'];
}
}
@@ -7,8 +7,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -5,7 +5,7 @@ backend: search_api_db
backend_config:
database: 'default:default'
min_chars: 3
partial_matches: false
matching: words
status: true
langcode: en
dependencies:
@@ -8,8 +8,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -7,8 +7,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -7,8 +7,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -7,8 +7,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -31,5 +31,5 @@ function search_api_test_hooks_search_api_query_andrew_hill_alter(QueryInterface
* Implements hook_search_api_results_TAG_alter().
*/
function search_api_test_hooks_search_api_results_andrew_hill_alter(ResultSetInterface &$results) {
drupal_set_message('Llama');
\Drupal::messenger()->addStatus('Llama');
}
@@ -70,22 +70,22 @@ function search_api_test_hooks_search_api_displays_alter(array &$displays) {
*/
function search_api_test_hooks_search_api_index_items_alter(IndexInterface $index, array &$items) {
unset($items['entity:node/1:en']);
drupal_set_message('Stormy');
\Drupal::messenger()->addStatus('Stormy');
}
/**
* Implements hook_search_api_items_indexed().
*/
function search_api_test_hooks_search_api_items_indexed(IndexInterface $index, array $item_ids) {
drupal_set_message('Please set me at ease');
\Drupal::messenger()->addStatus('Please set me at ease');
}
/**
* Implements hook_search_api_query_alter().
*/
function search_api_test_hooks_search_api_query_alter(QueryInterface &$query) {
drupal_set_message('Funky blue note');
drupal_set_message(sprintf('Search id: %s', $query->getSearchId(FALSE)));
\Drupal::messenger()->addStatus('Funky blue note');
\Drupal::messenger()->addStatus("Search id: {$query->getSearchId(FALSE)}");
$query->addTag('andrew_hill');
}
@@ -93,14 +93,14 @@ function search_api_test_hooks_search_api_query_alter(QueryInterface &$query) {
* Implements hook_search_api_results_alter().
*/
function search_api_test_hooks_search_api_results_alter(ResultSetInterface &$results) {
drupal_set_message('Stepping into tomorrow');
\Drupal::messenger()->addStatus('Stepping into tomorrow');
}
/**
* Implements hook_search_api_index_reindex().
*/
function search_api_test_hooks_search_api_index_reindex(IndexInterface $index, $clear = FALSE) {
drupal_set_message('Montara');
\Drupal::messenger()->addStatus('Montara');
}
/**
@@ -0,0 +1,33 @@
id: inconsistent_search_index
name: 'Inconsistent search index'
description: 'An index used for testing'
read_only: false
field_settings:
unknown_property:
label: 'Unknown property'
type: integer
datasource_id: 'entity:entity_test_mulrev_changed'
property_path: unknown_property
processor_settings:
add_url:
weights:
preprocess_index: -30
aggregated_field:
weights:
add_properties: 20
search_api_test: { }
options:
cron_limit: -1
index_directly: false
datasource_settings:
'entity:entity_test_mulrev_changed': { }
tracker_settings:
'default': { }
server: inconsistent_search_server
status: true
langcode: en
dependencies:
config:
- search_api.server.inconsistent_search_server
module:
- entity_test
@@ -0,0 +1,10 @@
id: inconsistent_search_server
name: 'Inconsistent search server'
description: 'A server used for testing'
backend: search_api_test
backend_config: { }
status: false
langcode: en
dependencies:
module:
- search_api_test
@@ -0,0 +1,15 @@
name: 'Search API Inconsistent Configs Test'
type: module
description: 'Support module for Search API tests'
package: Search
dependencies:
- search_api:search_api
- search_api:search_api_test
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1531985591
@@ -7,8 +7,8 @@ dependencies:
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -6,12 +6,13 @@ dependencies:
- search_api:search_api
- search_api:search_api_test_db
- drupal:node
- drupal:rest
- drupal:views
# core: 8.x
hidden: true
# Information added by Drupal.org packaging script on 2018-02-23
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2018-07-19
version: '8.x-1.9'
core: '8.x'
project: 'search_api'
datestamp: 1519387691
datestamp: 1531985591
@@ -22,6 +22,6 @@ function search_api_test_views_search_api_query_alter(QueryInterface $query) {
$message[] = "'$combined_property_path'";
}
}
drupal_set_message(implode(' ', $message));
\Drupal::messenger()->addStatus(implode(' ', $message));
}
}
@@ -17,6 +17,13 @@ trait ExampleContentTrait {
*/
protected $entities = [];
/**
* The Search API item IDs of the generated entities.
*
* @var string[]
*/
protected $ids = [];
/**
* Sets up the necessary bundles on the test entity type.
*/
@@ -82,10 +89,12 @@ trait ExampleContentTrait {
* The created entity.
*/
protected function addTestEntity($id, array $values) {
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_mulrev_changed');
$entity_type = 'entity_test_mulrev_changed';
$storage = \Drupal::entityTypeManager()->getStorage($entity_type);
$values['id'] = $id;
$this->entities[$id] = $storage->create($values);
$this->entities[$id]->save();
$this->ids[$id] = Utility::createCombinedId("entity:$entity_type", "$id:en");
return $this->entities[$id];
}
@@ -114,8 +123,9 @@ trait ExampleContentTrait {
* An array of item IDs.
*/
protected function getItemIds(array $entity_ids) {
$translate_ids = function ($entity_id) {
return Utility::createCombinedId('entity:entity_test_mulrev_changed', $entity_id . ':en');
$map = $this->ids;
$translate_ids = function ($entity_id) use ($map) {
return $map[$entity_id];
};
return array_map($translate_ids, $entity_ids);
}
@@ -1206,6 +1206,7 @@ class IntegrationTest extends SearchApiBrowserTestBase {
$this->submitForm($edit, 'Save');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('All content was scheduled for reindexing so the new settings can take effect.');
$this->assertSession()->responseContains($this->getIndex()->toUrl()->toString());
}
/**
@@ -31,7 +31,6 @@ class ViewsTest extends SearchApiBrowserTestBase {
public static $modules = [
'block',
'language',
'rest',
'search_api_test_views',
'views_ui',
];
@@ -86,9 +86,8 @@ class BasicTrackerTest extends KernelTestBase {
public function testTracking($indexing_order) {
// Add a logger that throws an exception when used, so a caught exception
// within any of the tracker methods will still cause a test fail.
// @todo Change to createMock() once we depend on Drupal 8.5+. See #2930839.
/** @var \PHPUnit_Framework_MockObject_MockObject|\Psr\Log\LoggerInterface $logger */
$logger = $this->getMock(LoggerInterface::class);
$logger = $this->createMock(LoggerInterface::class);
$logger->method('log')
->willReturnCallback(function ($level, $message, array $variables) {
$error = 'Tracking operation threw ';
@@ -318,7 +317,7 @@ class BasicTrackerTest extends KernelTestBase {
$this->tracker->setDatabaseConnection($connection);
/** @var \PHPUnit_Framework_MockObject_MockObject|\Psr\Log\LoggerInterface $logger */
$logger = $this->getMock(LoggerInterface::class);
$logger = $this->createMock(LoggerInterface::class);
$log = [];
$logger->method('log')->willReturnCallback(function () use (&$log) {
$log[] = func_get_args();
@@ -197,7 +197,12 @@ class ConfigOverrideKernelTest extends KernelTestBase {
// Verify that overriding "status" prevents the server's indexes from being
// disabled when attempting to disable the server.
$GLOBALS['config']['search_api.server.test_server']['status'] = TRUE;
$this->server->disable()->save();
\Drupal::configFactory()->clearStaticCache();
/** @var \Drupal\search_api\Entity\SearchApiConfigEntityStorage $server_storage */
$server_storage = \Drupal::entityTypeManager()
->getStorage('search_api_server');
$server = $server_storage->loadOverrideFree($server->id());
$server->disable()->save();
\Drupal::configFactory()->clearStaticCache();
$index = Index::load($this->index->id());
$this->assertTrue($index->status());
@@ -206,9 +211,6 @@ class ConfigOverrideKernelTest extends KernelTestBase {
// Verify that overrides are not present when loading the server
// override-free.
/** @var \Drupal\search_api\Entity\SearchApiConfigEntityStorage $server_storage */
$server_storage = \Drupal::entityTypeManager()
->getStorage('search_api_server');
$server = $server_storage->loadOverrideFree($server->id());
$this->assertEquals('Test server', $server->label());
}
@@ -0,0 +1,85 @@
<?php
namespace Drupal\Tests\search_api\Kernel\ConfigEntity;
use Drupal\Core\Serialization\Yaml;
use Drupal\KernelTests\KernelTestBase;
use Drupal\search_api\Entity\Index;
use Drupal\search_api\Entity\Server;
use Drupal\search_api_test\PluginTestTrait;
/**
* Tests that installing config entities from an extension works correctly.
*
* @group search_api
*/
class DefaultConfigEntityInstallationTest extends KernelTestBase {
use PluginTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = [
'entity_test',
'search_api',
'search_api_test',
'search_api_test_inconsistent_config',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_mulrev_changed');
$this->installEntitySchema('search_api_task');
$this->installConfig('search_api');
}
/**
* Tests that installing config entities from an extension works correctly.
*/
public function testExtensionInstallation() {
$this->installConfig('search_api_test_inconsistent_config');
$index = Index::load('inconsistent_search_index');
$this->assertNotNull($index);
$values = $index->toArray();
// Make sure the index was not processed at all upon saving.
$this->assertTrue($index->status());
$this->assertArrayNotHasKey('rendered_item', $values['processor_settings']);
$this->assertArrayHasKey('unknown_property', $values['field_settings']);
$methods = $this->getCalledMethods('processor');
$this->assertNotContains('preIndexSave', $methods);
}
/**
* Tests that creating new config entities directly works correctly.
*/
public function testNormalEntityCreation() {
$dir = __DIR__ . '/../../../search_api_test_inconsistent_config/config/install/';
$yaml_file = $dir . 'search_api.server.inconsistent_search_server.yml';
$values = Yaml::decode(file_get_contents($yaml_file));
Server::create($values)->save();
$yaml_file = $dir . 'search_api.index.inconsistent_search_index.yml';
$values = Yaml::decode(file_get_contents($yaml_file));
Index::create($values)->save();
$index = Index::load('inconsistent_search_index');
$this->assertNotNull($index);
$values = $index->toArray();
// Make sure the index was not processed at all upon saving.
$this->assertFalse($index->status());
$this->assertArrayHasKey('rendered_item', $values['processor_settings']);
$this->assertArrayNotHasKey('unknown_property', $values['field_settings']);
$methods = $this->getCalledMethods('processor');
$this->assertContains('preIndexSave', $methods);
}
}
@@ -2,6 +2,8 @@
namespace Drupal\Tests\search_api\Kernel\ConfigEntity;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\KernelTests\KernelTestBase;
use Drupal\field\Entity\FieldConfig;
@@ -304,8 +306,8 @@ class DependencyRemovalTest extends KernelTestBase {
// store (the index will purge any unsaved configuration of it upon
// deletion, which uses a "user-shared temp store", which in turn uses a
// key/value store).
$mock = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface');
$mock_factory = $this->getMock('Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface');
$mock = $this->createMock(KeyValueStoreExpirableInterface::class);
$mock_factory = $this->createMock(KeyValueExpirableFactoryInterface::class);
$mock_factory->method('get')->willReturn($mock);
$this->container->set('keyvalue.expirable', $mock_factory);
@@ -169,6 +169,61 @@ class ContentEntityDatasourceTest extends KernelTestBase {
}
}
/**
* Tests viewing of items.
*
* @covers ::viewItem
* @covers ::viewMultipleItems
*/
public function testItemViewing() {
$loaded_items = $this->datasource->loadMultiple($this->allItemIds);
$builder = \Drupal::entityTypeManager()
->getViewBuilder('entity_test_mulrev_changed');
$item = $loaded_items['1:l0'];
$build = $this->datasource->viewItem($item, 'foobar');
$expected = [
'#entity_test_mulrev_changed' => $item->getValue(),
'#view_mode' => 'foobar',
'#cache' => [
'tags' => [
'entity_test_mulrev_changed:1',
'entity_test_mulrev_changed_view',
],
'contexts' => [],
'max-age' => -1,
],
'#weight' => 0,
'#pre_render' => [
[$builder, 'build'],
],
];
$this->assertEquals($expected, $build);
$build = $this->datasource->viewMultipleItems($loaded_items, 'foobar');
$this->assertTrue($build['#sorted']);
$this->assertEquals([[$builder, 'buildMultiple']], $build['#pre_render']);
foreach ($loaded_items as $item_id => $item) {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $item->getValue();
$expected = [
'#entity_test_mulrev_changed' => $entity,
'#view_mode' => 'foobar',
'#cache' => [
'tags' => [
'entity_test_mulrev_changed:' . $entity->id(),
'entity_test_mulrev_changed_view',
],
'contexts' => [],
'max-age' => -1,
],
];
$this->assertArrayHasKey('#weight', $build[$item_id]);
unset($build[$item_id]['#weight']);
$this->assertEquals($expected, $build[$item_id]);
}
}
/**
* Verifies that paged item discovery works correctly.
*
@@ -93,6 +93,9 @@ class IndexImportTest extends KernelTestBase {
];
$sync->write('search_api.index.database_search_index', $import_config);
// The system.site key is required for import validation.
$sync->write('system.site', []);
// Import the test configuration.
$config_importer = $this->configImporter();
$this->assertTrue($config_importer->hasUnprocessedConfigurationChanges(), 'Import prepared');
@@ -12,7 +12,7 @@ trait PostRequestIndexingTrait {
*/
protected function triggerPostRequestIndexing() {
\Drupal::getContainer()->get('search_api.post_request_indexing')
->onKernelTerminate();
->destruct();
}
}
@@ -0,0 +1,63 @@
<?php
namespace Drupal\Tests\search_api\Kernel\Processor;
use Drupal\node\Entity\Node;
use Drupal\search_api\Item\Field;
use Drupal\search_api\Utility\Utility;
/**
* Tests the "Add URL" processor at a higher level.
*
* @group search_api
*
* @coversDefaultClass \Drupal\search_api\Plugin\search_api\processor\AddURL
*/
class AddURLKernelTest extends ProcessorTestBase {
/**
* The nodes created for testing.
*
* @var \Drupal\node\Entity\Node[]
*/
protected $nodes;
/**
* {@inheritdoc}
*/
public function setUp($processor = NULL) {
parent::setUp('add_url');
$url_field = new Field($this->index, 'url');
$url_field->setType('string');
$url_field->setPropertyPath('search_api_url');
$url_field->setLabel('Item URL');
$this->index->addField($url_field);
$this->index->save();
$this->nodes[0] = Node::create([
'title' => 'Test',
'type' => 'article',
]);
$this->nodes[0]->save();
}
/**
* Tests extracting the field for a search item.
*
* @covers ::addFieldValues
*/
public function testItemFieldExtraction() {
$node = $this->nodes[0];
$id = Utility::createCombinedId('entity:node', $node->id() . ':en');
$item = \Drupal::getContainer()
->get('search_api.fields_helper')
->createItemFromObject($this->index, $node->getTypedData(), $id);
// Extract field values and check the value of the URL field.
$fields = $item->getFields();
$expected = [$node->toUrl('canonical')->toString()];
$this->assertEquals($expected, $fields['url']->getValues());
}
}
@@ -2,6 +2,7 @@
namespace Drupal\Tests\search_api\Kernel\System;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\search_api\Entity\Index;
use Drupal\search_api\Entity\Server;
@@ -102,18 +103,15 @@ class QueryTest extends KernelTestBase {
$this->assertEquals($level, $query->getProcessingLevel());
$query->addTag('andrew_hill');
// @todo Use \Drupal::messenger() once we depend on Drupal 8.5+. See
// #2931730.
drupal_get_messages();
\Drupal::messenger()->deleteAll();
$query->execute();
$messages = drupal_get_messages();
$messages = \Drupal::messenger()->all();
\Drupal::messenger()->deleteAll();
$methods = $this->getCalledMethods('processor');
if ($hooks_and_processors_invoked) {
// @todo Replace "status" with MessengerInterface::TYPE_STATUS once we
// depend on Drupal 8.5+. See #2931730.
$expected = [
'status' => [
MessengerInterface::TYPE_STATUS => [
'Funky blue note',
'Search id: ',
'Stepping into tomorrow',
@@ -225,4 +223,31 @@ class QueryTest extends KernelTestBase {
$this->assertInstanceOf('Drupal\search_api_test\Plugin\search_api\display\TestDisplay', $query->getDisplayPlugin());
}
/**
* Tests the getOriginalQuery() method.
*/
public function testGetOriginalQuery() {
$this->getCalledMethods('backend');
$query = $this->index->query()
->addCondition('search_api_id', 2, '<>');
$query_clone_1 = $query->getOriginalQuery();
$this->assertEquals($query, $query_clone_1);
$this->assertNotSame($query, $query_clone_1);
$query->sort('search_api_id');
$query_clone_2 = clone $query;
$query->execute();
$methods = $this->getCalledMethods('backend');
$this->assertEquals(['search'], $methods);
$this->assertFalse($query_clone_1->hasExecuted());
$original_query = $query->getOriginalQuery();
$this->assertEquals($query_clone_2, $original_query);
$this->assertFalse($original_query->hasExecuted());
$original_query->execute();
$methods = $this->getCalledMethods('backend');
$this->assertEquals(['search'], $methods);
}
}

Some files were not shown because too many files have changed in this diff Show More