MediaWiki  master
ClassicInterwikiLookup.php
Go to the documentation of this file.
1 <?php
22 
23 use Interwiki;
24 use Language;
25 use MapCacheLRU;
31 use WANObjectCache;
33 
52  public const CONSTRUCTOR_OPTIONS = [
57  'wikiId',
58  ];
59 
60  private ServiceOptions $options;
62  private $contLang;
64  private $wanCache;
66  private $hookRunner;
68  private $dbProvider;
69 
71  private $instances;
79  private $interwikiScopes;
81  private $data;
83  private $wikiId;
85  private $thisSite = null;
86 
94  public function __construct(
95  ServiceOptions $options,
96  Language $contLang,
97  WANObjectCache $wanCache,
98  HookContainer $hookContainer,
99  IConnectionProvider $dbProvider
100  ) {
101  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
102  $this->options = $options;
103 
104  $this->contLang = $contLang;
105  $this->wanCache = $wanCache;
106  $this->hookRunner = new HookRunner( $hookContainer );
107  $this->dbProvider = $dbProvider;
108 
109  $this->instances = new MapCacheLRU( 1000 );
110  $this->interwikiScopes = $options->get( MainConfigNames::InterwikiScopes );
111 
112  $interwikiData = $options->get( MainConfigNames::InterwikiCache );
113  $this->data = is_array( $interwikiData ) ? $interwikiData : null;
114  $this->wikiId = $options->get( 'wikiId' );
115  }
116 
122  public function isValidInterwiki( $prefix ) {
123  $iw = $this->fetch( $prefix );
124  return (bool)$iw;
125  }
126 
132  public function fetch( $prefix ) {
133  if ( $prefix === null || $prefix === '' ) {
134  return null;
135  }
136 
137  $prefix = $this->contLang->lc( $prefix );
138 
139  return $this->instances->getWithSetCallback(
140  $prefix,
141  function () use ( $prefix ) {
142  return $this->load( $prefix );
143  }
144  );
145  }
146 
156  public function invalidateCache( $prefix ) {
157  $this->instances->clear( $prefix );
158 
159  $key = $this->wanCache->makeKey( 'interwiki', $prefix );
160  $this->wanCache->delete( $key );
161  }
162 
169  private function getPregenValue( string $prefix ) {
170  // Lazily resolve site name
171  if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
172  $this->thisSite = $this->data['__sites:' . $this->wikiId]
173  ?? $this->options->get( MainConfigNames::InterwikiFallbackSite );
174  }
175 
176  $value = $this->data[$this->wikiId . ':' . $prefix] ?? false;
177  // Site level
178  if ( $value === false && $this->interwikiScopes >= 3 ) {
179  $value = $this->data["_{$this->thisSite}:{$prefix}"] ?? false;
180  }
181  // Global level
182  if ( $value === false && $this->interwikiScopes >= 2 ) {
183  $value = $this->data["__global:{$prefix}"] ?? false;
184  }
185 
186  return $value;
187  }
188 
198  private function load( $prefix ) {
199  if ( $this->data !== null ) {
200  $value = $this->getPregenValue( $prefix );
201  return $value ? $this->makeFromPregen( $prefix, $value ) : false;
202  }
203 
204  $iwData = [];
205  $abort = !$this->hookRunner->onInterwikiLoadPrefix( $prefix, $iwData );
206  if ( isset( $iwData['iw_url'] ) ) {
207  // Hook provided data
208  return $this->makeFromRow( $iwData );
209  }
210  if ( $abort ) {
211  // Hook indicated no other source may be considered
212  return false;
213  }
214 
215  $fname = __METHOD__;
216  $iwData = $this->wanCache->getWithSetCallback(
217  $this->wanCache->makeKey( 'interwiki', $prefix ),
218  $this->options->get( MainConfigNames::InterwikiExpiry ),
219  function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix, $fname ) {
220  $dbr = $this->dbProvider->getReplicaDatabase();
221  $row = $dbr->newSelectQueryBuilder()
222  ->select( self::selectFields() )
223  ->from( 'interwiki' )
224  ->where( [ 'iw_prefix' => $prefix ] )
225  ->caller( $fname )->fetchRow();
226 
227  return $row ? (array)$row : '!NONEXISTENT';
228  }
229  );
230 
231  // Handle non-existent case
232  return is_array( $iwData ) ? $this->makeFromRow( $iwData ) : false;
233  }
234 
239  private function makeFromRow( array $row ) {
240  $url = $row['iw_url'];
241  $local = $row['iw_local'] ?? 0;
242  $trans = $row['iw_trans'] ?? 0;
243  $api = $row['iw_api'] ?? '';
244  $wikiId = $row['iw_wikiid'] ?? '';
245 
246  return new Interwiki( null, $url, $api, $wikiId, $local, $trans );
247  }
248 
254  private function makeFromPregen( string $prefix, string $value ) {
255  // Split values
256  [ $local, $url ] = explode( ' ', $value, 2 );
257  return new Interwiki( $prefix, $url, '', '', (int)$local );
258  }
259 
266  private function getAllPrefixesPregenerated( $local ) {
267  // Lazily resolve site name
268  if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
269  $this->thisSite = $this->data['__sites:' . $this->wikiId]
270  ?? $this->options->get( MainConfigNames::InterwikiFallbackSite );
271  }
272 
273  // List of interwiki sources
274  $sources = [];
275  // Global level
276  if ( $this->interwikiScopes >= 2 ) {
277  $sources[] = '__global';
278  }
279  // Site level
280  if ( $this->interwikiScopes >= 3 ) {
281  $sources[] = '_' . $this->thisSite;
282  }
283  $sources[] = $this->wikiId;
284 
285  $data = [];
286  foreach ( $sources as $source ) {
287  $list = $this->data['__list:' . $source] ?? '';
288  foreach ( explode( ' ', $list ) as $iw_prefix ) {
289  $row = $this->data["{$source}:{$iw_prefix}"] ?? null;
290  if ( !$row ) {
291  continue;
292  }
293 
294  [ $iw_local, $iw_url ] = explode( ' ', $row );
295 
296  if ( $local !== null && $local != $iw_local ) {
297  continue;
298  }
299 
300  $data[$iw_prefix] = [
301  'iw_prefix' => $iw_prefix,
302  'iw_url' => $iw_url,
303  'iw_local' => $iw_local,
304  ];
305  }
306  }
307 
308  return array_values( $data );
309  }
310 
326  public static function buildCdbHash(
327  array $allPrefixes, int $scope = 1, ?string $thisSite = null
328  ): array {
329  $result = [];
330  $wikiId = WikiMap::getCurrentWikiId();
331  $keyPrefix = ( $scope >= 2 ) ? '__global' : $wikiId;
332  if ( $scope >= 3 && $thisSite ) {
333  $result[ "__sites:$wikiId" ] = $thisSite;
334  $keyPrefix = "_$thisSite";
335  }
336  $list = [];
337  foreach ( $allPrefixes as $iwInfo ) {
338  $prefix = $iwInfo['iw_prefix'];
339  $result["$keyPrefix:$prefix"] = implode( ' ', [
340  $iwInfo['iw_local'] ?? 0, $iwInfo['iw_url']
341  ] );
342  $list[] = $prefix;
343  }
344  $result["__list:$keyPrefix"] = implode( ' ', $list );
345  $result["__list:__sites"] = $wikiId;
346  return $result;
347  }
348 
355  private function getAllPrefixesDB( $local ) {
356  $where = [];
357  if ( $local !== null ) {
358  $where['iw_local'] = (int)$local;
359  }
360 
361  $dbr = $this->dbProvider->getReplicaDatabase();
362  $res = $dbr->newSelectQueryBuilder()
363  ->select( self::selectFields() )
364  ->from( 'interwiki' )
365  ->where( $where )
366  ->orderBy( 'iw_prefix' )
367  ->caller( __METHOD__ )->fetchResultSet();
368 
369  $retval = [];
370  foreach ( $res as $row ) {
371  $retval[] = (array)$row;
372  }
373  return $retval;
374  }
375 
382  public function getAllPrefixes( $local = null ) {
383  if ( $this->data !== null ) {
384  return $this->getAllPrefixesPregenerated( $local );
385  } else {
386  return $this->getAllPrefixesDB( $local );
387  }
388  }
389 
395  private static function selectFields() {
396  return [
397  'iw_prefix',
398  'iw_url',
399  'iw_api',
400  'iw_wikiid',
401  'iw_local',
402  'iw_trans'
403  ];
404  }
405 
406 }
An interwiki record value object.
Definition: Interwiki.php:27
Base class for language-specific code.
Definition: Language.php:61
Store key-value entries in a size-limited in-memory LRU cache.
Definition: MapCacheLRU.php:34
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
InterwikiLookup backed by the interwiki database table or $wgInterwikiCache.
fetch( $prefix)
Get the Interwiki object for a given prefix.Interwiki prefix Interwiki|null|false Null for invalid,...
invalidateCache( $prefix)
Purge the instance cache and memcached for an interwiki prefix.
getAllPrefixes( $local=null)
Fetch all interwiki data.
isValidInterwiki( $prefix)
Check whether an interwiki prefix exists.Interwiki prefix bool Whether it exists
static buildCdbHash(array $allPrefixes, int $scope=1, ?string $thisSite=null)
Build an array in the format accepted by $wgInterwikiCache.
__construct(ServiceOptions $options, Language $contLang, WANObjectCache $wanCache, HookContainer $hookContainer, IConnectionProvider $dbProvider)
A class containing constants representing the names of configuration variables.
const InterwikiCache
Name constant for the InterwikiCache setting, for use with Config::get()
const InterwikiScopes
Name constant for the InterwikiScopes setting, for use with Config::get()
const InterwikiFallbackSite
Name constant for the InterwikiFallbackSite setting, for use with Config::get()
const InterwikiExpiry
Name constant for the InterwikiExpiry setting, for use with Config::get()
Tools for dealing with other locally-hosted wikis.
Definition: WikiMap.php:31
Multi-datacenter aware caching interface.
Service interface for looking up Interwiki records.
Provide primary and replica IDatabase connections.
$source