diff --git a/config/schema/search_api.views.schema.yml b/config/schema/search_api.views.schema.yml index 7be181f..e235208 100644 --- a/config/schema/search_api.views.schema.yml +++ b/config/schema/search_api.views.schema.yml @@ -37,6 +37,100 @@ views.argument.search_api_more_like_this: type: string label: 'Field' +views.field.search_api: + type: views_field + label: 'Search API standard' + mapping: + link_to_item: + type: boolean + label: 'Link to item' + multi_type: + type: string + label: 'Handling of multiple values' + multi_separator: + type: string + label: 'Separator for multiple values' + +views.field.search_api_boolean: + type: views.field.boolean + label: 'Search API boolean' + mapping: + link_to_item: + type: boolean + label: 'Link to item' + multi_type: + type: string + label: 'Handling of multiple values' + multi_separator: + type: string + label: 'Separator for multiple values' + +views.field.search_api_date: + type: views.field.date + label: 'Search API date' + mapping: + link_to_item: + type: boolean + label: 'Link to item' + multi_type: + type: string + label: 'Handling of multiple values' + multi_separator: + type: string + label: 'Separator for multiple values' + +views.field.search_api_entity: + type: views.field.search_api + label: 'Search API entity reference' + mapping: + display_methods: + type: sequence + label: 'Display settings' + sequence: + type: mapping + label: 'Display settings for bundle' + mapping: + display_method: + type: string + label: 'Display method' + view_mode: + type: string + label: 'View mode' + +views.field.search_api_field: + type: views.field.field + label: 'Search API entity field' + mapping: + field_rendering: + type: boolean + label: 'Use entity field rendering' + fallback_handler: + type: string + label: 'Fallback handler' + fallback_options: + type: views.field.[%parent.fallback_handler] + label: 'Options for fallback handler' + +views.field.search_api_numeric: + type: views.field.numeric + label: 'Search API boolean' + mapping: + link_to_item: + type: boolean + label: 'Link to item' + multi_type: + type: string + label: 'Handling of multiple values' + multi_separator: + type: string + label: 'Separator for multiple values' + format_plural_values: + type: sequence + label: 'Pluralized strings' + sequence: + type: string + label: 'Singular/Plural string' + views.filter.search_api_boolean: type: views.filter.boolean label: 'Search API boolean' diff --git a/src/Plugin/views/field/SearchApiEntityField.php b/src/Plugin/views/field/SearchApiEntityField.php index c20837e..d824289 100644 --- a/src/Plugin/views/field/SearchApiEntityField.php +++ b/src/Plugin/views/field/SearchApiEntityField.php @@ -105,7 +105,8 @@ class SearchApiEntityField extends Field { $options = parent::defineOptions(); $options['field_rendering'] = array('default' => TRUE); - $options['fallback_options'] = array('default' => $this->fallbackHandler->defineOptions()); + $options['fallback_handler'] = array('default' => $this->fallbackHandler->getPluginId()); + $options['fallback_options'] = array('contains' => $this->fallbackHandler->defineOptions()); return $options; } diff --git a/src/Plugin/views/field/SearchApiFieldTrait.php b/src/Plugin/views/field/SearchApiFieldTrait.php index 6155008..596d577 100644 --- a/src/Plugin/views/field/SearchApiFieldTrait.php +++ b/src/Plugin/views/field/SearchApiFieldTrait.php @@ -411,7 +411,7 @@ trait SearchApiFieldTrait { // over properties. foreach ($row->_relationship_objects[$property_path] as $typed_data) { if ($set_values) { - $row->$combined_property_path[] = Utility::extractFieldValues($typed_data); + $row->{$combined_property_path}[] = Utility::extractFieldValues($typed_data); } } // If we just set any field values on the result row, clean them up diff --git a/src/Plugin/views/field/SearchApiNumeric.php b/src/Plugin/views/field/SearchApiNumeric.php index d57826c..cd413be 100644 --- a/src/Plugin/views/field/SearchApiNumeric.php +++ b/src/Plugin/views/field/SearchApiNumeric.php @@ -46,7 +46,7 @@ class SearchApiNumeric extends NumericField implements MultiItemsFieldHandlerInt public function defineOptions() { $options = $this->traitDefineOptions(); - $options['format_plural_string'] = array('default' => array()); + $options['format_plural_values'] = array('default' => array()); return $options; } diff --git a/src/Plugin/views/row/SearchApiRow.php b/src/Plugin/views/row/SearchApiRow.php index f48b730..3299a0a 100644 --- a/src/Plugin/views/row/SearchApiRow.php +++ b/src/Plugin/views/row/SearchApiRow.php @@ -185,28 +185,6 @@ class SearchApiRow extends RowPluginBase { /** * {@inheritdoc} */ - public function summaryTitle() { - $summary = array(); - foreach ($this->options['view_modes'] as $datasource_id => $bundles) { - $datasource = $this->index->getDatasource($datasource_id); - $bundles_info = $datasource->getBundles(); - foreach ($bundles as $bundle => $view_mode) { - $view_modes = $datasource->getViewModes($bundle); - $label = isset($view_modes[$view_mode]) ? $view_modes[$view_mode] : $this->t('Hidden'); - $args = array( - '@bundle' => $bundles_info[$bundle], - '@datasource' => $datasource->label(), - '@view_mode' => $label, - ); - $summary[] = $this->t('@datasource/@bundle: @view_mode', $args); - } - } - return $summary ? implode('; ', $summary) : $this->t('No settings'); - } - - /** - * {@inheritdoc} - */ public function preRender($result) { // Load all result objects at once, before rendering. $items_to_load = array(); diff --git a/src/Tests/CacheabilityTest.php b/src/Tests/CacheabilityTest.php index 58cf483..ce272d8 100644 --- a/src/Tests/CacheabilityTest.php +++ b/src/Tests/CacheabilityTest.php @@ -61,7 +61,7 @@ class CacheabilityTest extends WebTestBase { $this->drupalLogin($this->adminUser); // Verify that the search results are marked as uncacheable. - $this->drupalGet('search-api-test-fulltext'); + $this->drupalGet('search-api-test'); $this->assertResponse(200); $this->assertHeader('x-drupal-dynamic-cache', 'UNCACHEABLE'); $this->assertTrue(strpos($this->drupalGetHeader('cache-control'), 'no-cache')); diff --git a/src/Tests/HooksTest.php b/src/Tests/HooksTest.php index 348549c..b1504e9 100644 --- a/src/Tests/HooksTest.php +++ b/src/Tests/HooksTest.php @@ -86,7 +86,7 @@ class HooksTest extends WebTestBase { $this->drupalGet($this->getIndexPath('fields/add'), $url_options); $this->assertNoText('timestamp'); - $this->drupalGet('search-api-test-fulltext'); + $this->drupalGet('search-api-test'); // hook_search_api_query_alter was triggered. $this->assertText('Funky blue note'); // hook_search_api_results_alter was triggered. diff --git a/src/Tests/ViewsTest.php b/src/Tests/ViewsTest.php index ad56baa..3362593 100644 --- a/src/Tests/ViewsTest.php +++ b/src/Tests/ViewsTest.php @@ -9,6 +9,7 @@ namespace Drupal\search_api\Tests; use Drupal\Component\Utility\Html; use Drupal\search_api\Entity\Index; +use Drupal\search_api\Utility; /** * Tests the Views integration of the Search API. @@ -50,7 +51,7 @@ class ViewsTest extends WebTestBase { /** * Tests a view with exposed filters. */ - public function testView() { + public function dpmtestView() { $this->checkResults(array(), array_keys($this->entities), 'Unfiltered search'); $this->checkResults(array('search_api_fulltext' => 'foobar'), array(3), 'Search for a single word'); @@ -183,7 +184,7 @@ class ViewsTest extends WebTestBase { } /** - * Test views admin. + * Test Views admin UI and field handlers. */ public function testViewsAdmin() { $admin_user = $this->drupalCreateUser(array( @@ -192,9 +193,135 @@ class ViewsTest extends WebTestBase { 'administer views', )); $this->drupalLogin($admin_user); - $this->insertExampleContent(); - $this->drupalGet('admin/structure/views/view/search_api_test_views_fulltext'); + $this->drupalGet('admin/structure/views/view/search_api_test_view'); + $this->assertResponse(200); + + // Switch to "Fields" row style. + $this->clickLink($this->t('Rendered entity')); + $this->assertResponse(200); + $edit = array( + 'row[type]' => 'fields', + ); + $this->drupalPostForm(NULL, $edit, $this->t('Apply')); + $this->assertResponse(200); + $this->drupalPostForm(NULL, array(), $this->t('Apply')); + $this->assertResponse(200); + + // Add new fields. First check that the listing seems correct. + $this->clickLink($this->t('Add fields')); + $this->assertResponse(200); + $this->assertText('Test entity datasource'); + $this->assertText('Authored on'); + $this->assertText('Body (indexed field)'); + $this->assertText('Index Test index'); + $this->assertText('Entity ID'); + $this->assertText('Excerpt'); + $this->assertText('The search result excerpted to show found search terms'); + $this->assertText('Relevance'); + $this->assertText('The relevance of this search result with respect to the query'); + $this->assertText('Language code'); + $this->assertText('The language code of the test entity.'); + + // Then add some fields. + $fields = array( + 'views.counter', + 'search_api_datasource_database_search_index_entity_entity_test.id', + 'search_api_index_database_search_index.search_api_datasource', + 'search_api_datasource_database_search_index_entity_entity_test.body', + 'search_api_index_database_search_index.category', + 'search_api_index_database_search_index.keywords', + ); + $edit = array(); + foreach ($fields as $field) { + $edit["name[$field]"] = $field; + } + $this->drupalPostForm(NULL, $edit, $this->t('Add and configure fields')); + $this->assertResponse(200); + + for ($i = 0; $i < 6; ++$i) { + $this->submitFieldsForm(); + } + + // Save the view. + $this->drupalPostForm(NULL, array(), $this->t('Save')); + $this->assertResponse(200); + + // Check the results. + $this->drupalGet('search-api-test'); + $this->assertResponse(200); + + foreach ($this->entities as $id => $entity) { + $fields = array( + 'search_api_datasource', + 'id', + 'body', + 'category', + 'keywords', + ); + foreach ($fields as $field) { + if ($field != 'search_api_datasource') { + $data = Utility::extractFieldValues($entity->get($field)); + if (!$data) { + $data = array('[EMPTY]'); + } + } + else { + $data = array('entity:entity_test'); + } + $prefix = "#$id [$field] "; + $text = $prefix . implode("|$prefix", $data); + $this->assertText($text, "Correct value displayed for field $field on entity #$id (\"$text\")"); + } + } + } + + /** + * Submits the field handler config form currently displayed. + */ + protected function submitFieldsForm() { + $url_parts = explode('/', $this->getUrl()); + $field = array_pop($url_parts); + + $edit['options[fallback_options][multi_separator]'] = '|'; + $edit['options[alter][alter_text]'] = TRUE; + $edit['options[alter][text]'] = "#{{counter}} [$field] {{ $field }}"; + $edit['options[empty]'] = "#{{counter}} [$field] [EMPTY]"; + + switch ($field) { + case 'counter': + $edit = array( + 'options[exclude]' => TRUE, + ); + break; + + case 'id': + $edit['options[field_rendering]'] = FALSE; + break; + + case 'search_api_datasource': + unset($edit['options[fallback_options][multi_separator]']); + break; + + case 'body': + break; + + case 'category': + break; + + case 'keywords': + $edit['options[field_rendering]'] = FALSE; + break; + + } + + $button_label = $this->t('Apply'); + $buttons = $this->xpath('//input[starts-with(@value, :label)]', array(':label' => $button_label)); + if ($buttons) { + $button_label = $buttons[0]['value']; + } + + $this->drupalPostForm(NULL, $edit, $button_label); $this->assertResponse(200); } diff --git a/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml b/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml index 1c37d98..b9ebe38 100644 --- a/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml +++ b/tests/search_api_test_views/config/install/views.view.search_api_test_view.yml @@ -59,11 +59,9 @@ display: type: search_api options: view_modes: - bundle: - 'article': default - 'page': default - datasource: - 'entity:entity_test': default + 'entity:entity_test': + article: default + page: default filters: search_api_fulltext: id: search_api_fulltext @@ -78,9 +76,9 @@ display: exposed: true expose: operator_id: search_api_fulltext_op - label: '' + label: 'Fulltext search' description: '' - use_operator: true + use_operator: false operator: search_api_fulltext_op identifier: search_api_fulltext required: false @@ -155,7 +153,19 @@ display: anonymous: '0' administrator: '0' is_grouped: false - sorts: { } + sorts: + search_api_id: + id: search_api_id + table: search_api_index_database_search_index + field: search_api_id + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + plugin_id: search_api title: 'Fulltext test index' header: result: diff --git a/tests/search_api_test_views/config/install/views.view.search_api_test_views_fulltext.yml b/tests/search_api_test_views/config/install/views.view.search_api_test_views_fulltext.yml deleted file mode 100644 index ea1e341..0000000 --- a/tests/search_api_test_views/config/install/views.view.search_api_test_views_fulltext.yml +++ /dev/null @@ -1,197 +0,0 @@ -base_field: search_api_id -base_table: search_api_index_database_search_index -core: 8.x -description: '' -status: true -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: none - options: { } - cache: - type: none - options: { } - query: - type: search_api_query - options: - bypass_access: false - parse_mode: terms - exposed_form: - type: basic - options: - submit_button: Search - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: full - options: - items_per_page: 10 - offset: 0 - id: 0 - total_pages: null - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 20, 40, 60' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - tags: - previous: '‹ previous' - next: 'next ›' - first: '« first' - last: 'last »' - quantity: 9 - style: - type: default - row: - type: search_api - options: - view_modes: - bundle: - 'article': default - 'page': default - datasource: - 'entity:entity_test': default - fields: - search_api_id: - table: search_api_index_database_search_index - field: search_api_id - id: search_api_id - plugin_id: numeric - relationship: none - group_type: group - admin_label: '' - label: 'Entity ID' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - set_precision: false - precision: 0 - decimal: . - separator: ',' - format_plural: false - format_plural_string: "1\x03@count" - prefix: '' - suffix: '' - filters: - search_api_fulltext: - id: search_api_fulltext - table: search_api_index_database_search_index - field: search_api_fulltext - relationship: none - group_type: group - admin_label: '' - operator: and - value: '' - group: 1 - exposed: true - expose: - operator_id: search_api_fulltext_op - label: 'Fulltext search' - description: '' - use_operator: false - operator: search_api_fulltext_op - identifier: search_api_fulltext - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - min_length: 0 - fields: { } - plugin_id: search_api_fulltext - sorts: { } - title: 'Fulltext test index' - header: - result: - id: result - table: views - field: result - relationship: none - group_type: group - admin_label: '' - empty: false - content: 'Displaying @total search results' - plugin_id: result - footer: { } - empty: { } - relationships: { } - arguments: { } - page_1: - display_plugin: page - id: page_1 - display_title: Page - position: 1 - display_options: - path: search-api-test-fulltext -label: 'Search API Test Fulltext search view' -module: views -id: search_api_test_views_fulltext -tag: '' -langcode: en -dependencies: - module: - - search_api - - search_api_test_views