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\Logging\PatrolLog;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\RecentChanges\RecentChange;
8use MediaWiki\User\UserIdentity;
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(
107        int $newReviewStatus = 0, ?UserIdentity $user = null, bool $fromRc = false
108    ): bool {
109        if ( !in_array( $newReviewStatus, QueueRecord::VALID_REVIEW_STATUSES ) ) {
110            // TODO: Should log an error here, or maybe just not accept invalid review status to begin with.
111            $newReviewStatus = QueueRecord::REVIEW_STATUS_UNREVIEWED;
112        }
113
114        if ( !$this->retrieve() ) {
115            // Page doesn't exist in pagetriage_page
116            return false;
117        }
118        if ( $this->currentReviewStatus === $newReviewStatus ) {
119            // Status doesn't change
120            return false;
121        }
122        if ( $this->currentReviewStatus === QueueRecord::REVIEW_STATUS_AUTOPATROLLED &&
123            $newReviewStatus !== QueueRecord::REVIEW_STATUS_UNREVIEWED ) {
124            // Only unreviewing is allowed for autopatrolled articles
125            return false;
126        }
127
128        $dbw = PageTriageUtil::getPrimaryConnection();
129        $dbw->startAtomic( __METHOD__ );
130        $set = [
131            'ptrp_reviewed' => $newReviewStatus,
132            'ptrp_reviewed_updated' => $dbw->timestamp( wfTimestampNow() ),
133            'ptrp_last_reviewed_by' => $user ? $user->getId() : 0
134        ];
135        $dbw->newUpdateQueryBuilder()
136            ->update( 'pagetriage_page' )
137            ->set( $set )
138            ->where( [
139                'ptrp_page_id' => $this->mPageId,
140                $dbw->expr( 'ptrp_reviewed', '!=', $newReviewStatus ),
141            ] )
142            ->caller( __METHOD__ )
143            ->execute();
144
145        if ( $dbw->affectedRows() > 0 ) {
146            $this->currentReviewStatus = $newReviewStatus;
147            $this->mReviewedUpdated = $set['ptrp_reviewed_updated'];
148            // @Todo - case for marking a page as untriaged and make sure this logic is correct
149            if ( !$fromRc && $newReviewStatus && $user ) {
150                $rc = RecentChange::newFromConds( [
151                    'rc_cur_id' => $this->mPageId,
152                    'rc_source' => RecentChange::SRC_NEW,
153                ], __METHOD__ );
154                if ( $rc && !$rc->getAttribute( 'rc_patrolled' ) ) {
155                    $rc->reallyMarkPatrolled();
156                    PatrolLog::record( $rc, false, $user, 'pagetriage' );
157                }
158            }
159        }
160        $dbw->endAtomic( __METHOD__ );
161
162        $articleMetadata = new ArticleMetadata( [ $this->mPageId ] );
163        $metadataArray = $articleMetadata->getMetadata();
164
165        if ( array_key_exists( $this->mPageId, $metadataArray ) ) {
166            $articleMetadata->flushMetadataFromCache( $this->mPageId );
167        }
168        return true;
169    }
170
171    /**
172     * Update the database record
173     * @param array $row key => value pair to be updated
174     * Todo: ptrpt_reviewed should not updated from this function, add exception to catch this
175     *       or find a better solution
176     */
177    public function update( $row ) {
178        if ( !$row ) {
179            return;
180        }
181
182        $dbw = PageTriageUtil::getPrimaryConnection();
183        $dbw->newUpdateQueryBuilder()
184            ->update( 'pagetriage_page' )
185            ->set( $row )
186            ->where( [ 'ptrp_page_id' => $this->mPageId ] )
187            ->caller( __METHOD__ )
188            ->execute();
189    }
190
191    /**
192     * Load a page triage record
193     * @return bool
194     */
195    public function retrieve() {
196        if ( $this->mLoaded ) {
197            return true;
198        }
199
200        $pageTriageServices = PageTriageServices::wrap( MediaWikiServices::getInstance() );
201        $queueLookup = $pageTriageServices->getQueueLookup();
202        $queueRecord = $queueLookup->getByPageId( $this->mPageId );
203        if ( !$queueRecord ) {
204            return false;
205        }
206
207        $this->currentReviewStatus = $queueRecord->getReviewedStatus();
208        $this->mReviewedUpdated = wfTimestamp( TS_UNIX, $queueRecord->getReviewedUpdatedTimestamp() );
209        $this->mLoaded = true;
210        return true;
211    }
212
213    /**
214     * Set the tags updated timestamp
215     * @param array $pageIds
216     * @return string
217     */
218    public static function bulkSetTagsUpdated( $pageIds ) {
219        $dbw = PageTriageUtil::getPrimaryConnection();
220
221        $now = wfTimestampNow();
222        $dbw->newUpdateQueryBuilder()
223            ->update( 'pagetriage_page' )
224            ->set( [ 'ptrp_tags_updated' => $dbw->timestamp( $now ) ] )
225            ->where( [ 'ptrp_page_id' => $pageIds ] )
226            ->caller( __METHOD__ )
227            ->execute();
228
229        return $now;
230    }
231}