00001 <?php
00002 # Copyright (C) 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
00003 # http://www.mediawiki.org/
00004 #
00005 # This program is free software; you can redistribute it and/or modify
00006 # it under the terms of the GNU General Public License as published by
00007 # the Free Software Foundation; either version 2 of the License, or
00008 # (at your option) any later version.
00009 #
00010 # This program is distributed in the hope that it will be useful,
00011 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00013 # GNU General Public License for more details.
00014 #
00015 # You should have received a copy of the GNU General Public License along
00016 # with this program; if not, write to the Free Software Foundation, Inc.,
00017 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00018 # http://www.gnu.org/copyleft/gpl.html
00019
00020 class LogEventsList {
00021 const NO_ACTION_LINK = 1;
00022
00023 private $skin;
00024 private $out;
00025 public $flags;
00026
00027 public function __construct( $skin, $out, $flags = 0 ) {
00028 $this->skin = $skin;
00029 $this->out = $out;
00030 $this->flags = $flags;
00031 $this->preCacheMessages();
00032 }
00033
00038 private function preCacheMessages() {
00039
00040 if( !isset( $this->message ) ) {
00041 $messages = array( 'revertmerge', 'protect_change', 'unblocklink', 'change-blocklink',
00042 'revertmove', 'undeletelink', 'undeleteviewlink', 'revdel-restore', 'hist', 'diff',
00043 'pipe-separator' );
00044 foreach( $messages as $msg ) {
00045 $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
00046 }
00047 }
00048 }
00049
00054 public function showHeader( $type ) {
00055
00056 $headerType = (count($type) == 1) ? $type[0] : '';
00057 if( LogPage::isLogType( $headerType ) ) {
00058 $this->out->setPageTitle( LogPage::logName( $headerType ) );
00059 $this->out->addHTML( LogPage::logHeader( $headerType ) );
00060 } else {
00061 $this->out->addHTML( wfMsgExt('alllogstext',array('parseinline')) );
00062 }
00063 }
00064
00076 public function showOptions( $types=array(), $user='', $page='', $pattern='', $year='',
00077 $month = '', $filter = null, $tagFilter='' )
00078 {
00079 global $wgScript, $wgMiserMode;
00080
00081 $action = $wgScript;
00082 $title = SpecialPage::getTitleFor( 'Log' );
00083 $special = $title->getPrefixedDBkey();
00084
00085
00086 $types = ($types === '') ? array() : (array)$types;
00087
00088 $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
00089
00090 $html = '';
00091 $html .= Xml::hidden( 'title', $special );
00092
00093
00094 $html .= $this->getTypeMenu( $types ) . "\n";
00095 $html .= $this->getUserInput( $user ) . "\n";
00096 $html .= $this->getTitleInput( $page ) . "\n";
00097 $html .= $this->getExtraInputs( $types ) . "\n";
00098
00099
00100 if (!$wgMiserMode) {
00101 $html .= $this->getTitlePattern( $pattern ) . "\n";
00102 }
00103
00104
00105 $html .= Xml::tags( 'p', null, Xml::dateMenu( $year, $month ) );
00106
00107
00108 if ($tagSelector) {
00109 $html .= Xml::tags( 'p', null, implode( ' ', $tagSelector ) );
00110 }
00111
00112
00113 if ($filter) {
00114 $html .= Xml::tags( 'p', null, $this->getFilterLinks( $filter ) );
00115 }
00116
00117
00118 $html .= Xml::submitButton( wfMsg( 'allpagessubmit' ) );
00119
00120
00121 $html = Xml::fieldset( wfMsg( 'log' ), $html );
00122
00123
00124 $html = Xml::tags( 'form', array( 'action' => $action, 'method' => 'get' ), $html );
00125
00126 $this->out->addHTML( $html );
00127 }
00128
00133 private function getFilterLinks( $filter ) {
00134 global $wgTitle, $wgLang;
00135
00136 $messages = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
00137
00138 $links = array();
00139 $hiddens = '';
00140 foreach( $filter as $type => $val ) {
00141
00142
00143 $query = $this->getDefaultQuery();
00144 $queryKey = "hide_{$type}_log";
00145
00146 $hideVal = 1 - intval($val);
00147 $query[$queryKey] = $hideVal;
00148
00149 $link = $this->skin->link(
00150 $wgTitle,
00151 $messages[$hideVal],
00152 array(),
00153 $query,
00154 array( 'known', 'noclasses' )
00155 );
00156
00157 $links[$type] = wfMsgHtml( "log-show-hide-{$type}", $link );
00158 $hiddens .= Xml::hidden( "hide_{$type}_log", $val ) . "\n";
00159 }
00160
00161 return '<small>'.$wgLang->pipeList( $links ) . '</small>' . $hiddens;
00162 }
00163
00164 private function getDefaultQuery() {
00165 if ( !isset( $this->mDefaultQuery ) ) {
00166 $this->mDefaultQuery = $_GET;
00167 unset( $this->mDefaultQuery['title'] );
00168 unset( $this->mDefaultQuery['dir'] );
00169 unset( $this->mDefaultQuery['offset'] );
00170 unset( $this->mDefaultQuery['limit'] );
00171 unset( $this->mDefaultQuery['order'] );
00172 unset( $this->mDefaultQuery['month'] );
00173 unset( $this->mDefaultQuery['year'] );
00174 }
00175 return $this->mDefaultQuery;
00176 }
00177
00182 private function getTypeMenu( $queryTypes ) {
00183 global $wgLogRestrictions, $wgUser;
00184
00185 $html = "<select name='type'>\n";
00186
00187 $validTypes = LogPage::validTypes();
00188 $typesByName = array();
00189
00190
00191 foreach( $validTypes as $type ) {
00192 $text = LogPage::logName( $type );
00193 $typesByName[$type] = $text;
00194 }
00195
00196
00197 asort($typesByName);
00198
00199
00200 $queryType = count($queryTypes) == 1 ? $queryTypes[0] : '';
00201
00202 foreach( $typesByName as $type => $text ) {
00203 $selected = ($type == $queryType);
00204
00205 if ( isset($wgLogRestrictions[$type]) ) {
00206 if ( $wgUser->isAllowed( $wgLogRestrictions[$type] ) ) {
00207 $html .= Xml::option( $text, $type, $selected ) . "\n";
00208 }
00209 } else {
00210 $html .= Xml::option( $text, $type, $selected ) . "\n";
00211 }
00212 }
00213
00214 $html .= '</select>';
00215 return $html;
00216 }
00217
00222 private function getUserInput( $user ) {
00223 return '<span style="white-space: nowrap">' .
00224 Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'mw-log-user', 15, $user ) .
00225 '</span>';
00226 }
00227
00232 private function getTitleInput( $title ) {
00233 return '<span style="white-space: nowrap">' .
00234 Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'mw-log-page', 20, $title ) .
00235 '</span>';
00236 }
00237
00241 private function getTitlePattern( $pattern ) {
00242 return '<span style="white-space: nowrap">' .
00243 Xml::checkLabel( wfMsg( 'log-title-wildcard' ), 'pattern', 'pattern', $pattern ) .
00244 '</span>';
00245 }
00246
00247 private function getExtraInputs( $types ) {
00248 global $wgRequest;
00249 $offender = $wgRequest->getVal('offender');
00250 $user = User::newFromName( $offender, false );
00251 if( !$user || ($user->getId() == 0 && !IP::isIPAddress($offender) ) ) {
00252 $offender = '';
00253 }
00254 if( count($types) == 1 && $types[0] == 'suppress' ) {
00255 return Xml::inputLabel( wfMsg('revdelete-offender'), 'offender',
00256 'mw-log-offender', 20, $offender );
00257 }
00258 return '';
00259 }
00260
00261 public function beginLogEventsList() {
00262 return "<ul>\n";
00263 }
00264
00265 public function endLogEventsList() {
00266 return "</ul>\n";
00267 }
00268
00273 public function logLine( $row ) {
00274 global $wgLang, $wgUser, $wgContLang;
00275
00276 $title = Title::makeTitle( $row->log_namespace, $row->log_title );
00277 $classes = array( "mw-logline-{$row->log_type}" );
00278 $time = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->log_timestamp ), true );
00279
00280 if( self::isDeleted( $row, LogPage::DELETED_USER ) ) {
00281 $userLink = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
00282 } else {
00283 $userLink = $this->skin->userLink( $row->log_user, $row->user_name ) .
00284 $this->skin->userToolLinks( $row->log_user, $row->user_name, true, 0, $row->user_editcount );
00285 }
00286
00287 if( self::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
00288 $comment = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
00289 } else {
00290 $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $row->log_comment );
00291 }
00292
00293 $paramArray = LogPage::extractParams( $row->log_params );
00294 $revert = $del = '';
00295
00296 if( !( $this->flags & self::NO_ACTION_LINK ) && $wgUser->isAllowed( 'deletedhistory' ) ) {
00297
00298 if( $row->log_deleted || $wgUser->isAllowed( 'deleterevision' ) ) {
00299 $del = $this->getShowHideLinks( $row ) . ' ';
00300 }
00301 }
00302
00303 if( ( $this->flags & self::NO_ACTION_LINK ) || ( $row->log_deleted & LogPage::DELETED_ACTION ) ) {
00304
00305 } else if( self::typeAction( $row, 'move', 'move', 'move' ) && !empty( $paramArray[0] ) ) {
00306 $destTitle = Title::newFromText( $paramArray[0] );
00307 if( $destTitle ) {
00308 $revert = '(' . $this->skin->link(
00309 SpecialPage::getTitleFor( 'Movepage' ),
00310 $this->message['revertmove'],
00311 array(),
00312 array(
00313 'wpOldTitle' => $destTitle->getPrefixedDBkey(),
00314 'wpNewTitle' => $title->getPrefixedDBkey(),
00315 'wpReason' => wfMsgForContent( 'revertmove' ),
00316 'wpMovetalk' => 0
00317 ),
00318 array( 'known', 'noclasses' )
00319 ) . ')';
00320 }
00321
00322 } else if( self::typeAction( $row, array( 'delete', 'suppress' ), 'delete', 'deletedhistory' ) ) {
00323 if( !$wgUser->isAllowed( 'undelete' ) ) {
00324 $viewdeleted = $this->message['undeleteviewlink'];
00325 } else {
00326 $viewdeleted = $this->message['undeletelink'];
00327 }
00328
00329 $revert = '(' . $this->skin->link(
00330 SpecialPage::getTitleFor( 'Undelete' ),
00331 $viewdeleted,
00332 array(),
00333 array( 'target' => $title->getPrefixedDBkey() ),
00334 array( 'known', 'noclasses' )
00335 ) . ')';
00336
00337 } else if( self::typeAction( $row, array( 'block', 'suppress' ), array( 'block', 'reblock' ), 'block' ) ) {
00338 $revert = '(' .
00339 $this->skin->link(
00340 SpecialPage::getTitleFor( 'Ipblocklist' ),
00341 $this->message['unblocklink'],
00342 array(),
00343 array(
00344 'action' => 'unblock',
00345 'ip' => $row->log_title
00346 ),
00347 'known'
00348 ) .
00349 $this->message['pipe-separator'] .
00350 $this->skin->link(
00351 SpecialPage::getTitleFor( 'Blockip', $row->log_title ),
00352 $this->message['change-blocklink'],
00353 array(),
00354 array(),
00355 'known'
00356 ) .
00357 ')';
00358
00359 } else if( self::typeAction( $row, 'protect', array( 'modify', 'protect', 'unprotect' ) ) ) {
00360 $revert .= ' (' .
00361 $this->skin->link( $title,
00362 $this->message['hist'],
00363 array(),
00364 array(
00365 'action' => 'history',
00366 'offset' => $row->log_timestamp
00367 )
00368 );
00369 if( $wgUser->isAllowed( 'protect' ) ) {
00370 $revert .= $this->message['pipe-separator'] .
00371 $this->skin->link( $title,
00372 $this->message['protect_change'],
00373 array(),
00374 array( 'action' => 'protect' ),
00375 'known' );
00376 }
00377 $revert .= ')';
00378
00379 } else if( self::typeAction( $row, 'merge', 'merge', 'mergehistory' ) ) {
00380 $merge = SpecialPage::getTitleFor( 'Mergehistory' );
00381 $revert = '(' . $this->skin->link(
00382 $merge,
00383 $this->message['revertmerge'],
00384 array(),
00385 array(
00386 'target' => $paramArray[0],
00387 'dest' => $title->getPrefixedDBkey(),
00388 'mergepoint' => $paramArray[1]
00389 ),
00390 array( 'known', 'noclasses' )
00391 ) . ')';
00392
00393 } else if( self::typeAction( $row, array( 'delete', 'suppress' ), 'revision', 'deletedhistory' ) ) {
00394 if( count($paramArray) >= 2 ) {
00395
00396 $key = $paramArray[0];
00397
00398 $Ids = explode( ',', $paramArray[1] );
00399 $query = $paramArray[1];
00400 $revert = array();
00401
00402 if( count($Ids) == 1 ) {
00403
00404 if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
00405 $revert[] = $this->skin->link(
00406 $title,
00407 $this->message['diff'],
00408 array(),
00409 array(
00410 'diff' => intval( $Ids[0] ),
00411 'unhide' => 1
00412 ),
00413 array( 'known', 'noclasses' )
00414 );
00415
00416 } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
00417 $revert[] = $this->skin->link(
00418 SpecialPage::getTitleFor( 'Undelete' ),
00419 $this->message['diff'],
00420 array(),
00421 array(
00422 'target' => $title->getPrefixedDBKey(),
00423 'diff' => 'prev',
00424 'timestamp' => $Ids[0]
00425 ),
00426 array( 'known', 'noclasses' )
00427 );
00428 }
00429 }
00430
00431 $revert[] = $this->skin->link(
00432 SpecialPage::getTitleFor( 'Revisiondelete' ),
00433 $this->message['revdel-restore'],
00434 array(),
00435 array(
00436 'target' => $title->getPrefixedText(),
00437 'type' => $key,
00438 'ids' => $query
00439 ),
00440 array( 'known', 'noclasses' )
00441 );
00442
00443 $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
00444 }
00445
00446 } else if( self::typeAction( $row, array( 'delete', 'suppress' ), 'event', 'deletedhistory' ) ) {
00447 if( count($paramArray) >= 1 ) {
00448 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
00449
00450 $Ids = explode( ',', $paramArray[0] );
00451 $query = $paramArray[0];
00452
00453 $revert = '(' . $this->skin->link(
00454 $revdel,
00455 $this->message['revdel-restore'],
00456 array(),
00457 array(
00458 'target' => $title->getPrefixedText(),
00459 'type' => 'logging',
00460 'ids' => $query
00461 ),
00462 array( 'known', 'noclasses' )
00463 ) . ')';
00464 }
00465
00466 } else if( self::typeAction( $row, 'newusers', 'create2' ) ) {
00467 if( isset( $paramArray[0] ) ) {
00468 $revert = $this->skin->userToolLinks( $paramArray[0], $title->getDBkey(), true );
00469 } else {
00470 # Fall back to a blue contributions link
00471 $revert = $this->skin->userToolLinks( 1, $title->getDBkey() );
00472 }
00473 if( $time < '20080129000000' ) {
00474 # Suppress $comment from old entries (before 2008-01-29),
00475 # not needed and can contain incorrect links
00476 $comment = '';
00477 }
00478
00479 } else {
00480 wfRunHooks( 'LogLine', array( $row->log_type, $row->log_action, $title, $paramArray,
00481 &$comment, &$revert, $row->log_timestamp ) );
00482 }
00483
00484 if( self::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
00485 $action = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
00486 } else {
00487 $action = LogPage::actionText( $row->log_type, $row->log_action, $title,
00488 $this->skin, $paramArray, true );
00489 }
00490
00491
00492 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' );
00493 $classes = array_merge( $classes, $newClasses );
00494
00495 if( $revert != '' ) {
00496 $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
00497 }
00498
00499 $time = htmlspecialchars( $time );
00500
00501 return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ),
00502 $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert . " $tagDisplay" ) . "\n";
00503 }
00504
00509 private function getShowHideLinks( $row ) {
00510 global $wgUser;
00511 if( $row->log_type == 'suppress' ) {
00512 return '';
00513 }
00514 $canHide = $wgUser->isAllowed( 'deleterevision' );
00515
00516 if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) {
00517 $del = $this->skin->revDeleteLinkDisabled( $canHide );
00518 } else {
00519 $target = SpecialPage::getTitleFor( 'Log', $row->log_type );
00520 $page = Title::makeTitle( $row->log_namespace, $row->log_title );
00521 $query = array(
00522 'target' => $target->getPrefixedDBkey(),
00523 'type' => 'logging',
00524 'ids' => $row->log_id,
00525 );
00526 $del = $this->skin->revDeleteLink( $query,
00527 self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide );
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
00560 public static function userCan( $row, $field ) {
00561 return self::userCanBitfield( $row->log_deleted, $field );
00562 }
00563
00571 public static function userCanBitfield( $bitfield, $field ) {
00572 if( $bitfield & $field ) {
00573 global $wgUser;
00574 $permission = '';
00575 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
00576 $permission = 'suppressrevision';
00577 } else {
00578 $permission = 'deletedhistory';
00579 }
00580 wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
00581 return $wgUser->isAllowed( $permission );
00582 } else {
00583 return true;
00584 }
00585 }
00586
00592 public static function isDeleted( $row, $field ) {
00593 return ( $row->log_deleted & $field ) == $field;
00594 }
00595
00615 public static function showLogExtract( &$out, $types=array(), $page='', $user='',
00616 $param = array() ) {
00617
00618 $defaultParameters = array(
00619 'lim' => 25,
00620 'conds' => array(),
00621 'showIfEmpty' => true,
00622 'msgKey' => array(''),
00623 'wrap' => "$1"
00624 );
00625
00626 # The + operator appends elements of remaining keys from the right
00627 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
00628 $param += $defaultParameters;
00629
00630 global $wgUser, $wgOut;
00631 # Convert $param array to individual variables
00632 $lim = $param['lim'];
00633 $conds = $param['conds'];
00634 $showIfEmpty = $param['showIfEmpty'];
00635 $msgKey = $param['msgKey'];
00636 $wrap = $param['wrap'];
00637 if ( !is_array( $msgKey ) )
00638 $msgKey = array( $msgKey );
00639 # Insert list of top 50 (or top $lim) items
00640 $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
00641 $pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
00642 if ( isset( $param['offset'] ) ) # Tell pager to ignore $wgRequest offset
00643 $pager->setOffset( $param['offset'] );
00644 if( $lim > 0 ) $pager->mLimit = $lim;
00645 $logBody = $pager->getBody();
00646 $s = '';
00647 if( $logBody ) {
00648 if ( $msgKey[0] ) {
00649 $s = '<div class="mw-warning-with-logexcerpt">';
00650
00651 if ( count( $msgKey ) == 1 ) {
00652 $s .= wfMsgExt( $msgKey[0], array( 'parse' ) );
00653 } else {
00654 $args = $msgKey;
00655 array_shift( $args );
00656 $s .= wfMsgExt( $msgKey[0], array( 'parse' ), $args );
00657 }
00658 }
00659 $s .= $loglist->beginLogEventsList() .
00660 $logBody .
00661 $loglist->endLogEventsList();
00662 } else {
00663 if ( $showIfEmpty )
00664 $s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ),
00665 wfMsgExt( 'logempty', array( 'parseinline' ) ) );
00666 }
00667 if( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link
00668 $urlParam = array();
00669 if ( $page != '')
00670 $urlParam['page'] = $page;
00671 if ( $user != '')
00672 $urlParam['user'] = $user;
00673 if ( !is_array( $types ) ) # Make it an array, if it isn't
00674 $types = array( $types );
00675 # If there is exactly one log type, we can link to Special:Log?type=foo
00676 if ( count( $types ) == 1 )
00677 $urlParam['type'] = $types[0];
00678 $s .= $wgUser->getSkin()->link(
00679 SpecialPage::getTitleFor( 'Log' ),
00680 wfMsgHtml( 'log-fulllog' ),
00681 array(),
00682 $urlParam
00683 );
00684
00685 }
00686 if ( $logBody && $msgKey[0] )
00687 $s .= '</div>';
00688
00689 if ( $wrap!='' ) { // Wrap message in html
00690 $s = str_replace( '$1', $s, $wrap );
00691 }
00692
00693 // $out can be either an OutputPage object or a String-by-reference
00694 if( $out instanceof OutputPage ){
00695 $out->addHTML( $s );
00696 } else {
00697 $out = $s;
00698 }
00699 return $pager->getNumRows();
00700 }
00701
00708 public static function getExcludeClause( $db, $audience = 'public' ) {
00709 global $wgLogRestrictions, $wgUser;
00710 // Reset the array, clears extra "where" clauses when $par is used
00711 $hiddenLogs = array();
00712 // Don't show private logs to unprivileged users
00713 foreach( $wgLogRestrictions as $logType => $right ) {
00714 if( $audience == 'public' || !$wgUser->isAllowed($right) ) {
00715 $safeType = $db->strencode( $logType );
00716 $hiddenLogs[] = $safeType;
00717 }
00718 }
00719 if( count($hiddenLogs) == 1 ) {
00720 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
00721 } elseif( $hiddenLogs ) {
00722 return 'log_type NOT IN (' . $db->makeList($hiddenLogs) . ')';
00723 }
00724 return false;
00725 }
00726 }
00727
00731 class LogPager extends ReverseChronologicalPager {
00732 private $types = array(), $user = '', $title = '', $pattern = '';
00733 private $typeCGI = '';
00734 public $mLogEventsList;
00735
00747 public function __construct( $list, $types = array(), $user = '', $title = '', $pattern = '',
00748 $conds = array(), $year = false, $month = false, $tagFilter = '' )
00749 {
00750 parent::__construct();
00751 $this->mConds = $conds;
00752
00753 $this->mLogEventsList = $list;
00754
00755 $this->limitType( $types );
00756 $this->limitUser( $user );
00757 $this->limitTitle( $title, $pattern );
00758 $this->getDateCond( $year, $month );
00759 $this->mTagFilter = $tagFilter;
00760 }
00761
00762 public function getDefaultQuery() {
00763 $query = parent::getDefaultQuery();
00764 $query['type'] = $this->typeCGI;
00765 $query['user'] = $this->user;
00766 $query['month'] = $this->mMonth;
00767 $query['year'] = $this->mYear;
00768 return $query;
00769 }
00770
00771
00772 public function getFilterParams() {
00773 global $wgFilterLogTypes, $wgUser, $wgRequest;
00774 $filters = array();
00775 if( count($this->types) ) {
00776 return $filters;
00777 }
00778 foreach( $wgFilterLogTypes as $type => $default ) {
00779
00780 if( $type !== 'patrol' || $wgUser->useNPPatrol() ) {
00781 $hide = $wgRequest->getInt( "hide_{$type}_log", $default );
00782 $filters[$type] = $hide;
00783 if( $hide )
00784 $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
00785 }
00786 }
00787 return $filters;
00788 }
00789
00796 private function limitType( $types ) {
00797 global $wgLogRestrictions, $wgUser;
00798
00799 $types = ($types === '') ? array() : (array)$types;
00800
00801 foreach ( $types as $type ) {
00802 if( isset( $wgLogRestrictions[$type] )
00803 && !$wgUser->isAllowed($wgLogRestrictions[$type])
00804 ) {
00805 $types = array_diff( $types, array( $type ) );
00806 }
00807 }
00808 $this->types = $types;
00809
00810
00811 $audience = $types ? 'user' : 'public';
00812 $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience );
00813 if( $hideLogs !== false ) {
00814 $this->mConds[] = $hideLogs;
00815 }
00816 if( count($types) ) {
00817 $this->mConds['log_type'] = $types;
00818
00819 if( count($types) == 1 ) $this->typeCGI = $types[0];
00820 }
00821 }
00822
00827 private function limitUser( $name ) {
00828 if( $name == '' ) {
00829 return false;
00830 }
00831 $usertitle = Title::makeTitleSafe( NS_USER, $name );
00832 if( is_null($usertitle) ) {
00833 return false;
00834 }
00835
00836 $userid = User::idFromName( $name );
00837 if( !$userid ) {
00838
00839
00840 $this->mConds[] = "NULL";
00841 } else {
00842 global $wgUser;
00843 $this->mConds['log_user'] = $userid;
00844
00845 if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
00846 $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_USER) . ' = 0';
00847 } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
00848 $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_USER) .
00849 ' != ' . LogPage::SUPPRESSED_USER;
00850 }
00851 $this->user = $usertitle->getText();
00852 }
00853 }
00854
00861 private function limitTitle( $page, $pattern ) {
00862 global $wgMiserMode, $wgUser;
00863
00864 $title = Title::newFromText( $page );
00865 if( strlen( $page ) == 0 || !$title instanceof Title )
00866 return false;
00867
00868 $this->title = $title->getPrefixedText();
00869 $ns = $title->getNamespace();
00870 $db = $this->mDb;
00871
00872 # Using the (log_namespace, log_title, log_timestamp) index with a
00873 # range scan (LIKE) on the first two parts, instead of simple equality,
00874 # makes it unusable for sorting. Sorted retrieval using another index
00875 # would be possible, but then we might have to scan arbitrarily many
00876 # nodes of that index. Therefore, we need to avoid this if $wgMiserMode
00877 # is on.
00878 #
00879 # This is not a problem with simple title matches, because then we can
00880 # use the page_time index. That should have no more than a few hundred
00881 # log entries for even the busiest pages, so it can be safely scanned
00882 # in full to satisfy an impossible condition on user or similar.
00883 if( $pattern && !$wgMiserMode ) {
00884 $this->mConds['log_namespace'] = $ns;
00885 $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
00886 $this->pattern = $pattern;
00887 } else {
00888 $this->mConds['log_namespace'] = $ns;
00889 $this->mConds['log_title'] = $title->getDBkey();
00890 }
00891
00892 if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
00893 $this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
00894 } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
00895 $this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
00896 ' != ' . LogPage::SUPPRESSED_ACTION;
00897 }
00898 }
00899
00900 public function getQueryInfo() {
00901 global $wgOut;
00902 $tables = array( 'logging', 'user' );
00903 $this->mConds[] = 'user_id = log_user';
00904 $index = array();
00905 $options = array();
00906 # Add log_search table if there are conditions on it
00907 if( array_key_exists('ls_field',$this->mConds) ) {
00908 $tables[] = 'log_search';
00909 $index['log_search'] = 'ls_field_val';
00910 $index['logging'] = 'PRIMARY';
00911 $options[] = 'DISTINCT';
00912 # Avoid usage of the wrong index by limiting
00913 # the choices of available indexes. This mainly
00914 # avoids site-breaking filesorts.
00915 } else if( $this->title || $this->pattern || $this->user ) {
00916 $index['logging'] = array( 'page_time', 'user_time' );
00917 if( count($this->types) == 1 ) {
00918 $index['logging'][] = 'log_user_type_time';
00919 }
00920 } else if( count($this->types) == 1 ) {
00921 $index['logging'] = 'type_time';
00922 } else {
00923 $index['logging'] = 'times';
00924 }
00925 $options['USE INDEX'] = $index;
00926 # Don't show duplicate rows when using log_search
00927 $info = array(
00928 'tables' => $tables,
00929 'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace',
00930 'log_title', 'log_params', 'log_comment', 'log_id', 'log_deleted',
00931 'log_timestamp', 'user_name', 'user_editcount' ),
00932 'conds' => $this->mConds,
00933 'options' => $options,
00934 'join_conds' => array(
00935 'user' => array( 'INNER JOIN', 'user_id=log_user' ),
00936 'log_search' => array( 'INNER JOIN', 'ls_log_id=log_id' )
00937 )
00938 );
00939 # Add ChangeTags filter query
00940 ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
00941 $info['join_conds'], $info['options'], $this->mTagFilter );
00942 return $info;
00943 }
00944
00945 function getIndexField() {
00946 return 'log_timestamp';
00947 }
00948
00949 public function getStartBody() {
00950 wfProfileIn( __METHOD__ );
00951 # Do a link batch query
00952 if( $this->getNumRows() > 0 ) {
00953 $lb = new LinkBatch;
00954 while( $row = $this->mResult->fetchObject() ) {
00955 $lb->add( $row->log_namespace, $row->log_title );
00956 $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
00957 $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
00958 }
00959 $lb->execute();
00960 $this->mResult->seek( 0 );
00961 }
00962 wfProfileOut( __METHOD__ );
00963 return '';
00964 }
00965
00966 public function formatRow( $row ) {
00967 return $this->mLogEventsList->logLine( $row );
00968 }
00969
00970 public function getType() {
00971 return $this->types;
00972 }
00973
00974 public function getUser() {
00975 return $this->user;
00976 }
00977
00978 public function getPage() {
00979 return $this->title;
00980 }
00981
00982 public function getPattern() {
00983 return $this->pattern;
00984 }
00985
00986 public function getYear() {
00987 return $this->mYear;
00988 }
00989
00990 public function getMonth() {
00991 return $this->mMonth;
00992 }
00993
00994 public function getTagFilter() {
00995 return $this->mTagFilter;
00996 }
00997
00998 public function doQuery() {
00999
01000 $this->mDb->setBigSelects();
01001 parent::doQuery();
01002 $this->mDb->setBigSelects( 'default' );
01003 }
01004 }
01005
01010 class LogReader {
01011 var $pager;
01015 function __construct( $request ) {
01016 global $wgUser, $wgOut;
01017 wfDeprecated(__METHOD__);
01018 # Get parameters
01019 $type = $request->getVal( 'type' );
01020 $user = $request->getText( 'user' );
01021 $title = $request->getText( 'page' );
01022 $pattern = $request->getBool( 'pattern' );
01023 $year = $request->getIntOrNull( 'year' );
01024 $month = $request->getIntOrNull( 'month' );
01025 $tagFilter = $request->getVal( 'tagfilter' );
01026 # Don't let the user get stuck with a certain date
01027 $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
01028 if( $skip ) {
01029 $year = '';
01030 $month = '';
01031 }
01032 # Use new list class to output results
01033 $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
01034 $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month, $tagFilter );
01035 }
01036
01041 public function hasRows() {
01042 return isset($this->pager) ? ($this->pager->getNumRows() > 0) : false;
01043 }
01044 }
01045
01050 class LogViewer {
01051 const NO_ACTION_LINK = 1;
01052
01056 var $reader;
01057
01063 function __construct( &$reader, $flags = 0 ) {
01064 wfDeprecated(__METHOD__);
01065 $this->reader =& $reader;
01066 $this->reader->pager->mLogEventsList->flags = $flags;
01067 # Aliases for shorter code...
01068 $this->pager =& $this->reader->pager;
01069 $this->list =& $this->reader->pager->mLogEventsList;
01070 }
01071
01075 public function show() {
01076 # Set title and add header
01077 $this->list->showHeader( $pager->getType() );
01078 # Show form options
01079 $this->list->showOptions( $this->pager->getType(), $this->pager->getUser(), $this->pager->getPage(),
01080 $this->pager->getPattern(), $this->pager->getYear(), $this->pager->getMonth() );
01081 # Insert list
01082 $logBody = $this->pager->getBody();
01083 if( $logBody ) {
01084 $wgOut->addHTML(
01085 $this->pager->getNavigationBar() .
01086 $this->list->beginLogEventsList() .
01087 $logBody .
01088 $this->list->endLogEventsList() .
01089 $this->pager->getNavigationBar()
01090 );
01091 } else {
01092 $wgOut->addWikiMsg( 'logempty' );
01093 }
01094 }
01095
01102 public function showList( &$out ) {
01103 $logBody = $this->pager->getBody();
01104 if( $logBody ) {
01105 $out->addHTML(
01106 $this->list->beginLogEventsList() .
01107 $logBody .
01108 $this->list->endLogEventsList()
01109 );
01110 } else {
01111 $out->addWikiMsg( 'logempty' );
01112 }
01113 }
01114 }