MediaWiki  master
LinksUpdate.php
Go to the documentation of this file.
1 <?php
23 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
28 use Wikimedia\ScopedCallback;
29 
37 class LinksUpdate extends DataUpdate {
38  use ProtectedHookAccessorTrait;
39 
40  // @todo make members protected, but make sure extensions don't break
41 
43  public $mId;
44 
46  public $mTitle;
47 
50 
55  public $mLinks;
56 
58  public $mImages;
59 
61  public $mTemplates;
62 
64  public $mExternals;
65 
67  public $mCategories;
68 
70  public $mInterlangs;
71 
73  public $mInterwikis;
74 
76  public $mProperties;
77 
79  public $mRecursive;
80 
83 
88  private $linkInsertions = null;
89 
93  private $linkDeletions = null;
94 
98  private $externalLinkInsertions = null;
99 
103  private $externalLinkDeletions = null;
104 
108  private $propertyInsertions = null;
109 
113  private $propertyDeletions = null;
114 
118  private $user;
119 
121  private $db;
122 
129  public function __construct( Title $title, ParserOutput $parserOutput, $recursive = true ) {
130  parent::__construct();
131 
132  $this->mTitle = $title;
133 
134  if ( !$this->mId ) {
135  // NOTE: subclasses may initialize mId before calling this constructor!
136  $this->mId = $title->getArticleID( Title::READ_LATEST );
137  }
138 
139  if ( !$this->mId ) {
140  throw new InvalidArgumentException(
141  "The Title object yields no ID. "
142  . "Perhaps the page [[{$title->getPrefixedDBkey()}]] doesn't exist?"
143  );
144  }
145 
146  $this->mParserOutput = $parserOutput;
147 
148  $this->mLinks = $parserOutput->getLinks();
149  $this->mImages = $parserOutput->getImages();
150  $this->mTemplates = $parserOutput->getTemplates();
151  $this->mExternals = $parserOutput->getExternalLinks();
152  $this->mCategories = $parserOutput->getCategories();
153  $this->mProperties = $parserOutput->getProperties();
154  $this->mInterwikis = $parserOutput->getInterwikiLinks();
155 
156  # Convert the format of the interlanguage links
157  # I didn't want to change it in the ParserOutput, because that array is passed all
158  # the way back to the skin, so either a skin API break would be required, or an
159  # inefficient back-conversion.
160  $ill = $parserOutput->getLanguageLinks();
161  $this->mInterlangs = [];
162  foreach ( $ill as $link ) {
163  list( $key, $title ) = explode( ':', $link, 2 );
164  $this->mInterlangs[$key] = $title;
165  }
166 
167  foreach ( $this->mCategories as &$sortkey ) {
168  # If the sortkey is longer then 255 bytes, it is truncated by DB, and then doesn't match
169  # when comparing existing vs current categories, causing T27254.
170  $sortkey = mb_strcut( $sortkey, 0, 255 );
171  }
172 
173  $this->mRecursive = $recursive;
174 
175  $this->getHookRunner()->onLinksUpdateConstructed( $this );
176  }
177 
183  public function doUpdate() {
184  if ( $this->ticket ) {
185  // Make sure all links update threads see the changes of each other.
186  // This handles the case when updates have to batched into several COMMITs.
187  $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId );
188  if ( !$scopedLock ) {
189  throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." );
190  }
191  }
192 
193  $this->getHookRunner()->onLinksUpdate( $this );
194  $this->doIncrementalUpdate();
195 
196  // Commit and release the lock (if set)
197  ScopedCallback::consume( $scopedLock );
198  // Run post-commit hook handlers without DBO_TRX
200  $this->getDB(),
201  __METHOD__,
202  function () {
203  $this->getHookRunner()->onLinksUpdateComplete( $this, $this->ticket );
204  }
205  ) );
206  }
207 
217  public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) {
218  $key = "{$dbw->getDomainID()}:LinksUpdate:$why:pageid:$pageId"; // per-wiki
219  $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 );
220  if ( !$scopedLock ) {
221  $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' );
222  $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [
223  'key' => $key,
224  'page_id' => $pageId,
225  ] );
226  return null;
227  }
228 
229  return $scopedLock;
230  }
231 
232  protected function doIncrementalUpdate() {
233  # Page links
234  $existingPL = $this->getExistingLinks();
235  $this->linkDeletions = $this->getLinkDeletions( $existingPL );
236  $this->linkInsertions = $this->getLinkInsertions( $existingPL );
237  $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
238 
239  # Image links
240  $existingIL = $this->getExistingImages();
241  $imageDeletes = $this->getImageDeletions( $existingIL );
242  $this->incrTableUpdate(
243  'imagelinks',
244  'il',
245  $imageDeletes,
246  $this->getImageInsertions( $existingIL ) );
247 
248  # Invalidate all image description pages which had links added or removed
249  $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existingIL );
250  $this->invalidateImageDescriptions( $imageUpdates );
251 
252  # External links
253  $existingEL = $this->getExistingExternals();
254  $this->externalLinkDeletions = $this->getExternalDeletions( $existingEL );
255  $this->externalLinkInsertions = $this->getExternalInsertions(
256  $existingEL );
257  $this->incrTableUpdate(
258  'externallinks',
259  'el',
260  $this->externalLinkDeletions,
261  $this->externalLinkInsertions );
262 
263  # Language links
264  $existingLL = $this->getExistingInterlangs();
265  $this->incrTableUpdate(
266  'langlinks',
267  'll',
268  $this->getInterlangDeletions( $existingLL ),
269  $this->getInterlangInsertions( $existingLL ) );
270 
271  # Inline interwiki links
272  $existingIW = $this->getExistingInterwikis();
273  $this->incrTableUpdate(
274  'iwlinks',
275  'iwl',
276  $this->getInterwikiDeletions( $existingIW ),
277  $this->getInterwikiInsertions( $existingIW ) );
278 
279  # Template links
280  $existingTL = $this->getExistingTemplates();
281  $this->incrTableUpdate(
282  'templatelinks',
283  'tl',
284  $this->getTemplateDeletions( $existingTL ),
285  $this->getTemplateInsertions( $existingTL ) );
286 
287  # Category links
288  $existingCL = $this->getExistingCategories();
289  $categoryDeletes = $this->getCategoryDeletions( $existingCL );
290  $this->incrTableUpdate(
291  'categorylinks',
292  'cl',
293  $categoryDeletes,
294  $this->getCategoryInsertions( $existingCL ) );
295  $categoryInserts = array_diff_assoc( $this->mCategories, $existingCL );
296  $categoryUpdates = $categoryInserts + $categoryDeletes;
297 
298  # Page properties
299  $existingPP = $this->getExistingProperties();
300  $this->propertyDeletions = $this->getPropertyDeletions( $existingPP );
301  $this->incrTableUpdate(
302  'page_props',
303  'pp',
304  $this->propertyDeletions,
305  $this->getPropertyInsertions( $existingPP ) );
306 
307  # Invalidate the necessary pages
308  $this->propertyInsertions = array_diff_assoc( $this->mProperties, $existingPP );
309  $changed = $this->propertyDeletions + $this->propertyInsertions;
310  $this->invalidateProperties( $changed );
311 
312  # Invalidate all categories which were added, deleted or changed (set symmetric difference)
313  $this->invalidateCategories( $categoryUpdates );
314  $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
315 
316  # Refresh links of all pages including this page
317  # This will be in a separate transaction
318  if ( $this->mRecursive ) {
319  $this->queueRecursiveJobs();
320  }
321 
322  # Update the links table freshness for this title
323  $this->updateLinksTimestamp();
324  }
325 
332  protected function queueRecursiveJobs() {
333  $action = $this->getCauseAction();
334  $agent = $this->getCauseAgent();
335 
336  self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks', $action, $agent );
337  if ( $this->mTitle->getNamespace() == NS_FILE ) {
338  // Process imagelinks in case the title is or was a redirect
339  self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks', $action, $agent );
340  }
341 
342  $bc = $this->mTitle->getBacklinkCache();
343  // Get jobs for cascade-protected backlinks for a high priority queue.
344  // If meta-templates change to using a new template, the new template
345  // should be implicitly protected as soon as possible, if applicable.
346  // These jobs duplicate a subset of the above ones, but can run sooner.
347  // Which ever runs first generally no-ops the other one.
348  $jobs = [];
349  foreach ( $bc->getCascadeProtectedLinks() as $title ) {
351  $title,
352  [
353  'causeAction' => $action,
354  'causeAgent' => $agent
355  ]
356  );
357  }
358  JobQueueGroup::singleton()->push( $jobs );
359  }
360 
369  public static function queueRecursiveJobsForTable(
370  Title $title, $table, $action = 'unknown', $userName = 'unknown'
371  ) {
372  if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
373  $job = new RefreshLinksJob(
374  $title,
375  [
376  'table' => $table,
377  'recursive' => true,
378  ] + Job::newRootJobParams( // "overall" refresh links job info
379  "refreshlinks:{$table}:{$title->getPrefixedText()}"
380  ) + [ 'causeAction' => $action, 'causeAgent' => $userName ]
381  );
382 
383  JobQueueGroup::singleton()->push( $job );
384  }
385  }
386 
390  private function invalidateCategories( $cats ) {
392  $this->getDB(), NS_CATEGORY, array_map( 'strval', array_keys( $cats ) )
393  );
394  }
395 
401  private function updateCategoryCounts( array $added, array $deleted ) {
402  global $wgUpdateRowsPerQuery;
403 
404  if ( !$added && !$deleted ) {
405  return;
406  }
407 
408  $domainId = $this->getDB()->getDomainID();
409  $wp = WikiPage::factory( $this->mTitle );
410  $lbf = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
411  // T163801: try to release any row locks to reduce contention
412  $lbf->commitAndWaitForReplication( __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
413 
414  foreach ( array_chunk( array_keys( $added ), $wgUpdateRowsPerQuery ) as $addBatch ) {
415  $wp->updateCategoryCounts( array_map( 'strval', $addBatch ), [], $this->mId );
416  $lbf->commitAndWaitForReplication(
417  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
418  }
419 
420  foreach ( array_chunk( array_keys( $deleted ), $wgUpdateRowsPerQuery ) as $deleteBatch ) {
421  $wp->updateCategoryCounts( [], array_map( 'strval', $deleteBatch ), $this->mId );
422  $lbf->commitAndWaitForReplication(
423  __METHOD__, $this->ticket, [ 'domain' => $domainId ] );
424  }
425  }
426 
430  private function invalidateImageDescriptions( array $images ) {
432  $this->getDB(), NS_FILE, array_map( 'strval', array_keys( $images ) )
433  );
434  }
435 
443  private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
444  $services = MediaWikiServices::getInstance();
445  $bSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
446  $lbf = $services->getDBLoadBalancerFactory();
447 
448  if ( $table === 'page_props' ) {
449  $fromField = 'pp_page';
450  } else {
451  $fromField = "{$prefix}_from";
452  }
453 
454  $deleteWheres = []; // list of WHERE clause arrays for each DB delete() call
455  if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) {
456  $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace";
457 
458  $curBatchSize = 0;
459  $curDeletionBatch = [];
460  $deletionBatches = [];
461  foreach ( $deletions as $ns => $dbKeys ) {
462  foreach ( $dbKeys as $dbKey => $unused ) {
463  $curDeletionBatch[$ns][$dbKey] = 1;
464  if ( ++$curBatchSize >= $bSize ) {
465  $deletionBatches[] = $curDeletionBatch;
466  $curDeletionBatch = [];
467  $curBatchSize = 0;
468  }
469  }
470  }
471  if ( $curDeletionBatch ) {
472  $deletionBatches[] = $curDeletionBatch;
473  }
474 
475  foreach ( $deletionBatches as $deletionBatch ) {
476  $deleteWheres[] = [
477  $fromField => $this->mId,
478  $this->getDB()->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" )
479  ];
480  }
481  } else {
482  if ( $table === 'langlinks' ) {
483  $toField = 'll_lang';
484  } elseif ( $table === 'page_props' ) {
485  $toField = 'pp_propname';
486  } else {
487  $toField = $prefix . '_to';
488  }
489 
490  $deletionBatches = array_chunk( array_keys( $deletions ), $bSize );
491  foreach ( $deletionBatches as $deletionBatch ) {
492  $deleteWheres[] = [
493  $fromField => $this->mId,
494  $toField => array_map( 'strval', $deletionBatch )
495  ];
496  }
497  }
498 
499  $domainId = $this->getDB()->getDomainID();
500 
501  foreach ( $deleteWheres as $deleteWhere ) {
502  $this->getDB()->delete( $table, $deleteWhere, __METHOD__ );
503  $lbf->commitAndWaitForReplication(
504  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
505  );
506  }
507 
508  $insertBatches = array_chunk( $insertions, $bSize );
509  foreach ( $insertBatches as $insertBatch ) {
510  $this->getDB()->insert( $table, $insertBatch, __METHOD__, [ 'IGNORE' ] );
511  $lbf->commitAndWaitForReplication(
512  __METHOD__, $this->ticket, [ 'domain' => $domainId ]
513  );
514  }
515 
516  if ( count( $insertions ) ) {
517  $this->getHookRunner()->onLinksUpdateAfterInsert( $this, $table, $insertions );
518  }
519  }
520 
528  private function getLinkInsertions( $existing = [] ) {
529  $arr = [];
530  foreach ( $this->mLinks as $ns => $dbkeys ) {
531  $diffs = isset( $existing[$ns] )
532  ? array_diff_key( $dbkeys, $existing[$ns] )
533  : $dbkeys;
534  foreach ( $diffs as $dbk => $id ) {
535  $arr[] = [
536  'pl_from' => $this->mId,
537  'pl_from_namespace' => $this->mTitle->getNamespace(),
538  'pl_namespace' => $ns,
539  'pl_title' => $dbk
540  ];
541  }
542  }
543 
544  return $arr;
545  }
546 
552  private function getTemplateInsertions( $existing = [] ) {
553  $arr = [];
554  foreach ( $this->mTemplates as $ns => $dbkeys ) {
555  $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
556  foreach ( $diffs as $dbk => $id ) {
557  $arr[] = [
558  'tl_from' => $this->mId,
559  'tl_from_namespace' => $this->mTitle->getNamespace(),
560  'tl_namespace' => $ns,
561  'tl_title' => $dbk
562  ];
563  }
564  }
565 
566  return $arr;
567  }
568 
575  private function getImageInsertions( $existing = [] ) {
576  $arr = [];
577  $diffs = array_diff_key( $this->mImages, $existing );
578  foreach ( $diffs as $iname => $dummy ) {
579  $arr[] = [
580  'il_from' => $this->mId,
581  'il_from_namespace' => $this->mTitle->getNamespace(),
582  'il_to' => $iname
583  ];
584  }
585 
586  return $arr;
587  }
588 
594  private function getExternalInsertions( $existing = [] ) {
595  $arr = [];
596  $diffs = array_diff_key( $this->mExternals, $existing );
597  foreach ( $diffs as $url => $dummy ) {
598  foreach ( LinkFilter::makeIndexes( $url ) as $index ) {
599  $arr[] = [
600  'el_from' => $this->mId,
601  'el_to' => $url,
602  'el_index' => $index,
603  'el_index_60' => substr( $index, 0, 60 ),
604  ];
605  }
606  }
607 
608  return $arr;
609  }
610 
619  private function getCategoryInsertions( $existing = [] ) {
620  global $wgCategoryCollation;
621  $diffs = array_diff_assoc( $this->mCategories, $existing );
622  $arr = [];
623 
624  $languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
625  ->getLanguageConverter();
626 
627  $collation = Collation::singleton();
628  foreach ( $diffs as $name => $prefix ) {
629  $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
630  $languageConverter->findVariantLink( $name, $nt, true );
631 
632  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
633  getCategoryLinkType( $this->mTitle->getNamespace() );
634 
635  # Treat custom sortkeys as a prefix, so that if multiple
636  # things are forced to sort as '*' or something, they'll
637  # sort properly in the category rather than in page_id
638  # order or such.
639  $sortkey = $collation->getSortKey( $this->mTitle->getCategorySortkey( $prefix ) );
640 
641  $arr[] = [
642  'cl_from' => $this->mId,
643  'cl_to' => $name,
644  'cl_sortkey' => $sortkey,
645  'cl_timestamp' => $this->getDB()->timestamp(),
646  'cl_sortkey_prefix' => $prefix,
647  'cl_collation' => $wgCategoryCollation,
648  'cl_type' => $type,
649  ];
650  }
651 
652  return $arr;
653  }
654 
662  private function getInterlangInsertions( $existing = [] ) {
663  $diffs = array_diff_assoc( $this->mInterlangs, $existing );
664  $arr = [];
665  foreach ( $diffs as $lang => $title ) {
666  $arr[] = [
667  'll_from' => $this->mId,
668  'll_lang' => $lang,
669  'll_title' => $title
670  ];
671  }
672 
673  return $arr;
674  }
675 
681  private function getPropertyInsertions( $existing = [] ) {
682  $diffs = array_diff_assoc( $this->mProperties, $existing );
683 
684  $arr = [];
685  foreach ( array_keys( $diffs ) as $name ) {
686  $arr[] = $this->getPagePropRowData( (string)$name );
687  }
688 
689  return $arr;
690  }
691 
708  private function getPagePropRowData( $prop ) {
710 
711  $value = $this->mProperties[$prop];
712 
713  $row = [
714  'pp_page' => $this->mId,
715  'pp_propname' => $prop,
716  'pp_value' => $value,
717  ];
718 
719  if ( $wgPagePropsHaveSortkey ) {
720  $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value );
721  }
722 
723  return $row;
724  }
725 
738  private function getPropertySortKeyValue( $value ) {
739  if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
740  return floatval( $value );
741  }
742 
743  return null;
744  }
745 
752  private function getInterwikiInsertions( $existing = [] ) {
753  $arr = [];
754  foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
755  $diffs = isset( $existing[$prefix] )
756  ? array_diff_key( $dbkeys, $existing[$prefix] )
757  : $dbkeys;
758 
759  foreach ( $diffs as $dbk => $id ) {
760  $arr[] = [
761  'iwl_from' => $this->mId,
762  'iwl_prefix' => $prefix,
763  'iwl_title' => $dbk
764  ];
765  }
766  }
767 
768  return $arr;
769  }
770 
777  private function getLinkDeletions( $existing ) {
778  $del = [];
779  foreach ( $existing as $ns => $dbkeys ) {
780  if ( isset( $this->mLinks[$ns] ) ) {
781  $del[$ns] = array_diff_key( $dbkeys, $this->mLinks[$ns] );
782  } else {
783  $del[$ns] = $dbkeys;
784  }
785  }
786 
787  return $del;
788  }
789 
796  private function getTemplateDeletions( $existing ) {
797  $del = [];
798  foreach ( $existing as $ns => $dbkeys ) {
799  if ( isset( $this->mTemplates[$ns] ) ) {
800  $del[$ns] = array_diff_key( $dbkeys, $this->mTemplates[$ns] );
801  } else {
802  $del[$ns] = $dbkeys;
803  }
804  }
805 
806  return $del;
807  }
808 
815  private function getImageDeletions( $existing ) {
816  return array_diff_key( $existing, $this->mImages );
817  }
818 
825  private function getExternalDeletions( $existing ) {
826  return array_diff_key( $existing, $this->mExternals );
827  }
828 
835  private function getCategoryDeletions( $existing ) {
836  return array_diff_assoc( $existing, $this->mCategories );
837  }
838 
845  private function getInterlangDeletions( $existing ) {
846  return array_diff_assoc( $existing, $this->mInterlangs );
847  }
848 
854  private function getPropertyDeletions( $existing ) {
855  return array_diff_assoc( $existing, $this->mProperties );
856  }
857 
864  private function getInterwikiDeletions( $existing ) {
865  $del = [];
866  foreach ( $existing as $prefix => $dbkeys ) {
867  if ( isset( $this->mInterwikis[$prefix] ) ) {
868  $del[$prefix] = array_diff_key( $dbkeys, $this->mInterwikis[$prefix] );
869  } else {
870  $del[$prefix] = $dbkeys;
871  }
872  }
873 
874  return $del;
875  }
876 
882  private function getExistingLinks() {
883  $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
884  [ 'pl_from' => $this->mId ], __METHOD__ );
885  $arr = [];
886  foreach ( $res as $row ) {
887  if ( !isset( $arr[$row->pl_namespace] ) ) {
888  $arr[$row->pl_namespace] = [];
889  }
890  $arr[$row->pl_namespace][$row->pl_title] = 1;
891  }
892 
893  return $arr;
894  }
895 
901  private function getExistingTemplates() {
902  $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
903  [ 'tl_from' => $this->mId ], __METHOD__ );
904  $arr = [];
905  foreach ( $res as $row ) {
906  if ( !isset( $arr[$row->tl_namespace] ) ) {
907  $arr[$row->tl_namespace] = [];
908  }
909  $arr[$row->tl_namespace][$row->tl_title] = 1;
910  }
911 
912  return $arr;
913  }
914 
920  private function getExistingImages() {
921  $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ],
922  [ 'il_from' => $this->mId ], __METHOD__ );
923  $arr = [];
924  foreach ( $res as $row ) {
925  $arr[$row->il_to] = 1;
926  }
927 
928  return $arr;
929  }
930 
936  private function getExistingExternals() {
937  $res = $this->getDB()->select( 'externallinks', [ 'el_to' ],
938  [ 'el_from' => $this->mId ], __METHOD__ );
939  $arr = [];
940  foreach ( $res as $row ) {
941  $arr[$row->el_to] = 1;
942  }
943 
944  return $arr;
945  }
946 
952  private function getExistingCategories() {
953  $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
954  [ 'cl_from' => $this->mId ], __METHOD__ );
955  $arr = [];
956  foreach ( $res as $row ) {
957  $arr[$row->cl_to] = $row->cl_sortkey_prefix;
958  }
959 
960  return $arr;
961  }
962 
969  private function getExistingInterlangs() {
970  $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ],
971  [ 'll_from' => $this->mId ], __METHOD__ );
972  $arr = [];
973  foreach ( $res as $row ) {
974  $arr[$row->ll_lang] = $row->ll_title;
975  }
976 
977  return $arr;
978  }
979 
984  private function getExistingInterwikis() {
985  $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
986  [ 'iwl_from' => $this->mId ], __METHOD__ );
987  $arr = [];
988  foreach ( $res as $row ) {
989  if ( !isset( $arr[$row->iwl_prefix] ) ) {
990  $arr[$row->iwl_prefix] = [];
991  }
992  $arr[$row->iwl_prefix][$row->iwl_title] = 1;
993  }
994 
995  return $arr;
996  }
997 
1003  private function getExistingProperties() {
1004  $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ],
1005  [ 'pp_page' => $this->mId ], __METHOD__ );
1006  $arr = [];
1007  foreach ( $res as $row ) {
1008  $arr[$row->pp_propname] = $row->pp_value;
1009  }
1010 
1011  return $arr;
1012  }
1013 
1018  public function getTitle() {
1019  return $this->mTitle;
1020  }
1021 
1027  public function getParserOutput() {
1028  return $this->mParserOutput;
1029  }
1030 
1035  public function getImages() {
1036  return $this->mImages;
1037  }
1038 
1046  public function setRevision( Revision $revision ) {
1047  wfDeprecated( __METHOD__, '1.35' );
1048  $this->mRevisionRecord = $revision->getRevisionRecord();
1049  }
1050 
1057  public function setRevisionRecord( RevisionRecord $revisionRecord ) {
1058  $this->mRevisionRecord = $revisionRecord;
1059  }
1060 
1066  public function getRevision() {
1067  wfDeprecated( __METHOD__, '1.35' );
1068  $revRecord = $this->mRevisionRecord;
1069  return $revRecord ? new Revision( $revRecord ) : null;
1070  }
1071 
1076  public function getRevisionRecord() {
1077  return $this->mRevisionRecord;
1078  }
1079 
1086  public function setTriggeringUser( User $user ) {
1087  $this->user = $user;
1088  }
1089 
1094  public function getTriggeringUser() {
1095  return $this->user;
1096  }
1097 
1102  private function invalidateProperties( $changed ) {
1104 
1105  $jobs = [];
1106  foreach ( $changed as $name => $value ) {
1107  if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
1108  $inv = $wgPagePropLinkInvalidations[$name];
1109  if ( !is_array( $inv ) ) {
1110  $inv = [ $inv ];
1111  }
1112  foreach ( $inv as $table ) {
1114  $this->mTitle,
1115  $table,
1116  [ 'causeAction' => 'page-props' ]
1117  );
1118  }
1119  }
1120  }
1121 
1122  JobQueueGroup::singleton()->lazyPush( $jobs );
1123  }
1124 
1130  public function getAddedLinks() {
1131  if ( $this->linkInsertions === null ) {
1132  return null;
1133  }
1134  $result = [];
1135  foreach ( $this->linkInsertions as $insertion ) {
1136  $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
1137  }
1138 
1139  return $result;
1140  }
1141 
1147  public function getRemovedLinks() {
1148  if ( $this->linkDeletions === null ) {
1149  return null;
1150  }
1151  $result = [];
1152  foreach ( $this->linkDeletions as $ns => $titles ) {
1153  foreach ( $titles as $title => $unused ) {
1154  $result[] = Title::makeTitle( $ns, $title );
1155  }
1156  }
1157 
1158  return $result;
1159  }
1160 
1167  public function getAddedExternalLinks() {
1168  if ( $this->externalLinkInsertions === null ) {
1169  return null;
1170  }
1171  $result = [];
1172  foreach ( $this->externalLinkInsertions as $key => $value ) {
1173  $result[] = $value['el_to'];
1174  }
1175  return $result;
1176  }
1177 
1184  public function getRemovedExternalLinks() {
1185  if ( $this->externalLinkDeletions === null ) {
1186  return null;
1187  }
1188  return array_keys( $this->externalLinkDeletions );
1189  }
1190 
1197  public function getAddedProperties() {
1199  }
1200 
1207  public function getRemovedProperties() {
1208  return $this->propertyDeletions;
1209  }
1210 
1214  private function updateLinksTimestamp() {
1215  if ( $this->mId ) {
1216  // The link updates made here only reflect the freshness of the parser output
1217  $timestamp = $this->mParserOutput->getCacheTime();
1218  $this->getDB()->update( 'page',
1219  [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ],
1220  [ 'page_id' => $this->mId ],
1221  __METHOD__
1222  );
1223  }
1224  }
1225 
1229  protected function getDB() {
1230  if ( !$this->db ) {
1231  $this->db = wfGetDB( DB_MASTER );
1232  }
1233 
1234  return $this->db;
1235  }
1236 
1243  public function isRecursive() {
1244  return $this->mRecursive;
1245  }
1246 }
LinksUpdate\getInterlangInsertions
getInterlangInsertions( $existing=[])
Get an array of interlanguage link insertions.
Definition: LinksUpdate.php:662
LinksUpdate\getTemplateInsertions
getTemplateInsertions( $existing=[])
Get an array of template insertions.
Definition: LinksUpdate.php:552
DataUpdate\getCauseAgent
getCauseAgent()
Definition: DataUpdate.php:72
LinksUpdate\invalidateImageDescriptions
invalidateImageDescriptions(array $images)
Definition: LinksUpdate.php:430
LinksUpdate\getTemplateDeletions
getTemplateDeletions( $existing)
Given an array of existing templates, returns those templates which are not in $this and thus should ...
Definition: LinksUpdate.php:796
LinksUpdate\$externalLinkInsertions
null array[] $externalLinkInsertions
Added external links if calculated.
Definition: LinksUpdate.php:98
LinksUpdate\getCategoryDeletions
getCategoryDeletions( $existing)
Given an array of existing categories, returns those categories which are not in $this and thus shoul...
Definition: LinksUpdate.php:835
LinksUpdate\getTriggeringUser
getTriggeringUser()
Definition: LinksUpdate.php:1094
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
LinksUpdate\doUpdate
doUpdate()
Update link tables with outgoing links from an updated article.
Definition: LinksUpdate.php:183
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:217
LinksUpdate\isRecursive
isRecursive()
Whether or not this LinksUpdate will also update pages which transclude the current page or otherwise...
Definition: LinksUpdate.php:1243
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:152
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
LinksUpdate\getRemovedProperties
getRemovedProperties()
Fetch page properties removed by this LinksUpdate.
Definition: LinksUpdate.php:1207
LinksUpdate\$mInterwikis
array $mInterwikis
2-D map of (prefix => DBK => 1)
Definition: LinksUpdate.php:73
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:864
LinksUpdate\setRevision
setRevision(Revision $revision)
Set the revision corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1046
ParserOutput\getImages
& getImages()
Definition: ParserOutput.php:599
LinksUpdate\$mId
int $mId
Page ID of the article linked from.
Definition: LinksUpdate.php:43
LinksUpdate\$linkInsertions
array[] null $linkInsertions
Added links if calculated.
Definition: LinksUpdate.php:88
LinksUpdate\$mCategories
array $mCategories
Map of category names to sort keys.
Definition: LinksUpdate.php:67
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:106
LinksUpdate\$externalLinkDeletions
null array $externalLinkDeletions
Deleted external links if calculated.
Definition: LinksUpdate.php:103
LinksUpdate
Class the manages updates of *_link tables as well as similar extension-managed tables.
Definition: LinksUpdate.php:37
NS_FILE
const NS_FILE
Definition: Defines.php:75
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:58
LinksUpdate\$db
IDatabase $db
Definition: LinksUpdate.php:121
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:815
$res
$res
Definition: testCompression.php:57
LinksUpdate\updateLinksTimestamp
updateLinksTimestamp()
Update links table freshness.
Definition: LinksUpdate.php:1214
HTMLCacheUpdateJob\newForBacklinks
static newForBacklinks(Title $title, $table, $params=[])
Definition: HTMLCacheUpdateJob.php:59
LinksUpdate\$propertyInsertions
null array $propertyInsertions
Added properties if calculated.
Definition: LinksUpdate.php:108
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:1145
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:825
LinksUpdate\queueRecursiveJobs
queueRecursiveJobs()
Queue recursive jobs for this page.
Definition: LinksUpdate.php:332
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:920
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:443
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:528
Revision
Definition: Revision.php:40
LinksUpdate\getExistingTemplates
getExistingTemplates()
Get an array of existing templates, as a 2-D array.
Definition: LinksUpdate.php:901
LinksUpdate\getCategoryInsertions
getCategoryInsertions( $existing=[])
Get an array of category insertions.
Definition: LinksUpdate.php:619
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:154
LinksUpdate\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted)
Update all the appropriate counts in the category table.
Definition: LinksUpdate.php:401
LinksUpdate\$mTemplates
array $mTemplates
Map of title strings to IDs for the template references, including broken ones.
Definition: LinksUpdate.php:61
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1026
LinksUpdate\doIncrementalUpdate
doIncrementalUpdate()
Definition: LinksUpdate.php:232
LinksUpdate\$mProperties
array $mProperties
Map of arbitrary name to value.
Definition: LinksUpdate.php:76
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:845
ParserOutput\getInterwikiLinks
getInterwikiLinks()
Definition: ParserOutput.php:551
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2461
LinksUpdate\invalidateProperties
invalidateProperties( $changed)
Invalidate any necessary link lists related to page property changes.
Definition: LinksUpdate.php:1102
$title
$title
Definition: testCompression.php:38
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:8978
LinksUpdate\getAddedExternalLinks
getAddedExternalLinks()
Fetch external links added by this LinksUpdate.
Definition: LinksUpdate.php:1167
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:592
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:83
LinksUpdate\getExistingProperties
getExistingProperties()
Get an array of existing categories, with the name in the key and sort key in the value.
Definition: LinksUpdate.php:1003
LinksUpdate\getRemovedExternalLinks
getRemovedExternalLinks()
Fetch external links removed by this LinksUpdate.
Definition: LinksUpdate.php:1184
LinksUpdate\$mTitle
Title $mTitle
Title object of the article linked from.
Definition: LinksUpdate.php:46
DB_MASTER
const DB_MASTER
Definition: defines.php:26
LinksUpdate\getTitle
getTitle()
Return the title object of the page being updated.
Definition: LinksUpdate.php:1018
Job\newRootJobParams
static newRootJobParams( $key)
Get "root job" parameters for a task.
Definition: Job.php:340
LinksUpdate\getImageInsertions
getImageInsertions( $existing=[])
Get an array of image insertions Skips the names specified in $existing.
Definition: LinksUpdate.php:575
ParserOutput\getLanguageLinks
& getLanguageLinks()
Definition: ParserOutput.php:547
LinksUpdate\__construct
__construct(Title $title, ParserOutput $parserOutput, $recursive=true)
Definition: LinksUpdate.php:129
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:969
ParserOutput\getExternalLinks
& getExternalLinks()
Definition: ParserOutput.php:607
LinksUpdate\$mParserOutput
ParserOutput $mParserOutput
Definition: LinksUpdate.php:49
LinksUpdate\getParserOutput
getParserOutput()
Returns parser output.
Definition: LinksUpdate.php:1027
LinksUpdate\getAddedLinks
getAddedLinks()
Fetch page links added by this LinksUpdate.
Definition: LinksUpdate.php:1130
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:618
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:752
LinksUpdate\getAddedProperties
getAddedProperties()
Fetch page properties added by this LinksUpdate.
Definition: LinksUpdate.php:1197
LinksUpdate\$mLinks
int[][] $mLinks
Map of title strings to IDs for the links in the document -var array<int,array<string,...
Definition: LinksUpdate.php:55
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:952
LinksUpdate\setRevisionRecord
setRevisionRecord(RevisionRecord $revisionRecord)
Set the RevisionRecord corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1057
LinksUpdate\$linkDeletions
null array $linkDeletions
Deleted links if calculated.
Definition: LinksUpdate.php:93
LinksUpdate\getPagePropRowData
getPagePropRowData( $prop)
Returns an associative array to be used for inserting a row into the page_props table.
Definition: LinksUpdate.php:708
LinksUpdate\getExistingExternals
getExistingExternals()
Get an array of existing external links, URLs in the keys.
Definition: LinksUpdate.php:936
LinksUpdate\getPropertyDeletions
getPropertyDeletions( $existing)
Get array of properties which should be deleted.
Definition: LinksUpdate.php:854
ParserOutput\getTemplates
& getTemplates()
Definition: ParserOutput.php:591
LinksUpdate\getImages
getImages()
Return the list of images used as generated by the parser.
Definition: LinksUpdate.php:1035
LinksUpdate\$mInterlangs
array $mInterlangs
Map of language codes to titles.
Definition: LinksUpdate.php:70
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:1086
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
LinksUpdate\getRemovedLinks
getRemovedLinks()
Fetch page links removed by this LinksUpdate.
Definition: LinksUpdate.php:1147
LinksUpdate\$mExternals
array $mExternals
URLs of external links, array key only.
Definition: LinksUpdate.php:64
LinksUpdate\getRevisionRecord
getRevisionRecord()
Definition: LinksUpdate.php:1076
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:50
$wgPagePropLinkInvalidations
$wgPagePropLinkInvalidations
Page property link table invalidation lists.
Definition: DefaultSettings.php:8073
LinksUpdate\getPropertyInsertions
getPropertyInsertions( $existing=[])
Get an array of page property insertions.
Definition: LinksUpdate.php:681
ParserOutput\getCategories
& getCategories()
Definition: ParserOutput.php:559
LinksUpdate\getExternalInsertions
getExternalInsertions( $existing=[])
Get an array of externallinks insertions.
Definition: LinksUpdate.php:594
LinksUpdate\$mRecursive
bool $mRecursive
Whether to queue jobs for recursive updates.
Definition: LinksUpdate.php:79
LinksUpdate\$user
User null $user
Definition: LinksUpdate.php:118
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:369
LinksUpdate\$propertyDeletions
null array $propertyDeletions
Deleted properties if calculated.
Definition: LinksUpdate.php:113
LinksUpdate\invalidateCategories
invalidateCategories( $cats)
Definition: LinksUpdate.php:390
LinksUpdate\$mRevisionRecord
RevisionRecord $mRevisionRecord
Revision for which this update has been triggered.
Definition: LinksUpdate.php:82
$wgCategoryCollation
$wgCategoryCollation
Specify how category names should be sorted, when listed on a category page.
Definition: DefaultSettings.php:8125
DataUpdate\getCauseAction
getCauseAction()
Definition: DataUpdate.php:65
LinksUpdate\getExistingLinks
getExistingLinks()
Get an array of existing links, as a 2-D array.
Definition: LinksUpdate.php:882
$wgPagePropsHaveSortkey
$wgPagePropsHaveSortkey
Whether the page_props table has a pp_sortkey column.
Definition: DefaultSettings.php:9141
LinksUpdate\getDB
getDB()
Definition: LinksUpdate.php:1229
Revision\getRevisionRecord
getRevisionRecord()
Definition: Revision.php:448
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:59
LinksUpdate\getRevision
getRevision()
Definition: LinksUpdate.php:1066
LinksUpdate\getPropertySortKeyValue
getPropertySortKeyValue( $value)
Determines the sort key for the given property value.
Definition: LinksUpdate.php:738
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:777
ParserOutput\getLinks
& getLinks()
Definition: ParserOutput.php:579
LinksUpdate\getExistingInterwikis
getExistingInterwikis()
Get an array of existing inline interwiki links, as a 2-D array.
Definition: LinksUpdate.php:984
$type
$type
Definition: testCompression.php:52