MediaWiki REL1_31
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->getCheckKey() );
52 }
53
54 private function getCheckKey() {
55 return wfMemcKey( 'gadgets-definition', Gadget::GADGET_CLASS_VERSION, self::CACHE_VERSION );
56 }
57
64 protected function loadGadgets() {
65 if ( $this->definitionCache !== null ) {
66 return $this->definitionCache; // process cache hit
67 }
68
69 // Ideally $t1Cache is APC, and $wanCache is memcached
70 $t1Cache = ObjectCache::getLocalServerInstance( 'hash' );
71 $wanCache = MediaWikiServices::getInstance()->getMainWANObjectCache();
72
73 $key = $this->getCheckKey();
74
75 // (a) Check the tier 1 cache
76 $value = $t1Cache->get( $key );
77 // Check if it passes a blind TTL check (avoids I/O)
78 if ( $value && ( microtime( true ) - $value['time'] ) < 10 ) {
79 $this->definitionCache = $value['gadgets']; // process cache
81 }
82 // Cache generated after the "check" time should be up-to-date
83 $ckTime = $wanCache->getCheckKeyTime( $key ) + WANObjectCache::HOLDOFF_TTL;
84 if ( $value && $value['time'] > $ckTime ) {
85 $this->definitionCache = $value['gadgets']; // process cache
87 }
88
89 // (b) Fetch value from WAN cache or regenerate if needed.
90 // This is hit occasionally and more so when the list changes.
91 $us = $this;
92 $value = $wanCache->getWithSetCallback(
93 $key,
95 function ( $old, &$ttl, &$setOpts ) use ( $us ) {
96 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
97
98 $now = microtime( true );
99 $gadgets = $us->fetchStructuredList();
100 if ( $gadgets === false ) {
101 $ttl = WANObjectCache::TTL_UNCACHEABLE;
102 }
103
104 return [ 'gadgets' => $gadgets, 'time' => $now ];
105 },
106 [ 'checkKeys' => [ $key ], 'lockTSE' => 300 ]
107 );
108
109 // Update the tier 1 cache as needed
110 if ( $value['gadgets'] !== false && $value['time'] > $ckTime ) {
111 // Set a modest TTL to keep the WAN key in cache
112 $t1Cache->set( $key, $value, mt_rand( 300, 600 ) );
113 }
114
115 $this->definitionCache = $value['gadgets'];
116
118 }
119
126 public function fetchStructuredList( $forceNewText = null ) {
127 if ( $forceNewText === null ) {
128 // T157210: avoid using wfMessage() to avoid staleness due to cache layering
129 $title = Title::makeTitle( NS_MEDIAWIKI, 'Gadgets-definition' );
130 $rev = Revision::newFromTitle( $title );
131 if ( !$rev || !$rev->getContent() || $rev->getContent()->isEmpty() ) {
132 return false; // don't cache
133 }
134
135 $g = $rev->getContent()->getNativeData();
136 } else {
137 $g = $forceNewText;
138 }
139
140 $gadgets = $this->listFromDefinition( $g );
141 if ( !count( $gadgets ) ) {
142 return false; // don't cache; Bug 37228
143 }
144
145 $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
146 wfDebug( __METHOD__ . ": $source parsed, cache entry should be updated\n" );
147
148 return $gadgets;
149 }
150
157 private function listFromDefinition( $definition ) {
158 $definition = preg_replace( '/<!--.*?-->/s', '', $definition );
159 $lines = preg_split( '/(\r\n|\r|\n)+/', $definition );
160
161 $gadgets = [];
162 $section = '';
163
164 foreach ( $lines as $line ) {
165 $m = [];
166 if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
167 $section = $m[1];
168 } else {
169 $gadget = $this->newFromDefinition( $line, $section );
170 if ( $gadget ) {
171 $gadgets[$gadget->getName()] = $gadget;
172 }
173 }
174 }
175
176 return $gadgets;
177 }
178
185 public function newFromDefinition( $definition, $category ) {
186 $m = [];
187 if ( !preg_match(
188 '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/',
189 $definition,
190 $m
191 ) ) {
192 return false;
193 }
194 // NOTE: the gadget name is used as part of the name of a form field,
195 // and must follow the rules defined in https://www.w3.org/TR/html4/types.html#type-cdata
196 // Also, title-normalization applies.
197 $info = [ 'category' => $category ];
198 $info['name'] = trim( str_replace( ' ', '_', $m[1] ) );
199 // If the name is too long, then RL will throw an MWException when
200 // we try to register the module
201 if ( !Gadget::isValidGadgetID( $info['name'] ) ) {
202 return false;
203 }
204 $info['definition'] = $definition;
205 $options = trim( $m[2], ' []' );
206
207 foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) {
208 $arr = preg_split( '/\s*=\s*/', $option, 2 );
209 $option = $arr[0];
210 if ( isset( $arr[1] ) ) {
211 $params = explode( ',', $arr[1] );
212 $params = array_map( 'trim', $params );
213 } else {
214 $params = [];
215 }
216
217 switch ( $option ) {
218 case 'ResourceLoader':
219 $info['resourceLoaded'] = true;
220 break;
221 case 'dependencies':
222 $info['dependencies'] = $params;
223 break;
224 case 'peers':
225 $info['peers'] = $params;
226 break;
227 case 'rights':
228 $info['requiredRights'] = $params;
229 break;
230 case 'hidden':
231 $info['hidden'] = true;
232 break;
233 case 'skins':
234 $info['requiredSkins'] = $params;
235 break;
236 case 'default':
237 $info['onByDefault'] = true;
238 break;
239 case 'targets':
240 $info['targets'] = $params;
241 break;
242 case 'type':
243 // Single value, not a list
244 $info['type'] = isset( $params[0] ) ? $params[0] : '';
245 break;
246 }
247 }
248
249 foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
250 $page = "MediaWiki:Gadget-$page";
251
252 if ( preg_match( '/\.js/', $page ) ) {
253 $info['scripts'][] = $page;
254 } elseif ( preg_match( '/\.css/', $page ) ) {
255 $info['styles'][] = $page;
256 }
257 }
258
259 return new Gadget( $info );
260 }
261}
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.
wfMemcKey()
Make a cache key for the local wiki.
$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: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:2001
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:3022
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:1777
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