Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 213
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
UnreviewedPages
0.00% covered (danger)
0.00%
0 / 213
0.00% covered (danger)
0.00%
0 / 8
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 showForm
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
42
 showPageList
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 formatRow
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
72
 getLineClass
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 updateQueryCache
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 1
42
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Html\Html;
4use MediaWiki\MainConfigNames;
5use MediaWiki\MediaWikiServices;
6use MediaWiki\SpecialPage\SpecialPage;
7use MediaWiki\Title\Title;
8
9class UnreviewedPages extends SpecialPage {
10    /** @var UnreviewedPagesPager */
11    private $pager = null;
12
13    /** @var int */
14    private $currentUnixTS;
15
16    /** @var int */
17    private $namespace;
18
19    /** @var string */
20    private $category;
21
22    /** @var bool */
23    private $hideRedirs;
24
25    /** @var bool */
26    private $isMiser;
27
28    /** How many entries are at most stored in the cache */
29    private const CACHE_SIZE = 5000;
30
31    public function __construct() {
32        parent::__construct( 'UnreviewedPages', 'unreviewedpages' );
33    }
34
35    /**
36     * @inheritDoc
37     */
38    public function execute( $par ) {
39        $request = $this->getRequest();
40
41        $this->isMiser = $this->getConfig()->get( MainConfigNames::MiserMode );
42
43        $this->setHeaders();
44        $this->addHelpLink( 'Help:Extension:FlaggedRevs' );
45        if ( !MediaWikiServices::getInstance()->getPermissionManager()
46            ->userHasRight( $this->getUser(), 'unreviewedpages' )
47        ) {
48            throw new PermissionsError( 'unreviewedpages' );
49        }
50
51        $this->currentUnixTS = (int)wfTimestamp();
52
53        # Get default namespace
54        $this->namespace = $request->getInt( 'namespace', FlaggedRevs::getFirstReviewNamespace() );
55        $category = trim( $request->getVal( 'category', '' ) );
56        $catTitle = Title::makeTitleSafe( NS_CATEGORY, $category );
57        $this->category = $catTitle === null ? '' : $catTitle->getText();
58        $level = $request->getInt( 'level' );
59        $this->hideRedirs = $request->getBool( 'hideredirs', true );
60
61        $this->pager = new UnreviewedPagesPager( $this, !$this->isMiser,
62            $this->namespace, !$this->hideRedirs, $this->category, $level );
63
64        $this->showForm();
65        $this->showPageList();
66    }
67
68    private function showForm() {
69        # Add explanatory text
70        $this->getOutput()->addWikiMsg( 'unreviewedpages-list',
71            $this->getLanguage()->formatNum( $this->pager->getNumRows() ) );
72
73        # show/hide links
74        $link = $this->getLinkRenderer()->makeLink(
75            $this->getPageTitle(),
76            $this->msg( $this->hideRedirs ? 'show' : 'hide' )->text(),
77            [],
78            [
79                'hideredirs' => $this->hideRedirs ? '0' : '1',
80                'category' => $this->category,
81                'namespace' => $this->namespace,
82            ]
83        );
84        $showhideredirs = $this->msg( 'unreviewedpages-showhide-redirect' )->rawParams( $link )->escaped();
85
86        # Add form...
87        $form = Html::openElement( 'form', [
88            'name' => 'unreviewedpages',
89            'action' => $this->getConfig()->get( MainConfigNames::Script ),
90            'method' => 'get',
91        ] ) . "\n";
92        $form .= "<fieldset><legend>" . $this->msg( 'unreviewedpages-legend' )->escaped() . "</legend>\n";
93        $form .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n";
94        # Add dropdowns as needed
95        if ( count( FlaggedRevs::getReviewNamespaces() ) > 1 ) {
96            $form .= FlaggedRevsXML::getNamespaceMenu( $this->namespace ) . '&#160;';
97        }
98        $form .=
99            "<span style='white-space: nowrap;'>" .
100            Xml::label( $this->msg( 'unreviewedpages-category' )->text(), 'category' ) . '&#160;' .
101            Xml::input( 'category', 30, $this->category, [ 'id' => 'category' ] ) .
102            '</span><br />';
103        $form .= $showhideredirs . '&#160;&#160;';
104        $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() );
105        $form .= '</fieldset>';
106        $form .= Html::closeElement( 'form' ) . "\n";
107
108        # Query may get too slow to be live...
109        if ( $this->isMiser ) {
110            $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
111
112            $ts = $dbr->newSelectQueryBuilder()
113                ->select( 'qci_timestamp' )
114                ->from( 'querycache_info' )
115                ->where( [ 'qci_type' => 'fr_unreviewedpages' ] )
116                ->caller( __METHOD__ )
117                ->fetchField();
118            if ( $ts ) {
119                $ts = wfTimestamp( TS_MW, $ts );
120                $td = $this->getLanguage()->timeanddate( $ts );
121                $d = $this->getLanguage()->date( $ts );
122                $t = $this->getLanguage()->time( $ts );
123                $form .= $this->msg( 'perfcachedts', $td, $d, $t, self::CACHE_SIZE )->parseAsBlock();
124            } else {
125                $form .= $this->msg( 'perfcached', self::CACHE_SIZE )->parseAsBlock();
126            }
127        }
128
129        $this->getOutput()->addHTML( $form );
130    }
131
132    private function showPageList() {
133        $out = $this->getOutput();
134        if ( $this->pager->getNumRows() ) {
135            $out->addHTML( $this->pager->getNavigationBar() );
136            $out->addHTML( $this->pager->getBody() );
137            $out->addHTML( $this->pager->getNavigationBar() );
138        } else {
139            $out->addWikiMsg( 'unreviewedpages-none' );
140        }
141    }
142
143    /**
144     * @param stdClass $row
145     * @return string HTML
146     */
147    public function formatRow( $row ) {
148        $title = Title::newFromRow( $row );
149
150        $stxt = '';
151        $linkRenderer = $this->getLinkRenderer();
152        $link = $linkRenderer->makeLink( $title, null, [], [ 'redirect' => 'no' ] );
153        $dirmark = $this->getLanguage()->getDirMark();
154        $hist = $linkRenderer->makeKnownLink(
155            $title,
156            $this->msg( 'hist' )->text(),
157            [],
158            [ 'action' => 'history' ]
159        );
160        $size = $row->page_len;
161        if ( $size !== null ) {
162            $stxt = ( $size == 0 )
163                ? $this->msg( 'historyempty' )->escaped()
164                : $this->msg( 'historysize' )->numParams( $size )->escaped();
165            $stxt = " <small>$stxt</small>";
166        }
167        # Get how long the first unreviewed edit has been waiting...
168        $firstPendingTime = (int)wfTimestamp( TS_UNIX, $row->creation );
169        $hours = ( $this->currentUnixTS - $firstPendingTime ) / 3600;
170        $days = round( $hours / 24 );
171        if ( $days >= 3 ) {
172            $age = ' ' . $this->msg( 'unreviewedpages-days' )->numParams( $days )->escaped();
173        } elseif ( $hours >= 1 ) {
174            $age = ' ' . $this->msg( 'unreviewedpages-hours' )->numParams( round( $hours ) )->escaped();
175        } else {
176            $age = ' ' . $this->msg( 'unreviewedpages-recent' )->escaped(); // hot off the press :)
177        }
178        if ( MediaWikiServices::getInstance()->getPermissionManager()
179            ->userHasRight( $this->getUser(), 'unwatchedpages' )
180        ) {
181            $uw = FRUserActivity::numUsersWatchingPage( $title );
182            $watching = ' ';
183            $watching .= $uw
184                ? $this->msg( 'unreviewedpages-watched' )->numParams( $uw )->escaped()
185                : $this->msg( 'unreviewedpages-unwatched' )->escaped();
186        } else {
187            $uw = -1;
188            $watching = '';
189        }
190        $css = $this->getLineClass( $hours, $uw );
191        $css = $css ? " class='$css'" : "";
192
193        return ( "<li{$css}>{$link} $dirmark {$stxt} ({$hist})" .
194            "{$age}{$watching}</li>" );
195    }
196
197    /**
198     * @param float $hours
199     * @param int $numUsersWatching Number of users or -1 when not allowed to see the number
200     * @return string
201     */
202    private function getLineClass( $hours, $numUsersWatching ) {
203        $days = $hours / 24;
204        if ( $numUsersWatching == 0 ) {
205            return 'fr-unreviewed-unwatched';
206        } elseif ( $days > 20 ) {
207            return 'fr-pending-long2';
208        } elseif ( $days > 7 ) {
209            return 'fr-pending-long';
210        } else {
211            return '';
212        }
213    }
214
215    /**
216     * Run an update to the cached query rows
217     * @return void
218     */
219    public static function updateQueryCache() {
220        $rNamespaces = FlaggedRevs::getReviewNamespaces();
221        if ( !$rNamespaces ) {
222            return;
223        }
224        $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
225
226        $insertRows = [];
227        // Find pages that were never reviewed at all...
228        $res = $dbr->newSelectQueryBuilder()
229            ->select( [ 'page_namespace', 'page_title', 'page_id' ] )
230            ->from( 'page' )
231            ->leftJoin( 'flaggedpages', null, 'fp_page_id = page_id' )
232            ->where( [
233                'page_namespace' => $rNamespaces,
234                'page_is_redirect' => 0, // no redirects
235                'fp_page_id' => null,
236            ] )
237            ->limit( self::CACHE_SIZE )
238            ->caller( __METHOD__ )
239            ->fetchResultSet();
240        foreach ( $res as $row ) {
241            $insertRows[] = [
242                'qc_type'       => 'fr_unreviewedpages',
243                'qc_namespace'  => $row->page_namespace,
244                'qc_title'      => $row->page_title,
245                'qc_value'      => $row->page_id
246            ];
247        }
248
249        $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
250
251        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
252        $dbw->startAtomic( __METHOD__ );
253        # Clear out any old cached data
254        $dbw->newDeleteQueryBuilder()
255            ->deleteFrom( 'querycache' )
256            ->where( [ 'qc_type' => 'fr_unreviewedpages' ] )
257            ->caller( __METHOD__ )
258            ->execute();
259        # Insert new data...
260        if ( $insertRows ) {
261            $dbw->newInsertQueryBuilder()
262                ->insertInto( 'querycache' )
263                ->rows( $insertRows )
264                ->caller( __METHOD__ )
265                ->execute();
266        }
267        # Update the querycache_info record for the page
268        $dbw->newDeleteQueryBuilder()
269            ->deleteFrom( 'querycache_info' )
270            ->where( [ 'qci_type' => 'fr_unreviewedpages' ] )
271            ->caller( __METHOD__ )
272            ->execute();
273        $dbw->newInsertQueryBuilder()
274            ->insertInto( 'querycache_info' )
275            ->row( [ 'qci_type' => 'fr_unreviewedpages', 'qci_timestamp' => $dbw->timestamp() ] )
276            ->caller( __METHOD__ )
277            ->execute();
278        $dbw->endAtomic( __METHOD__ );
279        $lbFactory->commitPrimaryChanges( __METHOD__ );
280
281        $insertRows = [];
282        // Find pages that were never marked as "quality"...
283        $res = $dbr->newSelectQueryBuilder()
284            ->select( [ 'page_namespace', 'page_title', 'page_id' ] )
285            ->from( 'page' )
286            ->leftJoin( 'flaggedpages', null, 'fp_page_id = page_id' )
287            ->where( [
288                'page_namespace' => $rNamespaces,
289                'page_is_redirect' => 0, // no redirects
290                $dbr->expr( 'fp_page_id', '=', null )->or( 'fp_quality', '=', 0 ),
291            ] )
292            ->limit( self::CACHE_SIZE )
293            ->caller( __METHOD__ )
294            ->fetchResultSet();
295        foreach ( $res as $row ) {
296            $insertRows[] = [
297                'qc_type'       => 'fr_unreviewedpages_q',
298                'qc_namespace'  => $row->page_namespace,
299                'qc_title'      => $row->page_title,
300                'qc_value'      => $row->page_id
301            ];
302        }
303
304        $dbw->startAtomic( __METHOD__ );
305        # Clear out any old cached data
306        $dbw->newDeleteQueryBuilder()
307            ->deleteFrom( 'querycache' )
308            ->where( [ 'qc_type' => 'fr_unreviewedpages_q' ] )
309            ->caller( __METHOD__ )
310            ->execute();
311        # Insert new data...
312        if ( $insertRows ) {
313            $dbw->newInsertQueryBuilder()
314                ->insertInto( 'querycache' )
315                ->rows( $insertRows )
316                ->caller( __METHOD__ )
317                ->execute();
318        }
319        # Update the querycache_info record for the page
320        $dbw->newDeleteQueryBuilder()
321            ->deleteFrom( 'querycache_info' )
322            ->where( [ 'qci_type' => 'fr_unreviewedpages_q' ] )
323            ->caller( __METHOD__ )
324            ->execute();
325        $dbw->newInsertQueryBuilder()
326            ->insertInto( 'querycache_info' )
327            ->row( [ 'qci_type' => 'fr_unreviewedpages_q', 'qci_timestamp' => $dbw->timestamp() ] )
328            ->caller( __METHOD__ )
329            ->execute();
330        $dbw->endAtomic( __METHOD__ );
331        $lbFactory->commitPrimaryChanges( __METHOD__ );
332    }
333
334    /**
335     * @return string
336     */
337    protected function getGroupName() {
338        return 'quality';
339    }
340}