MediaWiki REL1_32
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 // Check if it passes a blind TTL check (avoids I/O)
84 if ( $value && ( microtime( true ) - $value['time'] ) < 10 ) {
85 $this->definitionCache = $value['gadgets']; // process cache
87 }
88 // Cache generated after the "check" time should be up-to-date
89 $ckTime = $wanCache->getCheckKeyTime( $key ) + WANObjectCache::HOLDOFF_TTL;
90 if ( $value && $value['time'] > $ckTime ) {
91 $this->definitionCache = $value['gadgets']; // process cache
93 }
94
95 // (b) Fetch value from WAN cache or regenerate if needed.
96 // This is hit occasionally and more so when the list changes.
97 $us = $this;
98 $value = $wanCache->getWithSetCallback(
99 $key,
101 function ( $old, &$ttl, &$setOpts ) use ( $us ) {
102 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
103
104 $now = microtime( true );
105 $gadgets = $us->fetchStructuredList();
106 if ( $gadgets === false ) {
107 $ttl = WANObjectCache::TTL_UNCACHEABLE;
108 }
109
110 return [ 'gadgets' => $gadgets, 'time' => $now ];
111 },
112 [ 'checkKeys' => [ $key ], 'lockTSE' => 300 ]
113 );
114
115 // Update the tier 1 cache as needed
116 if ( $value['gadgets'] !== false && $value['time'] > $ckTime ) {
117 // Set a modest TTL to keep the WAN key in cache
118 $t1Cache->set( $key, $value, mt_rand( 300, 600 ) );
119 }
120
121 $this->definitionCache = $value['gadgets'];
122
124 }
125
132 public function fetchStructuredList( $forceNewText = null ) {
133 if ( $forceNewText === null ) {
134 // T157210: avoid using wfMessage() to avoid staleness due to cache layering
135 $title = Title::makeTitle( NS_MEDIAWIKI, 'Gadgets-definition' );
136 $rev = Revision::newFromTitle( $title );
137 if ( !$rev || !$rev->getContent() || $rev->getContent()->isEmpty() ) {
138 return false; // don't cache
139 }
140
141 $g = $rev->getContent()->getNativeData();
142 } else {
143 $g = $forceNewText;
144 }
145
146 $gadgets = $this->listFromDefinition( $g );
147 if ( !count( $gadgets ) ) {
148 return false; // don't cache; Bug 37228
149 }
150
151 $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
152 wfDebug( __METHOD__ . ": $source parsed, cache entry should be updated\n" );
153
154 return $gadgets;
155 }
156
163 private function listFromDefinition( $definition ) {
164 $definition = preg_replace( '/<!--.*?-->/s', '', $definition );
165 $lines = preg_split( '/(\r\n|\r|\n)+/', $definition );
166
167 $gadgets = [];
168 $section = '';
169
170 foreach ( $lines as $line ) {
171 $m = [];
172 if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
173 $section = $m[1];
174 } else {
175 $gadget = $this->newFromDefinition( $line, $section );
176 if ( $gadget ) {
177 $gadgets[$gadget->getName()] = $gadget;
178 }
179 }
180 }
181
182 return $gadgets;
183 }
184
191 public function newFromDefinition( $definition, $category ) {
192 $m = [];
193 if ( !preg_match(
194 '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/',
195 $definition,
196 $m
197 ) ) {
198 return false;
199 }
200 // NOTE: the gadget name is used as part of the name of a form field,
201 // and must follow the rules defined in https://www.w3.org/TR/html4/types.html#type-cdata
202 // Also, title-normalization applies.
203 $info = [ 'category' => $category ];
204 $info['name'] = trim( str_replace( ' ', '_', $m[1] ) );
205 // If the name is too long, then RL will throw an MWException when
206 // we try to register the module
207 if ( !Gadget::isValidGadgetID( $info['name'] ) ) {
208 return false;
209 }
210 $info['definition'] = $definition;
211 $options = trim( $m[2], ' []' );
212
213 foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) {
214 $arr = preg_split( '/\s*=\s*/', $option, 2 );
215 $option = $arr[0];
216 if ( isset( $arr[1] ) ) {
217 $params = explode( ',', $arr[1] );
218 $params = array_map( 'trim', $params );
219 } else {
220 $params = [];
221 }
222
223 switch ( $option ) {
224 case 'ResourceLoader':
225 $info['resourceLoaded'] = true;
226 break;
227 case 'dependencies':
228 $info['dependencies'] = $params;
229 break;
230 case 'peers':
231 $info['peers'] = $params;
232 break;
233 case 'rights':
234 $info['requiredRights'] = $params;
235 break;
236 case 'hidden':
237 $info['hidden'] = true;
238 break;
239 case 'skins':
240 $info['requiredSkins'] = $params;
241 break;
242 case 'default':
243 $info['onByDefault'] = true;
244 break;
245 case 'targets':
246 $info['targets'] = $params;
247 break;
248 case 'type':
249 // Single value, not a list
250 $info['type'] = isset( $params[0] ) ? $params[0] : '';
251 break;
252 }
253 }
254
255 foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
256 $page = "MediaWiki:Gadget-$page";
257
258 if ( preg_match( '/\.js/', $page ) ) {
259 $info['scripts'][] = $page;
260 } elseif ( preg_match( '/\.css/', $page ) ) {
261 $info['styles'][] = $page;
262 }
263 }
264
265 return new Gadget( $info );
266 }
267}
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.
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:133
Relational database abstraction object.
Definition Database.php:48
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template $section
Definition hooks.txt:3107
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1818
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
$params