MediaWiki master
TitleMatcher.php
Go to the documentation of this file.
1<?php
2namespace MediaWiki\Search;
3
6use Language;
17use RepoGroup;
19
28 public const CONSTRUCTOR_OPTIONS = [
30 ];
31
35 private $options;
36
41 private $language;
42
47 private $languageConverter;
48
52 private $hookRunner;
53
57 private $wikiPageFactory;
58
62 private $userNameUtils;
63
67 private $repoGroup;
68
69 private TitleFactory $titleFactory;
70
81 public function __construct(
82 ServiceOptions $options,
83 Language $contentLanguage,
84 LanguageConverterFactory $languageConverterFactory,
85 HookContainer $hookContainer,
86 WikiPageFactory $wikiPageFactory,
87 UserNameUtils $userNameUtils,
88 RepoGroup $repoGroup,
89 TitleFactory $titleFactory
90 ) {
91 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
92 $this->options = $options;
93
94 $this->language = $contentLanguage;
95 $this->languageConverter = $languageConverterFactory->getLanguageConverter( $contentLanguage );
96 $this->hookRunner = new HookRunner( $hookContainer );
97 $this->wikiPageFactory = $wikiPageFactory;
98 $this->userNameUtils = $userNameUtils;
99 $this->repoGroup = $repoGroup;
100 $this->titleFactory = $titleFactory;
101 }
102
110 public function getNearMatch( $searchterm ) {
111 $title = $this->getNearMatchInternal( $searchterm );
112
113 $this->hookRunner->onSearchGetNearMatchComplete( $searchterm, $title );
114 return $title;
115 }
116
124 public function getNearMatchResultSet( $searchterm ) {
125 return new SearchNearMatchResultSet( $this->getNearMatch( $searchterm ) );
126 }
127
133 protected function getNearMatchInternal( $searchterm ) {
134 $allSearchTerms = [ $searchterm ];
135
136 if ( $this->languageConverter->hasVariants() ) {
137 $allSearchTerms = array_unique( array_merge(
138 $allSearchTerms,
139 $this->languageConverter->autoConvertToAllVariants( $searchterm )
140 ) );
141 }
142
143 $titleResult = null;
144 if ( !$this->hookRunner->onSearchGetNearMatchBefore( $allSearchTerms, $titleResult ) ) {
145 return $titleResult;
146 }
147
148 // Most of our handling here deals with finding a valid title for the search term,
149 // but almost anything starting with '#' is "valid" and points to Main_Page#searchterm.
150 // Rather than doing something completely wrong, do nothing.
151 if ( $searchterm === '' || $searchterm[0] === '#' ) {
152 return null;
153 }
154
155 foreach ( $allSearchTerms as $term ) {
156 # Exact match? No need to look further.
157 $title = $this->titleFactory->newFromText( $term );
158 if ( $title === null ) {
159 return null;
160 }
161
162 # Try files if searching in the Media: namespace
163 if ( $title->getNamespace() === NS_MEDIA ) {
164 $title = Title::makeTitle( NS_FILE, $title->getText() );
165 }
166
167 if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
168 return $title;
169 }
170
171 # See if it still otherwise has content is some sensible sense
172 if ( $title->canExist() ) {
173 $page = $this->wikiPageFactory->newFromTitle( $title );
174 if ( $page->hasViewableContent() ) {
175 return $title;
176 }
177 }
178
179 if ( !$this->hookRunner->onSearchAfterNoDirectMatch( $term, $title ) ) {
180 return $title;
181 }
182
183 # Now try all lower case (i.e. first letter capitalized)
184 $title = $this->titleFactory->newFromText( $this->language->lc( $term ) );
185 if ( $title && $title->exists() ) {
186 return $title;
187 }
188
189 # Now try capitalized string
190 $title = $this->titleFactory->newFromText( $this->language->ucwords( $term ) );
191 if ( $title && $title->exists() ) {
192 return $title;
193 }
194
195 # Now try all upper case
196 $title = $this->titleFactory->newFromText( $this->language->uc( $term ) );
197 if ( $title && $title->exists() ) {
198 return $title;
199 }
200
201 # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
202 $title = $this->titleFactory->newFromText( $this->language->ucwordbreaks( $term ) );
203 if ( $title && $title->exists() ) {
204 return $title;
205 }
206
207 // Give hooks a chance at better match variants
208 $title = null;
209 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
210 if ( !$this->hookRunner->onSearchGetNearMatch( $term, $title ) ) {
211 return $title;
212 }
213 }
214
215 $title = $this->titleFactory->newFromTextThrow( $searchterm );
216
217 # Entering an IP address goes to the contributions page
218 if ( $this->options->get( MainConfigNames::EnableSearchContributorsByIP ) ) {
219 if ( ( $title->getNamespace() === NS_USER && $this->userNameUtils->isIP( $title->getText() ) )
220 || $this->userNameUtils->isIP( trim( $searchterm ) ) ) {
221 return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
222 }
223 }
224
225 # Entering a user goes to the user page whether it's there or not
226 if ( $title->getNamespace() === NS_USER ) {
227 return $title;
228 }
229
230 # Go to images that exist even if there's no local page.
231 # There may have been a funny upload, or it may be on a shared
232 # file repository such as Wikimedia Commons.
233 if ( $title->getNamespace() === NS_FILE ) {
234 $image = $this->repoGroup->findFile( $title );
235 if ( $image ) {
236 return $title;
237 }
238 }
239
240 # MediaWiki namespace? Page may be "implied" if not customized.
241 # Just return it, with caps forced as the message system likes it.
242 if ( $title->getNamespace() === NS_MEDIAWIKI ) {
243 return Title::makeTitle( NS_MEDIAWIKI, $this->language->ucfirst( $title->getText() ) );
244 }
245
246 # Quoted term? Try without the quotes...
247 $matches = [];
248 if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
249 return $this->getNearMatch( $matches[1] );
250 }
251
252 return null;
253 }
254}
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
const NS_MEDIAWIKI
Definition Defines.php:72
const NS_MEDIA
Definition Defines.php:52
Base class for language-specific code.
Definition Language.php:63
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
An interface for creating language converters.
getLanguageConverter( $language=null)
Provide a LanguageConverter for given language.
A class containing constants representing the names of configuration variables.
const EnableSearchContributorsByIP
Name constant for the EnableSearchContributorsByIP setting, for use with Config::get()
Service for creating WikiPage objects.
Service implementation of near match title search.
getNearMatch( $searchterm)
If an exact title match can be found, or a very slightly close match, return the title.
__construct(ServiceOptions $options, Language $contentLanguage, LanguageConverterFactory $languageConverterFactory, HookContainer $hookContainer, WikiPageFactory $wikiPageFactory, UserNameUtils $userNameUtils, RepoGroup $repoGroup, TitleFactory $titleFactory)
getNearMatchResultSet( $searchterm)
Do a near match (see SearchEngine::getNearMatch) and wrap it into a ISearchResultSet.
getNearMatchInternal( $searchterm)
Really find the title match.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Creates Title objects.
Represents a title within MediaWiki.
Definition Title.php:78
UserNameUtils service.
Prioritized list of file repositories.
Definition RepoGroup.php:30
A ISearchResultSet wrapper for TitleMatcher.
The shared interface for all language converters.
A set of SearchEngine results.