27use InvalidArgumentException;
42 use \MediaWiki\Api\SearchApi;
45 private $format =
null;
60 parent::__construct( $mainModule, $moduleName );
61 $this->linkBatchFactory = $linkBatchFactory;
63 $this->searchEngineConfig = $searchEngineConfig;
64 $this->searchEngineFactory = $searchEngineFactory;
65 $this->urlUtils = $urlUtils;
74 if ( $this->format ===
null ) {
77 if ( str_ends_with( $format,
'fm' ) ) {
78 $this->format = substr( $format, 0, -2 );
81 $this->format = $format;
96 $printer = $this->
getMain()->createPrinterByName(
'xml' . $this->fm );
97 '@phan-var ApiFormatXml $printer';
99 $printer->setRootElement(
'SearchSuggestion' );
112 $this->
getMain()->setCacheMaxAge(
114 $this->
getMain()->setCacheMode(
'public' );
115 $results = $this->search( $search,
$params );
122 foreach ( $results as &$r ) {
123 if ( is_string( $r[
'extract'] ) && !$r[
'extract trimmed'] ) {
140 private function search( $search, array
$params ) {
142 $titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) );
152 $nextSpecialPageId = -1;
154 if (
$params[
'redirects'] ===
null ) {
156 $resolveRedir = $this->
getFormat() !==
'json';
158 $resolveRedir =
$params[
'redirects'] ===
'resolve';
161 if ( $resolveRedir ) {
164 $lb = $this->linkBatchFactory->newLinkBatch( $titles );
165 if ( !$lb->isEmpty() ) {
166 $db = $this->
getDB();
167 $res = $db->newSelectQueryBuilder()
168 ->select( [
'page_namespace',
'page_title',
'rd_namespace',
'rd_title' ] )
170 ->join(
'redirect',
null, [
'rd_from = page_id' ] )
172 'rd_interwiki' =>
'',
173 $lb->constructSet(
'page', $db )
175 ->caller( __METHOD__ )
177 foreach ( $res as $row ) {
178 $redirects[$row->page_namespace][$row->page_title] =
179 [ $row->rd_namespace, $row->rd_title ];
185 foreach ( $titles as $title ) {
186 $ns = $title->getNamespace();
187 $dbkey = $title->getDBkey();
189 if ( isset( $redirects[$ns][$dbkey] ) ) {
190 [ $ns, $dbkey ] = $redirects[$ns][$dbkey];
192 $title = Title::makeTitle( $ns, $dbkey );
194 if ( !isset( $seen[$ns][$dbkey] ) ) {
195 $seen[$ns][$dbkey] =
true;
196 $resultId = $title->getArticleID();
197 if ( $resultId === 0 ) {
198 $resultId = $nextSpecialPageId;
199 $nextSpecialPageId--;
201 $results[$resultId] = [
203 'redirect from' => $from,
205 'extract trimmed' =>
false,
207 'url' => (string)$this->urlUtils->expand( $title->getFullURL(),
PROTO_CURRENT ),
212 foreach ( $titles as $title ) {
213 $resultId = $title->getArticleID();
214 if ( $resultId === 0 ) {
215 $resultId = $nextSpecialPageId;
216 $nextSpecialPageId--;
218 $results[$resultId] = [
220 'redirect from' =>
null,
222 'extract trimmed' =>
false,
224 'url' => (string)$this->urlUtils->expand( $title->getFullURL(),
PROTO_CURRENT ),
242 $result->addArrayType(
null,
'array' );
243 $result->addValue(
null, 0, strval( $search ) );
247 foreach ( $results as $r ) {
248 $terms[] = $r[
'title']->getPrefixedText();
249 $descriptions[] = strval( $r[
'extract'] );
252 $result->addValue(
null, 1, $terms );
253 $result->addValue(
null, 2, $descriptions );
254 $result->addValue(
null, 3, $urls );
267 foreach ( $results as $r ) {
269 'Text' => $r[
'title']->getPrefixedText(),
272 if ( is_string( $r[
'extract'] ) && $r[
'extract'] !==
'' ) {
273 $item[
'Description'] = $r[
'extract'];
275 if ( is_array( $r[
'image'] ) && isset( $r[
'image'][
'source'] ) ) {
276 $item[
'Image'] = array_intersect_key( $r[
'image'], $imageKeys );
282 $result->addValue(
null,
'version',
'2.0' );
283 $result->addValue(
null,
'xmlns',
'http://opensearch.org/searchsuggest2' );
284 $result->addValue(
null,
'Query', strval( $search ) );
285 $result->addSubelementsList(
null,
'Query' );
286 $result->addValue(
null,
'Section', $items );
297 ParamValidator::PARAM_DEFAULT =>
false,
299 ParamValidator::PARAM_DEPRECATED =>
true,
302 ParamValidator::PARAM_TYPE => [
'return',
'resolve' ],
307 ParamValidator::PARAM_DEFAULT =>
'json',
308 ParamValidator::PARAM_TYPE => [
'json',
'jsonfm',
'xml',
'xmlfm' ],
310 'warningsaserror' =>
false,
314 $allowedParams[
'limit'][ParamValidator::PARAM_DEFAULT] = $this->
getConfig()->get(
318 return $allowedParams;
324 'profile-type' => SearchEngine::COMPLETION_PROFILE_TYPE,
325 'help-message' =>
'apihelp-query+prefixsearch-param-profile'
332 'action=opensearch&search=Te'
333 =>
'apihelp-opensearch-example-te',
338 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Opensearch';
352 static $regex =
null;
354 if ( $regex ===
null ) {
356 '([^\d])\.\s',
'\!\s',
'\?\s',
361 $endgroup = implode(
'|', $endchars );
362 $end =
"(?:$endgroup)";
363 $sentence =
".{{$length},}?$end+";
364 $regex =
"/^($sentence)/u";
368 if ( preg_match( $regex, $text,
$matches ) ) {
372 return trim( explode(
"\n", $text )[0] );
385 $searchEngineConfig = $services->getSearchEngineConfig();
386 $ns = implode(
'|', $searchEngineConfig->defaultNamespaces() );
392 case 'application/x-suggestions+json':
393 return $canonicalServer .
394 wfScript(
'api' ) .
'?action=opensearch&search={searchTerms}&namespace=' . $ns;
396 case 'application/x-suggestions+xml':
397 return $canonicalServer .
399 '?action=opensearch&format=xml&search={searchTerms}&namespace=' . $ns;
402 throw new InvalidArgumentException( __METHOD__ .
": Unknown type '$type'" );
408class_alias( ApiOpenSearch::class,
'ApiOpenSearch' );
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
array $params
The job parameters.
This is the main API class, used for both external and internal processing.
A class containing constants representing the names of configuration variables.
const OpenSearchDefaultLimit
Name constant for the OpenSearchDefaultLimit setting, for use with Config::get()
const OpenSearchDescriptionLength
Name constant for the OpenSearchDescriptionLength setting, for use with Config::get()
const CanonicalServer
Name constant for the CanonicalServer setting, for use with Config::get()
const SearchSuggestCacheExpiry
Name constant for the SearchSuggestCacheExpiry setting, for use with Config::get()
Configuration handling class for SearchEngine.
Factory class for SearchEngine.
Contain a class for special pages.