diff --git a/src/config/sync/core.extension.yml b/src/config/sync/core.extension.yml index 1a10fc4..fd59339 100644 --- a/src/config/sync/core.extension.yml +++ b/src/config/sync/core.extension.yml @@ -101,6 +101,7 @@ module: ouatt_admin: 0 ouatt_graphql: 0 ouatt_puissanceagir: 0 + ouatt_searchapi: 0 ouatt_users: 0 page_cache: 0 pagerer: 0 diff --git a/src/config/sync/search_api.index.enquetes.yml b/src/config/sync/search_api.index.enquetes.yml new file mode 100644 index 0000000..ebe4f53 --- /dev/null +++ b/src/config/sync/search_api.index.enquetes.yml @@ -0,0 +1,225 @@ +uuid: a7a2d801-6649-4fc0-8bd7-e48821669e9a +langcode: fr +status: true +dependencies: + config: + - field.storage.node.field_caillou + - field.storage.node.field_description + - field.storage.node.field_action + - field.storage.node.field_entite + - field.storage.paragraph.field_entite + - field.storage.node.field_sources + - field.storage.paragraph.field_description + - field.storage.node.field_menace_maintien + - field.storage.node.field_recit_colophon + - search_api.server.ouatterrir + module: + - node + - paragraphs + - search_api +id: enquetes +name: enquetes +description: '' +read_only: false +field_settings: + field_action: + label: 'Entités » Paragraph » Entité » Contenu » Action' + datasource_id: 'entity:node' + property_path: 'field_entite:entity:field_entite:entity:field_action' + type: text + dependencies: + config: + - field.storage.node.field_action + - field.storage.node.field_entite + - field.storage.paragraph.field_entite + module: + - node + - paragraphs + field_caillou: + label: Caillou + datasource_id: 'entity:node' + property_path: field_caillou + type: text + dependencies: + config: + - field.storage.node.field_caillou + field_description: + label: Description + datasource_id: 'entity:node' + property_path: field_description + type: text + dependencies: + config: + - field.storage.node.field_description + field_menace_maintien: + label: 'Entités » Paragraph » Entité » Contenu » Menace/Maintien' + datasource_id: 'entity:node' + property_path: 'field_entite:entity:field_entite:entity:field_menace_maintien' + type: text + dependencies: + config: + - field.storage.node.field_entite + - field.storage.node.field_menace_maintien + - field.storage.paragraph.field_entite + module: + - node + - paragraphs + field_recit_colophon: + label: 'recit colophon' + datasource_id: 'entity:node' + property_path: field_recit_colophon + type: text + dependencies: + config: + - field.storage.node.field_recit_colophon + nid: + label: 'Identifiant (ID)' + datasource_id: 'entity:node' + property_path: nid + type: integer + dependencies: + module: + - node + node_grants: + label: 'Information d''accès du nœud.' + property_path: search_api_node_grants + type: string + indexed_locked: true + type_locked: true + hidden: true + processed: + label: 'Entités » Paragraph » Entité » Contenu » Experiences vécues » Paragraph » Experience vécue » Texte traité' + datasource_id: 'entity:node' + property_path: 'field_entite:entity:field_entite:entity:field_sources:entity:field_description:processed' + type: text + dependencies: + config: + - field.storage.node.field_entite + - field.storage.node.field_sources + - field.storage.paragraph.field_description + - field.storage.paragraph.field_entite + module: + - node + - paragraphs + status: + label: status + datasource_id: 'entity:node' + property_path: status + type: boolean + indexed_locked: true + type_locked: true + dependencies: + module: + - node + title: + label: Title + datasource_id: 'entity:node' + property_path: title + type: string + dependencies: + module: + - node + title_1: + label: Title + datasource_id: 'entity:node' + property_path: title + type: string + dependencies: + module: + - node + title_2: + label: 'Entités » Paragraph » Entité » Contenu » Title' + datasource_id: 'entity:node' + property_path: 'field_entite:entity:field_entite:entity:title' + type: string + dependencies: + config: + - field.storage.node.field_entite + - field.storage.paragraph.field_entite + module: + - node + - paragraphs + uid: + label: uid + datasource_id: 'entity:node' + property_path: uid + type: integer + indexed_locked: true + type_locked: true + dependencies: + module: + - node + uuid: + label: 'Identifiant universel unique (UUID)' + datasource_id: 'entity:node' + property_path: uuid + type: string + dependencies: + module: + - node +datasource_settings: + 'entity:node': + bundles: + default: false + selected: + - concernement + - entite + languages: + default: true + selected: { } +processor_settings: + add_url: { } + aggregated_field: { } + content_access: + weights: + preprocess_query: -30 + entity_status: { } + entity_type: { } + html_filter: + weights: + preprocess_index: -15 + preprocess_query: -15 + all_fields: false + fields: + - field_action + - field_caillou + - field_description + - field_menace_maintien + - field_recit_colophon + - processed + - title + - title_1 + - title_2 + - uuid + title: true + alt: true + tags: + b: 2 + em: 1 + h1: 5 + h2: 3 + h3: 2 + strong: 2 + u: 1 + ignorecase: + weights: + preprocess_index: -20 + preprocess_query: -20 + all_fields: false + fields: + - field_caillou + - field_description + - field_recit_colophon + - title + - title_1 + - uuid + language_with_fallback: { } + rendered_item: { } +tracker_settings: + default: + indexing_order: lifo +options: + cron_limit: 50 + index_directly: true + track_changes_in_references: true +server: ouatterrir diff --git a/src/config/sync/search_api.server.ouatterrir.yml b/src/config/sync/search_api.server.ouatterrir.yml new file mode 100644 index 0000000..aaf9c2f --- /dev/null +++ b/src/config/sync/search_api.server.ouatterrir.yml @@ -0,0 +1,14 @@ +uuid: 0f20c7d2-bede-4477-bacf-eca48d6a67fa +langcode: fr +status: true +dependencies: + module: + - search_api_db +id: ouatterrir +name: ouatterrir +description: '' +backend: search_api_db +backend_config: + database: 'default:default' + min_chars: 4 + matching: partial diff --git a/src/config/sync/user.role.anonymous.yml b/src/config/sync/user.role.anonymous.yml index 4a63cc0..c205eac 100644 --- a/src/config/sync/user.role.anonymous.yml +++ b/src/config/sync/user.role.anonymous.yml @@ -12,6 +12,7 @@ dependencies: - graphql - group - node + - ouatt_searchapi - system - workflow _core: @@ -25,6 +26,7 @@ permissions: - 'access devel information' - 'access group overview' - 'access kint' + - 'access ouatt search' - 'access synonyms entity autocomplete' - 'create confidentialite workflow_transition' - 'create corpus_documents workflow_transition' diff --git a/src/config/sync/user.role.authenticated.yml b/src/config/sync/user.role.authenticated.yml index b9aab46..8b45e97 100644 --- a/src/config/sync/user.role.authenticated.yml +++ b/src/config/sync/user.role.authenticated.yml @@ -15,6 +15,7 @@ dependencies: - graphql - group - node + - ouatt_searchapi - system - workflow _core: @@ -28,6 +29,7 @@ permissions: - 'access devel information' - 'access group overview' - 'access kint' + - 'access ouatt search' - 'access own confidentialite workflow_transion overview' - 'access synonyms entity autocomplete' - 'add composition entities' diff --git a/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.info.yml b/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.info.yml new file mode 100644 index 0000000..1c73f35 --- /dev/null +++ b/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.info.yml @@ -0,0 +1,8 @@ +name: Où Atterrir Search Api +type: module +description: 'Où Atterrir search api.' +package: Ouatterrir +core: 8.x +dependencies: + - search_api +core_version_requirement: ^8 || ^9 diff --git a/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.permissions.yml b/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.permissions.yml new file mode 100644 index 0000000..c8227e7 --- /dev/null +++ b/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.permissions.yml @@ -0,0 +1,4 @@ + +access ouatt search: + title: 'Access ouatt search' + description: 'Allow access to ouatt search' diff --git a/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.routing.yml b/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.routing.yml new file mode 100644 index 0000000..c39d095 --- /dev/null +++ b/src/web/modules/custom/ouatt_searchapi/ouatt_searchapi.routing.yml @@ -0,0 +1,7 @@ +ouatt_searchapi.getresults: + path: '/ouatt_searchapi/getresults' + defaults: + _controller: '\Drupal\ouatt_searchapi\Controller\Search::getResults' + _format: json + requirements: + _permission: 'access ouatt search' diff --git a/src/web/modules/custom/ouatt_searchapi/src/Controller/Search.php b/src/web/modules/custom/ouatt_searchapi/src/Controller/Search.php new file mode 100644 index 0000000..ecaebf6 --- /dev/null +++ b/src/web/modules/custom/ouatt_searchapi/src/Controller/Search.php @@ -0,0 +1,388 @@ +getCurrentLanguage()->getId(); + + $this->index = Index::load('enquetes'); + + $this->results = [ + 'uuids' => [], + 'nids' => [] + ]; + + // // ,---.| ,---. + // // |---'|---.,---.,---.,---.,---. | |. .,---.,---., . + // // | | || ,---|`---.|---' | || ||---'| | | + // // ` ` '` `---^`---'`---' `---\`---'`---'` `---| + // // `---' + // // (to match exact materials names (like "wood-skin")) + // $this->phrase_query = $this->index->query(['offset'=>0,'limit'=>10000]); + // // set parse mode and conjunction + // $parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode') + // ->createInstance('phrase'); + // $parse_mode->setConjunction('AND'); + // $this->phrase_query->setParseMode($parse_mode); + // // Set fulltext search keywords and fields. + // if ($this->keys) { + // $this->phrase_query->keys(implode(' ', $this->keys)); + // } + // $this->phrase_query->setFulltextFields(['title']); + + // // Restrict the search to specific languages. + // $this->phrase_query->setLanguages([$lang]); + + // // Do paging. + // // $this->and_query->range($this->offset, $this->limit); + // // retrieve all results + // // $this->and_query->range(0, -1); + + // // Add sorting. + // $this->phrase_query->sort('search_api_relevance', 'DESC'); + + // // Set one or more tags for the query. + // // @see hook_search_api_query_TAG_alter() + // // @see hook_search_api_results_TAG_alter() + // $this->phrase_query->addTag('ouatt_searchapi_search_phrase_query'); + + // $phrase_results = $this->phrase_query->execute(); + + // foreach ($phrase_results as $result) { + // $this->results['uuids'][] = $result->getField('uuid')->getValues()[0]; + // $this->results['nids'][] = $result->getField('nid')->getValues()[0]; + // } + + + + // // ,---. | ,---. + // // |---|,---.,---| | |. .,---.,---., . + // // | || || | | || ||---'| | | + // // ` '` '`---' `---\`---'`---'` `---| + // // `---' + // $this->and_query = $this->index->query(['offset'=>0,'limit'=>10000]); + // // set parse mode and conjunction + // $parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode') + // ->createInstance('terms'); + // $parse_mode->setConjunction('AND'); + // $this->and_query->setParseMode($parse_mode); + // // Set fulltext search keywords and fields. + // if ($this->keys) { + // $this->and_query->keys(implode(' ', $this->keys)); + // } + + // // in case of term id provided restrict the keys to taxo fields + // if ($this->terms && is_array($this->terms) && count($this->terms)) { + // $term_conditions = $this->and_query->createConditionGroup('OR'); + // // $term = (int) $this->term; + // foreach ($this->terms as $term) { + // $tid = $term->value; + // foreach (['tag_tid', 'thesaurus_tid'] as $field) { + // $term_conditions->addCondition($field, (int) $tid); + // } + // } + // $this->and_query->addConditionGroup($term_conditions); + + // // INSTEAD TRY TO BOOST THE TAG AND THESAURUS FIELDS + // // foreach ($taxoSolrFieldsName as $fname) { + // // // $solarium_query->addParam('bf', "recip(abs(ms(NOW,{$solrField})),3.16e-11,10,0.1)"); + // // $bfparam = "if(gt(termfreq({$fname},{$this->term}),0),^21,0)"; + // // $this->or_query->addParam('bf', $bfparam); + // // } + // // look @ ouatt_searchapi_search_api_solr_query_alter in ouatt_searchapi.module + // // $this->or_query->setOption('termid', $this->term); + // } + + // if ($this->filters) { + // // FILTERS + // $filters_conditions = $this->and_query->createConditionGroup('AND'); + // foreach ($this->filters as $filter) { + // $filter = (int) $filter; + // foreach (['thesaurus_tid'] as $field) { // 'tag_tid', + // $filters_conditions->addCondition($field, $filter); + // } + // } + // $this->and_query->addConditionGroup($filters_conditions); + + // if(!$this->keys) { + // // if no keys but filters switch query to direct and add wildcard solr keys *:* + // $direct_and_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode') + // ->createInstance('direct'); + // $direct_and_parse_mode->setConjunction('AND'); + // $this->and_query->setParseMode($direct_and_parse_mode); + // // $this->and_query->keys('*:*'); + // } + // } + // // else{ + // $fulltextFields = []; + // // Recherche uniquement sur les champ thésaurus et tag + // $fulltextFields += [ + // 'thesaurus_name_0', + // 'thesaurus_synonyms_0', + // 'thesaurus_name_1', + // 'thesaurus_synonyms_1', + // 'thesaurus_name_2', + // 'thesaurus_synonyms_2', + // 'thesaurus_name_3', + // 'thesaurus_synonyms_3', + // 'thesaurus_name_4', + // 'thesaurus_synonyms_4', + // 'thesaurus_name', + // 'thesaurus_synonyms', + // 'tag_name', + // 'tag_synonyms' + // ]; + // if(count($fulltextFields)){ + // $this->and_query->setFulltextFields($fulltextFields); + // } + // // } + + // // Restrict the search to specific languages. + // $this->and_query->setLanguages([$lang]); + + // // Do paging. + // // $this->and_query->range($this->offset, $this->limit); + // // retrieve all results + // // $this->and_query->range(0, -1); + + // // Add sorting. + // $this->and_query->sort('search_api_relevance', 'DESC'); + + // // Set one or more tags for the query. + // // @see hook_search_api_query_TAG_alter() + // // @see hook_search_api_results_TAG_alter() + // $this->and_query->addTag('ouatt_searchapi_search_and_query'); + + // $and_results = $this->and_query->execute(); + + // foreach ($and_results as $result) { + // // !! have to remove duplicates from phrase query + // $nid = $result->getField('nid')->getValues()[0]; + // if ( !in_array($nid, $this->results['nids']) ) { + // $this->results['uuids'][] = $result->getField('uuid')->getValues()[0]; + // $this->results['nids'][] = $result->getField('nid')->getValues()[0]; + // } + // } + + + // $this->exactematch_count = count($this->results['nids']); + + + + // ,---. ,---. + // | |,---. | |. .,---.,---., . + // | || | || ||---'| | | + // `---'` `---\`---'`---'` `---| + // `---' + $this->or_query = $this->index->query(['offset'=>0,'limit'=>10000]); + + // Change the parse mode for the search. + // Les différentes possibilités sont + // - « direct » => Requête directe + // - « terms » => Multiple words + // - « phrase » => Single phrase + // - " edismax " => ??? + $or_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode') + ->createInstance('direct'); + $or_parse_mode->setConjunction('OR'); + $this->or_query->setParseMode($or_parse_mode); + + // Set fulltext search keywords and fields. + if ($this->keys) { + $this->or_query->keys(implode(' ', $this->keys)); + } + + // // exclude results from previous and_query + // !! trigering solr "too many boolean clauses" error + // $exclude_and_results_conditions = $this->or_query->createConditionGroup('AND'); + // foreach ($this->results['nids'] as $nid) { + // $exclude_and_results_conditions->addCondition('nid', $nid, '<>'); + // } + // $this->or_query->addConditionGroup($exclude_and_results_conditions); + + // if (preg_match_all('/[WTRPCMFGSO]\d{4}/i', implode(' ', $this->keys), $matches)) { + // // in case we search for material references like W0117 + // $ref_conditions = $this->or_query->createConditionGroup('OR'); + // foreach ($matches[0] as $key => $value) { + // $ref_conditions->addCondition('field_reference', $value); + // } + // $this->or_query->addConditionGroup($ref_conditions); + // } + + // if ($this->filters) { + // // FILTERS + // $or_filters_conditions = $this->or_query->createConditionGroup('OR'); + // foreach ($this->filters as $filter) { + // $filter = (int) $filter; + // foreach (['thesaurus_tid'] as $field) { // 'tag_tid', + // $or_filters_conditions->addCondition($field, $filter); + // } + // } + // $this->or_query->addConditionGroup($or_filters_conditions); + + // if(!$this->keys) { + // // if no keys but filters switch query to direct and add wildcard solr keys *:* + // $direct_or_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode') + // ->createInstance('direct'); + // $direct_or_parse_mode->setConjunction('OR'); + // $this->or_query->setParseMode($direct_or_parse_mode); + // // $this->or_query->keys('*:*'); + // } + + // } + + // Restrict the search to specific languages. + // $this->or_query->setLanguages([$lang]); + + // Do paging. + // $this->or_query->range($this->offset, $this->limit); + // retrieve all results + // $this->or_query->range(0, -1); + + // Add sorting. + $this->or_query->sort('search_api_relevance', 'DESC'); + + // Set one or more tags for the query. + // @see hook_search_api_query_TAG_alter() + // @see hook_search_api_results_TAG_alter() + $this->or_query->addTag('ouatt_searchapi_search_or_query'); + + $or_results = $this->or_query->execute(); + + foreach ($or_results as $result) { + $nid = $result->getField('nid')->getValues()[0]; + // !! have to remove duplicates instead of $exclude_and_results_conditions (solr too many boolean clauses) + if ( !in_array($nid, $this->results['nids']) ) { + $this->results['uuids'][] = $result->getField('uuid')->getValues()[0]; + $this->results['nids'][] = $nid; + } + } + + // todo you may like / more like this + + } + + /** + * get params from request + */ + private function parseRequest(Request $request){ + // Get the typed string from the URL, if it exists. + $this->keys = $request->query->get('keys'); + if($this->keys){ + $this->keys = mb_strtolower($this->keys); + $this->keys = Tags::explode($this->keys); + // \Drupal::logger('ouatt_searchapi')->notice($this->keys); + } + // get the exacte term id in case of autocomplete + // $this->terms = $request->query->get('terms'); + $t = $request->query->get('terms'); + // $this->terms = strlen($t) ? explode(',', $t) : null; + $this->terms = $t && strlen($t) ? json_decode($t) : null; + + // get the filters of advanced search + $f = $request->query->get('filters'); + $this->filters = $f && strlen($f) ? explode(',', $f) : null; + // $this->allparams = $request->query->all(); + // $request->attributes->get('_raw_variables')->get('filters') + // + // $this->offset = $request->query->get('offset') ?? $this->offset; + // $this->limit = $request->query->get('limit') ?? $this->limit; + } + + /** + * Handler for ajax search. + */ + public function getResults(Request $request) { + $this->parseRequest($request); + + $resp = [ + // 'range' => array( + // 'offset' => $this->offset, + // 'limit' => $this->limit + // ), + ]; + + if ($this->keys || $this->filters) { + // $lang = \Drupal::languageManager()->getCurrentLanguage()->getId(); + + $this->sapiQuery(); + + $resp['keys'] = json_encode($this->keys); + $resp['terms'] = json_encode($this->terms); + $resp['filters'] = $this->filters; + // $resp['count'] = $this->results->getResultCount(); + $resp['count'] = count($this->results['nids']); + // $resp['exactematch_count'] = $this->exactematch_count; + // $resp['infos'] = t('The search found @exactmatchcount exact match result(s) for @count total result(s) with', array( + // "@exactmatchcount" => $resp['exactematch_count'], + // "@count" => $resp['count'] + // )); + if ($this->keys) { + $resp['infos'] .= t(' keywords @keys', array( + "@keys" => implode(', ', $this->keys) + )); + } + if ($this->keys && $this->filters) { + $resp['infos'] .= " and"; + } + // if ($this->filters) { + // // get the terms names from tid + // $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + // $filters_names = []; + // foreach ($this->filters as $tid) { + // /** @var \Drupal\Core\Entity\EntityInterface $entity */ + // $entity = $storage->load($tid); + // $entity_trans = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $lang); + // $filters_names[] = $entity_trans->getName(); + // } + // $resp['infos'] .= t(' filters @filters', array( + // "@filters" => implode(', ', $filters_names) + // )); + // } + // $resp['options'] = $this->query->getOptions(); + + // $uuids = []; + // $nids = []; + // foreach ($this->results as $result) { + // $uuids[] = $result->getField('uuid')->getValues()[0]; + // $nids[] = $result->getField('nid')->getValues()[0]; + // } + $resp['nids'] = array_slice($this->results['nids'], $this->offset, $this->limit); + $resp['uuids'] = array_slice($this->results['uuids'], $this->offset, $this->limit); + } + return new JsonResponse($resp); + } + +}