123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- <?php
- // https://www.drupal.org/docs/8/modules/search-api/developer-documentation/executing-a-search-in-code
- namespace Drupal\materio_sapi\Controller;
- use Drupal\Core\Controller\ControllerBase;
- use Symfony\Component\HttpFoundation\JsonResponse;
- use Symfony\Component\HttpFoundation\Request;
- use Drupal\Component\Utility\Tags;
- use Drupal\Component\Utility\Unicode;
- use Drupal\search_api\Entity\Index;
- // https://drupal.stackexchange.com/questions/225008/programatically-use-search-api
- /**
- * Defines a route controller for materio sapi search (regular and ajax).
- */
- class Base extends ControllerBase {
- private $limit = 15;
- private $offset = 0;
- private function sapiQuery(){
- // https://www.drupal.org/docs/8/modules/search-api/developer-documentation/executing-a-search-in-code
- // https://www.hashbangcode.com/article/drupal-8-date-search-boosting-search-api-and-solr-search
- // https://kgaut.net/blog/2018/drupal-8-search-api-effectuer-une-requete-dans-le-code.html
- // Otherwise, if you use Solr, you can also set the parse mode to “Direct query”
- // and use something like 'tm_name:alex AND tm_surname:john' as the keywords (field names may vary).
- // That will also ensure scoring works for the keywords (the other way just adds filters, which don’t affect scoring).
- // https://www.drupal.org/project/search_api/issues/3049097
- $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
- $this->index = Index::load('database');
- $this->results = [
- 'uuids' => [],
- 'nids' => []
- ];
- // PHRASE QUERY
- // (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('materio_sapi_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];
- }
- // AND QUERY
- $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 && 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 @ materio_sapi_search_api_solr_query_alter in materio_sapi.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('materio_sapi_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']);
- //
- // OR QUERY
- //
- $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('materio_sapi_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
- }
- private function defaultQuery(){
- $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
- $entity_storage = \Drupal::entityTypeManager()->getStorage('node');
- $this->query = $entity_storage->getQuery()
- ->condition('type', ['materiau', 'thematique'], 'IN')
- ->condition('status', '1')
- ->condition('langcode', $lang)
- ->range($this->offset, $this->limit)
- ->accessCheck(TRUE)
- ->sort('created', 'DESC');
- // ->condition('field_example', 'test_value')
- $this->results = $this->query->execute();
- $this->count_query = $entity_storage->getQuery()
- ->condition('type', ['materiau', 'thematique'], 'IN')
- ->condition('langcode', $lang)
- ->accessCheck(TRUE)
- ->condition('status', '1')
- ->count();
- $this->count = $this->count_query->execute();
- }
- /**
- * 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('materio_sapi')->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 = strlen($t) ? json_decode($t) : null;
- // get the filters of advanced search
- $f = $request->query->get('filters');
- $this->filters = 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);
- } else {
- // no keys or terms to search for
- // display the default base page
- $this->defaultQuery();
- // $uuids = [];
- $nids = [];
- // Using entityTypeManager
- // Get a node storage object.
- // $node_storage = \Drupal::entityTypeManager()->getStorage('node');
- foreach ($this->results as $result) {
- // $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
- // Load a single node.
- // $node = $node_storage->load($result);
- // check if has translation
- // i used to filter like bellow because of a graphql probleme
- // if ($node->hasTranslation($lang)) {
- $nids[] = $result;
- // }
- }
- // $resp['uuids'] = $uuids;
- $resp['nids'] = $nids;
- $resp['count'] = $this->count;
- $resp['infos'] = t('Please use the search form to search from our @count materials.', array(
- "@count" => $resp['count']
- ));
- }
- return new JsonResponse($resp);
- }
- /**
- * Handler for regular page search.
- */
- function pageHandler(Request $request){
- // \Drupal::logger('materio_sapi')->notice(print_r($request, true));
- $this->parseRequest($request);
- $resp = [
- "#title" => 'Base'
- ];
- if ($this->keys) {
- $resp['#title'] = implode(', ', $this->keys);
- // $this->sapiQuery();
- // $node_storage = \Drupal::entityTypeManager()->getStorage('node');
- // $node_view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
- // // $items = $this->results->getResultItems();
- // $nids = $this->results['nids'];
- // $this->items = [];
- // foreach ($nids as $nid) {
- // // \Drupal::logger('materio_sapi')->notice(print_r($nid, true));
- // try {
- // /** @var \Drupal\Core\Entity\EntityInterface $entity */
- // // $entity = $item->getOriginalObject()->getValue();
- // $entity = $node_storage->load($nid);
- // }
- // catch (SearchApiException $e) {
- // continue;
- // }
- // if (!$entity) {
- // continue;
- // }
- // // TODO: define dynamicly viewmode
- // $this->items[] = $node_view_builder->view($entity, 'teaser');
- // }
- // $resp['items'] = $this->items;
- $resp['items'] = [];
- }else{
- $resp['#markup'] = t("no keys to search for");
- }
- return $resp;
- }
- }
|