Search.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. // https://www.drupal.org/docs/8/modules/search-api/developer-documentation/executing-a-search-in-code
  3. namespace Drupal\ouatt_searchapi\Controller;
  4. use Drupal\Core\Controller\ControllerBase;
  5. use Symfony\Component\HttpFoundation\JsonResponse;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Drupal\Component\Utility\Tags;
  8. use Drupal\Component\Utility\Unicode;
  9. use Drupal\search_api\Entity\Index;
  10. // https://drupal.stackexchange.com/questions/225008/programatically-use-search-api
  11. /**
  12. * Defines a route controller for materio sapi search (regular and ajax).
  13. */
  14. class Search extends ControllerBase {
  15. // private $limit = 15;
  16. // private $offset = 0;
  17. private function sapiQuery(){
  18. // https://www.drupal.org/docs/8/modules/search-api/developer-documentation/executing-a-search-in-code
  19. // https://www.hashbangcode.com/article/drupal-8-date-search-boosting-search-api-and-solr-search
  20. // https://kgaut.net/blog/2018/drupal-8-search-api-effectuer-une-requete-dans-le-code.html
  21. // Otherwise, if you use Solr, you can also set the parse mode to “Direct query”
  22. // and use something like 'tm_name:alex AND tm_surname:john' as the keywords (field names may vary).
  23. // That will also ensure scoring works for the keywords (the other way just adds filters, which don’t affect scoring).
  24. // https://www.drupal.org/project/search_api/issues/3049097
  25. // $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  26. $this->index = Index::load('enquetes');
  27. $this->results = [
  28. 'uuids' => [],
  29. 'nids' => []
  30. ];
  31. // // ,---.| ,---.
  32. // // |---'|---.,---.,---.,---.,---. | |. .,---.,---., .
  33. // // | | || ,---|`---.|---' | || ||---'| | |
  34. // // ` ` '` `---^`---'`---' `---\`---'`---'` `---|
  35. // // `---'
  36. // // (to match exact materials names (like "wood-skin"))
  37. // $this->phrase_query = $this->index->query(['offset'=>0,'limit'=>10000]);
  38. // // set parse mode and conjunction
  39. // $parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  40. // ->createInstance('phrase');
  41. // $parse_mode->setConjunction('AND');
  42. // $this->phrase_query->setParseMode($parse_mode);
  43. // // Set fulltext search keywords and fields.
  44. // if ($this->keys) {
  45. // $this->phrase_query->keys(implode(' ', $this->keys));
  46. // }
  47. // $this->phrase_query->setFulltextFields(['title']);
  48. // // Restrict the search to specific languages.
  49. // $this->phrase_query->setLanguages([$lang]);
  50. // // Do paging.
  51. // // $this->and_query->range($this->offset, $this->limit);
  52. // // retrieve all results
  53. // // $this->and_query->range(0, -1);
  54. // // Add sorting.
  55. // $this->phrase_query->sort('search_api_relevance', 'DESC');
  56. // // Set one or more tags for the query.
  57. // // @see hook_search_api_query_TAG_alter()
  58. // // @see hook_search_api_results_TAG_alter()
  59. // $this->phrase_query->addTag('ouatt_searchapi_search_phrase_query');
  60. // $phrase_results = $this->phrase_query->execute();
  61. // foreach ($phrase_results as $result) {
  62. // $this->results['uuids'][] = $result->getField('uuid')->getValues()[0];
  63. // $this->results['nids'][] = $result->getField('nid')->getValues()[0];
  64. // }
  65. // // ,---. | ,---.
  66. // // |---|,---.,---| | |. .,---.,---., .
  67. // // | || || | | || ||---'| | |
  68. // // ` '` '`---' `---\`---'`---'` `---|
  69. // // `---'
  70. // $this->and_query = $this->index->query(['offset'=>0,'limit'=>10000]);
  71. // // set parse mode and conjunction
  72. // $parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  73. // ->createInstance('terms');
  74. // $parse_mode->setConjunction('AND');
  75. // $this->and_query->setParseMode($parse_mode);
  76. // // Set fulltext search keywords and fields.
  77. // if ($this->keys) {
  78. // $this->and_query->keys(implode(' ', $this->keys));
  79. // }
  80. // // in case of term id provided restrict the keys to taxo fields
  81. // if ($this->terms && is_array($this->terms) && count($this->terms)) {
  82. // $term_conditions = $this->and_query->createConditionGroup('OR');
  83. // // $term = (int) $this->term;
  84. // foreach ($this->terms as $term) {
  85. // $tid = $term->value;
  86. // foreach (['tag_tid', 'thesaurus_tid'] as $field) {
  87. // $term_conditions->addCondition($field, (int) $tid);
  88. // }
  89. // }
  90. // $this->and_query->addConditionGroup($term_conditions);
  91. // // INSTEAD TRY TO BOOST THE TAG AND THESAURUS FIELDS
  92. // // foreach ($taxoSolrFieldsName as $fname) {
  93. // // // $solarium_query->addParam('bf', "recip(abs(ms(NOW,{$solrField})),3.16e-11,10,0.1)");
  94. // // $bfparam = "if(gt(termfreq({$fname},{$this->term}),0),^21,0)";
  95. // // $this->or_query->addParam('bf', $bfparam);
  96. // // }
  97. // // look @ ouatt_searchapi_search_api_solr_query_alter in ouatt_searchapi.module
  98. // // $this->or_query->setOption('termid', $this->term);
  99. // }
  100. // if ($this->filters) {
  101. // // FILTERS
  102. // $filters_conditions = $this->and_query->createConditionGroup('AND');
  103. // foreach ($this->filters as $filter) {
  104. // $filter = (int) $filter;
  105. // foreach (['thesaurus_tid'] as $field) { // 'tag_tid',
  106. // $filters_conditions->addCondition($field, $filter);
  107. // }
  108. // }
  109. // $this->and_query->addConditionGroup($filters_conditions);
  110. // if(!$this->keys) {
  111. // // if no keys but filters switch query to direct and add wildcard solr keys *:*
  112. // $direct_and_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  113. // ->createInstance('direct');
  114. // $direct_and_parse_mode->setConjunction('AND');
  115. // $this->and_query->setParseMode($direct_and_parse_mode);
  116. // // $this->and_query->keys('*:*');
  117. // }
  118. // }
  119. // // else{
  120. // $fulltextFields = [];
  121. // // Recherche uniquement sur les champ thésaurus et tag
  122. // $fulltextFields += [
  123. // 'thesaurus_name_0',
  124. // 'thesaurus_synonyms_0',
  125. // 'thesaurus_name_1',
  126. // 'thesaurus_synonyms_1',
  127. // 'thesaurus_name_2',
  128. // 'thesaurus_synonyms_2',
  129. // 'thesaurus_name_3',
  130. // 'thesaurus_synonyms_3',
  131. // 'thesaurus_name_4',
  132. // 'thesaurus_synonyms_4',
  133. // 'thesaurus_name',
  134. // 'thesaurus_synonyms',
  135. // 'tag_name',
  136. // 'tag_synonyms'
  137. // ];
  138. // if(count($fulltextFields)){
  139. // $this->and_query->setFulltextFields($fulltextFields);
  140. // }
  141. // // }
  142. // // Restrict the search to specific languages.
  143. // $this->and_query->setLanguages([$lang]);
  144. // // Do paging.
  145. // // $this->and_query->range($this->offset, $this->limit);
  146. // // retrieve all results
  147. // // $this->and_query->range(0, -1);
  148. // // Add sorting.
  149. // $this->and_query->sort('search_api_relevance', 'DESC');
  150. // // Set one or more tags for the query.
  151. // // @see hook_search_api_query_TAG_alter()
  152. // // @see hook_search_api_results_TAG_alter()
  153. // $this->and_query->addTag('ouatt_searchapi_search_and_query');
  154. // $and_results = $this->and_query->execute();
  155. // foreach ($and_results as $result) {
  156. // // !! have to remove duplicates from phrase query
  157. // $nid = $result->getField('nid')->getValues()[0];
  158. // if ( !in_array($nid, $this->results['nids']) ) {
  159. // $this->results['uuids'][] = $result->getField('uuid')->getValues()[0];
  160. // $this->results['nids'][] = $result->getField('nid')->getValues()[0];
  161. // }
  162. // }
  163. // $this->exactematch_count = count($this->results['nids']);
  164. // ,---. ,---.
  165. // | |,---. | |. .,---.,---., .
  166. // | || | || ||---'| | |
  167. // `---'` `---\`---'`---'` `---|
  168. // `---'
  169. $this->or_query = $this->index->query(['offset'=>0,'limit'=>10000]);
  170. // Change the parse mode for the search.
  171. // Les différentes possibilités sont
  172. // - « direct » => Requête directe
  173. // - « terms » => Multiple words
  174. // - « phrase » => Single phrase
  175. // - " edismax " => ???
  176. $or_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  177. ->createInstance('direct');
  178. $or_parse_mode->setConjunction('OR');
  179. $this->or_query->setParseMode($or_parse_mode);
  180. // Set fulltext search keywords and fields.
  181. if ($this->keys) {
  182. $this->or_query->keys(implode(' ', $this->keys));
  183. }
  184. // // exclude results from previous and_query
  185. // !! trigering solr "too many boolean clauses" error
  186. // $exclude_and_results_conditions = $this->or_query->createConditionGroup('AND');
  187. // foreach ($this->results['nids'] as $nid) {
  188. // $exclude_and_results_conditions->addCondition('nid', $nid, '<>');
  189. // }
  190. // $this->or_query->addConditionGroup($exclude_and_results_conditions);
  191. // if (preg_match_all('/[WTRPCMFGSO]\d{4}/i', implode(' ', $this->keys), $matches)) {
  192. // // in case we search for material references like W0117
  193. // $ref_conditions = $this->or_query->createConditionGroup('OR');
  194. // foreach ($matches[0] as $key => $value) {
  195. // $ref_conditions->addCondition('field_reference', $value);
  196. // }
  197. // $this->or_query->addConditionGroup($ref_conditions);
  198. // }
  199. // if ($this->filters) {
  200. // // FILTERS
  201. // $or_filters_conditions = $this->or_query->createConditionGroup('OR');
  202. // foreach ($this->filters as $filter) {
  203. // $filter = (int) $filter;
  204. // foreach (['thesaurus_tid'] as $field) { // 'tag_tid',
  205. // $or_filters_conditions->addCondition($field, $filter);
  206. // }
  207. // }
  208. // $this->or_query->addConditionGroup($or_filters_conditions);
  209. // if(!$this->keys) {
  210. // // if no keys but filters switch query to direct and add wildcard solr keys *:*
  211. // $direct_or_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  212. // ->createInstance('direct');
  213. // $direct_or_parse_mode->setConjunction('OR');
  214. // $this->or_query->setParseMode($direct_or_parse_mode);
  215. // // $this->or_query->keys('*:*');
  216. // }
  217. // }
  218. // Restrict the search to specific languages.
  219. // $this->or_query->setLanguages([$lang]);
  220. // Do paging.
  221. // $this->or_query->range($this->offset, $this->limit);
  222. // retrieve all results
  223. // $this->or_query->range(0, -1);
  224. // Add sorting.
  225. $this->or_query->sort('search_api_relevance', 'DESC');
  226. // Set one or more tags for the query.
  227. // @see hook_search_api_query_TAG_alter()
  228. // @see hook_search_api_results_TAG_alter()
  229. $this->or_query->addTag('ouatt_searchapi_search_or_query');
  230. $or_results = $this->or_query->execute();
  231. foreach ($or_results as $result) {
  232. $nid = $result->getField('nid')->getValues()[0];
  233. // !! have to remove duplicates instead of $exclude_and_results_conditions (solr too many boolean clauses)
  234. if ( !in_array($nid, $this->results['nids']) ) {
  235. $this->results['uuids'][] = $result->getField('uuid')->getValues()[0];
  236. $this->results['nids'][] = $nid;
  237. }
  238. }
  239. // todo you may like / more like this
  240. }
  241. /**
  242. * get params from request
  243. */
  244. private function parseRequest(Request $request){
  245. // Get the typed string from the URL, if it exists.
  246. $this->keys = $request->query->get('keys');
  247. if($this->keys){
  248. $this->keys = mb_strtolower($this->keys);
  249. $this->keys = Tags::explode($this->keys);
  250. // \Drupal::logger('ouatt_searchapi')->notice($this->keys);
  251. }
  252. // get the exacte term id in case of autocomplete
  253. // $this->terms = $request->query->get('terms');
  254. $t = $request->query->get('terms');
  255. // $this->terms = strlen($t) ? explode(',', $t) : null;
  256. $this->terms = $t && strlen($t) ? json_decode($t) : null;
  257. // get the filters of advanced search
  258. $f = $request->query->get('filters');
  259. $this->filters = $f && strlen($f) ? explode(',', $f) : null;
  260. // $this->allparams = $request->query->all();
  261. // $request->attributes->get('_raw_variables')->get('filters')
  262. //
  263. // $this->offset = $request->query->get('offset') ?? $this->offset;
  264. // $this->limit = $request->query->get('limit') ?? $this->limit;
  265. }
  266. /**
  267. * Handler for ajax search.
  268. */
  269. public function getResults(Request $request) {
  270. $this->parseRequest($request);
  271. $resp = [
  272. // 'range' => array(
  273. // 'offset' => $this->offset,
  274. // 'limit' => $this->limit
  275. // ),
  276. ];
  277. if ($this->keys || $this->filters) {
  278. // $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  279. $this->sapiQuery();
  280. $resp['keys'] = json_encode($this->keys);
  281. $resp['terms'] = json_encode($this->terms);
  282. $resp['filters'] = $this->filters;
  283. // $resp['count'] = $this->results->getResultCount();
  284. $resp['count'] = count($this->results['nids']);
  285. // $resp['exactematch_count'] = $this->exactematch_count;
  286. // $resp['infos'] = t('The search found @exactmatchcount exact match result(s) for @count total result(s) with', array(
  287. // "@exactmatchcount" => $resp['exactematch_count'],
  288. // "@count" => $resp['count']
  289. // ));
  290. if ($this->keys) {
  291. $resp['infos'] .= t(' keywords @keys', array(
  292. "@keys" => implode(', ', $this->keys)
  293. ));
  294. }
  295. if ($this->keys && $this->filters) {
  296. $resp['infos'] .= " and";
  297. }
  298. // if ($this->filters) {
  299. // // get the terms names from tid
  300. // $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
  301. // $filters_names = [];
  302. // foreach ($this->filters as $tid) {
  303. // /** @var \Drupal\Core\Entity\EntityInterface $entity */
  304. // $entity = $storage->load($tid);
  305. // $entity_trans = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $lang);
  306. // $filters_names[] = $entity_trans->getName();
  307. // }
  308. // $resp['infos'] .= t(' filters @filters', array(
  309. // "@filters" => implode(', ', $filters_names)
  310. // ));
  311. // }
  312. // $resp['options'] = $this->query->getOptions();
  313. // $uuids = [];
  314. // $nids = [];
  315. // foreach ($this->results as $result) {
  316. // $uuids[] = $result->getField('uuid')->getValues()[0];
  317. // $nids[] = $result->getField('nid')->getValues()[0];
  318. // }
  319. $resp['nids'] = array_slice($this->results['nids'], $this->offset, $this->limit);
  320. $resp['uuids'] = array_slice($this->results['uuids'], $this->offset, $this->limit);
  321. }
  322. return new JsonResponse($resp);
  323. }
  324. }