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