Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
UnreviewedPagesPager
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 9
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 setLimit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatRow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
56
 getQueryCacheInfo
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
42
 getIndexField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doBatchLookups
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getStartBody
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEndBody
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Linker\LinksMigration;
4use MediaWiki\MediaWikiServices;
5use MediaWiki\Pager\AlphabeticPager;
6use MediaWiki\Title\TitleValue;
7
8/**
9 * Query to list out unreviewed pages
10 */
11class UnreviewedPagesPager extends AlphabeticPager {
12    /** @var UnreviewedPages */
13    private $mForm;
14
15    /** @var bool */
16    private $live;
17
18    /** @var int */
19    private $namespace;
20
21    /** @var string|null */
22    private $category;
23
24    /** @var bool */
25    private $showredirs;
26
27    /** @var int */
28    private $level;
29
30    // Don't get too expensive
31    private const PAGE_LIMIT = 50;
32
33    private LinksMigration $linksMigration;
34
35    /**
36     * @param UnreviewedPages $form
37     * @param bool $live
38     * @param int|null $namespace
39     * @param bool $redirs
40     * @param string|null $category
41     * @param int $level
42     */
43    public function __construct(
44        $form, $live, $namespace, $redirs = false, $category = null, $level = 0
45    ) {
46        $this->mForm = $form;
47        $this->live = (bool)$live;
48        # Must be a content page...
49        if ( $namespace !== null ) {
50            $namespace = (int)$namespace;
51        }
52        # Must be a single NS for performance reasons
53        if ( $namespace === null || !FlaggedRevs::isReviewNamespace( $namespace ) ) {
54            $namespace = FlaggedRevs::getFirstReviewNamespace();
55        }
56        $this->namespace = $namespace;
57        $this->category = $category ? str_replace( ' ', '_', $category ) : null;
58        $this->level = intval( $level );
59        $this->showredirs = (bool)$redirs;
60        parent::__construct();
61        // Don't get too expensive
62        $this->mLimitsShown = [ 20, 50 ];
63        $this->setLimit( $this->mLimit ); // apply max limit
64
65        $this->linksMigration = MediaWikiServices::getInstance()->getLinksMigration();
66    }
67
68    /**
69     * @inheritDoc
70     */
71    public function setLimit( $limit ) {
72        $this->mLimit = min( $limit, self::PAGE_LIMIT );
73    }
74
75    /**
76     * @inheritDoc
77     */
78    public function formatRow( $row ) {
79        return $this->mForm->formatRow( $row );
80    }
81
82    /**
83     * @inheritDoc
84     */
85    public function getQueryInfo() {
86        if ( !$this->live ) {
87            return $this->getQueryCacheInfo();
88        }
89        $qb = $this->mDb->newSelectQueryBuilder()
90            ->select( [ 'page_namespace', 'page_title', 'page_len', 'page_id',
91                'creation' => 'MIN(rev_timestamp)' ] )
92            ->from( 'page' )
93            ->leftJoin( 'revision', null, 'rev_page=page_id' )
94            ->leftJoin( 'flaggedpages', null, 'fp_page_id=page_id' )
95            // Reviewable pages only
96            ->where( [ 'page_namespace' => $this->namespace ] );
97        $groupBy = [ 'page_namespace', 'page_title', 'page_len', 'page_id' ];
98
99        // Filter by level
100        if ( $this->level == 1 ) {
101            $qb->andWhere(
102                $this->mDb->expr( 'fp_page_id', '=', null )->or( 'fp_quality', '=', 0 )
103            );
104        } else {
105            $qb->andWhere( [ 'fp_page_id' => null ] );
106        }
107        if ( !$this->showredirs ) {
108            $qb->andWhere( [ 'page_is_redirect' => 0 ] );
109        }
110        // Filter by category
111        if ( $this->category != '' ) {
112            $queryInfo = $this->linksMigration->getQueryInfo( 'categorylinks' );
113
114            $groupBy[] = 'cl_sortkey';
115
116            $qb->tables( $queryInfo['tables'] );
117            $qb->field( 'cl_sortkey' );
118            $qb->andWhere(
119                $this->linksMigration->getLinksConditions(
120                    'categorylinks',
121                    new TitleValue( NS_CATEGORY, $this->category )
122                )
123            );
124            $qb->andWhere( 'cl_from = page_id' );
125            $qb->joinConds( $queryInfo['joins'] );
126
127            // Note: single NS always specified
128            if ( $this->namespace === NS_FILE ) {
129                $qb->andWhere( [ 'cl_type' => 'file' ] );
130            } elseif ( $this->namespace === NS_CATEGORY ) {
131                $qb->andWhere( [ 'cl_type' => 'subcat' ] );
132            } else {
133                $qb->andWhere( [ 'cl_type' => 'page' ] );
134            }
135            $this->mIndexField = 'cl_sortkey_id';
136            $useIndex = [ 'categorylinks' => 'cl_sortkey_id' ];
137        } else {
138            $this->mIndexField = 'page_title';
139            $useIndex = [ 'page' => 'page_name_title' ];
140        }
141        $useIndex['revision'] = 'rev_page_timestamp';
142        $qb->useIndex( $useIndex );
143        $qb->groupBy( $groupBy );
144
145        return $qb->getQueryInfo();
146    }
147
148    /**
149     * @return array
150     */
151    private function getQueryCacheInfo() {
152        $conds = [];
153        $fields = [ 'page_namespace', 'page_title', 'page_len', 'page_id',
154            'qc_value', 'creation' => 'MIN(rev_timestamp)' ];
155        # Re-join on flaggedpages to double-check since things
156        # could have changed since the cache date. Also, use
157        # the proper cache for this level.
158        if ( $this->level == 1 ) {
159            $conds['qc_type'] = 'fr_unreviewedpages_q';
160            $conds[] = $this->mDb->expr( 'fp_page_id', '=', null )->or( 'fp_quality', '<', 1 );
161        } else {
162            $conds['qc_type'] = 'fr_unreviewedpages';
163            $conds['fp_page_id'] = null;
164        }
165        # Reviewable pages only
166        $conds['qc_namespace'] = $this->namespace;
167        # No redirects
168        if ( !$this->showredirs ) {
169            $conds['page_is_redirect'] = 0;
170        }
171        $this->mIndexField = 'qc_value'; // page_id
172        $joinConds = [];
173        # Filter by category
174        if ( $this->category != '' ) {
175            $queryInfo = $this->linksMigration->getQueryInfo( 'categorylinks' );
176            $tables = [ 'page', ...$queryInfo['tables'], 'querycache', 'flaggedpages', 'revision' ];
177
178            $joinConds = $queryInfo['joins'];
179
180            $conds = array_merge( $this->linksMigration->getLinksConditions(
181                'categorylinks',
182                new TitleValue( NS_CATEGORY, $this->category )
183            ), $conds );
184
185            $conds[] = 'cl_from = qc_value'; // page_id
186            # Note: single NS always specified
187            if ( $this->namespace === NS_FILE ) {
188                $conds['cl_type'] = 'file';
189            } elseif ( $this->namespace === NS_CATEGORY ) {
190                $conds['cl_type'] = 'subcat';
191            } else {
192                $conds['cl_type'] = 'page';
193            }
194        } else {
195            $tables = [ 'page', 'querycache', 'flaggedpages', 'revision' ];
196        }
197
198        $useIndex = [ 'querycache' => 'qc_type', 'page' => 'PRIMARY', 'revision' => 'rev_page_timestamp' ];
199
200        return [
201            'tables'  => $tables,
202            'fields'  => $fields,
203            'conds'   => $conds,
204            'options' => [ 'USE INDEX' => $useIndex, 'GROUP BY' => 'qc_value' ],
205            'join_conds' => array_merge(
206                [
207                    'querycache'    => [ 'LEFT JOIN', 'qc_value=page_id' ],
208                    'revision'      => [ 'LEFT JOIN', 'rev_page=page_id' ], // Get creation date
209                    'flaggedpages'  => [ 'LEFT JOIN', 'fp_page_id=page_id' ],
210                    'categorylinks' => [ 'LEFT JOIN', [ 'cl_from=page_id' ] ],
211                ],
212                $joinConds
213            )
214        ];
215    }
216
217    /**
218     * @inheritDoc
219     */
220    public function getIndexField() {
221        return $this->mIndexField;
222    }
223
224    /**
225     * @inheritDoc
226     */
227    protected function doBatchLookups() {
228        $lb = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch();
229        foreach ( $this->mResult as $row ) {
230            $lb->add( $row->page_namespace, $row->page_title );
231        }
232        $lb->execute();
233    }
234
235    /**
236     * @return string HTML
237     */
238    protected function getStartBody() {
239        return '<ul>';
240    }
241
242    /**
243     * @return string HTML
244     */
245    protected function getEndBody() {
246        return '</ul>';
247    }
248}