MediaWiki  master
DeletePage.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Page;
4 
5 use BadMethodCallException;
6 use BagOStuff;
7 use ChangeTags;
8 use CommentStore;
9 use Content;
11 use DeferredUpdates;
12 use DeletePageJob;
13 use Exception;
14 use JobQueueGroup;
16 use LinksUpdate;
17 use LogicException;
18 use ManualLogEntry;
30 use Message;
31 use RawMessage;
33 use SearchUpdate;
34 use SiteStatsUpdate;
35 use Status;
36 use StatusValue;
37 use Wikimedia\IPUtils;
40 use WikiPage;
41 
46 class DeletePage {
50  public const CONSTRUCTOR_OPTIONS = [
51  'DeleteRevisionsBatchSize',
52  'ActorTableSchemaMigrationStage',
53  'DeleteRevisionsLimit',
54  ];
55 
57  private $hookRunner;
59  private $revisionStore;
61  private $lbFactory;
63  private $loadBalancer;
65  private $jobQueueGroup;
67  private $commentStore;
69  private $options;
73  private $localWikiID;
75  private $webRequestID;
77  private $userFactory;
80 
82  private $isDeletePageUnitTest = false;
83 
85  private $page;
87  private $deleter;
88 
90  private $suppress = false;
92  private $tags = [];
94  private $logSubtype = 'delete';
96  private $forceImmediate = false;
97 
99  private $legacyHookErrors = '';
101  private $mergeLegacyHookErrors = true;
102 
106  private $wasScheduled;
108  private $attemptedDeletion = false;
109 
126  public function __construct(
127  HookContainer $hookContainer,
128  RevisionStore $revisionStore,
129  LBFactory $lbFactory,
132  ServiceOptions $serviceOptions,
134  string $localWikiID,
135  string $webRequestID,
136  WikiPageFactory $wikiPageFactory,
137  UserFactory $userFactory,
139  Authority $deleter,
140  BacklinkCacheFactory $backlinkCacheFactory
141  ) {
142  $this->hookRunner = new HookRunner( $hookContainer );
143  $this->revisionStore = $revisionStore;
144  $this->lbFactory = $lbFactory;
145  $this->loadBalancer = $this->lbFactory->getMainLB();
146  $this->jobQueueGroup = $jobQueueGroup;
147  $this->commentStore = $commentStore;
148  $serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
149  $this->options = $serviceOptions;
150  $this->recentDeletesCache = $recentDeletesCache;
151  $this->localWikiID = $localWikiID;
152  $this->webRequestID = $webRequestID;
153  $this->userFactory = $userFactory;
154 
155  $this->page = $wikiPageFactory->newFromTitle( $page );
156  $this->deleter = $deleter;
157  $this->backlinkCacheFactory = $backlinkCacheFactory;
158  }
159 
164  public function getLegacyHookErrors() {
166  }
167 
172  public function keepLegacyHookErrorsSeparate(): self {
173  $this->mergeLegacyHookErrors = false;
174  return $this;
175  }
176 
184  public function setSuppress( bool $suppress ): self {
185  $this->suppress = $suppress;
186  return $this;
187  }
188 
195  public function setTags( array $tags ): self {
196  $this->tags = $tags;
197  return $this;
198  }
199 
206  public function setLogSubtype( string $logSubtype ): self {
207  $this->logSubtype = $logSubtype;
208  return $this;
209  }
210 
217  public function forceImmediate( bool $forceImmediate ): self {
219  return $this;
220  }
221 
227  public function setIsDeletePageUnitTest( bool $test ): void {
228  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
229  throw new BadMethodCallException( __METHOD__ . ' can only be used in tests!' );
230  }
231  $this->isDeletePageUnitTest = $test;
232  }
233 
237  private function setDeletionAttempted(): void {
238  $this->attemptedDeletion = true;
239  $this->successfulDeletionsIDs = [];
240  $this->wasScheduled = false;
241  }
242 
247  private function assertDeletionAttempted(): void {
248  if ( !$this->attemptedDeletion ) {
249  throw new BadMethodCallException( 'No deletion was attempted' );
250  }
251  }
252 
257  public function getSuccessfulDeletionsIDs(): array {
258  $this->assertDeletionAttempted();
260  }
261 
266  public function deletionWasScheduled(): bool {
267  $this->assertDeletionAttempted();
268  return $this->wasScheduled;
269  }
270 
277  public function deleteIfAllowed( string $reason ): StatusValue {
278  $this->setDeletionAttempted();
279  $status = $this->authorizeDeletion();
280  if ( !$status->isGood() ) {
281  return $status;
282  }
283 
284  return $this->deleteUnsafe( $reason );
285  }
286 
290  private function authorizeDeletion(): PermissionStatus {
291  $status = PermissionStatus::newEmpty();
292  $this->deleter->authorizeWrite( 'delete', $this->page, $status );
293  if (
294  !$this->deleter->authorizeWrite( 'bigdelete', $this->page ) &&
295  $this->isBigDeletion()
296  ) {
297  $status->fatal( 'delete-toobig', Message::numParam( $this->options->get( 'DeleteRevisionsLimit' ) ) );
298  }
299  if ( $this->tags ) {
300  $status->merge( ChangeTags::canAddTagsAccompanyingChange( $this->tags, $this->deleter ) );
301  }
302  return $status;
303  }
304 
308  private function isBigDeletion(): bool {
309  $revLimit = $this->options->get( 'DeleteRevisionsLimit' );
310  if ( !$revLimit ) {
311  return false;
312  }
313 
314  $revCount = $this->revisionStore->countRevisionsByPageId(
315  $this->loadBalancer->getConnectionRef( DB_REPLICA ),
316  $this->page->getId()
317  );
318 
319  return $revCount > $revLimit;
320  }
321 
334  public function isBatchedDelete( int $safetyMargin = 0 ): bool {
335  $revCount = $this->revisionStore->countRevisionsByPageId(
336  $this->loadBalancer->getConnectionRef( DB_REPLICA ),
337  $this->page->getId()
338  );
339  $revCount += $safetyMargin;
340 
341  return $revCount >= $this->options->get( 'DeleteRevisionsBatchSize' );
342  }
343 
354  public function deleteUnsafe( string $reason ): Status {
355  $this->setDeletionAttempted();
356  $status = Status::newGood();
357 
358  $legacyDeleter = $this->userFactory->newFromAuthority( $this->deleter );
359  if ( !$this->hookRunner->onArticleDelete(
360  $this->page, $legacyDeleter, $reason, $this->legacyHookErrors, $status, $this->suppress )
361  ) {
362  if ( $this->mergeLegacyHookErrors && $this->legacyHookErrors !== '' ) {
363  if ( is_string( $this->legacyHookErrors ) ) {
364  $this->legacyHookErrors = [ $this->legacyHookErrors ];
365  }
366  foreach ( $this->legacyHookErrors as $legacyError ) {
367  $status->fatal( new RawMessage( $legacyError ) );
368  }
369  }
370  if ( $status->isOK() ) {
371  // Hook aborted but didn't set a fatal status
372  $status->fatal( 'delete-hook-aborted' );
373  }
374  return $status;
375  }
376 
377  // Use a new Status in case a hook handler put something here without aborting.
378  $status = Status::newGood();
379  $hookRes = $this->hookRunner->onPageDelete( $this->page, $this->deleter, $reason, $status, $this->suppress );
380  if ( !$hookRes && !$status->isGood() ) {
381  // Note: as per the PageDeleteHook documentation, `return false` is ignored if $status is good.
382  return $status;
383  }
384 
385  return $this->deleteInternal( $reason );
386  }
387 
400  public function deleteInternal( string $reason, ?string $webRequestId = null ): Status {
401  // The following is necessary for direct calls from the outside
402  $this->setDeletionAttempted();
403 
404  $title = $this->page->getTitle();
405  $status = Status::newGood();
406 
407  $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
408  $dbw->startAtomic( __METHOD__ );
409 
410  $this->page->loadPageData( WikiPage::READ_LATEST );
411  $id = $this->page->getId();
412  // T98706: lock the page from various other updates but avoid using
413  // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
414  // the revisions queries (which also JOIN on user). Only lock the page
415  // row and CAS check on page_latest to see if the trx snapshot matches.
416  $lockedLatest = $this->page->lockAndGetLatest();
417  if ( $id === 0 || $this->page->getLatest() !== $lockedLatest ) {
418  $dbw->endAtomic( __METHOD__ );
419  // Page not there or trx snapshot is stale
420  $status->error( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) );
421  return $status;
422  }
423 
424  // At this point we are now committed to returning an OK
425  // status unless some DB query error or other exception comes up.
426  // This way callers don't have to call rollback() if $status is bad
427  // unless they actually try to catch exceptions (which is rare).
428 
429  // we need to remember the old content so we can use it to generate all deletion updates.
430  $revisionRecord = $this->page->getRevisionRecord();
431  if ( !$revisionRecord ) {
432  throw new LogicException( "No revisions for $this->page?" );
433  }
434  try {
435  $content = $this->page->getContent( RevisionRecord::RAW );
436  } catch ( Exception $ex ) {
437  wfLogWarning( __METHOD__ . ': failed to load content during deletion! '
438  . $ex->getMessage() );
439 
440  $content = null;
441  }
442 
443  // Archive revisions. In immediate mode, archive all revisions. Otherwise, archive
444  // one batch of revisions and defer archival of any others to the job queue.
445  $explictTrxLogged = false;
446  while ( true ) {
447  $done = $this->archiveRevisions( $id );
448  if ( $done || !$this->forceImmediate ) {
449  break;
450  }
451  $dbw->endAtomic( __METHOD__ );
452  if ( $dbw->explicitTrxActive() ) {
453  // Explict transactions may never happen here in practice. Log to be sure.
454  if ( !$explictTrxLogged ) {
455  $explictTrxLogged = true;
456  LoggerFactory::getInstance( 'wfDebug' )->debug(
457  'explicit transaction active in ' . __METHOD__ . ' while deleting {title}', [
458  'title' => $title->getText(),
459  ] );
460  }
461  continue;
462  }
463  if ( $dbw->trxLevel() ) {
464  $dbw->commit( __METHOD__ );
465  }
466  $this->lbFactory->waitForReplication();
467  $dbw->startAtomic( __METHOD__ );
468  }
469 
470  if ( !$done ) {
471  $dbw->endAtomic( __METHOD__ );
472 
473  $jobParams = [
474  'namespace' => $title->getNamespace(),
475  'title' => $title->getDBkey(),
476  'wikiPageId' => $id,
477  'requestId' => $webRequestId ?? $this->webRequestID,
478  'reason' => $reason,
479  'suppress' => $this->suppress,
480  'userId' => $this->deleter->getUser()->getId(),
481  'tags' => json_encode( $this->tags ),
482  'logsubtype' => $this->logSubtype,
483  ];
484 
485  $job = new DeletePageJob( $jobParams );
486  $this->jobQueueGroup->push( $job );
487  $this->wasScheduled = true;
488  return $status;
489  }
490 
491  // Get archivedRevisionCount by db query, because there's no better alternative.
492  // Jobs cannot pass a count of archived revisions to the next job, because additional
493  // deletion operations can be started while the first is running. Jobs from each
494  // gracefully interleave, but would not know about each other's count. Deduplication
495  // in the job queue to avoid simultaneous deletion operations would add overhead.
496  // Number of archived revisions cannot be known beforehand, because edits can be made
497  // while deletion operations are being processed, changing the number of archivals.
498  $archivedRevisionCount = $dbw->selectRowCount(
499  'archive',
500  '*',
501  [
502  'ar_namespace' => $title->getNamespace(),
503  'ar_title' => $title->getDBkey(),
504  'ar_page_id' => $id
505  ], __METHOD__
506  );
507 
508  // Clone the title and wikiPage, so we have the information we need when
509  // we log and run the ArticleDeleteComplete hook.
510  $logTitle = clone $title;
511  $wikiPageBeforeDelete = clone $this->page;
512 
513  // Now that it's safely backed up, delete it
514  $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
515 
516  // Log the deletion, if the page was suppressed, put it in the suppression log instead
517  $logtype = $this->suppress ? 'suppress' : 'delete';
518 
519  $logEntry = new ManualLogEntry( $logtype, $this->logSubtype );
520  $logEntry->setPerformer( $this->deleter->getUser() );
521  $logEntry->setTarget( $logTitle );
522  $logEntry->setComment( $reason );
523  $logEntry->addTags( $this->tags );
524  if ( !$this->isDeletePageUnitTest ) {
525  // TODO: Remove conditional once ManualLogEntry is servicified (T253717)
526  $logid = $logEntry->insert();
527 
528  $dbw->onTransactionPreCommitOrIdle(
529  static function () use ( $logEntry, $logid ) {
530  // T58776: avoid deadlocks (especially from FileDeleteForm)
531  $logEntry->publish( $logid );
532  },
533  __METHOD__
534  );
535  } else {
536  $logid = 42;
537  }
538 
539  $dbw->endAtomic( __METHOD__ );
540 
541  $this->doDeleteUpdates( $revisionRecord );
542 
543  $legacyDeleter = $this->userFactory->newFromAuthority( $this->deleter );
544  $this->hookRunner->onArticleDeleteComplete(
545  $wikiPageBeforeDelete,
546  $legacyDeleter,
547  $reason,
548  $id,
549  $content,
550  $logEntry,
551  $archivedRevisionCount
552  );
553  $this->hookRunner->onPageDeleteComplete(
554  $wikiPageBeforeDelete,
555  $this->deleter,
556  $reason,
557  $id,
558  $revisionRecord,
559  $logEntry,
560  $archivedRevisionCount
561  );
562  $this->successfulDeletionsIDs[] = $logid;
563 
564  // Show log excerpt on 404 pages rather than just a link
565  $key = $this->recentDeletesCache->makeKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
566  $this->recentDeletesCache->set( $key, 1, BagOStuff::TTL_DAY );
567 
568  return $status;
569  }
570 
577  private function archiveRevisions( int $id ): bool {
578  // Given the lock above, we can be confident in the title and page ID values
579  $namespace = $this->page->getTitle()->getNamespace();
580  $dbKey = $this->page->getTitle()->getDBkey();
581 
582  $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
583 
584  $revQuery = $this->revisionStore->getQueryInfo();
585  $bitfield = false;
586 
587  // Bitfields to further suppress the content
588  if ( $this->suppress ) {
589  $bitfield = RevisionRecord::SUPPRESSED_ALL;
590  $revQuery['fields'] = array_diff( $revQuery['fields'], [ 'rev_deleted' ] );
591  }
592 
593  // For now, shunt the revision data into the archive table.
594  // Text is *not* removed from the text table; bulk storage
595  // is left intact to avoid breaking block-compression or
596  // immutable storage schemes.
597  // In the future, we may keep revisions and mark them with
598  // the rev_deleted field, which is reserved for this purpose.
599 
600  // Lock rows in `revision` and its temp tables, but not any others.
601  // Note array_intersect() preserves keys from the first arg, and we're
602  // assuming $revQuery has `revision` primary and isn't using subtables
603  // for anything we care about.
604  $dbw->lockForUpdate(
605  array_intersect(
606  $revQuery['tables'],
607  [ 'revision', 'revision_comment_temp', 'revision_actor_temp' ]
608  ),
609  [ 'rev_page' => $id ],
610  __METHOD__,
611  [],
612  $revQuery['joins']
613  );
614 
615  $deleteBatchSize = $this->options->get( 'DeleteRevisionsBatchSize' );
616  // Get as many of the page revisions as we are allowed to. The +1 lets us recognize the
617  // unusual case where there were exactly $deleteBatchSize revisions remaining.
618  $res = $dbw->select(
619  $revQuery['tables'],
620  $revQuery['fields'],
621  [ 'rev_page' => $id ],
622  __METHOD__,
623  [ 'ORDER BY' => 'rev_timestamp ASC, rev_id ASC', 'LIMIT' => $deleteBatchSize + 1 ],
624  $revQuery['joins']
625  );
626 
627  // Build their equivalent archive rows
628  $rowsInsert = [];
629  $revids = [];
630 
632  $ipRevIds = [];
633 
634  $done = true;
635  foreach ( $res as $row ) {
636  if ( count( $revids ) >= $deleteBatchSize ) {
637  $done = false;
638  break;
639  }
640 
641  $comment = $this->commentStore->getComment( 'rev_comment', $row );
642  $rowInsert = [
643  'ar_namespace' => $namespace,
644  'ar_title' => $dbKey,
645  'ar_actor' => $row->rev_actor,
646  'ar_timestamp' => $row->rev_timestamp,
647  'ar_minor_edit' => $row->rev_minor_edit,
648  'ar_rev_id' => $row->rev_id,
649  'ar_parent_id' => $row->rev_parent_id,
650  'ar_len' => $row->rev_len,
651  'ar_page_id' => $id,
652  'ar_deleted' => $this->suppress ? $bitfield : $row->rev_deleted,
653  'ar_sha1' => $row->rev_sha1,
654  ] + $this->commentStore->insert( $dbw, 'ar_comment', $comment );
655 
656  $rowsInsert[] = $rowInsert;
657  $revids[] = $row->rev_id;
658 
659  // Keep track of IP edits, so that the corresponding rows can
660  // be deleted in the ip_changes table.
661  if ( (int)$row->rev_user === 0 && IPUtils::isValid( $row->rev_user_text ) ) {
662  $ipRevIds[] = $row->rev_id;
663  }
664  }
665 
666  if ( count( $revids ) > 0 ) {
667  // Copy them into the archive table
668  $dbw->insert( 'archive', $rowsInsert, __METHOD__ );
669 
670  $dbw->delete( 'revision', [ 'rev_id' => $revids ], __METHOD__ );
671  $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
672  if ( $this->options->get( 'ActorTableSchemaMigrationStage' ) & SCHEMA_COMPAT_WRITE_TEMP ) {
673  $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
674  }
675 
676  // Also delete records from ip_changes as applicable.
677  if ( count( $ipRevIds ) > 0 ) {
678  $dbw->delete( 'ip_changes', [ 'ipc_rev_id' => $ipRevIds ], __METHOD__ );
679  }
680  }
681 
682  return $done;
683  }
684 
693  public function doDeleteUpdates( RevisionRecord $revRecord ): void {
694  try {
695  $countable = $this->page->isCountable();
696  } catch ( Exception $ex ) {
697  // fallback for deleting broken pages for which we cannot load the content for
698  // some reason. Note that doDeleteArticleReal() already logged this problem.
699  $countable = false;
700  }
701 
702  // Update site status
703  if ( !$this->isDeletePageUnitTest ) {
704  // TODO Remove conditional once DeferredUpdates is servicified (T265749)
706  [ 'edits' => 1, 'articles' => -$countable, 'pages' => -1 ]
707  ) );
708 
709  // Delete pagelinks, update secondary indexes, etc
710  $updates = $this->getDeletionUpdates( $revRecord );
711  foreach ( $updates as $update ) {
712  DeferredUpdates::addUpdate( $update );
713  }
714  }
715 
716  // Reparse any pages transcluding this page
718  $this->page->getTitle(),
719  'templatelinks',
720  'delete-page',
721  $this->deleter->getUser()->getName(),
722  $this->backlinkCacheFactory->getBacklinkCache( $this->page->getTitle() )
723  );
724  // Reparse any pages including this image
725  if ( $this->page->getTitle()->getNamespace() === NS_FILE ) {
727  $this->page->getTitle(),
728  'imagelinks',
729  'delete-page',
730  $this->deleter->getUser()->getName(),
731  $this->backlinkCacheFactory->getBacklinkCache( $this->page->getTitle() )
732  );
733  }
734 
735  if ( !$this->isDeletePageUnitTest ) {
736  // TODO Remove conditional once WikiPage::onArticleDelete is moved to a proper service
737  // Clear caches
738  WikiPage::onArticleDelete( $this->page->getTitle() );
739  }
740 
742  $this->page->getTitle(),
743  $revRecord,
744  null,
745  $this->localWikiID
746  );
747 
748  // Reset the page object and the Title object
749  $this->page->loadFromRow( false, WikiPage::READ_LATEST );
750 
751  if ( !$this->isDeletePageUnitTest ) {
752  // TODO Remove conditional once DeferredUpdates is servicified (T265749)
753  // Search engine
754  DeferredUpdates::addUpdate( new SearchUpdate( $this->page->getId(), $this->page->getTitle() ) );
755  }
756  }
757 
767  public function getDeletionUpdates( RevisionRecord $rev ): array {
768  $slotContent = array_map( static function ( SlotRecord $slot ) {
769  return $slot->getContent();
770  }, $rev->getSlots()->getSlots() );
771 
772  $allUpdates = [ new LinksDeletionUpdate( $this->page ) ];
773 
774  // NOTE: once Content::getDeletionUpdates() is removed, we only need the content
775  // model here, not the content object!
776  // TODO: consolidate with similar logic in DerivedPageDataUpdater::getSecondaryDataUpdates()
778  $content = null; // in case $slotContent is zero-length
779  foreach ( $slotContent as $role => $content ) {
780  $handler = $content->getContentHandler();
781 
782  $updates = $handler->getDeletionUpdates(
783  $this->page->getTitle(),
784  $role
785  );
786 
787  $allUpdates = array_merge( $allUpdates, $updates );
788  }
789 
790  $this->hookRunner->onPageDeletionDataUpdates(
791  $this->page->getTitle(), $rev, $allUpdates );
792 
793  // TODO: hard deprecate old hook in 1.33
794  $this->hookRunner->onWikiPageDeletionUpdates( $this->page, $content, $allUpdates );
795  return $allUpdates;
796  }
797 }
MediaWiki\Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:64
Page\DeletePage\authorizeDeletion
authorizeDeletion()
Definition: DeletePage.php:290
MediaWiki\Revision\RevisionRecord\SUPPRESSED_ALL
const SUPPRESSED_ALL
Definition: RevisionRecord.php:58
Page\DeletePage\isBatchedDelete
isBatchedDelete(int $safetyMargin=0)
Determines if this deletion would be batched (executed over time by the job queue) or not (completed ...
Definition: DeletePage.php:334
Message\numParam
static numParam( $num)
Definition: Message.php:1127
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:43
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
Page\DeletePage\setTags
setTags(array $tags)
Change tags to apply to the deletion action.
Definition: DeletePage.php:195
Page\DeletePage\assertDeletionAttempted
assertDeletionAttempted()
Asserts that a deletion operation was attempted.
Definition: DeletePage.php:247
Page\DeletePage\__construct
__construct(HookContainer $hookContainer, RevisionStore $revisionStore, LBFactory $lbFactory, JobQueueGroup $jobQueueGroup, CommentStore $commentStore, ServiceOptions $serviceOptions, BagOStuff $recentDeletesCache, string $localWikiID, string $webRequestID, WikiPageFactory $wikiPageFactory, UserFactory $userFactory, ProperPageIdentity $page, Authority $deleter, BacklinkCacheFactory $backlinkCacheFactory)
Definition: DeletePage.php:126
Page\DeletePage\$page
WikiPage $page
Definition: DeletePage.php:85
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
Page\DeletePage\$attemptedDeletion
bool $attemptedDeletion
Whether a deletion was attempted.
Definition: DeletePage.php:108
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
Page\DeletePage\$localWikiID
string $localWikiID
Definition: DeletePage.php:73
Page\DeletePage\$wasScheduled
bool null $wasScheduled
Definition: DeletePage.php:106
Page\DeletePage\keepLegacyHookErrorsSeparate
keepLegacyHookErrorsSeparate()
Definition: DeletePage.php:172
ResourceLoaderWikiModule
Abstraction for ResourceLoader modules which pull from wiki pages.
Definition: ResourceLoaderWikiModule.php:56
SearchUpdate
Database independent search index updater.
Definition: SearchUpdate.php:36
Page\DeletePage\deletionWasScheduled
deletionWasScheduled()
Definition: DeletePage.php:266
MediaWiki\Permissions\PermissionStatus\newEmpty
static newEmpty()
Definition: PermissionStatus.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
Page\DeletePage\$mergeLegacyHookErrors
bool $mergeLegacyHookErrors
Definition: DeletePage.php:101
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:60
LinksUpdate
Class the manages updates of *_link tables as well as similar extension-managed tables.
Definition: LinksUpdate.php:39
CommentStore
Handle database storage of comments such as edit summaries and log reasons.
Definition: CommentStore.php:42
Page\DeletePage\getLegacyHookErrors
getLegacyHookErrors()
Definition: DeletePage.php:164
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:86
Page\DeletePage\$commentStore
CommentStore $commentStore
Definition: DeletePage.php:67
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1056
Page\DeletePage\$deleter
Authority $deleter
Definition: DeletePage.php:87
$res
$res
Definition: testCompression.php:57
Page\DeletePage\archiveRevisions
archiveRevisions(int $id)
Archives revisions as part of page deletion.
Definition: DeletePage.php:577
$revQuery
$revQuery
Definition: testCompression.php:56
Page\DeletePage\getSuccessfulDeletionsIDs
getSuccessfulDeletionsIDs()
Definition: DeletePage.php:257
Page\DeletePage\$webRequestID
string $webRequestID
Definition: DeletePage.php:75
Page\DeletePage\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: DeletePage.php:50
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
Page\DeletePage\getDeletionUpdates
getDeletionUpdates(RevisionRecord $rev)
Definition: DeletePage.php:767
Page\DeletePage\$recentDeletesCache
BagOStuff $recentDeletesCache
Definition: DeletePage.php:71
Page\DeletePage\setIsDeletePageUnitTest
setIsDeletePageUnitTest(bool $test)
Definition: DeletePage.php:227
Page\DeletePage\$isDeletePageUnitTest
bool $isDeletePageUnitTest
Definition: DeletePage.php:82
Page\DeletePage\isBigDeletion
isBigDeletion()
Definition: DeletePage.php:308
ResourceLoaderWikiModule\invalidateModuleCache
static invalidateModuleCache(PageIdentity $page, ?RevisionRecord $old, ?RevisionRecord $new, string $domain)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
Definition: ResourceLoaderWikiModule.php:560
Page\DeletePage\deleteInternal
deleteInternal(string $reason, ?string $webRequestId=null)
Definition: DeletePage.php:400
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
Page\DeletePage\setLogSubtype
setLogSubtype(string $logSubtype)
Set a specific log subtype for the deletion log entry.
Definition: DeletePage.php:206
ChangeTags
Definition: ChangeTags.php:32
Page\DeletePage\$legacyHookErrors
string array $legacyHookErrors
Definition: DeletePage.php:99
DeferredUpdates
Class for managing the deferral of updates within the scope of a PHP script invocation.
Definition: DeferredUpdates.php:82
Page\DeletePage\$revisionStore
RevisionStore $revisionStore
Definition: DeletePage.php:59
WikiPage\onArticleDelete
static onArticleDelete(Title $title)
Clears caches when article is deleted.
Definition: WikiPage.php:2835
Page\DeletePage\$forceImmediate
bool $forceImmediate
Definition: DeletePage.php:96
Page\WikiPageFactory
Definition: WikiPageFactory.php:19
Page\DeletePage\deleteIfAllowed
deleteIfAllowed(string $reason)
Same as deleteUnsafe, but checks permissions.
Definition: DeletePage.php:277
Page\DeletePage\$loadBalancer
ILoadBalancer $loadBalancer
Definition: DeletePage.php:63
Page\DeletePage\$jobQueueGroup
JobQueueGroup $jobQueueGroup
Definition: DeletePage.php:65
$title
$title
Definition: testCompression.php:38
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:71
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Page\DeletePage\$logSubtype
string $logSubtype
Definition: DeletePage.php:94
SiteStatsUpdate
Class for handling updates to the site_stats table.
Definition: SiteStatsUpdate.php:27
Page\DeletePage\forceImmediate
forceImmediate(bool $forceImmediate)
If false, allows deleting over time via the job queue.
Definition: DeletePage.php:217
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
ChangeTags\canAddTagsAccompanyingChange
static canAddTagsAccompanyingChange(array $tags, Authority $performer=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:625
Page\ProperPageIdentity
Interface for objects representing a page that is (or could be, or used to be) an editable page on a ...
Definition: ProperPageIdentity.php:43
$content
$content
Definition: router.php:76
Page\DeletePage\setDeletionAttempted
setDeletionAttempted()
Called before attempting a deletion, allows the result getters to be used.
Definition: DeletePage.php:237
Page\DeletePage\setSuppress
setSuppress(bool $suppress)
If true, suppress all revisions and log the deletion in the suppression log instead of the deletion l...
Definition: DeletePage.php:184
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Page\DeletePage\$lbFactory
LBFactory $lbFactory
Definition: DeletePage.php:61
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
Page\DeletePage\$userFactory
UserFactory $userFactory
Definition: DeletePage.php:77
SCHEMA_COMPAT_WRITE_TEMP
const SCHEMA_COMPAT_WRITE_TEMP
Definition: Defines.php:264
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1456
LinksDeletionUpdate
Update object handling the cleanup of links tables after a page was deleted.
Definition: LinksDeletionUpdate.php:27
Page\DeletePage\$successfulDeletionsIDs
int[] null $successfulDeletionsIDs
Definition: DeletePage.php:104
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
Content
Base interface for content objects.
Definition: Content.php:35
Page\DeletePage\$tags
string[] $tags
Definition: DeletePage.php:92
Page\DeletePage\$hookRunner
HookRunner $hookRunner
Definition: DeletePage.php:57
Page\DeletePage\deleteUnsafe
deleteUnsafe(string $reason)
Back-end article deletion: deletes the article with database consistency, writes logs,...
Definition: DeletePage.php:354
Page\DeletePage\$backlinkCacheFactory
BacklinkCacheFactory $backlinkCacheFactory
Definition: DeletePage.php:79
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(PageIdentity $page, $table, $action='unknown', $userName='unknown', ?BacklinkCache $backlinkCache=null)
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:399
Page\DeletePage\doDeleteUpdates
doDeleteUpdates(RevisionRecord $revRecord)
Definition: DeletePage.php:693
Page\WikiPageFactory\newFromTitle
newFromTitle(PageIdentity $pageIdentity)
Create a WikiPage object from a title.
Definition: WikiPageFactory.php:52
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:49
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:42
DeletePageJob
Class DeletePageJob.
Definition: DeletePageJob.php:8
MediaWiki\Cache\BacklinkCacheFactory
Definition: BacklinkCacheFactory.php:33
Message
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:139
MediaWiki\Page
Definition: ContentModelChangeFactory.php:23
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:44
Page\DeletePage
Definition: DeletePage.php:46
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:554
DeferrableUpdate
Interface that deferrable updates should implement.
Definition: DeferrableUpdate.php:11
NS_FILE
const NS_FILE
Definition: Defines.php:70
RawMessage
Variant of the Message class.
Definition: RawMessage.php:35
Page\DeletePage\$options
ServiceOptions $options
Definition: DeletePage.php:69
MediaWiki\User\UserFactory
Creates User objects.
Definition: UserFactory.php:41
Page\DeletePage\$suppress
bool $suppress
Definition: DeletePage.php:90
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
JobQueueGroup
Class to handle enqueueing of background jobs.
Definition: JobQueueGroup.php:32