MediaWiki REL1_37
DeletePage.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Page;
4
5use BadMethodCallException;
6use BagOStuff;
7use ChangeTags;
9use Content;
13use Exception;
16use LinksUpdate;
17use LogicException;
30use Message;
31use RawMessage;
33use SearchUpdate;
35use Status;
36use StatusValue;
37use Wikimedia\IPUtils;
40use WikiPage;
41
50 public const CONSTRUCTOR_OPTIONS = [
51 'DeleteRevisionsBatchSize',
52 'ActorTableSchemaMigrationStage',
53 'DeleteRevisionsLimit',
54 ];
55
57 private $hookRunner;
61 private $lbFactory;
69 private $options;
73 private $localWikiID;
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 = '';
102
108 private $attemptedDeletion = false;
109
126 public function __construct(
127 HookContainer $hookContainer,
132 ServiceOptions $serviceOptions,
134 string $localWikiID,
135 string $webRequestID,
136 WikiPageFactory $wikiPageFactory,
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 {
218 $this->forceImmediate = $forceImmediate;
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();
259 return $this->successfulDeletionsIDs;
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
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 // This conditional is just a sanity check
667 if ( count( $revids ) > 0 ) {
668 // Copy them into the archive table
669 $dbw->insert( 'archive', $rowsInsert, __METHOD__ );
670
671 $dbw->delete( 'revision', [ 'rev_id' => $revids ], __METHOD__ );
672 $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
673 if ( $this->options->get( 'ActorTableSchemaMigrationStage' ) & SCHEMA_COMPAT_WRITE_TEMP ) {
674 $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
675 }
676
677 // Also delete records from ip_changes as applicable.
678 if ( count( $ipRevIds ) > 0 ) {
679 $dbw->delete( 'ip_changes', [ 'ipc_rev_id' => $ipRevIds ], __METHOD__ );
680 }
681 }
682
683 return $done;
684 }
685
694 public function doDeleteUpdates( RevisionRecord $revRecord ): void {
695 try {
696 $countable = $this->page->isCountable();
697 } catch ( Exception $ex ) {
698 // fallback for deleting broken pages for which we cannot load the content for
699 // some reason. Note that doDeleteArticleReal() already logged this problem.
700 $countable = false;
701 }
702
703 // Update site status
704 if ( !$this->isDeletePageUnitTest ) {
705 // TODO Remove conditional once DeferredUpdates is servicified (T265749)
706 DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
707 [ 'edits' => 1, 'articles' => -$countable, 'pages' => -1 ]
708 ) );
709
710 // Delete pagelinks, update secondary indexes, etc
711 $updates = $this->getDeletionUpdates( $revRecord );
712 foreach ( $updates as $update ) {
713 DeferredUpdates::addUpdate( $update );
714 }
715 }
716
717 // Reparse any pages transcluding this page
719 $this->page->getTitle(),
720 'templatelinks',
721 'delete-page',
722 $this->deleter->getUser()->getName(),
723 $this->backlinkCacheFactory->getBacklinkCache( $this->page->getTitle() )
724 );
725 // Reparse any pages including this image
726 if ( $this->page->getTitle()->getNamespace() === NS_FILE ) {
728 $this->page->getTitle(),
729 'imagelinks',
730 'delete-page',
731 $this->deleter->getUser()->getName(),
732 $this->backlinkCacheFactory->getBacklinkCache( $this->page->getTitle() )
733 );
734 }
735
736 if ( !$this->isDeletePageUnitTest ) {
737 // TODO Remove conditional once WikiPage::onArticleDelete is moved to a proper service
738 // Clear caches
739 WikiPage::onArticleDelete( $this->page->getTitle() );
740 }
741
743 $this->page->getTitle(),
744 $revRecord,
745 null,
746 $this->localWikiID
747 );
748
749 // Reset the page object and the Title object
750 $this->page->loadFromRow( false, WikiPage::READ_LATEST );
751
752 if ( !$this->isDeletePageUnitTest ) {
753 // TODO Remove conditional once DeferredUpdates is servicified (T265749)
754 // Search engine
755 DeferredUpdates::addUpdate( new SearchUpdate( $this->page->getId(), $this->page->getTitle() ) );
756 }
757 }
758
768 public function getDeletionUpdates( RevisionRecord $rev ): array {
769 $slotContent = array_map( static function ( SlotRecord $slot ) {
770 return $slot->getContent();
771 }, $rev->getSlots()->getSlots() );
772
773 $allUpdates = [ new LinksDeletionUpdate( $this->page ) ];
774
775 // NOTE: once Content::getDeletionUpdates() is removed, we only need the content
776 // model here, not the content object!
777 // TODO: consolidate with similar logic in DerivedPageDataUpdater::getSecondaryDataUpdates()
779 $content = null; // in case $slotContent is zero-length
780 foreach ( $slotContent as $role => $content ) {
781 $handler = $content->getContentHandler();
782
783 $updates = $handler->getDeletionUpdates(
784 $this->page->getTitle(),
785 $role
786 );
787
788 $allUpdates = array_merge( $allUpdates, $updates );
789 }
790
791 $this->hookRunner->onPageDeletionDataUpdates(
792 $this->page->getTitle(), $rev, $allUpdates );
793
794 // TODO: hard deprecate old hook in 1.33
795 $this->hookRunner->onWikiPageDeletionUpdates( $this->page, $content, $allUpdates );
796 return $allUpdates;
797 }
798}
const SCHEMA_COMPAT_WRITE_TEMP
Definition Defines.php:264
const NS_FILE
Definition Defines.php:70
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:88
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:86
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...
Handle database storage of comments such as edit summaries and log reasons.
Class for managing the deferral of updates within the scope of a PHP script invocation.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Class DeletePageJob.
Class to handle enqueueing of background jobs.
Update object handling the cleanup of links tables after a page was deleted.
Class the manages updates of *_link tables as well as similar extension-managed tables.
static queueRecursiveJobsForTable(PageIdentity $page, $table, $action='unknown', $userName='unknown', ?BacklinkCache $backlinkCache=null)
Queue a RefreshLinks job for any table.
Class for creating new log entries and inserting them into the database.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
PSR-3 logger instance factory.
__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)
setDeletionAttempted()
Called before attempting a deletion, allows the result getters to be used.
doDeleteUpdates(RevisionRecord $revRecord)
getDeletionUpdates(RevisionRecord $rev)
setTags(array $tags)
Change tags to apply to the deletion action.
deleteIfAllowed(string $reason)
Same as deleteUnsafe, but checks permissions.
setLogSubtype(string $logSubtype)
Set a specific log subtype for the deletion log entry.
assertDeletionAttempted()
Asserts that a deletion operation was attempted.
ServiceOptions $options
archiveRevisions(int $id)
Archives revisions as part of page deletion.
deleteInternal(string $reason, ?string $webRequestId=null)
isBatchedDelete(int $safetyMargin=0)
Determines if this deletion would be batched (executed over time by the job queue) or not (completed ...
string array $legacyHookErrors
setIsDeletePageUnitTest(bool $test)
RevisionStore $revisionStore
ILoadBalancer $loadBalancer
int[] null $successfulDeletionsIDs
BacklinkCacheFactory $backlinkCacheFactory
deleteUnsafe(string $reason)
Back-end article deletion: deletes the article with database consistency, writes logs,...
bool $attemptedDeletion
Whether a deletion was attempted.
JobQueueGroup $jobQueueGroup
forceImmediate(bool $forceImmediate)
If false, allows deleting over time via the job queue.
setSuppress(bool $suppress)
If true, suppress all revisions and log the deletion in the suppression log instead of the deletion l...
newFromTitle(PageIdentity $pageIdentity)
Create a WikiPage object from a title.
A StatusValue for permission errors.
Page revision base class.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
Creates User objects.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:138
static numParam( $num)
Definition Message.php:1101
Variant of the Message class.
Abstraction for ResourceLoader modules which pull from wiki pages.
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...
Database independent search index updater.
Class for handling updates to the site_stats table.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
fatal( $message,... $parameters)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
static newGood( $value=null)
Factory function for good results.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Class representing a MediaWiki article and history.
Definition WikiPage.php:60
static onArticleDelete(Title $title)
Clears caches when article is deleted.
An interface for generating database load balancers.
Definition LBFactory.php:42
Base interface for content objects.
Definition Content.php:35
Interface that deferrable updates should implement.
Interface for objects representing a page that is (or could be, or used to be) an editable page on a ...
This interface represents the authority associated the current execution context, such as a web reque...
Definition Authority.php:37
getMainLB( $domain=false)
Get the tracked load balancer instance for a main cluster.
Database cluster connection, tracking, load balancing, and transaction manager interface.
const DB_REPLICA
Definition defines.php:25
const DB_PRIMARY
Definition defines.php:27
if(count( $args)< 1) $job
$content
Definition router.php:76
return true
Definition router.php:92