MediaWiki master
TitleMatcher.php
Go to the documentation of this file.
1<?php
2namespace MediaWiki\Search;
3
19
27 public const CONSTRUCTOR_OPTIONS = [
29 ];
30
31 private ServiceOptions $options;
32 private Language $language;
33 private ILanguageConverter $languageConverter;
34 private HookRunner $hookRunner;
35 private WikiPageFactory $wikiPageFactory;
36 private UserNameUtils $userNameUtils;
37 private RepoGroup $repoGroup;
38 private TitleFactory $titleFactory;
39
40 public function __construct(
41 ServiceOptions $options,
42 Language $contentLanguage,
43 LanguageConverterFactory $languageConverterFactory,
44 HookContainer $hookContainer,
45 WikiPageFactory $wikiPageFactory,
46 UserNameUtils $userNameUtils,
47 RepoGroup $repoGroup,
48 TitleFactory $titleFactory
49 ) {
50 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
51 $this->options = $options;
52
53 $this->language = $contentLanguage;
54 $this->languageConverter = $languageConverterFactory->getLanguageConverter( $contentLanguage );
55 $this->hookRunner = new HookRunner( $hookContainer );
56 $this->wikiPageFactory = $wikiPageFactory;
57 $this->userNameUtils = $userNameUtils;
58 $this->repoGroup = $repoGroup;
59 $this->titleFactory = $titleFactory;
60 }
61
69 public function getNearMatch( $searchterm ) {
70 $title = $this->getNearMatchInternal( $searchterm );
71
72 $this->hookRunner->onSearchGetNearMatchComplete( $searchterm, $title );
73 return $title;
74 }
75
83 public function getNearMatchResultSet( $searchterm ) {
84 return new SearchNearMatchResultSet( $this->getNearMatch( $searchterm ) );
85 }
86
92 protected function getNearMatchInternal( $searchterm ) {
93 $allSearchTerms = [ $searchterm ];
94
95 if ( $this->languageConverter->hasVariants() ) {
96 $allSearchTerms = array_unique( array_merge(
97 $allSearchTerms,
98 $this->languageConverter->autoConvertToAllVariants( $searchterm )
99 ) );
100 }
101
102 $titleResult = null;
103 if ( !$this->hookRunner->onSearchGetNearMatchBefore( $allSearchTerms, $titleResult ) ) {
104 return $titleResult;
105 }
106
107 // Most of our handling here deals with finding a valid title for the search term,
108 // but almost anything starting with '#' is "valid" and points to Main_Page#searchterm.
109 // Rather than doing something completely wrong, do nothing.
110 if ( $searchterm === '' || $searchterm[0] === '#' ) {
111 return null;
112 }
113
114 foreach ( $allSearchTerms as $term ) {
115 # Exact match? No need to look further.
116 $title = $this->titleFactory->newFromText( $term );
117 if ( $title === null ) {
118 return null;
119 }
120
121 # Try files if searching in the Media: namespace
122 if ( $title->getNamespace() === NS_MEDIA ) {
123 $title = Title::makeTitle( NS_FILE, $title->getText() );
124 }
125
126 if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
127 return $title;
128 }
129
130 # See if it still otherwise has content is some sensible sense
131 if ( $title->canExist() ) {
132 $page = $this->wikiPageFactory->newFromTitle( $title );
133 if ( $page->hasViewableContent() ) {
134 return $title;
135 }
136 }
137
138 if ( !$this->hookRunner->onSearchAfterNoDirectMatch( $term, $title ) ) {
139 return $title;
140 }
141
142 # Now try all lower case (i.e. first letter capitalized)
143 $title = $this->titleFactory->newFromText( $this->language->lc( $term ) );
144 if ( $title && $title->exists() ) {
145 return $title;
146 }
147
148 # Now try capitalized string
149 $title = $this->titleFactory->newFromText( $this->language->ucwords( $term ) );
150 if ( $title && $title->exists() ) {
151 return $title;
152 }
153
154 # Now try all upper case
155 $title = $this->titleFactory->newFromText( $this->language->uc( $term ) );
156 if ( $title && $title->exists() ) {
157 return $title;
158 }
159
160 # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
161 $title = $this->titleFactory->newFromText( $this->language->ucwordbreaks( $term ) );
162 if ( $title && $title->exists() ) {
163 return $title;
164 }
165
166 // Give hooks a chance at better match variants
167 $title = null;
168 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
169 if ( !$this->hookRunner->onSearchGetNearMatch( $term, $title ) ) {
170 return $title;
171 }
172 }
173
174 $title = $this->titleFactory->newFromTextThrow( $searchterm );
175
176 # Entering an IP address goes to the contributions page
177 if ( $this->options->get( MainConfigNames::EnableSearchContributorsByIP ) ) {
178 if ( ( $title->getNamespace() === NS_USER && $this->userNameUtils->isIP( $title->getText() ) )
179 || $this->userNameUtils->isIP( trim( $searchterm ) ) ) {
180 return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
181 }
182 }
183
184 # Entering a user goes to the user page whether it's there or not
185 if ( $title->getNamespace() === NS_USER ) {
186 return $title;
187 }
188
189 # Go to images that exist even if there's no local page.
190 # There may have been a funny upload, or it may be on a shared
191 # file repository such as Wikimedia Commons.
192 if ( $title->getNamespace() === NS_FILE ) {
193 $image = $this->repoGroup->findFile( $title );
194 if ( $image ) {
195 return $title;
196 }
197 }
198
199 # MediaWiki namespace? Page may be "implied" if not customized.
200 # Just return it, with caps forced as the message system likes it.
201 if ( $title->getNamespace() === NS_MEDIAWIKI ) {
202 return Title::makeTitle( NS_MEDIAWIKI, $this->language->ucfirst( $title->getText() ) );
203 }
204
205 # Quoted term? Try without the quotes...
206 $matches = [];
207 if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
208 return $this->getNearMatch( $matches[1] );
209 }
210
211 return null;
212 }
213}
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,...
Prioritized list of file repositories.
Definition RepoGroup.php:38
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Base class for language-specific code.
Definition Language.php:81
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.
A ISearchResultSet wrapper for TitleMatcher.
A set of SearchEngine results.
The shared interface for all language converters.