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  $services = MediaWikiServices::getInstance();
410  $wp = $services->getWikiPageFactory()->newFromTitle( $this->mTitle );
411  $lbf = $services->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  $this->getHookRunner()->onLinksUpdateAfterInsert( $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 
625  $languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
626  ->getLanguageConverter();
627 
628  $collation = Collation::singleton();
629  foreach ( $diffs as $name => $prefix ) {
630  $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
631  $languageConverter->findVariantLink( $name, $nt, true );
632 
633  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
634  getCategoryLinkType( $this->mTitle->getNamespace() );
635 
636  # Treat custom sortkeys as a prefix, so that if multiple
637  # things are forced to sort as '*' or something, they'll
638  # sort properly in the category rather than in page_id
639  # order or such.
640  $sortkey = $collation->getSortKey( $this->mTitle->getCategorySortkey( $prefix ) );
641 
642  $arr[] = [
643  'cl_from' => $this->mId,
644  'cl_to' => $name,
645  'cl_sortkey' => $sortkey,
646  'cl_timestamp' => $this->getDB()->timestamp(),
647  'cl_sortkey_prefix' => $prefix,
648  'cl_collation' => $wgCategoryCollation,
649  'cl_type' => $type,
650  ];
651  }
652 
653  return $arr;
654  }
655 
663  private function getInterlangInsertions( $existing = [] ) {
664  $diffs = array_diff_assoc( $this->mInterlangs, $existing );
665  $arr = [];
666  foreach ( $diffs as $lang => $title ) {
667  $arr[] = [
668  'll_from' => $this->mId,
669  'll_lang' => $lang,
670  'll_title' => $title
671  ];
672  }
673 
674  return $arr;
675  }
676 
682  private function getPropertyInsertions( $existing = [] ) {
683  $diffs = array_diff_assoc( $this->mProperties, $existing );
684 
685  $arr = [];
686  foreach ( array_keys( $diffs ) as $name ) {
687  $arr[] = $this->getPagePropRowData( (string)$name );
688  }
689 
690  return $arr;
691  }
692 
708  private function getPagePropRowData( $prop ) {
709  $value = $this->mProperties[$prop];
710 
711  return [
712  'pp_page' => $this->mId,
713  'pp_propname' => $prop,
714  'pp_value' => $value,
715  'pp_sortkey' => $this->getPropertySortKeyValue( $value )
716  ];
717  }
718 
731  private function getPropertySortKeyValue( $value ) {
732  if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
733  return floatval( $value );
734  }
735 
736  return null;
737  }
738 
745  private function getInterwikiInsertions( $existing = [] ) {
746  $arr = [];
747  foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
748  $diffs = isset( $existing[$prefix] )
749  ? array_diff_key( $dbkeys, $existing[$prefix] )
750  : $dbkeys;
751 
752  foreach ( $diffs as $dbk => $id ) {
753  $arr[] = [
754  'iwl_from' => $this->mId,
755  'iwl_prefix' => $prefix,
756  'iwl_title' => $dbk
757  ];
758  }
759  }
760 
761  return $arr;
762  }
763 
770  private function getLinkDeletions( $existing ) {
771  $del = [];
772  foreach ( $existing as $ns => $dbkeys ) {
773  if ( isset( $this->mLinks[$ns] ) ) {
774  $del[$ns] = array_diff_key( $dbkeys, $this->mLinks[$ns] );
775  } else {
776  $del[$ns] = $dbkeys;
777  }
778  }
779 
780  return $del;
781  }
782 
789  private function getTemplateDeletions( $existing ) {
790  $del = [];
791  foreach ( $existing as $ns => $dbkeys ) {
792  if ( isset( $this->mTemplates[$ns] ) ) {
793  $del[$ns] = array_diff_key( $dbkeys, $this->mTemplates[$ns] );
794  } else {
795  $del[$ns] = $dbkeys;
796  }
797  }
798 
799  return $del;
800  }
801 
808  private function getImageDeletions( $existing ) {
809  return array_diff_key( $existing, $this->mImages );
810  }
811 
818  private function getExternalDeletions( $existing ) {
819  return array_diff_key( $existing, $this->mExternals );
820  }
821 
828  private function getCategoryDeletions( $existing ) {
829  return array_diff_assoc( $existing, $this->mCategories );
830  }
831 
838  private function getInterlangDeletions( $existing ) {
839  return array_diff_assoc( $existing, $this->mInterlangs );
840  }
841 
847  private function getPropertyDeletions( $existing ) {
848  return array_diff_assoc( $existing, $this->mProperties );
849  }
850 
857  private function getInterwikiDeletions( $existing ) {
858  $del = [];
859  foreach ( $existing as $prefix => $dbkeys ) {
860  if ( isset( $this->mInterwikis[$prefix] ) ) {
861  $del[$prefix] = array_diff_key( $dbkeys, $this->mInterwikis[$prefix] );
862  } else {
863  $del[$prefix] = $dbkeys;
864  }
865  }
866 
867  return $del;
868  }
869 
875  private function getExistingLinks() {
876  $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ],
877  [ 'pl_from' => $this->mId ], __METHOD__ );
878  $arr = [];
879  foreach ( $res as $row ) {
880  if ( !isset( $arr[$row->pl_namespace] ) ) {
881  $arr[$row->pl_namespace] = [];
882  }
883  $arr[$row->pl_namespace][$row->pl_title] = 1;
884  }
885 
886  return $arr;
887  }
888 
894  private function getExistingTemplates() {
895  $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ],
896  [ 'tl_from' => $this->mId ], __METHOD__ );
897  $arr = [];
898  foreach ( $res as $row ) {
899  if ( !isset( $arr[$row->tl_namespace] ) ) {
900  $arr[$row->tl_namespace] = [];
901  }
902  $arr[$row->tl_namespace][$row->tl_title] = 1;
903  }
904 
905  return $arr;
906  }
907 
913  private function getExistingImages() {
914  $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ],
915  [ 'il_from' => $this->mId ], __METHOD__ );
916  $arr = [];
917  foreach ( $res as $row ) {
918  $arr[$row->il_to] = 1;
919  }
920 
921  return $arr;
922  }
923 
929  private function getExistingExternals() {
930  $res = $this->getDB()->select( 'externallinks', [ 'el_to' ],
931  [ 'el_from' => $this->mId ], __METHOD__ );
932  $arr = [];
933  foreach ( $res as $row ) {
934  $arr[$row->el_to] = 1;
935  }
936 
937  return $arr;
938  }
939 
945  private function getExistingCategories() {
946  $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ],
947  [ 'cl_from' => $this->mId ], __METHOD__ );
948  $arr = [];
949  foreach ( $res as $row ) {
950  $arr[$row->cl_to] = $row->cl_sortkey_prefix;
951  }
952 
953  return $arr;
954  }
955 
962  private function getExistingInterlangs() {
963  $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ],
964  [ 'll_from' => $this->mId ], __METHOD__ );
965  $arr = [];
966  foreach ( $res as $row ) {
967  $arr[$row->ll_lang] = $row->ll_title;
968  }
969 
970  return $arr;
971  }
972 
977  private function getExistingInterwikis() {
978  $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ],
979  [ 'iwl_from' => $this->mId ], __METHOD__ );
980  $arr = [];
981  foreach ( $res as $row ) {
982  if ( !isset( $arr[$row->iwl_prefix] ) ) {
983  $arr[$row->iwl_prefix] = [];
984  }
985  $arr[$row->iwl_prefix][$row->iwl_title] = 1;
986  }
987 
988  return $arr;
989  }
990 
996  private function getExistingProperties() {
997  $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ],
998  [ 'pp_page' => $this->mId ], __METHOD__ );
999  $arr = [];
1000  foreach ( $res as $row ) {
1001  $arr[$row->pp_propname] = $row->pp_value;
1002  }
1003 
1004  return $arr;
1005  }
1006 
1011  public function getTitle() {
1012  return $this->mTitle;
1013  }
1014 
1020  public function getParserOutput() {
1021  return $this->mParserOutput;
1022  }
1023 
1028  public function getImages() {
1029  return $this->mImages;
1030  }
1031 
1039  public function setRevision( Revision $revision ) {
1040  wfDeprecated( __METHOD__, '1.35' );
1041  $this->mRevisionRecord = $revision->getRevisionRecord();
1042  }
1043 
1050  public function setRevisionRecord( RevisionRecord $revisionRecord ) {
1051  $this->mRevisionRecord = $revisionRecord;
1052  }
1053 
1059  public function getRevision() {
1060  wfDeprecated( __METHOD__, '1.35' );
1061  $revRecord = $this->mRevisionRecord;
1062  return $revRecord ? new Revision( $revRecord ) : null;
1063  }
1064 
1069  public function getRevisionRecord() {
1070  return $this->mRevisionRecord;
1071  }
1072 
1079  public function setTriggeringUser( User $user ) {
1080  $this->user = $user;
1081  }
1082 
1087  public function getTriggeringUser() {
1088  return $this->user;
1089  }
1090 
1095  private function invalidateProperties( $changed ) {
1097 
1098  $jobs = [];
1099  foreach ( $changed as $name => $value ) {
1100  if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
1101  $inv = $wgPagePropLinkInvalidations[$name];
1102  if ( !is_array( $inv ) ) {
1103  $inv = [ $inv ];
1104  }
1105  foreach ( $inv as $table ) {
1107  $this->mTitle,
1108  $table,
1109  [ 'causeAction' => 'page-props' ]
1110  );
1111  }
1112  }
1113  }
1114 
1115  JobQueueGroup::singleton()->lazyPush( $jobs );
1116  }
1117 
1123  public function getAddedLinks() {
1124  if ( $this->linkInsertions === null ) {
1125  return null;
1126  }
1127  $result = [];
1128  foreach ( $this->linkInsertions as $insertion ) {
1129  $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
1130  }
1131 
1132  return $result;
1133  }
1134 
1140  public function getRemovedLinks() {
1141  if ( $this->linkDeletions === null ) {
1142  return null;
1143  }
1144  $result = [];
1145  foreach ( $this->linkDeletions as $ns => $titles ) {
1146  foreach ( $titles as $title => $unused ) {
1147  $result[] = Title::makeTitle( $ns, $title );
1148  }
1149  }
1150 
1151  return $result;
1152  }
1153 
1160  public function getAddedExternalLinks() {
1161  if ( $this->externalLinkInsertions === null ) {
1162  return null;
1163  }
1164  return array_column( $this->externalLinkInsertions, 'el_to' );
1165  }
1166 
1173  public function getRemovedExternalLinks() {
1174  if ( $this->externalLinkDeletions === null ) {
1175  return null;
1176  }
1177  return array_keys( $this->externalLinkDeletions );
1178  }
1179 
1186  public function getAddedProperties() {
1188  }
1189 
1196  public function getRemovedProperties() {
1197  return $this->propertyDeletions;
1198  }
1199 
1203  private function updateLinksTimestamp() {
1204  if ( $this->mId ) {
1205  // The link updates made here only reflect the freshness of the parser output
1206  $timestamp = $this->mParserOutput->getCacheTime();
1207  $this->getDB()->update( 'page',
1208  [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ],
1209  [ 'page_id' => $this->mId ],
1210  __METHOD__
1211  );
1212  }
1213  }
1214 
1218  protected function getDB() {
1219  if ( !$this->db ) {
1220  $this->db = wfGetDB( DB_MASTER );
1221  }
1222 
1223  return $this->db;
1224  }
1225 
1232  public function isRecursive() {
1233  return $this->mRecursive;
1234  }
1235 }
LinksUpdate\getInterlangInsertions
getInterlangInsertions( $existing=[])
Get an array of interlanguage link insertions.
Definition: LinksUpdate.php:663
LinksUpdate\getTemplateInsertions
getTemplateInsertions( $existing=[])
Get an array of template insertions.
Definition: LinksUpdate.php:553
DataUpdate\getCauseAgent
getCauseAgent()
Definition: DataUpdate.php:72
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:789
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:828
LinksUpdate\getTriggeringUser
getTriggeringUser()
Definition: LinksUpdate.php:1087
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:183
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:217
LinksUpdate\isRecursive
isRecursive()
Whether or not this LinksUpdate will also update pages which transclude the current page or otherwise...
Definition: LinksUpdate.php:1232
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:173
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
LinksUpdate\getRemovedProperties
getRemovedProperties()
Fetch page properties removed by this LinksUpdate.
Definition: LinksUpdate.php:1196
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:857
LinksUpdate\setRevision
setRevision(Revision $revision)
Set the revision corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1039
ParserOutput\getImages
& getImages()
Definition: ParserOutput.php:622
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 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:103
LinksUpdate
Class the manages updates of *_link tables as well as similar extension-managed tables.
Definition: LinksUpdate.php:37
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:808
$res
$res
Definition: testCompression.php:57
LinksUpdate\updateLinksTimestamp
updateLinksTimestamp()
Update links table freshness.
Definition: LinksUpdate.php:1203
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: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:818
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:913
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:37
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:894
LinksUpdate\getCategoryInsertions
getCategoryInsertions( $existing=[])
Get an array of category insertions.
Definition: LinksUpdate.php:620
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:1034
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:838
ParserOutput\getInterwikiLinks
getInterwikiLinks()
Definition: ParserOutput.php:574
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2467
LinksUpdate\invalidateProperties
invalidateProperties( $changed)
Invalidate any necessary link lists related to page property changes.
Definition: LinksUpdate.php:1095
$title
$title
Definition: testCompression.php:38
$wgUpdateRowsPerQuery
$wgUpdateRowsPerQuery
Number of rows to update per query.
Definition: DefaultSettings.php:9067
LinksUpdate\getAddedExternalLinks
getAddedExternalLinks()
Fetch external links added by this LinksUpdate.
Definition: LinksUpdate.php:1160
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:626
LinksUpdate\getExistingProperties
getExistingProperties()
Get an array of existing categories, with the name in the key and sort key in the value.
Definition: LinksUpdate.php:996
LinksUpdate\getRemovedExternalLinks
getRemovedExternalLinks()
Fetch external links removed by this LinksUpdate.
Definition: LinksUpdate.php:1173
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:1011
Job\newRootJobParams
static newRootJobParams( $key)
Get "root job" parameters for a task.
Definition: Job.php:343
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:570
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:962
ParserOutput\getExternalLinks
& getExternalLinks()
Definition: ParserOutput.php:630
LinksUpdate\$mParserOutput
ParserOutput $mParserOutput
Definition: LinksUpdate.php:49
LinksUpdate\getParserOutput
getParserOutput()
Returns parser output.
Definition: LinksUpdate.php:1020
LinksUpdate\getAddedLinks
getAddedLinks()
Fetch page links added by this LinksUpdate.
Definition: LinksUpdate.php:1123
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:652
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:745
LinksUpdate\getAddedProperties
getAddedProperties()
Fetch page properties added by this LinksUpdate.
Definition: LinksUpdate.php:1186
LinksUpdate\$mLinks
int[][] $mLinks
Map of title strings to IDs for the links in the document.
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:945
LinksUpdate\setRevisionRecord
setRevisionRecord(RevisionRecord $revisionRecord)
Set the RevisionRecord corresponding to this LinksUpdate.
Definition: LinksUpdate.php:1050
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:929
LinksUpdate\getPropertyDeletions
getPropertyDeletions( $existing)
Get array of properties which should be deleted.
Definition: LinksUpdate.php:847
ParserOutput\getTemplates
& getTemplates()
Definition: ParserOutput.php:614
LinksUpdate\getImages
getImages()
Return the list of images used as generated by the parser.
Definition: LinksUpdate.php:1028
LinksUpdate\$mInterlangs
array $mInterlangs
Map of language codes to titles.
Definition: LinksUpdate.php:70
Title
Represents a title within MediaWiki.
Definition: Title.php:46
LinksUpdate\setTriggeringUser
setTriggeringUser(User $user)
Set the User who triggered this LinksUpdate.
Definition: LinksUpdate.php:1079
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
LinksUpdate\getRemovedLinks
getRemovedLinks()
Fetch page links removed by this LinksUpdate.
Definition: LinksUpdate.php:1140
LinksUpdate\$mExternals
array $mExternals
URLs of external links, array key only.
Definition: LinksUpdate.php:64
LinksUpdate\getRevisionRecord
getRevisionRecord()
Definition: LinksUpdate.php:1069
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:50
$wgPagePropLinkInvalidations
$wgPagePropLinkInvalidations
Page property link table invalidation lists.
Definition: DefaultSettings.php:8150
LinksUpdate\getPropertyInsertions
getPropertyInsertions( $existing=[])
Get an array of page property insertions.
Definition: LinksUpdate.php:682
ParserOutput\getCategories
& getCategories()
Definition: ParserOutput.php:582
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: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
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
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:8201
DataUpdate\getCauseAction
getCauseAction()
Definition: DataUpdate.php:65
LinksUpdate\getExistingLinks
getExistingLinks()
Get an array of existing links, as a 2-D array.
Definition: LinksUpdate.php:875
NS_FILE
const NS_FILE
Definition: Defines.php:70
LinksUpdate\getDB
getDB()
Definition: LinksUpdate.php:1218
Revision\getRevisionRecord
getRevisionRecord()
Definition: Revision.php:438
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:66
LinksUpdate\getRevision
getRevision()
Definition: LinksUpdate.php:1059
LinksUpdate\getPropertySortKeyValue
getPropertySortKeyValue( $value)
Determines the sort key for the given property value.
Definition: LinksUpdate.php:731
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:770
ParserOutput\getLinks
& getLinks()
Definition: ParserOutput.php:602
LinksUpdate\getExistingInterwikis
getExistingInterwikis()
Get an array of existing inline interwiki links, as a 2-D array.
Definition: LinksUpdate.php:977
$type
$type
Definition: testCompression.php:52