MediaWiki  master
LinksUpdate.php
Go to the documentation of this file.
1 <?php
23 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
30 use Wikimedia\ScopedCallback;
31 
39 class LinksUpdate extends DataUpdate {
40  use ProtectedHookAccessorTrait;
41 
42  // @todo make members protected, but make sure extensions don't break
43 
45  public $mId;
46 
48  public $mTitle;
49 
52 
57  public $mLinks;
58 
60  public $mImages;
61 
63  public $mTemplates;
64 
66  public $mExternals;
67 
69  public $mCategories;
70 
72  public $mInterlangs;
73 
75  public $mInterwikis;
76 
78  public $mProperties;
79 
81  public $mRecursive;
82 
85 
90  private $linkInsertions = null;
91 
95  private $linkDeletions = null;
96 
100  private $externalLinkInsertions = null;
101 
105  private $externalLinkDeletions = null;
106 
110  private $propertyInsertions = null;
111 
115  private $propertyDeletions = null;
116 
120  private $user;
121 
123  private $db;
124 
132  public function __construct( PageIdentity $page, ParserOutput $parserOutput, $recursive = true ) {
133  parent::__construct();
134 
135  // NOTE: mTitle is public and used in hooks. Will need careful deprecation.
136  $this->mTitle = Title::castFromPageIdentity( $page );
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  $this->getHookRunner()->onLinksUpdateConstructed( $this );
167  }
168 
174  public function doUpdate() {
175  if ( !$this->mId ) {
176  // NOTE: subclasses may initialize mId directly!
177  $this->mId = $this->mTitle->getArticleID( Title::READ_LATEST );
178  }
179 
180  if ( !$this->mId ) {
181  // Probably due to concurrent deletion or renaming of the page
182  $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' );
183  $logger->notice(
184  'LinksUpdate: The Title object yields no ID. Perhaps the page was deleted?',
185  [
186  'page_title' => $this->mTitle->getPrefixedDBkey(),
187  'cause_action' => $this->getCauseAction(),
188  'cause_agent' => $this->getCauseAgent()
189  ]
190  );
191 
192  // nothing to do
193  return;
194  }
195 
196  if ( $this->ticket ) {
197  // Make sure all links update threads see the changes of each other.
198  // This handles the case when updates have to batched into several COMMITs.
199  $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId );
200  if ( !$scopedLock ) {
201  throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." );
202  }
203  }
204 
205  $this->getHookRunner()->onLinksUpdate( $this );
206  $this->doIncrementalUpdate();
207 
208  // Commit and release the lock (if set)
209  ScopedCallback::consume( $scopedLock );
210  // Run post-commit hook handlers without DBO_TRX
212  $this->getDB(),
213  __METHOD__,
214  function () {
215  $this->getHookRunner()->onLinksUpdateComplete( $this, $this->ticket );
216  }
217  ) );
218  }
219 
229  public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) {
230  $key = "{$dbw->getDomainID()}:LinksUpdate:$why:pageid:$pageId"; // per-wiki
231  $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 );
232  if ( !$scopedLock ) {
233  $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' );
234  $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [
235  'key' => $key,
236  'page_id' => $pageId,
237  ] );
238  return null;
239  }
240 
241  return $scopedLock;
242  }
243 
244  protected function doIncrementalUpdate() {
245  # Page links
246  $existingPL = $this->getExistingLinks();
247  $this->linkDeletions = $this->getLinkDeletions( $existingPL );
248  $this->linkInsertions = $this->getLinkInsertions( $existingPL );
249  $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
250 
251  # Image links
252  $existingIL = $this->getExistingImages();
253  $imageDeletes = $this->getImageDeletions( $existingIL );
254  $imageAdditions = $this->getImageAdditions( $existingIL );
255  $this->incrTableUpdate(
256  'imagelinks',
257  'il',
258  $imageDeletes,
259  $this->getImageInsertions( $existingIL ) );
260 
261  # Image change tags
262  $enabledTags = ChangeTags::getSoftwareTags();
263  $mediaChangeTags = array_filter( [
264  count( $imageAdditions ) && in_array( 'mw-add-media', $enabledTags ) ? 'mw-add-media' : '',
265  count( $imageDeletes ) && in_array( 'mw-remove-media', $enabledTags ) ? 'mw-remove-media' : '',
266  ] );
267  $revisionRecord = $this->getRevisionRecord();
268  if ( $revisionRecord && count( $mediaChangeTags ) ) {
269  ChangeTags::addTags( $mediaChangeTags, null, $revisionRecord->getId() );
270  }
271 
272  # Invalidate all image description pages which had links added or removed
273  $imageUpdates = $imageDeletes + $imageAdditions;
274  $this->invalidateImageDescriptions( $imageUpdates );
275 
276  # External links
277  $existingEL = $this->getExistingExternals();
278  $this->externalLinkDeletions = $this->getExternalDeletions( $existingEL );
279  $this->externalLinkInsertions = $this->getExternalInsertions(
280  $existingEL );
281  $this->incrTableUpdate(
282  'externallinks',
283  'el',
284  $this->externalLinkDeletions,
285  $this->externalLinkInsertions );
286 
287  # Language links
288  $existingLL = $this->getExistingInterlangs();
289  $this->incrTableUpdate(
290  'langlinks',
291  'll',
292  $this->getInterlangDeletions( $existingLL ),
293  $this->getInterlangInsertions( $existingLL ) );
294 
295  # Inline interwiki links
296  $existingIW = $this->getExistingInterwikis();
297  $this->incrTableUpdate(
298  'iwlinks',
299  'iwl',
300  $this->getInterwikiDeletions( $existingIW ),
301  $this->getInterwikiInsertions( $existingIW ) );
302 
303  # Template links
304  $existingTL = $this->getExistingTemplates();
305  $this->incrTableUpdate(
306  'templatelinks',
307  'tl',
308  $this->getTemplateDeletions( $existingTL ),
309  $this->getTemplateInsertions( $existingTL ) );
310 
311  # Category links
312  $existingCL = $this->getExistingCategories();
313  $categoryDeletes = $this->getCategoryDeletions( $existingCL );
314  $this->incrTableUpdate(
315  'categorylinks',
316  'cl',
317  $categoryDeletes,
318  $this->getCategoryInsertions( $existingCL ) );
319  $categoryInserts = array_diff_assoc( $this->mCategories, $existingCL );
320  $categoryUpdates = $categoryInserts + $categoryDeletes;
321 
322  # Page properties
323  $existingPP = $this->getExistingProperties();
324  $this->propertyDeletions = $this->getPropertyDeletions( $existingPP );
325  $this->incrTableUpdate(
326  'page_props',
327  'pp',
328  $this->propertyDeletions,
329  $this->getPropertyInsertions( $existingPP ) );
330 
331  # Invalidate the necessary pages
332  $this->propertyInsertions = array_diff_assoc( $this->mProperties, $existingPP );
333  $changed = $this->propertyDeletions + $this->propertyInsertions;
334  $this->invalidateProperties( $changed );
335 
336  # Invalidate all categories which were added, deleted or changed (set symmetric difference)
337  $this->invalidateCategories( $categoryUpdates );
338  $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
339 
340  # Refresh links of all pages including this page
341  # This will be in a separate transaction
342  if ( $this->mRecursive ) {
343  $this->queueRecursiveJobs();
344  }
345 
346  # Update the links table freshness for this title
347  $this->updateLinksTimestamp();
348  }
349 
356  protected function queueRecursiveJobs() {
357  $backlinkCache = MediaWikiServices::getInstance()->getBacklinkCacheFactory()
358  ->getBacklinkCache( $this->mTitle );
359  $action = $this->getCauseAction();
360  $agent = $this->getCauseAgent();
361 
363  $this->mTitle, 'templatelinks', $action, $agent, $backlinkCache
364  );
365  if ( $this->mTitle->getNamespace() === NS_FILE ) {
366  // Process imagelinks in case the title is or was a redirect
368  $this->mTitle, 'imagelinks', $action, $agent, $backlinkCache
369  );
370  }
371 
372  // Get jobs for cascade-protected backlinks for a high priority queue.
373  // If meta-templates change to using a new template, the new template
374  // should be implicitly protected as soon as possible, if applicable.
375  // These jobs duplicate a subset of the above ones, but can run sooner.
376  // Which ever runs first generally no-ops the other one.
377  $jobs = [];
378  foreach ( $backlinkCache->getCascadeProtectedLinks() as $title ) {
380  $title,
381  [
382  'causeAction' => $action,
383  'causeAgent' => $agent
384  ]
385  );
386  }
387  JobQueueGroup::singleton()->push( $jobs );
388  }
389 
399  public static function queueRecursiveJobsForTable(
400  PageIdentity $page, $table, $action = 'unknown', $userName = 'unknown', ?BacklinkCache $backlinkCache = null
401  ) {
403  if ( !$backlinkCache ) {
404  wfDeprecatedMsg( __METHOD__ . " needs a BacklinkCache object, null passed", '1.37' );
405  $backlinkCache = MediaWikiServices::getInstance()->getBacklinkCacheFactory()
406  ->getBacklinkCache( $title );
407  }
408  if ( $backlinkCache->hasLinks( $table ) ) {
409  $job = new RefreshLinksJob(
410  $title,
411  [
412  'table' => $table,
413  'recursive' => true,
414  ] + Job::newRootJobParams( // "overall" refresh links job info
415  "refreshlinks:{$table}:{$title->getPrefixedText()}"
416  ) + [ 'causeAction' => $action, 'causeAgent' => $userName ]
417  );
418 
419  JobQueueGroup::singleton()->push( $job );
420  }
421  }
422 
426  private function invalidateCategories( $cats ) {
428  $this->getDB(), NS_CATEGORY, array_map( 'strval', array_keys( $cats ) )
429  );
430  }
431 
437  private function updateCategoryCounts( array $added, array $deleted ) {
438  global $wgUpdateRowsPerQuery;
439 
440  if ( !$added && !$deleted ) {
441  return;
442  }
443 
444  $domainId = $this->getDB()->getDomainID();
445  $services = MediaWikiServices::getInstance();
446  $wp = $services->getWikiPageFactory()->newFromTitle( $this->mTitle );
447  $lbf = $services->getDBLoadBalancerFactory();
448  // T163801: try to release any row locks to reduce contention
449  $lbf->commitAndWaitForReplication( __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
450 
451  foreach ( array_chunk( array_keys( $added ), $wgUpdateRowsPerQuery ) as $addBatch ) {
452  $wp->updateCategoryCounts( array_map( 'strval', $addBatch ), [], $this->mId );
453  $lbf->commitAndWaitForReplication(
454  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
455  }
456 
457  foreach ( array_chunk( array_keys( $deleted ), $wgUpdateRowsPerQuery ) as $deleteBatch ) {
458  $wp->updateCategoryCounts( [], array_map( 'strval', $deleteBatch ), $this->mId );
459  $lbf->commitAndWaitForReplication(
460  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
461  }
462  }
463 
467  private function invalidateImageDescriptions( array $images ) {
469  $this->getDB(), NS_FILE, array_map( 'strval', array_keys( $images ) )
470  );
471  }
472 
480  private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
481  $services = MediaWikiServices::getInstance();
482  $bSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
483  $lbf = $services->getDBLoadBalancerFactory();
484 
485  if ( $table === 'page_props' ) {
486  $fromField = 'pp_page';
487  } else {
488  $fromField = "{$prefix}_from";
489  }
490 
491  $deleteWheres = []; // list of WHERE clause arrays for each DB delete() call
492  if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) {
493  $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
494 
495  $curBatchSize = 0;
496  $curDeletionBatch = [];
497  $deletionBatches = [];
498  foreach ( $deletions as $ns => $dbKeys ) {
499  foreach ( $dbKeys as $dbKey => $unused ) {
500  $curDeletionBatch[$ns][$dbKey] = 1;
501  if ( ++$curBatchSize >= $bSize ) {
502  $deletionBatches[] = $curDeletionBatch;
503  $curDeletionBatch = [];
504  $curBatchSize = 0;
505  }
506  }
507  }
508  if ( $curDeletionBatch ) {
509  $deletionBatches[] = $curDeletionBatch;
510  }
511 
512  foreach ( $deletionBatches as $deletionBatch ) {
513  $deleteWheres[] = [
514  $fromField => $this->mId,
515  $this->getDB()->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
516  ];
517  }
518  } else {
519  if ( $table === 'langlinks' ) {
520  $toField = 'll_lang';
521  } elseif ( $table === 'page_props' ) {
522  $toField = 'pp_propname';
523  } else {
524  $toField = $prefix . '_to';
525  }
526 
527  $deletionBatches = array_chunk( array_keys( $deletions ), $bSize );
528  foreach ( $deletionBatches as $deletionBatch ) {
529  $deleteWheres[] = [
530  $fromField => $this->mId,
531  $toField => array_map( 'strval', $deletionBatch )
532  ];
533  }
534  }
535 
536  $domainId = $this->getDB()->getDomainID();
537 
538  foreach ( $deleteWheres as $deleteWhere ) {
539  $this->getDB()->delete( $table, $deleteWhere, __METHOD__ );
540  $lbf->commitAndWaitForReplication(
541  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
542  );
543  }
544 
545  $insertBatches = array_chunk( $insertions, $bSize );
546  foreach ( $insertBatches as $insertBatch ) {
547  $this->getDB()->insert( $table, $insertBatch, __METHOD__, [ 'IGNORE' ] );
548  $lbf->commitAndWaitForReplication(
549  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
550  );
551  }
552 
553  if ( count( $insertions ) ) {
554  $this->getHookRunner()->onLinksUpdateAfterInsert( $this, $table, $insertions );
555  }
556  }
557 
565  private function getLinkInsertions( $existing = [] ) {
566  $arr = [];
567  foreach ( $this->mLinks as $ns => $dbkeys ) {
568  $diffs = isset( $existing[$ns] )
569  ? array_diff_key( $dbkeys, $existing[$ns] )
570  : $dbkeys;
571  foreach ( $diffs as $dbk => $id ) {
572  $arr[] = [
573  'pl_from' => $this->mId,
574  'pl_from_namespace' => $this->mTitle->getNamespace(),
575  'pl_namespace' => $ns,
576  'pl_title' => $dbk
577  ];
578  }
579  }
580 
581  return $arr;
582  }
583 
589  private function getTemplateInsertions( $existing = [] ) {
590  $arr = [];
591  foreach ( $this->mTemplates as $ns => $dbkeys ) {
592  $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
593  foreach ( $diffs as $dbk => $id ) {
594  $arr[] = [
595  'tl_from' => $this->mId,
596  'tl_from_namespace' => $this->mTitle->getNamespace(),
597  'tl_namespace' => $ns,
598  'tl_title' => $dbk
599  ];
600  }
601  }
602 
603  return $arr;
604  }
605 
612  private function getImageInsertions( $existing = [] ) {
613  $arr = [];
614  $diffs = $this->getImageAdditions( $existing );
615  foreach ( $diffs as $iname => $dummy ) {
616  $arr[] = [
617  'il_from' => $this->mId,
618  'il_from_namespace' => $this->mTitle->getNamespace(),
619  'il_to' => $iname
620  ];
621  }
622 
623  return $arr;
624  }
625 
631  private function getExternalInsertions( $existing = [] ) {
632  $arr = [];
633  $diffs = array_diff_key( $this->mExternals, $existing );
634  foreach ( $diffs as $url => $dummy ) {
635  foreach ( LinkFilter::makeIndexes( $url ) as $index ) {
636  $arr[] = [
637  'el_from' => $this->mId,
638  'el_to' => $url,
639  'el_index' => $index,
640  'el_index_60' => substr( $index, 0, 60 ),
641  ];
642  }
643  }
644 
645  return $arr;
646  }
647 
656  private function getCategoryInsertions( $existing = [] ) {
657  global $wgCategoryCollation;
658  $diffs = array_diff_assoc( $this->mCategories, $existing );
659  $arr = [];
660 
661  $languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
662  ->getLanguageConverter();
663 
664  $collation = MediaWikiServices::getInstance()->getCollationFactory()->getCategoryCollation();
665  foreach ( $diffs as $name => $prefix ) {
666  $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
667  $languageConverter->findVariantLink( $name, $nt, true );
668 
669  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
670  getCategoryLinkType( $this->mTitle->getNamespace() );
671 
672  # Treat custom sortkeys as a prefix, so that if multiple
673  # things are forced to sort as '*' or something, they'll
674  # sort properly in the category rather than in page_id
675  # order or such.
676  $sortkey = $collation->getSortKey( $this->mTitle->getCategorySortkey( $prefix ) );
677 
678  $arr[] = [
679  'cl_from' => $this->mId,
680  'cl_to' => $name,
681  'cl_sortkey' => $sortkey,
682  'cl_timestamp' => $this->getDB()->timestamp(),
683  'cl_sortkey_prefix' => $prefix,
684  'cl_collation' => $wgCategoryCollation,
685  'cl_type' => $type,
686  ];
687  }
688 
689  return $arr;
690  }
691 
699  private function getInterlangInsertions( $existing = [] ) {
700  $diffs = array_diff_assoc( $this->mInterlangs, $existing );
701  $arr = [];
702  foreach ( $diffs as $lang => $title ) {
703  $arr[] = [
704  'll_from' => $this->mId,
705  'll_lang' => $lang,
706  'll_title' => $title
707  ];
708  }
709 
710  return $arr;
711  }
712 
718  private function getPropertyInsertions( $existing = [] ) {
719  $diffs = array_diff_assoc( $this->mProperties, $existing );
720 
721  $arr = [];
722  foreach ( array_keys( $diffs ) as $name ) {
723  $arr[] = $this->getPagePropRowData( (string)$name );
724  }
725 
726  return $arr;
727  }
728 
744  private function getPagePropRowData( $prop ) {
745  $value = $this->mProperties[$prop];
746 
747  return [
748  'pp_page' => $this->mId,
749  'pp_propname' => $prop,
750  'pp_value' => $value,
751  'pp_sortkey' => $this->getPropertySortKeyValue( $value )
752  ];
753  }
754 
767  private function getPropertySortKeyValue( $value ) {
768  if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
769  return floatval( $value );
770  }
771 
772  return null;
773  }
774 
781  private function getInterwikiInsertions( $existing = [] ) {
782  $arr = [];
783  foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
784  $diffs = isset( $existing[$prefix] )
785  ? array_diff_key( $dbkeys, $existing[$prefix] )
786  : $dbkeys;
787 
788  foreach ( $diffs as $dbk => $id ) {
789  $arr[] = [
790  'iwl_from' => $this->mId,
791  'iwl_prefix' => $prefix,
792  'iwl_title' => $dbk
793  ];
794  }
795  }
796 
797  return $arr;
798  }
799 
806  private function getImageAdditions( $existing ) {
807  return array_diff_key( $this->mImages, $existing );
808  }
809 
816  private function getLinkDeletions( $existing ) {
817  $del = [];
818  foreach ( $existing as $ns => $dbkeys ) {
819  if ( isset( $this->mLinks[$ns] ) ) {
820  $del[$ns] = array_diff_key( $dbkeys, $this->mLinks[$ns] );
821  } else {
822  $del[$ns] = $dbkeys;
823  }
824  }
825 
826  return $del;
827  }
828 
835  private function getTemplateDeletions( $existing ) {
836  $del = [];
837  foreach ( $existing as $ns => $dbkeys ) {
838  if ( isset( $this->mTemplates[$ns] ) ) {
839  $del[$ns] = array_diff_key( $dbkeys, $this->mTemplates[$ns] );
840  } else {
841  $del[$ns] = $dbkeys;
842  }
843  }
844 
845  return $del;
846  }
847 
854  private function getImageDeletions( $existing ) {
855  return array_diff_key( $existing, $this->mImages );
856  }
857 
864  private function getExternalDeletions( $existing ) {
865  return array_diff_key( $existing, $this->mExternals );
866  }
867 
874  private function getCategoryDeletions( $existing ) {
875  return array_diff_assoc( $existing, $this->mCategories );
876  }
877 
884  private function getInterlangDeletions( $existing ) {
885  return array_diff_assoc( $existing, $this->mInterlangs );
886  }
887 
893  private function getPropertyDeletions( $existing ) {
894  return array_diff_assoc( $existing, $this->mProperties );
895  }
896 
903  private function getInterwikiDeletions( $existing ) {
904  $del = [];
905  foreach ( $existing as $prefix => $dbkeys ) {
906  if ( isset( $this->mInterwikis[$prefix] ) ) {
907  $del[$prefix] = array_diff_key( $dbkeys, $this->mInterwikis[$prefix] );
908  } else {
909  $del[$prefix] = $dbkeys;
910  }
911  }
912 
913  return $del;
914  }
915 
921  private function getExistingLinks() {
922  $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
923  [ 'pl_from' => $this->mId ], __METHOD__ );
924  $arr = [];
925  foreach ( $res as $row ) {
926  if ( !isset( $arr[$row->pl_namespace] ) ) {
927  $arr[$row->pl_namespace] = [];
928  }
929  $arr[$row->pl_namespace][$row->pl_title] = 1;
930  }
931 
932  return $arr;
933  }
934 
940  private function getExistingTemplates() {
941  $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
942  [ 'tl_from' => $this->mId ], __METHOD__ );
943  $arr = [];
944  foreach ( $res as $row ) {
945  if ( !isset( $arr[$row->tl_namespace] ) ) {
946  $arr[$row->tl_namespace] = [];
947  }
948  $arr[$row->tl_namespace][$row->tl_title] = 1;
949  }
950 
951  return $arr;
952  }
953 
959  private function getExistingImages() {
960  $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ],
961  [ 'il_from' => $this->mId ], __METHOD__ );
962  $arr = [];
963  foreach ( $res as $row ) {
964  $arr[$row->il_to] = 1;
965  }
966 
967  return $arr;
968  }
969 
975  private function getExistingExternals() {
976  $res = $this->getDB()->select( 'externallinks', [ 'el_to' ],
977  [ 'el_from' => $this->mId ], __METHOD__ );
978  $arr = [];
979  foreach ( $res as $row ) {
980  $arr[$row->el_to] = 1;
981  }
982 
983  return $arr;
984  }
985 
991  private function getExistingCategories() {
992  $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
993  [ 'cl_from' => $this->mId ], __METHOD__ );
994  $arr = [];
995  foreach ( $res as $row ) {
996  $arr[$row->cl_to] = $row->cl_sortkey_prefix;
997  }
998 
999  return $arr;
1000  }
1001 
1008  private function getExistingInterlangs() {
1009  $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ],
1010  [ 'll_from' => $this->mId ], __METHOD__ );
1011  $arr = [];
1012  foreach ( $res as $row ) {
1013  $arr[$row->ll_lang] = $row->ll_title;
1014  }
1015 
1016  return $arr;
1017  }
1018 
1023  private function getExistingInterwikis() {
1024  $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
1025  [ 'iwl_from' => $this->mId ], __METHOD__ );
1026  $arr = [];
1027  foreach ( $res as $row ) {
1028  if ( !isset( $arr[$row->iwl_prefix] ) ) {
1029  $arr[$row->iwl_prefix] = [];
1030  }
1031  $arr[$row->iwl_prefix][$row->iwl_title] = 1;
1032  }
1033 
1034  return $arr;
1035  }
1036 
1042  private function getExistingProperties() {
1043  $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ],
1044  [ 'pp_page' => $this->mId ], __METHOD__ );
1045  $arr = [];
1046  foreach ( $res as $row ) {
1047  $arr[$row->pp_propname] = $row->pp_value;
1048  }
1049 
1050  return $arr;
1051  }
1052 
1057  public function getTitle() {
1058  return $this->mTitle;
1059  }
1060 
1066  public function getParserOutput() {
1067  return $this->mParserOutput;
1068  }
1069 
1074  public function getImages() {
1075  return $this->mImages;
1076  }
1077 
1084  public function setRevisionRecord( RevisionRecord $revisionRecord ) {
1085  $this->mRevisionRecord = $revisionRecord;
1086  }
1087 
1092  public function getRevisionRecord() {
1093  return $this->mRevisionRecord;
1094  }
1095 
1102  public function setTriggeringUser( UserIdentity $user ) {
1103  $this->user = $user;
1104  }
1105 
1112  public function getTriggeringUser(): ?UserIdentity {
1113  return $this->user;
1114  }
1115 
1120  private function invalidateProperties( $changed ) {
1122 
1123  $jobs = [];
1124  foreach ( $changed as $name => $value ) {
1125  if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
1126  $inv = $wgPagePropLinkInvalidations[$name];
1127  if ( !is_array( $inv ) ) {
1128  $inv = [ $inv ];
1129  }
1130  foreach ( $inv as $table ) {
1132  $this->mTitle,
1133  $table,
1134  [ 'causeAction' => 'page-props' ]
1135  );
1136  }
1137  }
1138  }
1139 
1140  JobQueueGroup::singleton()->lazyPush( $jobs );
1141  }
1142 
1148  public function getAddedLinks() {
1149  if ( $this->linkInsertions === null ) {
1150  return null;
1151  }
1152  $result = [];
1153  foreach ( $this->linkInsertions as $insertion ) {
1154  $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
1155  }
1156 
1157  return $result;
1158  }
1159 
1165  public function getRemovedLinks() {
1166  if ( $this->linkDeletions === null ) {
1167  return null;
1168  }
1169  $result = [];
1170  foreach ( $this->linkDeletions as $ns => $titles ) {
1171  foreach ( $titles as $title => $unused ) {
1172  $result[] = Title::makeTitle( $ns, $title );
1173  }
1174  }
1175 
1176  return $result;
1177  }
1178 
1185  public function getAddedExternalLinks() {
1186  if ( $this->externalLinkInsertions === null ) {
1187  return null;
1188  }
1189  return array_column( $this->externalLinkInsertions, 'el_to' );
1190  }
1191 
1198  public function getRemovedExternalLinks() {
1199  if ( $this->externalLinkDeletions === null ) {
1200  return null;
1201  }
1202  return array_keys( $this->externalLinkDeletions );
1203  }
1204 
1211  public function getAddedProperties() {
1213  }
1214 
1221  public function getRemovedProperties() {
1222  return $this->propertyDeletions;
1223  }
1224 
1228  private function updateLinksTimestamp() {
1229  if ( $this->mId ) {
1230  // The link updates made here only reflect the freshness of the parser output
1231  $timestamp = $this->mParserOutput->getCacheTime();
1232  $this->getDB()->update( 'page',
1233  [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ],
1234  [ 'page_id' => $this->mId ],
1235  __METHOD__
1236  );
1237  }
1238  }
1239 
1243  protected function getDB() {
1244  if ( !$this->db ) {
1245  $this->db = wfGetDB( DB_PRIMARY );
1246  }
1247 
1248  return $this->db;
1249  }
1250 
1257  public function isRecursive() {
1258  return $this->mRecursive;
1259  }
1260 }
LinksUpdate\getInterlangInsertions
getInterlangInsertions( $existing=[])
Get an array of interlanguage link insertions.
Definition: LinksUpdate.php:699
LinksUpdate\getTemplateInsertions
getTemplateInsertions( $existing=[])
Get an array of template insertions.
Definition: LinksUpdate.php:589
DataUpdate\getCauseAgent
getCauseAgent()
Definition: DataUpdate.php:72
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
LinksUpdate\invalidateImageDescriptions
invalidateImageDescriptions(array $images)
Definition: LinksUpdate.php:467
LinksUpdate\getTemplateDeletions
getTemplateDeletions( $existing)
Given an array of existing templates, returns those templates which are not in $this and thus should ...
Definition: LinksUpdate.php:835
LinksUpdate\$externalLinkInsertions
null array[] $externalLinkInsertions
Added external links if calculated.
Definition: LinksUpdate.php:100
LinksUpdate\getCategoryDeletions
getCategoryDeletions( $existing)
Given an array of existing categories, returns those categories which are not in $this and thus shoul...
Definition: LinksUpdate.php:874
LinksUpdate\getTriggeringUser
getTriggeringUser()
Get the user who triggered this LinksUpdate.
Definition: LinksUpdate.php:1112
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
LinksUpdate\doUpdate
doUpdate()
Update link tables with outgoing links from an updated article.
Definition: LinksUpdate.php:174
ParserOutput
Definition: ParserOutput.php:31
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:229
LinksUpdate\isRecursive
isRecursive()
Whether or not this LinksUpdate will also update pages which transclude the current page or otherwise...
Definition: LinksUpdate.php:1257
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:193
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
LinksUpdate\getRemovedProperties
getRemovedProperties()
Fetch page properties removed by this LinksUpdate.
Definition: LinksUpdate.php:1221
LinksUpdate\$mInterwikis
array $mInterwikis
2-D map of (prefix => DBK => 1)
Definition: LinksUpdate.php:75
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:903
ParserOutput\getImages
& getImages()
Definition: ParserOutput.php:622
LinksUpdate\$mId
int $mId
Page ID of the article linked from.
Definition: LinksUpdate.php:45
LinksUpdate\$linkInsertions
array[] null $linkInsertions
Added links if calculated.
Definition: LinksUpdate.php:90
LinksUpdate\$mCategories
array $mCategories
Map of category names to sort keys.
Definition: LinksUpdate.php:69
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:119
LinksUpdate\$externalLinkDeletions
null array $externalLinkDeletions
Deleted external links if calculated.
Definition: LinksUpdate.php:105
LinksUpdate
Class the manages updates of *_link tables as well as similar extension-managed tables.
Definition: LinksUpdate.php:39
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
LinksUpdate\$mImages
array $mImages
DB keys of the images used, in the array key only.
Definition: LinksUpdate.php:60
LinksUpdate\$db
IDatabase $db
Definition: LinksUpdate.php:123
BacklinkCache
Class for fetching backlink lists, approximate backlink counts and partitions.
Definition: BacklinkCache.php:50
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:854
$res
$res
Definition: testCompression.php:57
LinksUpdate\updateLinksTimestamp
updateLinksTimestamp()
Update links table freshness.
Definition: LinksUpdate.php:1228
RefreshLinksJob\newPrioritized
static newPrioritized(PageIdentity $page, array $params)
Definition: RefreshLinksJob.php:70
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
LinksUpdate\$propertyInsertions
null array $propertyInsertions
Added properties if calculated.
Definition: LinksUpdate.php:110
DataUpdate
Abstract base class for update jobs that do something with some secondary data extracted from article...
Definition: DataUpdate.php:30
ParserOutput\getProperties
getProperties()
Definition: ParserOutput.php:1173
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:864
LinksUpdate\queueRecursiveJobs
queueRecursiveJobs()
Queue recursive jobs for this page.
Definition: LinksUpdate.php:356
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:959
LinkFilter\makeIndexes
static makeIndexes( $url)
Converts a URL into a format for el_index.
Definition: LinkFilter.php:173
LinksUpdate\incrTableUpdate
incrTableUpdate( $table, $prefix, $deletions, $insertions)
Update a table by doing a delete query then an insert query.
Definition: LinksUpdate.php:480
Title\castFromPageIdentity
static castFromPageIdentity(?PageIdentity $pageIdentity)
Return a Title for a given PageIdentity.
Definition: Title.php:332
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:565
LinksUpdate\getExistingTemplates
getExistingTemplates()
Get an array of existing templates, as a 2-D array.
Definition: LinksUpdate.php:940
LinksUpdate\__construct
__construct(PageIdentity $page, ParserOutput $parserOutput, $recursive=true)
Definition: LinksUpdate.php:132
LinksUpdate\getCategoryInsertions
getCategoryInsertions( $existing=[])
Get an array of category insertions.
Definition: LinksUpdate.php:656
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1028
LinksUpdate\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted)
Update all the appropriate counts in the category table.
Definition: LinksUpdate.php:437
LinksUpdate\$mTemplates
array $mTemplates
Map of title strings to IDs for the template references, including broken ones.
Definition: LinksUpdate.php:63
LinksUpdate\doIncrementalUpdate
doIncrementalUpdate()
Definition: LinksUpdate.php:244
LinksUpdate\$mProperties
array $mProperties
Map of arbitrary name to value.
Definition: LinksUpdate.php:78
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:884
ParserOutput\getInterwikiLinks
getInterwikiLinks()
Definition: ParserOutput.php:574
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2202
LinksUpdate\invalidateProperties
invalidateProperties( $changed)
Invalidate any necessary link lists related to page property changes.
Definition: LinksUpdate.php:1120
ChangeTags\getSoftwareTags
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
Definition: ChangeTags.php:158
$title
$title
Definition: testCompression.php:38
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:9734
LinksUpdate\getAddedExternalLinks
getAddedExternalLinks()
Fetch external links added by this LinksUpdate.
Definition: LinksUpdate.php:1185
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:651
LinksUpdate\getExistingProperties
getExistingProperties()
Get an array of existing categories, with the name in the key and sort key in the value.
Definition: LinksUpdate.php:1042
LinksUpdate\getRemovedExternalLinks
getRemovedExternalLinks()
Fetch external links removed by this LinksUpdate.
Definition: LinksUpdate.php:1198
LinksUpdate\$mTitle
Title $mTitle
Title object of the article linked from.
Definition: LinksUpdate.php:48
LinksUpdate\getTitle
getTitle()
Return the title object of the page being updated.
Definition: LinksUpdate.php:1057
Job\newRootJobParams
static newRootJobParams( $key)
Get "root job" parameters for a task.
Definition: Job.php:345
LinksUpdate\getImageInsertions
getImageInsertions( $existing=[])
Get an array of image insertions Skips the names specified in $existing.
Definition: LinksUpdate.php:612
ParserOutput\getLanguageLinks
& getLanguageLinks()
Definition: ParserOutput.php:570
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:1008
ParserOutput\getExternalLinks
& getExternalLinks()
Definition: ParserOutput.php:630
LinksUpdate\$mParserOutput
ParserOutput $mParserOutput
Definition: LinksUpdate.php:51
LinksUpdate\getParserOutput
getParserOutput()
Returns parser output.
Definition: LinksUpdate.php:1066
LinksUpdate\getAddedLinks
getAddedLinks()
Fetch page links added by this LinksUpdate.
Definition: LinksUpdate.php:1148
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:677
RefreshLinksJob
Job to update link tables for pages.
Definition: RefreshLinksJob.php:43
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:781
LinksUpdate\getAddedProperties
getAddedProperties()
Fetch page properties added by this LinksUpdate.
Definition: LinksUpdate.php:1211
LinksUpdate\$user
UserIdentity null $user
Definition: LinksUpdate.php:120
LinksUpdate\$mLinks
int[][] $mLinks
Map of title strings to IDs for the links in the document.
Definition: LinksUpdate.php:57
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
Wikimedia\Rdbms\IDatabase\getScopedLockAndFlush
getScopedLockAndFlush( $lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object.
HTMLCacheUpdateJob\newForBacklinks
static newForBacklinks(PageReference $page, $table, $params=[])
Definition: HTMLCacheUpdateJob.php:61
LinksUpdate\getExistingCategories
getExistingCategories()
Get an array of existing categories, with the name in the key and sort key in the value.
Definition: LinksUpdate.php:991
LinksUpdate\setRevisionRecord
setRevisionRecord(RevisionRecord $revisionRecord)
Set the RevisionRecord corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1084
LinksUpdate\$linkDeletions
null array $linkDeletions
Deleted links if calculated.
Definition: LinksUpdate.php:95
LinksUpdate\getPagePropRowData
getPagePropRowData( $prop)
Returns an associative array to be used for inserting a row into the page_props table.
Definition: LinksUpdate.php:744
LinksUpdate\getExistingExternals
getExistingExternals()
Get an array of existing external links, URLs in the keys.
Definition: LinksUpdate.php:975
LinksUpdate\getPropertyDeletions
getPropertyDeletions( $existing)
Get array of properties which should be deleted.
Definition: LinksUpdate.php:893
ParserOutput\getTemplates
& getTemplates()
Definition: ParserOutput.php:614
LinksUpdate\getImages
getImages()
Return the list of images used as generated by the parser.
Definition: LinksUpdate.php:1074
LinksUpdate\$mInterlangs
array $mInterlangs
Map of language codes to titles.
Definition: LinksUpdate.php:72
Title
Represents a title within MediaWiki.
Definition: Title.php:48
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:114
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(PageIdentity $page, $table, $action='unknown', $userName='unknown', ?BacklinkCache $backlinkCache=null)
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:399
LinksUpdate\getRemovedLinks
getRemovedLinks()
Fetch page links removed by this LinksUpdate.
Definition: LinksUpdate.php:1165
LinksUpdate\$mExternals
array $mExternals
URLs of external links, array key only.
Definition: LinksUpdate.php:66
LinksUpdate\getRevisionRecord
getRevisionRecord()
Definition: LinksUpdate.php:1092
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:49
$wgPagePropLinkInvalidations
$wgPagePropLinkInvalidations
Page property link table invalidation lists.
Definition: DefaultSettings.php:8806
LinksUpdate\getPropertyInsertions
getPropertyInsertions( $existing=[])
Get an array of page property insertions.
Definition: LinksUpdate.php:718
ParserOutput\getCategories
& getCategories()
Definition: ParserOutput.php:582
LinksUpdate\getExternalInsertions
getExternalInsertions( $existing=[])
Get an array of externallinks insertions.
Definition: LinksUpdate.php:631
LinksUpdate\$mRecursive
bool $mRecursive
Whether to queue jobs for recursive updates.
Definition: LinksUpdate.php:81
LinksUpdate\$propertyDeletions
null array $propertyDeletions
Deleted properties if calculated.
Definition: LinksUpdate.php:115
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
LinksUpdate\invalidateCategories
invalidateCategories( $cats)
Definition: LinksUpdate.php:426
LinksUpdate\$mRevisionRecord
RevisionRecord $mRevisionRecord
Revision for which this update has been triggered.
Definition: LinksUpdate.php:84
LinksUpdate\setTriggeringUser
setTriggeringUser(UserIdentity $user)
Set the user who triggered this LinksUpdate.
Definition: LinksUpdate.php:1102
$wgCategoryCollation
$wgCategoryCollation
Specify how category names should be sorted, when listed on a category page.
Definition: DefaultSettings.php:8857
DataUpdate\getCauseAction
getCauseAction()
Definition: DataUpdate.php:65
LinksUpdate\getExistingLinks
getExistingLinks()
Get an array of existing links, as a 2-D array.
Definition: LinksUpdate.php:921
NS_FILE
const NS_FILE
Definition: Defines.php:70
LinksUpdate\getDB
getDB()
Definition: LinksUpdate.php:1243
LinksUpdate\getImageAdditions
getImageAdditions( $existing)
Given an array of existing images, returns $this images that are not in there and thus should be adde...
Definition: LinksUpdate.php:806
LinksUpdate\getPropertySortKeyValue
getPropertySortKeyValue( $value)
Determines the sort key for the given property value.
Definition: LinksUpdate.php:767
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:816
ParserOutput\getLinks
& getLinks()
Definition: ParserOutput.php:602
ChangeTags\addTags
static addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params=null, RecentChange $rc=null)
Add tags to a change given its rc_id, rev_id and/or log_id.
Definition: ChangeTags.php:328
LinksUpdate\getExistingInterwikis
getExistingInterwikis()
Get an array of existing inline interwiki links, as a 2-D array.
Definition: LinksUpdate.php:1023
$type
$type
Definition: testCompression.php:52