wikimedia /
mediawiki
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Efficient paging for SQL queries. |
||
| 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 | * @file |
||
| 21 | * @ingroup Pager |
||
| 22 | */ |
||
| 23 | |||
| 24 | /** |
||
| 25 | * Table-based display with a user-selectable sort order |
||
| 26 | * @ingroup Pager |
||
| 27 | */ |
||
| 28 | abstract class TablePager extends IndexPager { |
||
| 29 | protected $mSort; |
||
| 30 | |||
| 31 | protected $mCurrentRow; |
||
| 32 | |||
| 33 | public function __construct( IContextSource $context = null ) { |
||
| 34 | if ( $context ) { |
||
| 35 | $this->setContext( $context ); |
||
| 36 | } |
||
| 37 | |||
| 38 | $this->mSort = $this->getRequest()->getText( 'sort' ); |
||
| 39 | if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) |
||
| 40 | || !$this->isFieldSortable( $this->mSort ) |
||
| 41 | ) { |
||
| 42 | $this->mSort = $this->getDefaultSort(); |
||
| 43 | } |
||
| 44 | if ( $this->getRequest()->getBool( 'asc' ) ) { |
||
| 45 | $this->mDefaultDirection = IndexPager::DIR_ASCENDING; |
||
| 46 | } elseif ( $this->getRequest()->getBool( 'desc' ) ) { |
||
| 47 | $this->mDefaultDirection = IndexPager::DIR_DESCENDING; |
||
| 48 | } /* Else leave it at whatever the class default is */ |
||
| 49 | |||
| 50 | parent::__construct(); |
||
| 51 | } |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Get the formatted result list. Calls getStartBody(), formatRow() and getEndBody(), concatenates |
||
| 55 | * the results and returns them. |
||
| 56 | * |
||
| 57 | * Also adds the required styles to our OutputPage object (this means that if context wasn't |
||
| 58 | * passed to constructor or otherwise set up, you will get a pager with missing styles). |
||
| 59 | * |
||
| 60 | * This method has been made 'final' in 1.24. There's no reason to override it, and if there exist |
||
| 61 | * any subclasses that do, the style loading hack is probably broken in them. Let's fail fast |
||
| 62 | * rather than mysteriously render things wrong. |
||
| 63 | * |
||
| 64 | * @deprecated since 1.24, use getBodyOutput() or getFullOutput() instead |
||
| 65 | * @return string |
||
| 66 | */ |
||
| 67 | final public function getBody() { |
||
| 68 | $this->getOutput()->addModuleStyles( $this->getModuleStyles() ); |
||
| 69 | return parent::getBody(); |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Get the formatted result list. |
||
| 74 | * |
||
| 75 | * Calls getBody() and getModuleStyles() and builds a ParserOutput object. (This is a bit hacky |
||
| 76 | * but works well.) |
||
| 77 | * |
||
| 78 | * @since 1.24 |
||
| 79 | * @return ParserOutput |
||
| 80 | */ |
||
| 81 | public function getBodyOutput() { |
||
| 82 | $body = parent::getBody(); |
||
|
0 ignored issues
–
show
|
|||
| 83 | |||
| 84 | $pout = new ParserOutput; |
||
| 85 | $pout->setText( $body ); |
||
| 86 | $pout->addModuleStyles( $this->getModuleStyles() ); |
||
| 87 | return $pout; |
||
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * Get the formatted result list, with navigation bars. |
||
| 92 | * |
||
| 93 | * Calls getBody(), getNavigationBar() and getModuleStyles() and |
||
| 94 | * builds a ParserOutput object. (This is a bit hacky but works well.) |
||
| 95 | * |
||
| 96 | * @since 1.24 |
||
| 97 | * @return ParserOutput |
||
| 98 | */ |
||
| 99 | public function getFullOutput() { |
||
| 100 | $navigation = $this->getNavigationBar(); |
||
| 101 | $body = parent::getBody(); |
||
|
0 ignored issues
–
show
It seems like you call parent on a different method (
getBody() instead of getFullOutput()). Are you sure this is correct? If so, you might want to change this to $this->getBody().
This check looks for a call to a parent method whose name is different than the method from which it is called. Consider the following code: class Daddy
{
protected function getFirstName()
{
return "Eidur";
}
protected function getSurName()
{
return "Gudjohnsen";
}
}
class Son
{
public function getFirstName()
{
return parent::getSurname();
}
}
The Loading history...
|
|||
| 102 | |||
| 103 | $pout = new ParserOutput; |
||
| 104 | $pout->setText( $navigation . $body . $navigation ); |
||
| 105 | $pout->addModuleStyles( $this->getModuleStyles() ); |
||
| 106 | return $pout; |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * @protected |
||
| 111 | * @return string |
||
| 112 | */ |
||
| 113 | function getStartBody() { |
||
| 114 | $sortClass = $this->getSortHeaderClass(); |
||
| 115 | |||
| 116 | $s = ''; |
||
| 117 | $fields = $this->getFieldNames(); |
||
| 118 | |||
| 119 | // Make table header |
||
| 120 | foreach ( $fields as $field => $name ) { |
||
| 121 | if ( strval( $name ) == '' ) { |
||
| 122 | $s .= Html::rawElement( 'th', [], ' ' ) . "\n"; |
||
| 123 | } elseif ( $this->isFieldSortable( $field ) ) { |
||
| 124 | $query = [ 'sort' => $field, 'limit' => $this->mLimit ]; |
||
| 125 | $linkType = null; |
||
| 126 | $class = null; |
||
| 127 | |||
| 128 | if ( $this->mSort == $field ) { |
||
| 129 | // The table is sorted by this field already, make a link to sort in the other direction |
||
| 130 | // We don't actually know in which direction other fields will be sorted by default… |
||
| 131 | if ( $this->mDefaultDirection == IndexPager::DIR_DESCENDING ) { |
||
| 132 | $linkType = 'asc'; |
||
| 133 | $class = "$sortClass TablePager_sort-descending"; |
||
| 134 | $query['asc'] = '1'; |
||
| 135 | $query['desc'] = ''; |
||
| 136 | } else { |
||
| 137 | $linkType = 'desc'; |
||
| 138 | $class = "$sortClass TablePager_sort-ascending"; |
||
| 139 | $query['asc'] = ''; |
||
| 140 | $query['desc'] = '1'; |
||
| 141 | } |
||
| 142 | } |
||
| 143 | |||
| 144 | $link = $this->makeLink( htmlspecialchars( $name ), $query, $linkType ); |
||
| 145 | $s .= Html::rawElement( 'th', [ 'class' => $class ], $link ) . "\n"; |
||
| 146 | } else { |
||
| 147 | $s .= Html::element( 'th', [], $name ) . "\n"; |
||
| 148 | } |
||
| 149 | } |
||
| 150 | |||
| 151 | $tableClass = $this->getTableClass(); |
||
| 152 | $ret = Html::openElement( 'table', [ |
||
| 153 | 'class' => "mw-datatable $tableClass" ] |
||
| 154 | ); |
||
| 155 | $ret .= Html::rawElement( 'thead', [], Html::rawElement( 'tr', [], "\n" . $s . "\n" ) ); |
||
| 156 | $ret .= Html::openElement( 'tbody' ) . "\n"; |
||
| 157 | |||
| 158 | return $ret; |
||
| 159 | } |
||
| 160 | |||
| 161 | /** |
||
| 162 | * @protected |
||
| 163 | * @return string |
||
| 164 | */ |
||
| 165 | function getEndBody() { |
||
| 166 | return "</tbody></table>\n"; |
||
| 167 | } |
||
| 168 | |||
| 169 | /** |
||
| 170 | * @protected |
||
| 171 | * @return string |
||
| 172 | */ |
||
| 173 | function getEmptyBody() { |
||
| 174 | $colspan = count( $this->getFieldNames() ); |
||
| 175 | $msgEmpty = $this->msg( 'table_pager_empty' )->text(); |
||
| 176 | return Html::rawElement( 'tr', [], |
||
| 177 | Html::element( 'td', [ 'colspan' => $colspan ], $msgEmpty ) ); |
||
| 178 | } |
||
| 179 | |||
| 180 | /** |
||
| 181 | * @protected |
||
| 182 | * @param stdClass $row |
||
| 183 | * @return string HTML |
||
| 184 | */ |
||
| 185 | function formatRow( $row ) { |
||
| 186 | $this->mCurrentRow = $row; // In case formatValue etc need to know |
||
| 187 | $s = Html::openElement( 'tr', $this->getRowAttrs( $row ) ) . "\n"; |
||
| 188 | $fieldNames = $this->getFieldNames(); |
||
| 189 | |||
| 190 | foreach ( $fieldNames as $field => $name ) { |
||
| 191 | $value = isset( $row->$field ) ? $row->$field : null; |
||
| 192 | $formatted = strval( $this->formatValue( $field, $value ) ); |
||
| 193 | |||
| 194 | if ( $formatted == '' ) { |
||
| 195 | $formatted = ' '; |
||
| 196 | } |
||
| 197 | |||
| 198 | $s .= Html::rawElement( 'td', $this->getCellAttrs( $field, $value ), $formatted ) . "\n"; |
||
| 199 | } |
||
| 200 | |||
| 201 | $s .= Html::closeElement( 'tr' ) . "\n"; |
||
| 202 | |||
| 203 | return $s; |
||
| 204 | } |
||
| 205 | |||
| 206 | /** |
||
| 207 | * Get a class name to be applied to the given row. |
||
| 208 | * |
||
| 209 | * @protected |
||
| 210 | * |
||
| 211 | * @param object $row The database result row |
||
| 212 | * @return string |
||
| 213 | */ |
||
| 214 | function getRowClass( $row ) { |
||
| 215 | return ''; |
||
| 216 | } |
||
| 217 | |||
| 218 | /** |
||
| 219 | * Get attributes to be applied to the given row. |
||
| 220 | * |
||
| 221 | * @protected |
||
| 222 | * |
||
| 223 | * @param object $row The database result row |
||
| 224 | * @return array Array of attribute => value |
||
| 225 | */ |
||
| 226 | function getRowAttrs( $row ) { |
||
| 227 | $class = $this->getRowClass( $row ); |
||
| 228 | if ( $class === '' ) { |
||
| 229 | // Return an empty array to avoid clutter in HTML like class="" |
||
| 230 | return []; |
||
| 231 | } else { |
||
| 232 | return [ 'class' => $this->getRowClass( $row ) ]; |
||
| 233 | } |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * @return stdClass |
||
| 238 | */ |
||
| 239 | protected function getCurrentRow() { |
||
| 240 | return $this->mCurrentRow; |
||
| 241 | } |
||
| 242 | |||
| 243 | /** |
||
| 244 | * Get any extra attributes to be applied to the given cell. Don't |
||
| 245 | * take this as an excuse to hardcode styles; use classes and |
||
| 246 | * CSS instead. Row context is available in $this->mCurrentRow |
||
| 247 | * |
||
| 248 | * @protected |
||
| 249 | * |
||
| 250 | * @param string $field The column |
||
| 251 | * @param string $value The cell contents |
||
| 252 | * @return array Array of attr => value |
||
| 253 | */ |
||
| 254 | function getCellAttrs( $field, $value ) { |
||
| 255 | return [ 'class' => 'TablePager_col_' . $field ]; |
||
| 256 | } |
||
| 257 | |||
| 258 | /** |
||
| 259 | * @protected |
||
| 260 | * @return string |
||
| 261 | */ |
||
| 262 | function getIndexField() { |
||
| 263 | return $this->mSort; |
||
| 264 | } |
||
| 265 | |||
| 266 | /** |
||
| 267 | * @return string |
||
| 268 | */ |
||
| 269 | protected function getTableClass() { |
||
| 270 | return 'TablePager'; |
||
| 271 | } |
||
| 272 | |||
| 273 | /** |
||
| 274 | * @return string |
||
| 275 | */ |
||
| 276 | protected function getNavClass() { |
||
| 277 | return 'TablePager_nav'; |
||
| 278 | } |
||
| 279 | |||
| 280 | /** |
||
| 281 | * @return string |
||
| 282 | */ |
||
| 283 | protected function getSortHeaderClass() { |
||
| 284 | return 'TablePager_sort'; |
||
| 285 | } |
||
| 286 | |||
| 287 | /** |
||
| 288 | * A navigation bar with images |
||
| 289 | * @return string HTML |
||
| 290 | */ |
||
| 291 | public function getNavigationBar() { |
||
| 292 | if ( !$this->isNavigationBarShown() ) { |
||
| 293 | return ''; |
||
| 294 | } |
||
| 295 | |||
| 296 | $labels = [ |
||
| 297 | 'first' => 'table_pager_first', |
||
| 298 | 'prev' => 'table_pager_prev', |
||
| 299 | 'next' => 'table_pager_next', |
||
| 300 | 'last' => 'table_pager_last', |
||
| 301 | ]; |
||
| 302 | |||
| 303 | $linkTexts = []; |
||
| 304 | $disabledTexts = []; |
||
| 305 | foreach ( $labels as $type => $label ) { |
||
| 306 | $msgLabel = $this->msg( $label )->escaped(); |
||
| 307 | $linkTexts[$type] = "<div class='TablePager_nav-enabled'>$msgLabel</div>"; |
||
| 308 | $disabledTexts[$type] = "<div class='TablePager_nav-disabled'>$msgLabel</div>"; |
||
| 309 | } |
||
| 310 | $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); |
||
| 311 | |||
| 312 | $s = Html::openElement( 'table', [ 'class' => $this->getNavClass() ] ); |
||
| 313 | $s .= Html::openElement( 'tr' ) . "\n"; |
||
| 314 | $width = 100 / count( $links ) . '%'; |
||
| 315 | foreach ( $labels as $type => $label ) { |
||
| 316 | // We want every cell to have the same width. We could use table-layout: fixed; in CSS, |
||
| 317 | // but it only works if we specify the width of a cell or the table and we don't want to. |
||
| 318 | // There is no better way. <https://www.w3.org/TR/CSS2/tables.html#fixed-table-layout> |
||
| 319 | $s .= Html::rawElement( 'td', |
||
| 320 | [ 'style' => "width: $width;", 'class' => "TablePager_nav-$type" ], |
||
| 321 | $links[$type] ) . "\n"; |
||
| 322 | } |
||
| 323 | $s .= Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"; |
||
| 324 | return $s; |
||
| 325 | } |
||
| 326 | |||
| 327 | /** |
||
| 328 | * ResourceLoader modules that must be loaded to provide correct styling for this pager |
||
| 329 | * @since 1.24 |
||
| 330 | * @return string[] |
||
| 331 | */ |
||
| 332 | public function getModuleStyles() { |
||
| 333 | return [ 'mediawiki.pager.tablePager' ]; |
||
| 334 | } |
||
| 335 | |||
| 336 | /** |
||
| 337 | * Get a "<select>" element which has options for each of the allowed limits |
||
| 338 | * |
||
| 339 | * @param string $attribs Extra attributes to set |
||
| 340 | * @return string HTML fragment |
||
| 341 | */ |
||
| 342 | public function getLimitSelect( $attribs = [] ) { |
||
| 343 | $select = new XmlSelect( 'limit', false, $this->mLimit ); |
||
| 344 | $select->addOptions( $this->getLimitSelectList() ); |
||
| 345 | foreach ( $attribs as $name => $value ) { |
||
|
0 ignored issues
–
show
The expression
$attribs of type string|array is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
| 346 | $select->setAttribute( $name, $value ); |
||
| 347 | } |
||
| 348 | return $select->getHTML(); |
||
| 349 | } |
||
| 350 | |||
| 351 | /** |
||
| 352 | * Get a list of items to show in a "<select>" element of limits. |
||
| 353 | * This can be passed directly to XmlSelect::addOptions(). |
||
| 354 | * |
||
| 355 | * @since 1.22 |
||
| 356 | * @return array |
||
| 357 | */ |
||
| 358 | public function getLimitSelectList() { |
||
| 359 | # Add the current limit from the query string |
||
| 360 | # to avoid that the limit is lost after clicking Go next time |
||
| 361 | if ( !in_array( $this->mLimit, $this->mLimitsShown ) ) { |
||
| 362 | $this->mLimitsShown[] = $this->mLimit; |
||
| 363 | sort( $this->mLimitsShown ); |
||
| 364 | } |
||
| 365 | $ret = []; |
||
| 366 | foreach ( $this->mLimitsShown as $key => $value ) { |
||
| 367 | # The pair is either $index => $limit, in which case the $value |
||
| 368 | # will be numeric, or $limit => $text, in which case the $value |
||
| 369 | # will be a string. |
||
| 370 | if ( is_int( $value ) ) { |
||
| 371 | $limit = $value; |
||
| 372 | $text = $this->getLanguage()->formatNum( $limit ); |
||
| 373 | } else { |
||
| 374 | $limit = $key; |
||
| 375 | $text = $value; |
||
| 376 | } |
||
| 377 | $ret[$text] = $limit; |
||
| 378 | } |
||
| 379 | return $ret; |
||
| 380 | } |
||
| 381 | |||
| 382 | /** |
||
| 383 | * Get \<input type="hidden"\> elements for use in a method="get" form. |
||
| 384 | * Resubmits all defined elements of the query string, except for a |
||
| 385 | * blacklist, passed in the $blacklist parameter. |
||
| 386 | * |
||
| 387 | * @param array $blacklist Parameters from the request query which should not be resubmitted |
||
| 388 | * @return string HTML fragment |
||
| 389 | */ |
||
| 390 | function getHiddenFields( $blacklist = [] ) { |
||
| 391 | $blacklist = (array)$blacklist; |
||
| 392 | $query = $this->getRequest()->getQueryValues(); |
||
| 393 | foreach ( $blacklist as $name ) { |
||
| 394 | unset( $query[$name] ); |
||
| 395 | } |
||
| 396 | $s = ''; |
||
| 397 | foreach ( $query as $name => $value ) { |
||
| 398 | $s .= Html::hidden( $name, $value ) . "\n"; |
||
| 399 | } |
||
| 400 | return $s; |
||
| 401 | } |
||
| 402 | |||
| 403 | /** |
||
| 404 | * Get a form containing a limit selection dropdown |
||
| 405 | * |
||
| 406 | * @return string HTML fragment |
||
| 407 | */ |
||
| 408 | function getLimitForm() { |
||
| 409 | return Html::rawElement( |
||
| 410 | 'form', |
||
| 411 | [ |
||
| 412 | 'method' => 'get', |
||
| 413 | 'action' => wfScript(), |
||
| 414 | ], |
||
| 415 | "\n" . $this->getLimitDropdown() |
||
| 416 | ) . "\n"; |
||
| 417 | } |
||
| 418 | |||
| 419 | /** |
||
| 420 | * Gets a limit selection dropdown |
||
| 421 | * |
||
| 422 | * @return string |
||
| 423 | */ |
||
| 424 | function getLimitDropdown() { |
||
| 425 | # Make the select with some explanatory text |
||
| 426 | $msgSubmit = $this->msg( 'table_pager_limit_submit' )->escaped(); |
||
| 427 | |||
| 428 | return $this->msg( 'table_pager_limit' ) |
||
| 429 | ->rawParams( $this->getLimitSelect() )->escaped() . |
||
| 430 | "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" . |
||
| 431 | $this->getHiddenFields( [ 'limit' ] ); |
||
| 432 | } |
||
| 433 | |||
| 434 | /** |
||
| 435 | * Return true if the named field should be sortable by the UI, false |
||
| 436 | * otherwise |
||
| 437 | * |
||
| 438 | * @param string $field |
||
| 439 | */ |
||
| 440 | abstract function isFieldSortable( $field ); |
||
| 441 | |||
| 442 | /** |
||
| 443 | * Format a table cell. The return value should be HTML, but use an empty |
||
| 444 | * string not   for empty cells. Do not include the <td> and </td>. |
||
| 445 | * |
||
| 446 | * The current result row is available as $this->mCurrentRow, in case you |
||
| 447 | * need more context. |
||
| 448 | * |
||
| 449 | * @protected |
||
| 450 | * |
||
| 451 | * @param string $name The database field name |
||
| 452 | * @param string $value The value retrieved from the database |
||
| 453 | */ |
||
| 454 | abstract function formatValue( $name, $value ); |
||
| 455 | |||
| 456 | /** |
||
| 457 | * The database field name used as a default sort order. |
||
| 458 | * |
||
| 459 | * @protected |
||
| 460 | * |
||
| 461 | * @return string |
||
| 462 | */ |
||
| 463 | abstract function getDefaultSort(); |
||
| 464 | |||
| 465 | /** |
||
| 466 | * An array mapping database field names to a textual description of the |
||
| 467 | * field name, for use in the table header. The description should be plain |
||
| 468 | * text, it will be HTML-escaped later. |
||
| 469 | * |
||
| 470 | * @return array |
||
| 471 | */ |
||
| 472 | abstract function getFieldNames(); |
||
| 473 | } |
||
| 474 |
This check looks for a call to a parent method whose name is different than the method from which it is called.
Consider the following code:
The
getFirstName()method in theSoncalls the wrong method in the parent class.