MediaWiki
REL1_19
|
00001 <?php 00026 class LogEventsList { 00027 const NO_ACTION_LINK = 1; 00028 const NO_EXTRA_USER_LINKS = 2; 00029 00033 private $skin; 00034 00038 private $out; 00039 public $flags; 00040 00044 protected $message; 00045 00049 protected $mDefaultQuery; 00050 00051 public function __construct( $skin, $out, $flags = 0 ) { 00052 $this->skin = $skin; 00053 $this->out = $out; 00054 $this->flags = $flags; 00055 $this->preCacheMessages(); 00056 } 00057 00062 private function preCacheMessages() { 00063 // Precache various messages 00064 if( !isset( $this->message ) ) { 00065 $messages = array( 'revertmerge', 'protect_change', 'unblocklink', 'change-blocklink', 00066 'revertmove', 'undeletelink', 'undeleteviewlink', 'revdel-restore', 'hist', 'diff', 00067 'pipe-separator', 'revdel-restore-deleted', 'revdel-restore-visible' ); 00068 foreach( $messages as $msg ) { 00069 $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) ); 00070 } 00071 } 00072 } 00073 00079 public function showHeader( $type ) { 00080 wfDeprecated( __METHOD__, '1.19' ); 00081 // If only one log type is used, then show a special message... 00082 $headerType = (count($type) == 1) ? $type[0] : ''; 00083 if( LogPage::isLogType( $headerType ) ) { 00084 $page = new LogPage( $headerType ); 00085 $this->out->setPageTitle( $page->getName()->text() ); 00086 $this->out->addHTML( $page->getDescription()->parseAsBlock() ); 00087 } else { 00088 $this->out->addHTML( wfMsgExt('alllogstext',array('parseinline')) ); 00089 } 00090 } 00091 00104 public function showOptions( $types=array(), $user='', $page='', $pattern='', $year='', 00105 $month = '', $filter = null, $tagFilter='' ) { 00106 global $wgScript, $wgMiserMode; 00107 00108 $action = $wgScript; 00109 $title = SpecialPage::getTitleFor( 'Log' ); 00110 $special = $title->getPrefixedDBkey(); 00111 00112 // For B/C, we take strings, but make sure they are converted... 00113 $types = ($types === '') ? array() : (array)$types; 00114 00115 $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter ); 00116 00117 $html = Html::hidden( 'title', $special ); 00118 00119 // Basic selectors 00120 $html .= $this->getTypeMenu( $types ) . "\n"; 00121 $html .= $this->getUserInput( $user ) . "\n"; 00122 $html .= $this->getTitleInput( $page ) . "\n"; 00123 $html .= $this->getExtraInputs( $types ) . "\n"; 00124 00125 // Title pattern, if allowed 00126 if (!$wgMiserMode) { 00127 $html .= $this->getTitlePattern( $pattern ) . "\n"; 00128 } 00129 00130 // date menu 00131 $html .= Xml::tags( 'p', null, Xml::dateMenu( $year, $month ) ); 00132 00133 // Tag filter 00134 if ($tagSelector) { 00135 $html .= Xml::tags( 'p', null, implode( ' ', $tagSelector ) ); 00136 } 00137 00138 // Filter links 00139 if ($filter) { 00140 $html .= Xml::tags( 'p', null, $this->getFilterLinks( $filter ) ); 00141 } 00142 00143 // Submit button 00144 $html .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); 00145 00146 // Fieldset 00147 $html = Xml::fieldset( wfMsg( 'log' ), $html ); 00148 00149 // Form wrapping 00150 $html = Xml::tags( 'form', array( 'action' => $action, 'method' => 'get' ), $html ); 00151 00152 $this->out->addHTML( $html ); 00153 } 00154 00159 private function getFilterLinks( $filter ) { 00160 global $wgLang; 00161 // show/hide links 00162 $messages = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); 00163 // Option value -> message mapping 00164 $links = array(); 00165 $hiddens = ''; // keep track for "go" button 00166 foreach( $filter as $type => $val ) { 00167 // Should the below assignment be outside the foreach? 00168 // Then it would have to be copied. Not certain what is more expensive. 00169 $query = $this->getDefaultQuery(); 00170 $queryKey = "hide_{$type}_log"; 00171 00172 $hideVal = 1 - intval($val); 00173 $query[$queryKey] = $hideVal; 00174 00175 $link = Linker::link( 00176 $this->getDisplayTitle(), 00177 $messages[$hideVal], 00178 array(), 00179 $query, 00180 array( 'known', 'noclasses' ) 00181 ); 00182 00183 $links[$type] = wfMsgHtml( "log-show-hide-{$type}", $link ); 00184 $hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n"; 00185 } 00186 // Build links 00187 return '<small>'.$wgLang->pipeList( $links ) . '</small>' . $hiddens; 00188 } 00189 00190 private function getDefaultQuery() { 00191 global $wgRequest; 00192 00193 if ( !isset( $this->mDefaultQuery ) ) { 00194 $this->mDefaultQuery = $wgRequest->getQueryValues(); 00195 unset( $this->mDefaultQuery['title'] ); 00196 unset( $this->mDefaultQuery['dir'] ); 00197 unset( $this->mDefaultQuery['offset'] ); 00198 unset( $this->mDefaultQuery['limit'] ); 00199 unset( $this->mDefaultQuery['order'] ); 00200 unset( $this->mDefaultQuery['month'] ); 00201 unset( $this->mDefaultQuery['year'] ); 00202 } 00203 return $this->mDefaultQuery; 00204 } 00205 00212 public function getDisplayTitle() { 00213 return $this->out->getTitle(); 00214 } 00215 00216 public function getContext() { 00217 return $this->out->getContext(); 00218 } 00219 00224 private function getTypeMenu( $queryTypes ) { 00225 $queryType = count($queryTypes) == 1 ? $queryTypes[0] : ''; 00226 $selector = $this->getTypeSelector(); 00227 $selector->setDefault( $queryType ); 00228 return $selector->getHtml(); 00229 } 00230 00236 public function getTypeSelector() { 00237 global $wgUser; 00238 00239 $typesByName = array(); // Temporary array 00240 // First pass to load the log names 00241 foreach( LogPage::validTypes() as $type ) { 00242 $page = new LogPage( $type ); 00243 $restriction = $page->getRestriction(); 00244 if ( $wgUser->isAllowed( $restriction ) ) { 00245 $typesByName[$type] = $page->getName()->text(); 00246 } 00247 } 00248 00249 // Second pass to sort by name 00250 asort($typesByName); 00251 00252 // Always put "All public logs" on top 00253 $public = $typesByName['']; 00254 unset( $typesByName[''] ); 00255 $typesByName = array( '' => $public ) + $typesByName; 00256 00257 $select = new XmlSelect( 'type' ); 00258 foreach( $typesByName as $type => $name ) { 00259 $select->addOption( $name, $type ); 00260 } 00261 00262 return $select; 00263 } 00264 00269 private function getUserInput( $user ) { 00270 return '<span style="white-space: nowrap">' . 00271 Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'mw-log-user', 15, $user ) . 00272 '</span>'; 00273 } 00274 00279 private function getTitleInput( $title ) { 00280 return '<span style="white-space: nowrap">' . 00281 Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'mw-log-page', 20, $title ) . 00282 '</span>'; 00283 } 00284 00289 private function getTitlePattern( $pattern ) { 00290 return '<span style="white-space: nowrap">' . 00291 Xml::checkLabel( wfMsg( 'log-title-wildcard' ), 'pattern', 'pattern', $pattern ) . 00292 '</span>'; 00293 } 00294 00299 private function getExtraInputs( $types ) { 00300 global $wgRequest; 00301 $offender = $wgRequest->getVal('offender'); 00302 $user = User::newFromName( $offender, false ); 00303 if( !$user || ($user->getId() == 0 && !IP::isIPAddress($offender) ) ) { 00304 $offender = ''; // Blank field if invalid 00305 } 00306 if( count($types) == 1 && $types[0] == 'suppress' ) { 00307 return Xml::inputLabel( wfMsg('revdelete-offender'), 'offender', 00308 'mw-log-offender', 20, $offender ); 00309 } 00310 return ''; 00311 } 00312 00316 public function beginLogEventsList() { 00317 return "<ul>\n"; 00318 } 00319 00323 public function endLogEventsList() { 00324 return "</ul>\n"; 00325 } 00326 00331 public function logLine( $row ) { 00332 $entry = DatabaseLogEntry::newFromRow( $row ); 00333 $formatter = LogFormatter::newFromEntry( $entry ); 00334 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) ); 00335 00336 $action = $formatter->getActionText(); 00337 $comment = $formatter->getComment(); 00338 00339 $classes = array( 'mw-logline-' . $entry->getType() ); 00340 $title = $entry->getTarget(); 00341 $time = $this->logTimestamp( $entry ); 00342 00343 // Extract extra parameters 00344 $paramArray = LogPage::extractParams( $row->log_params ); 00345 // Add review/revert links and such... 00346 $revert = $this->logActionLinks( $row, $title, $paramArray, $comment ); 00347 00348 // Some user can hide log items and have review links 00349 $del = $this->getShowHideLinks( $row ); 00350 if( $del != '' ) $del .= ' '; 00351 00352 // Any tags... 00353 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' ); 00354 $classes = array_merge( $classes, $newClasses ); 00355 00356 return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ), 00357 $del . "$time $action $comment $revert $tagDisplay" ) . "\n"; 00358 } 00359 00360 private function logTimestamp( LogEntry $entry ) { 00361 global $wgLang; 00362 $time = $wgLang->timeanddate( wfTimestamp( TS_MW, $entry->getTimestamp() ), true ); 00363 return htmlspecialchars( $time ); 00364 } 00365 00375 private function logActionLinks( $row, $title, $paramArray, &$comment ) { 00376 global $wgUser; 00377 if( ( $this->flags & self::NO_ACTION_LINK ) // we don't want to see the action 00378 || self::isDeleted( $row, LogPage::DELETED_ACTION ) ) // action is hidden 00379 { 00380 return ''; 00381 } 00382 $revert = ''; 00383 if( self::typeAction( $row, 'move', 'move', 'move' ) && !empty( $paramArray[0] ) ) { 00384 $destTitle = Title::newFromText( $paramArray[0] ); 00385 if( $destTitle ) { 00386 $revert = '(' . Linker::link( 00387 SpecialPage::getTitleFor( 'Movepage' ), 00388 $this->message['revertmove'], 00389 array(), 00390 array( 00391 'wpOldTitle' => $destTitle->getPrefixedDBkey(), 00392 'wpNewTitle' => $title->getPrefixedDBkey(), 00393 'wpReason' => wfMsgForContent( 'revertmove' ), 00394 'wpMovetalk' => 0 00395 ), 00396 array( 'known', 'noclasses' ) 00397 ) . ')'; 00398 } 00399 // Show undelete link 00400 } elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'delete', 'deletedhistory' ) ) { 00401 if( !$wgUser->isAllowed( 'undelete' ) ) { 00402 $viewdeleted = $this->message['undeleteviewlink']; 00403 } else { 00404 $viewdeleted = $this->message['undeletelink']; 00405 } 00406 $revert = '(' . Linker::link( 00407 SpecialPage::getTitleFor( 'Undelete' ), 00408 $viewdeleted, 00409 array(), 00410 array( 'target' => $title->getPrefixedDBkey() ), 00411 array( 'known', 'noclasses' ) 00412 ) . ')'; 00413 // Show unblock/change block link 00414 } elseif( self::typeAction( $row, array( 'block', 'suppress' ), array( 'block', 'reblock' ), 'block' ) ) { 00415 $revert = '(' . 00416 Linker::link( 00417 SpecialPage::getTitleFor( 'Unblock', $row->log_title ), 00418 $this->message['unblocklink'], 00419 array(), 00420 array(), 00421 'known' 00422 ) . 00423 $this->message['pipe-separator'] . 00424 Linker::link( 00425 SpecialPage::getTitleFor( 'Block', $row->log_title ), 00426 $this->message['change-blocklink'], 00427 array(), 00428 array(), 00429 'known' 00430 ) . 00431 ')'; 00432 // Show change protection link 00433 } elseif( self::typeAction( $row, 'protect', array( 'modify', 'protect', 'unprotect' ) ) ) { 00434 $revert .= ' (' . 00435 Linker::link( $title, 00436 $this->message['hist'], 00437 array(), 00438 array( 00439 'action' => 'history', 00440 'offset' => $row->log_timestamp 00441 ) 00442 ); 00443 if( $wgUser->isAllowed( 'protect' ) ) { 00444 $revert .= $this->message['pipe-separator'] . 00445 Linker::link( $title, 00446 $this->message['protect_change'], 00447 array(), 00448 array( 'action' => 'protect' ), 00449 'known' ); 00450 } 00451 $revert .= ')'; 00452 // Show unmerge link 00453 } elseif( self::typeAction( $row, 'merge', 'merge', 'mergehistory' ) ) { 00454 $revert = '(' . Linker::link( 00455 SpecialPage::getTitleFor( 'MergeHistory' ), 00456 $this->message['revertmerge'], 00457 array(), 00458 array( 00459 'target' => $paramArray[0], 00460 'dest' => $title->getPrefixedDBkey(), 00461 'mergepoint' => $paramArray[1] 00462 ), 00463 array( 'known', 'noclasses' ) 00464 ) . ')'; 00465 // If an edit was hidden from a page give a review link to the history 00466 } elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'revision', 'deletedhistory' ) ) { 00467 $revert = RevisionDeleter::getLogLinks( $title, $paramArray, 00468 $this->message ); 00469 // Hidden log items, give review link 00470 } elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'event', 'deletedhistory' ) ) { 00471 if( count($paramArray) >= 1 ) { 00472 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); 00473 // $paramArray[1] is a CSV of the IDs 00474 $query = $paramArray[0]; 00475 // Link to each hidden object ID, $paramArray[1] is the url param 00476 $revert = '(' . Linker::link( 00477 $revdel, 00478 $this->message['revdel-restore'], 00479 array(), 00480 array( 00481 'target' => $title->getPrefixedText(), 00482 'type' => 'logging', 00483 'ids' => $query 00484 ), 00485 array( 'known', 'noclasses' ) 00486 ) . ')'; 00487 } 00488 // Do nothing. The implementation is handled by the hook modifiying the passed-by-ref parameters. 00489 } else { 00490 wfRunHooks( 'LogLine', array( $row->log_type, $row->log_action, $title, $paramArray, 00491 &$comment, &$revert, $row->log_timestamp ) ); 00492 } 00493 if( $revert != '' ) { 00494 $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>'; 00495 } 00496 return $revert; 00497 } 00498 00503 private function getShowHideLinks( $row ) { 00504 global $wgUser; 00505 if( ( $this->flags & self::NO_ACTION_LINK ) // we don't want to see the links 00506 || $row->log_type == 'suppress' ) { // no one can hide items from the suppress log 00507 return ''; 00508 } 00509 $del = ''; 00510 // Don't show useless link to people who cannot hide revisions 00511 if( $wgUser->isAllowed( 'deletedhistory' ) ) { 00512 if( $row->log_deleted || $wgUser->isAllowed( 'deleterevision' ) ) { 00513 $canHide = $wgUser->isAllowed( 'deleterevision' ); 00514 // If event was hidden from sysops 00515 if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) { 00516 $del = Linker::revDeleteLinkDisabled( $canHide ); 00517 } else { 00518 $target = SpecialPage::getTitleFor( 'Log', $row->log_type ); 00519 $query = array( 00520 'target' => $target->getPrefixedDBkey(), 00521 'type' => 'logging', 00522 'ids' => $row->log_id, 00523 ); 00524 $del = Linker::revDeleteLink( $query, 00525 self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide ); 00526 } 00527 } 00528 } 00529 return $del; 00530 } 00531 00539 public static function typeAction( $row, $type, $action, $right='' ) { 00540 $match = is_array($type) ? 00541 in_array( $row->log_type, $type ) : $row->log_type == $type; 00542 if( $match ) { 00543 $match = is_array( $action ) ? 00544 in_array( $row->log_action, $action ) : $row->log_action == $action; 00545 if( $match && $right ) { 00546 global $wgUser; 00547 $match = $wgUser->isAllowed( $right ); 00548 } 00549 } 00550 return $match; 00551 } 00552 00562 public static function userCan( $row, $field, User $user = null ) { 00563 return self::userCanBitfield( $row->log_deleted, $field, $user ); 00564 } 00565 00575 public static function userCanBitfield( $bitfield, $field, User $user = null ) { 00576 if( $bitfield & $field ) { 00577 if ( $bitfield & LogPage::DELETED_RESTRICTED ) { 00578 $permission = 'suppressrevision'; 00579 } else { 00580 $permission = 'deletedhistory'; 00581 } 00582 wfDebug( "Checking for $permission due to $field match on $bitfield\n" ); 00583 if ( $user === null ) { 00584 global $wgUser; 00585 $user = $wgUser; 00586 } 00587 return $user->isAllowed( $permission ); 00588 } else { 00589 return true; 00590 } 00591 } 00592 00598 public static function isDeleted( $row, $field ) { 00599 return ( $row->log_deleted & $field ) == $field; 00600 } 00601 00623 public static function showLogExtract( 00624 &$out, $types=array(), $page='', $user='', $param = array() 00625 ) { 00626 $defaultParameters = array( 00627 'lim' => 25, 00628 'conds' => array(), 00629 'showIfEmpty' => true, 00630 'msgKey' => array(''), 00631 'wrap' => "$1", 00632 'flags' => 0 00633 ); 00634 # The + operator appends elements of remaining keys from the right 00635 # handed array to the left handed, whereas duplicated keys are NOT overwritten. 00636 $param += $defaultParameters; 00637 # Convert $param array to individual variables 00638 $lim = $param['lim']; 00639 $conds = $param['conds']; 00640 $showIfEmpty = $param['showIfEmpty']; 00641 $msgKey = $param['msgKey']; 00642 $wrap = $param['wrap']; 00643 $flags = $param['flags']; 00644 if ( !is_array( $msgKey ) ) { 00645 $msgKey = array( $msgKey ); 00646 } 00647 00648 if ( $out instanceof OutputPage ) { 00649 $context = $out->getContext(); 00650 } else { 00651 $context = RequestContext::getMain(); 00652 } 00653 00654 # Insert list of top 50 (or top $lim) items 00655 $loglist = new LogEventsList( $context->getSkin(), $context->getOutput(), $flags ); 00656 $pager = new LogPager( $loglist, $types, $user, $page, '', $conds ); 00657 if ( isset( $param['offset'] ) ) { # Tell pager to ignore $wgRequest offset 00658 $pager->setOffset( $param['offset'] ); 00659 } 00660 if( $lim > 0 ) $pager->mLimit = $lim; 00661 $logBody = $pager->getBody(); 00662 $s = ''; 00663 if( $logBody ) { 00664 if ( $msgKey[0] ) { 00665 $s = '<div class="mw-warning-with-logexcerpt">'; 00666 00667 if ( count( $msgKey ) == 1 ) { 00668 $s .= wfMsgExt( $msgKey[0], array( 'parse' ) ); 00669 } else { // Process additional arguments 00670 $args = $msgKey; 00671 array_shift( $args ); 00672 $s .= wfMsgExt( $msgKey[0], array( 'parse' ), $args ); 00673 } 00674 } 00675 $s .= $loglist->beginLogEventsList() . 00676 $logBody . 00677 $loglist->endLogEventsList(); 00678 } else { 00679 if ( $showIfEmpty ) { 00680 $s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ), 00681 wfMsgExt( 'logempty', array( 'parseinline' ) ) ); 00682 } 00683 } 00684 if( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link 00685 $urlParam = array(); 00686 if ( $page instanceof Title ) { 00687 $urlParam['page'] = $page->getPrefixedDBkey(); 00688 } elseif ( $page != '' ) { 00689 $urlParam['page'] = $page; 00690 } 00691 if ( $user != '') 00692 $urlParam['user'] = $user; 00693 if ( !is_array( $types ) ) # Make it an array, if it isn't 00694 $types = array( $types ); 00695 # If there is exactly one log type, we can link to Special:Log?type=foo 00696 if ( count( $types ) == 1 ) 00697 $urlParam['type'] = $types[0]; 00698 $s .= Linker::link( 00699 SpecialPage::getTitleFor( 'Log' ), 00700 wfMsgHtml( 'log-fulllog' ), 00701 array(), 00702 $urlParam 00703 ); 00704 } 00705 if ( $logBody && $msgKey[0] ) { 00706 $s .= '</div>'; 00707 } 00708 00709 if ( $wrap != '' ) { // Wrap message in html 00710 $s = str_replace( '$1', $s, $wrap ); 00711 } 00712 00713 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */ 00714 if ( wfRunHooks( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) { 00715 // $out can be either an OutputPage object or a String-by-reference 00716 if ( $out instanceof OutputPage ){ 00717 $out->addHTML( $s ); 00718 } else { 00719 $out = $s; 00720 } 00721 } 00722 00723 return $pager->getNumRows(); 00724 } 00725 00733 public static function getExcludeClause( $db, $audience = 'public' ) { 00734 global $wgLogRestrictions, $wgUser; 00735 // Reset the array, clears extra "where" clauses when $par is used 00736 $hiddenLogs = array(); 00737 // Don't show private logs to unprivileged users 00738 foreach( $wgLogRestrictions as $logType => $right ) { 00739 if( $audience == 'public' || !$wgUser->isAllowed($right) ) { 00740 $safeType = $db->strencode( $logType ); 00741 $hiddenLogs[] = $safeType; 00742 } 00743 } 00744 if( count($hiddenLogs) == 1 ) { 00745 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] ); 00746 } elseif( $hiddenLogs ) { 00747 return 'log_type NOT IN (' . $db->makeList($hiddenLogs) . ')'; 00748 } 00749 return false; 00750 } 00751 }