MediaWiki  1.34.0
LinksUpdate.php
Go to the documentation of this file.
1 <?php
26 use Wikimedia\ScopedCallback;
27 
35 class LinksUpdate extends DataUpdate {
36  // @todo make members protected, but make sure extensions don't break
37 
39  public $mId;
40 
42  public $mTitle;
43 
46 
48  public $mLinks;
49 
51  public $mImages;
52 
54  public $mTemplates;
55 
57  public $mExternals;
58 
60  public $mCategories;
61 
63  public $mInterlangs;
64 
66  public $mInterwikis;
67 
69  public $mProperties;
70 
72  public $mRecursive;
73 
75  private $mRevision;
76 
80  private $linkInsertions = null;
81 
85  private $linkDeletions = null;
86 
90  private $externalLinkInsertions = null;
91 
95  private $externalLinkDeletions = null;
96 
100  private $propertyInsertions = null;
101 
105  private $propertyDeletions = null;
106 
110  private $user;
111 
113  private $db;
114 
121  function __construct( Title $title, ParserOutput $parserOutput, $recursive = true ) {
122  parent::__construct();
123 
124  $this->mTitle = $title;
125 
126  if ( !$this->mId ) {
127  // NOTE: subclasses may initialize mId before calling this constructor!
128  $this->mId = $title->getArticleID( Title::READ_LATEST );
129  }
130 
131  if ( !$this->mId ) {
132  throw new InvalidArgumentException(
133  "The Title object yields no ID. Perhaps the page doesn't exist?"
134  );
135  }
136 
137  $this->mParserOutput = $parserOutput;
138 
139  $this->mLinks = $parserOutput->getLinks();
140  $this->mImages = $parserOutput->getImages();
141  $this->mTemplates = $parserOutput->getTemplates();
142  $this->mExternals = $parserOutput->getExternalLinks();
143  $this->mCategories = $parserOutput->getCategories();
144  $this->mProperties = $parserOutput->getProperties();
145  $this->mInterwikis = $parserOutput->getInterwikiLinks();
146 
147  # Convert the format of the interlanguage links
148  # I didn't want to change it in the ParserOutput, because that array is passed all
149  # the way back to the skin, so either a skin API break would be required, or an
150  # inefficient back-conversion.
151  $ill = $parserOutput->getLanguageLinks();
152  $this->mInterlangs = [];
153  foreach ( $ill as $link ) {
154  list( $key, $title ) = explode( ':', $link, 2 );
155  $this->mInterlangs[$key] = $title;
156  }
157 
158  foreach ( $this->mCategories as &$sortkey ) {
159  # If the sortkey is longer then 255 bytes, it is truncated by DB, and then doesn't match
160  # when comparing existing vs current categories, causing T27254.
161  $sortkey = mb_strcut( $sortkey, 0, 255 );
162  }
163 
164  $this->mRecursive = $recursive;
165 
166  // Avoid PHP 7.1 warning from passing $this by reference
167  $linksUpdate = $this;
168  Hooks::run( 'LinksUpdateConstructed', [ &$linksUpdate ] );
169  }
170 
176  public function doUpdate() {
177  if ( $this->ticket ) {
178  // Make sure all links update threads see the changes of each other.
179  // This handles the case when updates have to batched into several COMMITs.
180  $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId );
181  if ( !$scopedLock ) {
182  throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." );
183  }
184  }
185 
186  // Avoid PHP 7.1 warning from passing $this by reference
187  $linksUpdate = $this;
188  Hooks::run( 'LinksUpdate', [ &$linksUpdate ] );
189  $this->doIncrementalUpdate();
190 
191  // Commit and release the lock (if set)
192  ScopedCallback::consume( $scopedLock );
193  // Run post-commit hook handlers without DBO_TRX
195  $this->getDB(),
196  __METHOD__,
197  function () {
198  // Avoid PHP 7.1 warning from passing $this by reference
199  $linksUpdate = $this;
200  Hooks::run( 'LinksUpdateComplete', [ &$linksUpdate, $this->ticket ] );
201  }
202  ) );
203  }
204 
214  public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) {
215  $key = "{$dbw->getDomainID()}:LinksUpdate:$why:pageid:$pageId"; // per-wiki
216  $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 );
217  if ( !$scopedLock ) {
218  $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' );
219  $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [
220  'key' => $key,
221  'page_id' => $pageId,
222  ] );
223  return null;
224  }
225 
226  return $scopedLock;
227  }
228 
229  protected function doIncrementalUpdate() {
230  # Page links
231  $existingPL = $this->getExistingLinks();
232  $this->linkDeletions = $this->getLinkDeletions( $existingPL );
233  $this->linkInsertions = $this->getLinkInsertions( $existingPL );
234  $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
235 
236  # Image links
237  $existingIL = $this->getExistingImages();
238  $imageDeletes = $this->getImageDeletions( $existingIL );
239  $this->incrTableUpdate(
240  'imagelinks',
241  'il',
242  $imageDeletes,
243  $this->getImageInsertions( $existingIL ) );
244 
245  # Invalidate all image description pages which had links added or removed
246  $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existingIL );
247  $this->invalidateImageDescriptions( $imageUpdates );
248 
249  # External links
250  $existingEL = $this->getExistingExternals();
251  $this->externalLinkDeletions = $this->getExternalDeletions( $existingEL );
252  $this->externalLinkInsertions = $this->getExternalInsertions(
253  $existingEL );
254  $this->incrTableUpdate(
255  'externallinks',
256  'el',
257  $this->externalLinkDeletions,
258  $this->externalLinkInsertions );
259 
260  # Language links
261  $existingLL = $this->getExistingInterlangs();
262  $this->incrTableUpdate(
263  'langlinks',
264  'll',
265  $this->getInterlangDeletions( $existingLL ),
266  $this->getInterlangInsertions( $existingLL ) );
267 
268  # Inline interwiki links
269  $existingIW = $this->getExistingInterwikis();
270  $this->incrTableUpdate(
271  'iwlinks',
272  'iwl',
273  $this->getInterwikiDeletions( $existingIW ),
274  $this->getInterwikiInsertions( $existingIW ) );
275 
276  # Template links
277  $existingTL = $this->getExistingTemplates();
278  $this->incrTableUpdate(
279  'templatelinks',
280  'tl',
281  $this->getTemplateDeletions( $existingTL ),
282  $this->getTemplateInsertions( $existingTL ) );
283 
284  # Category links
285  $existingCL = $this->getExistingCategories();
286  $categoryDeletes = $this->getCategoryDeletions( $existingCL );
287  $this->incrTableUpdate(
288  'categorylinks',
289  'cl',
290  $categoryDeletes,
291  $this->getCategoryInsertions( $existingCL ) );
292  $categoryInserts = array_diff_assoc( $this->mCategories, $existingCL );
293  $categoryUpdates = $categoryInserts + $categoryDeletes;
294 
295  # Page properties
296  $existingPP = $this->getExistingProperties();
297  $this->propertyDeletions = $this->getPropertyDeletions( $existingPP );
298  $this->incrTableUpdate(
299  'page_props',
300  'pp',
301  $this->propertyDeletions,
302  $this->getPropertyInsertions( $existingPP ) );
303 
304  # Invalidate the necessary pages
305  $this->propertyInsertions = array_diff_assoc( $this->mProperties, $existingPP );
306  $changed = $this->propertyDeletions + $this->propertyInsertions;
307  $this->invalidateProperties( $changed );
308 
309  # Invalidate all categories which were added, deleted or changed (set symmetric difference)
310  $this->invalidateCategories( $categoryUpdates );
311  $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
312 
313  # Refresh links of all pages including this page
314  # This will be in a separate transaction
315  if ( $this->mRecursive ) {
316  $this->queueRecursiveJobs();
317  }
318 
319  # Update the links table freshness for this title
320  $this->updateLinksTimestamp();
321  }
322 
329  protected function queueRecursiveJobs() {
330  $action = $this->getCauseAction();
331  $agent = $this->getCauseAgent();
332 
333  self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks', $action, $agent );
334  if ( $this->mTitle->getNamespace() == NS_FILE ) {
335  // Process imagelinks in case the title is or was a redirect
336  self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks', $action, $agent );
337  }
338 
339  $bc = $this->mTitle->getBacklinkCache();
340  // Get jobs for cascade-protected backlinks for a high priority queue.
341  // If meta-templates change to using a new template, the new template
342  // should be implicitly protected as soon as possible, if applicable.
343  // These jobs duplicate a subset of the above ones, but can run sooner.
344  // Which ever runs first generally no-ops the other one.
345  $jobs = [];
346  foreach ( $bc->getCascadeProtectedLinks() as $title ) {
348  $title,
349  [
350  'causeAction' => $action,
351  'causeAgent' => $agent
352  ]
353  );
354  }
355  JobQueueGroup::singleton()->push( $jobs );
356  }
357 
366  public static function queueRecursiveJobsForTable(
367  Title $title, $table, $action = 'unknown', $userName = 'unknown'
368  ) {
369  if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
370  $job = new RefreshLinksJob(
371  $title,
372  [
373  'table' => $table,
374  'recursive' => true,
375  ] + Job::newRootJobParams( // "overall" refresh links job info
376  "refreshlinks:{$table}:{$title->getPrefixedText()}"
377  ) + [ 'causeAction' => $action, 'causeAgent' => $userName ]
378  );
379 
380  JobQueueGroup::singleton()->push( $job );
381  }
382  }
383 
387  private function invalidateCategories( $cats ) {
388  PurgeJobUtils::invalidatePages( $this->getDB(), NS_CATEGORY, array_keys( $cats ) );
389  }
390 
396  private function updateCategoryCounts( array $added, array $deleted ) {
397  global $wgUpdateRowsPerQuery;
398 
399  if ( !$added && !$deleted ) {
400  return;
401  }
402 
403  $domainId = $this->getDB()->getDomainID();
404  $wp = WikiPage::factory( $this->mTitle );
405  $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
406  // T163801: try to release any row locks to reduce contention
407  $lbf->commitAndWaitForReplication( __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
408 
409  foreach ( array_chunk( array_keys( $added ), $wgUpdateRowsPerQuery ) as $addBatch ) {
410  $wp->updateCategoryCounts( $addBatch, [], $this->mId );
411  $lbf->commitAndWaitForReplication(
412  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
413  }
414 
415  foreach ( array_chunk( array_keys( $deleted ), $wgUpdateRowsPerQuery ) as $deleteBatch ) {
416  $wp->updateCategoryCounts( [], $deleteBatch, $this->mId );
417  $lbf->commitAndWaitForReplication(
418  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
419  }
420  }
421 
425  private function invalidateImageDescriptions( array $images ) {
426  PurgeJobUtils::invalidatePages( $this->getDB(), NS_FILE, array_keys( $images ) );
427  }
428 
436  private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
437  $services = MediaWikiServices::getInstance();
438  $bSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
439  $lbf = $services->getDBLoadBalancerFactory();
440 
441  if ( $table === 'page_props' ) {
442  $fromField = 'pp_page';
443  } else {
444  $fromField = "{$prefix}_from";
445  }
446 
447  $deleteWheres = []; // list of WHERE clause arrays for each DB delete() call
448  if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) {
449  $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
450 
451  $curBatchSize = 0;
452  $curDeletionBatch = [];
453  $deletionBatches = [];
454  foreach ( $deletions as $ns => $dbKeys ) {
455  foreach ( $dbKeys as $dbKey => $unused ) {
456  $curDeletionBatch[$ns][$dbKey] = 1;
457  if ( ++$curBatchSize >= $bSize ) {
458  $deletionBatches[] = $curDeletionBatch;
459  $curDeletionBatch = [];
460  $curBatchSize = 0;
461  }
462  }
463  }
464  if ( $curDeletionBatch ) {
465  $deletionBatches[] = $curDeletionBatch;
466  }
467 
468  foreach ( $deletionBatches as $deletionBatch ) {
469  $deleteWheres[] = [
470  $fromField => $this->mId,
471  $this->getDB()->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
472  ];
473  }
474  } else {
475  if ( $table === 'langlinks' ) {
476  $toField = 'll_lang';
477  } elseif ( $table === 'page_props' ) {
478  $toField = 'pp_propname';
479  } else {
480  $toField = $prefix . '_to';
481  }
482 
483  $deletionBatches = array_chunk( array_keys( $deletions ), $bSize );
484  foreach ( $deletionBatches as $deletionBatch ) {
485  $deleteWheres[] = [ $fromField => $this->mId, $toField => $deletionBatch ];
486  }
487  }
488 
489  $domainId = $this->getDB()->getDomainID();
490 
491  foreach ( $deleteWheres as $deleteWhere ) {
492  $this->getDB()->delete( $table, $deleteWhere, __METHOD__ );
493  $lbf->commitAndWaitForReplication(
494  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
495  );
496  }
497 
498  $insertBatches = array_chunk( $insertions, $bSize );
499  foreach ( $insertBatches as $insertBatch ) {
500  $this->getDB()->insert( $table, $insertBatch, __METHOD__, [ 'IGNORE' ] );
501  $lbf->commitAndWaitForReplication(
502  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
503  );
504  }
505 
506  if ( count( $insertions ) ) {
507  Hooks::run( 'LinksUpdateAfterInsert', [ $this, $table, $insertions ] );
508  }
509  }
510 
517  private function getLinkInsertions( $existing = [] ) {
518  $arr = [];
519  foreach ( $this->mLinks as $ns => $dbkeys ) {
520  $diffs = isset( $existing[$ns] )
521  ? array_diff_key( $dbkeys, $existing[$ns] )
522  : $dbkeys;
523  foreach ( $diffs as $dbk => $id ) {
524  $arr[] = [
525  'pl_from' => $this->mId,
526  'pl_from_namespace' => $this->mTitle->getNamespace(),
527  'pl_namespace' => $ns,
528  'pl_title' => $dbk
529  ];
530  }
531  }
532 
533  return $arr;
534  }
535 
541  private function getTemplateInsertions( $existing = [] ) {
542  $arr = [];
543  foreach ( $this->mTemplates as $ns => $dbkeys ) {
544  $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
545  foreach ( $diffs as $dbk => $id ) {
546  $arr[] = [
547  'tl_from' => $this->mId,
548  'tl_from_namespace' => $this->mTitle->getNamespace(),
549  'tl_namespace' => $ns,
550  'tl_title' => $dbk
551  ];
552  }
553  }
554 
555  return $arr;
556  }
557 
564  private function getImageInsertions( $existing = [] ) {
565  $arr = [];
566  $diffs = array_diff_key( $this->mImages, $existing );
567  foreach ( $diffs as $iname => $dummy ) {
568  $arr[] = [
569  'il_from' => $this->mId,
570  'il_from_namespace' => $this->mTitle->getNamespace(),
571  'il_to' => $iname
572  ];
573  }
574 
575  return $arr;
576  }
577 
583  private function getExternalInsertions( $existing = [] ) {
584  $arr = [];
585  $diffs = array_diff_key( $this->mExternals, $existing );
586  foreach ( $diffs as $url => $dummy ) {
587  foreach ( LinkFilter::makeIndexes( $url ) as $index ) {
588  $arr[] = [
589  'el_from' => $this->mId,
590  'el_to' => $url,
591  'el_index' => $index,
592  'el_index_60' => substr( $index, 0, 60 ),
593  ];
594  }
595  }
596 
597  return $arr;
598  }
599 
608  private function getCategoryInsertions( $existing = [] ) {
609  global $wgCategoryCollation;
610  $diffs = array_diff_assoc( $this->mCategories, $existing );
611  $arr = [];
612  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
613  $collation = Collation::singleton();
614  foreach ( $diffs as $name => $prefix ) {
615  $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
616  $contLang->findVariantLink( $name, $nt, true );
617 
618  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
619  getCategoryLinkType( $this->mTitle->getNamespace() );
620 
621  # Treat custom sortkeys as a prefix, so that if multiple
622  # things are forced to sort as '*' or something, they'll
623  # sort properly in the category rather than in page_id
624  # order or such.
625  $sortkey = $collation->getSortKey( $this->mTitle->getCategorySortkey( $prefix ) );
626 
627  $arr[] = [
628  'cl_from' => $this->mId,
629  'cl_to' => $name,
630  'cl_sortkey' => $sortkey,
631  'cl_timestamp' => $this->getDB()->timestamp(),
632  'cl_sortkey_prefix' => $prefix,
633  'cl_collation' => $wgCategoryCollation,
634  'cl_type' => $type,
635  ];
636  }
637 
638  return $arr;
639  }
640 
648  private function getInterlangInsertions( $existing = [] ) {
649  $diffs = array_diff_assoc( $this->mInterlangs, $existing );
650  $arr = [];
651  foreach ( $diffs as $lang => $title ) {
652  $arr[] = [
653  'll_from' => $this->mId,
654  'll_lang' => $lang,
655  'll_title' => $title
656  ];
657  }
658 
659  return $arr;
660  }
661 
667  function getPropertyInsertions( $existing = [] ) {
668  $diffs = array_diff_assoc( $this->mProperties, $existing );
669 
670  $arr = [];
671  foreach ( array_keys( $diffs ) as $name ) {
672  $arr[] = $this->getPagePropRowData( $name );
673  }
674 
675  return $arr;
676  }
677 
694  private function getPagePropRowData( $prop ) {
696 
697  $value = $this->mProperties[$prop];
698 
699  $row = [
700  'pp_page' => $this->mId,
701  'pp_propname' => $prop,
702  'pp_value' => $value,
703  ];
704 
705  if ( $wgPagePropsHaveSortkey ) {
706  $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value );
707  }
708 
709  return $row;
710  }
711 
724  private function getPropertySortKeyValue( $value ) {
725  if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
726  return floatval( $value );
727  }
728 
729  return null;
730  }
731 
738  private function getInterwikiInsertions( $existing = [] ) {
739  $arr = [];
740  foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
741  $diffs = isset( $existing[$prefix] )
742  ? array_diff_key( $dbkeys, $existing[$prefix] )
743  : $dbkeys;
744 
745  foreach ( $diffs as $dbk => $id ) {
746  $arr[] = [
747  'iwl_from' => $this->mId,
748  'iwl_prefix' => $prefix,
749  'iwl_title' => $dbk
750  ];
751  }
752  }
753 
754  return $arr;
755  }
756 
763  private function getLinkDeletions( $existing ) {
764  $del = [];
765  foreach ( $existing as $ns => $dbkeys ) {
766  if ( isset( $this->mLinks[$ns] ) ) {
767  $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] );
768  } else {
769  $del[$ns] = $existing[$ns];
770  }
771  }
772 
773  return $del;
774  }
775 
782  private function getTemplateDeletions( $existing ) {
783  $del = [];
784  foreach ( $existing as $ns => $dbkeys ) {
785  if ( isset( $this->mTemplates[$ns] ) ) {
786  $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] );
787  } else {
788  $del[$ns] = $existing[$ns];
789  }
790  }
791 
792  return $del;
793  }
794 
801  private function getImageDeletions( $existing ) {
802  return array_diff_key( $existing, $this->mImages );
803  }
804 
811  private function getExternalDeletions( $existing ) {
812  return array_diff_key( $existing, $this->mExternals );
813  }
814 
821  private function getCategoryDeletions( $existing ) {
822  return array_diff_assoc( $existing, $this->mCategories );
823  }
824 
831  private function getInterlangDeletions( $existing ) {
832  return array_diff_assoc( $existing, $this->mInterlangs );
833  }
834 
840  private function getPropertyDeletions( $existing ) {
841  return array_diff_assoc( $existing, $this->mProperties );
842  }
843 
850  private function getInterwikiDeletions( $existing ) {
851  $del = [];
852  foreach ( $existing as $prefix => $dbkeys ) {
853  if ( isset( $this->mInterwikis[$prefix] ) ) {
854  $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] );
855  } else {
856  $del[$prefix] = $existing[$prefix];
857  }
858  }
859 
860  return $del;
861  }
862 
868  private function getExistingLinks() {
869  $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
870  [ 'pl_from' => $this->mId ], __METHOD__ );
871  $arr = [];
872  foreach ( $res as $row ) {
873  if ( !isset( $arr[$row->pl_namespace] ) ) {
874  $arr[$row->pl_namespace] = [];
875  }
876  $arr[$row->pl_namespace][$row->pl_title] = 1;
877  }
878 
879  return $arr;
880  }
881 
887  private function getExistingTemplates() {
888  $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
889  [ 'tl_from' => $this->mId ], __METHOD__ );
890  $arr = [];
891  foreach ( $res as $row ) {
892  if ( !isset( $arr[$row->tl_namespace] ) ) {
893  $arr[$row->tl_namespace] = [];
894  }
895  $arr[$row->tl_namespace][$row->tl_title] = 1;
896  }
897 
898  return $arr;
899  }
900 
906  private function getExistingImages() {
907  $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ],
908  [ 'il_from' => $this->mId ], __METHOD__ );
909  $arr = [];
910  foreach ( $res as $row ) {
911  $arr[$row->il_to] = 1;
912  }
913 
914  return $arr;
915  }
916 
922  private function getExistingExternals() {
923  $res = $this->getDB()->select( 'externallinks', [ 'el_to' ],
924  [ 'el_from' => $this->mId ], __METHOD__ );
925  $arr = [];
926  foreach ( $res as $row ) {
927  $arr[$row->el_to] = 1;
928  }
929 
930  return $arr;
931  }
932 
938  private function getExistingCategories() {
939  $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
940  [ 'cl_from' => $this->mId ], __METHOD__ );
941  $arr = [];
942  foreach ( $res as $row ) {
943  $arr[$row->cl_to] = $row->cl_sortkey_prefix;
944  }
945 
946  return $arr;
947  }
948 
955  private function getExistingInterlangs() {
956  $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ],
957  [ 'll_from' => $this->mId ], __METHOD__ );
958  $arr = [];
959  foreach ( $res as $row ) {
960  $arr[$row->ll_lang] = $row->ll_title;
961  }
962 
963  return $arr;
964  }
965 
970  private function getExistingInterwikis() {
971  $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
972  [ 'iwl_from' => $this->mId ], __METHOD__ );
973  $arr = [];
974  foreach ( $res as $row ) {
975  if ( !isset( $arr[$row->iwl_prefix] ) ) {
976  $arr[$row->iwl_prefix] = [];
977  }
978  $arr[$row->iwl_prefix][$row->iwl_title] = 1;
979  }
980 
981  return $arr;
982  }
983 
989  private function getExistingProperties() {
990  $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ],
991  [ 'pp_page' => $this->mId ], __METHOD__ );
992  $arr = [];
993  foreach ( $res as $row ) {
994  $arr[$row->pp_propname] = $row->pp_value;
995  }
996 
997  return $arr;
998  }
999 
1004  public function getTitle() {
1005  return $this->mTitle;
1006  }
1007 
1013  public function getParserOutput() {
1014  return $this->mParserOutput;
1015  }
1016 
1021  public function getImages() {
1022  return $this->mImages;
1023  }
1024 
1032  public function setRevision( Revision $revision ) {
1033  $this->mRevision = $revision;
1034  }
1035 
1040  public function getRevision() {
1041  return $this->mRevision;
1042  }
1043 
1050  public function setTriggeringUser( User $user ) {
1051  $this->user = $user;
1052  }
1053 
1058  public function getTriggeringUser() {
1059  return $this->user;
1060  }
1061 
1066  private function invalidateProperties( $changed ) {
1068 
1069  $jobs = [];
1070  foreach ( $changed as $name => $value ) {
1071  if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
1072  $inv = $wgPagePropLinkInvalidations[$name];
1073  if ( !is_array( $inv ) ) {
1074  $inv = [ $inv ];
1075  }
1076  foreach ( $inv as $table ) {
1078  $this->mTitle,
1079  $table,
1080  [ 'causeAction' => 'page-props' ]
1081  );
1082  }
1083  }
1084  }
1085 
1086  JobQueueGroup::singleton()->lazyPush( $jobs );
1087  }
1088 
1094  public function getAddedLinks() {
1095  if ( $this->linkInsertions === null ) {
1096  return null;
1097  }
1098  $result = [];
1099  foreach ( $this->linkInsertions as $insertion ) {
1100  $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
1101  }
1102 
1103  return $result;
1104  }
1105 
1111  public function getRemovedLinks() {
1112  if ( $this->linkDeletions === null ) {
1113  return null;
1114  }
1115  $result = [];
1116  foreach ( $this->linkDeletions as $ns => $titles ) {
1117  foreach ( $titles as $title => $unused ) {
1118  $result[] = Title::makeTitle( $ns, $title );
1119  }
1120  }
1121 
1122  return $result;
1123  }
1124 
1131  public function getAddedExternalLinks() {
1132  if ( $this->externalLinkInsertions === null ) {
1133  return null;
1134  }
1135  $result = [];
1136  foreach ( $this->externalLinkInsertions as $key => $value ) {
1137  $result[] = $value['el_to'];
1138  }
1139  return $result;
1140  }
1141 
1148  public function getRemovedExternalLinks() {
1149  if ( $this->externalLinkDeletions === null ) {
1150  return null;
1151  }
1152  return array_keys( $this->externalLinkDeletions );
1153  }
1154 
1161  public function getAddedProperties() {
1163  }
1164 
1171  public function getRemovedProperties() {
1172  return $this->propertyDeletions;
1173  }
1174 
1178  private function updateLinksTimestamp() {
1179  if ( $this->mId ) {
1180  // The link updates made here only reflect the freshness of the parser output
1181  $timestamp = $this->mParserOutput->getCacheTime();
1182  $this->getDB()->update( 'page',
1183  [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ],
1184  [ 'page_id' => $this->mId ],
1185  __METHOD__
1186  );
1187  }
1188  }
1189 
1193  protected function getDB() {
1194  if ( !$this->db ) {
1195  $this->db = wfGetDB( DB_MASTER );
1196  }
1197 
1198  return $this->db;
1199  }
1200 
1207  public function isRecursive() {
1208  return $this->mRecursive;
1209  }
1210 }
LinksUpdate\getInterlangInsertions
getInterlangInsertions( $existing=[])
Get an array of interlanguage link insertions.
Definition: LinksUpdate.php:648
LinksUpdate\getTemplateInsertions
getTemplateInsertions( $existing=[])
Get an array of template insertions.
Definition: LinksUpdate.php:541
DataUpdate\getCauseAgent
getCauseAgent()
Definition: DataUpdate.php:67
LinksUpdate\invalidateImageDescriptions
invalidateImageDescriptions(array $images)
Definition: LinksUpdate.php:425
LinksUpdate\getTemplateDeletions
getTemplateDeletions( $existing)
Given an array of existing templates, returns those templates which are not in $this and thus should ...
Definition: LinksUpdate.php:782
LinksUpdate\getCategoryDeletions
getCategoryDeletions( $existing)
Given an array of existing categories, returns those categories which are not in $this and thus shoul...
Definition: LinksUpdate.php:821
LinksUpdate\getTriggeringUser
getTriggeringUser()
Definition: LinksUpdate.php:1058
LinksUpdate\doUpdate
doUpdate()
Update link tables with outgoing links from an updated article.
Definition: LinksUpdate.php:176
ParserOutput
Definition: ParserOutput.php:25
LinksUpdate\acquirePageLock
static acquirePageLock(IDatabase $dbw, $pageId, $why='atomicity')
Acquire a session-level lock for performing link table updates for a page on a DB.
Definition: LinksUpdate.php:214
LinksUpdate\isRecursive
isRecursive()
Whether or not this LinksUpdate will also update pages which transclude the current page or otherwise...
Definition: LinksUpdate.php:1207
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
LinksUpdate\getRemovedProperties
getRemovedProperties()
Fetch page properties removed by this LinksUpdate.
Definition: LinksUpdate.php:1171
LinksUpdate\$mInterwikis
array $mInterwikis
2-D map of (prefix => DBK => 1)
Definition: LinksUpdate.php:66
AutoCommitUpdate
Deferrable Update for closure/callback updates that should use auto-commit mode.
Definition: AutoCommitUpdate.php:9
LinksUpdate\getInterwikiDeletions
getInterwikiDeletions( $existing)
Given an array of existing interwiki links, returns those links which are not in $this and thus shoul...
Definition: LinksUpdate.php:850
LinksUpdate\setRevision
setRevision(Revision $revision)
Set the revision corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1032
ParserOutput\getImages
& getImages()
Definition: ParserOutput.php:565
LinksUpdate\$mId
int $mId
Page ID of the article linked from.
Definition: LinksUpdate.php:39
LinksUpdate\$mCategories
array $mCategories
Map of category names to sort keys.
Definition: LinksUpdate.php:60
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:85
LinksUpdate\$externalLinkDeletions
null array $externalLinkDeletions
Deleted external links if calculated.
Definition: LinksUpdate.php:95
LinksUpdate
Class the manages updates of *_link tables as well as similar extension-managed tables.
Definition: LinksUpdate.php:35
NS_FILE
const NS_FILE
Definition: Defines.php:66
PurgeJobUtils\invalidatePages
static invalidatePages(IDatabase $dbw, $namespace, array $dbkeys)
Invalidate the cache of a list of pages from a single namespace.
Definition: PurgeJobUtils.php:35
RefreshLinksJob\newPrioritized
static newPrioritized(Title $title, array $params)
Definition: RefreshLinksJob.php:68
LinksUpdate\$mImages
array $mImages
DB keys of the images used, in the array key only.
Definition: LinksUpdate.php:51
LinksUpdate\$db
IDatabase $db
Definition: LinksUpdate.php:113
LinksUpdate\getImageDeletions
getImageDeletions( $existing)
Given an array of existing images, returns those images which are not in $this and thus should be del...
Definition: LinksUpdate.php:801
$res
$res
Definition: testCompression.php:52
LinksUpdate\updateLinksTimestamp
updateLinksTimestamp()
Update links table freshness.
Definition: LinksUpdate.php:1178
HTMLCacheUpdateJob\newForBacklinks
static newForBacklinks(Title $title, $table, $params=[])
Definition: HTMLCacheUpdateJob.php:59
LinksUpdate\$propertyInsertions
null array $propertyInsertions
Added properties if calculated.
Definition: LinksUpdate.php:100
DataUpdate
Abstract base class for update jobs that do something with some secondary data extracted from article...
Definition: DataUpdate.php:28
ParserOutput\getProperties
getProperties()
Definition: ParserOutput.php:1081
LinksUpdate\getExternalDeletions
getExternalDeletions( $existing)
Given an array of existing external links, returns those links which are not in $this and thus should...
Definition: LinksUpdate.php:811
LinksUpdate\queueRecursiveJobs
queueRecursiveJobs()
Queue recursive jobs for this page.
Definition: LinksUpdate.php:329
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
LinksUpdate\getExistingImages
getExistingImages()
Get an array of existing images, image names in the keys.
Definition: LinksUpdate.php:906
LinkFilter\makeIndexes
static makeIndexes( $url)
Converts a URL into a format for el_index.
Definition: LinkFilter.php:171
LinksUpdate\incrTableUpdate
incrTableUpdate( $table, $prefix, $deletions, $insertions)
Update a table by doing a delete query then an insert query.
Definition: LinksUpdate.php:436
Collation\singleton
static singleton()
Definition: Collation.php:36
LinksUpdate\getLinkInsertions
getLinkInsertions( $existing=[])
Get an array of pagelinks insertions for passing to the DB Skips the titles specified by the 2-D arra...
Definition: LinksUpdate.php:517
Revision
Definition: Revision.php:40
LinksUpdate\getExistingTemplates
getExistingTemplates()
Get an array of existing templates, as a 2-D array.
Definition: LinksUpdate.php:887
LinksUpdate\getCategoryInsertions
getCategoryInsertions( $existing=[])
Get an array of category insertions.
Definition: LinksUpdate.php:608
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
LinksUpdate\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted)
Update all the appropriate counts in the category table.
Definition: LinksUpdate.php:396
LinksUpdate\$mTemplates
array $mTemplates
Map of title strings to IDs for the template references, including broken ones.
Definition: LinksUpdate.php:54
LinksUpdate\doIncrementalUpdate
doIncrementalUpdate()
Definition: LinksUpdate.php:229
LinksUpdate\$mProperties
array $mProperties
Map of arbitrary name to value.
Definition: LinksUpdate.php:69
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
LinksUpdate\getInterlangDeletions
getInterlangDeletions( $existing)
Given an array of existing interlanguage links, returns those links which are not in $this and thus s...
Definition: LinksUpdate.php:831
ParserOutput\getInterwikiLinks
getInterwikiLinks()
Definition: ParserOutput.php:525
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2575
LinksUpdate\invalidateProperties
invalidateProperties( $changed)
Invalidate any necessary link lists related to page property changes.
Definition: LinksUpdate.php:1066
LinksUpdate\$mRevision
Revision $mRevision
Revision for which this update has been triggered.
Definition: LinksUpdate.php:75
LinksUpdate\$linkInsertions
null array $linkInsertions
Added links if calculated.
Definition: LinksUpdate.php:80
$title
$title
Definition: testCompression.php:34
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:8480
LinksUpdate\getAddedExternalLinks
getAddedExternalLinks()
Fetch external links added by this LinksUpdate.
Definition: LinksUpdate.php:1131
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:74
LinksUpdate\getExistingProperties
getExistingProperties()
Get an array of existing categories, with the name in the key and sort key in the value.
Definition: LinksUpdate.php:989
LinksUpdate\getRemovedExternalLinks
getRemovedExternalLinks()
Fetch external links removed by this LinksUpdate.
Definition: LinksUpdate.php:1148
LinksUpdate\$mTitle
Title $mTitle
Title object of the article linked from.
Definition: LinksUpdate.php:42
DB_MASTER
const DB_MASTER
Definition: defines.php:26
LinksUpdate\getTitle
getTitle()
Return the title object of the page being updated.
Definition: LinksUpdate.php:1004
Job\newRootJobParams
static newRootJobParams( $key)
Get "root job" parameters for a task.
Definition: Job.php:308
LinksUpdate\getImageInsertions
getImageInsertions( $existing=[])
Get an array of image insertions Skips the names specified in $existing.
Definition: LinksUpdate.php:564
ParserOutput\getLanguageLinks
& getLanguageLinks()
Definition: ParserOutput.php:521
LinksUpdate\__construct
__construct(Title $title, ParserOutput $parserOutput, $recursive=true)
Definition: LinksUpdate.php:121
LinksUpdate\getExistingInterlangs
getExistingInterlangs()
Get an array of existing interlanguage links, with the language code in the key and the title in the ...
Definition: LinksUpdate.php:955
ParserOutput\getExternalLinks
& getExternalLinks()
Definition: ParserOutput.php:573
LinksUpdate\$mParserOutput
ParserOutput $mParserOutput
Definition: LinksUpdate.php:45
LinksUpdate\getParserOutput
getParserOutput()
Returns parser output.
Definition: LinksUpdate.php:1013
LinksUpdate\getAddedLinks
getAddedLinks()
Fetch page links added by this LinksUpdate.
Definition: LinksUpdate.php:1094
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
RefreshLinksJob
Job to update link tables for pages.
Definition: RefreshLinksJob.php:41
LinksUpdate\getInterwikiInsertions
getInterwikiInsertions( $existing=[])
Get an array of interwiki insertions for passing to the DB Skips the titles specified by the 2-D arra...
Definition: LinksUpdate.php:738
LinksUpdate\getAddedProperties
getAddedProperties()
Fetch page properties added by this LinksUpdate.
Definition: LinksUpdate.php:1161
Wikimedia\Rdbms\IDatabase\getScopedLockAndFlush
getScopedLockAndFlush( $lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object.
LinksUpdate\getExistingCategories
getExistingCategories()
Get an array of existing categories, with the name in the key and sort key in the value.
Definition: LinksUpdate.php:938
LinksUpdate\$linkDeletions
null array $linkDeletions
Deleted links if calculated.
Definition: LinksUpdate.php:85
LinksUpdate\getPagePropRowData
getPagePropRowData( $prop)
Returns an associative array to be used for inserting a row into the page_props table.
Definition: LinksUpdate.php:694
LinksUpdate\getExistingExternals
getExistingExternals()
Get an array of existing external links, URLs in the keys.
Definition: LinksUpdate.php:922
LinksUpdate\getPropertyDeletions
getPropertyDeletions( $existing)
Get array of properties which should be deleted.
Definition: LinksUpdate.php:840
ParserOutput\getTemplates
& getTemplates()
Definition: ParserOutput.php:557
LinksUpdate\getImages
getImages()
Return the list of images used as generated by the parser.
Definition: LinksUpdate.php:1021
LinksUpdate\$mInterlangs
array $mInterlangs
Map of language codes to titles.
Definition: LinksUpdate.php:63
Title
Represents a title within MediaWiki.
Definition: Title.php:42
LinksUpdate\setTriggeringUser
setTriggeringUser(User $user)
Set the User who triggered this LinksUpdate.
Definition: LinksUpdate.php:1050
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
LinksUpdate\getRemovedLinks
getRemovedLinks()
Fetch page links removed by this LinksUpdate.
Definition: LinksUpdate.php:1111
LinksUpdate\$mExternals
array $mExternals
URLs of external links, array key only.
Definition: LinksUpdate.php:57
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:50
$wgPagePropLinkInvalidations
$wgPagePropLinkInvalidations
Page property link table invalidation lists.
Definition: DefaultSettings.php:7624
LinksUpdate\getPropertyInsertions
getPropertyInsertions( $existing=[])
Get an array of page property insertions.
Definition: LinksUpdate.php:667
LinksUpdate\$externalLinkInsertions
null array $externalLinkInsertions
Added external links if calculated.
Definition: LinksUpdate.php:90
ParserOutput\getCategories
& getCategories()
Definition: ParserOutput.php:533
LinksUpdate\getExternalInsertions
getExternalInsertions( $existing=[])
Get an array of externallinks insertions.
Definition: LinksUpdate.php:583
LinksUpdate\$mRecursive
bool $mRecursive
Whether to queue jobs for recursive updates.
Definition: LinksUpdate.php:72
LinksUpdate\$user
User null $user
Definition: LinksUpdate.php:110
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:366
LinksUpdate\$propertyDeletions
null array $propertyDeletions
Deleted properties if calculated.
Definition: LinksUpdate.php:105
LinksUpdate\invalidateCategories
invalidateCategories( $cats)
Definition: LinksUpdate.php:387
$wgCategoryCollation
$wgCategoryCollation
Specify how category names should be sorted, when listed on a category page.
Definition: DefaultSettings.php:7676
DataUpdate\getCauseAction
getCauseAction()
Definition: DataUpdate.php:60
LinksUpdate\getExistingLinks
getExistingLinks()
Get an array of existing links, as a 2-D array.
Definition: LinksUpdate.php:868
$wgPagePropsHaveSortkey
$wgPagePropsHaveSortkey
Whether the page_props table has a pp_sortkey column.
Definition: DefaultSettings.php:8674
LinksUpdate\getDB
getDB()
Definition: LinksUpdate.php:1193
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
LinksUpdate\getRevision
getRevision()
Definition: LinksUpdate.php:1040
LinksUpdate\$mLinks
array $mLinks
Map of title strings to IDs for the links in the document.
Definition: LinksUpdate.php:48
LinksUpdate\getPropertySortKeyValue
getPropertySortKeyValue( $value)
Determines the sort key for the given property value.
Definition: LinksUpdate.php:724
LinksUpdate\getLinkDeletions
getLinkDeletions( $existing)
Given an array of existing links, returns those links which are not in $this and thus should be delet...
Definition: LinksUpdate.php:763
ParserOutput\getLinks
& getLinks()
Definition: ParserOutput.php:553
LinksUpdate\getExistingInterwikis
getExistingInterwikis()
Get an array of existing inline interwiki links, as a 2-D array.
Definition: LinksUpdate.php:970
$type
$type
Definition: testCompression.php:48