Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.65% covered (warning)
83.65%
87 / 104
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageTriage
83.65% covered (warning)
83.65%
87 / 104
33.33% covered (danger)
33.33%
2 / 6
30.18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addToPageTriageQueue
87.10% covered (warning)
87.10%
27 / 31
0.00% covered (danger)
0.00%
0 / 1
6.08
 setTriageStatus
73.81% covered (warning)
73.81%
31 / 42
0.00% covered (danger)
0.00%
0 / 1
17.52
 update
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 retrieve
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
3.01
 bulkSetTagsUpdated
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\PageTriage;
4
5use MediaWiki\MediaWikiServices;
6use MediaWiki\User\UserIdentity;
7use PatrolLog;
8use RecentChange;
9
10/**
11 * TODO: This class does too much. Refactoring into services and classes with single responsibility
12 * in progress, please don't add new methods here.
13 */
14class PageTriage {
15
16    /** @var int The relevant page ID. */
17    protected int $mPageId;
18    /** @var int Review status, valid values are QueueRecord::VALID_REVIEW_STATUSES. */
19    protected int $currentReviewStatus;
20    /** @var string MediaWiki-style timestamp of when the last review happened. */
21    protected string $mReviewedUpdated;
22
23    /** @var bool Used for in-process caching. */
24    protected bool $mLoaded;
25
26    public const CACHE_VERSION = 2;
27
28    /**
29     * @param int $pageId
30     */
31    public function __construct( int $pageId ) {
32        $this->mPageId = $pageId;
33        $this->mLoaded = false;
34    }
35
36    /**
37     * Add page to page triage queue
38     * @param int $reviewStatus The reviewed status of the page, see QueueRecord::VALID_REVIEW_STATUSES
39     * @param UserIdentity|null $user
40     * @param bool $fromRc
41     * @return bool true: add new record, false: update existing record
42     * @throws MWPageTriageMissingRevisionException
43     */
44    public function addToPageTriageQueue(
45        int $reviewStatus = 0,
46        UserIdentity $user = null,
47        bool $fromRc = false
48    ): bool {
49        if ( $this->retrieve() ) {
50            if ( $this->currentReviewStatus !== $reviewStatus ) {
51                $this->setTriageStatus( $reviewStatus, $user, $fromRc );
52            }
53            return false;
54        }
55
56        $dbw = PageTriageUtil::getPrimaryConnection();
57
58        // Pull page creation date from database
59        // must select from master here since the page has just been created, and probably
60        // hasn't propagated to the replicas yet.
61        $res = $dbw->newSelectQueryBuilder()
62            ->select( [
63                'creation_date' => 'MIN(rev_timestamp)',
64                'last_edit_date' => 'MAX(rev_timestamp)'
65            ] )
66            ->from( 'revision' )
67            ->where( [ 'rev_page' => $this->mPageId ] )
68            ->caller( __METHOD__ )
69            ->fetchRow();
70
71        if ( !$res ) {
72            throw new MWPageTriageMissingRevisionException( 'Page missing revision!' );
73        }
74
75        $queueRecord = new QueueRecord(
76            $this->mPageId,
77            $reviewStatus,
78            false,
79            $res->creation_date,
80            null,
81            $res->last_edit_date,
82            $user ? $user->getId() : 0,
83        );
84        /** @var QueueManager $queueManager */
85        $queueManager = MediaWikiServices::getInstance()->get( 'PageTriageQueueManager' );
86        $status = $queueManager->insert( $queueRecord );
87
88        if ( $status->isGood() ) {
89            $this->mReviewedUpdated = $queueRecord->getReviewedUpdatedTimestamp();
90            $this->currentReviewStatus = $reviewStatus;
91        }
92
93        return true;
94    }
95
96    /**
97     * Set the review status of an article in the PageTriage queue.
98     *
99     * TODO: Move this code into QueueManager::setStatusForPageId().
100     *
101     * @param int $newReviewStatus see QueueRecord::VALID_REVIEW_STATUSES
102     * @param UserIdentity|null $user
103     * @param bool $fromRc
104     * @return bool If a page status was updated
105     */
106    public function setTriageStatus( int $newReviewStatus = 0, UserIdentity $user = null, bool $fromRc = false ): bool {
107        if ( !in_array( $newReviewStatus, QueueRecord::VALID_REVIEW_STATUSES ) ) {
108            // TODO: Should log an error here, or maybe just not accept invalid review status to begin with.
109            $newReviewStatus = QueueRecord::REVIEW_STATUS_UNREVIEWED;
110        }
111
112        if ( !$this->retrieve() ) {
113            // Page doesn't exist in pagetriage_page
114            return false;
115        }
116        if ( $this->currentReviewStatus === $newReviewStatus ) {
117            // Status doesn't change
118            return false;
119        }
120        if ( $this->currentReviewStatus === QueueRecord::REVIEW_STATUS_AUTOPATROLLED &&
121            $newReviewStatus !== QueueRecord::REVIEW_STATUS_UNREVIEWED ) {
122            // Only unreviewing is allowed for autopatrolled articles
123            return false;
124        }
125
126        $dbw = PageTriageUtil::getPrimaryConnection();
127        $dbw->startAtomic( __METHOD__ );
128        $set = [
129            'ptrp_reviewed' => $newReviewStatus,
130            'ptrp_reviewed_updated' => $dbw->timestamp( wfTimestampNow() ),
131            'ptrp_last_reviewed_by' => $user ? $user->getId() : 0
132        ];
133        $dbw->newUpdateQueryBuilder()
134            ->update( 'pagetriage_page' )
135            ->set( $set )
136            ->where( [
137                'ptrp_page_id' => $this->mPageId,
138                $dbw->expr( 'ptrp_reviewed', '!=', $newReviewStatus ),
139            ] )
140            ->caller( __METHOD__ )
141            ->execute();
142
143        if ( $dbw->affectedRows() > 0 ) {
144            $this->currentReviewStatus = $newReviewStatus;
145            $this->mReviewedUpdated = $set['ptrp_reviewed_updated'];
146            // @Todo - case for marking a page as untriaged and make sure this logic is correct
147            if ( !$fromRc && $newReviewStatus && $user ) {
148                $rc = RecentChange::newFromConds( [
149                    'rc_cur_id' => $this->mPageId,
150                    'rc_new' => '1'
151                ], __METHOD__ );
152                if ( $rc && !$rc->getAttribute( 'rc_patrolled' ) ) {
153                    $rc->reallyMarkPatrolled();
154                    PatrolLog::record( $rc, false, $user, 'pagetriage' );
155                }
156            }
157        }
158        $dbw->endAtomic( __METHOD__ );
159
160        $articleMetadata = new ArticleMetadata( [ $this->mPageId ] );
161        $metadataArray = $articleMetadata->getMetadata();
162
163        if ( array_key_exists( $this->mPageId, $metadataArray ) ) {
164            $articleMetadata->flushMetadataFromCache( $this->mPageId );
165        }
166        return true;
167    }
168
169    /**
170     * Update the database record
171     * @param array $row key => value pair to be updated
172     * Todo: ptrpt_reviewed should not updated from this function, add exception to catch this
173     *       or find a better solution
174     */
175    public function update( $row ) {
176        if ( !$row ) {
177            return;
178        }
179
180        $dbw = PageTriageUtil::getPrimaryConnection();
181        $dbw->newUpdateQueryBuilder()
182            ->update( 'pagetriage_page' )
183            ->set( $row )
184            ->where( [ 'ptrp_page_id' => $this->mPageId ] )
185            ->caller( __METHOD__ )
186            ->execute();
187    }
188
189    /**
190     * Load a page triage record
191     * @return bool
192     */
193    public function retrieve() {
194        if ( $this->mLoaded ) {
195            return true;
196        }
197
198        $pageTriageServices = PageTriageServices::wrap( MediaWikiServices::getInstance() );
199        $queueLookup = $pageTriageServices->getQueueLookup();
200        $queueRecord = $queueLookup->getByPageId( $this->mPageId );
201        if ( !$queueRecord ) {
202            return false;
203        }
204
205        $this->currentReviewStatus = $queueRecord->getReviewedStatus();
206        $this->mReviewedUpdated = wfTimestamp( TS_UNIX, $queueRecord->getReviewedUpdatedTimestamp() );
207        $this->mLoaded = true;
208        return true;
209    }
210
211    /**
212     * Set the tags updated timestamp
213     * @param array $pageIds
214     * @return string
215     */
216    public static function bulkSetTagsUpdated( $pageIds ) {
217        $dbw = PageTriageUtil::getPrimaryConnection();
218
219        $now = wfTimestampNow();
220        $dbw->newUpdateQueryBuilder()
221            ->update( 'pagetriage_page' )
222            ->set( [ 'ptrp_tags_updated' => $dbw->timestamp( $now ) ] )
223            ->where( [ 'ptrp_page_id' => $pageIds ] )
224            ->caller( __METHOD__ )
225            ->execute();
226
227        return $now;
228    }
229}