Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
EditRevUpdater
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
6 / 6
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setLastEditPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearLastEditPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLogIdsForTarget
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 updateRev
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
8
 getCacheKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter;
4
5use InvalidArgumentException;
6use MediaWiki\Linker\LinkTarget;
7use MediaWiki\Revision\RevisionLookup;
8use MediaWiki\Revision\RevisionRecord;
9use Wikimedia\Rdbms\LBFactory;
10use WikiPage;
11
12/**
13 * This service allows "linking" the edit filter hook and the page save hook
14 */
15class EditRevUpdater {
16    public const SERVICE_NAME = 'AbuseFilterEditRevUpdater';
17
18    /** @var CentralDBManager */
19    private $centralDBManager;
20    /** @var RevisionLookup */
21    private $revisionLookup;
22    /** @var LBFactory */
23    private $lbFactory;
24    /** @var string */
25    private $wikiID;
26
27    /** @var WikiPage|null */
28    private $wikiPage;
29    /**
30     * @var int[][][] IDs of logged filters like [ page title => [ 'local' => [ids], 'global' => [ids] ] ].
31     * @phan-var array<string,array{local:int[],global:int[]}>
32     */
33    private $logIds = [];
34
35    /**
36     * @param CentralDBManager $centralDBManager
37     * @param RevisionLookup $revisionLookup
38     * @param LBFactory $lbFactory
39     * @param string $wikiID
40     */
41    public function __construct(
42        CentralDBManager $centralDBManager,
43        RevisionLookup $revisionLookup,
44        LBFactory $lbFactory,
45        string $wikiID
46    ) {
47        $this->centralDBManager = $centralDBManager;
48        $this->revisionLookup = $revisionLookup;
49        $this->lbFactory = $lbFactory;
50        $this->wikiID = $wikiID;
51    }
52
53    /**
54     * Set the WikiPage object used for the ongoing edit
55     *
56     * @param WikiPage $page
57     */
58    public function setLastEditPage( WikiPage $page ): void {
59        $this->wikiPage = $page;
60    }
61
62    /**
63     * Clear the WikiPage object used for the ongoing edit
64     */
65    public function clearLastEditPage(): void {
66        $this->wikiPage = null;
67    }
68
69    /**
70     * @param LinkTarget $target
71     * @param int[][] $logIds
72     * @phan-param array{local:int[],global:int[]} $logIds
73     */
74    public function setLogIdsForTarget( LinkTarget $target, array $logIds ): void {
75        if ( count( $logIds ) !== 2 || array_diff( array_keys( $logIds ), [ 'local', 'global' ] ) ) {
76            throw new InvalidArgumentException( 'Wrong keys; got: ' . implode( ', ', array_keys( $logIds ) ) );
77        }
78        $key = $this->getCacheKey( $target );
79        $this->logIds[$key] = $logIds;
80    }
81
82    /**
83     * @param WikiPage $wikiPage
84     * @param RevisionRecord $revisionRecord
85     * @return bool Whether the DB was updated
86     */
87    public function updateRev( WikiPage $wikiPage, RevisionRecord $revisionRecord ): bool {
88        $key = $this->getCacheKey( $wikiPage->getTitle() );
89        if ( !isset( $this->logIds[ $key ] ) || $wikiPage !== $this->wikiPage ) {
90            // This isn't the edit $this->logIds was set for
91            $this->logIds = [];
92            return false;
93        }
94
95        // Ignore null edit.
96        $parentRevId = $revisionRecord->getParentId();
97        if ( $parentRevId !== null ) {
98            $parentRev = $this->revisionLookup->getRevisionById( $parentRevId );
99            if ( $parentRev && $revisionRecord->hasSameContent( $parentRev ) ) {
100                $this->logIds = [];
101                return false;
102            }
103        }
104
105        $this->clearLastEditPage();
106
107        $ret = false;
108        $logs = $this->logIds[ $key ];
109        if ( $logs[ 'local' ] ) {
110            $dbw = $this->lbFactory->getPrimaryDatabase();
111            $dbw->update( 'abuse_filter_log',
112                [ 'afl_rev_id' => $revisionRecord->getId() ],
113                [ 'afl_id' => $logs['local'] ],
114                __METHOD__
115            );
116            $ret = true;
117        }
118
119        if ( $logs[ 'global' ] ) {
120            $fdb = $this->centralDBManager->getConnection( DB_PRIMARY );
121            $fdb->update( 'abuse_filter_log',
122                [ 'afl_rev_id' => $revisionRecord->getId() ],
123                [ 'afl_id' => $logs['global'], 'afl_wiki' => $this->wikiID ],
124                __METHOD__
125            );
126            $ret = true;
127        }
128        return $ret;
129    }
130
131    /**
132     * @param LinkTarget $target
133     * @return string
134     */
135    private function getCacheKey( LinkTarget $target ): string {
136        return $target->getNamespace() . '|' . $target->getText();
137    }
138}