Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
PageChangeTracker
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
7 / 7
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 flag
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 onPageDeleteComplete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onPageDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onPageMoveComplete
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 onPageSaveComplete
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isPageChange
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace CirrusSearch;
4
5use ManualLogEntry;
6use MediaWiki\Hook\PageMoveCompleteHook;
7use MediaWiki\Linker\LinkTarget;
8use MediaWiki\Page\Hook\PageDeleteCompleteHook;
9use MediaWiki\Page\Hook\PageDeleteHook;
10use MediaWiki\Page\ProperPageIdentity;
11use MediaWiki\Permissions\Authority;
12use MediaWiki\Revision\RevisionRecord;
13use MediaWiki\Storage\EditResult;
14use MediaWiki\Storage\Hook\PageSaveCompleteHook;
15use MediaWiki\User\UserIdentity;
16use StatusValue;
17
18/**
19 * Listen to a set of hooks to keep track if a pageId was involved in a "page change".
20 * A page change is a change happening to the page itself, based on the hooks used by EventBus
21 * to emit its page_change stream we use this to determine in which stream we might emit a cirrus
22 * event based on a LinksUpdateComplete hook. Mainly we want to identify if a particular LinksUpdate
23 * is caused by a page change or something else unrelated to the life of the page.
24 */
25class PageChangeTracker implements
26    PageSaveCompleteHook,
27    PageMoveCompleteHook,
28    PageDeleteCompleteHook,
29    PageDeleteHook
30{
31    /**
32     * @var array<int,bool>
33     */
34    private array $changedPages = [];
35    private int $maxStateSize;
36
37    public function __construct( int $maxStateSize = 512 ) {
38        $this->maxStateSize = $maxStateSize;
39    }
40
41    private function flag( int $pageId ): void {
42        if ( count( $this->changedPages ) >= $this->maxStateSize ) {
43            $this->changedPages = array_slice( $this->changedPages, 1 - $this->maxStateSize, null, true );
44        }
45        $this->changedPages[$pageId] = true;
46    }
47
48    /**
49     * @param ProperPageIdentity $page
50     * @param Authority $deleter
51     * @param string $reason
52     * @param int $pageID
53     * @param RevisionRecord $deletedRev
54     * @param ManualLogEntry $logEntry
55     * @param int $archivedRevisionCount
56     * @return void
57     */
58    public function onPageDeleteComplete(
59        ProperPageIdentity $page,
60        Authority $deleter,
61        string $reason,
62        int $pageID,
63        RevisionRecord $deletedRev,
64        ManualLogEntry $logEntry,
65        int $archivedRevisionCount
66    ) {
67        $this->flag( $pageID );
68    }
69
70    /**
71     * @param ProperPageIdentity $page
72     * @param Authority $deleter
73     * @param string $reason
74     * @param StatusValue $status
75     * @param bool $suppress
76     * @return void
77     */
78    public function onPageDelete( ProperPageIdentity $page,
79        Authority $deleter,
80        string $reason,
81        StatusValue $status,
82        bool $suppress
83    ) {
84        $this->flag( $page->getId() );
85    }
86
87    /**
88     * @param LinkTarget $old Old title
89     * @param LinkTarget $new New title
90     * @param UserIdentity $user User who did the move
91     * @param int $pageid Database ID of the page that's been moved
92     * @param int $redirid Database ID of the created redirect
93     * @param string $reason Reason for the move
94     * @param RevisionRecord $revision RevisionRecord created by the move
95     * @return bool|void True or no return value to continue or false stop other hook handlers,
96     *     doesn't abort the move itself
97     */
98    public function onPageMoveComplete( $old, $new, $user, $pageid, $redirid, $reason, $revision ) {
99        $this->flag( $pageid );
100        $this->flag( $redirid );
101    }
102
103    /**
104     * @param \WikiPage $wikiPage WikiPage modified
105     * @param UserIdentity $user User performing the modification
106     * @param string $summary Edit summary/comment
107     * @param int $flags Flags passed to WikiPage::doUserEditContent()
108     * @param RevisionRecord $revisionRecord New RevisionRecord of the article
109     * @param EditResult $editResult Object storing information about the effects of this edit,
110     *   including which edits were reverted and which edit is this based on (for reverts and null
111     *   edits).
112     * @return bool|void True or no return value to continue or false to stop other hook handlers
113     *    from being called; save cannot be aborted
114     */
115    public function onPageSaveComplete( $wikiPage, $user, $summary, $flags, $revisionRecord, $editResult ) {
116        if ( !$editResult->isNullEdit() ) {
117            $this->flag( $wikiPage->getId() );
118        }
119    }
120
121    /**
122     * Test if this pageId was references in a hook call earlier.
123     * Calling this function resets the state held by this class.
124     * @param int $pageId
125     * @return bool
126     */
127    public function isPageChange( int $pageId ): bool {
128        if ( array_key_exists( $pageId, $this->changedPages ) ) {
129            unset( $this->changedPages[$pageId] );
130            return true;
131        }
132        return false;
133    }
134}