[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Updater for link tracking tables after a page edit. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 */ 22 23 /** 24 * See docs/deferred.txt 25 * 26 * @todo document (e.g. one-sentence top-level class description). 27 */ 28 class LinksUpdate extends SqlDataUpdate { 29 // @todo make members protected, but make sure extensions don't break 30 31 /** @var int Page ID of the article linked from */ 32 public $mId; 33 34 /** @var Title Title object of the article linked from */ 35 public $mTitle; 36 37 /** @var ParserOutput */ 38 public $mParserOutput; 39 40 /** @var array Map of title strings to IDs for the links in the document */ 41 public $mLinks; 42 43 /** @var array DB keys of the images used, in the array key only */ 44 public $mImages; 45 46 /** @var array Map of title strings to IDs for the template references, including broken ones */ 47 public $mTemplates; 48 49 /** @var array URLs of external links, array key only */ 50 public $mExternals; 51 52 /** @var array Map of category names to sort keys */ 53 public $mCategories; 54 55 /** @var array Map of language codes to titles */ 56 public $mInterlangs; 57 58 /** @var array Map of arbitrary name to value */ 59 public $mProperties; 60 61 /** @var DatabaseBase Database connection reference */ 62 public $mDb; 63 64 /** @var array SELECT options to be used */ 65 public $mOptions; 66 67 /** @var bool Whether to queue jobs for recursive updates */ 68 public $mRecursive; 69 70 /** 71 * @var null|array Added links if calculated. 72 */ 73 private $linkInsertions = null; 74 75 /** 76 * @var null|array Deleted links if calculated. 77 */ 78 private $linkDeletions = null; 79 80 /** 81 * Constructor 82 * 83 * @param Title $title Title of the page we're updating 84 * @param ParserOutput $parserOutput Output from a full parse of this page 85 * @param bool $recursive Queue jobs for recursive updates? 86 * @throws MWException 87 */ 88 function __construct( $title, $parserOutput, $recursive = true ) { 89 parent::__construct( false ); // no implicit transaction 90 91 if ( !( $title instanceof Title ) ) { 92 throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " . 93 "Please see Article::editUpdates() for an invocation example.\n" ); 94 } 95 96 if ( !( $parserOutput instanceof ParserOutput ) ) { 97 throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " . 98 "Please see WikiPage::doEditUpdates() for an invocation example.\n" ); 99 } 100 101 $this->mTitle = $title; 102 $this->mId = $title->getArticleID(); 103 104 if ( !$this->mId ) { 105 throw new MWException( "The Title object did not provide an article " . 106 "ID. Perhaps the page doesn't exist?" ); 107 } 108 109 $this->mParserOutput = $parserOutput; 110 111 $this->mLinks = $parserOutput->getLinks(); 112 $this->mImages = $parserOutput->getImages(); 113 $this->mTemplates = $parserOutput->getTemplates(); 114 $this->mExternals = $parserOutput->getExternalLinks(); 115 $this->mCategories = $parserOutput->getCategories(); 116 $this->mProperties = $parserOutput->getProperties(); 117 $this->mInterwikis = $parserOutput->getInterwikiLinks(); 118 119 # Convert the format of the interlanguage links 120 # I didn't want to change it in the ParserOutput, because that array is passed all 121 # the way back to the skin, so either a skin API break would be required, or an 122 # inefficient back-conversion. 123 $ill = $parserOutput->getLanguageLinks(); 124 $this->mInterlangs = array(); 125 foreach ( $ill as $link ) { 126 list( $key, $title ) = explode( ':', $link, 2 ); 127 $this->mInterlangs[$key] = $title; 128 } 129 130 foreach ( $this->mCategories as &$sortkey ) { 131 # If the sortkey is longer then 255 bytes, 132 # it truncated by DB, and then doesn't get 133 # matched when comparing existing vs current 134 # categories, causing bug 25254. 135 # Also. substr behaves weird when given "". 136 if ( $sortkey !== '' ) { 137 $sortkey = substr( $sortkey, 0, 255 ); 138 } 139 } 140 141 $this->mRecursive = $recursive; 142 143 wfRunHooks( 'LinksUpdateConstructed', array( &$this ) ); 144 } 145 146 /** 147 * Update link tables with outgoing links from an updated article 148 */ 149 public function doUpdate() { 150 wfRunHooks( 'LinksUpdate', array( &$this ) ); 151 $this->doIncrementalUpdate(); 152 wfRunHooks( 'LinksUpdateComplete', array( &$this ) ); 153 } 154 155 protected function doIncrementalUpdate() { 156 wfProfileIn( __METHOD__ ); 157 158 # Page links 159 $existing = $this->getExistingLinks(); 160 $this->linkDeletions = $this->getLinkDeletions( $existing ); 161 $this->linkInsertions = $this->getLinkInsertions( $existing ); 162 $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions ); 163 164 # Image links 165 $existing = $this->getExistingImages(); 166 167 $imageDeletes = $this->getImageDeletions( $existing ); 168 $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes, 169 $this->getImageInsertions( $existing ) ); 170 171 # Invalidate all image description pages which had links added or removed 172 $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing ); 173 $this->invalidateImageDescriptions( $imageUpdates ); 174 175 # External links 176 $existing = $this->getExistingExternals(); 177 $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ), 178 $this->getExternalInsertions( $existing ) ); 179 180 # Language links 181 $existing = $this->getExistingInterlangs(); 182 $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ), 183 $this->getInterlangInsertions( $existing ) ); 184 185 # Inline interwiki links 186 $existing = $this->getExistingInterwikis(); 187 $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ), 188 $this->getInterwikiInsertions( $existing ) ); 189 190 # Template links 191 $existing = $this->getExistingTemplates(); 192 $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ), 193 $this->getTemplateInsertions( $existing ) ); 194 195 # Category links 196 $existing = $this->getExistingCategories(); 197 198 $categoryDeletes = $this->getCategoryDeletions( $existing ); 199 200 $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes, 201 $this->getCategoryInsertions( $existing ) ); 202 203 # Invalidate all categories which were added, deleted or changed (set symmetric difference) 204 $categoryInserts = array_diff_assoc( $this->mCategories, $existing ); 205 $categoryUpdates = $categoryInserts + $categoryDeletes; 206 $this->invalidateCategories( $categoryUpdates ); 207 $this->updateCategoryCounts( $categoryInserts, $categoryDeletes ); 208 209 # Page properties 210 $existing = $this->getExistingProperties(); 211 212 $propertiesDeletes = $this->getPropertyDeletions( $existing ); 213 214 $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes, 215 $this->getPropertyInsertions( $existing ) ); 216 217 # Invalidate the necessary pages 218 $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing ); 219 $this->invalidateProperties( $changed ); 220 221 # Update the links table freshness for this title 222 $this->updateLinksTimestamp(); 223 224 # Refresh links of all pages including this page 225 # This will be in a separate transaction 226 if ( $this->mRecursive ) { 227 $this->queueRecursiveJobs(); 228 } 229 230 wfProfileOut( __METHOD__ ); 231 } 232 233 /** 234 * Queue recursive jobs for this page 235 * 236 * Which means do LinksUpdate on all pages that include the current page, 237 * using the job queue. 238 */ 239 function queueRecursiveJobs() { 240 self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' ); 241 if ( $this->mTitle->getNamespace() == NS_FILE ) { 242 // Process imagelinks in case the title is or was a redirect 243 self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' ); 244 } 245 } 246 247 /** 248 * Queue a RefreshLinks job for any table. 249 * 250 * @param Title $title Title to do job for 251 * @param string $table Table to use (e.g. 'templatelinks') 252 */ 253 public static function queueRecursiveJobsForTable( Title $title, $table ) { 254 wfProfileIn( __METHOD__ ); 255 if ( $title->getBacklinkCache()->hasLinks( $table ) ) { 256 $job = new RefreshLinksJob( 257 $title, 258 array( 259 'table' => $table, 260 'recursive' => true, 261 ) + Job::newRootJobParams( // "overall" refresh links job info 262 "refreshlinks:{$table}:{$title->getPrefixedText()}" 263 ) 264 ); 265 JobQueueGroup::singleton()->push( $job ); 266 JobQueueGroup::singleton()->deduplicateRootJob( $job ); 267 } 268 wfProfileOut( __METHOD__ ); 269 } 270 271 /** 272 * @param array $cats 273 */ 274 function invalidateCategories( $cats ) { 275 $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) ); 276 } 277 278 /** 279 * Update all the appropriate counts in the category table. 280 * @param array $added Associative array of category name => sort key 281 * @param array $deleted Associative array of category name => sort key 282 */ 283 function updateCategoryCounts( $added, $deleted ) { 284 $a = WikiPage::factory( $this->mTitle ); 285 $a->updateCategoryCounts( 286 array_keys( $added ), array_keys( $deleted ) 287 ); 288 } 289 290 /** 291 * @param array $images 292 */ 293 function invalidateImageDescriptions( $images ) { 294 $this->invalidatePages( NS_FILE, array_keys( $images ) ); 295 } 296 297 /** 298 * Update a table by doing a delete query then an insert query 299 * @param string $table Table name 300 * @param string $prefix Field name prefix 301 * @param array $deletions 302 * @param array $insertions Rows to insert 303 */ 304 function incrTableUpdate( $table, $prefix, $deletions, $insertions ) { 305 if ( $table == 'page_props' ) { 306 $fromField = 'pp_page'; 307 } else { 308 $fromField = "{$prefix}_from"; 309 } 310 $where = array( $fromField => $this->mId ); 311 if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) { 312 if ( $table == 'iwlinks' ) { 313 $baseKey = 'iwl_prefix'; 314 } else { 315 $baseKey = "{$prefix}_namespace"; 316 } 317 $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" ); 318 if ( $clause ) { 319 $where[] = $clause; 320 } else { 321 $where = false; 322 } 323 } else { 324 if ( $table == 'langlinks' ) { 325 $toField = 'll_lang'; 326 } elseif ( $table == 'page_props' ) { 327 $toField = 'pp_propname'; 328 } else { 329 $toField = $prefix . '_to'; 330 } 331 if ( count( $deletions ) ) { 332 $where[$toField] = array_keys( $deletions ); 333 } else { 334 $where = false; 335 } 336 } 337 if ( $where ) { 338 $this->mDb->delete( $table, $where, __METHOD__ ); 339 } 340 if ( count( $insertions ) ) { 341 $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' ); 342 wfRunHooks( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) ); 343 } 344 } 345 346 /** 347 * Get an array of pagelinks insertions for passing to the DB 348 * Skips the titles specified by the 2-D array $existing 349 * @param array $existing 350 * @return array 351 */ 352 private function getLinkInsertions( $existing = array() ) { 353 $arr = array(); 354 foreach ( $this->mLinks as $ns => $dbkeys ) { 355 $diffs = isset( $existing[$ns] ) 356 ? array_diff_key( $dbkeys, $existing[$ns] ) 357 : $dbkeys; 358 foreach ( $diffs as $dbk => $id ) { 359 $arr[] = array( 360 'pl_from' => $this->mId, 361 'pl_from_namespace' => $this->mTitle->getNamespace(), 362 'pl_namespace' => $ns, 363 'pl_title' => $dbk 364 ); 365 } 366 } 367 368 return $arr; 369 } 370 371 /** 372 * Get an array of template insertions. Like getLinkInsertions() 373 * @param array $existing 374 * @return array 375 */ 376 private function getTemplateInsertions( $existing = array() ) { 377 $arr = array(); 378 foreach ( $this->mTemplates as $ns => $dbkeys ) { 379 $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys; 380 foreach ( $diffs as $dbk => $id ) { 381 $arr[] = array( 382 'tl_from' => $this->mId, 383 'tl_from_namespace' => $this->mTitle->getNamespace(), 384 'tl_namespace' => $ns, 385 'tl_title' => $dbk 386 ); 387 } 388 } 389 390 return $arr; 391 } 392 393 /** 394 * Get an array of image insertions 395 * Skips the names specified in $existing 396 * @param array $existing 397 * @return array 398 */ 399 private function getImageInsertions( $existing = array() ) { 400 $arr = array(); 401 $diffs = array_diff_key( $this->mImages, $existing ); 402 foreach ( $diffs as $iname => $dummy ) { 403 $arr[] = array( 404 'il_from' => $this->mId, 405 'il_from_namespace' => $this->mTitle->getNamespace(), 406 'il_to' => $iname 407 ); 408 } 409 410 return $arr; 411 } 412 413 /** 414 * Get an array of externallinks insertions. Skips the names specified in $existing 415 * @param array $existing 416 * @return array 417 */ 418 private function getExternalInsertions( $existing = array() ) { 419 $arr = array(); 420 $diffs = array_diff_key( $this->mExternals, $existing ); 421 foreach ( $diffs as $url => $dummy ) { 422 foreach ( wfMakeUrlIndexes( $url ) as $index ) { 423 $arr[] = array( 424 'el_id' => $this->mDb->nextSequenceValue( 'externallinks_el_id_seq' ), 425 'el_from' => $this->mId, 426 'el_to' => $url, 427 'el_index' => $index, 428 ); 429 } 430 } 431 432 return $arr; 433 } 434 435 /** 436 * Get an array of category insertions 437 * 438 * @param array $existing Mapping existing category names to sort keys. If both 439 * match a link in $this, the link will be omitted from the output 440 * 441 * @return array 442 */ 443 private function getCategoryInsertions( $existing = array() ) { 444 global $wgContLang, $wgCategoryCollation; 445 $diffs = array_diff_assoc( $this->mCategories, $existing ); 446 $arr = array(); 447 foreach ( $diffs as $name => $prefix ) { 448 $nt = Title::makeTitleSafe( NS_CATEGORY, $name ); 449 $wgContLang->findVariantLink( $name, $nt, true ); 450 451 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { 452 $type = 'subcat'; 453 } elseif ( $this->mTitle->getNamespace() == NS_FILE ) { 454 $type = 'file'; 455 } else { 456 $type = 'page'; 457 } 458 459 # Treat custom sortkeys as a prefix, so that if multiple 460 # things are forced to sort as '*' or something, they'll 461 # sort properly in the category rather than in page_id 462 # order or such. 463 $sortkey = Collation::singleton()->getSortKey( 464 $this->mTitle->getCategorySortkey( $prefix ) ); 465 466 $arr[] = array( 467 'cl_from' => $this->mId, 468 'cl_to' => $name, 469 'cl_sortkey' => $sortkey, 470 'cl_timestamp' => $this->mDb->timestamp(), 471 'cl_sortkey_prefix' => $prefix, 472 'cl_collation' => $wgCategoryCollation, 473 'cl_type' => $type, 474 ); 475 } 476 477 return $arr; 478 } 479 480 /** 481 * Get an array of interlanguage link insertions 482 * 483 * @param array $existing Mapping existing language codes to titles 484 * 485 * @return array 486 */ 487 private function getInterlangInsertions( $existing = array() ) { 488 $diffs = array_diff_assoc( $this->mInterlangs, $existing ); 489 $arr = array(); 490 foreach ( $diffs as $lang => $title ) { 491 $arr[] = array( 492 'll_from' => $this->mId, 493 'll_lang' => $lang, 494 'll_title' => $title 495 ); 496 } 497 498 return $arr; 499 } 500 501 /** 502 * Get an array of page property insertions 503 * @param array $existing 504 * @return array 505 */ 506 function getPropertyInsertions( $existing = array() ) { 507 $diffs = array_diff_assoc( $this->mProperties, $existing ); 508 509 $arr = array(); 510 foreach ( array_keys( $diffs ) as $name ) { 511 $arr[] = $this->getPagePropRowData( $name ); 512 } 513 514 return $arr; 515 } 516 517 /** 518 * Returns an associative array to be used for inserting a row into 519 * the page_props table. Besides the given property name, this will 520 * include the page id from $this->mId and any property value from 521 * $this->mProperties. 522 * 523 * The array returned will include the pp_sortkey field if this 524 * is present in the database (as indicated by $wgPagePropsHaveSortkey). 525 * The sortkey value is currently determined by getPropertySortKeyValue(). 526 * 527 * @note this assumes that $this->mProperties[$prop] is defined. 528 * 529 * @param string $prop The name of the property. 530 * 531 * @return array 532 */ 533 private function getPagePropRowData( $prop ) { 534 global $wgPagePropsHaveSortkey; 535 536 $value = $this->mProperties[$prop]; 537 538 $row = array( 539 'pp_page' => $this->mId, 540 'pp_propname' => $prop, 541 'pp_value' => $value, 542 ); 543 544 if ( $wgPagePropsHaveSortkey ) { 545 $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value ); 546 } 547 548 return $row; 549 } 550 551 /** 552 * Determines the sort key for the given property value. 553 * This will return $value if it is a float or int, 554 * 1 or resp. 0 if it is a bool, and null otherwise. 555 * 556 * @note In the future, we may allow the sortkey to be specified explicitly 557 * in ParserOutput::setProperty. 558 * 559 * @param mixed $value 560 * 561 * @return float|null 562 */ 563 private function getPropertySortKeyValue( $value ) { 564 if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) { 565 return floatval( $value ); 566 } 567 568 return null; 569 } 570 571 /** 572 * Get an array of interwiki insertions for passing to the DB 573 * Skips the titles specified by the 2-D array $existing 574 * @param array $existing 575 * @return array 576 */ 577 private function getInterwikiInsertions( $existing = array() ) { 578 $arr = array(); 579 foreach ( $this->mInterwikis as $prefix => $dbkeys ) { 580 $diffs = isset( $existing[$prefix] ) 581 ? array_diff_key( $dbkeys, $existing[$prefix] ) 582 : $dbkeys; 583 584 foreach ( $diffs as $dbk => $id ) { 585 $arr[] = array( 586 'iwl_from' => $this->mId, 587 'iwl_prefix' => $prefix, 588 'iwl_title' => $dbk 589 ); 590 } 591 } 592 593 return $arr; 594 } 595 596 /** 597 * Given an array of existing links, returns those links which are not in $this 598 * and thus should be deleted. 599 * @param array $existing 600 * @return array 601 */ 602 private function getLinkDeletions( $existing ) { 603 $del = array(); 604 foreach ( $existing as $ns => $dbkeys ) { 605 if ( isset( $this->mLinks[$ns] ) ) { 606 $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] ); 607 } else { 608 $del[$ns] = $existing[$ns]; 609 } 610 } 611 612 return $del; 613 } 614 615 /** 616 * Given an array of existing templates, returns those templates which are not in $this 617 * and thus should be deleted. 618 * @param array $existing 619 * @return array 620 */ 621 private function getTemplateDeletions( $existing ) { 622 $del = array(); 623 foreach ( $existing as $ns => $dbkeys ) { 624 if ( isset( $this->mTemplates[$ns] ) ) { 625 $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] ); 626 } else { 627 $del[$ns] = $existing[$ns]; 628 } 629 } 630 631 return $del; 632 } 633 634 /** 635 * Given an array of existing images, returns those images which are not in $this 636 * and thus should be deleted. 637 * @param array $existing 638 * @return array 639 */ 640 private function getImageDeletions( $existing ) { 641 return array_diff_key( $existing, $this->mImages ); 642 } 643 644 /** 645 * Given an array of existing external links, returns those links which are not 646 * in $this and thus should be deleted. 647 * @param array $existing 648 * @return array 649 */ 650 private function getExternalDeletions( $existing ) { 651 return array_diff_key( $existing, $this->mExternals ); 652 } 653 654 /** 655 * Given an array of existing categories, returns those categories which are not in $this 656 * and thus should be deleted. 657 * @param array $existing 658 * @return array 659 */ 660 private function getCategoryDeletions( $existing ) { 661 return array_diff_assoc( $existing, $this->mCategories ); 662 } 663 664 /** 665 * Given an array of existing interlanguage links, returns those links which are not 666 * in $this and thus should be deleted. 667 * @param array $existing 668 * @return array 669 */ 670 private function getInterlangDeletions( $existing ) { 671 return array_diff_assoc( $existing, $this->mInterlangs ); 672 } 673 674 /** 675 * Get array of properties which should be deleted. 676 * @param array $existing 677 * @return array 678 */ 679 function getPropertyDeletions( $existing ) { 680 return array_diff_assoc( $existing, $this->mProperties ); 681 } 682 683 /** 684 * Given an array of existing interwiki links, returns those links which are not in $this 685 * and thus should be deleted. 686 * @param array $existing 687 * @return array 688 */ 689 private function getInterwikiDeletions( $existing ) { 690 $del = array(); 691 foreach ( $existing as $prefix => $dbkeys ) { 692 if ( isset( $this->mInterwikis[$prefix] ) ) { 693 $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] ); 694 } else { 695 $del[$prefix] = $existing[$prefix]; 696 } 697 } 698 699 return $del; 700 } 701 702 /** 703 * Get an array of existing links, as a 2-D array 704 * 705 * @return array 706 */ 707 private function getExistingLinks() { 708 $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ), 709 array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions ); 710 $arr = array(); 711 foreach ( $res as $row ) { 712 if ( !isset( $arr[$row->pl_namespace] ) ) { 713 $arr[$row->pl_namespace] = array(); 714 } 715 $arr[$row->pl_namespace][$row->pl_title] = 1; 716 } 717 718 return $arr; 719 } 720 721 /** 722 * Get an array of existing templates, as a 2-D array 723 * 724 * @return array 725 */ 726 private function getExistingTemplates() { 727 $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ), 728 array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions ); 729 $arr = array(); 730 foreach ( $res as $row ) { 731 if ( !isset( $arr[$row->tl_namespace] ) ) { 732 $arr[$row->tl_namespace] = array(); 733 } 734 $arr[$row->tl_namespace][$row->tl_title] = 1; 735 } 736 737 return $arr; 738 } 739 740 /** 741 * Get an array of existing images, image names in the keys 742 * 743 * @return array 744 */ 745 private function getExistingImages() { 746 $res = $this->mDb->select( 'imagelinks', array( 'il_to' ), 747 array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions ); 748 $arr = array(); 749 foreach ( $res as $row ) { 750 $arr[$row->il_to] = 1; 751 } 752 753 return $arr; 754 } 755 756 /** 757 * Get an array of existing external links, URLs in the keys 758 * 759 * @return array 760 */ 761 private function getExistingExternals() { 762 $res = $this->mDb->select( 'externallinks', array( 'el_to' ), 763 array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions ); 764 $arr = array(); 765 foreach ( $res as $row ) { 766 $arr[$row->el_to] = 1; 767 } 768 769 return $arr; 770 } 771 772 /** 773 * Get an array of existing categories, with the name in the key and sort key in the value. 774 * 775 * @return array 776 */ 777 private function getExistingCategories() { 778 $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ), 779 array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions ); 780 $arr = array(); 781 foreach ( $res as $row ) { 782 $arr[$row->cl_to] = $row->cl_sortkey_prefix; 783 } 784 785 return $arr; 786 } 787 788 /** 789 * Get an array of existing interlanguage links, with the language code in the key and the 790 * title in the value. 791 * 792 * @return array 793 */ 794 private function getExistingInterlangs() { 795 $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ), 796 array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions ); 797 $arr = array(); 798 foreach ( $res as $row ) { 799 $arr[$row->ll_lang] = $row->ll_title; 800 } 801 802 return $arr; 803 } 804 805 /** 806 * Get an array of existing inline interwiki links, as a 2-D array 807 * @return array (prefix => array(dbkey => 1)) 808 */ 809 protected function getExistingInterwikis() { 810 $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ), 811 array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions ); 812 $arr = array(); 813 foreach ( $res as $row ) { 814 if ( !isset( $arr[$row->iwl_prefix] ) ) { 815 $arr[$row->iwl_prefix] = array(); 816 } 817 $arr[$row->iwl_prefix][$row->iwl_title] = 1; 818 } 819 820 return $arr; 821 } 822 823 /** 824 * Get an array of existing categories, with the name in the key and sort key in the value. 825 * 826 * @return array Array of property names and values 827 */ 828 private function getExistingProperties() { 829 $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ), 830 array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions ); 831 $arr = array(); 832 foreach ( $res as $row ) { 833 $arr[$row->pp_propname] = $row->pp_value; 834 } 835 836 return $arr; 837 } 838 839 /** 840 * Return the title object of the page being updated 841 * @return Title 842 */ 843 public function getTitle() { 844 return $this->mTitle; 845 } 846 847 /** 848 * Returns parser output 849 * @since 1.19 850 * @return ParserOutput 851 */ 852 public function getParserOutput() { 853 return $this->mParserOutput; 854 } 855 856 /** 857 * Return the list of images used as generated by the parser 858 * @return array 859 */ 860 public function getImages() { 861 return $this->mImages; 862 } 863 864 /** 865 * Invalidate any necessary link lists related to page property changes 866 * @param array $changed 867 */ 868 private function invalidateProperties( $changed ) { 869 global $wgPagePropLinkInvalidations; 870 871 foreach ( $changed as $name => $value ) { 872 if ( isset( $wgPagePropLinkInvalidations[$name] ) ) { 873 $inv = $wgPagePropLinkInvalidations[$name]; 874 if ( !is_array( $inv ) ) { 875 $inv = array( $inv ); 876 } 877 foreach ( $inv as $table ) { 878 $update = new HTMLCacheUpdate( $this->mTitle, $table ); 879 $update->doUpdate(); 880 } 881 } 882 } 883 } 884 885 /** 886 * Fetch page links added by this LinksUpdate. Only available after the update is complete. 887 * @since 1.22 888 * @return null|array Array of Titles 889 */ 890 public function getAddedLinks() { 891 if ( $this->linkInsertions === null ) { 892 return null; 893 } 894 $result = array(); 895 foreach ( $this->linkInsertions as $insertion ) { 896 $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] ); 897 } 898 899 return $result; 900 } 901 902 /** 903 * Fetch page links removed by this LinksUpdate. Only available after the update is complete. 904 * @since 1.22 905 * @return null|array Array of Titles 906 */ 907 public function getRemovedLinks() { 908 if ( $this->linkDeletions === null ) { 909 return null; 910 } 911 $result = array(); 912 foreach ( $this->linkDeletions as $ns => $titles ) { 913 foreach ( $titles as $title => $unused ) { 914 $result[] = Title::makeTitle( $ns, $title ); 915 } 916 } 917 918 return $result; 919 } 920 921 /** 922 * Update links table freshness 923 */ 924 protected function updateLinksTimestamp() { 925 if ( $this->mId ) { 926 // The link updates made here only reflect the freshness of the parser output 927 $timestamp = $this->mParserOutput->getCacheTime(); 928 $this->mDb->update( 'page', 929 array( 'page_links_updated' => $this->mDb->timestamp( $timestamp ) ), 930 array( 'page_id' => $this->mId ), 931 __METHOD__ 932 ); 933 } 934 } 935 } 936 937 /** 938 * Update object handling the cleanup of links tables after a page was deleted. 939 **/ 940 class LinksDeletionUpdate extends SqlDataUpdate { 941 /** @var WikiPage The WikiPage that was deleted */ 942 protected $mPage; 943 944 /** 945 * Constructor 946 * 947 * @param WikiPage $page Page we are updating 948 * @throws MWException 949 */ 950 function __construct( WikiPage $page ) { 951 parent::__construct( false ); // no implicit transaction 952 953 $this->mPage = $page; 954 955 if ( !$page->exists() ) { 956 throw new MWException( "Page ID not known, perhaps the page doesn't exist?" ); 957 } 958 } 959 960 /** 961 * Do some database updates after deletion 962 */ 963 public function doUpdate() { 964 $title = $this->mPage->getTitle(); 965 $id = $this->mPage->getId(); 966 967 # Delete restrictions for it 968 $this->mDb->delete( 'page_restrictions', array( 'pr_page' => $id ), __METHOD__ ); 969 970 # Fix category table counts 971 $cats = array(); 972 $res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ ); 973 974 foreach ( $res as $row ) { 975 $cats[] = $row->cl_to; 976 } 977 978 $this->mPage->updateCategoryCounts( array(), $cats ); 979 980 # If using cascading deletes, we can skip some explicit deletes 981 if ( !$this->mDb->cascadingDeletes() ) { 982 # Delete outgoing links 983 $this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ ); 984 $this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ ); 985 $this->mDb->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ ); 986 $this->mDb->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ ); 987 $this->mDb->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ ); 988 $this->mDb->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ ); 989 $this->mDb->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ ); 990 $this->mDb->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ ); 991 $this->mDb->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ ); 992 } 993 994 # If using cleanup triggers, we can skip some manual deletes 995 if ( !$this->mDb->cleanupTriggers() ) { 996 # Clean up recentchanges entries... 997 $this->mDb->delete( 'recentchanges', 998 array( 'rc_type != ' . RC_LOG, 999 'rc_namespace' => $title->getNamespace(), 1000 'rc_title' => $title->getDBkey() ), 1001 __METHOD__ ); 1002 $this->mDb->delete( 'recentchanges', 1003 array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ), 1004 __METHOD__ ); 1005 } 1006 } 1007 1008 /** 1009 * Update all the appropriate counts in the category table. 1010 * @param array $added Associative array of category name => sort key 1011 * @param array $deleted Associative array of category name => sort key 1012 */ 1013 function updateCategoryCounts( $added, $deleted ) { 1014 $a = WikiPage::factory( $this->mTitle ); 1015 $a->updateCategoryCounts( 1016 array_keys( $added ), array_keys( $deleted ) 1017 ); 1018 } 1019 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |