includes/PageChangeTracker.php
<?php
namespace CirrusSearch;
use ManualLogEntry;
use MediaWiki\Hook\PageMoveCompleteHook;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\Hook\PageDeleteCompleteHook;
use MediaWiki\Page\Hook\PageDeleteHook;
use MediaWiki\Page\ProperPageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Storage\EditResult;
use MediaWiki\Storage\Hook\PageSaveCompleteHook;
use MediaWiki\User\UserIdentity;
use StatusValue;
/**
* Listen to a set of hooks to keep track if a pageId was involved in a "page change".
* A page change is a change happening to the page itself, based on the hooks used by EventBus
* to emit its page_change stream we use this to determine in which stream we might emit a cirrus
* event based on a LinksUpdateComplete hook. Mainly we want to identify if a particular LinksUpdate
* is caused by a page change or something else unrelated to the life of the page.
*/
class PageChangeTracker implements
PageSaveCompleteHook,
PageMoveCompleteHook,
PageDeleteCompleteHook,
PageDeleteHook
{
/**
* @var array<int,bool>
*/
private array $changedPages = [];
private int $maxStateSize;
public function __construct( int $maxStateSize = 512 ) {
$this->maxStateSize = $maxStateSize;
}
private function flag( int $pageId ): void {
if ( count( $this->changedPages ) >= $this->maxStateSize ) {
$this->changedPages = array_slice( $this->changedPages, 1 - $this->maxStateSize, null, true );
}
$this->changedPages[$pageId] = true;
}
/**
* @param ProperPageIdentity $page
* @param Authority $deleter
* @param string $reason
* @param int $pageID
* @param RevisionRecord $deletedRev
* @param ManualLogEntry $logEntry
* @param int $archivedRevisionCount
* @return void
*/
public function onPageDeleteComplete(
ProperPageIdentity $page,
Authority $deleter,
string $reason,
int $pageID,
RevisionRecord $deletedRev,
ManualLogEntry $logEntry,
int $archivedRevisionCount
) {
$this->flag( $pageID );
}
/**
* @param ProperPageIdentity $page
* @param Authority $deleter
* @param string $reason
* @param StatusValue $status
* @param bool $suppress
* @return void
*/
public function onPageDelete( ProperPageIdentity $page,
Authority $deleter,
string $reason,
StatusValue $status,
bool $suppress
) {
$this->flag( $page->getId() );
}
/**
* @param LinkTarget $old Old title
* @param LinkTarget $new New title
* @param UserIdentity $user User who did the move
* @param int $pageid Database ID of the page that's been moved
* @param int $redirid Database ID of the created redirect
* @param string $reason Reason for the move
* @param RevisionRecord $revision RevisionRecord created by the move
* @return bool|void True or no return value to continue or false stop other hook handlers,
* doesn't abort the move itself
*/
public function onPageMoveComplete( $old, $new, $user, $pageid, $redirid, $reason, $revision ) {
$this->flag( $pageid );
$this->flag( $redirid );
}
/**
* @param \WikiPage $wikiPage WikiPage modified
* @param UserIdentity $user User performing the modification
* @param string $summary Edit summary/comment
* @param int $flags Flags passed to WikiPage::doUserEditContent()
* @param RevisionRecord $revisionRecord New RevisionRecord of the article
* @param EditResult $editResult Object storing information about the effects of this edit,
* including which edits were reverted and which edit is this based on (for reverts and null
* edits).
* @return bool|void True or no return value to continue or false to stop other hook handlers
* from being called; save cannot be aborted
*/
public function onPageSaveComplete( $wikiPage, $user, $summary, $flags, $revisionRecord, $editResult ) {
if ( !$editResult->isNullEdit() ) {
$this->flag( $wikiPage->getId() );
}
}
/**
* Test if this pageId was references in a hook call earlier.
* Calling this function resets the state held by this class.
* @param int $pageId
* @return bool
*/
public function isPageChange( int $pageId ): bool {
if ( array_key_exists( $pageId, $this->changedPages ) ) {
unset( $this->changedPages[$pageId] );
return true;
}
return false;
}
}