MediaWiki  master
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 
51  public $mLinks;
52 
54  public $mImages;
55 
57  public $mTemplates;
58 
60  public $mExternals;
61 
63  public $mCategories;
64 
66  public $mInterlangs;
67 
69  public $mInterwikis;
70 
72  public $mProperties;
73 
75  public $mRecursive;
76 
78  private $mRevision;
79 
84  private $linkInsertions = null;
85 
89  private $linkDeletions = null;
90 
94  private $externalLinkInsertions = null;
95 
99  private $externalLinkDeletions = null;
100 
104  private $propertyInsertions = null;
105 
109  private $propertyDeletions = null;
110 
114  private $user;
115 
117  private $db;
118 
125  public function __construct( Title $title, ParserOutput $parserOutput, $recursive = true ) {
126  parent::__construct();
127 
128  $this->mTitle = $title;
129 
130  if ( !$this->mId ) {
131  // NOTE: subclasses may initialize mId before calling this constructor!
132  $this->mId = $title->getArticleID( Title::READ_LATEST );
133  }
134 
135  if ( !$this->mId ) {
136  throw new InvalidArgumentException(
137  "The Title object yields no ID. Perhaps the page doesn't exist?"
138  );
139  }
140 
141  $this->mParserOutput = $parserOutput;
142 
143  $this->mLinks = $parserOutput->getLinks();
144  $this->mImages = $parserOutput->getImages();
145  $this->mTemplates = $parserOutput->getTemplates();
146  $this->mExternals = $parserOutput->getExternalLinks();
147  $this->mCategories = $parserOutput->getCategories();
148  $this->mProperties = $parserOutput->getProperties();
149  $this->mInterwikis = $parserOutput->getInterwikiLinks();
150 
151  # Convert the format of the interlanguage links
152  # I didn't want to change it in the ParserOutput, because that array is passed all
153  # the way back to the skin, so either a skin API break would be required, or an
154  # inefficient back-conversion.
155  $ill = $parserOutput->getLanguageLinks();
156  $this->mInterlangs = [];
157  foreach ( $ill as $link ) {
158  list( $key, $title ) = explode( ':', $link, 2 );
159  $this->mInterlangs[$key] = $title;
160  }
161 
162  foreach ( $this->mCategories as &$sortkey ) {
163  # If the sortkey is longer then 255 bytes, it is truncated by DB, and then doesn't match
164  # when comparing existing vs current categories, causing T27254.
165  $sortkey = mb_strcut( $sortkey, 0, 255 );
166  }
167 
168  $this->mRecursive = $recursive;
169 
170  // Avoid PHP 7.1 warning from passing $this by reference
171  $linksUpdate = $this;
172  Hooks::run( 'LinksUpdateConstructed', [ &$linksUpdate ] );
173  }
174 
180  public function doUpdate() {
181  if ( $this->ticket ) {
182  // Make sure all links update threads see the changes of each other.
183  // This handles the case when updates have to batched into several COMMITs.
184  $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId );
185  if ( !$scopedLock ) {
186  throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." );
187  }
188  }
189 
190  // Avoid PHP 7.1 warning from passing $this by reference
191  $linksUpdate = $this;
192  Hooks::run( 'LinksUpdate', [ &$linksUpdate ] );
193  $this->doIncrementalUpdate();
194 
195  // Commit and release the lock (if set)
196  ScopedCallback::consume( $scopedLock );
197  // Run post-commit hook handlers without DBO_TRX
199  $this->getDB(),
200  __METHOD__,
201  function () {
202  // Avoid PHP 7.1 warning from passing $this by reference
203  $linksUpdate = $this;
204  Hooks::run( 'LinksUpdateComplete', [ &$linksUpdate, $this->ticket ] );
205  }
206  ) );
207  }
208 
218  public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) {
219  $key = "{$dbw->getDomainID()}:LinksUpdate:$why:pageid:$pageId"; // per-wiki
220  $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 );
221  if ( !$scopedLock ) {
222  $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' );
223  $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [
224  'key' => $key,
225  'page_id' => $pageId,
226  ] );
227  return null;
228  }
229 
230  return $scopedLock;
231  }
232 
233  protected function doIncrementalUpdate() {
234  # Page links
235  $existingPL = $this->getExistingLinks();
236  $this->linkDeletions = $this->getLinkDeletions( $existingPL );
237  $this->linkInsertions = $this->getLinkInsertions( $existingPL );
238  $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
239 
240  # Image links
241  $existingIL = $this->getExistingImages();
242  $imageDeletes = $this->getImageDeletions( $existingIL );
243  $this->incrTableUpdate(
244  'imagelinks',
245  'il',
246  $imageDeletes,
247  $this->getImageInsertions( $existingIL ) );
248 
249  # Invalidate all image description pages which had links added or removed
250  $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existingIL );
251  $this->invalidateImageDescriptions( $imageUpdates );
252 
253  # External links
254  $existingEL = $this->getExistingExternals();
255  $this->externalLinkDeletions = $this->getExternalDeletions( $existingEL );
256  $this->externalLinkInsertions = $this->getExternalInsertions(
257  $existingEL );
258  $this->incrTableUpdate(
259  'externallinks',
260  'el',
261  $this->externalLinkDeletions,
262  $this->externalLinkInsertions );
263 
264  # Language links
265  $existingLL = $this->getExistingInterlangs();
266  $this->incrTableUpdate(
267  'langlinks',
268  'll',
269  $this->getInterlangDeletions( $existingLL ),
270  $this->getInterlangInsertions( $existingLL ) );
271 
272  # Inline interwiki links
273  $existingIW = $this->getExistingInterwikis();
274  $this->incrTableUpdate(
275  'iwlinks',
276  'iwl',
277  $this->getInterwikiDeletions( $existingIW ),
278  $this->getInterwikiInsertions( $existingIW ) );
279 
280  # Template links
281  $existingTL = $this->getExistingTemplates();
282  $this->incrTableUpdate(
283  'templatelinks',
284  'tl',
285  $this->getTemplateDeletions( $existingTL ),
286  $this->getTemplateInsertions( $existingTL ) );
287 
288  # Category links
289  $existingCL = $this->getExistingCategories();
290  $categoryDeletes = $this->getCategoryDeletions( $existingCL );
291  $this->incrTableUpdate(
292  'categorylinks',
293  'cl',
294  $categoryDeletes,
295  $this->getCategoryInsertions( $existingCL ) );
296  $categoryInserts = array_diff_assoc( $this->mCategories, $existingCL );
297  $categoryUpdates = $categoryInserts + $categoryDeletes;
298 
299  # Page properties
300  $existingPP = $this->getExistingProperties();
301  $this->propertyDeletions = $this->getPropertyDeletions( $existingPP );
302  $this->incrTableUpdate(
303  'page_props',
304  'pp',
305  $this->propertyDeletions,
306  $this->getPropertyInsertions( $existingPP ) );
307 
308  # Invalidate the necessary pages
309  $this->propertyInsertions = array_diff_assoc( $this->mProperties, $existingPP );
310  $changed = $this->propertyDeletions + $this->propertyInsertions;
311  $this->invalidateProperties( $changed );
312 
313  # Invalidate all categories which were added, deleted or changed (set symmetric difference)
314  $this->invalidateCategories( $categoryUpdates );
315  $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
316 
317  # Refresh links of all pages including this page
318  # This will be in a separate transaction
319  if ( $this->mRecursive ) {
320  $this->queueRecursiveJobs();
321  }
322 
323  # Update the links table freshness for this title
324  $this->updateLinksTimestamp();
325  }
326 
333  protected function queueRecursiveJobs() {
334  $action = $this->getCauseAction();
335  $agent = $this->getCauseAgent();
336 
337  self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks', $action, $agent );
338  if ( $this->mTitle->getNamespace() == NS_FILE ) {
339  // Process imagelinks in case the title is or was a redirect
340  self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks', $action, $agent );
341  }
342 
343  $bc = $this->mTitle->getBacklinkCache();
344  // Get jobs for cascade-protected backlinks for a high priority queue.
345  // If meta-templates change to using a new template, the new template
346  // should be implicitly protected as soon as possible, if applicable.
347  // These jobs duplicate a subset of the above ones, but can run sooner.
348  // Which ever runs first generally no-ops the other one.
349  $jobs = [];
350  foreach ( $bc->getCascadeProtectedLinks() as $title ) {
352  $title,
353  [
354  'causeAction' => $action,
355  'causeAgent' => $agent
356  ]
357  );
358  }
359  JobQueueGroup::singleton()->push( $jobs );
360  }
361 
370  public static function queueRecursiveJobsForTable(
371  Title $title, $table, $action = 'unknown', $userName = 'unknown'
372  ) {
373  if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
374  $job = new RefreshLinksJob(
375  $title,
376  [
377  'table' => $table,
378  'recursive' => true,
379  ] + Job::newRootJobParams( // "overall" refresh links job info
380  "refreshlinks:{$table}:{$title->getPrefixedText()}"
381  ) + [ 'causeAction' => $action, 'causeAgent' => $userName ]
382  );
383 
384  JobQueueGroup::singleton()->push( $job );
385  }
386  }
387 
391  private function invalidateCategories( $cats ) {
393  $this->getDB(), NS_CATEGORY, array_map( 'strval', array_keys( $cats ) )
394  );
395  }
396 
402  private function updateCategoryCounts( array $added, array $deleted ) {
403  global $wgUpdateRowsPerQuery;
404 
405  if ( !$added && !$deleted ) {
406  return;
407  }
408 
409  $domainId = $this->getDB()->getDomainID();
410  $wp = WikiPage::factory( $this->mTitle );
411  $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
412  // T163801: try to release any row locks to reduce contention
413  $lbf->commitAndWaitForReplication( __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
414 
415  foreach ( array_chunk( array_keys( $added ), $wgUpdateRowsPerQuery ) as $addBatch ) {
416  $wp->updateCategoryCounts( array_map( 'strval', $addBatch ), [], $this->mId );
417  $lbf->commitAndWaitForReplication(
418  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
419  }
420 
421  foreach ( array_chunk( array_keys( $deleted ), $wgUpdateRowsPerQuery ) as $deleteBatch ) {
422  $wp->updateCategoryCounts( [], array_map( 'strval', $deleteBatch ), $this->mId );
423  $lbf->commitAndWaitForReplication(
424  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
425  }
426  }
427 
431  private function invalidateImageDescriptions( array $images ) {
433  $this->getDB(), NS_FILE, array_map( 'strval', array_keys( $images ) )
434  );
435  }
436 
444  private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
445  $services = MediaWikiServices::getInstance();
446  $bSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
447  $lbf = $services->getDBLoadBalancerFactory();
448 
449  if ( $table === 'page_props' ) {
450  $fromField = 'pp_page';
451  } else {
452  $fromField = "{$prefix}_from";
453  }
454 
455  $deleteWheres = []; // list of WHERE clause arrays for each DB delete() call
456  if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) {
457  $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
458 
459  $curBatchSize = 0;
460  $curDeletionBatch = [];
461  $deletionBatches = [];
462  foreach ( $deletions as $ns => $dbKeys ) {
463  foreach ( $dbKeys as $dbKey => $unused ) {
464  $curDeletionBatch[$ns][$dbKey] = 1;
465  if ( ++$curBatchSize >= $bSize ) {
466  $deletionBatches[] = $curDeletionBatch;
467  $curDeletionBatch = [];
468  $curBatchSize = 0;
469  }
470  }
471  }
472  if ( $curDeletionBatch ) {
473  $deletionBatches[] = $curDeletionBatch;
474  }
475 
476  foreach ( $deletionBatches as $deletionBatch ) {
477  $deleteWheres[] = [
478  $fromField => $this->mId,
479  $this->getDB()->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
480  ];
481  }
482  } else {
483  if ( $table === 'langlinks' ) {
484  $toField = 'll_lang';
485  } elseif ( $table === 'page_props' ) {
486  $toField = 'pp_propname';
487  } else {
488  $toField = $prefix . '_to';
489  }
490 
491  $deletionBatches = array_chunk( array_keys( $deletions ), $bSize );
492  foreach ( $deletionBatches as $deletionBatch ) {
493  $deleteWheres[] = [
494  $fromField => $this->mId,
495  $toField => array_map( 'strval', $deletionBatch )
496  ];
497  }
498  }
499 
500  $domainId = $this->getDB()->getDomainID();
501 
502  foreach ( $deleteWheres as $deleteWhere ) {
503  $this->getDB()->delete( $table, $deleteWhere, __METHOD__ );
504  $lbf->commitAndWaitForReplication(
505  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
506  );
507  }
508 
509  $insertBatches = array_chunk( $insertions, $bSize );
510  foreach ( $insertBatches as $insertBatch ) {
511  $this->getDB()->insert( $table, $insertBatch, __METHOD__, [ 'IGNORE' ] );
512  $lbf->commitAndWaitForReplication(
513  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
514  );
515  }
516 
517  if ( count( $insertions ) ) {
518  Hooks::run( 'LinksUpdateAfterInsert', [ $this, $table, $insertions ] );
519  }
520  }
521 
529  private function getLinkInsertions( $existing = [] ) {
530  $arr = [];
531  foreach ( $this->mLinks as $ns => $dbkeys ) {
532  $diffs = isset( $existing[$ns] )
533  ? array_diff_key( $dbkeys, $existing[$ns] )
534  : $dbkeys;
535  foreach ( $diffs as $dbk => $id ) {
536  $arr[] = [
537  'pl_from' => $this->mId,
538  'pl_from_namespace' => $this->mTitle->getNamespace(),
539  'pl_namespace' => $ns,
540  'pl_title' => $dbk
541  ];
542  }
543  }
544 
545  return $arr;
546  }
547 
553  private function getTemplateInsertions( $existing = [] ) {
554  $arr = [];
555  foreach ( $this->mTemplates as $ns => $dbkeys ) {
556  $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
557  foreach ( $diffs as $dbk => $id ) {
558  $arr[] = [
559  'tl_from' => $this->mId,
560  'tl_from_namespace' => $this->mTitle->getNamespace(),
561  'tl_namespace' => $ns,
562  'tl_title' => $dbk
563  ];
564  }
565  }
566 
567  return $arr;
568  }
569 
576  private function getImageInsertions( $existing = [] ) {
577  $arr = [];
578  $diffs = array_diff_key( $this->mImages, $existing );
579  foreach ( $diffs as $iname => $dummy ) {
580  $arr[] = [
581  'il_from' => $this->mId,
582  'il_from_namespace' => $this->mTitle->getNamespace(),
583  'il_to' => $iname
584  ];
585  }
586 
587  return $arr;
588  }
589 
595  private function getExternalInsertions( $existing = [] ) {
596  $arr = [];
597  $diffs = array_diff_key( $this->mExternals, $existing );
598  foreach ( $diffs as $url => $dummy ) {
599  foreach ( LinkFilter::makeIndexes( $url ) as $index ) {
600  $arr[] = [
601  'el_from' => $this->mId,
602  'el_to' => $url,
603  'el_index' => $index,
604  'el_index_60' => substr( $index, 0, 60 ),
605  ];
606  }
607  }
608 
609  return $arr;
610  }
611 
620  private function getCategoryInsertions( $existing = [] ) {
621  global $wgCategoryCollation;
622  $diffs = array_diff_assoc( $this->mCategories, $existing );
623  $arr = [];
624  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
625  $collation = Collation::singleton();
626  foreach ( $diffs as $name => $prefix ) {
627  $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
628  $contLang->findVariantLink( $name, $nt, true );
629 
630  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
631  getCategoryLinkType( $this->mTitle->getNamespace() );
632 
633  # Treat custom sortkeys as a prefix, so that if multiple
634  # things are forced to sort as '*' or something, they'll
635  # sort properly in the category rather than in page_id
636  # order or such.
637  $sortkey = $collation->getSortKey( $this->mTitle->getCategorySortkey( $prefix ) );
638 
639  $arr[] = [
640  'cl_from' => $this->mId,
641  'cl_to' => $name,
642  'cl_sortkey' => $sortkey,
643  'cl_timestamp' => $this->getDB()->timestamp(),
644  'cl_sortkey_prefix' => $prefix,
645  'cl_collation' => $wgCategoryCollation,
646  'cl_type' => $type,
647  ];
648  }
649 
650  return $arr;
651  }
652 
660  private function getInterlangInsertions( $existing = [] ) {
661  $diffs = array_diff_assoc( $this->mInterlangs, $existing );
662  $arr = [];
663  foreach ( $diffs as $lang => $title ) {
664  $arr[] = [
665  'll_from' => $this->mId,
666  'll_lang' => $lang,
667  'll_title' => $title
668  ];
669  }
670 
671  return $arr;
672  }
673 
679  function getPropertyInsertions( $existing = [] ) {
680  $diffs = array_diff_assoc( $this->mProperties, $existing );
681 
682  $arr = [];
683  foreach ( array_keys( $diffs ) as $name ) {
684  $arr[] = $this->getPagePropRowData( (string)$name );
685  }
686 
687  return $arr;
688  }
689 
706  private function getPagePropRowData( $prop ) {
708 
709  $value = $this->mProperties[$prop];
710 
711  $row = [
712  'pp_page' => $this->mId,
713  'pp_propname' => $prop,
714  'pp_value' => $value,
715  ];
716 
717  if ( $wgPagePropsHaveSortkey ) {
718  $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value );
719  }
720 
721  return $row;
722  }
723 
736  private function getPropertySortKeyValue( $value ) {
737  if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
738  return floatval( $value );
739  }
740 
741  return null;
742  }
743 
750  private function getInterwikiInsertions( $existing = [] ) {
751  $arr = [];
752  foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
753  $diffs = isset( $existing[$prefix] )
754  ? array_diff_key( $dbkeys, $existing[$prefix] )
755  : $dbkeys;
756 
757  foreach ( $diffs as $dbk => $id ) {
758  $arr[] = [
759  'iwl_from' => $this->mId,
760  'iwl_prefix' => $prefix,
761  'iwl_title' => $dbk
762  ];
763  }
764  }
765 
766  return $arr;
767  }
768 
775  private function getLinkDeletions( $existing ) {
776  $del = [];
777  foreach ( $existing as $ns => $dbkeys ) {
778  if ( isset( $this->mLinks[$ns] ) ) {
779  $del[$ns] = array_diff_key( $dbkeys, $this->mLinks[$ns] );
780  } else {
781  $del[$ns] = $dbkeys;
782  }
783  }
784 
785  return $del;
786  }
787 
794  private function getTemplateDeletions( $existing ) {
795  $del = [];
796  foreach ( $existing as $ns => $dbkeys ) {
797  if ( isset( $this->mTemplates[$ns] ) ) {
798  $del[$ns] = array_diff_key( $dbkeys, $this->mTemplates[$ns] );
799  } else {
800  $del[$ns] = $dbkeys;
801  }
802  }
803 
804  return $del;
805  }
806 
813  private function getImageDeletions( $existing ) {
814  return array_diff_key( $existing, $this->mImages );
815  }
816 
823  private function getExternalDeletions( $existing ) {
824  return array_diff_key( $existing, $this->mExternals );
825  }
826 
833  private function getCategoryDeletions( $existing ) {
834  return array_diff_assoc( $existing, $this->mCategories );
835  }
836 
843  private function getInterlangDeletions( $existing ) {
844  return array_diff_assoc( $existing, $this->mInterlangs );
845  }
846 
852  private function getPropertyDeletions( $existing ) {
853  return array_diff_assoc( $existing, $this->mProperties );
854  }
855 
862  private function getInterwikiDeletions( $existing ) {
863  $del = [];
864  foreach ( $existing as $prefix => $dbkeys ) {
865  if ( isset( $this->mInterwikis[$prefix] ) ) {
866  $del[$prefix] = array_diff_key( $dbkeys, $this->mInterwikis[$prefix] );
867  } else {
868  $del[$prefix] = $dbkeys;
869  }
870  }
871 
872  return $del;
873  }
874 
880  private function getExistingLinks() {
881  $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
882  [ 'pl_from' => $this->mId ], __METHOD__ );
883  $arr = [];
884  foreach ( $res as $row ) {
885  if ( !isset( $arr[$row->pl_namespace] ) ) {
886  $arr[$row->pl_namespace] = [];
887  }
888  $arr[$row->pl_namespace][$row->pl_title] = 1;
889  }
890 
891  return $arr;
892  }
893 
899  private function getExistingTemplates() {
900  $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
901  [ 'tl_from' => $this->mId ], __METHOD__ );
902  $arr = [];
903  foreach ( $res as $row ) {
904  if ( !isset( $arr[$row->tl_namespace] ) ) {
905  $arr[$row->tl_namespace] = [];
906  }
907  $arr[$row->tl_namespace][$row->tl_title] = 1;
908  }
909 
910  return $arr;
911  }
912 
918  private function getExistingImages() {
919  $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ],
920  [ 'il_from' => $this->mId ], __METHOD__ );
921  $arr = [];
922  foreach ( $res as $row ) {
923  $arr[$row->il_to] = 1;
924  }
925 
926  return $arr;
927  }
928 
934  private function getExistingExternals() {
935  $res = $this->getDB()->select( 'externallinks', [ 'el_to' ],
936  [ 'el_from' => $this->mId ], __METHOD__ );
937  $arr = [];
938  foreach ( $res as $row ) {
939  $arr[$row->el_to] = 1;
940  }
941 
942  return $arr;
943  }
944 
950  private function getExistingCategories() {
951  $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
952  [ 'cl_from' => $this->mId ], __METHOD__ );
953  $arr = [];
954  foreach ( $res as $row ) {
955  $arr[$row->cl_to] = $row->cl_sortkey_prefix;
956  }
957 
958  return $arr;
959  }
960 
967  private function getExistingInterlangs() {
968  $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ],
969  [ 'll_from' => $this->mId ], __METHOD__ );
970  $arr = [];
971  foreach ( $res as $row ) {
972  $arr[$row->ll_lang] = $row->ll_title;
973  }
974 
975  return $arr;
976  }
977 
982  private function getExistingInterwikis() {
983  $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
984  [ 'iwl_from' => $this->mId ], __METHOD__ );
985  $arr = [];
986  foreach ( $res as $row ) {
987  if ( !isset( $arr[$row->iwl_prefix] ) ) {
988  $arr[$row->iwl_prefix] = [];
989  }
990  $arr[$row->iwl_prefix][$row->iwl_title] = 1;
991  }
992 
993  return $arr;
994  }
995 
1001  private function getExistingProperties() {
1002  $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ],
1003  [ 'pp_page' => $this->mId ], __METHOD__ );
1004  $arr = [];
1005  foreach ( $res as $row ) {
1006  $arr[$row->pp_propname] = $row->pp_value;
1007  }
1008 
1009  return $arr;
1010  }
1011 
1016  public function getTitle() {
1017  return $this->mTitle;
1018  }
1019 
1025  public function getParserOutput() {
1026  return $this->mParserOutput;
1027  }
1028 
1033  public function getImages() {
1034  return $this->mImages;
1035  }
1036 
1044  public function setRevision( Revision $revision ) {
1045  $this->mRevision = $revision;
1046  }
1047 
1052  public function getRevision() {
1053  return $this->mRevision;
1054  }
1055 
1062  public function setTriggeringUser( User $user ) {
1063  $this->user = $user;
1064  }
1065 
1070  public function getTriggeringUser() {
1071  return $this->user;
1072  }
1073 
1078  private function invalidateProperties( $changed ) {
1080 
1081  $jobs = [];
1082  foreach ( $changed as $name => $value ) {
1083  if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
1084  $inv = $wgPagePropLinkInvalidations[$name];
1085  if ( !is_array( $inv ) ) {
1086  $inv = [ $inv ];
1087  }
1088  foreach ( $inv as $table ) {
1090  $this->mTitle,
1091  $table,
1092  [ 'causeAction' => 'page-props' ]
1093  );
1094  }
1095  }
1096  }
1097 
1098  JobQueueGroup::singleton()->lazyPush( $jobs );
1099  }
1100 
1106  public function getAddedLinks() {
1107  if ( $this->linkInsertions === null ) {
1108  return null;
1109  }
1110  $result = [];
1111  foreach ( $this->linkInsertions as $insertion ) {
1112  $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
1113  }
1114 
1115  return $result;
1116  }
1117 
1123  public function getRemovedLinks() {
1124  if ( $this->linkDeletions === null ) {
1125  return null;
1126  }
1127  $result = [];
1128  foreach ( $this->linkDeletions as $ns => $titles ) {
1129  foreach ( $titles as $title => $unused ) {
1130  $result[] = Title::makeTitle( $ns, $title );
1131  }
1132  }
1133 
1134  return $result;
1135  }
1136 
1143  public function getAddedExternalLinks() {
1144  if ( $this->externalLinkInsertions === null ) {
1145  return null;
1146  }
1147  $result = [];
1148  foreach ( $this->externalLinkInsertions as $key => $value ) {
1149  $result[] = $value['el_to'];
1150  }
1151  return $result;
1152  }
1153 
1160  public function getRemovedExternalLinks() {
1161  if ( $this->externalLinkDeletions === null ) {
1162  return null;
1163  }
1164  return array_keys( $this->externalLinkDeletions );
1165  }
1166 
1173  public function getAddedProperties() {
1175  }
1176 
1183  public function getRemovedProperties() {
1184  return $this->propertyDeletions;
1185  }
1186 
1190  private function updateLinksTimestamp() {
1191  if ( $this->mId ) {
1192  // The link updates made here only reflect the freshness of the parser output
1193  $timestamp = $this->mParserOutput->getCacheTime();
1194  $this->getDB()->update( 'page',
1195  [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ],
1196  [ 'page_id' => $this->mId ],
1197  __METHOD__
1198  );
1199  }
1200  }
1201 
1205  protected function getDB() {
1206  if ( !$this->db ) {
1207  $this->db = wfGetDB( DB_MASTER );
1208  }
1209 
1210  return $this->db;
1211  }
1212 
1219  public function isRecursive() {
1220  return $this->mRecursive;
1221  }
1222 }
LinksUpdate\getInterlangInsertions
getInterlangInsertions( $existing=[])
Get an array of interlanguage link insertions.
Definition: LinksUpdate.php:660
LinksUpdate\getTemplateInsertions
getTemplateInsertions( $existing=[])
Get an array of template insertions.
Definition: LinksUpdate.php:553
DataUpdate\getCauseAgent
getCauseAgent()
Definition: DataUpdate.php:67
LinksUpdate\invalidateImageDescriptions
invalidateImageDescriptions(array $images)
Definition: LinksUpdate.php:431
LinksUpdate\getTemplateDeletions
getTemplateDeletions( $existing)
Given an array of existing templates, returns those templates which are not in $this and thus should ...
Definition: LinksUpdate.php:794
LinksUpdate\$externalLinkInsertions
null array[] $externalLinkInsertions
Added external links if calculated.
Definition: LinksUpdate.php:94
LinksUpdate\getCategoryDeletions
getCategoryDeletions( $existing)
Given an array of existing categories, returns those categories which are not in $this and thus shoul...
Definition: LinksUpdate.php:833
LinksUpdate\getTriggeringUser
getTriggeringUser()
Definition: LinksUpdate.php:1070
LinksUpdate\doUpdate
doUpdate()
Update link tables with outgoing links from an updated article.
Definition: LinksUpdate.php:180
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:218
LinksUpdate\isRecursive
isRecursive()
Whether or not this LinksUpdate will also update pages which transclude the current page or otherwise...
Definition: LinksUpdate.php:1219
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:35
LinksUpdate\getRemovedProperties
getRemovedProperties()
Fetch page properties removed by this LinksUpdate.
Definition: LinksUpdate.php:1183
LinksUpdate\$mInterwikis
array $mInterwikis
2-D map of (prefix => DBK => 1)
Definition: LinksUpdate.php:69
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:862
LinksUpdate\setRevision
setRevision(Revision $revision)
Set the revision corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1044
ParserOutput\getImages
& getImages()
Definition: ParserOutput.php:566
LinksUpdate\$mId
int $mId
Page ID of the article linked from.
Definition: LinksUpdate.php:39
LinksUpdate\$linkInsertions
array[] null $linkInsertions
Added links if calculated.
Definition: LinksUpdate.php:84
LinksUpdate\$mCategories
array $mCategories
Map of category names to sort keys.
Definition: LinksUpdate.php:63
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:99
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:54
LinksUpdate\$db
IDatabase $db
Definition: LinksUpdate.php:117
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:813
$res
$res
Definition: testCompression.php:54
LinksUpdate\updateLinksTimestamp
updateLinksTimestamp()
Update links table freshness.
Definition: LinksUpdate.php:1190
HTMLCacheUpdateJob\newForBacklinks
static newForBacklinks(Title $title, $table, $params=[])
Definition: HTMLCacheUpdateJob.php:59
LinksUpdate\$propertyInsertions
null array $propertyInsertions
Added properties if calculated.
Definition: LinksUpdate.php:104
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:1084
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:823
LinksUpdate\queueRecursiveJobs
queueRecursiveJobs()
Queue recursive jobs for this page.
Definition: LinksUpdate.php:333
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:918
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:444
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:529
Revision
Definition: Revision.php:40
LinksUpdate\getExistingTemplates
getExistingTemplates()
Get an array of existing templates, as a 2-D array.
Definition: LinksUpdate.php:899
LinksUpdate\getCategoryInsertions
getCategoryInsertions( $existing=[])
Get an array of category insertions.
Definition: LinksUpdate.php:620
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:141
LinksUpdate\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted)
Update all the appropriate counts in the category table.
Definition: LinksUpdate.php:402
LinksUpdate\$mTemplates
array $mTemplates
Map of title strings to IDs for the template references, including broken ones.
Definition: LinksUpdate.php:57
LinksUpdate\doIncrementalUpdate
doIncrementalUpdate()
Definition: LinksUpdate.php:233
LinksUpdate\$mProperties
array $mProperties
Map of arbitrary name to value.
Definition: LinksUpdate.php:72
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:843
ParserOutput\getInterwikiLinks
getInterwikiLinks()
Definition: ParserOutput.php:526
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2562
LinksUpdate\invalidateProperties
invalidateProperties( $changed)
Invalidate any necessary link lists related to page property changes.
Definition: LinksUpdate.php:1078
LinksUpdate\$mRevision
Revision $mRevision
Revision for which this update has been triggered.
Definition: LinksUpdate.php:78
$title
$title
Definition: testCompression.php:36
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:8462
LinksUpdate\getAddedExternalLinks
getAddedExternalLinks()
Fetch external links added by this LinksUpdate.
Definition: LinksUpdate.php:1143
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:584
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:1001
LinksUpdate\getRemovedExternalLinks
getRemovedExternalLinks()
Fetch external links removed by this LinksUpdate.
Definition: LinksUpdate.php:1160
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:1016
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:576
ParserOutput\getLanguageLinks
& getLanguageLinks()
Definition: ParserOutput.php:522
LinksUpdate\__construct
__construct(Title $title, ParserOutput $parserOutput, $recursive=true)
Definition: LinksUpdate.php:125
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:967
ParserOutput\getExternalLinks
& getExternalLinks()
Definition: ParserOutput.php:574
LinksUpdate\$mParserOutput
ParserOutput $mParserOutput
Definition: LinksUpdate.php:45
LinksUpdate\getParserOutput
getParserOutput()
Returns parser output.
Definition: LinksUpdate.php:1025
LinksUpdate\getAddedLinks
getAddedLinks()
Fetch page links added by this LinksUpdate.
Definition: LinksUpdate.php:1106
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:610
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:750
LinksUpdate\getAddedProperties
getAddedProperties()
Fetch page properties added by this LinksUpdate.
Definition: LinksUpdate.php:1173
LinksUpdate\$mLinks
int[][] $mLinks
Map of title strings to IDs for the links in the document @phan-var array<int,array<string,...
Definition: LinksUpdate.php:51
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:950
LinksUpdate\$linkDeletions
null array $linkDeletions
Deleted links if calculated.
Definition: LinksUpdate.php:89
LinksUpdate\getPagePropRowData
getPagePropRowData( $prop)
Returns an associative array to be used for inserting a row into the page_props table.
Definition: LinksUpdate.php:706
LinksUpdate\getExistingExternals
getExistingExternals()
Get an array of existing external links, URLs in the keys.
Definition: LinksUpdate.php:934
LinksUpdate\getPropertyDeletions
getPropertyDeletions( $existing)
Get array of properties which should be deleted.
Definition: LinksUpdate.php:852
ParserOutput\getTemplates
& getTemplates()
Definition: ParserOutput.php:558
LinksUpdate\getImages
getImages()
Return the list of images used as generated by the parser.
Definition: LinksUpdate.php:1033
LinksUpdate\$mInterlangs
array $mInterlangs
Map of language codes to titles.
Definition: LinksUpdate.php:66
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:1062
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
LinksUpdate\getRemovedLinks
getRemovedLinks()
Fetch page links removed by this LinksUpdate.
Definition: LinksUpdate.php:1123
LinksUpdate\$mExternals
array $mExternals
URLs of external links, array key only.
Definition: LinksUpdate.php:60
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:50
$wgPagePropLinkInvalidations
$wgPagePropLinkInvalidations
Page property link table invalidation lists.
Definition: DefaultSettings.php:7606
LinksUpdate\getPropertyInsertions
getPropertyInsertions( $existing=[])
Get an array of page property insertions.
Definition: LinksUpdate.php:679
ParserOutput\getCategories
& getCategories()
Definition: ParserOutput.php:534
LinksUpdate\getExternalInsertions
getExternalInsertions( $existing=[])
Get an array of externallinks insertions.
Definition: LinksUpdate.php:595
LinksUpdate\$mRecursive
bool $mRecursive
Whether to queue jobs for recursive updates.
Definition: LinksUpdate.php:75
LinksUpdate\$user
User null $user
Definition: LinksUpdate.php:114
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:370
LinksUpdate\$propertyDeletions
null array $propertyDeletions
Deleted properties if calculated.
Definition: LinksUpdate.php:109
LinksUpdate\invalidateCategories
invalidateCategories( $cats)
Definition: LinksUpdate.php:391
$wgCategoryCollation
$wgCategoryCollation
Specify how category names should be sorted, when listed on a category page.
Definition: DefaultSettings.php:7658
DataUpdate\getCauseAction
getCauseAction()
Definition: DataUpdate.php:60
LinksUpdate\getExistingLinks
getExistingLinks()
Get an array of existing links, as a 2-D array.
Definition: LinksUpdate.php:880
$wgPagePropsHaveSortkey
$wgPagePropsHaveSortkey
Whether the page_props table has a pp_sortkey column.
Definition: DefaultSettings.php:8641
LinksUpdate\getDB
getDB()
Definition: LinksUpdate.php:1205
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:52
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:1052
LinksUpdate\getPropertySortKeyValue
getPropertySortKeyValue( $value)
Determines the sort key for the given property value.
Definition: LinksUpdate.php:736
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:775
ParserOutput\getLinks
& getLinks()
Definition: ParserOutput.php:554
LinksUpdate\getExistingInterwikis
getExistingInterwikis()
Get an array of existing inline interwiki links, as a 2-D array.
Definition: LinksUpdate.php:982
$type
$type
Definition: testCompression.php:50