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 $loadBalancer;
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  ILoadBalancer $loadBalancer
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->loadBalancer = $loadBalancer;
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->loadBalancer->getConnectionRef( DB_REPLICA );
221  $row = $dbr->selectRow(
222  'interwiki',
223  self::selectFields(),
224  [ 'iw_prefix' => $prefix ],
225  $fname
226  );
227 
228  return $row ? (array)$row : '!NONEXISTENT';
229  }
230  );
231 
232  // Handle non-existent case
233  return is_array( $iwData ) ? $this->makeFromRow( $iwData ) : false;
234  }
235 
240  private function makeFromRow( array $row ) {
241  $url = $row['iw_url'];
242  $local = $row['iw_local'] ?? 0;
243  $trans = $row['iw_trans'] ?? 0;
244  $api = $row['iw_api'] ?? '';
245  $wikiId = $row['iw_wikiid'] ?? '';
246 
247  return new Interwiki( null, $url, $api, $wikiId, $local, $trans );
248  }
249 
255  private function makeFromPregen( string $prefix, string $value ) {
256  // Split values
257  [ $local, $url ] = explode( ' ', $value, 2 );
258  return new Interwiki( $prefix, $url, '', '', (int)$local );
259  }
260 
267  private function getAllPrefixesPregenerated( $local ) {
268  // Lazily resolve site name
269  if ( $this->interwikiScopes >= 3 && !$this->thisSite ) {
270  $this->thisSite = $this->data['__sites:' . $this->wikiId]
271  ?? $this->options->get( MainConfigNames::InterwikiFallbackSite );
272  }
273 
274  // List of interwiki sources
275  $sources = [];
276  // Global level
277  if ( $this->interwikiScopes >= 2 ) {
278  $sources[] = '__global';
279  }
280  // Site level
281  if ( $this->interwikiScopes >= 3 ) {
282  $sources[] = '_' . $this->thisSite;
283  }
284  $sources[] = $this->wikiId;
285 
286  $data = [];
287  foreach ( $sources as $source ) {
288  $list = $this->data['__list:' . $source] ?? '';
289  foreach ( explode( ' ', $list ) as $iw_prefix ) {
290  $row = $this->data["{$source}:{$iw_prefix}"] ?? null;
291  if ( !$row ) {
292  continue;
293  }
294 
295  [ $iw_local, $iw_url ] = explode( ' ', $row );
296 
297  if ( $local !== null && $local != $iw_local ) {
298  continue;
299  }
300 
301  $data[$iw_prefix] = [
302  'iw_prefix' => $iw_prefix,
303  'iw_url' => $iw_url,
304  'iw_local' => $iw_local,
305  ];
306  }
307  }
308 
309  return array_values( $data );
310  }
311 
327  public static function buildCdbHash(
328  array $allPrefixes, int $scope = 1, ?string $thisSite = null
329  ): array {
330  $result = [];
331  $wikiId = WikiMap::getCurrentWikiId();
332  $keyPrefix = ( $scope >= 2 ) ? '__global' : $wikiId;
333  if ( $scope >= 3 && $thisSite ) {
334  $result[ "__sites:$wikiId" ] = $thisSite;
335  $keyPrefix = "_$thisSite";
336  }
337  $list = [];
338  foreach ( $allPrefixes as $iwInfo ) {
339  $prefix = $iwInfo['iw_prefix'];
340  $result["$keyPrefix:$prefix"] = implode( ' ', [
341  $iwInfo['iw_local'] ?? 0, $iwInfo['iw_url']
342  ] );
343  $list[] = $prefix;
344  }
345  $result["__list:$keyPrefix"] = implode( ' ', $list );
346  $result["__list:__sites"] = $wikiId;
347  return $result;
348  }
349 
356  private function getAllPrefixesDB( $local ) {
357  $where = [];
358  if ( $local !== null ) {
359  $where['iw_local'] = (int)$local;
360  }
361 
362  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
363  $res = $dbr->select( 'interwiki',
364  self::selectFields(),
365  $where, __METHOD__, [ 'ORDER BY' => 'iw_prefix' ]
366  );
367 
368  $retval = [];
369  foreach ( $res as $row ) {
370  $retval[] = (array)$row;
371  }
372  return $retval;
373  }
374 
381  public function getAllPrefixes( $local = null ) {
382  if ( $this->data !== null ) {
383  return $this->getAllPrefixesPregenerated( $local );
384  } else {
385  return $this->getAllPrefixesDB( $local );
386  }
387  }
388 
394  private static function selectFields() {
395  return [
396  'iw_prefix',
397  'iw_url',
398  'iw_api',
399  'iw_wikiid',
400  'iw_local',
401  'iw_trans'
402  ];
403  }
404 
405 }
An interwiki record value object.
Definition: Interwiki.php:27
Base class for language-specific code.
Definition: Language.php:56
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:36
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.
__construct(ServiceOptions $options, Language $contLang, WANObjectCache $wanCache, HookContainer $hookContainer, ILoadBalancer $loadBalancer)
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.
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()
Helper tools for dealing with other locally-hosted wikis.
Definition: WikiMap.php:33
Multi-datacenter aware caching interface.
Service interface for looking up Interwiki records.
This class is a delegate to ILBFactory for a given database cluster.
$source
const DB_REPLICA
Definition: defines.php:26