MediaWiki
REL1_19
|
00001 <?php 00022 class LinksUpdate { 00023 00027 var $mId, 00028 $mTitle, 00029 $mParserOutput, 00030 $mLinks, 00031 $mImages, 00032 $mTemplates, 00033 $mExternals, 00034 $mCategories, 00035 $mInterlangs, 00036 $mProperties, 00037 $mDb, 00038 $mOptions, 00039 $mRecursive; 00040 00049 function __construct( $title, $parserOutput, $recursive = true ) { 00050 global $wgAntiLockFlags; 00051 00052 if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) { 00053 $this->mOptions = array(); 00054 } else { 00055 $this->mOptions = array( 'FOR UPDATE' ); 00056 } 00057 $this->mDb = wfGetDB( DB_MASTER ); 00058 00059 if ( !is_object( $title ) ) { 00060 throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " . 00061 "Please see Article::editUpdates() for an invocation example.\n" ); 00062 } 00063 $this->mTitle = $title; 00064 $this->mId = $title->getArticleID(); 00065 00066 $this->mParserOutput = $parserOutput; 00067 $this->mLinks = $parserOutput->getLinks(); 00068 $this->mImages = $parserOutput->getImages(); 00069 $this->mTemplates = $parserOutput->getTemplates(); 00070 $this->mExternals = $parserOutput->getExternalLinks(); 00071 $this->mCategories = $parserOutput->getCategories(); 00072 $this->mProperties = $parserOutput->getProperties(); 00073 $this->mInterwikis = $parserOutput->getInterwikiLinks(); 00074 00075 # Convert the format of the interlanguage links 00076 # I didn't want to change it in the ParserOutput, because that array is passed all 00077 # the way back to the skin, so either a skin API break would be required, or an 00078 # inefficient back-conversion. 00079 $ill = $parserOutput->getLanguageLinks(); 00080 $this->mInterlangs = array(); 00081 foreach ( $ill as $link ) { 00082 list( $key, $title ) = explode( ':', $link, 2 ); 00083 $this->mInterlangs[$key] = $title; 00084 } 00085 00086 foreach ( $this->mCategories as &$sortkey ) { 00087 # If the sortkey is longer then 255 bytes, 00088 # it truncated by DB, and then doesn't get 00089 # matched when comparing existing vs current 00090 # categories, causing bug 25254. 00091 # Also. substr behaves weird when given "". 00092 if ( $sortkey !== '' ) { 00093 $sortkey = substr( $sortkey, 0, 255 ); 00094 } 00095 } 00096 00097 $this->mRecursive = $recursive; 00098 00099 wfRunHooks( 'LinksUpdateConstructed', array( &$this ) ); 00100 } 00101 00105 public function doUpdate() { 00106 global $wgUseDumbLinkUpdate; 00107 00108 wfRunHooks( 'LinksUpdate', array( &$this ) ); 00109 if ( $wgUseDumbLinkUpdate ) { 00110 $this->doDumbUpdate(); 00111 } else { 00112 $this->doIncrementalUpdate(); 00113 } 00114 wfRunHooks( 'LinksUpdateComplete', array( &$this ) ); 00115 } 00116 00117 protected function doIncrementalUpdate() { 00118 wfProfileIn( __METHOD__ ); 00119 00120 # Page links 00121 $existing = $this->getExistingLinks(); 00122 $this->incrTableUpdate( 'pagelinks', 'pl', $this->getLinkDeletions( $existing ), 00123 $this->getLinkInsertions( $existing ) ); 00124 00125 # Image links 00126 $existing = $this->getExistingImages(); 00127 00128 $imageDeletes = $this->getImageDeletions( $existing ); 00129 $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes, 00130 $this->getImageInsertions( $existing ) ); 00131 00132 # Invalidate all image description pages which had links added or removed 00133 $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing ); 00134 $this->invalidateImageDescriptions( $imageUpdates ); 00135 00136 # External links 00137 $existing = $this->getExistingExternals(); 00138 $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ), 00139 $this->getExternalInsertions( $existing ) ); 00140 00141 # Language links 00142 $existing = $this->getExistingInterlangs(); 00143 $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ), 00144 $this->getInterlangInsertions( $existing ) ); 00145 00146 # Inline interwiki links 00147 $existing = $this->getExistingInterwikis(); 00148 $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ), 00149 $this->getInterwikiInsertions( $existing ) ); 00150 00151 # Template links 00152 $existing = $this->getExistingTemplates(); 00153 $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ), 00154 $this->getTemplateInsertions( $existing ) ); 00155 00156 # Category links 00157 $existing = $this->getExistingCategories(); 00158 00159 $categoryDeletes = $this->getCategoryDeletions( $existing ); 00160 00161 $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes, 00162 $this->getCategoryInsertions( $existing ) ); 00163 00164 # Invalidate all categories which were added, deleted or changed (set symmetric difference) 00165 $categoryInserts = array_diff_assoc( $this->mCategories, $existing ); 00166 $categoryUpdates = $categoryInserts + $categoryDeletes; 00167 $this->invalidateCategories( $categoryUpdates ); 00168 $this->updateCategoryCounts( $categoryInserts, $categoryDeletes ); 00169 00170 # Page properties 00171 $existing = $this->getExistingProperties(); 00172 00173 $propertiesDeletes = $this->getPropertyDeletions( $existing ); 00174 00175 $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes, 00176 $this->getPropertyInsertions( $existing ) ); 00177 00178 # Invalidate the necessary pages 00179 $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing ); 00180 $this->invalidateProperties( $changed ); 00181 00182 # Refresh links of all pages including this page 00183 # This will be in a separate transaction 00184 if ( $this->mRecursive ) { 00185 $this->queueRecursiveJobs(); 00186 } 00187 00188 wfProfileOut( __METHOD__ ); 00189 } 00190 00196 protected function doDumbUpdate() { 00197 wfProfileIn( __METHOD__ ); 00198 00199 # Refresh category pages and image description pages 00200 $existing = $this->getExistingCategories(); 00201 $categoryInserts = array_diff_assoc( $this->mCategories, $existing ); 00202 $categoryDeletes = array_diff_assoc( $existing, $this->mCategories ); 00203 $categoryUpdates = $categoryInserts + $categoryDeletes; 00204 $existing = $this->getExistingImages(); 00205 $imageUpdates = array_diff_key( $existing, $this->mImages ) + array_diff_key( $this->mImages, $existing ); 00206 00207 $this->dumbTableUpdate( 'pagelinks', $this->getLinkInsertions(), 'pl_from' ); 00208 $this->dumbTableUpdate( 'imagelinks', $this->getImageInsertions(), 'il_from' ); 00209 $this->dumbTableUpdate( 'categorylinks', $this->getCategoryInsertions(), 'cl_from' ); 00210 $this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' ); 00211 $this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' ); 00212 $this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(),'ll_from' ); 00213 $this->dumbTableUpdate( 'iwlinks', $this->getInterwikiInsertions(),'iwl_from' ); 00214 $this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' ); 00215 00216 # Update the cache of all the category pages and image description 00217 # pages which were changed, and fix the category table count 00218 $this->invalidateCategories( $categoryUpdates ); 00219 $this->updateCategoryCounts( $categoryInserts, $categoryDeletes ); 00220 $this->invalidateImageDescriptions( $imageUpdates ); 00221 00222 # Refresh links of all pages including this page 00223 # This will be in a separate transaction 00224 if ( $this->mRecursive ) { 00225 $this->queueRecursiveJobs(); 00226 } 00227 00228 wfProfileOut( __METHOD__ ); 00229 } 00230 00231 function queueRecursiveJobs() { 00232 global $wgUpdateRowsPerJob; 00233 wfProfileIn( __METHOD__ ); 00234 00235 $cache = $this->mTitle->getBacklinkCache(); 00236 $batches = $cache->partition( 'templatelinks', $wgUpdateRowsPerJob ); 00237 if ( !$batches ) { 00238 wfProfileOut( __METHOD__ ); 00239 return; 00240 } 00241 $jobs = array(); 00242 foreach ( $batches as $batch ) { 00243 list( $start, $end ) = $batch; 00244 $params = array( 00245 'table' => 'templatelinks', 00246 'start' => $start, 00247 'end' => $end, 00248 ); 00249 $jobs[] = new RefreshLinksJob2( $this->mTitle, $params ); 00250 } 00251 Job::batchInsert( $jobs ); 00252 00253 wfProfileOut( __METHOD__ ); 00254 } 00255 00262 function invalidatePages( $namespace, $dbkeys ) { 00263 if ( !count( $dbkeys ) ) { 00264 return; 00265 } 00266 00272 $now = $this->mDb->timestamp(); 00273 $ids = array(); 00274 $res = $this->mDb->select( 'page', array( 'page_id' ), 00275 array( 00276 'page_namespace' => $namespace, 00277 'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')', 00278 'page_touched < ' . $this->mDb->addQuotes( $now ) 00279 ), __METHOD__ 00280 ); 00281 foreach ( $res as $row ) { 00282 $ids[] = $row->page_id; 00283 } 00284 if ( !count( $ids ) ) { 00285 return; 00286 } 00287 00293 $this->mDb->update( 'page', array( 'page_touched' => $now ), 00294 array( 00295 'page_id IN (' . $this->mDb->makeList( $ids ) . ')', 00296 'page_touched < ' . $this->mDb->addQuotes( $now ) 00297 ), __METHOD__ 00298 ); 00299 } 00300 00304 function invalidateCategories( $cats ) { 00305 $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) ); 00306 } 00307 00313 function updateCategoryCounts( $added, $deleted ) { 00314 $a = WikiPage::factory( $this->mTitle ); 00315 $a->updateCategoryCounts( 00316 array_keys( $added ), array_keys( $deleted ) 00317 ); 00318 } 00319 00323 function invalidateImageDescriptions( $images ) { 00324 $this->invalidatePages( NS_FILE, array_keys( $images ) ); 00325 } 00326 00332 private function dumbTableUpdate( $table, $insertions, $fromField ) { 00333 $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ ); 00334 if ( count( $insertions ) ) { 00335 # The link array was constructed without FOR UPDATE, so there may 00336 # be collisions. This may cause minor link table inconsistencies, 00337 # which is better than crippling the site with lock contention. 00338 $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) ); 00339 } 00340 } 00341 00349 function incrTableUpdate( $table, $prefix, $deletions, $insertions ) { 00350 if ( $table == 'page_props' ) { 00351 $fromField = 'pp_page'; 00352 } else { 00353 $fromField = "{$prefix}_from"; 00354 } 00355 $where = array( $fromField => $this->mId ); 00356 if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) { 00357 if ( $table == 'iwlinks' ) { 00358 $baseKey = 'iwl_prefix'; 00359 } else { 00360 $baseKey = "{$prefix}_namespace"; 00361 } 00362 $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" ); 00363 if ( $clause ) { 00364 $where[] = $clause; 00365 } else { 00366 $where = false; 00367 } 00368 } else { 00369 if ( $table == 'langlinks' ) { 00370 $toField = 'll_lang'; 00371 } elseif ( $table == 'page_props' ) { 00372 $toField = 'pp_propname'; 00373 } else { 00374 $toField = $prefix . '_to'; 00375 } 00376 if ( count( $deletions ) ) { 00377 $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')'; 00378 } else { 00379 $where = false; 00380 } 00381 } 00382 if ( $where ) { 00383 $this->mDb->delete( $table, $where, __METHOD__ ); 00384 } 00385 if ( count( $insertions ) ) { 00386 $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' ); 00387 } 00388 } 00389 00396 private function getLinkInsertions( $existing = array() ) { 00397 $arr = array(); 00398 foreach( $this->mLinks as $ns => $dbkeys ) { 00399 $diffs = isset( $existing[$ns] ) 00400 ? array_diff_key( $dbkeys, $existing[$ns] ) 00401 : $dbkeys; 00402 foreach ( $diffs as $dbk => $id ) { 00403 $arr[] = array( 00404 'pl_from' => $this->mId, 00405 'pl_namespace' => $ns, 00406 'pl_title' => $dbk 00407 ); 00408 } 00409 } 00410 return $arr; 00411 } 00412 00418 private function getTemplateInsertions( $existing = array() ) { 00419 $arr = array(); 00420 foreach( $this->mTemplates as $ns => $dbkeys ) { 00421 $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys; 00422 foreach ( $diffs as $dbk => $id ) { 00423 $arr[] = array( 00424 'tl_from' => $this->mId, 00425 'tl_namespace' => $ns, 00426 'tl_title' => $dbk 00427 ); 00428 } 00429 } 00430 return $arr; 00431 } 00432 00439 private function getImageInsertions( $existing = array() ) { 00440 $arr = array(); 00441 $diffs = array_diff_key( $this->mImages, $existing ); 00442 foreach( $diffs as $iname => $dummy ) { 00443 $arr[] = array( 00444 'il_from' => $this->mId, 00445 'il_to' => $iname 00446 ); 00447 } 00448 return $arr; 00449 } 00450 00456 private function getExternalInsertions( $existing = array() ) { 00457 $arr = array(); 00458 $diffs = array_diff_key( $this->mExternals, $existing ); 00459 foreach( $diffs as $url => $dummy ) { 00460 foreach( wfMakeUrlIndexes( $url ) as $index ) { 00461 $arr[] = array( 00462 'el_from' => $this->mId, 00463 'el_to' => $url, 00464 'el_index' => $index, 00465 ); 00466 } 00467 } 00468 return $arr; 00469 } 00470 00479 private function getCategoryInsertions( $existing = array() ) { 00480 global $wgContLang, $wgCategoryCollation; 00481 $diffs = array_diff_assoc( $this->mCategories, $existing ); 00482 $arr = array(); 00483 foreach ( $diffs as $name => $prefix ) { 00484 $nt = Title::makeTitleSafe( NS_CATEGORY, $name ); 00485 $wgContLang->findVariantLink( $name, $nt, true ); 00486 00487 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { 00488 $type = 'subcat'; 00489 } elseif ( $this->mTitle->getNamespace() == NS_FILE ) { 00490 $type = 'file'; 00491 } else { 00492 $type = 'page'; 00493 } 00494 00495 # Treat custom sortkeys as a prefix, so that if multiple 00496 # things are forced to sort as '*' or something, they'll 00497 # sort properly in the category rather than in page_id 00498 # order or such. 00499 $sortkey = Collation::singleton()->getSortKey( 00500 $this->mTitle->getCategorySortkey( $prefix ) ); 00501 00502 $arr[] = array( 00503 'cl_from' => $this->mId, 00504 'cl_to' => $name, 00505 'cl_sortkey' => $sortkey, 00506 'cl_timestamp' => $this->mDb->timestamp(), 00507 'cl_sortkey_prefix' => $prefix, 00508 'cl_collation' => $wgCategoryCollation, 00509 'cl_type' => $type, 00510 ); 00511 } 00512 return $arr; 00513 } 00514 00522 private function getInterlangInsertions( $existing = array() ) { 00523 $diffs = array_diff_assoc( $this->mInterlangs, $existing ); 00524 $arr = array(); 00525 foreach( $diffs as $lang => $title ) { 00526 $arr[] = array( 00527 'll_from' => $this->mId, 00528 'll_lang' => $lang, 00529 'll_title' => $title 00530 ); 00531 } 00532 return $arr; 00533 } 00534 00540 function getPropertyInsertions( $existing = array() ) { 00541 $diffs = array_diff_assoc( $this->mProperties, $existing ); 00542 $arr = array(); 00543 foreach ( $diffs as $name => $value ) { 00544 $arr[] = array( 00545 'pp_page' => $this->mId, 00546 'pp_propname' => $name, 00547 'pp_value' => $value, 00548 ); 00549 } 00550 return $arr; 00551 } 00552 00559 private function getInterwikiInsertions( $existing = array() ) { 00560 $arr = array(); 00561 foreach( $this->mInterwikis as $prefix => $dbkeys ) { 00562 $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys; 00563 foreach ( $diffs as $dbk => $id ) { 00564 $arr[] = array( 00565 'iwl_from' => $this->mId, 00566 'iwl_prefix' => $prefix, 00567 'iwl_title' => $dbk 00568 ); 00569 } 00570 } 00571 return $arr; 00572 } 00573 00580 private function getLinkDeletions( $existing ) { 00581 $del = array(); 00582 foreach ( $existing as $ns => $dbkeys ) { 00583 if ( isset( $this->mLinks[$ns] ) ) { 00584 $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] ); 00585 } else { 00586 $del[$ns] = $existing[$ns]; 00587 } 00588 } 00589 return $del; 00590 } 00591 00598 private function getTemplateDeletions( $existing ) { 00599 $del = array(); 00600 foreach ( $existing as $ns => $dbkeys ) { 00601 if ( isset( $this->mTemplates[$ns] ) ) { 00602 $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] ); 00603 } else { 00604 $del[$ns] = $existing[$ns]; 00605 } 00606 } 00607 return $del; 00608 } 00609 00616 private function getImageDeletions( $existing ) { 00617 return array_diff_key( $existing, $this->mImages ); 00618 } 00619 00626 private function getExternalDeletions( $existing ) { 00627 return array_diff_key( $existing, $this->mExternals ); 00628 } 00629 00636 private function getCategoryDeletions( $existing ) { 00637 return array_diff_assoc( $existing, $this->mCategories ); 00638 } 00639 00646 private function getInterlangDeletions( $existing ) { 00647 return array_diff_assoc( $existing, $this->mInterlangs ); 00648 } 00649 00655 function getPropertyDeletions( $existing ) { 00656 return array_diff_assoc( $existing, $this->mProperties ); 00657 } 00658 00665 private function getInterwikiDeletions( $existing ) { 00666 $del = array(); 00667 foreach ( $existing as $prefix => $dbkeys ) { 00668 if ( isset( $this->mInterwikis[$prefix] ) ) { 00669 $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] ); 00670 } else { 00671 $del[$prefix] = $existing[$prefix]; 00672 } 00673 } 00674 return $del; 00675 } 00676 00682 private function getExistingLinks() { 00683 $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ), 00684 array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions ); 00685 $arr = array(); 00686 foreach ( $res as $row ) { 00687 if ( !isset( $arr[$row->pl_namespace] ) ) { 00688 $arr[$row->pl_namespace] = array(); 00689 } 00690 $arr[$row->pl_namespace][$row->pl_title] = 1; 00691 } 00692 return $arr; 00693 } 00694 00700 private function getExistingTemplates() { 00701 $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ), 00702 array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions ); 00703 $arr = array(); 00704 foreach ( $res as $row ) { 00705 if ( !isset( $arr[$row->tl_namespace] ) ) { 00706 $arr[$row->tl_namespace] = array(); 00707 } 00708 $arr[$row->tl_namespace][$row->tl_title] = 1; 00709 } 00710 return $arr; 00711 } 00712 00718 private function getExistingImages() { 00719 $res = $this->mDb->select( 'imagelinks', array( 'il_to' ), 00720 array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions ); 00721 $arr = array(); 00722 foreach ( $res as $row ) { 00723 $arr[$row->il_to] = 1; 00724 } 00725 return $arr; 00726 } 00727 00733 private function getExistingExternals() { 00734 $res = $this->mDb->select( 'externallinks', array( 'el_to' ), 00735 array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions ); 00736 $arr = array(); 00737 foreach ( $res as $row ) { 00738 $arr[$row->el_to] = 1; 00739 } 00740 return $arr; 00741 } 00742 00748 private function getExistingCategories() { 00749 $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ), 00750 array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions ); 00751 $arr = array(); 00752 foreach ( $res as $row ) { 00753 $arr[$row->cl_to] = $row->cl_sortkey_prefix; 00754 } 00755 return $arr; 00756 } 00757 00764 private function getExistingInterlangs() { 00765 $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ), 00766 array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions ); 00767 $arr = array(); 00768 foreach ( $res as $row ) { 00769 $arr[$row->ll_lang] = $row->ll_title; 00770 } 00771 return $arr; 00772 } 00773 00778 protected function getExistingInterwikis() { 00779 $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ), 00780 array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions ); 00781 $arr = array(); 00782 foreach ( $res as $row ) { 00783 if ( !isset( $arr[$row->iwl_prefix] ) ) { 00784 $arr[$row->iwl_prefix] = array(); 00785 } 00786 $arr[$row->iwl_prefix][$row->iwl_title] = 1; 00787 } 00788 return $arr; 00789 } 00790 00796 private function getExistingProperties() { 00797 $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ), 00798 array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions ); 00799 $arr = array(); 00800 foreach ( $res as $row ) { 00801 $arr[$row->pp_propname] = $row->pp_value; 00802 } 00803 return $arr; 00804 } 00805 00810 public function getTitle() { 00811 return $this->mTitle; 00812 } 00813 00819 public function getParserOutput() { 00820 return $this->mParserOutput; 00821 } 00822 00827 public function getImages() { 00828 return $this->mImages; 00829 } 00830 00835 private function invalidateProperties( $changed ) { 00836 global $wgPagePropLinkInvalidations; 00837 00838 foreach ( $changed as $name => $value ) { 00839 if ( isset( $wgPagePropLinkInvalidations[$name] ) ) { 00840 $inv = $wgPagePropLinkInvalidations[$name]; 00841 if ( !is_array( $inv ) ) { 00842 $inv = array( $inv ); 00843 } 00844 foreach ( $inv as $table ) { 00845 $update = new HTMLCacheUpdate( $this->mTitle, $table ); 00846 $update->doUpdate(); 00847 } 00848 } 00849 } 00850 } 00851 }