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