Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 164
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialCargoQuery
0.00% covered (danger)
0.00%
0 / 164
0.00% covered (danger)
0.00%
0 / 9
1190
0.00% covered (danger)
0.00%
0 / 1
 __construct
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 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 displayInputRow
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 displayTextArea
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 displayOrderByInput
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
42
 displayInputForm
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
42
 getWikitextForQuery
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
132
 displayBottomPane
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Shows the results of a Cargo query.
4 *
5 * @author Yaron Koren
6 * @ingroup Cargo
7 */
8
9use MediaWiki\Html\Html;
10use Wikimedia\Rdbms\DBQueryError;
11
12class SpecialCargoQuery extends SpecialPage {
13
14    /**
15     * Constructor
16     */
17    public function __construct() {
18        parent::__construct( 'CargoQuery' );
19    }
20
21    public function execute( $query ) {
22        // Check permissions.
23        if ( !$this->getUser()->isAllowed( 'runcargoqueries' ) ) {
24            $this->displayRestrictionError();
25        }
26
27        $this->setHeaders();
28        $out = $this->getOutput();
29        $req = $this->getRequest();
30        $out->enableOOUI();
31
32        $out->addModules( 'ext.cargo.main' );
33        $out->addModules( 'ext.cargo.cargoquery' );
34
35        // Using the 'tables' query parameter as an indicator of a user query being present.
36        if ( $req->getCheck( 'tables' ) ) {
37            // Allow operators to control how many Cargo queries any one user can run.
38            if ( $this->getUser()->pingLimiter( 'cargo-query' ) ) {
39                throw new ThrottledError();
40            }
41
42            // Execute the user's query:
43            // - CargoQueryPage gathers its data from the request's query parameters, and validates it. An exception
44            //   will be thrown in case of a violation.
45            // - The execution itself may naturally result in a DB query error.
46            // In both error states, we want to gracefully catch the exception (just like #cargo_query does) and still
47            // render the query editing interface to let the user do adjustments.
48            try {
49                $rep = new CargoQueryPage();
50                $rep->execute( $query );
51            } catch ( MWException | DBQueryError $e ) {
52                $out->addHTML( CargoUtils::formatError( $e->getMessage() ) );
53            }
54        }
55
56        $formHTML = $this->displayInputForm();
57
58        if ( $req->getCheck( 'tables' ) ) {
59            $html = $this->displayBottomPane( $this->msg( 'cargo-viewdata-modifyquery' ), $formHTML );
60            $wikitext = $this->getWikitextForQuery();
61            $html .= $this->displayBottomPane( $this->msg( 'cargo-viewdata-viewwikitext' ), $wikitext );
62        } else {
63            $html = $formHTML;
64        }
65
66        $out->addHTML( $html );
67    }
68
69    protected function getGroupName() {
70        return 'cargo';
71    }
72
73    /**
74     * This method is used for generating the input fields
75     * @param string $labelText
76     * @param string $fieldName
77     * @param int $size
78     * @return string
79     */
80    public function displayInputRow( $labelText, $fieldName, $size ) {
81        $req = $this->getRequest();
82
83        $label = Html::element( 'label', [ 'for' => $fieldName ], $labelText );
84        $row = "\n\t" . Html::rawElement( 'td', [ 'class' => 'mw-label' ], $label );
85        $input = new OOUI\TextInputWidget( [
86            'classes' => [ 'ext-cargo-' . $fieldName ],
87            'value' => $req->getVal( $fieldName )
88        ] );
89        $row .= "\n\t" . Html::rawElement( 'td', [], $input->toString() );
90        return Html::rawElement( 'tr', [ 'class' => 'ext-cargo-tr-' . $fieldName ], $row ) . "\n";
91    }
92
93    public function displayTextArea( $labelText, $fieldName, $size ) {
94        $req = $this->getRequest();
95
96        $label = Html::element( 'label', [ 'for' => $fieldName ], $labelText );
97        $row = "\n\t" . Html::rawElement( 'td', [ 'class' => 'mw-label' ], $label );
98        $input = new OOUI\MultilineTextInputWidget( [
99            'classes' => [ 'ext-cargo-' . $fieldName ],
100            'value' => $req->getVal( $fieldName )
101        ] );
102        $row .= "\n\t" . Html::rawElement( 'td', [], $input->toString() ) . "\n";
103        return Html::rawElement( 'tr', [ 'class' => 'ext-cargo-tr-' . $fieldName ], $row ) . "\n";
104    }
105
106    public function displayOrderByInput( $rowNum, $orderByValue, $orderByDirection ) {
107        $text = "\n" . '<tr class="orderByRow" data-order-by-num=' . $rowNum . '>';
108        if ( $rowNum == 0 ) {
109            $text .= '<td class="mw-label">' .
110                '<label for="order_by">' . $this->msg( 'cargo-viewdata-orderby' )->parse() . '</td>';
111        } else {
112            $text .= '<td></td>';
113        }
114        $options = [];
115        $value = '';
116        array_push( $options, [ 'data' => 'ASC', 'label' => 'ASC' ] );
117        if ( $orderByDirection == 'ASC' ) {
118            $value = 'ASC';
119        }
120        array_push( $options, [ 'data' => 'DESC', 'label' => 'DESC' ] );
121        if ( $orderByDirection == 'DESC' ) {
122            $value = 'DESC';
123        }
124        $directionSelect = new OOUI\DropdownInputWidget( [
125            'options' => $options,
126            'id' => 'order_by_options[' . $rowNum . ']',
127            'value' => $value,
128            'name' => 'order_by_options[' . $rowNum . ']'
129        ] );
130        $orderByInput = new OOUI\TextInputWidget( [
131            'id' => 'order_by[' . $rowNum . ']',
132            'value' => $orderByValue
133        ] );
134        $button = new OOUI\ButtonWidget( [
135            'id' => ( $rowNum == 0 ) ? 'addButton' : 'deleteButton',
136            'icon' => ( $rowNum == 0 ) ? 'add' : 'subtract'
137        ] );
138        $orderByRow = new OOUI\HorizontalLayout( [
139            'items' => [
140                $orderByInput,
141                $directionSelect,
142                $button,
143            ]
144        ] );
145        $text .= '<td>' . $orderByRow;
146
147        return $text;
148    }
149
150    public function displayInputForm() {
151        $req = $this->getRequest();
152        // Add the name of this special page as a hidden input, in
153        // case the wiki doesn't use nice URLs.
154        $hiddenTitleInput = Html::hidden( 'title', $this->getPageTitle()->getFullText() );
155
156        $text = <<<END
157<form id="queryform">
158$hiddenTitleInput
159<table class="cargoQueryTable" id="cargoQueryTable" >
160<tbody>
161END;
162
163        $text .= $this->displayInputRow( $this->msg( 'cargo-viewdata-tables' )->parse(), 'tables', 100 );
164        $text .= $this->displayInputRow( $this->msg( 'cargo-viewdata-fields' )->parse(), 'fields', 100 );
165        $text .= $this->displayTextArea( $this->msg( 'cargo-viewdata-where' )->parse(), 'where', 100 );
166        $text .= $this->displayTextArea( $this->msg( 'cargo-viewdata-joinon' )->parse(), 'join_on', 100 );
167        $text .= $this->displayInputRow( $this->msg( 'cargo-viewdata-groupby' )->parse(), 'group_by', 100 );
168        $text .= $this->displayTextArea( $this->msg( 'cargo-viewdata-having' )->parse(), 'having', 100 );
169        $orderByValues = $req->getArray( 'order_by' );
170        if ( $orderByValues != null ) {
171            $orderByDirections = $req->getArray( 'order_by_options' );
172            $rowNum = 0;
173            foreach ( $orderByValues as $i => $curOrderBy ) {
174                $orderByDir = ( $orderByDirections == null ) ? null : $orderByDirections[$i];
175                $text .= $this->displayOrderByInput( $rowNum++, $curOrderBy, $orderByDir );
176            }
177        } else {
178            $text .= $this->displayOrderByInput( 0, null, null );
179        }
180        $text .= $this->displayInputRow( $this->msg( 'cargo-viewdata-limit' )->parse(), 'limit', 3 );
181        $text .= $this->displayInputRow( $this->msg( 'cargo-viewdata-offset' )->parse(), 'offset', 3 );
182        $formatLabel = '<label for="format">' . $this->msg( 'cargo-viewdata-format' )->parse();
183        $options = [];
184        $formatOptionDefault = $this->msg( 'cargo-viewdata-defaultformat' )->parse();
185        array_push( $options, [ 'data' => '', 'label' => '(' . $formatOptionDefault . ')' ] );
186        $value = '';
187        $formatClasses = CargoQueryDisplayer::getAllFormatClasses();
188        foreach ( $formatClasses as $formatName => $formatClass ) {
189            if ( $formatName == $req->getVal( 'format' ) ) {
190                $value = $formatName;
191            }
192            array_push( $options, [ 'data' => $formatName, 'label' => $formatName ] );
193        }
194        $formatDropdown = new OOUI\DropdownInputWIdget( [
195            'options' => $options,
196            'name' => 'format',
197            'id' => 'format',
198            'value' => $value
199        ] );
200        $text .= <<<END
201<tr class="ext-cargo-tr-format">
202<td class="mw-label">
203$formatLabel
204</td>
205<td>$formatDropdown
206
207END;
208        $submitButton = new OOUI\ButtonInputWidget( [
209            'label' => $this->msg( 'htmlform-submit' )->parse(),
210            'type' => 'submit',
211            'flags' => [ 'primary', 'progressive' ]
212        ] );
213        $text .= <<<END
214
215</select>
216</td>
217</tr>
218</tbody>
219</table>
220<br>
221$submitButton
222</form>
223
224END;
225        return $text;
226    }
227
228    public function getWikitextForQuery() {
229        $req = $this->getRequest();
230
231        $wikitext = "{{#cargo_query:\n";
232        $vals = $req->getValues();
233        $firstParam = true;
234        foreach ( $vals as $key => $val ) {
235            if ( $key == 'title' || $key == 'order_by_options' ) {
236                continue;
237            }
238            $key = str_replace( '_', ' ', $key );
239            if ( $key == 'order by' && is_array( $val ) ) {
240                // Possibly no longer necessary.
241                $orderByVal = '';
242                foreach ( $val as $i => $orderByField ) {
243                    if ( $orderByField == '' ) {
244                        continue;
245                    }
246                    if ( array_key_exists( 'order_by_options', $vals ) ) {
247                        $option = $vals['order_by_options'][$i];
248                    } else {
249                        $option = '';
250                    }
251                    $orderByVal .= $orderByField . ' ' . $option . ', ';
252                }
253                $val = $orderByVal;
254            }
255            $val = trim( $val );
256            $val = trim( $val, ',' );
257            if ( $val == '' ) {
258                continue;
259            }
260            if ( $firstParam ) {
261                $firstParam = false;
262            } else {
263                $wikitext .= '|';
264            }
265            $wikitext .= "$key=$val\n";
266        }
267        $wikitext .= "}}";
268
269        return '<pre>' . htmlspecialchars( $wikitext ) . '</pre>';
270    }
271
272    private function displayBottomPane( $paneName, $paneText ) {
273        $html = <<<END
274<div style="max-width: 70em;">
275<span style="width: 100%;" class="oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-indicatorElement oo-ui-labelElement oo-ui-buttonWidget">
276<a href="#" class="specialCargoQuery-extraPane-toggle oo-ui-buttonElement-button" role="button" tabindex="0" aria-disabled="false" rel="nofollow">
277$paneName
278<span class="oo-ui-indicatorElement-indicator oo-ui-indicator-down"></span>
279</a>
280</span>
281<div class="specialCargoQuery-extraPane">
282$paneText
283</div>
284</div>
285
286END;
287        return $html;
288    }
289
290}