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 | * Generates a list of changes using an Enhanced system (uses javascript). |
||
| 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 | */ |
||
| 22 | |||
| 23 | class EnhancedChangesList extends ChangesList { |
||
| 24 | |||
| 25 | /** |
||
| 26 | * @var RCCacheEntryFactory |
||
| 27 | */ |
||
| 28 | protected $cacheEntryFactory; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * @var array Array of array of RCCacheEntry |
||
| 32 | */ |
||
| 33 | protected $rc_cache; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * @param IContextSource|Skin $obj |
||
| 37 | * @throws MWException |
||
| 38 | */ |
||
| 39 | public function __construct( $obj ) { |
||
| 40 | if ( $obj instanceof Skin ) { |
||
| 41 | // @todo: deprecate constructing with Skin |
||
| 42 | $context = $obj->getContext(); |
||
| 43 | } else { |
||
| 44 | if ( !$obj instanceof IContextSource ) { |
||
| 45 | throw new MWException( 'EnhancedChangesList must be constructed with a ' |
||
| 46 | . 'context source or skin.' ); |
||
| 47 | } |
||
| 48 | |||
| 49 | $context = $obj; |
||
| 50 | } |
||
| 51 | |||
| 52 | parent::__construct( $context ); |
||
| 53 | |||
| 54 | // message is set by the parent ChangesList class |
||
| 55 | $this->cacheEntryFactory = new RCCacheEntryFactory( |
||
| 56 | $context, |
||
| 57 | $this->message, |
||
| 58 | $this->linkRenderer |
||
| 59 | ); |
||
| 60 | } |
||
| 61 | |||
| 62 | /** |
||
| 63 | * Add the JavaScript file for enhanced changeslist |
||
| 64 | * @return string |
||
| 65 | */ |
||
| 66 | public function beginRecentChangesList() { |
||
| 67 | $this->rc_cache = []; |
||
| 68 | $this->rcMoveIndex = 0; |
||
| 69 | $this->rcCacheIndex = 0; |
||
| 70 | $this->lastdate = ''; |
||
| 71 | $this->rclistOpen = false; |
||
| 72 | $this->getOutput()->addModuleStyles( [ |
||
| 73 | 'mediawiki.special.changeslist', |
||
| 74 | 'mediawiki.special.changeslist.enhanced', |
||
| 75 | ] ); |
||
| 76 | $this->getOutput()->addModules( [ |
||
| 77 | 'jquery.makeCollapsible', |
||
| 78 | 'mediawiki.icon', |
||
| 79 | ] ); |
||
| 80 | |||
| 81 | return '<div class="mw-changeslist">'; |
||
| 82 | } |
||
| 83 | |||
| 84 | /** |
||
| 85 | * Format a line for enhanced recentchange (aka with javascript and block of lines). |
||
| 86 | * |
||
| 87 | * @param RecentChange $rc |
||
| 88 | * @param bool $watched |
||
| 89 | * @param int $linenumber (default null) |
||
| 90 | * |
||
| 91 | * @return string |
||
| 92 | */ |
||
| 93 | public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) { |
||
| 94 | |||
| 95 | $date = $this->getLanguage()->userDate( |
||
| 96 | $rc->mAttribs['rc_timestamp'], |
||
| 97 | $this->getUser() |
||
| 98 | ); |
||
| 99 | |||
| 100 | $ret = ''; |
||
| 101 | |||
| 102 | # If it's a new day, add the headline and flush the cache |
||
| 103 | if ( $date != $this->lastdate ) { |
||
| 104 | # Process current cache |
||
| 105 | $ret = $this->recentChangesBlock(); |
||
| 106 | $this->rc_cache = []; |
||
| 107 | $ret .= Xml::element( 'h4', null, $date ) . "\n"; |
||
| 108 | $this->lastdate = $date; |
||
| 109 | } |
||
| 110 | |||
| 111 | $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $rc, $watched ); |
||
| 112 | $this->addCacheEntry( $cacheEntry ); |
||
| 113 | |||
| 114 | return $ret; |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Put accumulated information into the cache, for later display. |
||
| 119 | * Page moves go on their own line. |
||
| 120 | * |
||
| 121 | * @param RCCacheEntry $cacheEntry |
||
| 122 | */ |
||
| 123 | protected function addCacheEntry( RCCacheEntry $cacheEntry ) { |
||
| 124 | $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry ); |
||
| 125 | |||
| 126 | if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) { |
||
| 127 | $this->rc_cache[$cacheGroupingKey] = []; |
||
| 128 | } |
||
| 129 | |||
| 130 | array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry ); |
||
| 131 | } |
||
| 132 | |||
| 133 | /** |
||
| 134 | * @todo use rc_source to group, if set; fallback to rc_type |
||
| 135 | * |
||
| 136 | * @param RCCacheEntry $cacheEntry |
||
| 137 | * |
||
| 138 | * @return string |
||
| 139 | */ |
||
| 140 | protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) { |
||
| 141 | $title = $cacheEntry->getTitle(); |
||
| 142 | $cacheGroupingKey = $title->getPrefixedDBkey(); |
||
| 143 | |||
| 144 | $type = $cacheEntry->mAttribs['rc_type']; |
||
| 145 | |||
| 146 | if ( $type == RC_LOG ) { |
||
| 147 | // Group by log type |
||
| 148 | $cacheGroupingKey = SpecialPage::getTitleFor( |
||
| 149 | 'Log', |
||
| 150 | $cacheEntry->mAttribs['rc_log_type'] |
||
| 151 | )->getPrefixedDBkey(); |
||
| 152 | } |
||
| 153 | |||
| 154 | return $cacheGroupingKey; |
||
| 155 | } |
||
| 156 | |||
| 157 | /** |
||
| 158 | * Enhanced RC group |
||
| 159 | * @param RCCacheEntry[] $block |
||
| 160 | * @return string |
||
| 161 | * @throws DomainException |
||
| 162 | */ |
||
| 163 | protected function recentChangesBlockGroup( $block ) { |
||
| 164 | $recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' ); |
||
| 165 | |||
| 166 | # Add the namespace and title of the block as part of the class |
||
| 167 | $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' ]; |
||
| 168 | if ( $block[0]->mAttribs['rc_log_type'] ) { |
||
| 169 | # Log entry |
||
| 170 | $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-' |
||
| 171 | . $block[0]->mAttribs['rc_log_type'] ); |
||
| 172 | } else { |
||
| 173 | $tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns' |
||
| 174 | . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); |
||
| 175 | } |
||
| 176 | if ( $block[0]->watched |
||
| 177 | && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched |
||
| 178 | ) { |
||
| 179 | $tableClasses[] = 'mw-changeslist-line-watched'; |
||
| 180 | } else { |
||
| 181 | $tableClasses[] = 'mw-changeslist-line-not-watched'; |
||
| 182 | } |
||
| 183 | |||
| 184 | # Collate list of users |
||
| 185 | $userlinks = []; |
||
| 186 | # Other properties |
||
| 187 | $curId = 0; |
||
| 188 | # Some catalyst variables... |
||
| 189 | $namehidden = true; |
||
| 190 | $allLogs = true; |
||
| 191 | $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' ); |
||
| 192 | |||
| 193 | # Default values for RC flags |
||
| 194 | $collectedRcFlags = []; |
||
| 195 | foreach ( $recentChangesFlags as $key => $value ) { |
||
| 196 | $flagGrouping = ( isset( $recentChangesFlags[$key]['grouping'] ) ? |
||
| 197 | $recentChangesFlags[$key]['grouping'] : 'any' ); |
||
| 198 | View Code Duplication | switch ( $flagGrouping ) { |
|
| 199 | case 'all': |
||
| 200 | $collectedRcFlags[$key] = true; |
||
| 201 | break; |
||
| 202 | case 'any': |
||
| 203 | $collectedRcFlags[$key] = false; |
||
| 204 | break; |
||
| 205 | default: |
||
| 206 | throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" ); |
||
| 207 | } |
||
| 208 | } |
||
| 209 | foreach ( $block as $rcObj ) { |
||
| 210 | // If all log actions to this page were hidden, then don't |
||
| 211 | // give the name of the affected page for this block! |
||
| 212 | if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) { |
||
| 213 | $namehidden = false; |
||
| 214 | } |
||
| 215 | $u = $rcObj->userlink; |
||
| 216 | if ( !isset( $userlinks[$u] ) ) { |
||
| 217 | $userlinks[$u] = 0; |
||
| 218 | } |
||
| 219 | if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) { |
||
| 220 | $allLogs = false; |
||
| 221 | } |
||
| 222 | # Get the latest entry with a page_id and oldid |
||
| 223 | # since logs may not have these. |
||
| 224 | if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) { |
||
| 225 | $curId = $rcObj->mAttribs['rc_cur_id']; |
||
| 226 | } |
||
| 227 | |||
| 228 | $userlinks[$u]++; |
||
| 229 | } |
||
| 230 | |||
| 231 | # Sort the list and convert to text |
||
| 232 | krsort( $userlinks ); |
||
| 233 | asort( $userlinks ); |
||
| 234 | $users = []; |
||
| 235 | foreach ( $userlinks as $userlink => $count ) { |
||
| 236 | $text = $userlink; |
||
| 237 | $text .= $this->getLanguage()->getDirMark(); |
||
| 238 | if ( $count > 1 ) { |
||
| 239 | // @todo FIXME: Hardcoded '×'. Should be a message. |
||
| 240 | $formattedCount = $this->getLanguage()->formatNum( $count ) . '×'; |
||
| 241 | $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped(); |
||
| 242 | } |
||
| 243 | array_push( $users, $text ); |
||
| 244 | } |
||
| 245 | |||
| 246 | # Article link |
||
| 247 | $articleLink = ''; |
||
| 248 | $revDeletedMsg = false; |
||
| 249 | if ( $namehidden ) { |
||
| 250 | $revDeletedMsg = $this->msg( 'rev-deleted-event' )->escaped(); |
||
| 251 | } elseif ( $allLogs ) { |
||
| 252 | $articleLink = $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); |
||
| 253 | } else { |
||
| 254 | $articleLink = $this->getArticleLink( $block[0], $block[0]->unpatrolled, $block[0]->watched ); |
||
| 255 | } |
||
| 256 | |||
| 257 | $queryParams['curid'] = $curId; |
||
|
0 ignored issues
–
show
|
|||
| 258 | |||
| 259 | # Sub-entries |
||
| 260 | $lines = []; |
||
| 261 | foreach ( $block as $i => $rcObj ) { |
||
| 262 | $line = $this->getLineData( $block, $rcObj, $queryParams ); |
||
| 263 | if ( !$line ) { |
||
| 264 | // completely ignore this RC entry if we don't want to render it |
||
| 265 | unset( $block[$i] ); |
||
| 266 | continue; |
||
| 267 | } |
||
| 268 | |||
| 269 | // Roll up flags |
||
| 270 | foreach ( $line['recentChangesFlagsRaw'] as $key => $value ) { |
||
| 271 | $flagGrouping = ( isset( $recentChangesFlags[$key]['grouping'] ) ? |
||
| 272 | $recentChangesFlags[$key]['grouping'] : 'any' ); |
||
| 273 | View Code Duplication | switch ( $flagGrouping ) { |
|
| 274 | case 'all': |
||
| 275 | if ( !$value ) { |
||
| 276 | $collectedRcFlags[$key] = false; |
||
| 277 | } |
||
| 278 | break; |
||
| 279 | case 'any': |
||
| 280 | if ( $value ) { |
||
| 281 | $collectedRcFlags[$key] = true; |
||
| 282 | } |
||
| 283 | break; |
||
| 284 | default: |
||
| 285 | throw new DomainException( "Unknown grouping type \"{$flagGrouping}\"" ); |
||
| 286 | } |
||
| 287 | } |
||
| 288 | |||
| 289 | $lines[] = $line; |
||
| 290 | } |
||
| 291 | |||
| 292 | // Further down are some assumptions that $block is a 0-indexed array |
||
| 293 | // with (count-1) as last key. Let's make sure it is. |
||
| 294 | $block = array_values( $block ); |
||
| 295 | |||
| 296 | if ( empty( $block ) || !$lines ) { |
||
| 297 | // if we can't show anything, don't display this block altogether |
||
| 298 | return ''; |
||
| 299 | } |
||
| 300 | |||
| 301 | $logText = $this->getLogText( $block, $queryParams, $allLogs, |
||
| 302 | $collectedRcFlags['newpage'], $namehidden |
||
| 303 | ); |
||
| 304 | |||
| 305 | # Character difference (does not apply if only log items) |
||
| 306 | $charDifference = false; |
||
| 307 | if ( $RCShowChangedSize && !$allLogs ) { |
||
| 308 | $last = 0; |
||
| 309 | $first = count( $block ) - 1; |
||
| 310 | # Some events (like logs and category changes) have an "empty" size, so we need to skip those... |
||
| 311 | while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) { |
||
| 312 | $last++; |
||
| 313 | } |
||
| 314 | while ( $last < $first && $block[$first]->mAttribs['rc_old_len'] === null ) { |
||
| 315 | $first--; |
||
| 316 | } |
||
| 317 | # Get net change |
||
| 318 | $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ); |
||
| 319 | } |
||
| 320 | |||
| 321 | $numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers ); |
||
| 322 | $usersList = $this->msg( 'brackets' )->rawParams( |
||
| 323 | implode( $this->message['semicolon-separator'], $users ) |
||
| 324 | )->escaped(); |
||
| 325 | |||
| 326 | $templateParams = [ |
||
| 327 | 'articleLink' => $articleLink, |
||
| 328 | 'charDifference' => $charDifference, |
||
| 329 | 'collectedRcFlags' => $this->recentChangesFlags( $collectedRcFlags ), |
||
| 330 | 'languageDirMark' => $this->getLanguage()->getDirMark(), |
||
| 331 | 'lines' => $lines, |
||
| 332 | 'logText' => $logText, |
||
| 333 | 'numberofWatchingusers' => $numberofWatchingusers, |
||
| 334 | 'rev-deleted-event' => $revDeletedMsg, |
||
| 335 | 'tableClasses' => $tableClasses, |
||
| 336 | 'timestamp' => $block[0]->timestamp, |
||
| 337 | 'users' => $usersList, |
||
| 338 | ]; |
||
| 339 | |||
| 340 | $this->rcCacheIndex++; |
||
| 341 | |||
| 342 | $templateParser = new TemplateParser(); |
||
| 343 | return $templateParser->processTemplate( |
||
| 344 | 'EnhancedChangesListGroup', |
||
| 345 | $templateParams |
||
| 346 | ); |
||
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * @param RCCacheEntry[] $block |
||
| 351 | * @param RCCacheEntry $rcObj |
||
| 352 | * @param array $queryParams |
||
| 353 | * @return array |
||
| 354 | * @throws Exception |
||
| 355 | * @throws FatalError |
||
| 356 | * @throws MWException |
||
| 357 | */ |
||
| 358 | protected function getLineData( array $block, RCCacheEntry $rcObj, array $queryParams = [] ) { |
||
| 359 | $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' ); |
||
| 360 | |||
| 361 | $classes = [ 'mw-enhanced-rc' ]; |
||
| 362 | $type = $rcObj->mAttribs['rc_type']; |
||
| 363 | $data = []; |
||
| 364 | $lineParams = []; |
||
| 365 | |||
| 366 | if ( $rcObj->watched |
||
| 367 | && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched |
||
| 368 | ) { |
||
| 369 | $classes = [ 'mw-enhanced-watched' ]; |
||
| 370 | } |
||
| 371 | |||
| 372 | $separator = ' <span class="mw-changeslist-separator">. .</span> '; |
||
| 373 | |||
| 374 | $data['recentChangesFlags'] = [ |
||
| 375 | 'newpage' => $type == RC_NEW, |
||
| 376 | 'minor' => $rcObj->mAttribs['rc_minor'], |
||
| 377 | 'unpatrolled' => $rcObj->unpatrolled, |
||
| 378 | 'bot' => $rcObj->mAttribs['rc_bot'], |
||
| 379 | ]; |
||
| 380 | |||
| 381 | $params = $queryParams; |
||
| 382 | |||
| 383 | if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) { |
||
| 384 | $params['oldid'] = $rcObj->mAttribs['rc_this_oldid']; |
||
| 385 | } |
||
| 386 | |||
| 387 | # Log timestamp |
||
| 388 | if ( $type == RC_LOG ) { |
||
| 389 | $link = $rcObj->timestamp; |
||
| 390 | # Revision link |
||
| 391 | } elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) { |
||
| 392 | $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> '; |
||
| 393 | } else { |
||
| 394 | $link = $this->linkRenderer->makeKnownLink( |
||
| 395 | $rcObj->getTitle(), |
||
| 396 | new HtmlArmor( $rcObj->timestamp ), |
||
| 397 | [], |
||
| 398 | $params |
||
| 399 | ); |
||
| 400 | if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) { |
||
| 401 | $link = '<span class="history-deleted">' . $link . '</span> '; |
||
| 402 | } |
||
| 403 | } |
||
| 404 | $data['timestampLink'] = $link; |
||
| 405 | |||
| 406 | $currentAndLastLinks = ''; |
||
| 407 | if ( !$type == RC_LOG || $type == RC_NEW ) { |
||
| 408 | $currentAndLastLinks .= ' ' . $this->msg( 'parentheses' )->rawParams( |
||
| 409 | $rcObj->curlink . |
||
| 410 | $this->message['pipe-separator'] . |
||
| 411 | $rcObj->lastlink |
||
| 412 | )->escaped(); |
||
| 413 | } |
||
| 414 | $data['currentAndLastLinks'] = $currentAndLastLinks; |
||
| 415 | $data['separatorAfterCurrentAndLastLinks'] = $separator; |
||
| 416 | |||
| 417 | # Character diff |
||
| 418 | if ( $RCShowChangedSize ) { |
||
| 419 | $cd = $this->formatCharacterDifference( $rcObj ); |
||
| 420 | if ( $cd !== '' ) { |
||
| 421 | $data['characterDiff'] = $cd; |
||
| 422 | $data['separatorAfterCharacterDiff'] = $separator; |
||
| 423 | } |
||
| 424 | } |
||
| 425 | |||
| 426 | View Code Duplication | if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) { |
|
| 427 | $data['logEntry'] = $this->insertLogEntry( $rcObj ); |
||
| 428 | } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) { |
||
| 429 | $data['comment'] = $this->insertComment( $rcObj ); |
||
| 430 | } else { |
||
| 431 | # User links |
||
| 432 | $data['userLink'] = $rcObj->userlink; |
||
| 433 | $data['userTalkLink'] = $rcObj->usertalklink; |
||
| 434 | $data['comment'] = $this->insertComment( $rcObj ); |
||
| 435 | } |
||
| 436 | |||
| 437 | # Rollback |
||
| 438 | $data['rollback'] = $this->getRollback( $rcObj ); |
||
| 439 | |||
| 440 | # Tags |
||
| 441 | $data['tags'] = $this->getTags( $rcObj, $classes ); |
||
| 442 | |||
| 443 | // give the hook a chance to modify the data |
||
| 444 | $success = Hooks::run( 'EnhancedChangesListModifyLineData', |
||
| 445 | [ $this, &$data, $block, $rcObj, &$classes ] ); |
||
| 446 | if ( !$success ) { |
||
| 447 | // skip entry if hook aborted it |
||
| 448 | return []; |
||
| 449 | } |
||
| 450 | |||
| 451 | $lineParams['recentChangesFlagsRaw'] = []; |
||
| 452 | if ( isset( $data['recentChangesFlags'] ) ) { |
||
| 453 | $lineParams['recentChangesFlags'] = $this->recentChangesFlags( $data['recentChangesFlags'] ); |
||
|
0 ignored issues
–
show
It seems like
$data['recentChangesFlags'] can also be of type string; however, ChangesList::recentChangesFlags() does only seem to accept array, maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. Loading history...
|
|||
| 454 | # FIXME: This is used by logic, don't return it in the template params. |
||
| 455 | $lineParams['recentChangesFlagsRaw'] = $data['recentChangesFlags']; |
||
| 456 | unset( $data['recentChangesFlags'] ); |
||
| 457 | } |
||
| 458 | |||
| 459 | if ( isset( $data['timestampLink'] ) ) { |
||
| 460 | $lineParams['timestampLink'] = $data['timestampLink']; |
||
| 461 | unset( $data['timestampLink'] ); |
||
| 462 | } |
||
| 463 | |||
| 464 | $lineParams['classes'] = array_values( $classes ); |
||
| 465 | |||
| 466 | // everything else: makes it easier for extensions to add or remove data |
||
| 467 | $lineParams['data'] = array_values( $data ); |
||
| 468 | |||
| 469 | return $lineParams; |
||
| 470 | } |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Generates amount of changes (linking to diff ) & link to history. |
||
| 474 | * |
||
| 475 | * @param array $block |
||
| 476 | * @param array $queryParams |
||
| 477 | * @param bool $allLogs |
||
| 478 | * @param bool $isnew |
||
| 479 | * @param bool $namehidden |
||
| 480 | * @return string |
||
| 481 | */ |
||
| 482 | protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) { |
||
| 483 | if ( empty( $block ) ) { |
||
| 484 | return ''; |
||
| 485 | } |
||
| 486 | |||
| 487 | # Changes message |
||
| 488 | static $nchanges = []; |
||
| 489 | static $sinceLastVisitMsg = []; |
||
| 490 | |||
| 491 | $n = count( $block ); |
||
| 492 | if ( !isset( $nchanges[$n] ) ) { |
||
| 493 | $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped(); |
||
| 494 | } |
||
| 495 | |||
| 496 | $sinceLast = 0; |
||
| 497 | $unvisitedOldid = null; |
||
| 498 | /** @var $rcObj RCCacheEntry */ |
||
| 499 | foreach ( $block as $rcObj ) { |
||
| 500 | // Same logic as below inside main foreach |
||
| 501 | if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) { |
||
| 502 | $sinceLast++; |
||
| 503 | $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid']; |
||
| 504 | } |
||
| 505 | } |
||
| 506 | if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) { |
||
| 507 | $sinceLastVisitMsg[$sinceLast] = |
||
| 508 | $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped(); |
||
| 509 | } |
||
| 510 | |||
| 511 | $currentRevision = 0; |
||
| 512 | foreach ( $block as $rcObj ) { |
||
| 513 | if ( !$currentRevision ) { |
||
| 514 | $currentRevision = $rcObj->mAttribs['rc_this_oldid']; |
||
| 515 | } |
||
| 516 | } |
||
| 517 | |||
| 518 | # Total change link |
||
| 519 | $links = []; |
||
| 520 | /** @var $block0 RecentChange */ |
||
| 521 | $block0 = $block[0]; |
||
| 522 | $last = $block[count( $block ) - 1]; |
||
| 523 | if ( !$allLogs ) { |
||
| 524 | if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) || |
||
|
0 ignored issues
–
show
The variable
$rcObj does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
Loading history...
|
|||
| 525 | $isnew || |
||
| 526 | $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE |
||
| 527 | ) { |
||
| 528 | $links['total-changes'] = $nchanges[$n]; |
||
| 529 | } else { |
||
| 530 | $links['total-changes'] = $this->linkRenderer->makeKnownLink( |
||
| 531 | $block0->getTitle(), |
||
| 532 | new HtmlArmor( $nchanges[$n] ), |
||
| 533 | [], |
||
| 534 | $queryParams + [ |
||
| 535 | 'diff' => $currentRevision, |
||
| 536 | 'oldid' => $last->mAttribs['rc_last_oldid'], |
||
| 537 | ] |
||
| 538 | ); |
||
| 539 | if ( $sinceLast > 0 && $sinceLast < $n ) { |
||
| 540 | $links['total-changes-since-last'] = $this->linkRenderer->makeKnownLink( |
||
| 541 | $block0->getTitle(), |
||
| 542 | new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ), |
||
| 543 | [], |
||
| 544 | $queryParams + [ |
||
| 545 | 'diff' => $currentRevision, |
||
| 546 | 'oldid' => $unvisitedOldid, |
||
| 547 | ] |
||
| 548 | ); |
||
| 549 | } |
||
| 550 | } |
||
| 551 | } |
||
| 552 | |||
| 553 | # History |
||
| 554 | if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) { |
||
|
0 ignored issues
–
show
This
if statement is empty and can be removed.
This check looks for the bodies of These if (rand(1, 6) > 3) {
//print "Check failed";
} else {
print "Check succeeded";
}
could be turned into if (rand(1, 6) <= 3) {
print "Check succeeded";
}
This is much more concise to read. Loading history...
|
|||
| 555 | // don't show history link for logs |
||
| 556 | } elseif ( $namehidden || !$block0->getTitle()->exists() ) { |
||
| 557 | $links['history'] = $this->message['enhancedrc-history']; |
||
| 558 | } else { |
||
| 559 | $params = $queryParams; |
||
| 560 | $params['action'] = 'history'; |
||
| 561 | |||
| 562 | $links['history'] = $this->linkRenderer->makeKnownLink( |
||
| 563 | $block0->getTitle(), |
||
| 564 | new HtmlArmor( $this->message['enhancedrc-history'] ), |
||
| 565 | [], |
||
| 566 | $params |
||
| 567 | ); |
||
| 568 | } |
||
| 569 | |||
| 570 | # Allow others to alter, remove or add to these links |
||
| 571 | Hooks::run( 'EnhancedChangesList::getLogText', |
||
| 572 | [ $this, &$links, $block ] ); |
||
| 573 | |||
| 574 | if ( !$links ) { |
||
| 575 | return ''; |
||
| 576 | } |
||
| 577 | |||
| 578 | $logtext = implode( $this->message['pipe-separator'], $links ); |
||
| 579 | $logtext = $this->msg( 'parentheses' )->rawParams( $logtext )->escaped(); |
||
| 580 | return ' ' . $logtext; |
||
| 581 | } |
||
| 582 | |||
| 583 | /** |
||
| 584 | * Enhanced RC ungrouped line. |
||
| 585 | * |
||
| 586 | * @param RecentChange|RCCacheEntry $rcObj |
||
| 587 | * @return string A HTML formatted line (generated using $r) |
||
| 588 | */ |
||
| 589 | protected function recentChangesBlockLine( $rcObj ) { |
||
| 590 | $data = []; |
||
| 591 | |||
| 592 | $query['curid'] = $rcObj->mAttribs['rc_cur_id']; |
||
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$query was never initialized. Although not strictly required by PHP, it is generally a good practice to add $query = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop. Loading history...
|
|||
| 593 | |||
| 594 | $type = $rcObj->mAttribs['rc_type']; |
||
| 595 | $logType = $rcObj->mAttribs['rc_log_type']; |
||
| 596 | $classes = $this->getHTMLClasses( $rcObj, $rcObj->watched ); |
||
|
0 ignored issues
–
show
The property
watched does not seem to exist in RecentChange.
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. Loading history...
|
|||
| 597 | $classes[] = 'mw-enhanced-rc'; |
||
| 598 | |||
| 599 | View Code Duplication | if ( $logType ) { |
|
| 600 | # Log entry |
||
| 601 | $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType ); |
||
| 602 | } else { |
||
| 603 | $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' . |
||
| 604 | $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] ); |
||
| 605 | } |
||
| 606 | |||
| 607 | # Flag and Timestamp |
||
| 608 | $data['recentChangesFlags'] = [ |
||
| 609 | 'newpage' => $type == RC_NEW, |
||
| 610 | 'minor' => $rcObj->mAttribs['rc_minor'], |
||
| 611 | 'unpatrolled' => $rcObj->unpatrolled, |
||
|
0 ignored issues
–
show
The property
unpatrolled does not seem to exist in RecentChange.
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. Loading history...
|
|||
| 612 | 'bot' => $rcObj->mAttribs['rc_bot'], |
||
| 613 | ]; |
||
| 614 | // timestamp is not really a link here, but is called timestampLink |
||
| 615 | // for consistency with EnhancedChangesListModifyLineData |
||
| 616 | $data['timestampLink'] = $rcObj->timestamp; |
||
|
0 ignored issues
–
show
The property
timestamp does not seem to exist. Did you mean notificationtimestamp?
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. Loading history...
|
|||
| 617 | |||
| 618 | # Article or log link |
||
| 619 | if ( $logType ) { |
||
| 620 | $logPage = new LogPage( $logType ); |
||
| 621 | $logTitle = SpecialPage::getTitleFor( 'Log', $logType ); |
||
| 622 | $logName = $logPage->getName()->text(); |
||
| 623 | $data['logLink'] = $this->msg( 'parentheses' ) |
||
| 624 | ->rawParams( |
||
| 625 | $this->linkRenderer->makeKnownLink( $logTitle, $logName ) |
||
| 626 | )->escaped(); |
||
| 627 | } else { |
||
| 628 | $data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched ); |
||
| 629 | } |
||
| 630 | |||
| 631 | # Diff and hist links |
||
| 632 | if ( $type != RC_LOG && $type != RC_CATEGORIZE ) { |
||
| 633 | $query['action'] = 'history'; |
||
| 634 | $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query ); |
||
|
0 ignored issues
–
show
$rcObj of type object<RecentChange> is not a sub-type of object<RCCacheEntry>. It seems like you assume a child class of the class RecentChange to be always present.
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass. Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type. Loading history...
|
|||
| 635 | } |
||
| 636 | $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator">. .</span> '; |
||
| 637 | |||
| 638 | # Character diff |
||
| 639 | if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) { |
||
| 640 | $cd = $this->formatCharacterDifference( $rcObj ); |
||
| 641 | if ( $cd !== '' ) { |
||
| 642 | $data['characterDiff'] = $cd; |
||
| 643 | $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator">. .</span> '; |
||
| 644 | } |
||
| 645 | } |
||
| 646 | |||
| 647 | if ( $type == RC_LOG ) { |
||
| 648 | $data['logEntry'] = $this->insertLogEntry( $rcObj ); |
||
| 649 | } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) { |
||
| 650 | $data['comment'] = $this->insertComment( $rcObj ); |
||
| 651 | View Code Duplication | } else { |
|
| 652 | $data['userLink'] = $rcObj->userlink; |
||
|
0 ignored issues
–
show
The property
userlink does not seem to exist in RecentChange.
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. Loading history...
|
|||
| 653 | $data['userTalkLink'] = $rcObj->usertalklink; |
||
|
0 ignored issues
–
show
The property
usertalklink does not seem to exist in RecentChange.
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. Loading history...
|
|||
| 654 | $data['comment'] = $this->insertComment( $rcObj ); |
||
| 655 | if ( $type == RC_CATEGORIZE ) { |
||
| 656 | $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query ); |
||
|
0 ignored issues
–
show
$rcObj of type object<RecentChange> is not a sub-type of object<RCCacheEntry>. It seems like you assume a child class of the class RecentChange to be always present.
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass. Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type. Loading history...
|
|||
| 657 | } |
||
| 658 | $data['rollback'] = $this->getRollback( $rcObj ); |
||
| 659 | } |
||
| 660 | |||
| 661 | # Tags |
||
| 662 | $data['tags'] = $this->getTags( $rcObj, $classes ); |
||
| 663 | |||
| 664 | # Show how many people are watching this if enabled |
||
| 665 | $data['watchingUsers'] = $this->numberofWatchingusers( $rcObj->numberofWatchingusers ); |
||
| 666 | |||
| 667 | // give the hook a chance to modify the data |
||
| 668 | $success = Hooks::run( 'EnhancedChangesListModifyBlockLineData', |
||
| 669 | [ $this, &$data, $rcObj ] ); |
||
| 670 | if ( !$success ) { |
||
| 671 | // skip entry if hook aborted it |
||
| 672 | return ''; |
||
| 673 | } |
||
| 674 | |||
| 675 | $line = Html::openElement( 'table', [ 'class' => $classes ] ) . |
||
| 676 | Html::openElement( 'tr' ); |
||
| 677 | $line .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>'; |
||
| 678 | |||
| 679 | if ( isset( $data['recentChangesFlags'] ) ) { |
||
| 680 | $line .= $this->recentChangesFlags( $data['recentChangesFlags'] ); |
||
| 681 | unset( $data['recentChangesFlags'] ); |
||
| 682 | } |
||
| 683 | |||
| 684 | if ( isset( $data['timestampLink'] ) ) { |
||
| 685 | $line .= ' ' . $data['timestampLink']; |
||
| 686 | unset( $data['timestampLink'] ); |
||
| 687 | } |
||
| 688 | $line .= ' </td><td>'; |
||
| 689 | |||
| 690 | // everything else: makes it easier for extensions to add or remove data |
||
| 691 | $line .= implode( '', $data ); |
||
| 692 | |||
| 693 | $line .= "</td></tr></table>\n"; |
||
| 694 | |||
| 695 | return $line; |
||
| 696 | } |
||
| 697 | |||
| 698 | /** |
||
| 699 | * Returns value to be used in 'historyLink' element of $data param in |
||
| 700 | * EnhancedChangesListModifyBlockLineData hook. |
||
| 701 | * |
||
| 702 | * @since 1.27 |
||
| 703 | * |
||
| 704 | * @param RCCacheEntry $rc |
||
| 705 | * @param array $query array of key/value pairs to append as a query string |
||
| 706 | * @return string HTML |
||
| 707 | */ |
||
| 708 | public function getDiffHistLinks( RCCacheEntry $rc, array $query ) { |
||
| 709 | $pageTitle = $rc->getTitle(); |
||
| 710 | if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) { |
||
| 711 | // For categorizations we must swap the category title with the page title! |
||
| 712 | $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) ); |
||
| 713 | } |
||
| 714 | |||
| 715 | $retVal = ' ' . $this->msg( 'parentheses' ) |
||
| 716 | ->rawParams( $rc->difflink . $this->message['pipe-separator'] |
||
| 717 | . $this->linkRenderer->makeKnownLink( |
||
| 718 | $pageTitle, |
||
|
0 ignored issues
–
show
It seems like
$pageTitle defined by \Title::newFromID($rc->getAttribute('rc_cur_id')) on line 712 can be null; however, MediaWiki\Linker\LinkRenderer::makeKnownLink() does not accept null, maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
Loading history...
|
|||
| 719 | new HtmlArmor( $this->message['hist'] ), |
||
| 720 | [], |
||
| 721 | $query |
||
| 722 | ) )->escaped(); |
||
| 723 | return $retVal; |
||
| 724 | } |
||
| 725 | |||
| 726 | /** |
||
| 727 | * If enhanced RC is in use, this function takes the previously cached |
||
| 728 | * RC lines, arranges them, and outputs the HTML |
||
| 729 | * |
||
| 730 | * @return string |
||
| 731 | */ |
||
| 732 | protected function recentChangesBlock() { |
||
| 733 | if ( count( $this->rc_cache ) == 0 ) { |
||
| 734 | return ''; |
||
| 735 | } |
||
| 736 | |||
| 737 | $blockOut = ''; |
||
| 738 | foreach ( $this->rc_cache as $block ) { |
||
| 739 | if ( count( $block ) < 2 ) { |
||
| 740 | $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) ); |
||
| 741 | } else { |
||
| 742 | $blockOut .= $this->recentChangesBlockGroup( $block ); |
||
| 743 | } |
||
| 744 | } |
||
| 745 | |||
| 746 | return '<div>' . $blockOut . '</div>'; |
||
| 747 | } |
||
| 748 | |||
| 749 | /** |
||
| 750 | * Returns text for the end of RC |
||
| 751 | * If enhanced RC is in use, returns pretty much all the text |
||
| 752 | * @return string |
||
| 753 | */ |
||
| 754 | public function endRecentChangesList() { |
||
| 755 | return $this->recentChangesBlock() . '</div>'; |
||
| 756 | } |
||
| 757 | } |
||
| 758 |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArrayis initialized the first time when the foreach loop is entered. You can also see that the value of thebarkey is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.