MediaWiki  master
SearchHandler.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Rest\Handler;
4 
5 use Config;
6 use InvalidArgumentException;
14 use RequestContext;
15 use SearchEngine;
18 use SearchResult;
20 use Status;
21 use User;
25 
29 class SearchHandler extends Handler {
30 
33 
36 
39 
41  private $user;
42 
46  public const FULLTEXT_MODE = 'fulltext';
47 
51  public const COMPLETION_MODE = 'completion';
52 
57 
61  private $mode = null;
62 
64  private const LIMIT = 50;
65 
67  private const MAX_LIMIT = 100;
68 
70  private const OFFSET = 0;
71 
79 
86  public function __construct(
91  ) {
92  $this->permissionManager = $permissionManager;
93  $this->searchEngineFactory = $searchEngineFactory;
94  $this->searchEngineConfig = $searchEngineConfig;
95 
96  // @todo Inject this, when there is a good way to do that, see T239753
97  $this->user = RequestContext::getMain()->getUser();
98 
99  // @todo Avoid injecting the entire config, see T246377
100  $this->completionCacheExpiry = $config->get( 'SearchSuggestCacheExpiry' );
101  }
102 
103  protected function postInitSetup() {
104  $this->mode = $this->getConfig()['mode'] ?? self::FULLTEXT_MODE;
105 
106  if ( !in_array( $this->mode, self::SUPPORTED_MODES ) ) {
107  throw new InvalidArgumentException(
108  "Unsupported search mode `{$this->mode}` configured. Supported modes: " .
109  implode( ', ', self::SUPPORTED_MODES )
110  );
111  }
112  }
113 
117  private function createSearchEngine() {
118  $limit = $this->getValidatedParams()['limit'];
119 
120  $searchEngine = $this->searchEngineFactory->create();
121  $searchEngine->setNamespaces( $this->searchEngineConfig->defaultNamespaces() );
122  $searchEngine->setLimitOffset( $limit, self::OFFSET );
123  return $searchEngine;
124  }
125 
126  public function needsWriteAccess() {
127  return false;
128  }
129 
136  private function getSearchResultsOrThrow( $results ) {
137  if ( $results ) {
138  if ( $results instanceof Status ) {
139  $status = $results;
140  if ( !$status->isOK() ) {
141  list( $error ) = $status->splitByErrorType();
142  if ( $error->getErrors() ) { // Only throw for errors, suppress warnings (for now)
143  $errorMessages = $error->getMessage();
144  throw new LocalizedHttpException(
145  new MessageValue( "rest-search-error", [ $errorMessages->getKey() ] )
146  );
147  }
148  }
149  $statusValue = $status->getValue();
150  if ( $statusValue instanceof ISearchResultSet ) {
151  return $statusValue->extractResults();
152  }
153  } else {
154  return $results->extractResults();
155  }
156  }
157  return [];
158  }
159 
167  private function doSearch( $searchEngine ) {
168  $query = $this->getValidatedParams()['q'];
169 
170  if ( $this->mode == self::COMPLETION_MODE ) {
171  $completionSearch = $searchEngine->completionSearchWithVariants( $query );
172  return $this->buildPageInfosFromSuggestions( $completionSearch->getSuggestions() );
173  } else {
174  $titleSearch = $searchEngine->searchTitle( $query );
175  $textSearch = $searchEngine->searchText( $query );
176 
177  $titleSearchResults = $this->getSearchResultsOrThrow( $titleSearch );
178  $textSearchResults = $this->getSearchResultsOrThrow( $textSearch );
179 
180  $mergedResults = array_merge( $titleSearchResults, $textSearchResults );
181  return $this->buildPageInfosFromSearchResults( $mergedResults );
182  }
183  }
184 
194  private function buildPageInfosFromSuggestions( array $suggestions ): array {
195  $pageInfos = [];
196 
197  foreach ( $suggestions as $sugg ) {
198  $title = $sugg->getSuggestedTitle();
199  if ( $title && $title->exists() ) {
200  $pageID = $title->getArticleID();
201  if ( !isset( $pageInfos[$pageID] ) &&
202  $this->permissionManager->quickUserCan( 'read', $this->user, $title )
203  ) {
204  $pageInfos[ $pageID ] = [ $title, $sugg, null ];
205  }
206  }
207  }
208  return $pageInfos;
209  }
210 
220  private function buildPageInfosFromSearchResults( array $searchResults ): array {
221  $pageInfos = [];
222 
223  foreach ( $searchResults as $result ) {
224  if ( !$result->isBrokenTitle() && !$result->isMissingRevision() ) {
225  $title = $result->getTitle();
226  $pageID = $title->getArticleID();
227  if ( !isset( $pageInfos[$pageID] ) &&
228  $this->permissionManager->quickUserCan( 'read', $this->user, $title )
229  ) {
230  $pageInfos[$pageID] = [ $title, null, $result ];
231  }
232  }
233  }
234  return $pageInfos;
235  }
236 
244  private function buildResultFromPageInfos( array $pageInfos ): array {
245  return array_map( function ( $pageInfo ) {
246  list( $title, $sugg, $result ) = $pageInfo;
247  return [
248  'id' => $title->getArticleID(),
249  'key' => $title->getPrefixedDBkey(),
250  'title' => $title->getPrefixedText(),
251  'excerpt' => ( $sugg ? $sugg->getText() : $result->getTextSnippet() ) ?: null,
252  ];
253  },
254  $pageInfos );
255  }
256 
264  private function serializeThumbnail( ?SearchResultThumbnail $thumbnail ) : ?array {
265  if ( $thumbnail == null ) {
266  return null;
267  }
268 
269  return [
270  'mimetype' => $thumbnail->getMimeType(),
271  'size' => $thumbnail->getSize(),
272  'width' => $thumbnail->getWidth(),
273  'height' => $thumbnail->getHeight(),
274  'duration' => $thumbnail->getDuration(),
275  'url' => $thumbnail->getUrl(),
276  ];
277  }
278 
289  private function buildDescriptionsFromPageIdentities( array $pageIdentities ) {
290  $descriptions = array_fill_keys( array_keys( $pageIdentities ), null );
291 
292  $this->getHookRunner()->onSearchResultProvideDescription( $pageIdentities, $descriptions );
293 
294  return array_map( function ( $description ) {
295  return [ 'description' => $description ];
296  }, $descriptions );
297  }
298 
310  private function buildThumbnailsFromPageIdentities( array $pageIdentities ) {
311  $thumbnails = array_fill_keys( array_keys( $pageIdentities ), null );
312 
313  $this->getHookRunner()->onSearchResultProvideThumbnail( $pageIdentities, $thumbnails );
314 
315  return array_map( function ( $thumbnail ) {
316  return [ 'thumbnail' => $this->serializeThumbnail( $thumbnail ) ];
317  }, $thumbnails );
318  }
319 
324  public function execute() {
325  $searchEngine = $this->createSearchEngine();
326  $pageInfos = $this->doSearch( $searchEngine );
327  $pageIdentities = array_map( function ( $pageInfo ) {
328  list( $title ) = $pageInfo;
330  $title->getArticleID(),
331  $title->getNamespace(),
332  $title->getDBkey()
333  );
334  }, $pageInfos );
335 
336  $result = array_map( "array_merge",
337  $this->buildResultFromPageInfos( $pageInfos ),
338  $this->buildDescriptionsFromPageIdentities( $pageIdentities ),
339  $this->buildThumbnailsFromPageIdentities( $pageIdentities )
340  );
341 
342  $response = $this->getResponseFactory()->createJson( [ 'pages' => $result ] );
343 
344  if ( $this->mode === self::COMPLETION_MODE && $this->completionCacheExpiry ) {
345  // Type-ahead completion matches should be cached by the client and
346  // in the CDN, especially for short prefixes.
347  // See also $wgSearchSuggestCacheExpiry and ApiOpenSearch
348  $response->setHeader( 'Cache-Control', 'public, max-age=' . $this->completionCacheExpiry );
349  }
350 
351  return $response;
352  }
353 
354  public function getParamSettings() {
355  return [
356  'q' => [
357  self::PARAM_SOURCE => 'query',
358  ParamValidator::PARAM_TYPE => 'string',
359  ParamValidator::PARAM_REQUIRED => true,
360  ],
361  'limit' => [
362  self::PARAM_SOURCE => 'query',
363  ParamValidator::PARAM_TYPE => 'integer',
364  ParamValidator::PARAM_REQUIRED => false,
365  ParamValidator::PARAM_DEFAULT => self::LIMIT,
366  IntegerDef::PARAM_MIN => 1,
367  IntegerDef::PARAM_MAX => self::MAX_LIMIT,
368  ],
369  ];
370  }
371 }
MediaWiki\Rest\Handler
Definition: AbstractContributionHandler.php:3
MediaWiki\Rest\Handler\getResponseFactory
getResponseFactory()
Get the ResponseFactory which can be used to generate Response objects.
Definition: Handler.php:151
MediaWiki\Rest\Handler\SearchHandler\buildPageInfosFromSearchResults
buildPageInfosFromSearchResults(array $searchResults)
Remove duplicate pages and turn search results into array with information needed for further process...
Definition: SearchHandler.php:220
MediaWiki\Rest\Handler\SearchHandler\buildDescriptionsFromPageIdentities
buildDescriptionsFromPageIdentities(array $pageIdentities)
Turn page info into serializable array with description field for the page.
Definition: SearchHandler.php:289
MediaWiki\Rest\Handler\SearchHandler\execute
execute()
Definition: SearchHandler.php:324
MediaWiki\Rest\Handler\SearchHandler\buildResultFromPageInfos
buildResultFromPageInfos(array $pageInfos)
Turn array of page info into serializable array with common information about the page.
Definition: SearchHandler.php:244
MediaWiki\Rest\Handler\SearchHandler\buildPageInfosFromSuggestions
buildPageInfosFromSuggestions(array $suggestions)
Remove duplicate pages and turn suggestions into array with information needed for further processing...
Definition: SearchHandler.php:194
MediaWiki\Rest\Handler\SearchHandler\LIMIT
const LIMIT
Limit results to 50 pages per default.
Definition: SearchHandler.php:64
MediaWiki\Rest\Handler\SearchHandler\getParamSettings
getParamSettings()
Fetch ParamValidator settings for parameters.
Definition: SearchHandler.php:354
SearchEngineFactory
Factory class for SearchEngine.
Definition: SearchEngineFactory.php:13
Wikimedia\Message\MessageValue
Value object representing a message for i18n.
Definition: MessageValue.php:16
MediaWiki\Rest\Handler
Base class for REST route handlers.
Definition: Handler.php:16
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
MediaWiki\Rest\Handler\SearchHandler\buildThumbnailsFromPageIdentities
buildThumbnailsFromPageIdentities(array $pageIdentities)
Turn page info into serializable array with thumbnail information for the page.
Definition: SearchHandler.php:310
Config
Interface for configuration instances.
Definition: Config.php:30
MediaWiki\Search\Entity\SearchResultThumbnail\getDuration
getDuration()
Duration of the representation in seconds or null if not applicable.
Definition: SearchResultThumbnail.php:125
MediaWiki\Rest\Handler\SearchHandler\serializeThumbnail
serializeThumbnail(?SearchResultThumbnail $thumbnail)
Converts SearchResultThumbnail object into serializable array.
Definition: SearchHandler.php:264
SearchResult
NOTE: this class is being refactored into an abstract base class.
Definition: SearchResult.php:38
MediaWiki\Search\Entity\SearchResultThumbnail\getHeight
getHeight()
Height of the representation in pixels or null if not applicable.
Definition: SearchResultThumbnail.php:101
MediaWiki\Rest\Handler\SearchHandler\OFFSET
const OFFSET
Default to first page.
Definition: SearchHandler.php:70
MediaWiki\Rest\Handler\SearchHandler\$mode
string $mode
Definition: SearchHandler.php:61
MediaWiki\Rest\Handler\SearchHandler\FULLTEXT_MODE
const FULLTEXT_MODE
Search page body and titles.
Definition: SearchHandler.php:46
MediaWiki\Rest\Handler\SearchHandler\needsWriteAccess
needsWriteAccess()
Indicates whether this route requires write access.
Definition: SearchHandler.php:126
MediaWiki\Search\Entity\SearchResultThumbnail\getWidth
getWidth()
Width of the representation in pixels or null if not applicable.
Definition: SearchResultThumbnail.php:93
SearchSuggestion
Search suggestion.
Definition: SearchSuggestion.php:25
MediaWiki\Rest\Response
Definition: Response.php:8
MediaWiki\Rest\Handler\SearchHandler\SUPPORTED_MODES
const SUPPORTED_MODES
Supported modes.
Definition: SearchHandler.php:56
$title
$title
Definition: testCompression.php:38
RequestContext
Group all the pieces relevant to the context of a request into one instance @newable.
Definition: RequestContext.php:38
MediaWiki\Rest\Handler\getValidatedParams
getValidatedParams()
Fetch the validated parameters.
Definition: Handler.php:257
MediaWiki\Rest\Handler\SearchHandler\__construct
__construct(Config $config, PermissionManager $permissionManager, SearchEngineFactory $searchEngineFactory, SearchEngineConfig $searchEngineConfig)
Definition: SearchHandler.php:86
ISearchResultSet
A set of SearchEngine results.
Definition: ISearchResultSet.php:12
MediaWiki\Rest\Handler\SearchHandler\COMPLETION_MODE
const COMPLETION_MODE
Search title completion matches.
Definition: SearchHandler.php:51
MediaWiki\Rest\Handler\getConfig
getConfig()
Get the configuration array for the current route.
Definition: Handler.php:140
MediaWiki\Rest\Handler\getHookRunner
getHookRunner()
Get a HookRunner for running core hooks.
Definition: Handler.php:291
MediaWiki\Rest\Handler\$config
array $config
Definition: Handler.php:31
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:49
MediaWiki\Rest\Handler\SearchHandler\MAX_LIMIT
const MAX_LIMIT
Hard limit results to 100 pages.
Definition: SearchHandler.php:67
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:453
SearchEngine
Contain a class for special pages Stable to extend.
Definition: SearchEngine.php:37
MediaWiki\Search\Entity\SearchResultThumbnail
Class that stores information about thumbnail, e.
Definition: SearchResultThumbnail.php:9
MediaWiki\Rest\Entity\SearchResultPageIdentityValue
Lightweight value class representing a page identity.
Definition: SearchResultPageIdentityValue.php:11
MediaWiki\Rest\Handler\SearchHandler\$user
User $user
Definition: SearchHandler.php:41
MediaWiki\Rest\Handler\SearchHandler\createSearchEngine
createSearchEngine()
Definition: SearchHandler.php:117
MediaWiki\Search\Entity\SearchResultThumbnail\getSize
getSize()
Size of the representation in bytes or null if not applicable.
Definition: SearchResultThumbnail.php:117
Wikimedia\ParamValidator\TypeDef\IntegerDef
Type definition for integer types.
Definition: IntegerDef.php:23
MediaWiki\Rest\Handler\SearchHandler
Handler class for Core REST API endpoint that handles basic search.
Definition: SearchHandler.php:29
MediaWiki\Rest\Handler\SearchHandler\postInitSetup
postInitSetup()
The handler can override this to do any necessary setup after init() is called to inject the dependen...
Definition: SearchHandler.php:103
SearchEngineConfig
Configuration handling class for SearchEngine.
Definition: SearchEngineConfig.php:12
MediaWiki\Rest\Handler\SearchHandler\doSearch
doSearch( $searchEngine)
Execute search and return info about pages for further processing.
Definition: SearchHandler.php:167
MediaWiki\Search\Entity\SearchResultThumbnail\getMimeType
getMimeType()
Internet mime type for the representation, like "image/png" or "audio/mp3".
Definition: SearchResultThumbnail.php:109
MediaWiki\Rest\Handler\SearchHandler\getSearchResultsOrThrow
getSearchResultsOrThrow( $results)
Get SearchResults when results are either SearchResultSet or Status objects.
Definition: SearchHandler.php:136
MediaWiki\Rest\Handler\SearchHandler\$searchEngineConfig
SearchEngineConfig $searchEngineConfig
Definition: SearchHandler.php:38
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:55
MediaWiki\Rest\Handler\SearchHandler\$searchEngineFactory
SearchEngineFactory $searchEngineFactory
Definition: SearchHandler.php:35
MediaWiki\Rest\Handler\SearchHandler\$completionCacheExpiry
int null $completionCacheExpiry
Expiry time for use as max-age value in the cache-control header of completion search responses.
Definition: SearchHandler.php:78
Wikimedia\ParamValidator\ParamValidator
Service for formatting and validating API parameters.
Definition: ParamValidator.php:42
MediaWiki\Search\Entity\SearchResultThumbnail\getUrl
getUrl()
Full URL to the contents of the file.
Definition: SearchResultThumbnail.php:85
MediaWiki\Rest\LocalizedHttpException
@newable
Definition: LocalizedHttpException.php:10
MediaWiki\Rest\Handler\SearchHandler\$permissionManager
PermissionManager $permissionManager
Definition: SearchHandler.php:32