MediaWiki master
TablePager.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Pager;
22
27use OOUI\ButtonGroupWidget;
28use OOUI\ButtonWidget;
29use stdClass;
30use XmlSelect;
31
38abstract class TablePager extends IndexPager {
40 protected $mSort;
41
43 protected $mCurrentRow;
44
51 public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
52 if ( $context ) {
53 $this->setContext( $context );
54 }
55
56 $this->mSort = $this->getRequest()->getText( 'sort' );
57 if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
58 || !$this->isFieldSortable( $this->mSort )
59 ) {
60 $this->mSort = $this->getDefaultSort();
61 }
62 if ( $this->getRequest()->getBool( 'asc' ) ) {
63 $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
64 } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
65 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
66 } /* Else leave it at whatever the class default is */
67
68 // Parent constructor needs mSort set, so we call it last
69 parent::__construct( null, $linkRenderer );
70 }
71
87 final public function getBody() {
88 wfDeprecated( __METHOD__, '1.24' );
89 return parent::getBody();
90 }
91
101 public function getBodyOutput() {
102 $body = parent::getBody();
103
104 $pout = new ParserOutput;
105 $pout->setRawText( $body );
106 return $pout;
107 }
108
118 public function getFullOutput() {
119 $navigation = $this->getNavigationBar();
120 $body = parent::getBody();
121
122 $pout = new ParserOutput;
123 $pout->setRawText( $navigation . $body . $navigation );
124 $pout->addModuleStyles( $this->getModuleStyles() );
125 return $pout;
126 }
127
132 protected function getStartBody() {
133 $sortClass = $this->getSortHeaderClass();
134
135 $s = '';
136 $fields = $this->getFieldNames();
137
138 // Make table header
139 foreach ( $fields as $field => $name ) {
140 if ( strval( $name ) == '' ) {
141 $s .= Html::rawElement( 'th', [], "\u{00A0}" ) . "\n";
142 } elseif ( $this->isFieldSortable( $field ) ) {
143 $query = [ 'sort' => $field, 'limit' => $this->mLimit ];
144 $linkType = null;
145 $class = null;
146
147 if ( $this->mSort == $field ) {
148 // The table is sorted by this field already, make a link to sort in the other direction
149 // We don't actually know in which direction other fields will be sorted by default…
150 if ( $this->mDefaultDirection == IndexPager::DIR_DESCENDING ) {
151 $linkType = 'asc';
152 $class = "$sortClass mw-datatable-is-sorted mw-datatable-is-descending";
153 $query['asc'] = '1';
154 $query['desc'] = '';
155 } else {
156 $linkType = 'desc';
157 $class = "$sortClass mw-datatable-is-sorted mw-datatable-is-ascending";
158 $query['asc'] = '';
159 $query['desc'] = '1';
160 }
161 }
162
163 $link = $this->makeLink( htmlspecialchars( $name ), $query, $linkType );
164 $s .= Html::rawElement( 'th', [ 'class' => $class ], $link ) . "\n";
165 } else {
166 $s .= Html::element( 'th', [], $name ) . "\n";
167 }
168 }
169
170 $ret = Html::openElement( 'table', [
171 'class' => $this->getTableClass() ]
172 );
173 $ret .= Html::rawElement( 'thead', [], Html::rawElement( 'tr', [], "\n" . $s . "\n" ) );
174 $ret .= Html::openElement( 'tbody' ) . "\n";
175
176 return $ret;
177 }
178
183 protected function getEndBody() {
184 return "</tbody></table>\n";
185 }
186
190 protected function getEmptyBody() {
191 $colspan = count( $this->getFieldNames() );
192 $msgEmpty = $this->msg( 'table_pager_empty' )->text();
193 return Html::rawElement( 'tr', [],
194 Html::element( 'td', [ 'colspan' => $colspan ], $msgEmpty ) );
195 }
196
202 public function formatRow( $row ) {
203 $this->mCurrentRow = $row; // In case formatValue etc need to know
204 $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n";
205 $fieldNames = $this->getFieldNames();
206
207 foreach ( $fieldNames as $field => $name ) {
208 $value = $row->$field ?? null;
209 $formatted = strval( $this->formatValue( $field, $value ) );
210
211 if ( $formatted == '' ) {
212 $formatted = "\u{00A0}";
213 }
214
215 $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n";
216 }
217
218 $s .= Html::closeElement( 'tr' ) . "\n";
219
220 return $s;
221 }
222
231 protected function getRowClass( $row ) {
232 return '';
233 }
234
243 protected function getRowAttrs( $row ) {
244 return [ 'class' => $this->getRowClass( $row ) ];
245 }
246
250 protected function getCurrentRow() {
251 return $this->mCurrentRow;
252 }
253
265 protected function getCellAttrs( $field, $value ) {
266 return [ 'class' => 'TablePager_col_' . $field ];
267 }
268
273 public function getIndexField() {
274 return $this->mSort;
275 }
276
283 protected function getTableClass() {
284 return 'mw-datatable';
285 }
286
291 protected function getNavClass() {
292 return 'TablePager_nav';
293 }
294
299 protected function getSortHeaderClass() {
300 return 'TablePager_sort';
301 }
302
309 public function getNavigationBar() {
310 if ( !$this->isNavigationBarShown() ) {
311 return '';
312 }
313
314 $this->getOutput()->enableOOUI();
315
316 $types = [ 'first', 'prev', 'next', 'last' ];
317
318 $queries = $this->getPagingQueries();
319
320 $buttons = [];
321
322 $title = $this->getTitle();
323
324 foreach ( $types as $type ) {
325 $buttons[] = new ButtonWidget( [
326 // Messages used here:
327 // * table_pager_first
328 // * table_pager_prev
329 // * table_pager_next
330 // * table_pager_last
331 'classes' => [ 'TablePager-button-' . $type ],
332 'flags' => [ 'progressive' ],
333 'framed' => false,
334 'label' => $this->msg( 'table_pager_' . $type )->text(),
335 'href' => $queries[ $type ] ?
336 $title->getLinkURL( $queries[ $type ] + $this->getDefaultQuery() ) :
337 null,
338 'icon' => $type === 'prev' ? 'previous' : $type,
339 'disabled' => $queries[ $type ] === false
340 ] );
341 }
342 return new ButtonGroupWidget( [
343 'classes' => [ $this->getNavClass() ],
344 'items' => $buttons,
345 ] );
346 }
347
351 public function getModuleStyles() {
352 return array_merge(
353 parent::getModuleStyles(), [ 'oojs-ui.styles.icons-movement' ]
354 );
355 }
356
363 public function getLimitSelect( $attribs = [] ) {
364 $select = new XmlSelect( 'limit', false, $this->mLimit );
365 $select->addOptions( $this->getLimitSelectList() );
366 foreach ( $attribs as $name => $value ) {
367 $select->setAttribute( $name, $value );
368 }
369 return $select->getHTML();
370 }
371
379 public function getLimitSelectList() {
380 # Add the current limit from the query string
381 # to avoid that the limit is lost after clicking Go next time
382 if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) {
383 $this->mLimitsShown[] = $this->mLimit;
384 sort( $this->mLimitsShown );
385 }
386 $ret = [];
387 foreach ( $this->mLimitsShown as $key => $value ) {
388 # The pair is either $index => $limit, in which case the $value
389 # will be numeric, or $limit => $text, in which case the $value
390 # will be a string.
391 if ( is_int( $value ) ) {
392 $limit = $value;
393 $text = $this->getLanguage()->formatNum( $limit );
394 } else {
395 $limit = $key;
396 $text = $value;
397 }
398 $ret[$text] = $limit;
399 }
400 return $ret;
401 }
402
412 public function getHiddenFields( $noResubmit = [] ) {
413 $noResubmit = (array)$noResubmit;
414 $query = $this->getRequest()->getQueryValues();
415 foreach ( $noResubmit as $name ) {
416 unset( $query[$name] );
417 }
418 $s = '';
419 foreach ( $query as $name => $value ) {
420 if ( is_array( $value ) ) {
421 // Per WebRequest::getVal: Array values are discarded for security reasons.
422 continue;
423 }
424 $s .= Html::hidden( $name, $value ) . "\n";
425 }
426 return $s;
427 }
428
434 public function getLimitForm() {
435 return Html::rawElement(
436 'form',
437 [
438 'method' => 'get',
439 'action' => wfScript(),
440 ],
441 "\n" . $this->getLimitDropdown()
442 ) . "\n";
443 }
444
450 private function getLimitDropdown() {
451 # Make the select with some explanatory text
452 $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped();
453
454 return $this->msg( 'table_pager_limit' )
455 ->rawParams( $this->getLimitSelect() )->escaped() .
456 "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
457 $this->getHiddenFields( [ 'limit' ] );
458 }
459
467 abstract protected function isFieldSortable( $field );
468
480 abstract public function formatValue( $name, $value );
481
491 abstract public function getDefaultSort();
492
500 abstract protected function getFieldNames();
501}
502
504class_alias( TablePager::class, 'TablePager' );
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
setContext(IContextSource $context)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Class that generates HTML for internal links.
Efficient paging for SQL queries that use a (roughly unique) index.
int $mLimit
The maximum number of entries to show.
const DIR_ASCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
isNavigationBarShown()
Returns whether to show the "navigation bar".
getPagingQueries()
Get a URL query array for the prev, next, first and last links.
makeLink( $text, array $query=null, $type=null)
Make a self-link.
Table-based display with a user-selectable sort order.
getModuleStyles()
ResourceLoader modules that must be loaded to provide correct styling for this pager....
getRowClass( $row)
Get a class name to be applied to the given row.
formatValue( $name, $value)
Format a table cell.
__construct(IContextSource $context=null, LinkRenderer $linkRenderer=null)
isFieldSortable( $field)
Return true if the named field should be sortable by the UI, false otherwise.
getTableClass()
TablePager relies on mw-datatable for styling, see T214208.
getNavigationBar()
A navigation bar with images.
getHiddenFields( $noResubmit=[])
Get <input type="hidden"> elements for use in a method="get" form.
getLimitSelectList()
Get a list of items to show in a "<select>" element of limits.
getFieldNames()
An array mapping database field names to a textual description of the field name, for use in the tabl...
getLimitForm()
Get a form containing a limit selection dropdown.
getBody()
Get the formatted result list.
getLimitSelect( $attribs=[])
Get a "<select>" element which has options for each of the allowed limits.
getRowAttrs( $row)
Get attributes to be applied to the given row.
getBodyOutput()
Get the formatted result list.
getDefaultSort()
The database field name used as a default sort order.
getFullOutput()
Get the formatted result list, with navigation bars.
getCellAttrs( $field, $value)
Get any extra attributes to be applied to the given cell.
getIndexField()
Returns the name of the index field.If the pager supports multiple orders, it may return an array of ...
ParserOutput is a rendering of a Content object or a message.
setRawText(?string $text)
Set the raw text of the ParserOutput.
Class for generating HTML <select> or <datalist> elements.
Definition XmlSelect.php:28
Interface for objects which can provide a MediaWiki context on request.
element(SerializerNode $parent, SerializerNode $node, $contents)