Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
37.89% covered (danger)
37.89%
36 / 95
44.44% covered (danger)
44.44%
4 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialShortPages
38.30% covered (danger)
38.30%
36 / 94
44.44% covered (danger)
44.44%
4 / 9
198.25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isSyndicated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfo
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
 reallyDoQuery
21.62% covered (danger)
21.62%
8 / 37
0.00% covered (danger)
0.00%
0 / 1
139.26
 getOrderFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 preprocessResults
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sortDescending
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatResult
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
20
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Html\Html;
10use MediaWiki\Linker\Linker;
11use MediaWiki\MainConfigNames;
12use MediaWiki\Page\LinkBatchFactory;
13use MediaWiki\Skin\Skin;
14use MediaWiki\SpecialPage\QueryPage;
15use MediaWiki\Title\NamespaceInfo;
16use MediaWiki\Title\Title;
17use stdClass;
18use Wikimedia\Rdbms\IConnectionProvider;
19use Wikimedia\Rdbms\IReadableDatabase;
20use Wikimedia\Rdbms\IResultWrapper;
21
22/**
23 * List of the shortest pages in the database.
24 *
25 * @ingroup SpecialPage
26 */
27class SpecialShortPages extends QueryPage {
28
29    public function __construct(
30        private readonly NamespaceInfo $namespaceInfo,
31        IConnectionProvider $dbProvider,
32        LinkBatchFactory $linkBatchFactory
33    ) {
34        parent::__construct( 'Shortpages' );
35        $this->setDatabaseProvider( $dbProvider );
36        $this->setLinkBatchFactory( $linkBatchFactory );
37    }
38
39    /** @inheritDoc */
40    public function isSyndicated() {
41        return false;
42    }
43
44    /** @inheritDoc */
45    public function getQueryInfo() {
46        $config = $this->getConfig();
47        $tables = [ 'page' ];
48        $conds = [
49            'page_namespace' => array_diff(
50                $this->namespaceInfo->getContentNamespaces(),
51                $config->get( MainConfigNames::ShortPagesNamespaceExclusions )
52            ),
53            'page_is_redirect' => 0
54        ];
55        $joinConds = [];
56        $options = [ 'USE INDEX' => [ 'page' => 'page_redirect_namespace_len' ] ];
57
58        // Allow extensions to modify the query
59        $this->getHookRunner()->onShortPagesQuery( $tables, $conds, $joinConds, $options );
60
61        return [
62            'tables' => $tables,
63            'fields' => [
64                'namespace' => 'page_namespace',
65                'title' => 'page_title',
66                'value' => 'page_len'
67            ],
68            'conds' => $conds,
69            'join_conds' => $joinConds,
70            'options' => $options
71        ];
72    }
73
74    /** @inheritDoc */
75    public function reallyDoQuery( $limit, $offset = false ) {
76        $fname = static::class . '::reallyDoQuery';
77        $dbr = $this->getRecacheDB();
78        $query = $this->getQueryInfo();
79        $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
80        $namespaces = $conds['page_namespace'];
81        unset( $conds['page_namespace'] );
82
83        if ( count( $namespaces ) === 1 || !$dbr->unionSupportsOrderAndLimit() ) {
84            return parent::reallyDoQuery( $limit, $offset );
85        }
86
87        // Optimization: Fix slow query on MySQL in the case of multiple content namespaces,
88        // by rewriting this as a UNION of separate single namespace queries (T168010).
89        $sqb = $dbr->newSelectQueryBuilder()
90            ->select( isset( $query['fields'] ) ? (array)$query['fields'] : [] )
91            ->tables( isset( $query['tables'] ) ? (array)$query['tables'] : [] )
92            ->where( $conds )
93            ->options( isset( $query['options'] ) ? (array)$query['options'] : [] )
94            ->joinConds( isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [] );
95        if ( $limit !== false ) {
96            if ( $offset !== false ) {
97                // We need to increase the limit by the offset rather than
98                // using the offset directly, otherwise it'll skip incorrectly
99                // in the subqueries.
100                $sqb->limit( intval( $limit ) + intval( $offset ) );
101            } else {
102                $sqb->limit( intval( $limit ) );
103            }
104        }
105
106        $order = $this->getOrderFields();
107        if ( $this->sortDescending() ) {
108            foreach ( $order as &$field ) {
109                $field .= ' DESC';
110            }
111        }
112
113        $uqb = $dbr->newUnionQueryBuilder()->all();
114        foreach ( $namespaces as $namespace ) {
115            $nsSqb = clone $sqb;
116            $nsSqb->orderBy( $order );
117            $nsSqb->andWhere( [ 'page_namespace' => $namespace ] );
118            $uqb->add( $nsSqb );
119        }
120
121        if ( $limit !== false ) {
122            $uqb->limit( intval( $limit ) );
123        }
124        if ( $offset !== false ) {
125            $uqb->offset( intval( $offset ) );
126        }
127        $orderBy = 'value';
128        if ( $this->sortDescending() ) {
129            $orderBy .= ' DESC';
130        }
131        $uqb->orderBy( $orderBy );
132        return $uqb->caller( $fname )->fetchResultSet();
133    }
134
135    /** @inheritDoc */
136    protected function getOrderFields() {
137        return [ 'page_len' ];
138    }
139
140    /**
141     * @param IReadableDatabase $db
142     * @param IResultWrapper $res
143     */
144    public function preprocessResults( $db, $res ) {
145        $this->executeLBFromResultWrapper( $res );
146    }
147
148    /** @inheritDoc */
149    protected function sortDescending() {
150        return false;
151    }
152
153    /**
154     * @param Skin $skin
155     * @param stdClass $result Result row
156     * @return string
157     */
158    public function formatResult( $skin, $result ) {
159        $title = Title::makeTitleSafe( $result->namespace, $result->title );
160        if ( !$title ) {
161            return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ],
162                Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) );
163        }
164
165        $linkRenderer = $this->getLinkRenderer();
166        $hlink = $linkRenderer->makeKnownLink(
167            $title,
168            $this->msg( 'hist' )->text(),
169            [],
170            [ 'action' => 'history' ]
171        );
172        $hlinkInParentheses = $this->msg( 'parentheses' )->rawParams( $hlink )->escaped();
173
174        if ( $this->isCached() ) {
175            $plink = $linkRenderer->makeLink( $title );
176            $exists = $title->exists();
177        } else {
178            $plink = $linkRenderer->makeKnownLink( $title );
179            $exists = true;
180        }
181        $contentLanguage = $this->getContentLanguage();
182        $bdiAttrs = [
183            'dir' => $contentLanguage->getDir(),
184            'lang' => $contentLanguage->getHtmlCode(),
185        ];
186        $plink = Html::rawElement( 'bdi', $bdiAttrs, $plink );
187        $size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
188        $result = "{$hlinkInParentheses} {$plink} [{$size}]";
189
190        return $exists ? $result : Html::rawElement( 'del', [], $result );
191    }
192
193    /** @inheritDoc */
194    protected function getGroupName() {
195        return 'maintenance';
196    }
197}
198
199/**
200 * Retain the old class name for backwards compatibility.
201 * @deprecated since 1.41
202 */
203class_alias( SpecialShortPages::class, 'SpecialShortPages' );