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 |
1 | 80 | + native |
Index: trunk/extensions/GlobalUsage/GlobalUsage.sql |
— | — | @@ -9,16 +9,12 @@ |
10 | 10 | gil_page_title varchar(255) not null, |
11 | 11 | -- Image name |
12 | 12 | gil_to varchar(255) not null, |
13 | | - -- Exists locally |
14 | | - gil_is_local tinyint(1) not null, |
| 13 | + |
15 | 14 | |
16 | 15 | -- Note: You might want to shorten the gil_wiki part of the indices. |
17 | 16 | -- If the domain format is used, only the "en.wikip" part is needed for an |
18 | 17 | -- unique lookup |
19 | 18 | |
20 | 19 | 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 @@ |
5 | 5 | gil_page_namespace TEXT NOT NULL, |
6 | 6 | gil_page_title TEXT NOT NULL, |
7 | 7 | gil_to TEXT NOT NULL, |
8 | | - gil_is_local SMALLINT NOT NULL, |
9 | 8 | PRIMARY KEY (gil_wiki, gil_page) |
10 | 9 | ); |
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 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | | -class GlobalUsage extends SpecialPage { |
5 | | - private static $database = array(); |
6 | | - private static $interwiki = null; |
| 4 | +class GlobalUsage { |
| 5 | + private $interwiki; |
| 6 | + private $db; |
7 | 7 | |
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; |
11 | 17 | } |
12 | 18 | |
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 | + ); |
32 | 35 | } |
33 | | - return self::$interwiki; |
| 36 | + $this->db->insert( 'globalimagelinks', $insert, __METHOD__ ); |
34 | 37 | } |
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 | + ); |
46 | 52 | } |
| 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 | + } |
47 | 68 | |
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. |
52 | 73 | */ |
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; |
60 | 76 | |
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 | + ); |
70 | 93 | } |
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__ ); |
77 | 95 | } |
78 | 96 | |
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 | + ); |
89 | 116 | } |
90 | 117 | |
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 | | - } |
107 | 118 | |
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 | | - } |
122 | 119 | |
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 | | - } |
137 | 120 | |
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 | + |
186 | 122 | } |
Index: trunk/extensions/GlobalUsage/GlobalUsage.i18n.php |
— | — | @@ -12,6 +12,7 @@ |
13 | 13 | */ |
14 | 14 | $messages['en'] = array( |
15 | 15 | 'globalusage' => 'Global file usage', |
| 16 | + 'globalusage-for' => 'Global usage for "$1"', |
16 | 17 | 'globalusage-desc' => '[[Special:GlobalUsage|Special page]] to view global file usage', |
17 | 18 | 'globalusage-ok' => 'Search', |
18 | 19 | 'globalusage-text' => 'Search global file usage.' |
Index: trunk/extensions/GlobalUsage/GlobalUsage.php |
— | — | @@ -1,6 +1,6 @@ |
2 | 2 | <?php |
3 | 3 | /* |
4 | | - Copyright (c) 2008 Bryan Tong Minh |
| 4 | + Copyright (c) 2008 - 2009 Bryan Tong Minh |
5 | 5 | |
6 | 6 | Permission is hereby granted, free of charge, to any person |
7 | 7 | obtaining a copy of this software and associated documentation |
— | — | @@ -33,37 +33,40 @@ |
34 | 34 | exit( 1 ); |
35 | 35 | } |
36 | 36 | |
37 | | -// Defines |
38 | | -define('GUIW_LOCAL', 0); |
39 | | -define('GUIW_SERVER', 1); |
40 | 37 | |
41 | | -if (isset($_SERVER) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) { |
42 | | - $dir = dirname(__FILE__) . '/'; |
| 38 | +$dir = dirname(__FILE__) . '/'; |
43 | 39 | |
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 | +); |
53 | 49 | |
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'; |
59 | 57 | |
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'; |
67 | 69 | |
| 70 | + |
68 | 71 | // If set to false, the local database contains the globalimagelinks table |
69 | 72 | // Else set to something understandable to LBFactory |
70 | | -$wgguMasterDatabase = false; |
| 73 | +$wgGlobalUsageDatabase = false; |
Index: trunk/extensions/GlobalUsage/readme.txt |
— | — | @@ -11,21 +11,4 @@ |
12 | 12 | may use different namespaces, the namespace name needs to be included in the |
13 | 13 | link as well. |
14 | 14 | |
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. |
18 | 15 | |
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 |
1 | 103 | + native |