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 MediaWiki\Hook\PageMoveCompleteHook;
6use MediaWiki\Linker\LinkTarget;
7use MediaWiki\Logging\ManualLogEntry;
8use MediaWiki\Page\Hook\PageDeleteCompleteHook;
9use MediaWiki\Page\Hook\PageDeleteHook;
10use MediaWiki\Page\ProperPageIdentity;
11use MediaWiki\Page\WikiPage;
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    PageDeleteHook
31{
32    /**
33     * @var array<int,bool>
34     */
35    private array $changedPages = [];
36    private int $maxStateSize;
37
38    public function __construct( int $maxStateSize = 512 ) {
39        $this->maxStateSize = $maxStateSize;
40    }
41
42    private function flag( int $pageId ): void {
43        if ( count( $this->changedPages ) >= $this->maxStateSize ) {
44            $this->changedPages = array_slice( $this->changedPages, 1 - $this->maxStateSize, null, true );
45        }
46        $this->changedPages[$pageId] = true;
47    }
48
49    /**
50     * @param ProperPageIdentity $page
51     * @param Authority $deleter
52     * @param string $reason
53     * @param int $pageID
54     * @param RevisionRecord $deletedRev
55     * @param ManualLogEntry $logEntry
56     * @param int $archivedRevisionCount
57     * @return void
58     */
59    public function onPageDeleteComplete(
60        ProperPageIdentity $page,
61        Authority $deleter,
62        string $reason,
63        int $pageID,
64        RevisionRecord $deletedRev,
65        ManualLogEntry $logEntry,
66        int $archivedRevisionCount
67    ) {
68        $this->flag( $pageID );
69    }
70
71    /**
72     * @param ProperPageIdentity $page
73     * @param Authority $deleter
74     * @param string $reason
75     * @param StatusValue $status
76     * @param bool $suppress
77     * @return void
78     */
79    public function onPageDelete( ProperPageIdentity $page,
80        Authority $deleter,
81        string $reason,
82        StatusValue $status,
83        bool $suppress
84    ) {
85        $this->flag( $page->getId() );
86    }
87
88    /**
89     * @param LinkTarget $old Old title
90     * @param LinkTarget $new New title
91     * @param UserIdentity $user User who did the move
92     * @param int $pageid Database ID of the page that's been moved
93     * @param int $redirid Database ID of the created redirect
94     * @param string $reason Reason for the move
95     * @param RevisionRecord $revision RevisionRecord created by the move
96     * @return bool|void True or no return value to continue or false stop other hook handlers,
97     *     doesn't abort the move itself
98     */
99    public function onPageMoveComplete( $old, $new, $user, $pageid, $redirid, $reason, $revision ) {
100        $this->flag( $pageid );
101        $this->flag( $redirid );
102    }
103
104    /**
105     * @param WikiPage $wikiPage WikiPage modified
106     * @param UserIdentity $user User performing the modification
107     * @param string $summary Edit summary/comment
108     * @param int $flags Flags passed to WikiPage::doUserEditContent()
109     * @param RevisionRecord $revisionRecord New RevisionRecord of the article
110     * @param EditResult $editResult Object storing information about the effects of this edit,
111     *   including which edits were reverted and which edit is this based on (for reverts and null
112     *   edits).
113     * @return bool|void True or no return value to continue or false to stop other hook handlers
114     *    from being called; save cannot be aborted
115     */
116    public function onPageSaveComplete( $wikiPage, $user, $summary, $flags, $revisionRecord, $editResult ) {
117        if ( !$editResult->isNullEdit() ) {
118            $this->flag( $wikiPage->getId() );
119        }
120    }
121
122    /**
123     * Test if this pageId was references in a hook call earlier.
124     * Calling this function resets the state held by this class.
125     * @param int $pageId
126     * @return bool
127     */
128    public function isPageChange( int $pageId ): bool {
129        if ( array_key_exists( $pageId, $this->changedPages ) ) {
130            unset( $this->changedPages[$pageId] );
131            return true;
132        }
133        return false;
134    }
135}