ported $wgAntiLockFlags from REL1_4
[lhc/web/wiklou.git] / includes / LinkCache.php
1 <?php
2 /**
3 * Cache for article titles (prefixed DB keys) and ids linked from one source
4 * @package MediaWiki
5 * @subpackage Cache
6 */
7
8 /**
9 *
10 */
11 # These are used in incrementalSetup()
12 define ('LINKCACHE_GOOD', 0);
13 define ('LINKCACHE_BAD', 1);
14 define ('LINKCACHE_IMAGE', 2);
15 define ('LINKCACHE_PAGE', 3);
16
17 /**
18 * @package MediaWiki
19 * @subpackage Cache
20 */
21 class LinkCache {
22 // Increment $mClassVer whenever old serialized versions of this class
23 // becomes incompatible with the new version.
24 /* private */ var $mClassVer = 3;
25
26 /* private */ var $mPageLinks;
27 /* private */ var $mGoodLinks, $mBadLinks, $mActive;
28 /* private */ var $mImageLinks, $mCategoryLinks;
29 /* private */ var $mPreFilled, $mOldGoodLinks, $mOldBadLinks;
30 /* private */ var $mForUpdate;
31
32 /* private */ function getKey( $title ) {
33 global $wgDBname;
34 return $wgDBname.':lc:title:'.$title;
35 }
36
37 function LinkCache() {
38 $this->mActive = true;
39 $this->mPreFilled = false;
40 $this->mForUpdate = false;
41 $this->mPageLinks = array();
42 $this->mGoodLinks = array();
43 $this->mBadLinks = array();
44 $this->mImageLinks = array();
45 $this->mCategoryLinks = array();
46 $this->mOldGoodLinks = array();
47 $this->mOldBadLinks = array();
48 }
49
50 /**
51 * General accessor to get/set whether SELECT FOR UPDATE should be used
52 */
53 function forUpdate( $update = NULL ) {
54 return wfSetVar( $this->mForUpdate, $update );
55 }
56
57 function getGoodLinkID( $title ) {
58 if ( array_key_exists( $title, $this->mGoodLinks ) ) {
59 return $this->mGoodLinks[$title];
60 } else {
61 return 0;
62 }
63 }
64
65 function isBadLink( $title ) {
66 return array_key_exists( $title, $this->mBadLinks );
67 }
68
69 function addGoodLinkObj( $id, $title ) {
70 if ( $this->mActive ) {
71 $dbkey = $title->getPrefixedDbKey();
72 $this->mGoodLinks[$dbkey] = $id;
73 $this->mPageLinks[$dbkey] = $title;
74 }
75 }
76
77 function addBadLinkObj( $title ) {
78 $dbkey = $title->getPrefixedDbKey();
79 if ( $this->mActive && ( ! $this->isBadLink( $dbkey ) ) ) {
80 $this->mBadLinks[$dbkey] = 1;
81 $this->mPageLinks[$dbkey] = $title;
82 }
83 }
84
85 function addImageLink( $title ) {
86 if ( $this->mActive ) { $this->mImageLinks[$title] = 1; }
87 }
88
89 function addImageLinkObj( $nt ) {
90 if ( $this->mActive ) { $this->mImageLinks[$nt->getDBkey()] = 1; }
91 }
92
93 function addCategoryLink( $title, $sortkey ) {
94 if ( $this->mActive ) { $this->mCategoryLinks[$title] = $sortkey; }
95 }
96
97 function addCategoryLinkObj( &$nt, $sortkey ) {
98 $this->addCategoryLink( $nt->getDBkey(), $sortkey );
99 }
100
101 function clearBadLink( $title ) {
102 unset( $this->mBadLinks[$title] );
103 $this->clearLink( $title );
104 }
105
106 function clearLink( $title ) {
107 global $wgMemc, $wgLinkCacheMemcached;
108 if( $wgLinkCacheMemcached )
109 $wgMemc->delete( $this->getKey( $title ) );
110 }
111
112 function suspend() { $this->mActive = false; }
113 function resume() { $this->mActive = true; }
114 function getPageLinks() { return $this->mPageLinks; }
115 function getGoodLinks() { return $this->mGoodLinks; }
116 function getBadLinks() { return array_keys( $this->mBadLinks ); }
117 function getImageLinks() { return $this->mImageLinks; }
118 function getCategoryLinks() { return $this->mCategoryLinks; }
119
120 function addLink( $title ) {
121 $nt = Title::newFromDBkey( $title );
122 if( $nt ) {
123 return $this->addLinkObj( $nt );
124 } else {
125 return 0;
126 }
127 }
128
129 function addLinkObj( &$nt ) {
130 global $wgMemc, $wgLinkCacheMemcached, $wgAntiLockFlags;
131 $title = $nt->getPrefixedDBkey();
132 if ( $this->isBadLink( $title ) ) { return 0; }
133 $id = $this->getGoodLinkID( $title );
134 if ( 0 != $id ) { return $id; }
135
136 $fname = 'LinkCache::addLinkObj';
137 wfProfileIn( $fname );
138
139 $ns = $nt->getNamespace();
140 $t = $nt->getDBkey();
141
142 if ( '' == $title ) {
143 wfProfileOut( $fname );
144 return 0;
145 }
146
147 $id = NULL;
148 if( $wgLinkCacheMemcached )
149 $id = $wgMemc->get( $key = $this->getKey( $title ) );
150 if( ! is_integer( $id ) ) {
151 if ( $this->mForUpdate ) {
152 $db =& wfGetDB( DB_MASTER );
153 if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
154 $options = array( 'FOR UPDATE' );
155 }
156 } else {
157 $db =& wfGetDB( DB_SLAVE );
158 $options = array();
159 }
160
161 $id = $db->selectField( 'page', 'page_id', array( 'page_namespace' => $ns, 'page_title' => $t ), $fname, $options );
162 if ( !$id ) {
163 $id = 0;
164 }
165 if( $wgLinkCacheMemcached )
166 $wgMemc->add( $key, $id, 3600*24 );
167 }
168
169 if( 0 == $id ) {
170 $this->addBadLinkObj( $nt );
171 } else {
172 $this->addGoodLinkObj( $id, $nt );
173 }
174 wfProfileOut( $fname );
175 return $id;
176 }
177
178 /**
179 * Bulk-check the pagelinks and page arrays for existence info.
180 * @param Title $fromtitle
181 */
182 function preFill( &$fromtitle ) {
183 global $wgEnablePersistentLC;
184
185 $fname = 'LinkCache::preFill';
186 wfProfileIn( $fname );
187
188 $this->suspend();
189 $id = $fromtitle->getArticleID();
190 $this->resume();
191
192 if( $id == 0 ) {
193 wfDebug( "$fname - got id 0 for title '" . $fromtitle->getPrefixedDBkey() . "'\n" );
194 wfProfileOut( $fname );
195 return;
196 }
197
198 if ( $wgEnablePersistentLC ) {
199 if( $this->fillFromLinkscc( $id ) ){
200 wfProfileOut( $fname );
201 return;
202 }
203 }
204 if ( $this->mForUpdate ) {
205 $db =& wfGetDB( DB_MASTER );
206 $options = 'FOR UPDATE';
207 } else {
208 $db =& wfGetDB( DB_SLAVE );
209 $options = '';
210 }
211
212 $page = $db->tableName( 'page' );
213 $pagelinks = $db->tableName( 'pagelinks' );
214
215 $sql = "SELECT page_id,pl_namespace,pl_title
216 FROM $pagelinks
217 LEFT JOIN $page
218 ON pl_namespace=page_namespace AND pl_title=page_title
219 WHERE pl_from=$id $options";
220 $res = $db->query( $sql, $fname );
221 while( $s = $db->fetchObject( $res ) ) {
222 $title = Title::makeTitle( $s->pl_namespace, $s->pl_title );
223 if( $s->page_id ) {
224 $this->addGoodLinkObj( $s->page_id, $title );
225 } else {
226 $this->addBadLinkObj( $title );
227 }
228 }
229 $this->mOldPageLinks = $this->mPageLinks;
230 $this->mOldBadLinks = $this->mBadLinks;
231 $this->mOldGoodLinks = $this->mGoodLinks;
232 $this->mPreFilled = true;
233
234 if ( $wgEnablePersistentLC ) {
235 $this->saveToLinkscc( $id );
236 }
237 wfProfileOut( $fname );
238 }
239
240 function getGoodAdditions() {
241 return array_diff( $this->mGoodLinks, $this->mOldGoodLinks );
242 }
243
244 function getBadAdditions() {
245 #wfDebug( "mOldBadLinks: " . implode( ', ', array_keys( $this->mOldBadLinks ) ) . "\n" );
246 #wfDebug( "mBadLinks: " . implode( ', ', array_keys( $this->mBadLinks ) ) . "\n" );
247 return array_values( array_diff( array_keys( $this->mBadLinks ), array_keys( $this->mOldBadLinks ) ) );
248 }
249
250 function getImageAdditions() {
251 return array_diff_assoc( $this->mImageLinks, $this->mOldImageLinks );
252 }
253
254 function getGoodDeletions() {
255 return array_diff( $this->mOldGoodLinks, $this->mGoodLinks );
256 }
257
258 function getBadDeletions() {
259 return array_values( array_diff( array_keys( $this->mOldBadLinks ), array_keys( $this->mBadLinks ) ));
260 }
261
262 function getImageDeletions() {
263 return array_diff_assoc( $this->mOldImageLinks, $this->mImageLinks );
264 }
265
266 function getPageAdditions() {
267 $set = array_diff( array_keys( $this->mPageLinks ), array_keys( $this->mOldPageLinks ) );
268 $out = array();
269 foreach( $set as $key ) {
270 $out[$key] = $this->mPageLinks[$key];
271 }
272 return $out;
273 }
274
275 function getPageDeletions() {
276 $set = array_diff( array_keys( $this->mOldPageLinks ), array_keys( $this->mPageLinks ) );
277 $out = array();
278 foreach( $set as $key ) {
279 $out[$key] = $this->mOldPageLinks[$key];
280 }
281 return $out;
282 }
283
284 /**
285 * Parameters:
286 * @param $which is one of the LINKCACHE_xxx constants
287 * @param $del,$add are the incremental update arrays which will be filled.
288 *
289 * @return Returns whether or not it's worth doing the incremental version.
290 *
291 * For example, if [[List of mathematical topics]] was blanked,
292 * it would take a long, long time to do incrementally.
293 */
294 function incrementalSetup( $which, &$del, &$add ) {
295 if ( ! $this->mPreFilled ) {
296 return false;
297 }
298
299 switch ( $which ) {
300 case LINKCACHE_GOOD:
301 $old =& $this->mOldGoodLinks;
302 $cur =& $this->mGoodLinks;
303 $del = $this->getGoodDeletions();
304 $add = $this->getGoodAdditions();
305 break;
306 case LINKCACHE_BAD:
307 $old =& $this->mOldBadLinks;
308 $cur =& $this->mBadLinks;
309 $del = $this->getBadDeletions();
310 $add = $this->getBadAdditions();
311 break;
312 case LINKCACHE_PAGE:
313 $old =& $this->mOldPageLinks;
314 $cur =& $this->mPageLinks;
315 $del = $this->getPageDeletions();
316 $add = $this->getPageAdditions();
317 break;
318 default: # LINKCACHE_IMAGE
319 return false;
320 }
321
322 return true;
323 }
324
325 /**
326 * Clears cache but leaves old preFill copies alone
327 */
328 function clear() {
329 $this->mPageLinks = array();
330 $this->mGoodLinks = array();
331 $this->mBadLinks = array();
332 $this->mImageLinks = array();
333 }
334
335 /**
336 * @access private
337 */
338 function fillFromLinkscc( $id ){
339 $fname = 'LinkCache::fillFromLinkscc';
340
341 $id = IntVal( $id );
342 if ( $this->mForUpdate ) {
343 $db =& wfGetDB( DB_MASTER );
344 $options = 'FOR UPDATE';
345 } else {
346 $db =& wfGetDB( DB_SLAVE );
347 $options = '';
348 }
349 $raw = $db->selectField( 'linkscc', 'lcc_cacheobj', array( 'lcc_pageid' => $id ), $fname, $options );
350 if ( $raw === false ) {
351 return false;
352 }
353
354 $cacheobj = false;
355 if( function_exists( 'gzuncompress' ) )
356 $cacheobj = @gzuncompress( $raw );
357
358 if($cacheobj == FALSE){
359 $cacheobj = $raw;
360 }
361 $cc = @unserialize( $cacheobj );
362 if( isset( $cc->mClassVer ) and ($cc->mClassVer == $this->mClassVer ) ){
363 $this->mOldPageLinks = $this->mPageLinks = $cc->mPageLinks;
364 $this->mOldGoodLinks = $this->mGoodLinks = $cc->mGoodLinks;
365 $this->mOldBadLinks = $this->mBadLinks = $cc->mBadLinks;
366 $this->mPreFilled = true;
367 return TRUE;
368 } else {
369 return FALSE;
370 }
371
372 }
373
374 /**
375 * @access private
376 */
377 function saveToLinkscc( $pid ){
378 global $wgCompressedPersistentLC;
379 if( $wgCompressedPersistentLC and function_exists( 'gzcompress' ) ) {
380 $ser = gzcompress( serialize( $this ), 3 );
381 } else {
382 $ser = serialize( $this );
383 }
384 $db =& wfGetDB( DB_MASTER );
385 $db->replace( 'linkscc', array( 'lcc_pageid' ), array( 'lcc_pageid' => $pid, 'lcc_cacheobj' => $ser ) );
386 }
387
388 /**
389 * Delete linkscc rows which link to here
390 * @param $title The page linked to
391 * @static
392 */
393 function linksccClearLinksTo( $title ){
394 global $wgEnablePersistentLC;
395 if ( $wgEnablePersistentLC ) {
396 $fname = 'LinkCache::linksccClearLinksTo';
397 $pid = intval( $pid );
398 $dbw =& wfGetDB( DB_MASTER );
399 # Delete linkscc rows which link to here
400 $dbw->deleteJoin( 'linkscc', 'pagelinks', 'lcc_pageid', 'pl_from',
401 array(
402 'pl_namespace' => $title->getNamespace(),
403 'pl-title' => $title->getDbKey() ),
404 $fname );
405 # Delete linkscc row representing this page
406 $dbw->delete( 'linkscc', array( 'lcc_pageid' => $pid ), $fname);
407 }
408
409 }
410
411 /**
412 * @param $pid is a page id
413 * @static
414 */
415 function linksccClearPage( $pid ){
416 global $wgEnablePersistentLC;
417 if ( $wgEnablePersistentLC ) {
418 $pid = intval( $pid );
419 $dbw =& wfGetDB( DB_MASTER );
420 $dbw->delete( 'linkscc', array( 'lcc_pageid' => $pid ) );
421 }
422 }
423 }
424
425 /**
426 * Class representing a list of titles
427 * The execute() method checks them all for existence and adds them to a LinkCache object
428 +
429 * @package MediaWikki
430 * @subpackage Cache
431 */
432 class LinkBatch {
433 /**
434 * 2-d array, first index namespace, second index dbkey, value arbitrary
435 */
436 var $data = array();
437
438 function LinkBatch( $arr = array() ) {
439 foreach( $arr as $item ) {
440 $this->addObj( $item );
441 }
442 }
443
444 function addObj( $title ) {
445 $this->add( $title->getNamespace(), $title->getDBkey() );
446 }
447
448 function add( $ns, $dbkey ) {
449 if ( $ns < 0 ) {
450 return;
451 }
452 if ( !array_key_exists( $ns, $this->data ) ) {
453 $this->data[$ns] = array();
454 }
455
456 $this->data[$ns][$dbkey] = 1;
457 }
458
459 function execute( &$cache ) {
460 $fname = 'LinkBatch::execute';
461 $namespaces = array();
462
463 if ( !count( $this->data ) ) {
464 return;
465 }
466
467 wfProfileIn( $fname );
468
469 // Construct query
470 // This is very similar to Parser::replaceLinkHolders
471 $dbr = wfGetDB( DB_SLAVE );
472 $page = $dbr->tableName( 'page' );
473 $sql = "SELECT page_id, page_namespace, page_title FROM $page WHERE "
474 . $this->constructSet( 'page', $dbr );
475
476 // Do query
477 $res = $dbr->query( $sql, $fname );
478
479 // Process results
480 // For each returned entry, add it to the list of good links, and remove it from $remaining
481
482 $remaining = $this->data;
483 while ( $row = $dbr->fetchObject( $res ) ) {
484 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
485 $cache->addGoodLinkObj( $row->page_id, $title );
486 unset( $remaining[$row->page_namespace][$row->page_title] );
487 }
488 $dbr->freeResult( $res );
489
490 // The remaining links in $data are bad links, register them as such
491 foreach ( $remaining as $ns => $dbkeys ) {
492 foreach ( $dbkeys as $dbkey => $nothing ) {
493 $title = Title::makeTitle( $ns, $dbkey );
494 $cache->addBadLinkObj( $title );
495 }
496 }
497
498 wfProfileOut( $fname );
499 }
500
501 /**
502 * Construct a WHERE clause which will match all the given titles.
503 * Give the appropriate table's field name prefix ('page', 'pl', etc).
504 *
505 * @param string $prefix
506 * @return string
507 * @access public
508 */
509 function constructSet( $prefix, $db ) {
510 $first = true;
511 $sql = '';
512 foreach ( $this->data as $ns => $dbkeys ) {
513 if ( !count( $dbkeys ) ) {
514 continue;
515 }
516
517 if ( $first ) {
518 $first = false;
519 } else {
520 $sql .= ' OR ';
521 }
522 $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN (";
523
524 $firstTitle = true;
525 foreach( $dbkeys as $dbkey => $nothing ) {
526 if ( $firstTitle ) {
527 $firstTitle = false;
528 } else {
529 $sql .= ',';
530 }
531 $sql .= $db->addQuotes( $dbkey );
532 }
533
534 $sql .= '))';
535 }
536 return $sql;
537 }
538 }
539
540 ?>