MediaWiki  master
TitleMatcher.php
Go to the documentation of this file.
1 <?php
2 namespace MediaWiki\Search;
3 
6 use Language;
17 use RepoGroup;
19 
23 class TitleMatcher {
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 }
255 
259 class_alias( TitleMatcher::class, 'SearchNearMatcher' );
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
$matches
Base class for language-specific code.
Definition: Language.php:61
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...
Definition: HookRunner.php:568
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.
Definition: SpecialPage.php:66
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:76
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
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.