MediaWiki  1.34.0
MediaWikiGadgetsDefinitionRepo.php
Go to the documentation of this file.
1 <?php
2 
6 
11  const CACHE_VERSION = 2;
12 
14 
21  public function getGadget( $id ) {
22  $gadgets = $this->loadGadgets();
23  if ( !isset( $gadgets[$id] ) ) {
24  throw new InvalidArgumentException( "No gadget registered for '$id'" );
25  }
26 
27  return $gadgets[$id];
28  }
29 
30  public function getGadgetIds() {
31  $gadgets = $this->loadGadgets();
32  if ( $gadgets ) {
33  return array_keys( $gadgets );
34  } else {
35  return [];
36  }
37  }
38 
39  public function handlePageUpdate( LinkTarget $target ) {
40  if ( $target->getNamespace() == NS_MEDIAWIKI && $target->getText() == 'Gadgets-definition' ) {
41  $this->purgeDefinitionCache();
42  }
43  }
44 
49  private function purgeDefinitionCache() {
50  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
51  $cache->touchCheckKey( $this->getDefinitionCacheKey() );
52  }
53 
54  private function getDefinitionCacheKey() {
55  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
56 
57  return $cache->makeKey(
58  'gadgets-definition',
60  self::CACHE_VERSION
61  );
62  }
63 
70  protected function loadGadgets() {
71  if ( $this->definitionCache !== null ) {
72  return $this->definitionCache; // process cache hit
73  }
74 
75  // Ideally $t1Cache is APC, and $wanCache is memcached
76  $t1Cache = ObjectCache::getLocalServerInstance( 'hash' );
77  $wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
78 
79  $key = $this->getDefinitionCacheKey();
80 
81  // (a) Check the tier 1 cache
82  $value = $t1Cache->get( $key );
83  // Randomize logical APC expiry to avoid stampedes
84  // somewhere between 7.0 and 15.0 (seconds)
85  $cutoffAge = mt_rand( 7 * 1e6, 15 * 1e6 ) / 1e6;
86  // Check if it passes a blind TTL check (avoids I/O)
87  if ( $value && ( microtime( true ) - $value['time'] ) < $cutoffAge ) {
88  $this->definitionCache = $value['gadgets']; // process cache
90  }
91  // Cache generated after the "check" time should be up-to-date
92  $ckTime = $wanCache->getCheckKeyTime( $key ) + WANObjectCache::HOLDOFF_TTL;
93  if ( $value && $value['time'] > $ckTime ) {
94  $this->definitionCache = $value['gadgets']; // process cache
96  }
97 
98  // (b) Fetch value from WAN cache or regenerate if needed.
99  // This is hit occasionally and more so when the list changes.
100  $us = $this;
101  $value = $wanCache->getWithSetCallback(
102  $key,
104  function ( $old, &$ttl, &$setOpts ) use ( $us ) {
105  $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
106 
107  $now = microtime( true );
108  $gadgets = $us->fetchStructuredList();
109  if ( $gadgets === false ) {
110  $ttl = WANObjectCache::TTL_UNCACHEABLE;
111  }
112 
113  return [ 'gadgets' => $gadgets, 'time' => $now ];
114  },
115  [ 'checkKeys' => [ $key ], 'lockTSE' => 300 ]
116  );
117 
118  // Update the tier 1 cache as needed
119  if ( $value['gadgets'] !== false && $value['time'] > $ckTime ) {
120  // Set a modest TTL to keep the WAN key in cache
121  $t1Cache->set( $key, $value, mt_rand( 300, 600 ) );
122  }
123 
124  $this->definitionCache = $value['gadgets'];
125 
126  return $this->definitionCache;
127  }
128 
135  public function fetchStructuredList( $forceNewText = null ) {
136  if ( $forceNewText === null ) {
137  // T157210: avoid using wfMessage() to avoid staleness due to cache layering
138  $title = Title::makeTitle( NS_MEDIAWIKI, 'Gadgets-definition' );
139  $rev = Revision::newFromTitle( $title );
140  if ( !$rev || !$rev->getContent() || $rev->getContent()->isEmpty() ) {
141  return false; // don't cache
142  }
143 
144  $g = $rev->getContent()->getNativeData();
145  } else {
146  $g = $forceNewText;
147  }
148 
149  $gadgets = $this->listFromDefinition( $g );
150  if ( !count( $gadgets ) ) {
151  return false; // don't cache; Bug 37228
152  }
153 
154  $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
155  wfDebug( __METHOD__ . ": $source parsed, cache entry should be updated\n" );
156 
157  return $gadgets;
158  }
159 
166  private function listFromDefinition( $definition ) {
167  $definition = preg_replace( '/<!--.*?-->/s', '', $definition );
168  $lines = preg_split( '/(\r\n|\r|\n)+/', $definition );
169 
170  $gadgets = [];
171  $section = '';
172 
173  foreach ( $lines as $line ) {
174  $m = [];
175  if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
176  $section = $m[1];
177  } else {
178  $gadget = $this->newFromDefinition( $line, $section );
179  if ( $gadget ) {
180  $gadgets[$gadget->getName()] = $gadget;
181  }
182  }
183  }
184 
185  return $gadgets;
186  }
187 
194  public function newFromDefinition( $definition, $category ) {
195  $m = [];
196  if ( !preg_match(
197  '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/',
198  $definition,
199  $m
200  ) ) {
201  return false;
202  }
203  // NOTE: the gadget name is used as part of the name of a form field,
204  // and must follow the rules defined in https://www.w3.org/TR/html4/types.html#type-cdata
205  // Also, title-normalization applies.
206  $info = [ 'category' => $category ];
207  $info['name'] = trim( str_replace( ' ', '_', $m[1] ) );
208  // If the name is too long, then RL will throw an MWException when
209  // we try to register the module
210  if ( !Gadget::isValidGadgetID( $info['name'] ) ) {
211  return false;
212  }
213  $info['definition'] = $definition;
214  $options = trim( $m[2], ' []' );
215 
216  foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) {
217  $arr = preg_split( '/\s*=\s*/', $option, 2 );
218  $option = $arr[0];
219  if ( isset( $arr[1] ) ) {
220  $params = explode( ',', $arr[1] );
221  $params = array_map( 'trim', $params );
222  } else {
223  $params = [];
224  }
225 
226  switch ( $option ) {
227  case 'ResourceLoader':
228  $info['resourceLoaded'] = true;
229  break;
230  case 'dependencies':
231  $info['dependencies'] = $params;
232  break;
233  case 'peers':
234  $info['peers'] = $params;
235  break;
236  case 'rights':
237  $info['requiredRights'] = $params;
238  break;
239  case 'hidden':
240  $info['hidden'] = true;
241  break;
242  case 'skins':
243  $info['requiredSkins'] = $params;
244  break;
245  case 'default':
246  $info['onByDefault'] = true;
247  break;
248  case 'targets':
249  $info['targets'] = $params;
250  break;
251  case 'type':
252  // Single value, not a list
253  $info['type'] = $params[0] ?? '';
254  break;
255  }
256  }
257 
258  foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
259  $page = "MediaWiki:Gadget-$page";
260 
261  if ( preg_match( '/\.js/', $page ) ) {
262  $info['scripts'][] = $page;
263  } elseif ( preg_match( '/\.css/', $page ) ) {
264  $info['styles'][] = $page;
265  }
266  }
267 
268  return new Gadget( $info );
269  }
270 }
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
MediaWiki\Linker\LinkTarget\getText
getText()
Returns the link in text form, without namespace prefix or fragment.
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
Gadget\CACHE_TTL
const CACHE_TTL
Definition: Gadget.php:23
MediaWikiGadgetsDefinitionRepo\purgeDefinitionCache
purgeDefinitionCache()
Purge the definitions cache, for example if MediaWiki:Gadgets-definition was edited.
Definition: MediaWikiGadgetsDefinitionRepo.php:49
MediaWikiGadgetsDefinitionRepo\getGadgetIds
getGadgetIds()
Get the ids of the gadgets provided by this repository.
Definition: MediaWikiGadgetsDefinitionRepo.php:30
MediaWikiGadgetsDefinitionRepo
Gadgets repo powered by MediaWiki:Gadgets-definition.
Definition: MediaWikiGadgetsDefinitionRepo.php:10
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:138
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
MediaWikiGadgetsDefinitionRepo\handlePageUpdate
handlePageUpdate(LinkTarget $target)
Given that the provided page was updated, invalidate caches if necessary.
Definition: MediaWikiGadgetsDefinitionRepo.php:39
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2575
Gadget
Wrapper for one gadget.
Definition: Gadget.php:17
$lines
$lines
Definition: router.php:61
$title
$title
Definition: testCompression.php:34
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
GadgetRepo
Definition: GadgetRepo.php:5
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:913
MediaWikiGadgetsDefinitionRepo\listFromDefinition
listFromDefinition( $definition)
Generates a structured list of Gadget objects from a definition.
Definition: MediaWikiGadgetsDefinitionRepo.php:166
$line
$line
Definition: cdb.php:59
Gadget\isValidGadgetID
static isValidGadgetID( $id)
Whether the provided gadget id is valid.
Definition: Gadget.php:114
MediaWikiGadgetsDefinitionRepo\newFromDefinition
newFromDefinition( $definition, $category)
Creates an instance of this class from definition in MediaWiki:Gadgets-definition.
Definition: MediaWikiGadgetsDefinitionRepo.php:194
MediaWikiGadgetsDefinitionRepo\fetchStructuredList
fetchStructuredList( $forceNewText=null)
Fetch list of gadgets and returns it as associative array of sections with gadgets e....
Definition: MediaWikiGadgetsDefinitionRepo.php:135
$cache
$cache
Definition: mcc.php:33
$source
$source
Definition: mwdoc-filter.php:34
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:68
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
MediaWikiGadgetsDefinitionRepo\CACHE_VERSION
const CACHE_VERSION
Definition: MediaWikiGadgetsDefinitionRepo.php:11
MediaWikiGadgetsDefinitionRepo\getGadget
getGadget( $id)
Definition: MediaWikiGadgetsDefinitionRepo.php:21
MediaWikiGadgetsDefinitionRepo\loadGadgets
loadGadgets()
Loads list of gadgets and returns it as associative array of sections with gadgets e....
Definition: MediaWikiGadgetsDefinitionRepo.php:70
MediaWikiGadgetsDefinitionRepo\$definitionCache
$definitionCache
Definition: MediaWikiGadgetsDefinitionRepo.php:13
Gadget\GADGET_CLASS_VERSION
const GADGET_CLASS_VERSION
Increment this when changing class structure.
Definition: Gadget.php:21
MediaWikiGadgetsDefinitionRepo\getDefinitionCacheKey
getDefinitionCacheKey()
Definition: MediaWikiGadgetsDefinitionRepo.php:54
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:268