39 private $format =
null;
62 parent::__construct( $mainModule, $moduleName );
63 $this->linkBatchFactory = $linkBatchFactory;
65 $this->searchEngineConfig = $searchEngineConfig;
66 $this->searchEngineFactory = $searchEngineFactory;
67 $this->urlUtils = $urlUtils;
76 if ( $this->format ===
null ) {
79 if ( str_ends_with( $format,
'fm' ) ) {
80 $this->format = substr( $format, 0, -2 );
83 $this->format = $format;
98 $printer = $this->
getMain()->createPrinterByName(
'xml' . $this->fm );
99 '@phan-var ApiFormatXml $printer';
101 $printer->setRootElement(
'SearchSuggestion' );
114 $this->
getMain()->setCacheMaxAge(
115 $this->
getConfig()->
get( MainConfigNames::SearchSuggestCacheExpiry ) );
116 $this->
getMain()->setCacheMode(
'public' );
117 $results = $this->search( $search,
$params );
123 $length = $this->
getConfig()->get( MainConfigNames::OpenSearchDescriptionLength );
124 foreach ( $results as &$r ) {
125 if ( is_string( $r[
'extract'] ) && !$r[
'extract trimmed'] ) {
126 $r[
'extract'] = self::trimExtract( $r[
'extract'], $length );
142 private function search( $search, array
$params ) {
144 $titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) );
154 $nextSpecialPageId = -1;
156 if (
$params[
'redirects'] ===
null ) {
158 $resolveRedir = $this->
getFormat() !==
'json';
160 $resolveRedir =
$params[
'redirects'] ===
'resolve';
163 if ( $resolveRedir ) {
166 $lb = $this->linkBatchFactory->newLinkBatch( $titles );
167 if ( !$lb->isEmpty() ) {
168 $db = $this->
getDB();
169 $res = $db->newSelectQueryBuilder()
170 ->select( [
'page_namespace',
'page_title',
'rd_namespace',
'rd_title' ] )
172 ->join(
'redirect',
null, [
'rd_from = page_id' ] )
174 'rd_interwiki' =>
'',
175 $lb->constructSet(
'page', $db )
177 ->caller( __METHOD__ )
179 foreach ( $res as $row ) {
180 $redirects[$row->page_namespace][$row->page_title] =
181 [ $row->rd_namespace, $row->rd_title ];
187 foreach ( $titles as $title ) {
188 $ns = $title->getNamespace();
189 $dbkey = $title->getDBkey();
191 if ( isset( $redirects[$ns][$dbkey] ) ) {
192 [ $ns, $dbkey ] = $redirects[$ns][$dbkey];
194 $title = Title::makeTitle( $ns, $dbkey );
196 if ( !isset( $seen[$ns][$dbkey] ) ) {
197 $seen[$ns][$dbkey] =
true;
198 $resultId = $title->getArticleID();
199 if ( $resultId === 0 ) {
200 $resultId = $nextSpecialPageId;
201 $nextSpecialPageId--;
203 $results[$resultId] = [
205 'redirect from' => $from,
207 'extract trimmed' =>
false,
209 'url' => (string)$this->urlUtils->expand( $title->getFullURL(),
PROTO_CURRENT ),
214 foreach ( $titles as $title ) {
215 $resultId = $title->getArticleID();
216 if ( $resultId === 0 ) {
217 $resultId = $nextSpecialPageId;
218 $nextSpecialPageId--;
220 $results[$resultId] = [
222 'redirect from' =>
null,
224 'extract trimmed' =>
false,
226 'url' => (string)$this->urlUtils->expand( $title->getFullURL(),
PROTO_CURRENT ),
244 $result->addArrayType(
null,
'array' );
245 $result->addValue(
null, 0, strval( $search ) );
249 foreach ( $results as $r ) {
250 $terms[] = $r[
'title']->getPrefixedText();
251 $descriptions[] = strval( $r[
'extract'] );
254 $result->addValue(
null, 1, $terms );
255 $result->addValue(
null, 2, $descriptions );
256 $result->addValue(
null, 3, $urls );
269 foreach ( $results as $r ) {
271 'Text' => $r[
'title']->getPrefixedText(),
274 if ( is_string( $r[
'extract'] ) && $r[
'extract'] !==
'' ) {
275 $item[
'Description'] = $r[
'extract'];
277 if ( is_array( $r[
'image'] ) && isset( $r[
'image'][
'source'] ) ) {
278 $item[
'Image'] = array_intersect_key( $r[
'image'], $imageKeys );
280 ApiResult::setSubelementsList( $item, array_keys( $item ) );
283 ApiResult::setIndexedTagName( $items,
'Item' );
284 $result->addValue(
null,
'version',
'2.0' );
285 $result->addValue(
null,
'xmlns',
'http://opensearch.org/searchsuggest2' );
286 $result->addValue(
null,
'Query', strval( $search ) );
287 $result->addSubelementsList(
null,
'Query' );
288 $result->addValue(
null,
'Section', $items );
299 ParamValidator::PARAM_DEFAULT =>
false,
301 ParamValidator::PARAM_DEPRECATED =>
true,
304 ParamValidator::PARAM_TYPE => [
'return',
'resolve' ],
309 ParamValidator::PARAM_DEFAULT =>
'json',
310 ParamValidator::PARAM_TYPE => [
'json',
'jsonfm',
'xml',
'xmlfm' ],
312 'warningsaserror' =>
false,
316 $allowedParams[
'limit'][ParamValidator::PARAM_DEFAULT] = $this->
getConfig()->get(
317 MainConfigNames::OpenSearchDefaultLimit
320 return $allowedParams;
326 'profile-type' => SearchEngine::COMPLETION_PROFILE_TYPE,
327 'help-message' =>
'apihelp-query+prefixsearch-param-profile'
334 'action=opensearch&search=Te'
335 =>
'apihelp-opensearch-example-te',
340 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Opensearch';
354 static $regex =
null;
356 if ( $regex ===
null ) {
358 '([^\d])\.\s',
'\!\s',
'\?\s',
363 $endgroup = implode(
'|', $endchars );
364 $end =
"(?:$endgroup)";
365 $sentence =
".{{$length},}?$end+";
366 $regex =
"/^($sentence)/u";
370 if ( preg_match( $regex, $text,
$matches ) ) {
374 return trim( explode(
"\n", $text )[0] );
385 $services = MediaWikiServices::getInstance();
386 $canonicalServer = $services->getMainConfig()->get( MainConfigNames::CanonicalServer );
387 $searchEngineConfig = $services->getSearchEngineConfig();
388 $ns = implode(
'|', $searchEngineConfig->defaultNamespaces() );
394 case 'application/x-suggestions+json':
395 return $canonicalServer .
396 wfScript(
'api' ) .
'?action=opensearch&search={searchTerms}&namespace=' . $ns;
398 case 'application/x-suggestions+xml':
399 return $canonicalServer .
401 '?action=opensearch&format=xml&search={searchTerms}&namespace=' . $ns;
404 throw new InvalidArgumentException( __METHOD__ .
": Unknown type '$type'" );
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
buildSearchEngine(array $params=null)
Build the search engine to use.
buildCommonApiParams( $isScrollable=true)
The set of api parameters that are shared between api calls that call the SearchEngine.
array $params
The job parameters.
This abstract class implements many basic API functions, and is the base of all API classes.
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
getMain()
Get the main module.
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
getHookRunner()
Get an ApiHookRunner for running core API hooks.
This is the main API class, used for both external and internal processing.
static trimExtract( $text, $length)
Trim an extract to a sensible length.
getHelpUrls()
Return links to more detailed help pages about the module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
__construct(ApiMain $mainModule, $moduleName, LinkBatchFactory $linkBatchFactory, SearchEngineConfig $searchEngineConfig, SearchEngineFactory $searchEngineFactory, UrlUtils $urlUtils)
getCustomPrinter()
If the module may only be used with a certain format module, it should override this method to return...
getExamplesMessages()
Returns usage examples for this module.
populateResult( $search, &$results)
getFormat()
Get the output format.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
static getOpenSearchTemplate( $type)
Fetch the template for a type.
A class containing constants representing the names of configuration variables.
Configuration handling class for SearchEngine.
Factory class for SearchEngine.
trait SearchApi
Traits for API components that use a SearchEngine.