MediaWiki  master
ClassicInterwikiLookup.php
Go to the documentation of this file.
1 <?php
24 
25 use Interwiki;
26 use Language;
27 use MapCacheLRU;
30 use MWException;
31 use WANObjectCache;
32 use WikiMap;
35 
49 
53  private $localCache;
54 
58  private $contLang;
59 
63  private $objectCache;
64 
68  private $objectCacheExpiry;
69 
73  private $data;
74 
78  private $interwikiScopes;
79 
83  private $fallbackSite;
84 
88  private $thisSite = null;
89 
91  private $hookRunner;
92 
94  private $loadBalancer;
95 
110  public function __construct(
111  Language $contLang,
112  WANObjectCache $objectCache,
113  HookContainer $hookContainer,
114  ILoadBalancer $loadBalancer,
115  $objectCacheExpiry,
116  $interwikiData,
117  $interwikiScopes,
118  $fallbackSite
119  ) {
120  $this->localCache = new MapCacheLRU( 1000 );
121 
122  $this->contLang = $contLang;
123  $this->objectCache = $objectCache;
124  $this->hookRunner = new HookRunner( $hookContainer );
125  $this->loadBalancer = $loadBalancer;
126  $this->objectCacheExpiry = $objectCacheExpiry;
127  if ( is_array( $interwikiData ) ) {
128  $this->data = $interwikiData;
129  } elseif ( $interwikiData ) {
130  throw new MWException(
131  'Setting $wgInterwikiCache to a CDB path is no longer supported' );
132  }
133  $this->interwikiScopes = $interwikiScopes;
134  $this->fallbackSite = $fallbackSite;
135  }
136 
143  public function isValidInterwiki( $prefix ) {
144  $result = $this->fetch( $prefix );
145 
146  return (bool)$result;
147  }
148 
155  public function fetch( $prefix ) {
156  if ( $prefix == '' ) {
157  return null;
158  }
159 
160  $prefix = $this->contLang->lc( $prefix );
161 
162  return $this->localCache->getWithSetCallback(
163  $prefix,
164  function () use ( $prefix ) {
165  if ( $this->data !== null ) {
166  $iw = $this->fetchPregenerated( $prefix );
167  } else {
168  $iw = $this->load( $prefix );
169  if ( !$iw ) {
170  $iw = false;
171  }
172  }
173  return $iw;
174  }
175  );
176  }
177 
183  public function resetLocalCache() {
184  $this->localCache->clear();
185  }
186 
191  public function invalidateCache( $prefix ) {
192  $this->localCache->clear( $prefix );
193 
194  $key = $this->objectCache->makeKey( 'interwiki', $prefix );
195  $this->objectCache->delete( $key );
196  }
197 
206  private function fetchPregenerated( $prefix ) {
207  $value = $this->getPregeneratedEntry( $prefix );
208 
209  if ( $value ) {
210  // Split values
211  list( $local, $url ) = explode( ' ', $value, 2 );
212  return new Interwiki( $prefix, $url, '', '', (int)$local );
213  } else {
214  return false;
215  }
216  }
217 
226  private function getPregeneratedEntry( $prefix ) {
227  wfDebug( __METHOD__ . "( $prefix )" );
228 
229  $wikiId = WikiMap::getCurrentWikiId();
230 
231  // Resolve site name
232  if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
233  $this->thisSite = $this->data['__sites:' . $wikiId] ?? $this->fallbackSite;
234  }
235 
236  $value = $this->data[$wikiId . ':' . $prefix] ?? false;
237  // Site level
238  if ( $value === false && $this->interwikiScopes >= 3 ) {
239  $value = $this->data["_{$this->thisSite}:{$prefix}"] ?? false;
240  }
241  // Global Level
242  if ( $value === false && $this->interwikiScopes >= 2 ) {
243  $value = $this->data["__global:{$prefix}"] ?? false;
244  }
245 
246  return $value;
247  }
248 
255  private function load( $prefix ) {
256  $iwData = [];
257  if ( !$this->hookRunner->onInterwikiLoadPrefix( $prefix, $iwData ) ) {
258  return $this->loadFromArray( $iwData );
259  }
260 
261  if ( is_array( $iwData ) ) {
262  $iw = $this->loadFromArray( $iwData );
263  if ( $iw ) {
264  return $iw; // handled by hook
265  }
266  }
267 
268  $fname = __METHOD__;
269  $iwData = $this->objectCache->getWithSetCallback(
270  $this->objectCache->makeKey( 'interwiki', $prefix ),
271  $this->objectCacheExpiry,
272  function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix, $fname ) {
273  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
274 
275  $setOpts += Database::getCacheSetOptions( $dbr );
276 
277  $row = $dbr->selectRow(
278  'interwiki',
279  self::selectFields(),
280  [ 'iw_prefix' => $prefix ],
281  $fname
282  );
283 
284  return $row ? (array)$row : '!NONEXISTENT';
285  }
286  );
287 
288  if ( is_array( $iwData ) ) {
289  return $this->loadFromArray( $iwData ) ?: false;
290  }
291 
292  return false;
293  }
294 
301  private function loadFromArray( $mc ) {
302  if ( isset( $mc['iw_url'] ) ) {
303  $url = $mc['iw_url'];
304  $local = $mc['iw_local'] ?? 0;
305  $trans = $mc['iw_trans'] ?? 0;
306  $api = $mc['iw_api'] ?? '';
307  $wikiId = $mc['iw_wikiid'] ?? '';
308 
309  return new Interwiki( null, $url, $api, $wikiId, $local, $trans );
310  }
311 
312  return false;
313  }
314 
321  private function getAllPrefixesPregenerated( $local ) {
322  wfDebug( __METHOD__ . "()" );
323 
324  $wikiId = WikiMap::getCurrentWikiId();
325 
326  $data = [];
327  /* Resolve site name */
328  if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
329  $this->thisSite = $this->data['__sites:' . $wikiId] ?? $this->fallbackSite;
330  }
331 
332  // List of interwiki sources
333  $sources = [];
334  // Global Level
335  if ( $this->interwikiScopes >= 2 ) {
336  $sources[] = '__global';
337  }
338  // Site level
339  if ( $this->interwikiScopes >= 3 ) {
340  $sources[] = '_' . $this->thisSite;
341  }
342  $sources[] = $wikiId;
343 
344  foreach ( $sources as $source ) {
345  $list = $this->data['__list:' . $source] ?? '';
346  foreach ( explode( ' ', $list ) as $iw_prefix ) {
347  $row = $this->data["{$source}:{$iw_prefix}"] ?? null;
348  if ( !$row ) {
349  continue;
350  }
351 
352  list( $iw_local, $iw_url ) = explode( ' ', $row );
353 
354  if ( $local !== null && $local != $iw_local ) {
355  continue;
356  }
357 
358  $data[$iw_prefix] = [
359  'iw_prefix' => $iw_prefix,
360  'iw_url' => $iw_url,
361  'iw_local' => $iw_local,
362  ];
363  }
364  }
365 
366  return array_values( $data );
367  }
368 
382  public static function buildCdbHash(
383  array $allPrefixes, int $scope = 1, ?string $thisSite = null
384  ): array {
385  $result = [];
386  $wikiId = WikiMap::getCurrentWikiId();
387  $keyPrefix = ( $scope >= 2 ) ? '__global' : $wikiId;
388  if ( $scope >= 3 && $thisSite ) {
389  $result[ "__sites:$wikiId" ] = $thisSite;
390  $keyPrefix = "_$thisSite";
391  }
392  $list = [];
393  foreach ( $allPrefixes as $iwInfo ) {
394  $prefix = $iwInfo['iw_prefix'];
395  $result["$keyPrefix:$prefix"] = implode( ' ', [
396  $iwInfo['iw_local'] ?? 0, $iwInfo['iw_url']
397  ] );
398  $list[] = $prefix;
399  }
400  $result["__list:$keyPrefix"] = implode( ' ', $list );
401  $result["__list:__sites"] = $wikiId;
402  return $result;
403  }
404 
411  private function getAllPrefixesDB( $local ) {
412  $db = $this->loadBalancer->getConnectionRef( DB_REPLICA );
413 
414  $where = [];
415 
416  if ( $local !== null ) {
417  $where['iw_local'] = (int)$local;
418  }
419 
420  $res = $db->select( 'interwiki',
421  self::selectFields(),
422  $where, __METHOD__, [ 'ORDER BY' => 'iw_prefix' ]
423  );
424 
425  $retval = [];
426  foreach ( $res as $row ) {
427  $retval[] = (array)$row;
428  }
429 
430  return $retval;
431  }
432 
439  public function getAllPrefixes( $local = null ) {
440  if ( $this->data !== null ) {
441  return $this->getAllPrefixesPregenerated( $local );
442  }
443 
444  return $this->getAllPrefixesDB( $local );
445  }
446 
452  private static function selectFields() {
453  return [
454  'iw_prefix',
455  'iw_url',
456  'iw_api',
457  'iw_wikiid',
458  'iw_local',
459  'iw_trans'
460  ];
461  }
462 
463 }
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Value object for representing interwiki records.
Definition: Interwiki.php:26
Base class for language-specific code.
Definition: Language.php:53
MediaWiki exception.
Definition: MWException.php:29
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:36
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:564
InterwikiLookup implementing the "classic" interwiki storage (hardcoded up to MW 1....
fetch( $prefix)
Fetch an Interwiki object.
invalidateCache( $prefix)
Purge the in-process and object cache for an interwiki prefix.
resetLocalCache()
Resets locally cached Interwiki objects.
getAllPrefixes( $local=null)
Returns all interwiki prefixes.
isValidInterwiki( $prefix)
Check whether an interwiki prefix exists.
__construct(Language $contLang, WANObjectCache $objectCache, HookContainer $hookContainer, ILoadBalancer $loadBalancer, $objectCacheExpiry, $interwikiData, $interwikiScopes, $fallbackSite)
static buildCdbHash(array $allPrefixes, int $scope=1, ?string $thisSite=null)
Given the array returned by getAllPrefixes(), build a PHP hash which can be given to self::__construc...
Multi-datacenter aware caching interface.
Helper tools for dealing with other locally-hosted wikis.
Definition: WikiMap.php:29
static getCurrentWikiId()
Definition: WikiMap.php:303
static getCacheSetOptions(?IDatabase ... $dbs)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:3146
Service interface for looking up Interwiki records.
Create and track the database connections and transactions for a given database cluster.
$source
Copyright (C) 2018 Kunal Mehta legoktm@debian.org
const DB_REPLICA
Definition: defines.php:26