TLDR; Use the Boost Query (bq) parameter
function mymodule_search_api_solr_query_alter(array &$call_args, SearchApiQueryInterface $query) {
$call_args['params']['bq'][] = 'im_field_product$flag_favorite_user:"' . $uid . '"^10';
}
There are several things at play that made this question difficult to solve.
At first I was looking at hook_search_api_query_alter()
which I didn't understand, only to find out later (thanks to LeDrupaliste) there is also a hook_search_api_solr_query_alter()
.
Then there was the challenge to sort the results based on the flag. I wanted to return all relevant content, but have favorite (flagged) products returned first. Mostly on the shop landing page (empty search query).
So I needed to sort the results, giving the results with the matching flag and user higher priority. Too bad flag's integration with Search API exposes it as a multi-value field and SOLR is not capable to sort on mutli-value fields.
So in the end the client and I came to agree that we only display flagged products on the empty query page, which was implemented like this:
function mymodule_search_api_solr_query_alter(array &$call_args, SearchApiQueryInterface $query) {
if (empty($call_args['query'])) {
$call_args['query'] = 'im_field_product$flag_favorite_user:7^20 OR *:*';
}
}
If the query is empty, query for the indexed flag field im_field_product$flag_favorite_user
where the value matches the users uid 7
in this case and boost the score 20
times to ensure high relevance. To prevent the query from hiding all other results, also include all other non-empty fields *:*
.
It is not yet exactly what I wanted, but it is very close. Now I only need to find a way to append this a regular search. Returning all items matching the keywords, but with the favorite onces on top.
$call_args['query'] = 'im_field_product$flag_favorite_user:7^20 OR ' . $call_args['query'];
Above returns the flagged items always first even if no field matches the rest of the query. Undesirable as it provides irrelevant search results.
(i get a yellow product even though I search for a red one)
$call_args['query'] = 'im_field_product$flag_favorite_user:7^20 AND ' . $call_args['query'];
Above returns only results if the product is flagged and contains the correct query fields. No other products matching the query will be returned.
(only red products that are marked favorite are returned)
$call_args['query'] = 'im_field_product$flag_favorite_user:7^20 ' . $call_args['query'];
Same as with AND
.
I will update the answer once I've found a way to do this properly.
ps. I've found the field name through the SOLR web interface, but Search API SOLR probably provides it somehow.
UPDATE
In the end I solved it like this:
if (empty($call_args['query'])) {
$call_args['query'] .= 'im_field_product$flag_favorite_user:' . $uid . '^9 OR *';
}
else {
$call_args['query'] .= ' AND (im_field_product$flag_favorite_user:' . $uid . '^9 OR *)';
}
It adds a single filter to the query if no query exists, showing all products, but giving priority to the flagged ones. If there is an existing query, we use the AND clause to again select all products and give priority to the flagged ones, but also match it against the existing query so it honors the provided search terms. It is important to match everything (*
) in the AND clause otherwise only flagged products are shown.
UPDATE2
I just discovered the Boost Query parameter.
The bq parameter specifies an additional, optional, query clause that
will be added to the user's main query to influence the score.
So the code has been updated to:
function mymodule_search_api_solr_query_alter(array &$call_args, SearchApiQueryInterface $query) {
global $user;
$uid = $user->uid;
$call_args['params']['bq'][] = 'im_field_product$flag_favorite_user:"' . $uid . '"^10';
}