16 var $mId, # Page ID of the article linked from
17 $mTitle, # Title object of the article linked from
18 $mParserOutput, # Parser output containing the links to be inserted into the database
19 $mLinks, # Map of title strings to IDs for the links in the document
20 $mImages, # DB keys of the images used, in the array key only
21 $mTemplates, # Map of title strings to IDs for the template references, including broken ones
22 $mExternals, # URLs of external links, array key only
23 $mCategories, # Map of category names to sort keys
24 $mDb, # Database connection reference
25 $mOptions; # SELECT options to be used (array)
30 * Initialize private variables
32 * @param string $title
34 function LinksUpdate( $title, $parserOutput ) {
35 global $wgAntiLockFlags;
37 if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK
) {
38 $this->mOptions
= array();
40 $this->mOptions
= array( 'FOR UPDATE' );
42 $this->mDb
=& wfGetDB( DB_MASTER
);
44 if ( !is_object( $title ) ) {
45 wfDebugDieBacktrace( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
46 "Please see Article::editUpdates() for an invocation example.\n" );
48 $this->mTitle
= $title;
49 $this->mId
= $title->getArticleID();
50 $this->mParserOutput
= $parserOutput;
53 $this->mLinks
=& $this->mParserOutput
->getLinks();
54 $this->mImages
=& $this->mParserOutput
->getImages();
55 $this->mTemplates
=& $this->mParserOutput
->getTemplates();
56 $this->mExternals
=& $this->mParserOutput
->getExternalLinks();
57 $this->mCategories
=& $this->mParserOutput
->getCategories();
62 * Update link tables with outgoing links from an updated article
65 global $wgUseDumbLinkUpdate;
66 if ( $wgUseDumbLinkUpdate ) {
67 $this->doDumbUpdate();
69 $this->doIncrementalUpdate();
73 function doIncrementalUpdate() {
74 $fname = 'LinksUpdate::doIncrementalUpdate';
75 wfProfileIn( $fname );
78 $existing = $this->getExistingLinks();
79 $this->incrTableUpdate( 'pagelinks', 'pl', $this->getLinkDeletions( $existing ),
80 $this->getLinkInsertions( $existing ) );
83 $existing = $this->getExistingTemplates();
84 $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
85 $this->getTemplateInsertions( $existing ) );
88 $existing = $this->getExistingImages();
89 $this->incrTableUpdate( 'imagelinks', 'il', $this->getImageDeletions( $existing ),
90 $this->getImageInsertions( $existing ) );
93 $existing = $this->getExistingExternals();
94 $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ),
95 $this->getExternalInsertions( $existing ) );
98 $existing = $this->getExistingCategories();
99 $this->incrTableUpdate( 'categorylinks', 'cl', $this->getCategoryDeletions( $existing ),
100 $this->getCategoryInsertions( $existing ) );
102 # I think this works out to a set XOR operation, the idea is to invalidate all
103 # categories which were added, deleted or changed
104 # FIXME: surely there's a more appropriate place to put this update?
105 $categoryUpdates = array_diff_assoc( $existing, $this->mCategories
) +
array_diff_assoc( $this->mCategories
, $existing );
106 $this->invalidateCategories( $categoryUpdates );
108 wfProfileOut( $fname );
112 * Link update which clears the previous entries and inserts new ones
113 * May be slower or faster depending on level of lock contention and write speed of DB
114 * Also useful where link table corruption needs to be repaired, e.g. in refreshLinks.php
116 function doDumbUpdate() {
117 $fname = 'LinksUpdate::doDumbUpdate';
118 wfProfileIn( $fname );
120 $existing = $this->getExistingCategories();
121 $categoryUpdates = array_diff_assoc( $existing, $this->mCategories
) +
array_diff_assoc( $this->mCategories
, $existing );
123 $this->dumbTableUpdate( 'pagelinks', $this->getLinkInsertions(), 'pl_from' );
124 $this->dumbTableUpdate( 'imagelinks', $this->getImageInsertions(), 'il_from' );
125 $this->dumbTableUpdate( 'categorylinks', $this->getCategoryInsertions(), 'cl_from' );
126 $this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' );
127 $this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' );
129 # Update the cache of all the category pages
130 $this->invalidateCategories( $categoryUpdates );
132 wfProfileOut( $fname );
135 function invalidateCategories( $cats ) {
136 $fname = 'LinksUpdate::invalidateCategories';
137 if ( count( $cats ) ) {
138 $this->mDb
->update( 'page', array( 'page_touched' => $this->mDb
->timestamp() ),
140 'page_namespace' => NS_CATEGORY
,
141 'page_title IN (' . $this->mDb
->makeList( array_keys( $cats ) ) . ')'
147 function dumbTableUpdate( $table, $insertions, $fromField ) {
148 $fname = 'LinksUpdate::dumbTableUpdate';
149 $this->mDb
->delete( $table, array( $fromField => $this->mId
), $fname );
150 if ( count( $insertions ) ) {
151 # The link array was constructed without FOR UPDATE, so there may be collisions
152 # Ignoring for now, I'm not sure if that causes problems or not, but I'm fairly
153 # sure it's better than without IGNORE
154 $this->mDb
->insert( $table, $insertions, $fname, array( 'IGNORE' ) );
159 * Make a WHERE clause from a 2-d NS/dbkey array
161 * @param array $arr 2-d array indexed by namespace and DB key
162 * @param string $prefix Field name prefix, without the underscore
164 function makeWhereFrom2d( &$arr, $prefix ) {
166 $lb->setArray( $arr );
167 return $lb->constructSet( $prefix, $this->mDb
);
171 * Update a table by doing a delete query then an insert query
174 function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
175 $fname = 'LinksUpdate::incrTableUpdate';
176 $where = array( "{$prefix}_from" => $this->mId
);
177 if ( $table == 'pagelinks' ||
$table == 'templatelinks' ) {
178 $clause = $this->makeWhereFrom2d( $deletions, $prefix );
185 if ( count( $deletions ) ) {
186 $where[] = "{$prefix}_to IN (" . $this->mDb
->makeList( array_keys( $deletions ) ) . ')';
192 $this->mDb
->delete( $table, $where, $fname );
194 if ( count( $insertions ) ) {
195 $this->mDb
->insert( $table, $insertions, $fname, 'IGNORE' );
201 * Get an array of pagelinks insertions for passing to the DB
202 * Skips the titles specified by the 2-D array $existing
205 function getLinkInsertions( $existing = array() ) {
207 foreach( $this->mLinks
as $ns => $dbkeys ) {
208 # array_diff_key() was introduced in PHP 5.1, there is a compatibility function
209 # in GlobalFunctions.php
210 $diffs = isset( $existing[$ns] ) ?
array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
211 foreach ( $diffs as $dbk => $id ) {
213 'pl_from' => $this->mId
,
214 'pl_namespace' => $ns,
223 * Get an array of template insertions. Like getLinkInsertions()
226 function getTemplateInsertions( $existing = array() ) {
228 foreach( $this->mTemplates
as $ns => $dbkeys ) {
229 $diffs = isset( $existing[$ns] ) ?
array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
230 foreach ( $diffs as $dbk => $id ) {
232 'tl_from' => $this->mId
,
233 'tl_namespace' => $ns,
242 * Get an array of image insertions
243 * Skips the names specified in $existing
246 function getImageInsertions( $existing = array() ) {
248 $diffs = array_diff_key( $this->mImages
, $existing );
249 foreach( $diffs as $iname => $dummy ) {
251 'il_from' => $this->mId
,
259 * Get an array of externallinks insertions. Skips the names specified in $existing
262 function getExternalInsertions( $existing = array() ) {
264 $diffs = array_diff_key( $this->mExternals
, $existing );
265 foreach( $diffs as $url => $dummy ) {
267 'el_from' => $this->mId
,
269 'el_index' => wfMakeUrlIndex( $url ),
276 * Get an array of category insertions
277 * @param array $existing Array mapping existing category names to sort keys. If both
278 * match a link in $this, the link will be omitted from the output
281 function getCategoryInsertions( $existing = array() ) {
282 $diffs = array_diff_assoc( $this->mCategories
, $existing );
284 foreach ( $diffs as $name => $sortkey ) {
286 'cl_from' => $this->mId
,
288 'cl_sortkey' => $sortkey
295 * Given an array of existing links, returns those links which are not in $this
296 * and thus should be deleted.
299 function getLinkDeletions( $existing ) {
301 foreach ( $existing as $ns => $dbkeys ) {
302 if ( isset( $this->mLinks
[$ns] ) ) {
303 $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks
[$ns] );
305 $del[$ns] = $existing[$ns];
312 * Given an array of existing templates, returns those templates which are not in $this
313 * and thus should be deleted.
316 function getTemplateDeletions( $existing ) {
318 foreach ( $existing as $ns => $dbkeys ) {
319 if ( isset( $this->mTemplates
[$ns] ) ) {
320 $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates
[$ns] );
322 $del[$ns] = $existing[$ns];
329 * Given an array of existing images, returns those images which are not in $this
330 * and thus should be deleted.
333 function getImageDeletions( $existing ) {
334 return array_diff_key( $existing, $this->mImages
);
338 * Given an array of existing external links, returns those links which are not
339 * in $this and thus should be deleted.
342 function getExternalDeletions( $existing ) {
343 return array_diff_key( $existing, $this->mExternals
);
347 * Given an array of existing categories, returns those categories which are not in $this
348 * and thus should be deleted.
351 function getCategoryDeletions( $existing ) {
352 return array_diff_assoc( $existing, $this->mCategories
);
356 * Get an array of existing links, as a 2-D array
359 function getExistingLinks() {
360 $fname = 'LinksUpdate::getExistingLinks';
361 $res = $this->mDb
->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
362 array( 'pl_from' => $this->mId
), $fname, $this->mOptions
);
364 while ( $row = $this->mDb
->fetchObject( $res ) ) {
365 if ( !isset( $arr[$row->pl_namespace
] ) ) {
366 $arr[$row->pl_namespace
] = array();
368 $arr[$row->pl_namespace
][$row->pl_title
] = 1;
370 $this->mDb
->freeResult( $res );
375 * Get an array of existing templates, as a 2-D array
378 function getExistingTemplates() {
379 $fname = 'LinksUpdate::getExistingTemplates';
380 $res = $this->mDb
->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
381 array( 'tl_from' => $this->mId
), $fname, $this->mOptions
);
383 while ( $row = $this->mDb
->fetchObject( $res ) ) {
384 if ( !isset( $arr[$row->tl_namespace
] ) ) {
385 $arr[$row->tl_namespace
] = array();
387 $arr[$row->tl_namespace
][$row->tl_title
] = 1;
389 $this->mDb
->freeResult( $res );
394 * Get an array of existing images, image names in the keys
397 function getExistingImages() {
398 $fname = 'LinksUpdate::getExistingImages';
399 $res = $this->mDb
->select( 'imagelinks', array( 'il_to' ),
400 array( 'il_from' => $this->mId
), $fname, $this->mOptions
);
402 while ( $row = $this->mDb
->fetchObject( $res ) ) {
403 $arr[$row->il_to
] = 1;
405 $this->mDb
->freeResult( $res );
410 * Get an array of existing external links, URLs in the keys
413 function getExistingExternals() {
414 $fname = 'LinksUpdate::getExistingExternals';
415 $res = $this->mDb
->select( 'externallinks', array( 'el_to' ),
416 array( 'el_from' => $this->mId
), $fname, $this->mOptions
);
418 while ( $row = $this->mDb
->fetchObject( $res ) ) {
419 $arr[$row->el_to
] = 1;
421 $this->mDb
->freeResult( $res );
426 * Get an array of existing categories, with the name in the key and sort key in the value.
429 function getExistingCategories() {
430 $fname = 'LinksUpdate::getExistingCategories';
431 $res = $this->mDb
->select( 'categorylinks', array( 'cl_to', 'cl_sortkey' ),
432 array( 'cl_from' => $this->mId
), $fname, $this->mOptions
);
434 while ( $row = $this->mDb
->fetchObject( $res ) ) {
435 $arr[$row->cl_to
] = $row->cl_sortkey
;
437 $this->mDb
->freeResult( $res );