477 lines
16 KiB
PHP
477 lines
16 KiB
PHP
<?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;
|
||
}
|
||
}
|