MediaWiki REL1_34
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
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}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
$line
Definition cdb.php:59
Wrapper for one gadget.
Definition Gadget.php:17
const GADGET_CLASS_VERSION
Increment this when changing class structure.
Definition Gadget.php:21
const CACHE_TTL
Definition Gadget.php:23
static isValidGadgetID( $id)
Whether the provided gadget id is valid.
Definition Gadget.php:114
Gadgets repo powered by MediaWiki:Gadgets-definition.
newFromDefinition( $definition, $category)
Creates an instance of this class from definition in MediaWiki:Gadgets-definition.
handlePageUpdate(LinkTarget $target)
Given that the provided page was updated, invalidate caches if necessary.
loadGadgets()
Loads list of gadgets and returns it as associative array of sections with gadgets e....
listFromDefinition( $definition)
Generates a structured list of Gadget objects from a definition.
getGadgetIds()
Get the ids of the gadgets provided by this repository.
purgeDefinitionCache()
Purge the definitions cache, for example if MediaWiki:Gadgets-definition was edited.
fetchStructuredList( $forceNewText=null)
Fetch list of gadgets and returns it as associative array of sections with gadgets e....
MediaWikiServices is the service locator for the application scope of MediaWiki.
Relational database abstraction object.
Definition Database.php:49
const NS_MEDIAWIKI
Definition Defines.php:77
getNamespace()
Get the namespace index.
getText()
Returns the link in text form, without namespace prefix or fragment.
$cache
Definition mcc.php:33
$source
const DB_REPLICA
Definition defines.php:25
$lines
Definition router.php:61