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