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