- 🇫🇷France gaspounet
Two years and a half later I'm asking the same question: how to implement spellcheck in the context of a rest export?
For example, is it possible to include the "did you mean" suggestion inside a metadata as pager or facets do?
- 🇫🇷France gaspounet
OK, I answer to my own question,
For those who are interested, here is some code to create a custom serializer to use within a REST Export display which shows spellcheck suggestions in a "spellcheck" metadata inside the JSON response.
modules/custom/my_custom_module/Plugin/views/style/CustomSerializer.php
<?php namespace Drupal\my_custom_module\Plugin\views\style; use Drupal\Core\Form\FormStateInterface; use Drupal\rest\Plugin\views\style\Serializer; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Serializer\SerializerInterface; /** * The style plugin for serialized output formats. * * @ingroup views_style_plugins * * @ViewsStyle( * id = "my_custom_serializer", * title = @Translation( "My Custom serializer" ), * help = @Translation( "Serializes views row data using the Serializer component." ), * display_types = { "data" } * ) */ class CustomSerializer extends Serializer { public static function create( ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition ) { return new static( $configuration, $plugin_id, $plugin_definition, $container -> get( 'serializer' ), $container -> getParameter( 'serializer.formats' ), $container -> getParameter( 'serializer.format_providers' ) ); } public function __construct( array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, array $serializer_formats, array $serializer_format_providers ) { parent::__construct( $configuration, $plugin_id, $plugin_definition, $serializer, $serializer_formats, $serializer_format_providers ); } protected function defineOptions() { $options = parent::defineOptions(); $options[ 'spellcheck_count' ] = [ 'default' => 1 ]; return $options; } public function buildOptionsForm( &$form, FormStateInterface $form_state ) { parent::buildOptionsForm( $form, $form_state ); $form[ 'spellcheck_count' ] = [ '#type' => 'number', '#title' => $this -> t( 'Spellcheck, amount of results to show' ), '#default_value' => $this -> options[ 'spellcheck_count' ], ]; } public function render() { $rows = []; foreach ( $this -> view -> result as $row_index => $row ) { $this -> view -> row_index = $row_index; $row = $this -> view -> rowPlugin -> render( $row ); //YOUR OWN CODE IF YOU WANT TO MAKE SOME MODIFICATION ON EACH ROW $rows[] = $row; } unset( $this -> view -> row_index) ; if ( empty( $this -> view -> live_preview ) ) { $content_type = $this -> displayHandler -> getContentType(); } else { $content_type = !empty( $this -> options[ 'formats' ] ) ? reset( $this -> options[ 'formats' ] ) : 'json'; } $fields = []; foreach ( $this -> view -> field as $field_name => $field ) { if ( $field -> options[ 'exclude' ] == 0 && $field -> label() ) { $fields[ $field_name ] = $field -> label(); } } $current_page = 0; $total_items = 0; $total_pages = 0; $items_per_page = 0; $pager = $this -> view -> pager; if ( $pager ) { $class = get_class( $pager ); $current_page = $pager -> getCurrentPage() ?? 0; $items_per_page = $pager -> getItemsPerPage(); $total_items = $pager -> getTotalItems(); if ( !in_array( $class, [ 'Drupal\views\Plugin\views\pager\None', 'Drupal\views\Plugin\views\pager\Some', ] ) ) { $total_pages = $pager -> getPagerTotal(); } } else { $current_page = intval( \Drupal::request() -> query -> get( 'page' ) ); $items_per_page = 1; $total_pages = count( $rows ); $total_items = count( $rows ); } $spellcheck_suggestions = []; $query = $this -> view -> query; if ( $query instanceof \Drupal\search_api\Plugin\views\query\SearchApiQuery && $query -> getIndex() -> getServerInstance()-> supportsFeature( 'search_api_spellcheck' ) ) { $search_api_query = $query -> getSearchApiQuery(); $index = $search_api_query -> getIndex(); $backend = $index -> getServerInstance() -> getBackend(); $connector = $backend -> getSolrConnector(); $solarium_query = $connector -> getSelectQuery(); $spellcheck = $solarium_query -> getSpellcheck(); $schema_languages = $backend -> getSchemaLanguageStatistics(); $dictionaries = []; foreach ( \Drupal\search_api_solr\Utility\Utility::ensureLanguageCondition( $search_api_query ) as $language_id ) { if ( isset( $schema_languages[ $language_id ] ) && $schema_languages[ $language_id ] ) { $dictionaries[] = $schema_languages[ $language_id ]; } } if ( $dictionaries ) { $spellcheck -> setDictionary( $dictionaries ); } else { $spellcheck -> setDictionary( \Drupal\Core\Language\LanguageInterface::LANGCODE_NOT_SPECIFIED ); } $keys = $query -> getKeys(); if ( $keys && is_array( $keys ) ) { $spellcheck -> setQuery( implode( ' ', array_filter( $keys, 'is_int', ARRAY_FILTER_USE_KEY ) ) ); } $spellcheck -> setCount( $this -> options[ 'spellcheck_count' ] ); $spellcheck -> setBuild( TRUE ); $spellcheck -> setCollate( TRUE ); $spellcheck -> setExtendedResults( TRUE ); $spellcheck -> setCollateExtendedResults( TRUE ); $resultset = $connector -> execute( $solarium_query ); $spellcheck_results = $resultset -> getSpellcheck(); $spellcheck_suggestions = []; if ( $spellcheck_results ) { foreach ( $spellcheck_results -> getSuggestions() as $suggestion ) { $spellcheck_suggestions[] = [ 'word' => $suggestion -> getWord(), 'numFound' => $suggestion -> getNumFound(), 'startOffset' => $suggestion -> getStartOffset(), 'endOffset' => $suggestion -> getEndOffset(), 'suggestion' => $suggestion -> getWords() ]; } } } return $this -> serializer -> serialize( [ 'title' => $this -> view -> getTitle(), 'rows' => $rows, 'spellcheck' => $spellcheck_suggestions, 'fields' => $fields, 'pager' => [ 'current_page' => $current_page, 'total_items' => $total_items, 'total_pages' => $total_pages, 'items_per_page' => $items_per_page, ] ], $content_type, [ 'views_style_plugin' => $this ] ); } }
For example, if I set an exposed search_api_fulltext filter (be careful that this is not working for "direct parse") and if I search for the word "spac", I obtain the following JSON response:
{"title":"My view",rows":[],"spellcheck":[{"word":"space","numFound":10,"startOffset":0,"endOffset":4,"suggestion":[{"word":"space","freq":60},{"word":"spacex","freq":7},{"word":"spain","freq":7},{"word":"scaf","freq":6},{"word":"spare","freq":6},{"word":"sdas","freq":3},{"word":"spot","freq":3},{"word":"seal","freq":2},{"word":"seas","freq":2},{"word":"spaces","freq":2}]}],"fields":{"date":"Date","description":"Description","country":"Country","source":"Source","tag":"Tag(s)","created":"Edited on"},"pager":{"current_page":0,"total_items":0,"total_pages":0,"items_per_page":50}}
It's just a first shot, I know it can be improved