Base.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. <?php
  2. // https://www.drupal.org/docs/8/modules/search-api/developer-documentation/executing-a-search-in-code
  3. namespace Drupal\materio_sapi\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 Base 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('database');
  27. $this->results = [
  28. 'uuids' => [],
  29. 'nids' => []
  30. ];
  31. // AND QUERY
  32. $this->and_query = $this->index->query(['offset'=>0,'limit'=>10000]);
  33. // set parse mode and conjunction
  34. $parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  35. ->createInstance('terms');
  36. $parse_mode->setConjunction('AND');
  37. $this->and_query->setParseMode($parse_mode);
  38. // Set fulltext search keywords and fields.
  39. if ($this->keys) {
  40. $this->and_query->keys($this->keys);
  41. }
  42. // in case of term id provided restrict the keys to taxo fields
  43. if ($this->term) {
  44. $term_conditions = $this->and_query->createConditionGroup('OR');
  45. $term = (int) $this->term;
  46. foreach (['tag_tid', 'thesaurus_tid'] as $field) {
  47. $term_conditions->addCondition($field, $term);
  48. }
  49. $this->and_query->addConditionGroup($term_conditions);
  50. // INSTEAD TRY TO BOOST THE TAG AND THESAURUS FIELDS
  51. // foreach ($taxoSolrFieldsName as $fname) {
  52. // // $solarium_query->addParam('bf', "recip(abs(ms(NOW,{$solrField})),3.16e-11,10,0.1)");
  53. // $bfparam = "if(gt(termfreq({$fname},{$this->term}),0),^21,0)";
  54. // $this->or_query->addParam('bf', $bfparam);
  55. // }
  56. // look @ materio_sapi_search_api_solr_query_alter in materio_sapi.module
  57. // $this->or_query->setOption('termid', $this->term);
  58. }
  59. if ($this->filters) {
  60. // FILTERS
  61. $filters_conditions = $this->and_query->createConditionGroup('AND');
  62. foreach ($this->filters as $filter) {
  63. $filter = (int) $filter;
  64. foreach (['thesaurus_tid'] as $field) { // 'tag_tid',
  65. $filters_conditions->addCondition($field, $filter);
  66. }
  67. }
  68. $this->and_query->addConditionGroup($filters_conditions);
  69. if(!$this->keys) {
  70. // if no keys but filters switch query to direct and add wildcard solr keys *:*
  71. $direct_and_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  72. ->createInstance('direct');
  73. $direct_and_parse_mode->setConjunction('AND');
  74. $this->and_query->setParseMode($direct_and_parse_mode);
  75. // $this->and_query->keys('*:*');
  76. }
  77. }
  78. // else{
  79. $fulltextFields = [];
  80. // Recherche uniquement sur les champ thésaurus et tag
  81. $fulltextFields += [
  82. 'thesaurus_name_0',
  83. 'thesaurus_synonyms_0',
  84. 'thesaurus_name_1',
  85. 'thesaurus_synonyms_1',
  86. 'thesaurus_name_2',
  87. 'thesaurus_synonyms_2',
  88. 'thesaurus_name_3',
  89. 'thesaurus_synonyms_3',
  90. 'thesaurus_name_4',
  91. 'thesaurus_synonyms_4',
  92. 'thesaurus_name',
  93. 'thesaurus_synonyms',
  94. 'tag_name',
  95. 'tag_synonyms'
  96. ];
  97. if(count($fulltextFields)){
  98. $this->and_query->setFulltextFields($fulltextFields);
  99. }
  100. // }
  101. // Restrict the search to specific languages.
  102. $this->and_query->setLanguages([$lang]);
  103. // Do paging.
  104. // $this->and_query->range($this->offset, $this->limit);
  105. // retrieve all results
  106. // $this->and_query->range(0, -1);
  107. // Add sorting.
  108. $this->and_query->sort('search_api_relevance', 'DESC');
  109. // Set one or more tags for the query.
  110. // @see hook_search_api_query_TAG_alter()
  111. // @see hook_search_api_results_TAG_alter()
  112. $this->and_query->addTag('materio_sapi_search_and_query');
  113. $and_results = $this->and_query->execute();
  114. foreach ($and_results as $result) {
  115. $this->results['uuids'][] = $result->getField('uuid')->getValues()[0];
  116. $this->results['nids'][] = $result->getField('nid')->getValues()[0];
  117. }
  118. $this->exactematch_count = count($this->results['nids']);
  119. //
  120. // OR QUERY
  121. //
  122. $this->or_query = $this->index->query(['offset'=>0,'limit'=>10000]);
  123. // Change the parse mode for the search.
  124. // Les différentes possibilités sont
  125. // - « direct » => Requête directe
  126. // - « terms » => Multiple words
  127. // - « phrase » => Single phrase
  128. // - " edismax " => ???
  129. $or_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  130. ->createInstance('direct');
  131. $or_parse_mode->setConjunction('OR');
  132. $this->or_query->setParseMode($or_parse_mode);
  133. // Set fulltext search keywords and fields.
  134. if ($this->keys) {
  135. $this->or_query->keys($this->keys);
  136. }
  137. // exclude results from and_query
  138. $exclude_and_results_conditions = $this->or_query->createConditionGroup('AND');
  139. foreach ($this->results['nids'] as $nid) {
  140. $exclude_and_results_conditions->addCondition('nid', $nid, '<>');
  141. }
  142. $this->or_query->addConditionGroup($exclude_and_results_conditions);
  143. if (preg_match_all('/[WTRPCMFGSO]\d{4}/i', $this->keys, $matches)) {
  144. // in case we search for material references like W0117
  145. $ref_conditions = $this->or_query->createConditionGroup('OR');
  146. foreach ($matches[0] as $key => $value) {
  147. $ref_conditions->addCondition('field_reference', $value);
  148. }
  149. $this->or_query->addConditionGroup($ref_conditions);
  150. }
  151. if ($this->filters) {
  152. // FILTERS
  153. $or_filters_conditions = $this->or_query->createConditionGroup('OR');
  154. foreach ($this->filters as $filter) {
  155. $filter = (int) $filter;
  156. foreach (['thesaurus_tid'] as $field) { // 'tag_tid',
  157. $or_filters_conditions->addCondition($field, $filter);
  158. }
  159. }
  160. $this->or_query->addConditionGroup($or_filters_conditions);
  161. if(!$this->keys) {
  162. // if no keys but filters switch query to direct and add wildcard solr keys *:*
  163. $direct_or_parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')
  164. ->createInstance('direct');
  165. $direct_or_parse_mode->setConjunction('OR');
  166. $this->or_query->setParseMode($direct_or_parse_mode);
  167. // $this->or_query->keys('*:*');
  168. }
  169. }
  170. // Restrict the search to specific languages.
  171. $this->or_query->setLanguages([$lang]);
  172. // Do paging.
  173. // $this->or_query->range($this->offset, $this->limit);
  174. // retrieve all results
  175. // $this->or_query->range(0, -1);
  176. // Add sorting.
  177. $this->or_query->sort('search_api_relevance', 'DESC');
  178. // Set one or more tags for the query.
  179. // @see hook_search_api_query_TAG_alter()
  180. // @see hook_search_api_results_TAG_alter()
  181. $this->or_query->addTag('materio_sapi_search_or_query');
  182. $or_results = $this->or_query->execute();
  183. foreach ($or_results as $result) {
  184. $this->results['uuids'][] = $result->getField('uuid')->getValues()[0];
  185. $this->results['nids'][] = $result->getField('nid')->getValues()[0];
  186. }
  187. // todo you may like / more like this
  188. }
  189. private function defaultQuery(){
  190. $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  191. $entity_storage = \Drupal::entityTypeManager()->getStorage('node');
  192. $this->query = $entity_storage->getQuery()
  193. ->condition('type', ['materiau', 'thematique'], 'IN')
  194. ->condition('status', '1')
  195. ->condition('langcode', $lang)
  196. ->range($this->offset, $this->limit)
  197. ->accessCheck(TRUE)
  198. ->sort('created', 'DESC');
  199. // ->condition('field_example', 'test_value')
  200. $this->results = $this->query->execute();
  201. $this->count_query = $entity_storage->getQuery()
  202. ->condition('type', ['materiau', 'thematique'], 'IN')
  203. ->condition('langcode', $lang)
  204. ->accessCheck(TRUE)
  205. ->condition('status', '1')
  206. ->count();
  207. $this->count = $this->count_query->execute();
  208. }
  209. /**
  210. * get params from request
  211. */
  212. private function parseRequest(Request $request){
  213. // Get the typed string from the URL, if it exists.
  214. $this->keys = $request->query->get('keys');
  215. if($this->keys){
  216. $this->keys = mb_strtolower($this->keys);
  217. // $this->keys = Tags::explode($this->keys);
  218. \Drupal::logger('materio_sapi')->notice($this->keys);
  219. }
  220. // get the exacte term id in case of autocomplete
  221. $this->term = $request->query->get('term');
  222. // get the filters of advanced search
  223. $f = $request->query->get('filters');
  224. $this->filters = strlen($f) ? explode(',', $f) : null;
  225. // $this->allparams = $request->query->all();
  226. // $request->attributes->get('_raw_variables')->get('filters')
  227. //
  228. $this->offset = $request->query->get('offset') ?? $this->offset;
  229. $this->limit = $request->query->get('limit') ?? $this->limit;
  230. }
  231. /**
  232. * Handler for ajax search.
  233. */
  234. public function getResults(Request $request) {
  235. $this->parseRequest($request);
  236. $resp = [
  237. 'range' => array(
  238. 'offset' => $this->offset,
  239. 'limit' => $this->limit
  240. ),
  241. ];
  242. if ($this->keys || $this->filters) {
  243. $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  244. $this->sapiQuery();
  245. $resp['keys'] = $this->keys;
  246. $resp['term'] = $this->term;
  247. $resp['filters'] = $this->filters;
  248. // $resp['count'] = $this->results->getResultCount();
  249. $resp['count'] = count($this->results['nids']);
  250. $resp['exactematch_count'] = $this->exactematch_count;
  251. $resp['infos'] = t('The search found @exactmatchcount exact match result(s) for @count total result(s) with', array(
  252. "@exactmatchcount" => $resp['exactematch_count'],
  253. "@count" => $resp['count']
  254. ));
  255. if ($this->keys) {
  256. $resp['infos'] .= t(' keywords @keys', array(
  257. "@keys" => $this->keys
  258. ));
  259. }
  260. if ($this->keys && $this->filters) {
  261. $resp['infos'] .= " and";
  262. }
  263. if ($this->filters) {
  264. // get the terms names from tid
  265. $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
  266. $filters_names = [];
  267. foreach ($this->filters as $tid) {
  268. /** @var \Drupal\Core\Entity\EntityInterface $entity */
  269. $entity = $storage->load($tid);
  270. $entity_trans = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $lang);
  271. $filters_names[] = $entity_trans->getName();
  272. }
  273. $resp['infos'] .= t(' filters @filters', array(
  274. "@filters" => implode(', ', $filters_names)
  275. ));
  276. }
  277. // $resp['options'] = $this->query->getOptions();
  278. // $uuids = [];
  279. // $nids = [];
  280. // foreach ($this->results as $result) {
  281. // $uuids[] = $result->getField('uuid')->getValues()[0];
  282. // $nids[] = $result->getField('nid')->getValues()[0];
  283. // }
  284. $resp['nids'] = array_slice($this->results['nids'], $this->offset, $this->limit);
  285. $resp['uuids'] = array_slice($this->results['uuids'], $this->offset, $this->limit);
  286. } else {
  287. // no keys or terms to search for
  288. // display the default base page
  289. $this->defaultQuery();
  290. // $uuids = [];
  291. $nids = [];
  292. // Using entityTypeManager
  293. // Get a node storage object.
  294. // $node_storage = \Drupal::entityTypeManager()->getStorage('node');
  295. foreach ($this->results as $result) {
  296. // $lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
  297. // Load a single node.
  298. // $node = $node_storage->load($result);
  299. // check if has translation
  300. // i used to filter like bellow because of a graphql probleme
  301. // if ($node->hasTranslation($lang)) {
  302. $nids[] = $result;
  303. // }
  304. }
  305. // $resp['uuids'] = $uuids;
  306. $resp['nids'] = $nids;
  307. $resp['count'] = $this->count;
  308. $resp['infos'] = t('Please use the search form to search from our @count materials.', array(
  309. "@count" => $resp['count']
  310. ));
  311. }
  312. return new JsonResponse($resp);
  313. }
  314. /**
  315. * Handler for regular page search.
  316. */
  317. function pageHandler(Request $request){
  318. // \Drupal::logger('materio_sapi')->notice(print_r($request, true));
  319. $this->parseRequest($request);
  320. $resp = [
  321. "#title" => 'Base'
  322. ];
  323. if ($this->keys) {
  324. $resp['#title'] = $this->keys;
  325. // $this->sapiQuery();
  326. // $node_storage = \Drupal::entityTypeManager()->getStorage('node');
  327. // $node_view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
  328. // // $items = $this->results->getResultItems();
  329. // $nids = $this->results['nids'];
  330. // $this->items = [];
  331. // foreach ($nids as $nid) {
  332. // // \Drupal::logger('materio_sapi')->notice(print_r($nid, true));
  333. // try {
  334. // /** @var \Drupal\Core\Entity\EntityInterface $entity */
  335. // // $entity = $item->getOriginalObject()->getValue();
  336. // $entity = $node_storage->load($nid);
  337. // }
  338. // catch (SearchApiException $e) {
  339. // continue;
  340. // }
  341. // if (!$entity) {
  342. // continue;
  343. // }
  344. // // TODO: define dynamicly viewmode
  345. // $this->items[] = $node_view_builder->view($entity, 'teaser');
  346. // }
  347. // $resp['items'] = $this->items;
  348. $resp['items'] = [];
  349. }else{
  350. $resp['#markup'] = t("no keys to search for");
  351. }
  352. return $resp;
  353. }
  354. }