contrib modules updates
This commit is contained in:
@@ -1,3 +1,74 @@
|
||||
Search API 1.11 (2018-11-18):
|
||||
-----------------------------
|
||||
- #3012778 by Shashwat Purav, pamatt, drunken monkey: Fixed several doc comment
|
||||
typos.
|
||||
- #3011340 by pfrenssen, idimopoulos, drunken monkey: Fixed problem when
|
||||
updating multiple indexes' datasource settings via "drush cim".
|
||||
- #2986623 by drunken monkey, valic: Added the "Reverse entity references"
|
||||
processor.
|
||||
- #3009136 by drunken monkey, mkalkbrenner, borisson_: Added the
|
||||
"search_api_retrieved_field_values" query option.
|
||||
- #3006267 by idebr, drunken monkey: Removed conditional code for Drupal 8.5.
|
||||
- #3007311 by drunken monkey: Fixed catch clause in
|
||||
PostRequestIndexing::destruct().
|
||||
- #2994022 by drunken monkey, Watergate, borisson_: Fixed facets when there are
|
||||
no search results in Database backend.
|
||||
- #2898917 by Lennard Westerveld, RoSk0, drunken monkey: Fixed Views filters
|
||||
with checkboxes and "is all of" operator.
|
||||
- #3001259 by klausi, drunken monkey, borisson_: Fixed HTML escaping throughout
|
||||
the module.
|
||||
- #3001030 by mkalkbrenner, drunken monkey, mvantuch: Fixed excerpt not taking
|
||||
Solr filters into account.
|
||||
- #3004017 by idebr, drunken monkey: Fixed server/index config schemas to use
|
||||
UUID field from Core definition.
|
||||
- #2971619 by idebr, drunken monkey: Refactored the access check for the
|
||||
"Execute pending tasks" local action.
|
||||
- #2995342 by AndrewsizZ, drunken monkey, borisson_: Added stripping of
|
||||
script/style HTML tags in excerpts to Highlight processor.
|
||||
|
||||
Search API 1.10 (2018-09-17):
|
||||
-----------------------------
|
||||
- #2993787 by drunken monkey, borisson_, mkalkbrenner: Added the possibility to
|
||||
boost fields in MLT queries.
|
||||
- #2936631 by drunken monkey, borisson_: Added support for the "no_ui"
|
||||
definition key for plugins.
|
||||
- #2996936 by drunken monkey, borisson_: Added suggested packages to
|
||||
composer.json.
|
||||
- #2987156 by hchonov, drunken monkey, borisson_: Fixed missing dependency on
|
||||
modules providing fields.
|
||||
- #2994638 by drunken monkey, Grayle, borisson_: Fixed warning in aggregated
|
||||
fields without values.
|
||||
- #2987237 by quicksketch, drunken monkey, borisson_: Fixed requirements hook
|
||||
for the Taxonomy Term Handlers module preventing updates from running.
|
||||
- #2992662 by capysara, drunken monkey, borisson_: Fixed the reindexing link
|
||||
when saving index processors or fields.
|
||||
- #2989689 by drunken monkey, valthebald, borisson_: Fixed bug in Views cache
|
||||
plugin alter hook.
|
||||
- #2919676 by drunken monkey, borisson_: Fixed server entity link definitions.
|
||||
- #2996278 by drunken monkey: Removed one pointless variable.
|
||||
- #2987245 by drunken monkey: Fixed use of account_switcher service for
|
||||
switching the currently active account.
|
||||
- #2996907 by zerolab, drunken monkey: Fixed entity tracking using access
|
||||
checks.
|
||||
- #2993434 by jasonawant, drunken monkey: Fixed handling of comma-separated
|
||||
values in Views contextual filters.
|
||||
- #2983843 by drunken monkey, borisson_: Fixed suggestion of installing default
|
||||
module when dependencies aren't met.
|
||||
- #2979316 by drunken monkey, Sutharsan: Fixed highlighting problems with
|
||||
multi-byte characters.
|
||||
- #2986310 by capysara, drunken monkey: Added link to reindexing when saving
|
||||
fields.
|
||||
- #2986013 by tim-diels, drunken monkey, borisson_: Fixed casting of
|
||||
nun-numeric strings in the database backend.
|
||||
- #2983630 by chipway, borisson_, drunken monkey: Fixed format of System
|
||||
dependency in search_api.info.yml.
|
||||
- #2982660 by jhedstrom, drunken monkey, borisson_: Fixed indexed time of
|
||||
date-only fields.
|
||||
- #2986216 by drunken monkey: Added the "legacy" group to
|
||||
SearchApiDbUpdate8102Test.
|
||||
- #2920943 by drunken monkey: Fixed error handling for "Rendered item"
|
||||
processor.
|
||||
|
||||
Search API 1.9 (2018-07-19):
|
||||
----------------------------
|
||||
- #2956012 by drunken monkey, DuaelFr: Added uncustomized view modes as options
|
||||
|
||||
@@ -84,7 +84,7 @@ framework.
|
||||
- Datasources: src/Datasource
|
||||
- Data types: src/DataType
|
||||
- Displays: src/Display
|
||||
- ParseModes: src/ParseMode
|
||||
- Parse modes: src/ParseMode
|
||||
- Processors: src/Processor
|
||||
- Trackers: src/Tracker
|
||||
The display plugins are a bit of a special case there, because they aren't
|
||||
@@ -106,6 +106,32 @@ module's issue regarding potential module-specific changes to that policy [7].
|
||||
[6] https://www.drupal.org/core/d8-bc-policy
|
||||
[7] https://www.drupal.org/node/2871549
|
||||
|
||||
SERVER BACKEND FEATURES
|
||||
-----------------------
|
||||
|
||||
Server backend features are a way for other contrib modules to cleanly define
|
||||
ways in which the Search API can be extended. For more information, see [8].
|
||||
|
||||
[8] https://www.drupal.org/docs/8/modules/search-api/getting-started/server-backends-and-features
|
||||
|
||||
The Search API module itself currently defines one feature:
|
||||
|
||||
- More Like This (search_api_mlt)
|
||||
This feature can be used to retrieve a list of search results that are similar
|
||||
to a given indexed item. A backend that supports this plugin has to recognize
|
||||
the "search_api_mlt" query option. If present, it contains an associative
|
||||
array with the following keys:
|
||||
- id: The Search API item ID (consisting of the datasource ID and the
|
||||
datasource-specific item ID – passing a plain entity ID will NOT work!) of
|
||||
the item for which similar results should be found.
|
||||
- fields: A simple array of fields which should be used for determining
|
||||
similarity. Backends can choose to ignore this field.
|
||||
- "field boosts": (optional) An associative array mapping fields to a numeric
|
||||
"boost" value that determines how important they should be considered when
|
||||
determining similarity. Backends can choose to ignore this field.
|
||||
The feature can be used in the UI via the "More like this" Views contextual
|
||||
filter.
|
||||
|
||||
MAINTAINERS
|
||||
-----------
|
||||
Current maintainers:
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
"require-dev": {
|
||||
"drupal/search_api_autocomplete": "@dev"
|
||||
},
|
||||
"suggest": {
|
||||
"drupal/facets": "Adds the ability to create faceted searches.",
|
||||
"drupal/search_api_autocomplete": "Allows adding autocomplete suggestions to search fields.",
|
||||
"drupal/search_api_solr": "Adds support for using Apache Solr as a backend."
|
||||
},
|
||||
"extra": {
|
||||
"drush": {
|
||||
"services": {
|
||||
|
||||
@@ -8,9 +8,6 @@ search_api.index.*:
|
||||
name:
|
||||
type: label
|
||||
label: Name'
|
||||
uuid:
|
||||
type: string
|
||||
label: 'UUID'
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
|
||||
@@ -8,9 +8,6 @@ search_api.server.*:
|
||||
name:
|
||||
type: label
|
||||
label: 'Name'
|
||||
uuid:
|
||||
type: string
|
||||
label: 'UUID'
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ package: Search
|
||||
dependencies:
|
||||
- search_api:search_api
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+3
-3
@@ -13,8 +13,8 @@ dependencies:
|
||||
- drupal:views
|
||||
- search_api:search_api_db
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+6
-54
@@ -5,7 +5,7 @@
|
||||
* Install, update and uninstall functions for the DB Search Defaults module.
|
||||
*/
|
||||
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\search_api\IndexListBuilder;
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
@@ -14,60 +14,12 @@ function search_api_db_defaults_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
if ($phase == 'install') {
|
||||
$node_types = NodeType::loadMultiple();
|
||||
$required_types = [
|
||||
'article' => ['body', 'comment', 'field_tags', 'field_image'],
|
||||
'page' => ['body'],
|
||||
];
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */
|
||||
$entity_field_manager = \Drupal::service('entity_field.manager');
|
||||
|
||||
foreach ($required_types as $required_type_id => $required_fields) {
|
||||
if (!isset($node_types[$required_type_id])) {
|
||||
$requirements['search_api_db_defaults:' . $required_type_id] = [
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('Content type @content_type not found. Database Search Defaults module could not be installed.', ['@content_type' => $required_type_id]),
|
||||
];
|
||||
}
|
||||
else {
|
||||
// Check if all the fields are here.
|
||||
$fields = $entity_field_manager->getFieldDefinitions('node', $required_type_id);
|
||||
foreach ($required_fields as $required_field) {
|
||||
if (!isset($fields[$required_field])) {
|
||||
$requirements['search_api_db_defaults:' . $required_type_id . ':' . $required_field] = [
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('Field @field in content type @node_type not found. Database Search Defaults module could not be installed', ['@node_type' => $required_type_id, '@field' => $required_field]),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\Drupal::moduleHandler()->moduleExists('search_api_db')) {
|
||||
$entities_to_check = [
|
||||
'search_api_index' => 'default_index',
|
||||
'search_api_server' => 'default_server',
|
||||
'view' => 'search_content',
|
||||
$errors = IndexListBuilder::checkDefaultsModuleCanBeInstalled();
|
||||
foreach ($errors as $type => $error) {
|
||||
$requirements["search_api_db_defaults:$type"] = [
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => $error,
|
||||
];
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */
|
||||
$entity_type_manager = \Drupal::service('entity_type.manager');
|
||||
foreach ($entities_to_check as $entity_type => $entity_id) {
|
||||
// Find out if the entity is already in place. If so, fail to install the
|
||||
// module.
|
||||
$entity_storage = $entity_type_manager->getStorage($entity_type);
|
||||
$entity_storage->resetCache();
|
||||
$entity = $entity_storage->load($entity_id);
|
||||
|
||||
if ($entity) {
|
||||
$requirements['search_api_db_defaults:defaults_exist'] = [
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('It looks like the default setup provided by this module already exists on your site. Cannot re-install module.'),
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-22
@@ -3,7 +3,6 @@
|
||||
namespace Drupal\search_api_db\Plugin\search_api\backend;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Database\Database as CoreDatabase;
|
||||
@@ -1166,7 +1165,7 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// We just log the error, hoping we can index the other items.
|
||||
$this->getLogger()->warning(Html::escape($e->getMessage()));
|
||||
$this->getLogger()->warning($e->getMessage());
|
||||
}
|
||||
}
|
||||
return $indexed;
|
||||
@@ -1405,8 +1404,8 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
* @param mixed $value
|
||||
* The value to convert.
|
||||
* @param string $type
|
||||
* The type to convert to. One of the keys from
|
||||
* search_api_default_field_types().
|
||||
* The Search API type to convert to. (Has to be a type supported by this
|
||||
* backend.)
|
||||
* @param string $original_type
|
||||
* The value's original type.
|
||||
* @param \Drupal\search_api\IndexInterface $index
|
||||
@@ -1471,7 +1470,6 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
return $tokens;
|
||||
|
||||
case 'string':
|
||||
case 'uri':
|
||||
// For non-dates, PHP can handle this well enough.
|
||||
if ($original_type == 'date') {
|
||||
return date('c', $value);
|
||||
@@ -1484,9 +1482,10 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
|
||||
case 'integer':
|
||||
case 'date':
|
||||
case 'duration':
|
||||
return (int) $value;
|
||||
|
||||
case 'decimal':
|
||||
return 0 + $value;
|
||||
return (float) $value;
|
||||
|
||||
case 'boolean':
|
||||
return $value ? 1 : 0;
|
||||
@@ -1602,16 +1601,21 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
$results = $query->getResults();
|
||||
|
||||
$skip_count = $query->getOption('skip result count');
|
||||
$count = NULL;
|
||||
if (!$skip_count) {
|
||||
$count_query = $db_query->countQuery();
|
||||
$results->setResultCount($count_query->execute()->fetchField());
|
||||
$count = $count_query->execute()->fetchField();
|
||||
$results->setResultCount($count);
|
||||
}
|
||||
|
||||
if ($skip_count || $results->getResultCount()) {
|
||||
if ($query->getOption('search_api_facets')) {
|
||||
$results->setExtraData('search_api_facets', $this->getFacets($query, clone $db_query));
|
||||
}
|
||||
|
||||
// With a "min_count" of 0, some facets can even be available if there are
|
||||
// no results.
|
||||
if ($query->getOption('search_api_facets')) {
|
||||
$facets = $this->getFacets($query, clone $db_query, $count);
|
||||
$results->setExtraData('search_api_facets', $facets);
|
||||
}
|
||||
// Everything else can be skipped if the count is 0.
|
||||
if ($skip_count || $count) {
|
||||
$query_options = $query->getOptions();
|
||||
if (isset($query_options['offset']) || isset($query_options['limit'])) {
|
||||
$offset = isset($query_options['offset']) ? $query_options['offset'] : 0;
|
||||
@@ -2353,16 +2357,13 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
* The search query for which facets should be computed.
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface $db_query
|
||||
* A database select query which returns all results of that search query.
|
||||
* @param int|null $result_count
|
||||
* (optional) The total number of results of the search query, if known.
|
||||
*
|
||||
* @return array
|
||||
* An array of facets, as specified by the search_api_facets feature.
|
||||
*/
|
||||
protected function getFacets(QueryInterface $query, SelectInterface $db_query) {
|
||||
$table = $this->getTemporaryResultsTable($db_query);
|
||||
if (!$table) {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getFacets(QueryInterface $query, SelectInterface $db_query, $result_count = NULL) {
|
||||
$fields = $this->getFieldInfo($query->getIndex());
|
||||
$ret = [];
|
||||
foreach ($query->getOption('search_api_facets') as $key => $facet) {
|
||||
@@ -2374,8 +2375,33 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
$field = $fields[$facet['field']];
|
||||
|
||||
if (empty($facet['operator']) || $facet['operator'] != 'or') {
|
||||
// All the AND facets can use the main query.
|
||||
$select = $this->database->select($table, 't');
|
||||
// First, check whether this can even possibly have any results.
|
||||
if ($result_count !== NULL && $result_count < $facet['min_count']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// All the AND facets can use the main query. If we didn't yet create a
|
||||
// temporary table for them yet, do so now.
|
||||
if (!isset($table)) {
|
||||
$table = $this->getTemporaryResultsTable($db_query);
|
||||
}
|
||||
if ($table) {
|
||||
$select = $this->database->select($table, 't');
|
||||
}
|
||||
else {
|
||||
// If no temporary table could be created (most likely due to a
|
||||
// missing permission), use a nested query instead.
|
||||
$select = $this->database->select(clone $db_query, 't');
|
||||
}
|
||||
// In case we didn't get the result count passed to the method, we can
|
||||
// get it now. (This allows us to skip AND facets with a min_count
|
||||
// higher than the result count.)
|
||||
if ($result_count === NULL) {
|
||||
$result_count = $select->countQuery()->execute()->fetchField();
|
||||
if ($result_count < $facet['min_count']) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For OR facets, we need to build a different base query that excludes
|
||||
@@ -2388,7 +2414,13 @@ class Database extends BackendPluginBase implements PluginFormInterface {
|
||||
unset($conditions[$i]);
|
||||
}
|
||||
}
|
||||
$or_db_query = $this->createDbQuery($or_query, $fields);
|
||||
try {
|
||||
$or_db_query = $this->createDbQuery($or_query, $fields);
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$this->logException($e, '%type while trying to create a facets query: @message in %function (line %line of %file).');
|
||||
continue;
|
||||
}
|
||||
$select = $this->database->select($or_db_query, 't');
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ use Drupal\search_api_db\Tests\DatabaseTestsTrait;
|
||||
* Tests whether search_api_db_update_8102() works correctly.
|
||||
*
|
||||
* @group search_api
|
||||
* @group legacy
|
||||
*
|
||||
* @see https://www.drupal.org/node/2884451
|
||||
*/
|
||||
|
||||
+3
-3
@@ -8,8 +8,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+60
-4
@@ -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\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
|
||||
use Drupal\search_api\Entity\Index;
|
||||
use Drupal\search_api\Entity\Server;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
@@ -93,6 +94,7 @@ class BackendTest extends BackendTestBase {
|
||||
$this->regressionTest2926733();
|
||||
$this->regressionTest2938646();
|
||||
$this->regressionTest2925464();
|
||||
$this->regressionTest2994022();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -637,6 +639,54 @@ class BackendTest extends BackendTestBase {
|
||||
$this->indexItems($index->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing of field types.
|
||||
*
|
||||
* @see https://www.drupal.org/project/search_api/issues/2994022
|
||||
*/
|
||||
protected function regressionTest2994022() {
|
||||
$query = $this->buildSearch('nonexistent_search_term');
|
||||
$facets['category'] = [
|
||||
'field' => 'category',
|
||||
'limit' => 0,
|
||||
'min_count' => 0,
|
||||
'missing' => FALSE,
|
||||
'operator' => 'and',
|
||||
];
|
||||
$query->setOption('search_api_facets', $facets);
|
||||
$results = $query->execute();
|
||||
$this->assertResults([], $results, 'Non-existent keyword');
|
||||
$expected = [
|
||||
['count' => 0, 'filter' => '"article_category"'],
|
||||
['count' => 0, 'filter' => '"item_category"'],
|
||||
];
|
||||
$category_facets = $results->getExtraData('search_api_facets')['category'];
|
||||
usort($category_facets, [$this, 'facetCompare']);
|
||||
$this->assertEquals($expected, $category_facets, 'Correct facets were returned for minimum count 0');
|
||||
|
||||
$query = $this->buildSearch('nonexistent_search_term');
|
||||
$conditions = $query->createConditionGroup('AND', ['facet:category']);
|
||||
$conditions->addCondition('category', 'article_category');
|
||||
$query->addConditionGroup($conditions);
|
||||
$facets['category'] = [
|
||||
'field' => 'category',
|
||||
'limit' => 0,
|
||||
'min_count' => 0,
|
||||
'missing' => FALSE,
|
||||
'operator' => 'and',
|
||||
];
|
||||
$query->setOption('search_api_facets', $facets);
|
||||
$results = $query->execute();
|
||||
$this->assertResults([], $results, 'Non-existent keyword with filter');
|
||||
$expected = [
|
||||
['count' => 0, 'filter' => '"article_category"'],
|
||||
['count' => 0, 'filter' => '"item_category"'],
|
||||
];
|
||||
$category_facets = $results->getExtraData('search_api_facets')['category'];
|
||||
usort($category_facets, [$this, 'facetCompare']);
|
||||
$this->assertEquals($expected, $category_facets, 'Correct facets were returned for minimum count 0');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -723,7 +773,7 @@ class BackendTest extends BackendTestBase {
|
||||
$expected = [
|
||||
'search_api_db_database_search_index' => 'search_api_db_database_search_index',
|
||||
];
|
||||
$this->assertEquals($expected, $tables, 'All the tables of the the Database Search module have been removed.');
|
||||
$this->assertEquals($expected, $tables, 'All the tables of the Database Search module have been removed.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -850,14 +900,20 @@ class BackendTest extends BackendTestBase {
|
||||
// Test different input values, similar to @dataProvider (but with less
|
||||
// overhead).
|
||||
$t = 1400000000;
|
||||
$f = 'Y-m-d H:i:s';
|
||||
$date_time_format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
|
||||
$date_format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
|
||||
$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],
|
||||
'date string' => [gmdate($date_time_format, $t), $t],
|
||||
'date string with timezone' => [date($date_time_format . 'P', $t), $t],
|
||||
'date only' => [
|
||||
date($date_format, $t),
|
||||
// Date-only fields are stored with the default time (12:00:00).
|
||||
strtotime(date($date_format, $t) . 'T12:00:00+00:00'),
|
||||
],
|
||||
];
|
||||
|
||||
// Get storage information for quickly checking the indexed value.
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ dependencies:
|
||||
- drupal:taxonomy
|
||||
- drupal:views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+14
-9
@@ -10,15 +10,20 @@ use Drupal\Core\Url;
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function search_api_views_taxonomy_requirements() {
|
||||
$requirements['search_api_views_taxonomy'] = [
|
||||
'title' => t('Search API Taxonomy Term Handlers'),
|
||||
'value' => t('Out-dated'),
|
||||
'description' => t('All functionality of the "Search API Taxonomy Term Handlers" module has been moved into the Search API module. You should <a href=":uninstall">uninstall this module</a>.', [
|
||||
':uninstall' => Url::fromRoute('system.modules_uninstall')->toString(),
|
||||
]),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
];
|
||||
function search_api_views_taxonomy_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
// We don't want to prevent update.php from running.
|
||||
if ($phase !== 'update') {
|
||||
$requirements['search_api_views_taxonomy'] = [
|
||||
'title' => t('Search API Taxonomy Term Handlers'),
|
||||
'value' => t('Out-dated'),
|
||||
'description' => t('All functionality of the "Search API Taxonomy Term Handlers" module has been moved into the Search API module. You should <a href=":uninstall">uninstall this module</a>.', [
|
||||
':uninstall' => Url::fromRoute('system.modules_uninstall')->toString(),
|
||||
]),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
];
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ package: Search
|
||||
# core: 8.x
|
||||
configure: search_api.overview
|
||||
dependencies:
|
||||
- system (>=8.5)
|
||||
- drupal:system (>=8.5)
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
@@ -12,8 +12,7 @@ search_api.execute_tasks:
|
||||
_controller: '\Drupal\search_api\Controller\TaskController::executeTasks'
|
||||
_title: 'Execute pending tasks'
|
||||
requirements:
|
||||
_permission: 'administer search_api'
|
||||
_search_api_tasks: 'TRUE'
|
||||
_custom_access: '\Drupal\search_api\Controller\TaskController::executeTasksAccess'
|
||||
|
||||
entity.search_api_server.add_form:
|
||||
path: '/admin/config/search/search-api/add-server'
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
services:
|
||||
access_check.search_api_tasks:
|
||||
class: Drupal\search_api\Controller\ExecuteTasksAccessCheck
|
||||
arguments: ['@search_api.task_manager']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _search_api_tasks }
|
||||
|
||||
logger.channel.search_api:
|
||||
parent: logger.channel_base
|
||||
arguments: ['search_api']
|
||||
|
||||
@@ -119,7 +119,8 @@ function search_api_views_plugins_cache_alter(array &$plugins) {
|
||||
foreach (Index::loadMultiple() as $index) {
|
||||
$bases[] = 'search_api_index_' . $index->id();
|
||||
}
|
||||
$plugins['search_api']['base'] = $bases;
|
||||
$plugins['search_api_tag']['base'] = $bases;
|
||||
$plugins['search_api_time']['base'] = $bases;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api\Controller;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\search_api\Task\TaskManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides an access check for the "Execute pending tasks" route.
|
||||
*/
|
||||
class ExecuteTasksAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The tasks manager service.
|
||||
*
|
||||
* @var \Drupal\search_api\Task\TaskManagerInterface
|
||||
*/
|
||||
protected $tasksManager;
|
||||
|
||||
/**
|
||||
* Creates an ExecuteTasksAccessCheck object.
|
||||
*
|
||||
* @param \Drupal\search_api\Task\TaskManagerInterface $tasksManager
|
||||
* The tasks manager service.
|
||||
*/
|
||||
public function __construct(TaskManagerInterface $tasksManager) {
|
||||
$this->tasksManager = $tasksManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access() {
|
||||
// @todo Once #2722237 is fixed, see whether this can't just use the
|
||||
// "search_api_task_list" cache tag instead.
|
||||
if ($this->tasksManager->getTasksCount()) {
|
||||
return AccessResult::allowed()->setCacheMaxAge(0);
|
||||
}
|
||||
return AccessResult::forbidden()->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Drupal\search_api\Controller;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\search_api\Task\TaskManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
@@ -62,4 +64,23 @@ class TaskController extends ControllerBase {
|
||||
return batch_process(Url::fromRoute('search_api.overview'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for executing pending tasks.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* Run access checks for this account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function executeTasksAccess(AccountInterface $account) {
|
||||
// @todo Once we depend on Drupal 8.7+, check whether this can't just use
|
||||
// the "search_api_task_list" cache tag instead. (See #2722237.)
|
||||
if ($this->taskManager->getTasksCount()) {
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer search_api')
|
||||
->setCacheMaxAge(0);
|
||||
}
|
||||
return AccessResult::forbidden()->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Drupal\search_api\DataType;
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\search_api\Plugin\HideablePluginInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for data type plugins.
|
||||
@@ -14,7 +15,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
* @see \Drupal\search_api\DataType\DataTypePluginBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface DataTypeInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface {
|
||||
interface DataTypeInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns the label of the data type.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Drupal\search_api\DataType;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\search_api\Plugin\HideablePluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* @see \Drupal\search_api\DataType\DataTypeInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class DataTypePluginBase extends PluginBase implements DataTypeInterface {
|
||||
abstract class DataTypePluginBase extends HideablePluginBase implements DataTypeInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\search_api\Plugin\HideablePluginInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for display plugins.
|
||||
@@ -15,7 +16,7 @@ use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
* @see \Drupal\search_api\Display\DisplayPluginBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface DisplayInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
interface DisplayInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns the display label.
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace Drupal\search_api\Display;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\search_api\Plugin\HideablePluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* @see \Drupal\search_api\Display\DisplayInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class DisplayPluginBase extends PluginBase implements DisplayInterface {
|
||||
abstract class DisplayPluginBase extends HideablePluginBase implements DisplayInterface {
|
||||
|
||||
/**
|
||||
* The current path service.
|
||||
|
||||
@@ -51,12 +51,12 @@ use Drupal\search_api\Utility\Utility;
|
||||
* "backend_config",
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/admin/config/search/search-api/index/{search_api_server}",
|
||||
* "canonical" = "/admin/config/search/search-api/server/{search_api_server}",
|
||||
* "add-form" = "/admin/config/search/search-api/add-server",
|
||||
* "edit-form" = "/admin/config/search/search-api/index/{search_api_server}/edit",
|
||||
* "delete-form" = "/admin/config/search/search-api/index/{search_api_server}/delete",
|
||||
* "disable" = "/admin/config/search/search-api/index/{search_api_server}/disable",
|
||||
* "enable" = "/admin/config/search/search-api/index/{search_api_server}/enable",
|
||||
* "edit-form" = "/admin/config/search/search-api/server/{search_api_server}/edit",
|
||||
* "delete-form" = "/admin/config/search/search-api/server/{search_api_server}/delete",
|
||||
* "disable" = "/admin/config/search/search-api/server/{search_api_server}/disable",
|
||||
* "enable" = "/admin/config/search/search-api/server/{search_api_server}/enable",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
|
||||
@@ -187,6 +187,9 @@ class IndexFieldsForm extends EntityForm {
|
||||
|
||||
$data_types = [];
|
||||
foreach ($instances as $name => $type) {
|
||||
if ($type->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
$data_types[$name] = [
|
||||
'label' => $type->label(),
|
||||
'description' => $type->getDescription(),
|
||||
@@ -219,7 +222,6 @@ class IndexFieldsForm extends EntityForm {
|
||||
* The build structure.
|
||||
*/
|
||||
protected function buildFieldsTable(array $fields) {
|
||||
$types = $this->dataTypePluginManager->getInstancesOptions();
|
||||
$fallback_types = $this->dataTypeHelper
|
||||
->getDataTypeFallbackMapping($this->entity);
|
||||
|
||||
@@ -236,6 +238,7 @@ class IndexFieldsForm extends EntityForm {
|
||||
}
|
||||
}
|
||||
|
||||
$types = [];
|
||||
$fulltext_types = [
|
||||
[
|
||||
'value' => 'text',
|
||||
@@ -243,6 +246,9 @@ class IndexFieldsForm extends EntityForm {
|
||||
];
|
||||
// Add all data types with fallback "text" to fulltext types as well.
|
||||
foreach ($this->dataTypePluginManager->getInstances() as $type_id => $type) {
|
||||
if (!$type->isHidden()) {
|
||||
$types[$type_id] = $type->label();
|
||||
}
|
||||
if ($type->getFallbackType() == 'text') {
|
||||
$fulltext_types[] = [
|
||||
'value' => $type_id,
|
||||
@@ -488,7 +494,8 @@ class IndexFieldsForm extends EntityForm {
|
||||
|
||||
$this->messenger->addStatus($this->t('The changes were successfully saved.'));
|
||||
if ($this->entity->isReindexing()) {
|
||||
$this->messenger->addStatus($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
|
||||
$url = $this->entity->toUrl('canonical');
|
||||
$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()]));
|
||||
}
|
||||
|
||||
return SAVED_UPDATED;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
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;
|
||||
@@ -13,6 +12,7 @@ use Drupal\Core\Form\SubformState;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
use Drupal\search_api\Utility\PluginHelperInterface;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -96,7 +96,7 @@ class IndexForm extends EntityForm {
|
||||
->loadMultiple();
|
||||
foreach ($servers as $server_id => $server) {
|
||||
// @todo Special formatting for disabled servers.
|
||||
$options[$server_id] = Html::escape($server->label());
|
||||
$options[$server_id] = Utility::escapeHtml($server->label());
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
@@ -185,8 +185,11 @@ class IndexForm extends EntityForm {
|
||||
];
|
||||
$datasource_options = [];
|
||||
foreach ($this->pluginHelper->createDatasourcePlugins($index) as $datasource_id => $datasource) {
|
||||
$datasource_options[$datasource_id] = $datasource->label();
|
||||
$form['datasources'][$datasource_id]['#description'] = $datasource->getDescription();
|
||||
if ($datasource->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
$datasource_options[$datasource_id] = Utility::escapeHtml($datasource->label());
|
||||
$form['datasources'][$datasource_id]['#description'] = Utility::escapeHtml($datasource->getDescription());
|
||||
}
|
||||
asort($datasource_options, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
$form['datasources']['#options'] = $datasource_options;
|
||||
@@ -230,8 +233,11 @@ class IndexForm extends EntityForm {
|
||||
];
|
||||
$tracker_options = [];
|
||||
foreach ($this->pluginHelper->createTrackerPlugins($index) as $tracker_id => $tracker) {
|
||||
$tracker_options[$tracker_id] = $tracker->label();
|
||||
$form['tracker'][$tracker_id]['#description'] = $tracker->getDescription();
|
||||
if ($tracker->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
$tracker_options[$tracker_id] = Utility::escapeHtml($tracker->label());
|
||||
$form['tracker'][$tracker_id]['#description'] = Utility::escapeHtml($tracker->getDescription());
|
||||
}
|
||||
asort($tracker_options, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
$form['tracker']['#options'] = $tracker_options;
|
||||
@@ -419,7 +425,7 @@ class IndexForm extends EntityForm {
|
||||
|
||||
$form['tracker_config']['#type'] = 'details';
|
||||
$form['tracker_config']['#title'] = $this->t('Configure the %plugin tracker', ['%plugin' => $tracker->label()]);
|
||||
$form['tracker_config']['#description'] = Html::escape($tracker->getDescription());
|
||||
$form['tracker_config']['#description'] = Utility::escapeHtml($tracker->getDescription());
|
||||
$form['tracker_config']['#open'] = $index->isNew();
|
||||
|
||||
// If the user changed the tracker and the new one has a config form, show
|
||||
|
||||
@@ -336,7 +336,7 @@ class IndexProcessorsForm extends EntityForm {
|
||||
$save_status = parent::save($form, $form_state);
|
||||
$this->messenger->addStatus($this->t('The indexing workflow was successfully edited.'));
|
||||
if ($this->entity->isReindexing()) {
|
||||
$url = $this->entity->toUrl();
|
||||
$url = $this->entity->toUrl('canonical');
|
||||
$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()]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\search_api\Form;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
@@ -12,6 +13,7 @@ use Drupal\Core\Url;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\search_api\Backend\BackendPluginManager;
|
||||
use Drupal\search_api\ServerInterface;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -140,10 +142,19 @@ class ServerForm extends EntityForm {
|
||||
foreach ($backends as $backend_id => $definition) {
|
||||
$config = $backend_id === $server->getBackendId() ? $server->getBackendConfig() : [];
|
||||
$config['#server'] = $server;
|
||||
$backend = $this->backendPluginManager
|
||||
->createInstance($backend_id, $config);
|
||||
$backend_options[$backend_id] = $backend->label();
|
||||
$descriptions[$backend_id]['#description'] = $backend->getDescription();
|
||||
try {
|
||||
/** @var \Drupal\search_api\Backend\BackendInterface $backend */
|
||||
$backend = $this->backendPluginManager
|
||||
->createInstance($backend_id, $config);
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
continue;
|
||||
}
|
||||
if ($backend->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
$backend_options[$backend_id] = Utility::escapeHtml($backend->label());
|
||||
$descriptions[$backend_id]['#description'] = Utility::escapeHtml($backend->getDescription());
|
||||
}
|
||||
asort($backend_options, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
if ($backend_options) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\search_api;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
@@ -9,6 +10,7 @@ use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -50,6 +52,75 @@ class IndexListBuilder extends ConfigEntityListBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the "Database Search Defaults" module can be installed.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
|
||||
* An array of error messages describing why the module cannot be installed,
|
||||
* keyed by a short, machine name-like identifier for the kind of error. If
|
||||
* the array is empty, the module can be installed.
|
||||
*/
|
||||
public static function checkDefaultsModuleCanBeInstalled() {
|
||||
$errors = [];
|
||||
|
||||
$node_types = NodeType::loadMultiple();
|
||||
$required_types = [
|
||||
'article' => ['body', 'comment', 'field_tags', 'field_image'],
|
||||
'page' => ['body'],
|
||||
];
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */
|
||||
$entity_field_manager = \Drupal::service('entity_field.manager');
|
||||
|
||||
foreach ($required_types as $required_type_id => $required_fields) {
|
||||
if (!isset($node_types[$required_type_id])) {
|
||||
$errors[$required_type_id] = t('Content type @content_type not found. Database Search Defaults module could not be installed.', ['@content_type' => $required_type_id]);
|
||||
}
|
||||
else {
|
||||
// Check if all the fields are here.
|
||||
$fields = $entity_field_manager->getFieldDefinitions('node', $required_type_id);
|
||||
foreach ($required_fields as $required_field) {
|
||||
if (!isset($fields[$required_field])) {
|
||||
$errors[$required_type_id . ':' . $required_field] = t('Field @field in content type @node_type not found. Database Search Defaults module could not be installed', [
|
||||
'@node_type' => $required_type_id,
|
||||
'@field' => $required_field
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\Drupal::moduleHandler()->moduleExists('search_api_db')) {
|
||||
$entities_to_check = [
|
||||
'search_api_index' => 'default_index',
|
||||
'search_api_server' => 'default_server',
|
||||
'view' => 'search_content',
|
||||
];
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */
|
||||
$entity_type_manager = \Drupal::service('entity_type.manager');
|
||||
foreach ($entities_to_check as $entity_type => $entity_id) {
|
||||
try {
|
||||
// Find out if the entity is already in place. If so, fail to install
|
||||
// the module.
|
||||
$entity_storage = $entity_type_manager->getStorage($entity_type);
|
||||
$entity_storage->resetCache();
|
||||
$entity = $entity_storage->load($entity_id);
|
||||
if ($entity) {
|
||||
$errors['defaults_exist'] = t('It looks like the default setup provided by this module already exists on your site. Cannot re-install module.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
// This can only happen for the view, if the Views module isn't
|
||||
// installed. Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -160,7 +231,7 @@ class IndexListBuilder extends ConfigEntityListBuilder {
|
||||
'#type' => 'table',
|
||||
'#header' => $this->buildHeader(),
|
||||
'#rows' => [],
|
||||
'#empty' => $entity_groups['lone_indexes'] ? '' : $this->t('There are no servers or indexes defined. For a quick start, we suggest you install the Database Search Defaults module.'),
|
||||
'#empty' => '',
|
||||
'#attributes' => [
|
||||
'id' => 'search-api-entity-list',
|
||||
'class' => ['search-api-entity-list'],
|
||||
@@ -186,6 +257,14 @@ class IndexListBuilder extends ConfigEntityListBuilder {
|
||||
$list['lone_indexes']['table']['#rows'][$entity->id()] = $this->buildRow($entity);
|
||||
}
|
||||
}
|
||||
elseif (!$list['servers']['#rows']) {
|
||||
if (static::checkDefaultsModuleCanBeInstalled() === []) {
|
||||
$list['servers']['#empty'] = $this->t('There are no servers or indexes defined. For a quick start, we suggest you install the Database Search Defaults module.');
|
||||
}
|
||||
else {
|
||||
$list['servers']['#empty'] = $this->t('There are no servers or indexes defined.');
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
@@ -253,6 +253,8 @@ interface ItemInterface extends \Traversable {
|
||||
* highlighting data should also include any non-highlighted field values,
|
||||
* to avoid having to determine which values are included and which
|
||||
* aren't.
|
||||
* - highlighted_keys: The exact tokens that matched keys in this item's
|
||||
* text values. The value is an array of strings.
|
||||
* However, contrib modules can define arbitrary other keys. (Usually they
|
||||
* should be prefixed with the module name, though.)
|
||||
* @param mixed $default
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Drupal\search_api\ParseMode;
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\search_api\Plugin\HideablePluginInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for parse mode plugins.
|
||||
@@ -14,7 +15,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
* @see \Drupal\search_api\ParseMode\ParseModePluginBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ParseModeInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface {
|
||||
interface ParseModeInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns the label of the parse mode.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Drupal\search_api\ParseMode;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\search_api\Plugin\HideablePluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* @see \Drupal\search_api\ParseMode\ParseModeInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class ParseModePluginBase extends PluginBase implements ParseModeInterface {
|
||||
abstract class ParseModePluginBase extends HideablePluginBase implements ParseModeInterface {
|
||||
|
||||
/**
|
||||
* The default conjunction to use when parsing keywords.
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\PluginDependencyTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base class for all configurable Search API plugins.
|
||||
*/
|
||||
abstract class ConfigurablePluginBase extends PluginBase implements ConfigurablePluginInterface {
|
||||
abstract class ConfigurablePluginBase extends HideablePluginBase implements ConfigurablePluginInterface {
|
||||
|
||||
// Normally, we'd just need \Drupal\Core\Entity\DependencyTrait here for
|
||||
// plugins. However, in a few cases, plugins use plugins themselves, and then
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
* @see \Drupal\Core\Plugin\PluginFormInterface
|
||||
* @see \Drupal\search_api\Plugin\PluginFormTrait
|
||||
*/
|
||||
interface ConfigurablePluginInterface extends PluginInspectionInterface, DerivativeInspectionInterface, DrupalConfigurablePluginInterface, ContainerFactoryPluginInterface {
|
||||
interface ConfigurablePluginInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, DrupalConfigurablePluginInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns the label for use on the administration pages.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api\Plugin;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
|
||||
/**
|
||||
* Provides a base class for plugins that can be hidden.
|
||||
*/
|
||||
class HideablePluginBase extends PluginBase implements HideablePluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isHidden() {
|
||||
return !empty($this->pluginDefinition['no_ui']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an interface for plugins that can be hidden.
|
||||
*/
|
||||
interface HideablePluginInterface {
|
||||
|
||||
/**
|
||||
* Determines whether this plugin should be hidden in the UI.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this processor should be hidden from the user; FALSE otherwise.
|
||||
*/
|
||||
public function isHidden();
|
||||
|
||||
}
|
||||
+9
-2
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin\search_api\data_type;
|
||||
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
|
||||
use Drupal\search_api\DataType\DataTypePluginBase;
|
||||
|
||||
/**
|
||||
@@ -26,8 +28,13 @@ class DateDataType extends DataTypePluginBase {
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
$timezone = new \DateTimezone('UTC');
|
||||
$date = new \Datetime($value, $timezone);
|
||||
|
||||
$timezone = new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE);
|
||||
$date = new DateTimePlus($value, $timezone);
|
||||
// Add in time component if this is a date-only field.
|
||||
if (strpos($value, ':') === FALSE) {
|
||||
$date->setDefaultDateTime();
|
||||
}
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -11,15 +11,15 @@ interface TextTokenInterface {
|
||||
* Retrieves the text value of this token.
|
||||
*
|
||||
* @return string
|
||||
* The the text value of this token.
|
||||
* The text value of this token.
|
||||
*/
|
||||
public function getText();
|
||||
|
||||
/**
|
||||
* Sets the the text value of this token.
|
||||
* Sets the text value of this token.
|
||||
*
|
||||
* @param string $text
|
||||
* The new the text value of this token.
|
||||
* The new text value of this token.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
|
||||
+12
-2
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin\search_api\datasource;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
@@ -11,6 +10,7 @@ use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
@@ -507,7 +507,7 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
|
||||
$options = [];
|
||||
if (($bundles = $this->getEntityBundles())) {
|
||||
foreach ($bundles as $bundle => $bundle_info) {
|
||||
$options[$bundle] = Html::escape($bundle_info['label']);
|
||||
$options[$bundle] = Utility::escapeHtml($bundle_info['label']);
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
@@ -677,6 +677,9 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
|
||||
->getStorage($this->getEntityTypeId())
|
||||
->getQuery();
|
||||
|
||||
// When tracking items, we never want access checks.
|
||||
$select->accessCheck(FALSE);
|
||||
|
||||
// We want to determine all entities of either one of the given bundles OR
|
||||
// one of the given languages. That means we can't just filter for $bundles
|
||||
// if $languages is given. Instead, we have to filter for all bundles we
|
||||
@@ -930,6 +933,13 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte
|
||||
}
|
||||
}
|
||||
|
||||
// The field might be provided by a module which is not the provider of the
|
||||
// entity type, therefore we need to add a dependency on that module.
|
||||
if ($property instanceof FieldStorageDefinitionInterface) {
|
||||
$provider = $property->getProvider();
|
||||
$dependencies['module'][$provider] = $provider;
|
||||
}
|
||||
|
||||
$property = $this->getFieldsHelper()->getInnerProperty($property);
|
||||
|
||||
if ($property instanceof EntityDataDefinitionInterface) {
|
||||
|
||||
+9
-3
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin\search_api\processor;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
|
||||
@@ -13,6 +12,7 @@ use Drupal\search_api\IndexInterface;
|
||||
use Drupal\search_api\Item\FieldInterface;
|
||||
use Drupal\search_api\Plugin\PluginFormTrait;
|
||||
use Drupal\search_api\Processor\ProcessorPluginBase;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -151,6 +151,7 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface {
|
||||
*/
|
||||
protected function findHierarchicalProperties(EntityDataDefinitionInterface $property, $property_label) {
|
||||
$entity_type_id = $property->getEntityTypeId();
|
||||
$property_label = Utility::escapeHtml($property_label);
|
||||
$options = [];
|
||||
|
||||
// Check properties for potential hierarchy. Check two levels down, since
|
||||
@@ -159,9 +160,10 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface {
|
||||
foreach ($this->getFieldsHelper()->getNestedProperties($property) as $name_2 => $property_2) {
|
||||
$property_2_label = $property_2->getLabel();
|
||||
$property_2 = $this->getFieldsHelper()->getInnerProperty($property_2);
|
||||
$is_reference = FALSE;
|
||||
if ($property_2 instanceof EntityDataDefinitionInterface) {
|
||||
if ($property_2->getEntityTypeId() == $entity_type_id) {
|
||||
$options["$entity_type_id-$name_2"] = Html::escape($property_label . ' » ' . $property_2_label);
|
||||
$is_reference = TRUE;
|
||||
}
|
||||
}
|
||||
elseif ($property_2 instanceof ComplexDataDefinitionInterface) {
|
||||
@@ -169,12 +171,16 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface {
|
||||
$property_3 = $this->getFieldsHelper()->getInnerProperty($property_3);
|
||||
if ($property_3 instanceof EntityDataDefinitionInterface) {
|
||||
if ($property_3->getEntityTypeId() == $entity_type_id) {
|
||||
$options["$entity_type_id-$name_2"] = Html::escape($property_label . ' » ' . $property_2_label);
|
||||
$is_reference = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($is_reference) {
|
||||
$property_2_label = Utility::escapeHtml($property_2_label);
|
||||
$options["$entity_type_id-$name_2"] = $property_label . ' » ' . $property_2_label;
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
+6
-2
@@ -94,11 +94,15 @@ class AggregatedFields extends ProcessorPluginBase {
|
||||
break;
|
||||
|
||||
case 'max':
|
||||
$values = [max($values)];
|
||||
if ($values) {
|
||||
$values = [max($values)];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'min':
|
||||
$values = [min($values)];
|
||||
if ($values) {
|
||||
$values = [min($values)];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'first':
|
||||
|
||||
+22
-11
@@ -279,11 +279,22 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
foreach ($item as $values) {
|
||||
$text = array_merge($text, $values);
|
||||
}
|
||||
$item_keys = $keys;
|
||||
|
||||
// If the backend already did highlighting and told us the exact keys it
|
||||
// found in the item's text values, we can use those for our own
|
||||
// highlighting. This will help us take stemming, transliteration, etc.
|
||||
// into account properly.
|
||||
$highlighted_keys = $results[$item_id]->getExtraData('highlighted_keys');
|
||||
if ($highlighted_keys) {
|
||||
$item_keys = array_unique(array_merge($keys, $highlighted_keys));
|
||||
}
|
||||
|
||||
// @todo This is pretty poor handling for the borders between different
|
||||
// values/fields. Better would be to pass an array and have proper
|
||||
// handling of this in createExcerpt(), ensuring that no snippet goes
|
||||
// across multiple values/fields.
|
||||
$results[$item_id]->setExcerpt($this->createExcerpt(implode($this->getEllipses()[1], $text), $keys));
|
||||
$results[$item_id]->setExcerpt($this->createExcerpt(implode($this->getEllipses()[1], $text), $item_keys));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,12 +449,15 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
* created.
|
||||
*/
|
||||
protected function createExcerpt($text, array $keys) {
|
||||
// Remove HTML tags <script> and <style> with all of their contents.
|
||||
$text = preg_replace('#<(style|script).*?>.*?</\1>#is', ' ', $text);
|
||||
|
||||
// Prepare text by stripping HTML tags and decoding HTML entities.
|
||||
$text = strip_tags(str_replace(['<', '>'], [' <', '> '], $text));
|
||||
$text = Html::decodeEntities($text);
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
$text = trim($text, ' ');
|
||||
$text_length = strlen($text);
|
||||
$text_length = mb_strlen($text);
|
||||
|
||||
// Try to reach the requested excerpt length with about two fragments (each
|
||||
// with a keyword and some context).
|
||||
@@ -483,13 +497,12 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
$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];
|
||||
// Convert the byte position into a multi-byte character position.
|
||||
$found_position = mb_strlen(substr(" $text", 0, $found_position));
|
||||
}
|
||||
}
|
||||
elseif (function_exists('mb_stripos')) {
|
||||
$found_position = mb_stripos($text, $key, $look_start[$key], 'UTF-8');
|
||||
}
|
||||
else {
|
||||
$found_position = stripos($text, $key, $look_start[$key]);
|
||||
$found_position = mb_stripos($text, $key, $look_start[$key], 'UTF-8');
|
||||
}
|
||||
if ($found_position !== FALSE) {
|
||||
$look_start[$key] = $found_position + 1;
|
||||
@@ -500,7 +513,7 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
// Locate a space before and after this match, leaving some context on
|
||||
// each end.
|
||||
if ($found_position > $context_length) {
|
||||
$before = strpos($text, ' ', $found_position - $context_length);
|
||||
$before = mb_strpos($text, ' ', $found_position - $context_length);
|
||||
if ($before !== FALSE) {
|
||||
++$before;
|
||||
}
|
||||
@@ -510,7 +523,7 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
}
|
||||
if ($before !== FALSE && $before <= $found_position) {
|
||||
if ($text_length > $found_position + $context_length) {
|
||||
$after = strrpos(substr($text, 0, $found_position + $context_length), ' ', $found_position);
|
||||
$after = mb_strrpos(mb_substr($text, 0, $found_position + $context_length), ' ', $found_position);
|
||||
}
|
||||
else {
|
||||
$after = $text_length;
|
||||
@@ -540,10 +553,8 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
|
||||
// Collapse overlapping text ranges into one. The sorting makes it O(n).
|
||||
$new_ranges = [];
|
||||
$max_end = 0;
|
||||
$working_from = $working_to = NULL;
|
||||
foreach ($ranges as $this_from => $this_to) {
|
||||
$max_end = max($max_end, $this_to);
|
||||
if ($working_from === NULL) {
|
||||
// This is the first time through this loop: initialize.
|
||||
$working_from = $this_from;
|
||||
@@ -568,7 +579,7 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface {
|
||||
// Fetch text within the combined ranges we found.
|
||||
$out = [];
|
||||
foreach ($new_ranges as $from => $to) {
|
||||
$out[] = Html::escape(substr($text, $from, $to - $from));
|
||||
$out[] = Html::escape(mb_substr($text, $from, $to - $from));
|
||||
}
|
||||
if (!$out) {
|
||||
return NULL;
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin\search_api\processor\Property;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
@@ -70,7 +69,7 @@ class AggregatedFieldProperty extends ConfigurablePropertyBase {
|
||||
continue;
|
||||
}
|
||||
$label = $datasource_labels[$datasource_id] . $property->getLabel();
|
||||
$field_options[$combined_id] = Html::escape($label);
|
||||
$field_options[$combined_id] = Utility::escapeHtml($label);
|
||||
if ($property instanceof ConfigurablePropertyInterface) {
|
||||
$description = $property->getFieldDescription($field);
|
||||
}
|
||||
|
||||
+35
-22
@@ -5,7 +5,7 @@ namespace Drupal\search_api\Plugin\search_api\processor;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Drupal\Core\Session\AccountSwitcherInterface;
|
||||
use Drupal\Core\Session\UserSession;
|
||||
use Drupal\Core\Theme\ThemeInitializationInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
@@ -39,9 +39,9 @@ class RenderedItem extends ProcessorPluginBase {
|
||||
/**
|
||||
* The current_user service used by this plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface|null
|
||||
* @var \Drupal\Core\Session\AccountSwitcherInterface|null
|
||||
*/
|
||||
protected $currentUser;
|
||||
protected $accountSwitcher;
|
||||
|
||||
/**
|
||||
* The renderer to use.
|
||||
@@ -78,7 +78,7 @@ class RenderedItem extends ProcessorPluginBase {
|
||||
/** @var static $plugin */
|
||||
$plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$plugin->setCurrentUser($container->get('current_user'));
|
||||
$plugin->setAccountSwitcher($container->get('account_switcher'));
|
||||
$plugin->setRenderer($container->get('renderer'));
|
||||
$plugin->setLogger($container->get('logger.channel.search_api'));
|
||||
$plugin->setThemeManager($container->get('theme.manager'));
|
||||
@@ -89,25 +89,25 @@ class RenderedItem extends ProcessorPluginBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current user.
|
||||
* Retrieves the account switcher service.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountProxyInterface
|
||||
* The current user.
|
||||
* @return \Drupal\Core\Session\AccountSwitcherInterface
|
||||
* The account switcher service.
|
||||
*/
|
||||
public function getCurrentUser() {
|
||||
return $this->currentUser ?: \Drupal::currentUser();
|
||||
public function getAccountSwitcher() {
|
||||
return $this->accountSwitcher ?: \Drupal::service('account_switcher');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current user.
|
||||
* Sets the account switcher service.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Session\AccountSwitcherInterface $current_user
|
||||
* The account switcher service.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCurrentUser(AccountProxyInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
public function setAccountSwitcher(AccountSwitcherInterface $current_user) {
|
||||
$this->accountSwitcher = $current_user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -229,8 +229,6 @@ class RenderedItem extends ProcessorPluginBase {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addFieldValues(ItemInterface $item) {
|
||||
$original_user = $this->currentUser->getAccount();
|
||||
|
||||
// Switch to the default theme in case the admin theme is enabled.
|
||||
$active_theme = $this->getThemeManager()->getActiveTheme();
|
||||
$default_theme = $this->getConfigFactory()
|
||||
@@ -250,7 +248,8 @@ class RenderedItem extends ProcessorPluginBase {
|
||||
|
||||
// Change the current user to our dummy implementation to ensure we are
|
||||
// using the configured roles.
|
||||
$this->currentUser->setAccount(new UserSession(['roles' => $configuration['roles']]));
|
||||
$this->getAccountSwitcher()
|
||||
->switchTo(new UserSession(['roles' => $configuration['roles']]));
|
||||
|
||||
$datasource_id = $item->getDatasourceId();
|
||||
$datasource = $item->getDatasource();
|
||||
@@ -268,15 +267,29 @@ class RenderedItem extends ProcessorPluginBase {
|
||||
$view_mode = (string) $configuration['view_mode'][$datasource_id][$bundle];
|
||||
}
|
||||
|
||||
$build = $datasource->viewItem($item->getOriginalObject(), $view_mode);
|
||||
$value = (string) $this->getRenderer()->renderPlain($build);
|
||||
if ($value) {
|
||||
$field->addValue($value);
|
||||
try {
|
||||
$build = $datasource->viewItem($item->getOriginalObject(), $view_mode);
|
||||
$value = (string) $this->getRenderer()->renderPlain($build);
|
||||
if ($value) {
|
||||
$field->addValue($value);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// This could throw all kinds of exceptions in specific scenarios, so we
|
||||
// just catch all of them here. Not having a field value for this field
|
||||
// probably makes sense in that case, so we just log an error and
|
||||
// continue.
|
||||
$variables = [
|
||||
'%item_id' => $item->getId(),
|
||||
'%view_mode' => $view_mode,
|
||||
'%index' => $this->index->label(),
|
||||
];
|
||||
$this->logException($e, '%type while trying to render item %item_id with view mode %view_mode for search index %index: @message in %function (line %line of %file).', $variables);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original user.
|
||||
$this->currentUser->setAccount($original_user);
|
||||
$this->getAccountSwitcher()->switchBack();
|
||||
// Restore the original theme.
|
||||
$this->getThemeManager()->setActiveTheme($active_theme);
|
||||
|
||||
|
||||
+417
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api\Plugin\search_api\processor;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManager;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\search_api\Datasource\DatasourceInterface;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
use Drupal\search_api\Item\ItemInterface;
|
||||
use Drupal\search_api\Processor\EntityProcessorProperty;
|
||||
use Drupal\search_api\Processor\ProcessorPluginBase;
|
||||
use Drupal\search_api\SearchApiException;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Allows indexing of reverse entity references.
|
||||
*
|
||||
* @SearchApiProcessor(
|
||||
* id = "reverse_entity_references",
|
||||
* label = @Translation("Reverse entity references"),
|
||||
* description = @Translation("Allows indexing of entities that link to the indexed entity."),
|
||||
* stages = {
|
||||
* "add_properties" = 0,
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class ReverseEntityReferences extends ProcessorPluginBase {
|
||||
|
||||
/**
|
||||
* Static cache for all entity references.
|
||||
*
|
||||
* @var array[][]|null
|
||||
*
|
||||
* @see \Drupal\search_api\Plugin\search_api\processor\ReverseEntityReferences::getEntityReferences()
|
||||
*/
|
||||
protected $references;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManager|null
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The entity type bundle info.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|null
|
||||
*/
|
||||
protected $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface|null
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The cache.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface|null
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
/** @var static $processor */
|
||||
$processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$processor->setEntityTypeManager($container->get('entity_type.manager'));
|
||||
$processor->setEntityFieldManager($container->get('entity_field.manager'));
|
||||
$processor->setEntityTypeBundleInfo($container->get('entity_type.bundle.info'));
|
||||
$processor->setLanguageManager($container->get('language_manager'));
|
||||
$processor->setCache($container->get('cache.default'));
|
||||
|
||||
return $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity type manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function getEntityTypeManager() {
|
||||
return $this->entityTypeManager ?: \Drupal::entityTypeManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity type manager.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The new entity type manager.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity field manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityFieldManager
|
||||
* The entity field manager.
|
||||
*/
|
||||
public function getEntityFieldManager() {
|
||||
return $this->entityFieldManager ?: \Drupal::service('entity_field.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity field manager.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityFieldManager $entity_field_manager
|
||||
* The new entity field manager.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEntityFieldManager(EntityFieldManager $entity_field_manager) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity type bundle info.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
* The entity type bundle info.
|
||||
*/
|
||||
public function getEntityTypeBundleInfo() {
|
||||
return $this->entityTypeBundleInfo ?: \Drupal::service('entity_type.bundle.info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity type bundle info.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* The new entity type bundle info.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEntityTypeBundleInfo(EntityTypeBundleInfoInterface $entity_type_bundle_info) {
|
||||
$this->entityTypeBundleInfo = $entity_type_bundle_info;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the language manager.
|
||||
*
|
||||
* @return \Drupal\Core\Language\LanguageManagerInterface
|
||||
* The language manager.
|
||||
*/
|
||||
public function getLanguageManager() {
|
||||
return $this->languageManager ?: \Drupal::service('language_manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language manager.
|
||||
*
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The new language manager.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLanguageManager(LanguageManagerInterface $language_manager) {
|
||||
$this->languageManager = $language_manager;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cache.
|
||||
*
|
||||
* @return \Drupal\Core\Cache\CacheBackendInterface
|
||||
* The cache.
|
||||
*/
|
||||
public function getCache() {
|
||||
return $this->cache ?: \Drupal::service('cache.default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The new cache.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCache(CacheBackendInterface $cache) {
|
||||
$this->cache = $cache;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function supportsIndex(IndexInterface $index) {
|
||||
foreach ($index->getDatasources() as $datasource) {
|
||||
if ($datasource->getEntityTypeId()) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
|
||||
$properties = [];
|
||||
|
||||
if (!$datasource || !$datasource->getEntityTypeId()) {
|
||||
return $properties;
|
||||
}
|
||||
|
||||
$references = $this->getEntityReferences();
|
||||
$entity_type_id = $datasource->getEntityTypeId();
|
||||
if (isset($references[$entity_type_id])) {
|
||||
foreach ($references[$entity_type_id] as $key => $reference) {
|
||||
$entity_type_id = $reference['entity_type'];
|
||||
try {
|
||||
$entity_type = $this->getEntityTypeManager()
|
||||
->getDefinition($entity_type_id);
|
||||
}
|
||||
catch (PluginNotFoundException $e) {
|
||||
continue;
|
||||
}
|
||||
$args = [
|
||||
'%entity_type' => $entity_type->getLabel(),
|
||||
'%property' => $reference['label'],
|
||||
];
|
||||
$definition = [
|
||||
'label' => $this->t('Reverse reference: %entity_type using %property', $args),
|
||||
'description' => $this->t("All %entity_type entities that reference this item via the %property field."),
|
||||
'type' => "entity:$entity_type_id",
|
||||
'processor_id' => $this->getPluginId(),
|
||||
// We can't really know whether this will end up being multi-valued, so
|
||||
// we err on the side of caution.
|
||||
'is_list' => TRUE,
|
||||
];
|
||||
$property = new EntityProcessorProperty($definition);
|
||||
$property->setEntityTypeId($entity_type_id);
|
||||
$properties["search_api_reverse_entity_references_$key"] = $property;
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addFieldValues(ItemInterface $item) {
|
||||
try {
|
||||
$entity = $item->getOriginalObject()->getValue();
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!($entity instanceof EntityInterface)) {
|
||||
return;
|
||||
}
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$entity_id = $entity->id();
|
||||
$langcode = $entity->language()->getId();
|
||||
$datasource_id = $item->getDatasourceId();
|
||||
|
||||
/** @var \Drupal\search_api\Item\FieldInterface[][][] $to_extract */
|
||||
$to_extract = [];
|
||||
$prefix = 'search_api_reverse_entity_references_';
|
||||
$prefix_length = strlen($prefix);
|
||||
foreach ($item->getFields() as $field) {
|
||||
$property_path = $field->getPropertyPath();
|
||||
list($direct, $nested) = Utility::splitPropertyPath($property_path, FALSE);
|
||||
if ($field->getDatasourceId() === $datasource_id
|
||||
&& substr($direct, 0, $prefix_length) === $prefix) {
|
||||
$property_name = substr($direct, $prefix_length);
|
||||
$to_extract[$property_name][$nested][] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$references = $this->getEntityReferences();
|
||||
foreach ($to_extract as $property_name => $fields_to_extract) {
|
||||
if (!isset($references[$entity_type_id][$property_name])) {
|
||||
continue;
|
||||
}
|
||||
$property_info = $references[$entity_type_id][$property_name];
|
||||
|
||||
try {
|
||||
$storage = $this->getEntityTypeManager()
|
||||
->getStorage($property_info['entity_type']);
|
||||
}
|
||||
// @todo Replace with multi-catch once we depend on PHP 7.1+.
|
||||
catch (InvalidPluginDefinitionException $e) {
|
||||
continue;
|
||||
}
|
||||
catch (PluginNotFoundException $e) {
|
||||
continue;
|
||||
}
|
||||
$entity_ids = $storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition($property_info['property'], $entity_id)
|
||||
->execute();
|
||||
|
||||
$entities = $storage->loadMultiple($entity_ids);
|
||||
if (!$entities) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is a pretty hack-y work-around to make property extraction work for
|
||||
// Views fields, too. In general, adding entities as field values is a
|
||||
// pretty bad idea, so this might blow up in some use cases. Just do it for
|
||||
// now and hope for the best.
|
||||
if (isset($fields_to_extract[''])) {
|
||||
foreach ($fields_to_extract[''] as $field) {
|
||||
$field->setValues(array_values($entities));
|
||||
}
|
||||
unset($fields_to_extract['']);
|
||||
}
|
||||
foreach ($entities as $referencing_entity) {
|
||||
$typed_data = $referencing_entity->getTypedData();
|
||||
$this->getFieldsHelper()
|
||||
->extractFields($typed_data, $fields_to_extract, $langcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all entity references.
|
||||
*
|
||||
* @return array[][]
|
||||
* An associative array of entity reference information keyed by the
|
||||
* referenced entity type's ID and a custom identifier for the property
|
||||
* (consisting of referencing entity type and property name), with values
|
||||
* being associative arrays with the following keys:
|
||||
* - label: The property label.
|
||||
* - entity_type: The referencing entity type.
|
||||
* - property: The property name.
|
||||
*/
|
||||
public function getEntityReferences() {
|
||||
if ($this->references !== NULL) {
|
||||
return $this->references;
|
||||
}
|
||||
|
||||
// Property labels differ by language, so we need to vary the cache
|
||||
// according to the current language.
|
||||
$langcode = $this->getLanguageManager()->getCurrentLanguage()->getId();
|
||||
$cid = "search_api:reverse_entity_references:$langcode";
|
||||
$cache = $this->getCache()->get($cid);
|
||||
if (isset($cache->data)) {
|
||||
$this->references = $cache->data;
|
||||
}
|
||||
else {
|
||||
$this->references = [];
|
||||
|
||||
$entity_types = $this->getEntityTypeManager()->getDefinitions();
|
||||
$field_manager = $this->getEntityFieldManager();
|
||||
$entity_type_bundle_info = $this->getEntityTypeBundleInfo();
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
if (!($entity_type instanceof ContentEntityTypeInterface)) {
|
||||
continue;
|
||||
}
|
||||
/** @var \Drupal\Core\Field\FieldDefinitionInterface[] $properties */
|
||||
$properties = $field_manager->getBaseFieldDefinitions($entity_type_id);
|
||||
$bundles = $entity_type_bundle_info->getBundleInfo($entity_type_id);
|
||||
foreach ($bundles as $bundle => $info) {
|
||||
$properties += $field_manager->getFieldDefinitions($entity_type_id, $bundle);
|
||||
}
|
||||
|
||||
foreach ($properties as $name => $property) {
|
||||
if ($property->getType() !== 'entity_reference') {
|
||||
continue;
|
||||
}
|
||||
$settings = $property->getSettings();
|
||||
if (empty($settings['target_type'])) {
|
||||
continue;
|
||||
}
|
||||
$this->references[$settings['target_type']]["{$entity_type_id}__$name"] = [
|
||||
'label' => $property->getLabel(),
|
||||
'entity_type' => $entity_type_id,
|
||||
'property' => $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$tags = [
|
||||
'entity_types',
|
||||
'entity_bundles',
|
||||
'entity_field_info',
|
||||
];
|
||||
$this->getCache()->set($cid, $this->references, Cache::PERMANENT, $tags);
|
||||
}
|
||||
|
||||
return $this->references;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -2,12 +2,12 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin\search_api\processor;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
use Drupal\search_api\Plugin\PluginFormTrait;
|
||||
use Drupal\search_api\Processor\ProcessorPluginBase;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
@@ -56,7 +56,7 @@ class RoleFilter extends ProcessorPluginBase implements PluginFormInterface {
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$options = array_map(function (RoleInterface $role) {
|
||||
return Html::escape($role->label());
|
||||
return Utility::escapeHtml($role->label());
|
||||
}, user_roles());
|
||||
|
||||
$form['default'] = [
|
||||
|
||||
+2
-3
@@ -3,7 +3,6 @@
|
||||
namespace Drupal\search_api\Plugin\views\argument;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
@@ -130,10 +129,10 @@ class SearchApiDate extends SearchApiStandard {
|
||||
$dates[] = $date_string;
|
||||
}
|
||||
}
|
||||
return $dates ? implode(', ', $dates) : Html::escape($this->argument);
|
||||
return $dates ? implode(', ', $dates) : $this->argument;
|
||||
}
|
||||
|
||||
return Html::escape($this->argument);
|
||||
return $this->argument;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-1
@@ -81,10 +81,14 @@ class SearchApiFulltext extends SearchApiStandard {
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Parse mode'),
|
||||
'#description' => $this->t('Choose how the search keys will be parsed.'),
|
||||
'#options' => $this->getParseModeManager()->getInstancesOptions(),
|
||||
'#options' => [],
|
||||
'#default_value' => $this->options['parse_mode'],
|
||||
];
|
||||
foreach ($this->getParseModeManager()->getInstances() as $key => $mode) {
|
||||
if ($mode->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
$form['parse_mode']['#options'][$key] = $mode->label();
|
||||
if ($mode->getDescription()) {
|
||||
$states['visible'][':input[name="options[parse_mode]"]']['value'] = $key;
|
||||
$form["parse_mode_{$key}_description"] = [
|
||||
|
||||
+12
-2
@@ -168,9 +168,19 @@ class SearchApiStandard extends ArgumentPluginBase {
|
||||
public function query($group_by = FALSE) {
|
||||
$this->fillValue();
|
||||
|
||||
// If there are multiple arguments, add query conditions accordingly.
|
||||
// E.g 1+2+3 (for OR) or 1,2,3 (for AND).
|
||||
if (count($this->value) > 1) {
|
||||
$operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
|
||||
$this->query->addCondition($this->realField, $this->value, $operator);
|
||||
if ($this->operator === 'or') {
|
||||
$operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
|
||||
$this->query->addCondition($this->realField, $this->value, $operator);
|
||||
}
|
||||
else {
|
||||
foreach ($this->value as $value) {
|
||||
$operator = empty($this->options['not']) ? '=' : '<>';
|
||||
$this->query->addCondition($this->realField, $value, $operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($this->value) {
|
||||
$operator = empty($this->options['not']) ? '=' : '<>';
|
||||
|
||||
+3
-4
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Drupal\search_api\Plugin\views\argument;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
|
||||
/**
|
||||
@@ -30,14 +29,14 @@ class SearchApiTerm extends SearchApiStandard {
|
||||
foreach ($this->value as $tid) {
|
||||
$taxonomy_term = Term::load($tid);
|
||||
if ($taxonomy_term) {
|
||||
$terms[] = Html::escape($taxonomy_term->label());
|
||||
$terms[] = $taxonomy_term->label();
|
||||
}
|
||||
}
|
||||
|
||||
return $terms ? implode(', ', $terms) : Html::escape($this->argument);
|
||||
return $terms ? implode(', ', $terms) : $this->argument;
|
||||
}
|
||||
else {
|
||||
return Html::escape($this->argument);
|
||||
return $this->argument;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-4
@@ -337,7 +337,11 @@ trait SearchApiFieldTrait {
|
||||
*/
|
||||
public function query() {
|
||||
$combined_property_path = $this->getCombinedPropertyPath();
|
||||
$this->addRetrievedProperty($combined_property_path);
|
||||
$field_id = NULL;
|
||||
if (!empty($this->definition['search_api field'])) {
|
||||
$field_id = $this->definition['search_api field'];
|
||||
}
|
||||
$this->addRetrievedProperty($combined_property_path, $field_id);
|
||||
if ($this->options['link_to_item']) {
|
||||
$this->addRetrievedProperty("$combined_property_path:_object");
|
||||
}
|
||||
@@ -350,11 +354,15 @@ trait SearchApiFieldTrait {
|
||||
* The combined property path of the property that should be retrieved.
|
||||
* "_object" can be used as a property name to indicate the loaded object is
|
||||
* required.
|
||||
* @param string|null $field_id
|
||||
* (optional) The ID of the field corresponding to this property, if any.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function addRetrievedProperty($combined_property_path) {
|
||||
$this->getQuery()->addRetrievedProperty($combined_property_path);
|
||||
protected function addRetrievedProperty($combined_property_path, $field_id = NULL) {
|
||||
if ($field_id) {
|
||||
$this->getQuery()->addRetrievedFieldValue($field_id);
|
||||
}
|
||||
|
||||
list($datasource_id, $property_path) = Utility::splitCombinedId($combined_property_path);
|
||||
$this->retrievedProperties[$datasource_id][$property_path] = $combined_property_path;
|
||||
@@ -467,7 +475,7 @@ trait SearchApiFieldTrait {
|
||||
/**
|
||||
* Expands the properties to retrieve for this field.
|
||||
*
|
||||
* The properties are taken from this object's $retrievedProperties property,
|
||||
* The properties are taken from this object's $retrievedFieldValues property,
|
||||
* with all their ancestors also added to the array, with the ancestor
|
||||
* properties always ordered before their descendants.
|
||||
*
|
||||
|
||||
+4
@@ -39,6 +39,10 @@ trait SearchApiFilterTrait {
|
||||
* @see \Drupal\views\Plugin\views\filter\ManyToOne::opHelper()
|
||||
*/
|
||||
protected function opHelper() {
|
||||
// Form API returns unchecked options in the form of option_id => 0. This
|
||||
// breaks the generated query for "is all of" filters so we remove them.
|
||||
$this->value = array_filter($this->value, 'static::arrayFilterZero');
|
||||
|
||||
if (empty($this->value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
+5
-1
@@ -141,10 +141,14 @@ class SearchApiFulltext extends FilterPluginBase {
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Parse mode'),
|
||||
'#description' => $this->t('Choose how the search keys will be parsed.'),
|
||||
'#options' => $this->getParseModeManager()->getInstancesOptions(),
|
||||
'#options' => [],
|
||||
'#default_value' => $this->options['parse_mode'],
|
||||
];
|
||||
foreach ($this->getParseModeManager()->getInstances() as $key => $mode) {
|
||||
if ($mode->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
$form['parse_mode']['#options'][$key] = $mode->label();
|
||||
if ($mode->getDescription()) {
|
||||
$states['visible'][':input[name="options[parse_mode]"]']['value'] = $key;
|
||||
$form["parse_mode_{$key}_description"] = [
|
||||
|
||||
+34
-18
@@ -3,7 +3,6 @@
|
||||
namespace Drupal\search_api\Plugin\views\query;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Database\Query\ConditionInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
@@ -19,7 +18,6 @@ use Drupal\search_api\Processor\ConfigurablePropertyInterface;
|
||||
use Drupal\search_api\Query\ConditionGroupInterface;
|
||||
use Drupal\search_api\Query\QueryInterface;
|
||||
use Drupal\search_api\SearchApiException;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\query\QueryPluginBase;
|
||||
@@ -86,14 +84,11 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
protected $abort = FALSE;
|
||||
|
||||
/**
|
||||
* The properties that should be retrieved from result items.
|
||||
* The IDs of fields whose values should be retrieved by the backend.
|
||||
*
|
||||
* The array is keyed by datasource ID (which might be NULL) and property
|
||||
* path, the values are the associated combined property paths.
|
||||
*
|
||||
* @var string[][]
|
||||
* @var string[]
|
||||
*/
|
||||
protected $retrievedProperties = [];
|
||||
protected $retrievedFieldValues = [];
|
||||
|
||||
/**
|
||||
* The query's conditions representing the different Views filter groups.
|
||||
@@ -250,8 +245,6 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
if (!$this->index) {
|
||||
$this->abort(new FormattableMarkup('View %view is not based on Search API but tries to use its query plugin.', ['%view' => $view->storage->label()]));
|
||||
}
|
||||
$this->retrievedProperties = array_fill_keys($this->index->getDatasourceIds(), []);
|
||||
$this->retrievedProperties[NULL] = [];
|
||||
$this->query = $this->index->query();
|
||||
$this->query->addTag('views');
|
||||
$this->query->addTag('views_' . $view->id());
|
||||
@@ -278,10 +271,28 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
* The combined property path of the property that should be retrieved.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @deprecated in 8.x-1.11. Use ::addRetrievedFieldValue() instead.
|
||||
*/
|
||||
public function addRetrievedProperty($combined_property_path) {
|
||||
list($datasource_id, $property_path) = Utility::splitCombinedId($combined_property_path);
|
||||
$this->retrievedProperties[$datasource_id][$property_path] = $combined_property_path;
|
||||
@trigger_error('\Drupal\search_api\Plugin\views\query\SearchApiQuery::addRetrievedProperty() is deprecated in Search API 8.x-1.11. Use addRetrievedFieldValue() instead. See https://www.drupal.org/project/search_api/issues/3009136', E_USER_DEPRECATED);
|
||||
$this->addField(NULL, $combined_property_path);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field value to be retrieved.
|
||||
*
|
||||
* Helps backends that support returning fields to determine which of the
|
||||
* fields should actually be returned.
|
||||
*
|
||||
* @param string $field_id
|
||||
* The ID of the field whose value should be retrieved.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRetrievedFieldValue($field_id) {
|
||||
$this->retrievedFieldValues[$field_id] = $field_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -290,7 +301,7 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
*
|
||||
* This replicates the interface of Views' default SQL backend to simplify
|
||||
* the Views integration of the Search API. If you are writing Search
|
||||
* API-specific Views code, you should better use the addRetrievedProperty()
|
||||
* API-specific Views code, you should better use the addRetrievedFieldValue()
|
||||
* method.
|
||||
*
|
||||
* @param string|null $table
|
||||
@@ -306,10 +317,15 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
* The name that this field can be referred to as (always $field).
|
||||
*
|
||||
* @see \Drupal\views\Plugin\views\query\Sql::addField()
|
||||
* @see \Drupal\search_api\Plugin\views\query\SearchApiQuery::addField()
|
||||
* @see \Drupal\search_api\Plugin\views\query\SearchApiQuery::addRetrievedFieldValue()
|
||||
*/
|
||||
public function addField($table, $field, $alias = '', array $params = []) {
|
||||
$this->addRetrievedProperty($field);
|
||||
foreach ($this->getIndex()->getFields(TRUE) as $field_id => $field_object) {
|
||||
if ($field_object->getCombinedPropertyPath() === $field) {
|
||||
$this->addRetrievedFieldValue($field_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
|
||||
@@ -440,9 +456,9 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
// Save query information for Views UI.
|
||||
$view->build_info['query'] = (string) $this->query;
|
||||
|
||||
// Add the properties to be retrieved to the query, as information for the
|
||||
// Add the fields to be retrieved to the query, as information for the
|
||||
// backend.
|
||||
$this->query->setOption('search_api_retrieved_properties', $this->retrievedProperties);
|
||||
$this->query->setOption('search_api_retrieved_field_values', array_values($this->retrievedFieldValues));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,7 +475,7 @@ class SearchApiQuery extends QueryPluginBase {
|
||||
if ($this->shouldAbort()) {
|
||||
if (error_displayable()) {
|
||||
foreach ($this->errors as $msg) {
|
||||
$this->getMessenger()->addError(Html::escape($msg));
|
||||
$this->getMessenger()->addError($msg);
|
||||
}
|
||||
}
|
||||
$view->result = [];
|
||||
|
||||
@@ -132,14 +132,6 @@ interface ProcessorInterface extends IndexPluginInterface {
|
||||
*/
|
||||
public function isLocked();
|
||||
|
||||
/**
|
||||
* Determines whether this processor should be hidden from the user.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this processor should be hidden from the user; FALSE otherwise.
|
||||
*/
|
||||
public function isHidden();
|
||||
|
||||
/**
|
||||
* Retrieves the properties this processor defines for the given datasource.
|
||||
*
|
||||
|
||||
@@ -141,7 +141,8 @@ abstract class ProcessorPluginBase extends IndexPluginBase implements ProcessorI
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isHidden() {
|
||||
return !empty($this->pluginDefinition['hidden']);
|
||||
return !empty($this->pluginDefinition['hidden'])
|
||||
|| !empty($this->pluginDefinition['no_ui']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -432,12 +432,10 @@ interface QueryInterface extends ConditionSetInterface {
|
||||
* access checks, if available and enabled for the index.
|
||||
* - search_api_bypass_access: If set to TRUE, entity access checks will be
|
||||
* skipped, even if enabled for the index.
|
||||
* - search_api_retrieved_properties: A list of properties that will be
|
||||
* required from results by the code displaying the results list. This
|
||||
* option, if present, should be an array keyed by datasource ID and
|
||||
* (datasource-internal) property path, with combined property paths as
|
||||
* the values. (The "_object" pseudo-property can be used where a whole
|
||||
* object (entity or other) is required.)
|
||||
* - search_api_retrieved_field_values: A list of IDs of fields whose values
|
||||
* should be returned along with the results by the backend, if possible.
|
||||
* For backends that support retrieving fields values, this allows them to
|
||||
* only retrieve the values that are actually needed.
|
||||
* However, contrib modules might introduce arbitrary other keys with their
|
||||
* own, special meaning. (Usually they should be prefixed with the module
|
||||
* name, though, to avoid conflicts.)
|
||||
|
||||
@@ -174,9 +174,6 @@ interface ResultSetInterface extends \Traversable {
|
||||
* key instead.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @todo Add unsetExtraData() instead of special NULL handling? And/or
|
||||
* just have to use &getAllExtraData()?
|
||||
*/
|
||||
public function setExtraData($key, $data = NULL);
|
||||
|
||||
|
||||
@@ -279,7 +279,16 @@ class TaskManager implements TaskManagerInterface {
|
||||
// Schedule the batch.
|
||||
batch_set($batch_definition);
|
||||
if (function_exists('drush_backend_batch_process')) {
|
||||
drush_backend_batch_process();
|
||||
$result = drush_backend_batch_process();
|
||||
// Drush performs batch processing in a separate PHP request. When the
|
||||
// last batch is processed the batch list is cleared, but this only takes
|
||||
// effect in the other request. Take the same action here to ensure that
|
||||
// we are not requeueing stale batches when there are multiple tasks being
|
||||
// handled in a single request.
|
||||
if ($result['context']['drush_batch_process_finished'] === TRUE) {
|
||||
$batch = &batch_get();
|
||||
$batch = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace Drupal\search_api\Tracker;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\search_api\Utility\Utility;
|
||||
|
||||
/**
|
||||
* Manages tracker plugins.
|
||||
@@ -45,7 +45,7 @@ class TrackerPluginManager extends DefaultPluginManager {
|
||||
public function getOptionsList() {
|
||||
$options = [];
|
||||
foreach ($this->getDefinitions() as $plugin_id => $plugin_definition) {
|
||||
$options[$plugin_id] = Html::escape($plugin_definition['label']);
|
||||
$options[$plugin_id] = Utility::escapeHtml($plugin_definition['label']);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
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;
|
||||
@@ -50,7 +49,10 @@ class PostRequestIndexing implements PostRequestIndexingInterface, DestructableI
|
||||
try {
|
||||
$storage = $this->entityTypeManager->getStorage('search_api_index');
|
||||
}
|
||||
catch (InvalidPluginDefinitionException $e) {
|
||||
// @todo @todo Replace with multi-catch for
|
||||
// InvalidPluginDefinitionException and PluginNotFoundException once we
|
||||
// depend on PHP 7.1+.
|
||||
catch (\Exception $e) {
|
||||
// It might be possible that the module got uninstalled during the rest
|
||||
// of the page request, or something else happened. To be on the safe
|
||||
// side, catch the exception in case the entity type isn't found.
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
namespace Drupal\search_api\Utility;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
use Drupal\search_api\Plugin\search_api\data_type\value\TextToken;
|
||||
|
||||
@@ -248,4 +251,22 @@ class Utility {
|
||||
return in_array($value, $settings['selected']) != $settings['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes HTML special characters in plain text, if necessary.
|
||||
*
|
||||
* @param string|\Drupal\Component\Render\MarkupInterface $text
|
||||
* The text to escape.
|
||||
*
|
||||
* @return \Drupal\Component\Render\MarkupInterface
|
||||
* If a markup object was passed as $text, it is returned as-is. Otherwise,
|
||||
* the text is escaped and returned
|
||||
*/
|
||||
public static function escapeHtml($text) {
|
||||
if ($text instanceof MarkupInterface) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return Markup::create(Html::escape((string) $text));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -7,8 +7,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ use Drupal\search_api\DataType\DataTypePluginBase;
|
||||
*
|
||||
* @SearchApiDataType(
|
||||
* id = "search_api_test",
|
||||
* label = @Translation("Test data type"),
|
||||
* description = @Translation("Dummy data type implementation")
|
||||
* label = @Translation(""Test" data type"),
|
||||
* description = @Translation("Dummy <em>data type</em> implementation")
|
||||
* )
|
||||
*/
|
||||
class TestDataType extends DataTypePluginBase {
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ use Drupal\search_api_test\TestPluginTrait;
|
||||
*
|
||||
* @SearchApiDatasource(
|
||||
* id = "search_api_test",
|
||||
* label = @Translation("Test datasource"),
|
||||
* label = @Translation(""Test" datasource"),
|
||||
* description = @Translation("This is the <em>test datasource</em> plugin description."),
|
||||
* )
|
||||
*/
|
||||
class TestDatasource extends DatasourcePluginBase {
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ use Drupal\search_api_test\TestPluginTrait;
|
||||
*
|
||||
* @SearchApiTracker(
|
||||
* id = "search_api_test",
|
||||
* label = @Translation("Test tracker"),
|
||||
* description = @Translation("This is the test tracker plugin description."),
|
||||
* label = @Translation(""Test" tracker"),
|
||||
* description = @Translation("This is the <em>test tracker</em> plugin description."),
|
||||
* )
|
||||
*/
|
||||
class TestTracker extends TrackerPluginBase implements PluginFormInterface {
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test\Plugin\search_api\tracker;
|
||||
|
||||
/**
|
||||
* Provides a tracker implementation which uses a FIFO-like processing order.
|
||||
*
|
||||
* @SearchApiTracker(
|
||||
* id = "search_api_test_string_label",
|
||||
* label = ""String label" test tracker",
|
||||
* description = "This is the <em>test tracker with string label</em> plugin description.",
|
||||
* )
|
||||
*/
|
||||
class TestTrackerStringLabel extends TestTracker {
|
||||
}
|
||||
+3
-3
@@ -8,8 +8,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+3
-3
@@ -7,8 +7,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+3
-3
@@ -7,8 +7,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+3
-3
@@ -7,8 +7,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+3
-3
@@ -8,8 +8,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
name: 'Search API Test No UI'
|
||||
type: module
|
||||
description: 'Support module for Search API tests ("No UI" plugins)'
|
||||
package: Search
|
||||
dependencies:
|
||||
- search_api:search_api
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1542564486
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test_no_ui\Plugin\search_api\backend;
|
||||
|
||||
use Drupal\search_api\Backend\BackendPluginBase;
|
||||
use Drupal\search_api\IndexInterface;
|
||||
use Drupal\search_api\Query\QueryInterface;
|
||||
|
||||
/**
|
||||
* Provides a test backend that should be hidden from the UI.
|
||||
*
|
||||
* @SearchApiBackend(
|
||||
* id = "search_api_test_no_ui",
|
||||
* label = @Translation("No UI backend"),
|
||||
* no_ui = true,
|
||||
* )
|
||||
*/
|
||||
class NoUi extends BackendPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function indexItems(IndexInterface $index, array $items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems(IndexInterface $index, array $item_ids) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAllIndexItems(IndexInterface $index, $datasource_id = NULL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function search(QueryInterface $query) {
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test_no_ui\Plugin\search_api\data_type;
|
||||
|
||||
use Drupal\search_api\DataType\DataTypePluginBase;
|
||||
|
||||
/**
|
||||
* Provides a test data type that should be hidden from the UI.
|
||||
*
|
||||
* @SearchApiDataType(
|
||||
* id = "search_api_test_no_ui",
|
||||
* label = @Translation("No UI data type"),
|
||||
* no_ui = true,
|
||||
* )
|
||||
*/
|
||||
class NoUi extends DataTypePluginBase {
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test_no_ui\Plugin\search_api\datasource;
|
||||
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\search_api\Datasource\DatasourcePluginBase;
|
||||
|
||||
/**
|
||||
* Provides a test datasource that should be hidden from the UI.
|
||||
*
|
||||
* @SearchApiDatasource(
|
||||
* id = "search_api_test_no_ui",
|
||||
* label = @Translation("No UI datasource"),
|
||||
* no_ui = true,
|
||||
* )
|
||||
*/
|
||||
class NoUi extends DatasourcePluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemId(ComplexDataInterface $item) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test_no_ui\Plugin\search_api\parse_mode;
|
||||
|
||||
use Drupal\search_api\ParseMode\ParseModePluginBase;
|
||||
|
||||
/**
|
||||
* Provides a parse mode that should be hidden from the UI.
|
||||
*
|
||||
* @SearchApiParseMode(
|
||||
* id = "search_api_test_no_ui",
|
||||
* label = @Translation("No UI parse mode"),
|
||||
* no_ui = true,
|
||||
* )
|
||||
*/
|
||||
class NoUi extends ParseModePluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseInput($keys) {
|
||||
return $keys;
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test_no_ui\Plugin\search_api\processor;
|
||||
|
||||
use Drupal\search_api\Processor\ProcessorPluginBase;
|
||||
|
||||
/**
|
||||
* Provides a test processor that should be hidden from the UI.
|
||||
*
|
||||
* @SearchApiProcessor(
|
||||
* id = "search_api_test_no_ui",
|
||||
* label = @Translation("No UI processor"),
|
||||
* no_ui = true,
|
||||
* )
|
||||
*/
|
||||
class NoUi extends ProcessorPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsStage($stage_identifier) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\search_api_test_no_ui\Plugin\search_api\tracker;
|
||||
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\search_api\Plugin\search_api\tracker\Basic;
|
||||
|
||||
/**
|
||||
* Provides a test tracker that should be hidden from the UI.
|
||||
*
|
||||
* @SearchApiTracker(
|
||||
* id = "search_api_test_no_ui",
|
||||
* label = @Translation("No UI tracker"),
|
||||
* no_ui = true,
|
||||
* )
|
||||
*/
|
||||
class NoUi extends Basic implements PluginFormInterface {
|
||||
}
|
||||
+3
-3
@@ -7,8 +7,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ dependencies:
|
||||
# core: 8.x
|
||||
hidden: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-07-19
|
||||
version: '8.x-1.9'
|
||||
# Information added by Drupal.org packaging script on 2018-11-18
|
||||
version: '8.x-1.11'
|
||||
core: '8.x'
|
||||
project: 'search_api'
|
||||
datestamp: 1531985591
|
||||
datestamp: 1542564486
|
||||
|
||||
+5
-11
@@ -10,18 +10,12 @@ use Drupal\search_api\Query\QueryInterface;
|
||||
/**
|
||||
* Implements hook_search_api_query_alter().
|
||||
*
|
||||
* Prints the contents of the "search_api_retrieved_properties" query option to
|
||||
* the page (if present) so it can be checked by the testing code.
|
||||
* Prints the contents of the "search_api_retrieved_field_values" query option
|
||||
* to the page (if present) so it can be checked by the testing code.
|
||||
*/
|
||||
function search_api_test_views_search_api_query_alter(QueryInterface $query) {
|
||||
$properties = $query->getOption('search_api_retrieved_properties');
|
||||
if ($properties) {
|
||||
$message = [];
|
||||
foreach ($properties as $datasource_properties) {
|
||||
foreach ($datasource_properties as $combined_property_path) {
|
||||
$message[] = "'$combined_property_path'";
|
||||
}
|
||||
}
|
||||
\Drupal::messenger()->addStatus(implode(' ', $message));
|
||||
$fields = $query->getOption('search_api_retrieved_field_values');
|
||||
if ($fields) {
|
||||
\Drupal::messenger()->addStatus("'" . implode("' '", $fields) . "'");
|
||||
}
|
||||
}
|
||||
|
||||
+19
-4
@@ -63,6 +63,7 @@ class IntegrationTest extends SearchApiBrowserTestBase {
|
||||
'node',
|
||||
'search_api',
|
||||
'search_api_test',
|
||||
'search_api_test_no_ui',
|
||||
'field_ui',
|
||||
'link',
|
||||
'image',
|
||||
@@ -204,6 +205,8 @@ class IntegrationTest extends SearchApiBrowserTestBase {
|
||||
$this->drupalGet($settings_path);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->assertSession()->pageTextNotContains('No UI backend');
|
||||
|
||||
$edit = [
|
||||
'name' => '',
|
||||
'status' => 1,
|
||||
@@ -280,6 +283,14 @@ class IntegrationTest extends SearchApiBrowserTestBase {
|
||||
$this->drupalGet($settings_path);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->assertSession()->pageTextNotContains('No UI datasource');
|
||||
$this->assertSession()->pageTextNotContains('No UI tracker');
|
||||
|
||||
// Make sure plugin labels are only escaped when necessary.
|
||||
$this->assertHtmlEscaped('"Test" tracker');
|
||||
$this->assertHtmlEscaped('"String label" test tracker');
|
||||
$this->assertHtmlEscaped('"Test" datasource');
|
||||
|
||||
// Make sure datasource and tracker plugin descriptions are displayed.
|
||||
$dummy_index = Index::create();
|
||||
foreach (['createDatasourcePlugins', 'createTrackerPlugins'] as $method) {
|
||||
@@ -288,9 +299,8 @@ class IntegrationTest extends SearchApiBrowserTestBase {
|
||||
->get('search_api.plugin_helper')
|
||||
->$method($dummy_index);
|
||||
foreach ($plugins as $plugin) {
|
||||
$description = strip_tags($plugin->getDescription());
|
||||
$description = Html::decodeEntities($description);
|
||||
$this->assertSession()->pageTextContains($description);
|
||||
$description = Utility::escapeHtml($plugin->getDescription());
|
||||
$this->assertSession()->responseContains($description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,6 +777,9 @@ class IntegrationTest extends SearchApiBrowserTestBase {
|
||||
|
||||
$this->drupalGet($this->getIndexPath('fields'));
|
||||
$this->assertHtmlEscaped($field_name);
|
||||
// Also check data type labels/descriptions.
|
||||
$this->assertHtmlEscaped('"Test" data type');
|
||||
$this->assertSession()->responseContains('Dummy <em>data type</em> implementation');
|
||||
|
||||
$edit = [
|
||||
'datasource_configs[entity:node][bundles][default]' => 1,
|
||||
@@ -807,6 +820,8 @@ class IntegrationTest extends SearchApiBrowserTestBase {
|
||||
$this->addField('entity:node', $property_path, $label);
|
||||
}
|
||||
|
||||
$this->assertSession()->pageTextNotContains('No UI data type');
|
||||
|
||||
$index = $this->getIndex(TRUE);
|
||||
$fields = $index->getFields();
|
||||
|
||||
@@ -1206,7 +1221,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());
|
||||
$this->assertSession()->responseContains($this->getIndex()->toUrl('canonical')->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-7
@@ -210,13 +210,7 @@ class OverviewPageTest extends SearchApiBrowserTestBase {
|
||||
|
||||
$this->drupalGet($this->overviewPageUrl);
|
||||
$basic_url = $this->urlGenerator->generateFromRoute('entity.search_api_server.canonical', ['search_api_server' => $server->id()]);
|
||||
$destination = '';
|
||||
// Drupal 8.5.x introduced "destination" parameters to all operations links
|
||||
// by default, so we now need to take that into account.
|
||||
// @todo Remove once we depend on 8.5.
|
||||
if (version_compare(\Drupal::VERSION, '8.5.x-dev', '>=')) {
|
||||
$destination = "?destination=" . $this->urlGenerator->generateFromRoute('search_api.overview');
|
||||
}
|
||||
$destination = "?destination=" . $this->urlGenerator->generateFromRoute('search_api.overview');
|
||||
$this->assertSession()->responseContains("<a href=\"$basic_url/edit$destination\">Edit</a>");
|
||||
$this->assertSession()->responseContains("<a href=\"$basic_url/disable$destination\">Disable</a>");
|
||||
$this->assertSession()->responseContains("<a href=\"$basic_url/delete$destination\">Delete</a>");
|
||||
|
||||
+23
@@ -30,6 +30,7 @@ class ProcessorIntegrationTest extends SearchApiBrowserTestBase {
|
||||
public static $modules = [
|
||||
'filter',
|
||||
'taxonomy',
|
||||
'search_api_test_no_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -160,6 +161,13 @@ class ProcessorIntegrationTest extends SearchApiBrowserTestBase {
|
||||
sort($actual_processors);
|
||||
$this->assertEquals($enabled, $actual_processors);
|
||||
|
||||
$this->checkNoUiIntegration();
|
||||
$enabled[] = 'search_api_test_no_ui';
|
||||
sort($enabled);
|
||||
$actual_processors = array_keys($this->loadIndex()->getProcessors());
|
||||
sort($actual_processors);
|
||||
$this->assertEquals($enabled, $actual_processors);
|
||||
|
||||
$this->checkRoleFilterIntegration();
|
||||
$enabled[] = 'role_filter';
|
||||
sort($enabled);
|
||||
@@ -506,6 +514,21 @@ TAGS
|
||||
$this->enableProcessor('entity_status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the "No UI" test processor.
|
||||
*/
|
||||
public function checkNoUiIntegration() {
|
||||
$this->loadProcessorsTab();
|
||||
$this->assertSession()->pageTextNotContains('No UI processor');
|
||||
|
||||
// Ensure that the processor can still be enabled programmatically – and
|
||||
// stays enabled when submitting the processors form.
|
||||
$index = $this->loadIndex();
|
||||
$processor = \Drupal::getContainer()->get('search_api.plugin_helper')
|
||||
->createProcessorPlugin($index, 'search_api_test_no_ui');
|
||||
$index->addProcessor($processor)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the UI for the "Role filter" processor.
|
||||
*/
|
||||
|
||||
@@ -252,6 +252,9 @@ class ViewsTest extends SearchApiBrowserTestBase {
|
||||
// have any effect.
|
||||
$this->checkResults([], [2, 4, 5], 'Search with arguments', 'all/item+article/strawberry+apple');
|
||||
|
||||
// Check "OR" contextual filters (using commas).
|
||||
$this->checkResults([], [4], 'Search with OR arguments', 'all/item,article/strawberry,apple');
|
||||
|
||||
$this->checkResults([], [], 'Search with unknown datasource argument', 'entity:foobar/all/all');
|
||||
|
||||
$query = [
|
||||
@@ -663,6 +666,19 @@ class ViewsTest extends SearchApiBrowserTestBase {
|
||||
$this->submitForm($edit, 'Expose filter');
|
||||
$this->submitPluginForm([]);
|
||||
|
||||
// Add a "Search: Fulltext search" filter.
|
||||
$this->clickLink('Add filter criteria');
|
||||
$edit = [
|
||||
'name[search_api_index_database_search_index.search_api_fulltext]' => 'search_api_index_database_search_index.search_api_fulltext',
|
||||
];
|
||||
$this->submitForm($edit, 'Add and configure filter criteria');
|
||||
$this->assertSession()->pageTextNotContains('No UI parse mode');
|
||||
$edit = [
|
||||
'options[expose_button][checkbox][checkbox]' => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Expose filter');
|
||||
$this->submitPluginForm([]);
|
||||
|
||||
// Save the view.
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
@@ -723,21 +739,18 @@ class ViewsTest extends SearchApiBrowserTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the expected retrieved properties were listed on the page.
|
||||
// Since the fields with the "field_rendering" option enabled will need the
|
||||
// complete loaded entity, these are only present as "_object" here.
|
||||
// Check whether the expected retrieved fields were listed on the page.
|
||||
// These are only "keywords" and "rendered_item", since only fields that
|
||||
// correspond to an indexed field are included (not when a field is added
|
||||
// via the datasource table), and only if "Use entity field rendering" is
|
||||
// disabled.
|
||||
// @see search_api_test_views_search_api_query_alter()
|
||||
$retrieved_properties = [
|
||||
Utility::createCombinedId($datasource_id, 'id'),
|
||||
Utility::createCombinedId($datasource_id, '_object'),
|
||||
Utility::createCombinedId($datasource_id, 'keywords'),
|
||||
Utility::createCombinedId($datasource_id, 'user_id'),
|
||||
Utility::createCombinedId($datasource_id, 'user_id:entity:_object'),
|
||||
Utility::createCombinedId($datasource_id, 'user_id:entity:roles'),
|
||||
Utility::createCombinedId(NULL, 'rendered_item'),
|
||||
$retrieved_fields = [
|
||||
'keywords',
|
||||
'rendered_item',
|
||||
];
|
||||
foreach ($retrieved_properties as $combined_property_path) {
|
||||
$this->assertSession()->pageTextContains("'$combined_property_path'");
|
||||
foreach ($retrieved_fields as $field_id) {
|
||||
$this->assertSession()->pageTextContains("'$field_id'");
|
||||
}
|
||||
|
||||
// Check that click-sorting works correctly.
|
||||
|
||||
@@ -88,7 +88,9 @@ abstract class BackendTestBase extends KernelTestBase {
|
||||
$this->searchNoResults();
|
||||
$this->indexItems($this->indexId);
|
||||
$this->searchSuccess();
|
||||
$this->checkFacets();
|
||||
if ($this->getServer()->supportsFeature('search_api_facets')) {
|
||||
$this->checkFacets();
|
||||
}
|
||||
$this->checkSecondServer();
|
||||
$this->regressionTests();
|
||||
$this->clearIndex();
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\search_api\Kernel\Processor;
|
||||
|
||||
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\search_api\Datasource\DatasourceInterface;
|
||||
use Drupal\search_api\Item\Field;
|
||||
use Drupal\search_api\Processor\ProcessorPropertyInterface;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the "Reverse entity references" processor.
|
||||
*
|
||||
* @group search_api
|
||||
*
|
||||
* @coversDefaultClass \Drupal\search_api\Plugin\search_api\processor\ReverseEntityReferences
|
||||
*/
|
||||
class ReverseEntityReferencesTest extends ProcessorTestBase {
|
||||
|
||||
/**
|
||||
* The nodes created for testing.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node[]
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
/**
|
||||
* The author UIDs of the created nodes, keyed by index in $this->nodes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nodeUids = [
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp($processor = NULL) {
|
||||
parent::setUp('reverse_entity_references');
|
||||
|
||||
// Create a node type for testing.
|
||||
$type = NodeType::create(['type' => 'page', 'name' => 'page']);
|
||||
$type->save();
|
||||
|
||||
// Insert the anonymous user into the database, as well as some other users.
|
||||
User::create([
|
||||
'uid' => 0,
|
||||
'name' => '',
|
||||
])->save();
|
||||
User::create([
|
||||
'uid' => 1,
|
||||
'name' => 'admin',
|
||||
])->save();
|
||||
User::create([
|
||||
'uid' => 2,
|
||||
'name' => 'user',
|
||||
])->save();
|
||||
User::create([
|
||||
'uid' => 3,
|
||||
'name' => 'other user',
|
||||
])->save();
|
||||
|
||||
// Create nodes.
|
||||
foreach ($this->nodeUids as $i => $uid) {
|
||||
$values = [
|
||||
'type' => 'page',
|
||||
'title' => 'test title',
|
||||
'uid' => $uid,
|
||||
];
|
||||
$this->nodes[$i] = Node::create($values);
|
||||
$this->nodes[$i]->save();
|
||||
}
|
||||
|
||||
// Switch the index to index users and add a reverse reference to the nodes
|
||||
// authored by the indexed user.
|
||||
$datasources = \Drupal::getContainer()
|
||||
->get('search_api.plugin_helper')
|
||||
->createDatasourcePlugins($this->index, [
|
||||
'entity:user',
|
||||
]);
|
||||
$this->index->setDatasources($datasources);
|
||||
$field = new Field($this->index, 'nid');
|
||||
$field->setType('integer');
|
||||
$field->setPropertyPath('search_api_reverse_entity_references_node__uid:nid');
|
||||
$field->setDatasourceId('entity:user');
|
||||
$field->setLabel('Authored nodes');
|
||||
$this->index->addField($field);
|
||||
$this->index->save();
|
||||
|
||||
\Drupal::getContainer()
|
||||
->get('search_api.index_task_manager')
|
||||
->addItemsAll($this->index);
|
||||
$index_storage = \Drupal::entityTypeManager()
|
||||
->getStorage('search_api_index');
|
||||
$index_storage->resetCache([$this->index->id()]);
|
||||
$this->index = $index_storage->load($this->index->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that property definitions are created correctly.
|
||||
*
|
||||
* @covers ::getPropertyDefinitions
|
||||
*/
|
||||
public function testGetPropertyDefinitions() {
|
||||
$properties = $this->processor->getPropertyDefinitions(NULL);
|
||||
$this->assertEmpty($properties);
|
||||
|
||||
$datasource = $this->createMock(DatasourceInterface::class);
|
||||
$datasource->method('getEntityTypeId')
|
||||
->willReturn(NULL);
|
||||
$properties = $this->processor->getPropertyDefinitions($datasource);
|
||||
$this->assertEmpty($properties);
|
||||
|
||||
$datasource = $this->index->getDatasource('entity:user');
|
||||
$properties = $this->processor->getPropertyDefinitions($datasource);
|
||||
$this->assertArrayHasKey('search_api_reverse_entity_references_node__uid', $properties);
|
||||
|
||||
/** @var \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface|\Drupal\search_api\Processor\ProcessorPropertyInterface $property */
|
||||
$property = $properties['search_api_reverse_entity_references_node__uid'];
|
||||
$this->assertInstanceOf(EntityDataDefinitionInterface::class, $property);
|
||||
$this->assertInstanceOf(ProcessorPropertyInterface::class, $property);
|
||||
$this->assertEquals('node', $property->getEntityTypeId());
|
||||
$this->assertEquals('reverse_entity_references', $property->getProcessorId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that field value extraction works correctly.
|
||||
*
|
||||
* @covers ::addFieldValues
|
||||
*/
|
||||
public function testAddFieldValues() {
|
||||
$nids_by_user = [];
|
||||
foreach ($this->nodeUids as $i => $uid) {
|
||||
$nids_by_user[$uid][] = $this->nodes[$i]->id();
|
||||
}
|
||||
|
||||
$fields_helper = \Drupal::getContainer()->get('search_api.fields_helper');
|
||||
foreach ([0, 1, 2, 3] as $uid) {
|
||||
$item = $fields_helper->createItem($this->index, "entity:user/$uid:en");
|
||||
|
||||
// This will automatically trigger field extraction.
|
||||
$nids = $item->getField('nid')->getValues();
|
||||
|
||||
sort($nids);
|
||||
$nids_by_user += [$uid => []];
|
||||
$this->assertEquals($nids_by_user[$uid], $nids, "Unexpected field values extracted for user #$uid.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+17
-1
@@ -71,6 +71,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
'datasourceInstances' => [
|
||||
'entity:test1' => $datasource,
|
||||
'entity:test2' => $datasource,
|
||||
'entity:test3' => $datasource,
|
||||
],
|
||||
'processorInstances' => [],
|
||||
'field_settings' => [
|
||||
@@ -89,6 +90,11 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
'datasource_id' => 'entity:test2',
|
||||
'property_path' => 'foobaz:bla',
|
||||
],
|
||||
'always_empty' => [
|
||||
'type' => 'string',
|
||||
'datasource_id' => 'entity:test3',
|
||||
'property_path' => 'always_empty',
|
||||
],
|
||||
'aggregated_field' => [
|
||||
'type' => 'text',
|
||||
'property_path' => 'aggregated_field',
|
||||
@@ -146,6 +152,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
'entity:test1/foo',
|
||||
'entity:test1/foo:bar',
|
||||
'entity:test2/foobaz:bla',
|
||||
'entity:test3/always_empty',
|
||||
],
|
||||
];
|
||||
$this->index->getField($this->fieldId)->setConfiguration($configuration);
|
||||
@@ -166,7 +173,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
}
|
||||
$items = [];
|
||||
$i = 0;
|
||||
foreach (['entity:test1', 'entity:test2'] as $datasource_id) {
|
||||
foreach (['entity:test1', 'entity:test2', 'entity:test3'] as $datasource_id) {
|
||||
$this->itemIds[$i++] = $item_id = Utility::createCombinedId($datasource_id, '1:en');
|
||||
$item = \Drupal::getContainer()
|
||||
->get('search_api.fields_helper')
|
||||
@@ -191,6 +198,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
|
||||
$this->assertEquals(array_map($this->valueCallback, $expected[0]), $items[$this->itemIds[0]]->getField($this->fieldId)->getValues(), 'Correct aggregation for item 1.');
|
||||
$this->assertEquals(array_map($this->valueCallback, $expected[1]), $items[$this->itemIds[1]]->getField($this->fieldId)->getValues(), 'Correct aggregation for item 2.');
|
||||
$this->assertEquals(array_map($this->valueCallback, $expected[2]), $items[$this->itemIds[2]]->getField($this->fieldId)->getValues(), 'Correct aggregation for item 3.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,6 +217,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
['foo', 'bar', 'baz'],
|
||||
['foobar'],
|
||||
[],
|
||||
],
|
||||
],
|
||||
'"Concatenation" aggregation' => [
|
||||
@@ -216,6 +225,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
["foo\n\nbar\n\nbaz"],
|
||||
['foobar'],
|
||||
[''],
|
||||
],
|
||||
],
|
||||
'"Sum" aggregation' => [
|
||||
@@ -223,6 +233,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
[22],
|
||||
[7],
|
||||
[0],
|
||||
],
|
||||
TRUE,
|
||||
],
|
||||
@@ -231,6 +242,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
[3],
|
||||
[1],
|
||||
[0],
|
||||
],
|
||||
],
|
||||
'"Maximum" aggregation' => [
|
||||
@@ -238,6 +250,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
[16],
|
||||
[7],
|
||||
[],
|
||||
],
|
||||
TRUE,
|
||||
],
|
||||
@@ -246,6 +259,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
[2],
|
||||
[7],
|
||||
[],
|
||||
],
|
||||
TRUE,
|
||||
],
|
||||
@@ -254,6 +268,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
['foo'],
|
||||
['foobar'],
|
||||
[],
|
||||
],
|
||||
],
|
||||
'"Last" aggregation' => [
|
||||
@@ -261,6 +276,7 @@ class AggregatedFieldsTest extends UnitTestCase {
|
||||
[
|
||||
['baz'],
|
||||
['foobar'],
|
||||
[],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
+98
@@ -468,6 +468,60 @@ class HighlightTest extends UnitTestCase {
|
||||
$this->assertEquals($correct_output, $excerpt, 'Excerpt was added.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether excerpt creation correctly handles HTML tags.
|
||||
*/
|
||||
public function testPostprocessSearchResultsExcerptStripTags() {
|
||||
$query = $this->createMock(QueryInterface::class);
|
||||
$query->expects($this->once())
|
||||
->method('getProcessingLevel')
|
||||
->willReturn(QueryInterface::PROCESSING_FULL);
|
||||
$query->expects($this->atLeastOnce())
|
||||
->method('getOriginalKeys')
|
||||
->will($this->returnValue(['#conjunction' => 'AND', 'foo']));
|
||||
/** @var \Drupal\search_api\Query\QueryInterface $query */
|
||||
|
||||
$field = $this->createTestField('body', 'entity:node/body');
|
||||
|
||||
$this->index->expects($this->atLeastOnce())
|
||||
->method('getFields')
|
||||
->will($this->returnValue(['body' => $field]));
|
||||
|
||||
$this->processor->setIndex($this->index);
|
||||
|
||||
$body_value = <<<'END'
|
||||
Sentence with foo keyword.
|
||||
<script>
|
||||
var foo = 1;
|
||||
</script>
|
||||
<style>
|
||||
a.foo {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
And another foo!
|
||||
END;
|
||||
$fields = [
|
||||
'entity:node/body' => [
|
||||
'type' => 'text',
|
||||
'values' => [$body_value],
|
||||
],
|
||||
];
|
||||
|
||||
$items = $this->createItems($this->index, 1, $fields);
|
||||
|
||||
$results = new ResultSet($query);
|
||||
$results->setResultItems($items);
|
||||
$results->setResultCount(1);
|
||||
|
||||
$this->processor->postprocessSearchResults($results);
|
||||
|
||||
$output = $results->getResultItems();
|
||||
$excerpt = $output[$this->itemIds[0]]->getExcerpt();
|
||||
$correct_output = '… Sentence with <strong>foo</strong> keyword. And another <strong>foo</strong>! …';
|
||||
$this->assertEquals($correct_output, $excerpt, 'Excerpt was added.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether highlighting works on a longer text matching near the end.
|
||||
*/
|
||||
@@ -600,6 +654,50 @@ class HighlightTest extends UnitTestCase {
|
||||
$this->assertEmpty($excerpt, 'No excerpt added when disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether excerpt creation uses the "highlighted_keys" extra data.
|
||||
*/
|
||||
public function testPostprocessSearchResultsExcerptWithKeysFromBackend() {
|
||||
$query = $this->createMock(QueryInterface::class);
|
||||
$query->expects($this->once())
|
||||
->method('getProcessingLevel')
|
||||
->willReturn(QueryInterface::PROCESSING_FULL);
|
||||
$query->expects($this->atLeastOnce())
|
||||
->method('getOriginalKeys')
|
||||
->will($this->returnValue(['#conjunction' => 'AND', 'congues']));
|
||||
/** @var \Drupal\search_api\Query\QueryInterface $query */
|
||||
|
||||
$field = $this->createTestField('body', 'entity:node/body');
|
||||
|
||||
$this->index->expects($this->atLeastOnce())
|
||||
->method('getFields')
|
||||
->will($this->returnValue(['body' => $field]));
|
||||
|
||||
$this->processor->setIndex($this->index);
|
||||
|
||||
$body_values = [$this->getFieldBody()];
|
||||
$fields = [
|
||||
'entity:node/body' => [
|
||||
'type' => 'text',
|
||||
'values' => $body_values,
|
||||
],
|
||||
];
|
||||
|
||||
$items = $this->createItems($this->index, 1, $fields);
|
||||
$items[$this->itemIds[0]]->setExtraData('highlighted_keys', ['congue']);
|
||||
|
||||
$results = new ResultSet($query);
|
||||
$results->setResultItems($items);
|
||||
$results->setResultCount(1);
|
||||
|
||||
$this->processor->postprocessSearchResults($results);
|
||||
|
||||
$output = $results->getResultItems();
|
||||
$excerpt = $output[$this->itemIds[0]]->getExcerpt();
|
||||
$correct_output = '… tristique, ligula sit amet condimentum dapibus, lorem nunc <strong>congue</strong> velit, et dictum augue leo sodales augue. Maecenas …';
|
||||
$this->assertEquals($correct_output, $excerpt, 'Excerpt was added.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether highlighting works on a longer text.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user