MediaWiki  REL1_24
EnhancedChangesList.php
Go to the documentation of this file.
00001 <?php
00023 class EnhancedChangesList extends ChangesList {
00024 
00028     protected $cacheEntryFactory;
00029 
00033     protected $rc_cache;
00034 
00038     public function __construct( $obj ) {
00039         if ( $obj instanceof Skin ) {
00040             // @todo: deprecate constructing with Skin
00041             $context = $obj->getContext();
00042         } else {
00043             if ( !$obj instanceof IContextSource ) {
00044                 throw new MWException( 'EnhancedChangesList must be constructed with a '
00045                     . 'context source or skin.' );
00046             }
00047 
00048             $context = $obj;
00049         }
00050 
00051         parent::__construct( $context );
00052 
00053         // message is set by the parent ChangesList class
00054         $this->cacheEntryFactory = new RCCacheEntryFactory(
00055             $context,
00056             $this->message
00057         );
00058     }
00059 
00064     public function beginRecentChangesList() {
00065         $this->rc_cache = array();
00066         $this->rcMoveIndex = 0;
00067         $this->rcCacheIndex = 0;
00068         $this->lastdate = '';
00069         $this->rclistOpen = false;
00070         $this->getOutput()->addModuleStyles( array(
00071             'mediawiki.special.changeslist',
00072             'mediawiki.special.changeslist.enhanced',
00073         ) );
00074         $this->getOutput()->addModules( array(
00075             'jquery.makeCollapsible',
00076             'mediawiki.icon',
00077         ) );
00078 
00079         return '<div class="mw-changeslist">';
00080     }
00081 
00090     public function recentChangesLine( &$baseRC, $watched = false ) {
00091         wfProfileIn( __METHOD__ );
00092 
00093         $date = $this->getLanguage()->userDate(
00094             $baseRC->mAttribs['rc_timestamp'],
00095             $this->getUser()
00096         );
00097 
00098         $ret = '';
00099 
00100         # If it's a new day, add the headline and flush the cache
00101         if ( $date != $this->lastdate ) {
00102             # Process current cache
00103             $ret = $this->recentChangesBlock();
00104             $this->rc_cache = array();
00105             $ret .= Xml::element( 'h4', null, $date ) . "\n";
00106             $this->lastdate = $date;
00107         }
00108 
00109         $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $baseRC, $watched );
00110         $this->addCacheEntry( $cacheEntry );
00111 
00112         wfProfileOut( __METHOD__ );
00113 
00114         return $ret;
00115     }
00116 
00123     protected function addCacheEntry( RCCacheEntry $cacheEntry ) {
00124         $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry );
00125 
00126         if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) {
00127             $this->rc_cache[$cacheGroupingKey] = array();
00128         }
00129 
00130         array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry );
00131     }
00132 
00140     protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) {
00141         $title = $cacheEntry->getTitle();
00142         $cacheGroupingKey = $title->getPrefixedDBkey();
00143 
00144         $type = $cacheEntry->mAttribs['rc_type'];
00145 
00146         if ( $type == RC_LOG ) {
00147             // Group by log type
00148             $cacheGroupingKey = SpecialPage::getTitleFor(
00149                 'Log',
00150                 $cacheEntry->mAttribs['rc_log_type']
00151             )->getPrefixedDBkey();
00152         }
00153 
00154         return $cacheGroupingKey;
00155     }
00156 
00162     protected function recentChangesBlockGroup( $block ) {
00163         wfProfileIn( __METHOD__ );
00164 
00165         # Add the namespace and title of the block as part of the class
00166         $classes = array( 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' );
00167         if ( $block[0]->mAttribs['rc_log_type'] ) {
00168             # Log entry
00169             $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
00170                 . $block[0]->mAttribs['rc_log_type'] );
00171         } else {
00172             $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
00173                 . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
00174         }
00175         $classes[] = $block[0]->watched && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched
00176             ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
00177         $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
00178             Html::openElement( 'tr' );
00179 
00180         # Collate list of users
00181         $userlinks = array();
00182         # Other properties
00183         $unpatrolled = false;
00184         $isnew = false;
00185         $allBots = true;
00186         $allMinors = true;
00187         $curId = $currentRevision = 0;
00188         # Some catalyst variables...
00189         $namehidden = true;
00190         $allLogs = true;
00191         $oldid = '';
00192         $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' );
00193         foreach ( $block as $rcObj ) {
00194             $oldid = $rcObj->mAttribs['rc_last_oldid'];
00195             if ( $rcObj->mAttribs['rc_type'] == RC_NEW ) {
00196                 $isnew = true;
00197             }
00198             // If all log actions to this page were hidden, then don't
00199             // give the name of the affected page for this block!
00200             if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
00201                 $namehidden = false;
00202             }
00203             $u = $rcObj->userlink;
00204             if ( !isset( $userlinks[$u] ) ) {
00205                 $userlinks[$u] = 0;
00206             }
00207             if ( $rcObj->unpatrolled ) {
00208                 $unpatrolled = true;
00209             }
00210             if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
00211                 $allLogs = false;
00212             }
00213             # Get the latest entry with a page_id and oldid
00214             # since logs may not have these.
00215             if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
00216                 $curId = $rcObj->mAttribs['rc_cur_id'];
00217             }
00218             if ( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) {
00219                 $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
00220             }
00221 
00222             if ( !$rcObj->mAttribs['rc_bot'] ) {
00223                 $allBots = false;
00224             }
00225             if ( !$rcObj->mAttribs['rc_minor'] ) {
00226                 $allMinors = false;
00227             }
00228 
00229             $userlinks[$u]++;
00230         }
00231 
00232         # Sort the list and convert to text
00233         krsort( $userlinks );
00234         asort( $userlinks );
00235         $users = array();
00236         foreach ( $userlinks as $userlink => $count ) {
00237             $text = $userlink;
00238             $text .= $this->getLanguage()->getDirMark();
00239             if ( $count > 1 ) {
00240                 // @todo FIXME: Hardcoded '×'. Should be a message.
00241                 $formattedCount = $this->getLanguage()->formatNum( $count ) . '×';
00242                 $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped();
00243             }
00244             array_push( $users, $text );
00245         }
00246 
00247         $users = ' <span class="changedby">'
00248             . $this->msg( 'brackets' )->rawParams(
00249                 implode( $this->message['semicolon-separator'], $users )
00250             )->escaped() . '</span>';
00251 
00252         $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow ' .
00253             'mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>';
00254         $r .= "<td>$tl</td>";
00255 
00256         # Main line
00257         $r .= '<td class="mw-enhanced-rc">' . $this->recentChangesFlags( array(
00258             'newpage' => $isnew, # show, when one have this flag
00259             'minor' => $allMinors, # show only, when all have this flag
00260             'unpatrolled' => $unpatrolled, # show, when one have this flag
00261             'bot' => $allBots, # show only, when all have this flag
00262         ) );
00263 
00264         # Timestamp
00265         $r .= '&#160;' . $block[0]->timestamp . '&#160;</td><td>';
00266 
00267         # Article link
00268         if ( $namehidden ) {
00269             $r .= ' <span class="history-deleted">' .
00270                 $this->msg( 'rev-deleted-event' )->escaped() . '</span>';
00271         } elseif ( $allLogs ) {
00272             $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
00273         } else {
00274             $this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched );
00275         }
00276 
00277         $r .= $this->getLanguage()->getDirMark();
00278 
00279         $queryParams['curid'] = $curId;
00280 
00281         # Changes message
00282         static $nchanges = array();
00283         static $sinceLastVisitMsg = array();
00284 
00285         $n = count( $block );
00286         if ( !isset( $nchanges[$n] ) ) {
00287             $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
00288         }
00289 
00290         $sinceLast = 0;
00291         $unvisitedOldid = null;
00293         foreach ( $block as $rcObj ) {
00294             // Same logic as below inside main foreach
00295             if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
00296                 $sinceLast++;
00297                 $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
00298             }
00299         }
00300         if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
00301             $sinceLastVisitMsg[$sinceLast] =
00302                 $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
00303         }
00304 
00305         # Total change link
00306         $r .= ' ';
00307         $logtext = '';
00309         $block0 = $block[0];
00310         if ( !$allLogs ) {
00311             if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
00312                 $logtext .= $nchanges[$n];
00313             } elseif ( $isnew ) {
00314                 $logtext .= $nchanges[$n];
00315             } else {
00316                 $logtext .= Linker::link(
00317                     $block0->getTitle(),
00318                     $nchanges[$n],
00319                     array(),
00320                     $queryParams + array(
00321                         'diff' => $currentRevision,
00322                         'oldid' => $oldid,
00323                     ),
00324                     array( 'known', 'noclasses' )
00325                 );
00326                 if ( $sinceLast > 0 && $sinceLast < $n ) {
00327                     $logtext .= $this->message['pipe-separator'] . Linker::link(
00328                         $block0->getTitle(),
00329                         $sinceLastVisitMsg[$sinceLast],
00330                         array(),
00331                         $queryParams + array(
00332                             'diff' => $currentRevision,
00333                             'oldid' => $unvisitedOldid,
00334                         ),
00335                         array( 'known', 'noclasses' )
00336                     );
00337                 }
00338             }
00339         }
00340 
00341         # History
00342         if ( $allLogs ) {
00343             // don't show history link for logs
00344         } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
00345             $logtext .= $this->message['pipe-separator'] . $this->message['enhancedrc-history'];
00346         } else {
00347             $params = $queryParams;
00348             $params['action'] = 'history';
00349 
00350             $logtext .= $this->message['pipe-separator'] .
00351                 Linker::linkKnown(
00352                     $block0->getTitle(),
00353                     $this->message['enhancedrc-history'],
00354                     array(),
00355                     $params
00356                 );
00357         }
00358 
00359         if ( $logtext !== '' ) {
00360             $r .= $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
00361         }
00362 
00363         $r .= ' <span class="mw-changeslist-separator">. .</span> ';
00364 
00365         # Character difference (does not apply if only log items)
00366         if ( $RCShowChangedSize && !$allLogs ) {
00367             $last = 0;
00368             $first = count( $block ) - 1;
00369             # Some events (like logs) have an "empty" size, so we need to skip those...
00370             while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
00371                 $last++;
00372             }
00373             while ( $first > $last && $block[$first]->mAttribs['rc_old_len'] === null ) {
00374                 $first--;
00375             }
00376             # Get net change
00377             $chardiff = $this->formatCharacterDifference( $block[$first], $block[$last] );
00378 
00379             if ( $chardiff == '' ) {
00380                 $r .= ' ';
00381             } else {
00382                 $r .= ' ' . $chardiff . ' <span class="mw-changeslist-separator">. .</span> ';
00383             }
00384         }
00385 
00386         $r .= $users;
00387         $r .= $this->numberofWatchingusers( $block0->numberofWatchingusers );
00388         $r .= '</td></tr>';
00389 
00390         # Sub-entries
00391         foreach ( $block as $rcObj ) {
00392             # Classes to apply -- TODO implement
00393             $classes = array();
00394             $type = $rcObj->mAttribs['rc_type'];
00395 
00396             $trClass = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
00397                 ? ' class="mw-enhanced-watched"' : '';
00398 
00399             $r .= '<tr' . $trClass . '><td></td><td class="mw-enhanced-rc">';
00400             $r .= $this->recentChangesFlags( array(
00401                 'newpage' => $type == RC_NEW,
00402                 'minor' => $rcObj->mAttribs['rc_minor'],
00403                 'unpatrolled' => $rcObj->unpatrolled,
00404                 'bot' => $rcObj->mAttribs['rc_bot'],
00405             ) );
00406             $r .= '&#160;</td><td class="mw-enhanced-rc-nested"><span class="mw-enhanced-rc-time">';
00407 
00408             $params = $queryParams;
00409 
00410             if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
00411                 $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
00412             }
00413 
00414             # Log timestamp
00415             if ( $type == RC_LOG ) {
00416                 $link = $rcObj->timestamp;
00417             # Revision link
00418             } elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
00419                 $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> ';
00420             } else {
00421 
00422                 $link = Linker::linkKnown(
00423                     $rcObj->getTitle(),
00424                     $rcObj->timestamp,
00425                     array(),
00426                     $params
00427                 );
00428                 if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) {
00429                     $link = '<span class="history-deleted">' . $link . '</span> ';
00430                 }
00431             }
00432             $r .= $link . '</span>';
00433 
00434             if ( !$type == RC_LOG || $type == RC_NEW ) {
00435                 $r .= ' ' . $this->msg( 'parentheses' )->rawParams(
00436                     $rcObj->curlink .
00437                         $this->message['pipe-separator'] .
00438                         $rcObj->lastlink
00439                 )->escaped();
00440             }
00441             $r .= ' <span class="mw-changeslist-separator">. .</span> ';
00442 
00443             # Character diff
00444             if ( $RCShowChangedSize ) {
00445                 $cd = $this->formatCharacterDifference( $rcObj );
00446                 if ( $cd !== '' ) {
00447                     $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
00448                 }
00449             }
00450 
00451             if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
00452                 $r .= $this->insertLogEntry( $rcObj );
00453             } else {
00454                 # User links
00455                 $r .= $rcObj->userlink;
00456                 $r .= $rcObj->usertalklink;
00457                 $r .= $this->insertComment( $rcObj );
00458             }
00459 
00460             # Rollback
00461             $this->insertRollback( $r, $rcObj );
00462             # Tags
00463             $this->insertTags( $r, $rcObj, $classes );
00464 
00465             $r .= "</td></tr>\n";
00466         }
00467         $r .= "</table>\n";
00468 
00469         $this->rcCacheIndex++;
00470 
00471         wfProfileOut( __METHOD__ );
00472 
00473         return $r;
00474     }
00475 
00482     protected function recentChangesBlockLine( $rcObj ) {
00483         wfProfileIn( __METHOD__ );
00484         $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
00485 
00486         $type = $rcObj->mAttribs['rc_type'];
00487         $logType = $rcObj->mAttribs['rc_log_type'];
00488         $classes = array( 'mw-enhanced-rc' );
00489         if ( $logType ) {
00490             # Log entry
00491             $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
00492         } else {
00493             $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
00494                 $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
00495         }
00496         $classes[] = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched
00497             ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched';
00498         $r = Html::openElement( 'table', array( 'class' => $classes ) ) .
00499             Html::openElement( 'tr' );
00500 
00501         $r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>';
00502         # Flag and Timestamp
00503         $r .= $this->recentChangesFlags( array(
00504             'newpage' => $type == RC_NEW,
00505             'minor' => $rcObj->mAttribs['rc_minor'],
00506             'unpatrolled' => $rcObj->unpatrolled,
00507             'bot' => $rcObj->mAttribs['rc_bot'],
00508         ) );
00509         $r .= '&#160;' . $rcObj->timestamp . '&#160;</td><td>';
00510         # Article or log link
00511         if ( $logType ) {
00512             $logPage = new LogPage( $logType );
00513             $logTitle = SpecialPage::getTitleFor( 'Log', $logType );
00514             $logName = $logPage->getName()->escaped();
00515             $r .= $this->msg( 'parentheses' )
00516                 ->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped();
00517         } else {
00518             $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched );
00519         }
00520         # Diff and hist links
00521         if ( $type != RC_LOG ) {
00522             $query['action'] = 'history';
00523             $r .= ' ' . $this->msg( 'parentheses' )
00524                 ->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
00525                     $rcObj->getTitle(),
00526                     $this->message['hist'],
00527                     array(),
00528                     $query
00529                 ) )->escaped();
00530         }
00531         $r .= ' <span class="mw-changeslist-separator">. .</span> ';
00532         # Character diff
00533         if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
00534             $cd = $this->formatCharacterDifference( $rcObj );
00535             if ( $cd !== '' ) {
00536                 $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> ';
00537             }
00538         }
00539 
00540         if ( $type == RC_LOG ) {
00541             $r .= $this->insertLogEntry( $rcObj );
00542         } else {
00543             $r .= ' ' . $rcObj->userlink . $rcObj->usertalklink;
00544             $r .= $this->insertComment( $rcObj );
00545             $this->insertRollback( $r, $rcObj );
00546         }
00547 
00548         # Tags
00549         $this->insertTags( $r, $rcObj, $classes );
00550         # Show how many people are watching this if enabled
00551         $r .= $this->numberofWatchingusers( $rcObj->numberofWatchingusers );
00552 
00553         $r .= "</td></tr></table>\n";
00554 
00555         wfProfileOut( __METHOD__ );
00556 
00557         return $r;
00558     }
00559 
00566     protected function recentChangesBlock() {
00567         if ( count( $this->rc_cache ) == 0 ) {
00568             return '';
00569         }
00570 
00571         wfProfileIn( __METHOD__ );
00572 
00573         $blockOut = '';
00574         foreach ( $this->rc_cache as $block ) {
00575             if ( count( $block ) < 2 ) {
00576                 $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
00577             } else {
00578                 $blockOut .= $this->recentChangesBlockGroup( $block );
00579             }
00580         }
00581 
00582         wfProfileOut( __METHOD__ );
00583 
00584         return '<div>' . $blockOut . '</div>';
00585     }
00586 
00592     public function endRecentChangesList() {
00593         return $this->recentChangesBlock() . '</div>';
00594     }
00595 }