r56201 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r56200‎ | r56201 | r56202 >
Date:18:28, 11 September 2009
Author:btongminh
Status:resolved (Comments)
Tags:
Comment:
Rewrote this globalusage extension. Still needs a population script and a lot of work on the UI.
Modified paths:
  • /trunk/extensions/GlobalUsage/GlobalUsage.i18n.php (modified) (history)
  • /trunk/extensions/GlobalUsage/GlobalUsage.pg.sql (modified) (history)
  • /trunk/extensions/GlobalUsage/GlobalUsage.php (modified) (history)
  • /trunk/extensions/GlobalUsage/GlobalUsage.sql (modified) (history)
  • /trunk/extensions/GlobalUsage/GlobalUsageDaemon.php (deleted) (history)
  • /trunk/extensions/GlobalUsage/GlobalUsageHooks.php (added) (history)
  • /trunk/extensions/GlobalUsage/GlobalUsage_body.php (modified) (history)
  • /trunk/extensions/GlobalUsage/SpecialGlobalUsage.php (added) (history)
  • /trunk/extensions/GlobalUsage/populateGlobalUsage.php (deleted) (history)
  • /trunk/extensions/GlobalUsage/readme.txt (modified) (history)

Diff [purge]

Index: trunk/extensions/GlobalUsage/GlobalUsageDaemon.php
@@ -1,434 +0,0 @@
2 -<?php
3 -
4 -class GlobalUsageDaemon {
5 - // This daemon supports updating from multiple wikis
6 -
7 - // Array of wiki => timestamp pairs
8 - public $timestamps;
9 - // Array of database key => database pairs
10 - private $databases;
11 - // Array of localized namespaces
12 - private $namespaces;
13 - // Location of the log file
14 - private $log;
15 - // Stderr pointer
16 - private $stderr;
17 - // Array of wikis containing config settings
18 - private $wikiList;
19 -
20 - public function __construct($log, $wikiList, $silent = false) {
21 - $this->databases = array();
22 - $this->timestamps = array();
23 - $this->namespaces = array();
24 - $this->log = $log;
25 - $this->wikiList = $wikiList;
26 -
27 - if (!$silent)
28 - $this->stderr = fopen('php://stderr', 'w');
29 - else
30 - $this->stderr = null;
31 -
32 - if (($fp = fopen($this->log, 'r')) !== false) {
33 - flock($fp, LOCK_EX);
34 - while (!feof($fp)) {
35 - $line = fgets($fp);
36 - if (strpos($line, "\t") !== false) {
37 - list($lw, $lt) = explode("\t", $line, 2);
38 - $this->timestamps[$lw] = trim($lt);
39 - }
40 - }
41 - fclose($fp);
42 - }
43 - $this->debug("Read previous data from {$log}");
44 - $this->debug('Position is defined for the following wikis: '.
45 - implode(', ', array_keys($this->timestamps)));
46 - }
47 -
48 - public function debug($string) {
49 - if ($this->stderr) fwrite($this->stderr, "{$string}\n");
50 - }
51 -
52 - /*
53 - * Populate the globalimagelinks table from the local imagelinks
54 - */
55 - public function populateGlobalUsage($wiki, $interval, $throttle = 1000000, $maxLag) {
56 - $this->debug("Populating globalimagelinks on {$wiki}");
57 -
58 - if (!isset($this->namespaces[$wiki]))
59 - $this->fetchNamespaces($wiki);
60 - $namespaces = $this->namespaces[$wiki];
61 -
62 - $dbw = GlobalUsage::getDatabase(DB_MASTER);
63 - $dbw->immediateBegin();
64 - $dbw->delete('globalimagelinks', array('gil_wiki' => $wiki), __METHOD__);
65 -
66 - $dbr = $this->getDatabase($wiki);
67 -
68 - // Account for slave lag
69 - $res = $dbr->select('recentchanges', 'MAX(rc_timestamp) AS timestamp');
70 - $row = $res->fetchRow();
71 - $timestamp = substr(substr($row['timestamp'], 0, 14 - $interval).
72 - '00000000000000', 0, 14);
73 - $res->free();
74 -
75 - $prevPage = 0;
76 - $prevImage = '';
77 - $limit = 2000;
78 - do {
79 - $loopStart = microtime(true);
80 -
81 - $sql =
82 - // Join order is important for sorting
83 - 'SELECT STRAIGHT_JOIN '.
84 - 'page_id, page_namespace, page_title, il_to, img_name IS NOT NULL AS is_local '.
85 - 'FROM '.$dbr->tableName('imagelinks').' '.
86 - // MySQL will choose the il_to index from il_to > 'O'
87 - // TODO: Doesn't work on the Toolserver
88 - 'FORCE INDEX(il_from) '.
89 - 'LEFT JOIN '.$dbr->tableName('image').' ON il_to = img_name '.
90 - 'JOIN '.$dbr->tableName('page').' ON page_id = il_from '.
91 - 'WHERE il_from >= '.$prevPage.' AND il_to > '.$dbr->addQuotes($prevImage).
92 - 'ORDER BY il_from, il_to ';
93 - $query = $dbr->limitResult($sql, $limit, 0);
94 - $res = $dbr->query($query, __METHOD__);
95 -
96 - $count = 0;
97 - $rows = array();
98 - while ($row = $res->fetchRow()) {
99 - $count++;
100 - $rows[] = array(
101 - 'gil_wiki' => $wiki,
102 - 'gil_page' => $row['page_id'],
103 - 'gil_page_namespace' => $namespaces[$row['page_namespace']],
104 - 'gil_page_title' => $row['page_title'],
105 - 'gil_to' => $row['il_to'],
106 - 'gil_is_local' => $row['is_local']
107 - );
108 - $prevPage = $row['page_id'];
109 - $prevImage = $row['il_to'];
110 - }
111 - $res->free();
112 -
113 - $dbw->insert( 'globalimagelinks', $rows, __METHOD__, 'IGNORE' );
114 -
115 - $timeTaken = microtime(true) - $loopStart;
116 - $rps = $count / $timeTaken;
117 - $this->debug("Inserted {$count} rows in {$timeTaken} seconds; {$rps} rows per second");
118 - if ($rps > $throttle) {
119 - $sleepTime = ($rps / $throttle - 1) * $timeTaken;
120 - $this->debug("Throttled {$sleepTime} seconds");
121 - sleep($sleepTime);
122 - }
123 - if ($maxLag) {
124 - $lb = wfGetLB($wiki);
125 - do {
126 - list($host, $lag) = $lb->getMaxLag();
127 - if ($lag > $maxLag) {
128 - $this->debug("Waiting for {$host}; lagged {$lag} seconds");
129 - sleep($lag - $maxLag);
130 - }
131 - } while ($lag > $maxLag);
132 - }
133 - } while ($count == $limit);
134 - $dbw->immediateCommit();
135 -
136 - $this->setTimestamp($wiki, $timestamp);
137 - }
138 -
139 - /*
140 - * Populate the globalimagelinks table from the recentchanges
141 - */
142 - public function processRecentChanges($wiki, $interval = 2) {
143 - global $wgDBtype;
144 - $dbr = $this->getDatabase($wiki);
145 - $dbw = GlobalUsage::getDatabase(DB_MASTER);
146 -
147 - $tables = array(
148 - 'img' => $dbr->tableName('image'),
149 - 'il' => $dbr->tableName('imagelinks'),
150 - 'log' => $dbr->tableName('logging'),
151 - 'page' => $dbr->tableName('page'),
152 - 'rc' => $dbr->tableName('recentchanges'),
153 - );
154 -
155 - // Get timestamp
156 - $timestamp = substr($this->timestamps[$wiki], 0, 14 - $interval);
157 -
158 - $dbw->immediateBegin();
159 -
160 - $timestamp_like_rc = $wgDBtype === 'postgres'
161 - ? "TO_CHAR(rc_timestamp, 'YYYYMMDDHH24MISS') = '$timestamp'"
162 - : "rc_timestamp LIKE '{$timestamp}%'";
163 -
164 - // Update links on all recentchanges
165 - $query = 'SELECT DISTINCT '.
166 - 'page_id AS id, rc_namespace AS ns, rc_title AS title '.
167 - "FROM {$tables['rc']}, {$tables['page']} ".
168 - 'WHERE page_namespace = rc_namespace AND page_title = rc_title '.
169 - 'AND rc_namespace <> -1 '.
170 - "AND $timestamp_like_rc";
171 -
172 - $res = $dbr->query($query, __METHOD__);
173 -
174 - $rows = array();
175 - while($row = $res->fetchRow())
176 - $rows[] = $row;
177 - $res->free();
178 - $this->processRows($rows, $wiki, $dbr, $dbw);
179 -
180 - $timestamp_like_log = $wgDBtype === 'postgres'
181 - ? "TO_CHAR(log_timestamp, 'YYYYMMDDHH24MISS') = '$timestamp'"
182 - : "log_timestamp LIKE '{$timestamp}%'";
183 -
184 - // Update links on deletion or undeletion of an article
185 - $query = 'SELECT '.
186 - 'page_id AS id, log_namespace AS ns, log_title AS title, '.
187 - 'page_id IS NOT NULL AS is_local '.
188 - "FROM {$tables['log']} ".
189 - "LEFT JOIN {$tables['page']} ON ".
190 - 'page_namespace = log_namespace AND '.
191 - 'page_title = log_title '.
192 - "WHERE log_action = 'delete' ".
193 - "AND $timestamp_like_log";
194 -
195 - $res = $dbr->query($query, __METHOD__);
196 -
197 - $rows = array();
198 - while($row = $res->fetchRow()) {
199 - if ($row['is_local']) {
200 - $rows[] = $row;
201 - } else {
202 - $dbw->delete( 'globalimagelinks', array(
203 - 'gil_wiki' => $wiki,
204 - 'gil_page' => $row['id'],
205 - ), __METHOD__);
206 - }
207 - }
208 - $res->free();
209 - $this->processRows($rows, $wiki, $dbr, $dbw);
210 -
211 - // Set the is_local flag on images
212 - $query = 'SELECT DISTINCT '.
213 - 'log_title, img_name IS NOT NULL AS is_local '.
214 - "FROM {$tables['log']}, {$tables['il']} ".
215 - "LEFT JOIN {$tables['img']} ON il_to = img_name ".
216 - "WHERE log_namespace = 6 AND log_type IN ('upload', 'delete') ".
217 - "AND $timestamp_like_log";
218 -
219 - $res = $dbr->query($query, __METHOD__);
220 -
221 - while($row = $res->fetchRow()) {
222 - $dbw->update( 'globalimagelinks',
223 - array( 'gil_is_local' => $row['is_local'] ),
224 - array(
225 - 'gil_wiki' => $wiki,
226 - 'gil_to' => $row['log_title']
227 - ),
228 - __METHOD__ );
229 - }
230 - $res->free();
231 -
232 - // Update titles on page move
233 - $res = $dbr->select('logging',
234 - array('log_namespace', 'log_title', 'log_params'),
235 - "log_type = 'move' AND $timestamp_like_log",
236 - __METHOD__);
237 -
238 - while($row = $res->fetchRow()) {
239 - $namespace = '';
240 - $title = $row['log_params'];
241 - if (strpos($row['log_params'], ':') !== false) {
242 - $new_title = explode(':', $row['log_params'], 2);
243 - if (in_array($new_title[0], $this->namespaces[$wiki])) {
244 - list($namespace, $title) = $new_title;
245 - }
246 - }
247 - // FIXME: Unindexed update!
248 - $dbw->update( 'globalimagelinks', array(
249 - 'gil_page_namespace' => $namespace,
250 - 'gil_page_title' => $title
251 - ), array(
252 - 'gil_wiki' => $wiki,
253 - 'gil_page_namespace' => $row['log_namespace'],
254 - 'gil_page_title' => $row['log_title']
255 - ), __METHOD__ );
256 - }
257 - $res->free();
258 -
259 - $dbw->immediateCommit();
260 -
261 - // Set new timestamp
262 - $newTs = wfTimestamp(TS_MW, $this->incrementTimestamp(
263 - $this->timestamps[$wiki], $interval));
264 - $this->setTimestamp($wiki, $newTs);
265 -
266 - // Return when this function should be called again
267 - $waitUntil = wfTimestamp(TS_MW, $this->incrementTimestamp($newTs, $interval));
268 -
269 - $res = $dbr->select('recentchanges', 'MAX(rc_timestamp) AS r', '', __METHOD__);
270 - $row = $res->fetchRow();
271 - $res->free();
272 - return array($waitUntil, $row['r'] > $waitUntil);
273 -
274 - }
275 -
276 - /*
277 - * Call doUpdate
278 - */
279 - private function processRows($rows, $wiki, $dbr, $dbw) {
280 - if (count($rows)) {
281 - foreach ($rows as $row)
282 - GlobalUsage::doUpdate($row['id'], $wiki,
283 - $row['ns'], $row['title'], $dbr, $dbw);
284 - }
285 - }
286 -
287 - /*
288 - * Get namespace names
289 - */
290 - private function fetchNamespaces($wiki) {
291 - global $wgContLang;
292 - if ($wiki == GlobalUsage::getLocalInterwiki()) {
293 - $this->namespaces[$wiki] = $wgContLang->getFormattedNamespaces();
294 - } else {
295 - // Not the current wiki, need to fetch using the API
296 - $this->debug("Fetching namespaces from external wiki {$wiki}");
297 -
298 - if (!isset($this->wikiList[$wiki])) {
299 - // Raise error
300 - }
301 - $address = $this->wikiList[$wiki];
302 - $address .= '?action=query&meta=siteinfo&siprop=namespaces&format=php';
303 -
304 - $curl = curl_init($address);
305 - curl_setopt($curl, CURLOPT_USERAGENT, 'GlobalUsage/1.0');
306 - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
307 - $data = unserialize(curl_exec($curl));
308 - curl_close($curl);
309 - if (!$data) return false;
310 -
311 - $this->namespaces[$wiki] = array();
312 - foreach ($data['query']['namespaces'] as $id => $value)
313 - $this->namespaces[$wiki][$id] = $value['*'];
314 - return true;
315 - }
316 - }
317 -
318 - /*
319 - * Get database object for reading
320 - */
321 - private function getDatabase($wiki) {
322 - if ($wiki == GlobalUsage::getLocalInterwiki())
323 - return wfGetDB(DB_SLAVE);
324 - else
325 - return wfGetDB(DB_SLAVE, array(), $wiki);
326 - }
327 -
328 - /*
329 - * Save timestamp to a logfile to continue after
330 - */
331 - private function setTimestamp($wiki, $timestamp) {
332 - $written = false;
333 - if (($fp = fopen($this->log, 'r+')) === false) {
334 - // Raise an error
335 - }
336 -
337 - flock($fp, LOCK_EX);
338 - fseek($fp, 0, SEEK_SET);
339 - while (!feof($fp)) {
340 - $line = fgets($fp);
341 - if (strpos($line, "\t") !== false) {
342 - list($lw, $lt) = explode("\t", $line, 2);
343 - if ($lw == $wiki) {
344 - fseek($fp, ftell($fp) - 15, SEEK_SET);
345 - fwrite($fp, $timestamp);
346 - $written = true;
347 - }
348 - }
349 - }
350 - if (!$written) fwrite($fp, "{$wiki}\t{$timestamp}\n");
351 - fclose($fp);
352 -
353 - $this->timestamps[$wiki] = $timestamp;
354 - }
355 - private function incrementTimestamp($timestamp, $interval) {
356 - $timestamp = (string)((int)$timestamp + pow(10, $interval));
357 - return gmmktime(
358 - substr($timestamp, 8, 2),
359 - substr($timestamp, 10, 2),
360 - substr($timestamp, 12, 2),
361 - substr($timestamp, 4, 2),
362 - substr($timestamp, 6, 2),
363 - substr($timestamp, 0, 4)
364 - );
365 - }
366 -
367 - public function runLocalDaemon($wiki, $interval) {
368 - $this->debug("Running local daemon on {$wiki}");
369 -
370 - // Fetch namespaces
371 - if (!isset($this->namespaces[$wiki]))
372 - if (!$this->fetchNamespaces($wiki))
373 - die("Could not fetch namespaces for {$wiki}\n");
374 - $dbr = $this->getDatabase($wiki);
375 -
376 - while (true) {
377 - list($waitUntil, $hasMore) = $this->processRecentChanges($wiki, $interval);
378 - while (wfTimestamp(TS_UNIX, $waitUntil) > time() - $dbr->getLag()) {
379 - $sleepTime = max(wfTimestamp(TS_UNIX, $waitUntil) + $dbr->getLag() - time(), 0);
380 - $this->debug("Sleeping {$sleepTime} seconds: ".
381 - 'need to wait until '.$waitUntil.
382 - '; now is '.wfTimestamp(TS_MW));
383 - sleep($sleepTime);
384 - }
385 - }
386 - }
387 - public function runDaemon($interval) {
388 - $waitUntil = array();
389 -
390 - foreach ($this->wikiList as $wiki => $info)
391 - $waitUntil[$wiki] = 0;
392 -
393 - $this->debug("Running GlobalUsage daemon on the following wikis: ".
394 - implode(', ', array_keys($waitUntil)));
395 - while (true) {
396 - // Sort by time
397 - asort($waitUntil);
398 - reset($waitUntil);
399 -
400 - $dbr = $this->getDatabase(key($waitUntil));
401 - if (current($waitUntil) != 0) {
402 - $waitUntilTime = wfTimestamp(TS_UNIX, current($waitUntil));
403 - $lag = $dbr->getLag();
404 - while ($waitUntilTime > time() - $lag) {
405 - $sleepTime = max($waitUntilTime - time() + $lag, 0);
406 - $this->debug("Sleeping {$sleepTime} seconds: ".
407 - 'need to wait until '.current($waitUntil).
408 - '; now is '.wfTimestamp(TS_MW, time() - $lag));
409 - sleep($sleepTime);
410 - }
411 - }
412 -
413 - $wiki = key($waitUntil);
414 -
415 - // Fetch namespaces
416 - if (!isset($this->namespaces[$wiki]))
417 - if (!$this->fetchNamespaces($wiki)) {
418 - $this->debug("Could not fetch namespaces for {$wiki}");
419 - unset($waitUntil[$wiki]);
420 - continue;
421 - }
422 -
423 - $this->debug("Processing recentchanges for {$wiki}");
424 - $now = time();
425 - list($waitUntil[$wiki], $hasMore) = $this->processRecentChanges($wiki, $interval);
426 - if (!$hasMore) {
427 - // There are no more entries. Set the timestamp to *now* to avoid locking
428 - // of other wikis by rarely updated wikis
429 - $next = substr(substr(wfTimestamp(TS_MW, $now - $dbr->getLag()),
430 - 0, 14 - $interval).'00000000000000', 0, 14);
431 - $waitUntil[$wiki] = wfTimestamp(TS_MW, $this->incrementTimestamp($next, $interval));
432 - }
433 - }
434 - }
435 -}
Index: trunk/extensions/GlobalUsage/populateGlobalUsage.php
@@ -1,116 +0,0 @@
2 -<?php
3 -
4 -$wgguWikiList = array();
5 -
6 -$optionsWithArgs = array( 'wiki', 'interval', 'log', 'throttle' );
7 -
8 -require_once 'maintenance/commandLine.inc';
9 -require_once 'extensions/GlobalUsage/GlobalUsage.php';
10 -require_once 'extensions/GlobalUsage/GlobalUsage_body.php';
11 -require_once 'extensions/GlobalUsage/GlobalUsageDaemon.php';
12 -
13 -/*
14 - * We might want to use stdout later to to output useful data, so output error
15 - * messages to stderr where they belong.
16 - */
17 -function dieInit($msg, $exitCode = 1) {
18 - $fp = fopen('php://stderr', 'w');
19 - fwrite($fp, $msg);
20 - fwrite($fp, "\n");
21 - fclose($fp);
22 - exit($exitCode);
23 -}
24 -
25 -if(isset($options['help']) || !isset($options['log']))
26 - dieInit(
27 -"This script will populate the GlobalUsage table from the local imagelinks
28 -table. It will then continue to keep the table up to data using the logging
29 -and recentchanges tables.
30 -
31 -Usage:
32 - php extensions/GlobalUsage/populateGlobalUsage.inc --log <file>
33 - [--wiki <wiki>] [--interval <interval>] [--daemon]
34 - [--verbose] [--help]
35 -
36 - --log File to log current timestamp to
37 -
38 - --wiki The wiki to populate from. If this is equal to
39 - \$wgLocalInterwiki the database settings will be read
40 - from LocalSettings.php. If this is not equal, a config
41 - variable \$wgguWikiList[\$wiki] is expected with the
42 - url of the API entry point.
43 - --interval Pull interval in powers of 10.
44 - --throttle Maximum number of rows per second the populator is
45 - allowed to insert.
46 - --wait-for-slaves
47 - Seconds to wait for slaves. Default 0, disabled.
48 -
49 - --daemon Run as daemon processing all wikis in \$wgguWikiList.
50 - Useful when the extension can't be installed.
51 - --no-daemon Does not run a daemon after population
52 -
53 - --silent Don't print information to stderr
54 - --help Show this help
55 -",
56 - // Only exit with code 0 when no actual error occured
57 - intval(!isset($options['help'])));
58 -
59 -$defaults = array(
60 - 'wiki' => GlobalUsage::getLocalInterwiki(),
61 - 'interval' => 2,
62 - 'daemon' => false,
63 - 'no-daemon' => false,
64 - 'silent' => false,
65 - 'throttle' => 1000000,
66 - 'wait-for-slaves' => 0,
67 -);
68 -
69 -$options = array_merge( $defaults, $options );
70 -
71 -/*
72 - * Check whether the passed parameters are sane
73 - */
74 -
75 -// Check whether the log file is writable
76 -if (!touch($options['log']))
77 - dieInit("Unable to modify {$options['log']}");
78 -
79 -// Check whether the specified wiki is known
80 -if ($options['wiki'] != GlobalUsage::getLocalInterwiki()
81 - && !isset($wgguWikiList[$options['wiki']])
82 - && !$options['daemon'])
83 - dieInit("Unknown wiki '{$options['wiki']}' in \$wgguWikiList");
84 -
85 -// Check whether interval is within bounds
86 -$options['interval'] = intval($options['interval']);
87 -if ($options['interval'] < 1 || $options['interval'] > 13)
88 - dieInit("Interval must be at > 0 and < 14");
89 -
90 -// Check the throttle
91 -$options['throttle'] = intval($options['throttle']);
92 -if ($options['throttle'] < 1)
93 - dieInit("Throttle must be >= 1");
94 -
95 -if (!$options['daemon']) {
96 - // Remove all but the specified wiki
97 - $wgguWikiList = array($options['wiki'] => $wgguWikiList[$options['wiki']]);
98 -}
99 -
100 -// Create the daemon object
101 -$daemon = new GlobalUsageDaemon($options['log'], $wgguWikiList, $options['silent']);
102 -
103 -// Populate all unpopulated wikis
104 -foreach($wgguWikiList as $wiki => $info) {
105 - if (!isset($daemon->timestamps[$wiki]))
106 - $daemon->populateGlobalUsage($wiki, $options['interval'],
107 - $options['throttle'], intval($options['wait-for-slaves']));
108 -}
109 -
110 -// Run the daemon
111 -if ($options['no-daemon']) exit(0);
112 -if ($options['daemon'])
113 - $daemon->runDaemon($options['interval']);
114 -else
115 - $daemon->runLocalDaemon($options['wiki'], $options['interval']);
116 -
117 -// This point is never reached
\ No newline at end of file
Index: trunk/extensions/GlobalUsage/GlobalUsageHooks.php
@@ -0,0 +1,78 @@
 2+<?php
 3+class GlobalUsageHooks {
 4+ private static $gu = null;
 5+
 6+ /**
 7+ * Hook to LinksUpdateComplete
 8+ * Deletes old links from usage table and insert new ones.
 9+ */
 10+ public static function onLinksUpdate( $linksUpdater ) {
 11+ $title = $linksUpdater->getTitle();
 12+
 13+ // Create a list of locally existing images
 14+ $images = array_keys( $linksUpdater->getExistingImages() );
 15+ $localFiles = array_keys( RepoGroup::singleton()->getLocalRepo()->findFiles( $images ) );
 16+
 17+ $gu = self::getGlobalUsage();
 18+ $gu->deleteFrom( $title->getArticleId( GAID_FOR_UPDATE ) );
 19+ $gu->setUsage( $title, array_diff( $images, $localFiles ) );
 20+
 21+ return true;
 22+ }
 23+ /**
 24+ * Hook to TitleMoveComplete
 25+ * Sets the page title in usage table to the new name.
 26+ */
 27+ public static function onTitleMove( $ot, $nt, $user, $pageid, $redirid ) {
 28+ $gu = self::getGlobalUsage();
 29+ $gu->moveTo( $pageid, $nt );
 30+ return true;
 31+ }
 32+ /**
 33+ * Hook to ArticleDeleteComplete
 34+ * Deletes entries from usage table.
 35+ * In case of an image, copies the local link table to the global.
 36+ */
 37+ public static function onArticleDelete( $article, $user, $reason ) {
 38+ $title = $article->getTitle();
 39+ $gu = self::getGlobalUsage();
 40+ $gu->deleteFrom( $title->getArticleId( GAID_FOR_UPDATE ) );
 41+ if ( $title->getNamespace() == NS_FILE ) {
 42+ $gu->copyFromLocal( $title );
 43+ }
 44+ return true;
 45+ }
 46+
 47+ /**
 48+ * Hook to FileUndeleteComplete
 49+ * Deletes the file from the global link table.
 50+ */
 51+ public static function onFileUndelete( $title, $versions, $user, $reason ) {
 52+ $gu = self::getGlobalUsage();
 53+ $gu->deleteTo( $title );
 54+ return true;
 55+ }
 56+ /**
 57+ * Hook to UploadComplete
 58+ * Deletes the file from the global link table.
 59+ */
 60+ public static function onUpload( $upload ) {
 61+ $gu = self::getGlobalUsage();
 62+ $gu->deleteTo( $upload->getTitle() );
 63+ return true;
 64+ }
 65+
 66+ /**
 67+ * Initializes a GlobalUsage object for the current wiki.
 68+ */
 69+ private static function getGlobalUsage() {
 70+ global $wgLocalInterwiki, $wgGlobalUsageDatabase;
 71+ if ( is_null( self::$gu ) ) {
 72+ self::$gu = new GlobalUsage( $wgLocalInterwiki,
 73+ wfGetDB( DB_MASTER, array(), $wgGlobalUsageDatabase )
 74+ );
 75+ }
 76+
 77+ return self::$gu;
 78+ }
 79+}
\ No newline at end of file
Property changes on: trunk/extensions/GlobalUsage/GlobalUsageHooks.php
___________________________________________________________________
Name: svn:eol-style
180 + native
Index: trunk/extensions/GlobalUsage/GlobalUsage.sql
@@ -9,16 +9,12 @@
1010 gil_page_title varchar(255) not null,
1111 -- Image name
1212 gil_to varchar(255) not null,
13 - -- Exists locally
14 - gil_is_local tinyint(1) not null,
 13+
1514
1615 -- Note: You might want to shorten the gil_wiki part of the indices.
1716 -- If the domain format is used, only the "en.wikip" part is needed for an
1817 -- unique lookup
1918
2019 PRIMARY KEY (gil_wiki, gil_page, gil_to),
21 - -- On gil_is_local change
22 - INDEX (gil_wiki, gil_to),
23 - -- On the special page itself
24 - INDEX (gil_to, gil_is_local)
25 -) /*$wgDBTableOptions*/;
\ No newline at end of file
 20+ INDEX (gil_to, gil_wiki)
 21+) /*$wgDBTableOptions*/;
Index: trunk/extensions/GlobalUsage/GlobalUsage.pg.sql
@@ -4,8 +4,7 @@
55 gil_page_namespace TEXT NOT NULL,
66 gil_page_title TEXT NOT NULL,
77 gil_to TEXT NOT NULL,
8 - gil_is_local SMALLINT NOT NULL,
98 PRIMARY KEY (gil_wiki, gil_page)
109 );
11 -CREATE INDEX globalimagelinks_wiki ON globalimagelinks(gil_wiki, gil_to);
12 -CREATE INDEX globalimagelinks_to ON globalimagelinks(gil_to, gil_is_local);
 10+CREATE INDEX globalimagelinks_wiki ON globalimagelinks(gil_to, gil_wiki);
 11+
Index: trunk/extensions/GlobalUsage/GlobalUsage_body.php
@@ -1,185 +1,121 @@
22 <?php
33
4 -class GlobalUsage extends SpecialPage {
5 - private static $database = array();
6 - private static $interwiki = null;
 4+class GlobalUsage {
 5+ private $interwiki;
 6+ private $db;
77
8 - function __construct() {
9 - parent::__construct('GlobalUsage');
10 - wfLoadExtensionMessages( 'GlobalUsage' );
 8+ /**
 9+ * Construct a GlobalUsage instance for a certain wiki.
 10+ *
 11+ * @param $interwiki string Interwiki prefix of the wiki
 12+ * @param $db mixed Database object
 13+ */
 14+ public function __construct( $interwiki, $db ) {
 15+ $this->interwiki = $interwiki;
 16+ $this->db = $db;
1117 }
1218
13 - static function getDatabase( $dbFlags = DB_MASTER ) {
14 - global $wgguIsMaster, $wgguMasterDatabase;
15 - if ( !isset( self::$database[$dbFlags] ) )
16 - self::$database[$dbFlags] = wfGetDB( $dbFlags, array(), $wgguMasterDatabase );
17 - return self::$database[$dbFlags];
18 - }
19 - static function getLocalInterwiki() {
20 - global $wgguInterwikiStyle, $wgLocalInterwiki, $wgServerName;
21 - if (!self::$interwiki) {
22 - switch ($wgguInterwikiStyle) {
23 - case GUIW_LOCAL:
24 - self::$interwiki = $wgLocalInterwiki;
25 - break;
26 - case GUIW_SERVER_NAME:
27 - self::$interwiki = $wgServerName;
28 - break;
29 - default:
30 - self::$interwiki = $wgLocalInterwiki;
31 - }
 19+ /**
 20+ * Sets the images used by a certain page
 21+ *
 22+ * @param $title Title Title of the page
 23+ * @param $images array Array of db keys of images used
 24+ */
 25+ public function setUsage( $title, $images ) {
 26+ $insert = array();
 27+ foreach ( $images as $name ) {
 28+ $insert[] = array(
 29+ 'gil_wiki' => $this->interwiki,
 30+ 'gil_page' => $title->getArticleID( GAID_FOR_UPDATE ),
 31+ 'gil_page_namespace' => $title->getNsText(),
 32+ 'gil_page_title' => $title->getText(),
 33+ 'gil_to' => $name
 34+ );
3235 }
33 - return self::$interwiki;
 36+ $this->db->insert( 'globalimagelinks', $insert, __METHOD__ );
3437 }
35 -
36 - static function updateLinks( $linksUpdater ) {
37 - $title = $linksUpdater->getTitle();
38 - $dbr = wfGetDB(DB_SLAVE);
39 - $dbw = self::getDatabase();
40 - $dbw->immediateBegin();
41 - self::doUpdate($title->getArticleID(), self::getLocalInterwiki(),
42 - $title->getNsText(), $title->getDBkey(), $dbr, $dbw );
43 - $dbw->immediateCommit();
44 -
45 - return true;
 38+ /**
 39+ * Deletes all entries from a certain page
 40+ *
 41+ * @param $id int Page id of the page
 42+ */
 43+ public function deleteFrom( $id ) {
 44+ $this->db->delete(
 45+ 'globalimagelinks',
 46+ array(
 47+ 'gil_wiki' => $this->interwiki,
 48+ 'gil_page' => $id
 49+ ),
 50+ __METHOD__
 51+ );
4652 }
 53+ /**
 54+ * Deletes all entries to a certain image
 55+ *
 56+ * @param $title Title Title of the file
 57+ */
 58+ public function deleteTo( $title ) {
 59+ $this->db->delete(
 60+ 'globalimagelinks',
 61+ array(
 62+ 'gil_wiki' => $this->interwiki,
 63+ 'gil_to' => $title->getDBkey()
 64+ ),
 65+ __METHOD__
 66+ );
 67+ }
4768
48 - /*
49 - * Perform the update for a certain page on a certain database. The caller is
50 - * responsible for creating a master datbase object and performing
51 - * immediateBegin() and immediateCommit().
 69+ /**
 70+ * Copy local links to global table
 71+ *
 72+ * @param $title Title Title of the file to copy entries from.
5273 */
53 - static function doUpdate( $pageId, $wiki, $pageNamespace, $pageTitle,
54 - &$dbr, &$dbw ) {
55 - $query = 'SELECT il_to, img_name IS NOT NULL AS is_local '.
56 - 'FROM '.$dbr->tableName('imagelinks').' '.
57 - 'LEFT JOIN '.$dbr->tableName('image').' ON '.
58 - 'il_to = img_name WHERE il_from = '.$pageId;
59 - $res = $dbr->query($query, __METHOD__);
 74+ public function copyFromLocal( $title ) {
 75+ global $wgContLang;
6076
61 - $rows = array();
62 - while ($row = $res->fetchRow()) {
63 - $rows[] = array(
64 - "gil_wiki" => $wiki,
65 - "gil_page" => $pageId,
66 - "gil_page_namespace" => $pageNamespace,
67 - "gil_page_title" => $pageTitle,
68 - "gil_to" => $row['il_to'],
69 - "gil_is_local" => $row['is_local']);
 77+ $dbr = wfGetDB( DB_SLAVE );
 78+ $res = $dbr->select(
 79+ array( 'imagelinks', 'page' ),
 80+ array( 'il_to', 'page_id', 'page_namespace', 'page_title' ),
 81+ array( 'il_from = page_id', 'il_to' => $title->getDBkey() ),
 82+ __METHOD__
 83+ );
 84+ $insert = array();
 85+ foreach ( $res as $row ) {
 86+ $insert[] = array(
 87+ 'gil_wiki' => $this->interwiki,
 88+ 'gil_page' => $row->page_id,
 89+ 'gil_page_namespace' => $wgContLang->getNsText( $row->page_namespace ),
 90+ 'gil_page_title' => $row->page_title,
 91+ 'gil_to' => $row->il_to,
 92+ );
7093 }
71 - $res->free();
72 -
73 - $dbw->delete('globalimagelinks', array(
74 - 'gil_wiki' => $wiki, 'gil_page' => $pageId),
75 - __METHOD__);
76 - $dbw->insert( 'globalimagelinks', $rows, __METHOD__, 'IGNORE' );
 94+ $this->db->insert( 'globalimagelinks', $insert, __METHOD__ );
7795 }
7896
79 -
80 - // Set gil_is_local for an image
81 - static function setLocalFlag( $imageName, $isLocal ) {
82 - $dbw = self::getDatabase();
83 - $dbw->immediateBegin();
84 - $dbw->update( 'globalimagelinks', array( 'gil_is_local' => $isLocal ), array(
85 - 'gil_wiki' => self::getLocalInterwiki(),
86 - 'gil_to' => $imageName),
87 - __METHOD__ );
88 - $dbw->immediateCommit();
 97+ /**
 98+ * Changes the page title
 99+ *
 100+ * @param $id int Page id of the page
 101+ * @param $title Title New title of the page
 102+ */
 103+ public function moveTo( $id, $title ) {
 104+ $this->db->update(
 105+ 'globalimagelinks',
 106+ array(
 107+ 'gil_page_namespace' => $title->getNsText(),
 108+ 'gil_page_title' => $title->getText()
 109+ ),
 110+ array(
 111+ 'gil_wiki' => $this->interwiki,
 112+ 'gil_page' => $id
 113+ ),
 114+ __METHOD__
 115+ );
89116 }
90117
91 - // Set gil_is_local to false
92 - static function fileDeleted( &$file, &$oldimage, &$article, &$user, $reason ) {
93 - if ( !$oldimage )
94 - self::setLocalFlag( $article->getTitle()->getDBkey(), 0 );
95 - return true;
96 - }
97 - // Set gil_is_local to true
98 - static function fileUndeleted( &$title, $versions, &$user, $reason ) {
99 - self::setLocalFlag( $title->getDBkey(), 1 );
100 - return true;
101 - }
102 - static function imageUploaded( $uploadForm ) {
103 - $imageName = $uploadForm->mLocalFile->getTitle()->getDBkey();
104 - self::setLocalFlag( $imageName, 1 );
105 - return true;
106 - }
107118
108 -
109 - static function articleDeleted( &$article, &$user, $reason ) {
110 - $dbw = self::getDatabase();
111 - $dbw->immediateBegin();
112 - $dbw->delete( 'globalimagelinks', array(
113 - 'gil_wiki' => self::getLocalInterwiki(),
114 - // Use GAID_FOR_UPDATE to make sure the old id is fetched from
115 - // the link cache
116 - 'gil_page' => $article->getTitle()->getArticleId(GAID_FOR_UPDATE)),
117 - __METHOD__ );
118 - $dbw->immediateCommit();
119 -
120 - return true;
121 - }
122119
123 - static function articleMoved( &$movePageForm, &$from, &$to ) {
124 - $dbw = self::getDatabase();
125 - $dbw->immediateBegin();
126 - $dbw->update( 'globalimagelinks', array(
127 - 'gil_page_namespace' => $to->getNsText(),
128 - 'gil_page_title' => $to->getDBkey()
129 - ), array(
130 - 'gil_wiki' => self::getLocalInterwiki(),
131 - 'gil_page' => $to->getArticleId()
132 - ), __METHOD__ );
133 - $dbw->immediateCommit();
134 -
135 - return true;
136 - }
137120
138 - public function execute( $par ) {
139 - global $wgOut, $wgScript, $wgRequest;
140 -
141 - $this->setHeaders();
142 -
143 - $self = Title::makeTitle( NS_SPECIAL, 'GlobalUsage' );
144 - $target= Title::makeTitleSafe( NS_IMAGE, $wgRequest->getText( 'target', $par ) );
145 -
146 - $wgOut->addWikiText( wfMsg( 'globalusage-text' ) );
147 -
148 - $form = Xml::openElement( 'form', array(
149 - 'id' => 'mw-globalusage-form',
150 - 'method' => 'get',
151 - 'action' => $wgScript ));
152 - $form .= Xml::hidden( 'title', $self->getPrefixedDbKey() );
153 - $form .= Xml::openElement( 'fieldset' );
154 - $form .= Xml::element( 'legend', array(), wfMsg( 'globalusage' ));
155 - $form .= Xml::inputLabel( wfMsg( 'filename' ), 'target',
156 - 'target', 50, $target->getDBkey() );
157 - $form .= Xml::submitButton( wfMsg( 'globalusage-ok' ) );
158 - $form .= Xml::closeElement( 'fieldset' );
159 - $form .= Xml::closeElement( 'form' );
160 -
161 - $wgOut->addHTML( $form );
162 -
163 - if ( !$target->getDBkey() ) return;
164 -
165 - $dbr = self::getDatabase( DB_SLAVE );
166 - $res = $dbr->select( 'globalimagelinks',
167 - array( 'gil_wiki', 'gil_page_namespace', 'gil_page_title' ),
168 - array( 'gil_to' => $target->getDBkey(), 'gil_is_local' => 0 ),
169 - __METHOD__ );
170 -
171 - // Quick dirty list output
172 - while ( $row = $dbr->fetchObject($res) )
173 - $wgOut->addWikiText(self::formatItem( $row ) );
174 - $dbr->freeResult($res);
175 - }
176 -
177 - public static function formatItem( $row ) {
178 - $out = '* [[';
179 - if ( self::getLocalInterwiki() != $row->gil_wiki )
180 - $out .= ':'.$row->gil_wiki;
181 - if ( $row->gil_page_namespace )
182 - $out .= ':'.str_replace('_', ' ', $row->gil_page_namespace);
183 - $out .= ':'.str_replace('_', ' ', $row->gil_page_title)."]]\n";
184 - return $out;
185 - }
 121+
186122 }
Index: trunk/extensions/GlobalUsage/GlobalUsage.i18n.php
@@ -12,6 +12,7 @@
1313 */
1414 $messages['en'] = array(
1515 'globalusage' => 'Global file usage',
 16+ 'globalusage-for' => 'Global usage for "$1"',
1617 'globalusage-desc' => '[[Special:GlobalUsage|Special page]] to view global file usage',
1718 'globalusage-ok' => 'Search',
1819 'globalusage-text' => 'Search global file usage.'
Index: trunk/extensions/GlobalUsage/GlobalUsage.php
@@ -1,6 +1,6 @@
22 <?php
33 /*
4 - Copyright (c) 2008 Bryan Tong Minh
 4+ Copyright (c) 2008 - 2009 Bryan Tong Minh
55
66 Permission is hereby granted, free of charge, to any person
77 obtaining a copy of this software and associated documentation
@@ -33,37 +33,40 @@
3434 exit( 1 );
3535 }
3636
37 -// Defines
38 -define('GUIW_LOCAL', 0);
39 -define('GUIW_SERVER', 1);
4037
41 -if (isset($_SERVER) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
42 - $dir = dirname(__FILE__) . '/';
 38+$dir = dirname(__FILE__) . '/';
4339
44 - $wgExtensionCredits['specialpage'][] = array(
45 - 'path' => __FILE__,
46 - 'name' => 'Global Usage',
47 - 'author' => 'Bryan Tong Minh',
48 - 'description' => 'Special page to view global file usage',
49 - 'descriptionmsg' => 'globalusage-desc',
50 - 'url' => 'http://www.mediawiki.org/wiki/Extension:GlobalUsage',
51 - 'version' => '1.1',
52 - );
 40+$wgExtensionCredits['specialpage'][] = array(
 41+ 'path' => __FILE__,
 42+ 'name' => 'Global Usage',
 43+ 'author' => 'Bryan Tong Minh',
 44+ 'description' => 'Special page to view global file usage',
 45+ 'descriptionmsg' => 'globalusage-desc',
 46+ 'url' => 'http://www.mediawiki.org/wiki/Extension:GlobalUsage',
 47+ 'version' => '2.0',
 48+);
5349
54 - $wgExtensionMessagesFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
55 - $wgAutoloadClasses['GlobalUsage'] = $dir . 'GlobalUsage_body.php';
56 - $wgExtensionMessageFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
57 - $wgExtensionAliasesFiles['GlobalUsage'] = $dir . 'GlobalUsage.alias.php';
58 - $wgSpecialPages['GlobalUsage'] = 'GlobalUsage';
 50+$wgExtensionMessagesFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
 51+$wgAutoloadClasses['GlobalUsage'] = $dir . 'GlobalUsage_body.php';
 52+$wgAutoloadClasses['GlobalUsageHooks'] = $dir . 'GlobalUsageHooks.php';
 53+$wgAutoloadClasses['SpecialGlobalUsage'] = $dir . 'SpecialGlobalUsage.php';
 54+$wgExtensionMessageFiles['GlobalUsage'] = $dir . 'GlobalUsage.i18n.php';
 55+$wgExtensionAliasesFiles['GlobalUsage'] = $dir . 'GlobalUsage.alias.php';
 56+$wgSpecialPages['GlobalUsage'] = 'SpecialGlobalUsage';
5957
60 - $wgHooks['LinksUpdate'][] = array( 'GlobalUsage', 'updateLinks' );
61 - $wgHooks['ArticleDeleteComplete'][] = array( 'GlobalUsage', 'articleDeleted' );
62 - $wgHooks['FileDeleteComplete'][] = array( 'GlobalUsage', 'fileDeleted' );
63 - $wgHooks['FileUndeleteComplete'][] = array( 'GlobalUsage', 'fileUndeleted' );
64 - $wgHooks['UploadComplete'][] = array( 'GlobalUsage', 'imageUploaded' );
65 - $wgHooks['SpecialMovepageAfterMove'][] = array( 'GlobalUsage', 'articleMoved' );
66 -}
 58+/* Things that can cause link updates:
 59+ * - Local LinksUpdate
 60+ * - Local article deletion (remove from table)
 61+ * - Local article move (update page title)
 62+ * - Local file upload/deletion/move (toggle is_local flag)
 63+ */
 64+$wgHooks['LinksUpdateComplete'][] = 'GlobalUsageHooks::onLinksUpdate';
 65+$wgHooks['ArticleDelete'][] = 'GlobalUsageHooks::onArticleDelete';
 66+$wgHooks['FileUndeleteComplete'][] = 'GlobalUsageHooks::onFileUndelete';
 67+$wgHooks['UploadComplete'][] = 'GlobalUsageHooks::onUpload';
 68+$wgHooks['TitleMoveComplete'][] = 'GlobalUsageHooks::onTitleMove';
6769
 70+
6871 // If set to false, the local database contains the globalimagelinks table
6972 // Else set to something understandable to LBFactory
70 -$wgguMasterDatabase = false;
 73+$wgGlobalUsageDatabase = false;
Index: trunk/extensions/GlobalUsage/readme.txt
@@ -11,21 +11,4 @@
1212 may use different namespaces, the namespace name needs to be included in the
1313 link as well.
1414
15 -There should be one globalimagelinks table per farm, even if multiple shared
16 -image repositories are used. The field gil_is_local indicates whether the file
17 -exists locally.
1815
19 -== GlobalUsageDaemon and populating the table ==
20 -This extension provides a daemon which can be used when for some reason hooks
21 -can not be used, like on the Toolserver. This daemon will readout recentchanges
22 -to fill the globalimagelinks table.
23 -
24 -This daemon is also useful for populating the globalimagelinks table. Using
25 -this method it is possible to get an as consistent as posible database. To do
26 -so first create the table on the master wiki. Then, on each project separately:
27 -* Run extensions/GlobalUsage/populateGlobalUsage.php on that wiki. See
28 - php extensions/GlobalUsage/populateGlobalUsage.php --help for information.
29 -* When the daemon has finished populating the table from the local imagelinks
30 - and started to follow recentchanges, enable the extension.
31 -* When the daemon has reached the time at which the extension was enabled,
32 - the daemon can be stopped.
\ No newline at end of file
Index: trunk/extensions/GlobalUsage/SpecialGlobalUsage.php
@@ -0,0 +1,101 @@
 2+<?php
 3+/**
 4+ * Crappy ui towards globalimagelinks
 5+ */
 6+
 7+class SpecialGlobalUsage extends SpecialPage {
 8+ public function __construct() {
 9+ parent::__construct( 'GlobalUsage', 'globalusage' );
 10+
 11+ wfLoadExtensionMessages( 'globalusage' );
 12+ }
 13+
 14+ public function execute( $par ) {
 15+ global $wgOut, $wgRequest;
 16+
 17+ $target = $par ? $par : $wgRequest->getVal( 'target' );
 18+ $title = Title::newFromText( $target, NS_FILE );
 19+
 20+ $this->setHeaders();
 21+
 22+ if ( is_null( $title ) )
 23+ {
 24+ $wgOut->setPageTitle( wfMsg( 'globalusage' ) );
 25+ return;
 26+ }
 27+
 28+ $wgOut->setPageTitle( wfMsg( 'globalusage-for', $title->getPrefixedText() ) );
 29+
 30+ $pager = new GlobalUsagePager( $title );
 31+
 32+ $wgOut->addHTML(
 33+ '<p>' . $pager->getNavigationBar() . '</p>' .
 34+ '<ul>' . $pager->getBody() . '</ul>' .
 35+ '<p>' . $pager->getNavigationBar() . '</p>' );
 36+ }
 37+}
 38+
 39+/**
 40+ * Pager for globalimagelinks.
 41+ */
 42+class GlobalUsagePager extends IndexPager {
 43+ public function __construct( $title = null ) {
 44+ // Initialize parent first
 45+ parent::__construct();
 46+
 47+ $this->title = $title;
 48+
 49+ // Override the DB
 50+ global $wgGlobalUsageDatabase;
 51+ $this->mDb = wfGetDB( DB_SLAVE, array(), $wgGlobalUsageDatabase );
 52+ }
 53+ public function formatRow( $row ) {
 54+ return '<li>'
 55+ . htmlspecialchars( $row->gil_wiki ) . ':'
 56+ . htmlspecialchars( $row->gil_page_namespace ) . ':'
 57+ . htmlspecialchars( $row->gil_page_title )
 58+ . "</li>\n";
 59+ }
 60+ public function getQueryInfo() {
 61+ $info = array(
 62+ 'tables' => array( 'globalimagelinks' ),
 63+ 'fields' => array(
 64+ 'gil_wiki',
 65+ 'gil_page_namespace',
 66+ 'gil_page_title',
 67+ ),
 68+ );
 69+ if ( !is_null( $this->title ) && $this->title->getNamespace() == NS_FILE ) {
 70+ $info['conds'] = array( 'gil_to' => $this->title->getDBkey() );
 71+ }
 72+ return $info;
 73+ }
 74+ public function getIndexField() {
 75+ // FIXME: This is non-unique! Needs a hack in IndexPager to sort on (wiki, page)
 76+ return 'gil_wiki';
 77+ }
 78+
 79+
 80+ public function getNavigationBar() {
 81+ global $wgLang;
 82+
 83+ if ( isset( $this->mNavigationBar ) ) {
 84+ return $this->mNavigationBar;
 85+ }
 86+ $fmtLimit = $wgLang->formatNum( $this->mLimit );
 87+ $linkTexts = array(
 88+ 'prev' => wfMsgExt( 'whatlinkshere-prev', array( 'escape', 'parsemag' ), $fmtLimit ),
 89+ 'next' => wfMsgExt( 'whatlinkshere-next', array( 'escape', 'parsemag' ), $fmtLimit ),
 90+ 'first' => wfMsgHtml( 'page_first' ),
 91+ 'last' => wfMsgHtml( 'page_last' )
 92+ );
 93+
 94+ $pagingLinks = $this->getPagingLinks( $linkTexts );
 95+ $limitLinks = $this->getLimitLinks();
 96+ $limits = $wgLang->pipeList( $limitLinks );
 97+
 98+ $this->mNavigationBar = "(" . $wgLang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
 99+ wfMsgExt( 'viewprevnext', array( 'parsemag', 'escape', 'replaceafter' ), $pagingLinks['prev'], $pagingLinks['next'], $limits );
 100+ return $this->mNavigationBar;
 101+ }
 102+}
\ No newline at end of file
Property changes on: trunk/extensions/GlobalUsage/SpecialGlobalUsage.php
___________________________________________________________________
Name: svn:eol-style
1103 + native

Follow-up revisions

RevisionCommit summaryAuthorDate
r59762Per comments on r56201:...btongminh21:59, 5 December 2009
r112687Bug 34795 - commonswiki.globalimagelinks.gil_page_title contains page titles ...reedy14:20, 29 February 2012

Comments

#Comment by Tim Starling (talk | contribs)   04:59, 1 December 2009

$this->target = Title::newFromText( $target, NS_FILE );

That is the wrong function, Title::newFromText() accepts a *default* namespace parameter which is overridable by passing a string with colons. Use Title::makeTitleSafe() in cases where you want the namespace to be fixed.

getImagePageQuery() contains a cache with unbounded size. This may lead to an OOM in extensions such as DumpHTML. Please limit the cache size to 100 items or so. I would recommend using a class-scope static variable for the cache instead of a function-scope variable since they tend to be more flexible.

Good work in general, well done.

#Comment by Bryan (talk | contribs)   10:57, 1 December 2009

makeTitleSafe automatically adds the File: prefix even if the input string already starts with File:. I would expect that users will sometimes enter the namespace prefixed title in the search box. Should I just ignore that?

#Comment by Tim Starling (talk | contribs)   22:38, 1 December 2009

I suppose it would be OK to use it for that purpose.

#Comment by Bryan (talk | contribs)   21:59, 5 December 2009

Cache and title construction fixed in r59762.

Status & tagging log