Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 128
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialPagesWithProp
0.00% covered (danger)
0.00%
0 / 127
0.00% covered (danger)
0.00%
0 / 14
756
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isCacheable
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 / 51
0.00% covered (danger)
0.00%
0 / 1
12
 onSubmit
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 prefixSearchSubpages
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isSyndicated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 linkParameters
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 getOrderFields
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 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 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 getExistingPropNames
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 queryExistingProps
0.00% covered (danger)
0.00%
0 / 16
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\HTMLForm\HTMLForm;
11use MediaWiki\Skin\Skin;
12use MediaWiki\SpecialPage\QueryPage;
13use MediaWiki\Title\Title;
14use stdClass;
15use Wikimedia\Rdbms\IConnectionProvider;
16
17/**
18 * Special:PagesWithProp to search the page_props table
19 *
20 * @ingroup SpecialPage
21 * @since 1.21
22 */
23class SpecialPagesWithProp extends QueryPage {
24
25    /**
26     * @var string|null
27     */
28    private $propName = null;
29
30    /**
31     * @var string[]|null
32     */
33    private $existingPropNames = null;
34
35    /**
36     * @var int|null
37     */
38    private $ns;
39
40    /**
41     * @var bool
42     */
43    private $reverse = false;
44
45    /**
46     * @var bool
47     */
48    private $sortByValue = false;
49
50    public function __construct( IConnectionProvider $dbProvider ) {
51        parent::__construct( 'PagesWithProp' );
52        $this->setDatabaseProvider( $dbProvider );
53    }
54
55    /** @inheritDoc */
56    public function isCacheable() {
57        return false;
58    }
59
60    /** @inheritDoc */
61    public function execute( $par ) {
62        $this->setHeaders();
63        $this->outputHeader();
64        $this->getOutput()->addModuleStyles( 'mediawiki.special' );
65
66        $request = $this->getRequest();
67        $propname = $request->getVal( 'propname', $par );
68        $this->ns = $request->getIntOrNull( 'namespace' );
69        $this->reverse = $request->getBool( 'reverse' );
70        $this->sortByValue = $request->getBool( 'sortbyvalue' );
71
72        $propnames = $this->getExistingPropNames();
73
74        $fields = [
75            'propname' => [
76                'type' => 'combobox',
77                'name' => 'propname',
78                'options' => $propnames,
79                'default' => $propname,
80                'label-message' => 'pageswithprop-prop',
81                'required' => true,
82            ],
83            'namespace' => [
84                'type' => 'namespaceselect',
85                'name' => 'namespace',
86                'label-message' => 'namespace',
87                'all' => '',
88                'default' => $this->ns,
89            ],
90            'reverse' => [
91                'type' => 'check',
92                'name' => 'reverse',
93                'default' => $this->reverse,
94                'label-message' => 'pageswithprop-reverse',
95                'required' => false,
96            ],
97            'sortbyvalue' => [
98                'type' => 'check',
99                'name' => 'sortbyvalue',
100                'default' => $this->sortByValue,
101                'label-message' => 'pageswithprop-sortbyvalue',
102                'required' => false,
103            ]
104        ];
105
106        $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() )
107            ->setMethod( 'get' )
108            ->setTitle( $this->getPageTitle() ) // Remove subpage
109            ->setSubmitCallback( $this->onSubmit( ... ) )
110            ->setWrapperLegendMsg( 'pageswithprop-legend' )
111            ->addHeaderHtml( $this->msg( 'pageswithprop-text' )->parseAsBlock() )
112            ->setSubmitTextMsg( 'pageswithprop-submit' )
113            ->prepareForm();
114        $form->displayForm( false );
115        if ( $propname !== '' && $propname !== null ) {
116            $form->trySubmit();
117        }
118    }
119
120    /**
121     * @param array $data
122     * @param HTMLForm $form
123     */
124    private function onSubmit( $data, $form ) {
125        $this->propName = $data['propname'];
126        parent::execute( $data['propname'] );
127    }
128
129    /**
130     * Return an array of subpages beginning with $search that this special page will accept.
131     *
132     * @param string $search Prefix to search for
133     * @param int $limit Maximum number of results to return
134     * @param int $offset Number of pages to skip
135     * @return string[] Matching subpages
136     */
137    public function prefixSearchSubpages( $search, $limit, $offset ) {
138        $subpages = array_keys( $this->queryExistingProps( $limit, $offset ) );
139        // We've already limited and offsetted, set to N and 0 respectively.
140        return self::prefixSearchArray( $search, count( $subpages ), $subpages, 0 );
141    }
142
143    /**
144     * Disable RSS/Atom feeds
145     * @return bool
146     */
147    public function isSyndicated() {
148        return false;
149    }
150
151    /**
152     * @inheritDoc
153     */
154    protected function linkParameters() {
155        $params = [
156            'reverse' => $this->reverse,
157            'sortbyvalue' => $this->sortByValue,
158        ];
159        if ( $this->ns !== null ) {
160            $params['namespace'] = $this->ns;
161        }
162        return $params;
163    }
164
165    /** @inheritDoc */
166    public function getQueryInfo() {
167        $query = [
168            'tables' => [ 'page_props', 'page' ],
169            'fields' => [
170                'page_id' => 'pp_page',
171                'page_namespace',
172                'page_title',
173                'page_len',
174                'page_is_redirect',
175                'page_latest',
176                'pp_value',
177            ],
178            'conds' => [
179                'pp_propname' => $this->propName,
180            ],
181            'join_conds' => [
182                'page' => [ 'JOIN', 'page_id = pp_page' ]
183            ],
184            'options' => []
185        ];
186
187        if ( $this->ns !== null ) {
188            $query['conds']['page_namespace'] = $this->ns;
189        }
190
191        return $query;
192    }
193
194    /** @inheritDoc */
195    protected function getOrderFields() {
196        $sort = [ 'page_id' ];
197        if ( $this->sortByValue ) {
198            array_unshift( $sort, 'pp_sortkey' );
199        }
200        return $sort;
201    }
202
203    /**
204     * @return bool
205     */
206    public function sortDescending() {
207        return !$this->reverse;
208    }
209
210    /**
211     * @param Skin $skin
212     * @param stdClass $result Result row
213     * @return string
214     */
215    public function formatResult( $skin, $result ) {
216        $title = Title::newFromRow( $result );
217        $ret = $this->getLinkRenderer()->makeKnownLink( $title );
218        if ( $result->pp_value !== '' ) {
219            // Do not show very long or binary values on the special page
220            $valueLength = strlen( $result->pp_value );
221            $isBinary = str_contains( $result->pp_value, "\0" );
222            $isTooLong = $valueLength > 1024;
223
224            if ( $isBinary || $isTooLong ) {
225                $message = $this
226                    ->msg( $isBinary ? 'pageswithprop-prophidden-binary' : 'pageswithprop-prophidden-long' )
227                    ->sizeParams( $valueLength );
228
229                $propValue = Html::element( 'span', [ 'class' => 'prop-value-hidden' ], $message->text() );
230            } else {
231                $propValue = Html::element( 'span', [ 'class' => 'prop-value' ], $result->pp_value );
232            }
233
234            $ret .= $this->msg( 'colon-separator' )->escaped() . $propValue;
235        }
236
237        return $ret;
238    }
239
240    public function getExistingPropNames(): array {
241        if ( $this->existingPropNames === null ) {
242            $this->existingPropNames = $this->queryExistingProps();
243        }
244        return $this->existingPropNames;
245    }
246
247    protected function queryExistingProps( ?int $limit = null, int $offset = 0 ): array {
248        $queryBuilder = $this->getDatabaseProvider()
249            ->getReplicaDatabase()
250            ->newSelectQueryBuilder()
251            ->select( 'pp_propname' )
252            ->distinct()
253            ->from( 'page_props' )
254            ->orderBy( 'pp_propname' );
255
256        if ( $limit ) {
257            $queryBuilder->limit( $limit );
258        }
259        if ( $offset ) {
260            $queryBuilder->offset( $offset );
261        }
262        $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
263
264        $propnames = [];
265        foreach ( $res as $row ) {
266            $propnames[$row->pp_propname] = $row->pp_propname;
267        }
268
269        return $propnames;
270    }
271
272    /** @inheritDoc */
273    protected function getGroupName() {
274        return 'pages';
275    }
276}
277
278/**
279 * Retain the old class name for backwards compatibility.
280 * @deprecated since 1.41
281 */
282class_alias( SpecialPagesWithProp::class, 'SpecialPagesWithProp' );