]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/LogEventsList.php
MediaWiki 1.14.0
[autoinstallsdev/mediawiki.git] / includes / LogEventsList.php
1 <?php
2 # Copyright (C) 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
3 # http://www.mediawiki.org/
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 class LogEventsList {
21         const NO_ACTION_LINK = 1;
22
23         private $skin;
24         private $out;
25         public $flags;
26
27         public function __construct( $skin, $out, $flags = 0 ) {
28                 $this->skin = $skin;
29                 $this->out = $out;
30                 $this->flags = $flags;
31                 $this->preCacheMessages();
32         }
33
34         /**
35          * As we use the same small set of messages in various methods and that
36          * they are called often, we call them once and save them in $this->message
37          */
38         private function preCacheMessages() {
39                 // Precache various messages
40                 if( !isset( $this->message ) ) {
41                         $messages = array( 'revertmerge', 'protect_change', 'unblocklink', 'change-blocklink',
42                                 'revertmove', 'undeletelink', 'revdel-restore', 'rev-delundel', 'hist', 'pipe-separator' );
43                         foreach( $messages as $msg ) {
44                                 $this->message[$msg] = wfMsgExt( $msg, array( 'escape' ) );
45                         }
46                 }
47         }
48
49         /**
50          * Set page title and show header for this log type
51          * @param $type String
52          */
53         public function showHeader( $type ) {
54                 if( LogPage::isLogType( $type ) ) {
55                         $this->out->setPageTitle( LogPage::logName( $type ) );
56                         $this->out->addHTML( LogPage::logHeader( $type ) );
57                 }
58         }
59
60         /**
61          * Show options for the log list
62          * @param $type String
63          * @param $user String
64          * @param $page String
65          * @param $pattern String
66          * @param $year Integer: year
67          * @param $month Integer: month
68          * @param $filter Boolean
69          */
70         public function showOptions( $type = '', $user = '', $page = '', $pattern = '', $year = '', 
71                         $month = '', $filter = null ) 
72         {
73                 global $wgScript, $wgMiserMode;
74                 $action = htmlspecialchars( $wgScript );
75                 $title = SpecialPage::getTitleFor( 'Log' );
76                 $special = htmlspecialchars( $title->getPrefixedDBkey() );
77
78                 $this->out->addHTML( "<form action=\"$action\" method=\"get\"><fieldset>" .
79                         Xml::element( 'legend', array(), wfMsg( 'log' ) ) .
80                         Xml::hidden( 'title', $special ) . "\n" .
81                         $this->getTypeMenu( $type ) . "\n" .
82                         $this->getUserInput( $user ) . "\n" .
83                         $this->getTitleInput( $page ) . "\n" .
84                         ( !$wgMiserMode ? ($this->getTitlePattern( $pattern )."\n") : "" ) .
85                         "<p>" . $this->getDateMenu( $year, $month ) . "\n" .
86                         ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) .
87                         Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "</p>\n" .
88                         "</fieldset></form>"
89                 );
90         }
91         
92         private function getFilterLinks( $logType, $filter ) {
93                 global $wgTitle;
94                 // show/hide links
95                 $messages = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
96                 // Option value -> message mapping
97                 $links = array();
98                 foreach( $filter as $type => $val ) {
99                         $hideVal = 1 - intval($val);
100                         $link = $this->skin->makeKnownLinkObj( $wgTitle, $messages[$hideVal],
101                                 wfArrayToCGI( array( "hide_{$type}_log" => $hideVal ), $this->getDefaultQuery() )
102                         );
103                         $links[$type] = wfMsgHtml( "log-show-hide-{$type}", $link );
104                 }
105                 // Build links
106                 return implode( ' | ', $links );
107         }
108         
109         private function getDefaultQuery() {
110                 if ( !isset( $this->mDefaultQuery ) ) {
111                         $this->mDefaultQuery = $_GET;
112                         unset( $this->mDefaultQuery['title'] );
113                         unset( $this->mDefaultQuery['dir'] );
114                         unset( $this->mDefaultQuery['offset'] );
115                         unset( $this->mDefaultQuery['limit'] );
116                         unset( $this->mDefaultQuery['order'] );
117                         unset( $this->mDefaultQuery['month'] );
118                         unset( $this->mDefaultQuery['year'] );
119                 }
120                 return $this->mDefaultQuery;
121         }
122
123         /**
124          * @param $queryType String
125          * @return String: Formatted HTML
126          */
127         private function getTypeMenu( $queryType ) {
128                 global $wgLogRestrictions, $wgUser;
129
130                 $html = "<select name='type'>\n";
131
132                 $validTypes = LogPage::validTypes();
133                 $typesByName = array(); // Temporary array
134
135                 // First pass to load the log names
136                 foreach( $validTypes as $type ) {
137                         $text = LogPage::logName( $type );
138                         $typesByName[$text] = $type;
139                 }
140
141                 // Second pass to sort by name
142                 ksort($typesByName);
143
144                 // Third pass generates sorted XHTML content
145                 foreach( $typesByName as $text => $type ) {
146                         $selected = ($type == $queryType);
147                         // Restricted types
148                         if ( isset($wgLogRestrictions[$type]) ) {
149                                 if ( $wgUser->isAllowed( $wgLogRestrictions[$type] ) ) {
150                                         $html .= Xml::option( $text, $type, $selected ) . "\n";
151                                 }
152                         } else {
153                                 $html .= Xml::option( $text, $type, $selected ) . "\n";
154                         }
155                 }
156
157                 $html .= '</select>';
158                 return $html;
159         }
160
161         /**
162          * @param $user String
163          * @return String: Formatted HTML
164          */
165         private function getUserInput( $user ) {
166                 return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'user', 15, $user );
167         }
168
169         /**
170          * @param $title String
171          * @return String: Formatted HTML
172          */
173         private function getTitleInput( $title ) {
174                 return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'page', 20, $title );
175         }
176
177         /**
178          * @param $year Integer
179          * @param $month Integer
180          * @return string Formatted HTML
181          */
182         private function getDateMenu( $year, $month ) {
183                 # Offset overrides year/month selection
184                 if( $month && $month !== -1 ) {
185                         $encMonth = intval( $month );
186                 } else {
187                         $encMonth = '';
188                 }
189                 if ( $year ) {
190                         $encYear = intval( $year );
191                 } else if( $encMonth ) {
192                         $thisMonth = intval( gmdate( 'n' ) );
193                         $thisYear = intval( gmdate( 'Y' ) );
194                         if( intval($encMonth) > $thisMonth ) {
195                                 $thisYear--;
196                         }
197                         $encYear = $thisYear;
198                 } else {
199                         $encYear = '';
200                 }
201                 return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
202                         Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) .
203                         ' '.
204                         Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
205                         Xml::monthSelector( $encMonth, -1 );
206         }
207
208         /**
209          * @return boolean Checkbox
210          */
211         private function getTitlePattern( $pattern ) {
212                 return '<span style="white-space: nowrap">' .
213                         Xml::checkLabel( wfMsg( 'log-title-wildcard' ), 'pattern', 'pattern', $pattern ) .
214                         '</span>';
215         }
216
217         public function beginLogEventsList() {
218                 return "<ul>\n";
219         }
220
221         public function endLogEventsList() {
222                 return "</ul>\n";
223         }
224
225         /**
226          * @param $row Row: a single row from the result set
227          * @return String: Formatted HTML list item
228          */
229         public function logLine( $row ) {
230                 global $wgLang, $wgUser, $wgContLang;
231
232                 $title = Title::makeTitle( $row->log_namespace, $row->log_title );
233                 $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->log_timestamp), true );
234                 // User links
235                 if( self::isDeleted($row,LogPage::DELETED_USER) ) {
236                         $userLink = '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
237                 } else {
238                         $userLink = $this->skin->userLink( $row->log_user, $row->user_name ) .
239                                 $this->skin->userToolLinks( $row->log_user, $row->user_name, true, 0, $row->user_editcount );
240                 }
241                 // Comment
242                 if( self::isDeleted($row,LogPage::DELETED_COMMENT) ) {
243                         $comment = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
244                 } else {
245                         $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $row->log_comment );
246                 }
247                 // Extract extra parameters
248                 $paramArray = LogPage::extractParams( $row->log_params );
249                 $revert = $del = '';
250                 // Some user can hide log items and have review links
251                 if( $wgUser->isAllowed( 'deleterevision' ) ) {
252                         $del = $this->getShowHideLinks( $row ) . ' ';
253                 }
254                 // Add review links and such...
255                 if( ($this->flags & self::NO_ACTION_LINK) || ($row->log_deleted & LogPage::DELETED_ACTION) ) {
256                         // Action text is suppressed...
257                 } else if( self::typeAction($row,'move','move','move') && !empty($paramArray[0]) ) {
258                         $destTitle = Title::newFromText( $paramArray[0] );
259                         if( $destTitle ) {
260                                 $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ),
261                                         $this->message['revertmove'],
262                                         'wpOldTitle=' . urlencode( $destTitle->getPrefixedDBkey() ) .
263                                         '&wpNewTitle=' . urlencode( $title->getPrefixedDBkey() ) .
264                                         '&wpReason=' . urlencode( wfMsgForContent( 'revertmove' ) ) .
265                                         '&wpMovetalk=0' ) . ')';
266                         }
267                 // Show undelete link
268                 } else if( self::typeAction($row,array('delete','suppress'),'delete','delete') ) {
269                         $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ),
270                                 $this->message['undeletelink'], 'target='. urlencode( $title->getPrefixedDBkey() ) ) . ')';
271                 // Show unblock/change block link
272                 } else if( self::typeAction($row,array('block','suppress'),array('block','reblock'),'block') ) {
273                         $revert = '(' .
274                                 $this->skin->link( SpecialPage::getTitleFor( 'Ipblocklist' ),
275                                         $this->message['unblocklink'],
276                                         array(),
277                                         array( 'action' => 'unblock', 'ip' => $row->log_title ),
278                                         'known' ) 
279                                 . ' ' . $this->message['pipe-separator'] . ' ' .
280                                 $this->skin->link( SpecialPage::getTitleFor( 'Blockip', $row->log_title ), 
281                                         $this->message['change-blocklink'],
282                                         array(), array(), 'known' ) .
283                                 ')';
284                 // Show change protection link
285                 } else if( self::typeAction( $row, 'protect', array( 'modify', 'protect', 'unprotect' ) ) ) {
286                         $revert .= ' (' . 
287                                 $this->skin->link( $title,
288                                         $this->message['hist'],
289                                         array(),
290                                         array( 'action' => 'history', 'offset' => $row->log_timestamp ) );
291                         if( $wgUser->isAllowed( 'protect' ) ) {
292                                 $revert .= ' ' . $this->message['pipe-separator'] . ' ' .
293                                         $this->skin->link( $title,
294                                                 $this->message['protect_change'],
295                                                 array(),
296                                                 array( 'action' => 'protect' ),
297                                                 'known' );
298                         }
299                         $revert .= ')';
300                 // Show unmerge link
301                 } else if( self::typeAction($row,'merge','merge','mergehistory') ) {
302                         $merge = SpecialPage::getTitleFor( 'Mergehistory' );
303                         $revert = '(' .  $this->skin->makeKnownLinkObj( $merge, $this->message['revertmerge'],
304                                 wfArrayToCGI( array('target' => $paramArray[0], 'dest' => $title->getPrefixedDBkey(), 
305                                         'mergepoint' => $paramArray[1] ) ) ) . ')';
306                 // If an edit was hidden from a page give a review link to the history
307                 } else if( self::typeAction($row,array('delete','suppress'),'revision','deleterevision') ) {
308                         if( count($paramArray) == 2 ) {
309                                 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
310                                 // Different revision types use different URL params...
311                                 $key = $paramArray[0];
312                                 // Link to each hidden object ID, $paramArray[1] is the url param
313                                 $Ids = explode( ',', $paramArray[1] );
314                                 $revParams = '';
315                                 foreach( $Ids as $n => $id ) {
316                                         $revParams .= '&' . urlencode($key) . '[]=' . urlencode($id);
317                                 }
318                                 $revert = '(' . $this->skin->makeKnownLinkObj( $revdel, $this->message['revdel-restore'], 
319                                         'target=' . $title->getPrefixedUrl() . $revParams ) . ')';
320                         }
321                 // Hidden log items, give review link
322                 } else if( self::typeAction($row,array('delete','suppress'),'event','deleterevision') ) {
323                         if( count($paramArray) == 1 ) {
324                                 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
325                                 $Ids = explode( ',', $paramArray[0] );
326                                 // Link to each hidden object ID, $paramArray[1] is the url param
327                                 $logParams = '';
328                                 foreach( $Ids as $n => $id ) {
329                                         $logParams .= '&logid[]=' . intval($id);
330                                 }
331                                 $revert = '(' . $this->skin->makeKnownLinkObj( $revdel, $this->message['revdel-restore'], 
332                                         'target=' . $title->getPrefixedUrl() . $logParams ) . ')';
333                         }
334                 // Self-created users
335                 } else if( self::typeAction($row,'newusers','create2') ) {
336                         if( isset( $paramArray[0] ) ) {
337                                 $revert = $this->skin->userToolLinks( $paramArray[0], $title->getDBkey(), true );
338                         } else {
339                                 # Fall back to a blue contributions link
340                                 $revert = $this->skin->userToolLinks( 1, $title->getDBkey() );
341                         }
342                         if( $time < '20080129000000' ) {
343                                 # Suppress $comment from old entries (before 2008-01-29),
344                                 # not needed and can contain incorrect links
345                                 $comment = '';
346                         }
347                 // Do nothing. The implementation is handled by the hook modifiying the passed-by-ref parameters.
348                 } else {
349                         wfRunHooks( 'LogLine', array( $row->log_type, $row->log_action, $title, $paramArray,
350                                 &$comment, &$revert, $row->log_timestamp ) );
351                 }
352                 // Event description
353                 if( self::isDeleted($row,LogPage::DELETED_ACTION) ) {
354                         $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
355                 } else {
356                         $action = LogPage::actionText( $row->log_type, $row->log_action, $title,
357                                 $this->skin, $paramArray, true );
358                 }
359
360                 if( $revert != '' ) {
361                         $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
362                 }
363
364                 return Xml::tags( 'li', array( "class" => "mw-logline-$row->log_type" ),
365                         $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert );
366         }
367
368         /**
369          * @param $row Row
370          * @return string
371          */
372         private function getShowHideLinks( $row ) {
373                 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
374                 // If event was hidden from sysops
375                 if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) {
376                         $del = $this->message['rev-delundel'];
377                 } else if( $row->log_type == 'suppress' ) {
378                         // No one should be hiding from the oversight log
379                         $del = $this->message['rev-delundel'];
380                 } else {
381                         $target = SpecialPage::getTitleFor( 'Log', $row->log_type );
382                         $del = $this->skin->makeKnownLinkObj( $revdel, $this->message['rev-delundel'],
383                                 'target=' . $target->getPrefixedUrl() . '&logid='.$row->log_id );
384                         // Bolden oversighted content
385                         if( self::isDeleted( $row, LogPage::DELETED_RESTRICTED ) )
386                                 $del = "<strong>$del</strong>";
387                 }
388                 return "<tt>(<small>$del</small>)</tt>";
389         }
390
391         /**
392          * @param $row Row
393          * @param $type Mixed: string/array
394          * @param $action Mixed: string/array
395          * @param $right string
396          * @return bool
397          */
398         public static function typeAction( $row, $type, $action, $right='' ) {
399                 $match = is_array($type) ? in_array($row->log_type,$type) : $row->log_type == $type;
400                 if( $match ) {
401                         $match = is_array($action) ?
402                                 in_array($row->log_action,$action) : $row->log_action == $action;
403                         if( $match && $right ) {
404                                 global $wgUser;
405                                 $match = $wgUser->isAllowed( $right );
406                         }
407                 }
408                 return $match;
409         }
410
411         /**
412          * Determine if the current user is allowed to view a particular
413          * field of this log row, if it's marked as deleted.
414          * @param $row Row
415          * @param $field Integer
416          * @return Boolean
417          */
418         public static function userCan( $row, $field ) {
419                 if( ( $row->log_deleted & $field ) == $field ) {
420                         global $wgUser;
421                         $permission = ( $row->log_deleted & LogPage::DELETED_RESTRICTED ) == LogPage::DELETED_RESTRICTED
422                                 ? 'suppressrevision'
423                                 : 'deleterevision';
424                         wfDebug( "Checking for $permission due to $field match on $row->log_deleted\n" );
425                         return $wgUser->isAllowed( $permission );
426                 } else {
427                         return true;
428                 }
429         }
430
431         /**
432          * @param $row Row
433          * @param $field Integer: one of DELETED_* bitfield constants
434          * @return Boolean
435          */
436         public static function isDeleted( $row, $field ) {
437                 return ($row->log_deleted & $field) == $field;
438         }
439
440         /**
441          * Quick function to show a short log extract
442          * @param $out OutputPage
443          * @param $type String
444          * @param $page String
445          * @param $user String
446          * @param $lim Integer
447          * @param $conds Array
448          */
449         public static function showLogExtract( $out, $type='', $page='', $user='', $lim=0, $conds=array() ) {
450                 global $wgUser;
451                 # Insert list of top 50 or so items
452                 $loglist = new LogEventsList( $wgUser->getSkin(), $out, 0 );
453                 $pager = new LogPager( $loglist, $type, $user, $page, '', $conds );
454                 if( $lim > 0 ) $pager->mLimit = $lim;
455                 $logBody = $pager->getBody();
456                 if( $logBody ) {
457                         $out->addHTML(
458                                 $loglist->beginLogEventsList() .
459                                 $logBody .
460                                 $loglist->endLogEventsList()
461                         );
462                 } else {
463                         $out->addWikiMsg( 'logempty' );
464                 }
465                 return $pager->getNumRows();
466         }
467
468         /**
469          * SQL clause to skip forbidden log types for this user
470          * @param $db Database
471          * @return mixed (string or false)
472          */
473         public static function getExcludeClause( $db ) {
474                 global $wgLogRestrictions, $wgUser;
475                 // Reset the array, clears extra "where" clauses when $par is used
476                 $hiddenLogs = array();
477                 // Don't show private logs to unprivileged users
478                 foreach( $wgLogRestrictions as $logType => $right ) {
479                         if( !$wgUser->isAllowed($right) ) {
480                                 $safeType = $db->strencode( $logType );
481                                 $hiddenLogs[] = $safeType;
482                         }
483                 }
484                 if( count($hiddenLogs) == 1 ) {
485                         return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
486                 } elseif( $hiddenLogs ) {
487                         return 'log_type NOT IN (' . $db->makeList($hiddenLogs) . ')';
488                 }
489                 return false;
490         }
491 }
492
493 /**
494  * @ingroup Pager
495  */
496 class LogPager extends ReverseChronologicalPager {
497         private $type = '', $user = '', $title = '', $pattern = '';
498         public $mLogEventsList;
499
500         /**
501          * constructor
502          * @param $list LogEventsList
503          * @param $type String
504          * @param $user String
505          * @param $title String
506          * @param $pattern String
507          * @param $conds Array
508          * @param $year Integer
509          * @param $month Integer
510          */
511         public function __construct( $list, $type = '', $user = '', $title = '', $pattern = '', 
512                 $conds = array(), $year = false, $month = false ) 
513         {
514                 parent::__construct();
515                 $this->mConds = $conds;
516
517                 $this->mLogEventsList = $list;
518
519                 $this->limitType( $type );
520                 $this->limitUser( $user );
521                 $this->limitTitle( $title, $pattern );
522                 $this->getDateCond( $year, $month );
523         }
524
525         public function getDefaultQuery() {
526                 $query = parent::getDefaultQuery();
527                 $query['type'] = $this->type;
528                 $query['user'] = $this->user;
529                 $query['month'] = $this->mMonth;
530                 $query['year'] = $this->mYear;
531                 return $query;
532         }
533
534         public function getFilterParams() {
535                 global $wgFilterLogTypes, $wgUser, $wgRequest;
536                 $filters = array();
537                 if( $this->type ) {
538                         return $filters;
539                 }
540                 foreach( $wgFilterLogTypes as $type => $default ) {
541                         // Avoid silly filtering
542                         if( $type !== 'patrol' || $wgUser->useNPPatrol() ) {
543                                 $hide = $wgRequest->getInt( "hide_{$type}_log", $default );
544                                 $filters[$type] = $hide;
545                                 if( $hide )
546                                         $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
547                         }
548                 }
549                 return $filters;
550         }
551
552         /**
553          * Set the log reader to return only entries of the given type.
554          * Type restrictions enforced here
555          * @param $type String: A log type ('upload', 'delete', etc)
556          */
557         private function limitType( $type ) {
558                 global $wgLogRestrictions, $wgUser;
559                 // Don't even show header for private logs; don't recognize it...
560                 if( isset($wgLogRestrictions[$type]) && !$wgUser->isAllowed($wgLogRestrictions[$type]) ) {
561                         $type = '';
562                 }
563                 // Don't show private logs to unpriviledged users
564                 $hideLogs = LogEventsList::getExcludeClause( $this->mDb );
565                 if( $hideLogs !== false ) {
566                         $this->mConds[] = $hideLogs;
567                 }
568                 if( !$type ) {
569                         return false;
570                 }
571                 $this->type = $type;
572                 $this->mConds['log_type'] = $type;
573         }
574
575         /**
576          * Set the log reader to return only entries by the given user.
577          * @param $name String: (In)valid user name
578          */
579         private function limitUser( $name ) {
580                 if( $name == '' ) {
581                         return false;
582                 }
583                 $usertitle = Title::makeTitleSafe( NS_USER, $name );
584                 if( is_null($usertitle) ) {
585                         return false;
586                 }
587                 /* Fetch userid at first, if known, provides awesome query plan afterwards */
588                 $userid = User::idFromName( $name );
589                 if( !$userid ) {
590                         /* It should be nicer to abort query at all,
591                            but for now it won't pass anywhere behind the optimizer */
592                         $this->mConds[] = "NULL";
593                 } else {
594                         $this->mConds['log_user'] = $userid;
595                         $this->user = $usertitle->getText();
596                 }
597         }
598
599         /**
600          * Set the log reader to return only entries affecting the given page.
601          * (For the block and rights logs, this is a user page.)
602          * @param $page String: Title name as text
603          * @param $pattern String
604          */
605         private function limitTitle( $page, $pattern ) {
606                 global $wgMiserMode;
607
608                 $title = Title::newFromText( $page );
609                 if( strlen($page) == 0 || !$title instanceof Title )
610                         return false;
611
612                 $this->title = $title->getPrefixedText();
613                 $ns = $title->getNamespace();
614                 # Using the (log_namespace, log_title, log_timestamp) index with a
615                 # range scan (LIKE) on the first two parts, instead of simple equality,
616                 # makes it unusable for sorting.  Sorted retrieval using another index
617                 # would be possible, but then we might have to scan arbitrarily many
618                 # nodes of that index. Therefore, we need to avoid this if $wgMiserMode
619                 # is on.
620                 #
621                 # This is not a problem with simple title matches, because then we can
622                 # use the page_time index.  That should have no more than a few hundred
623                 # log entries for even the busiest pages, so it can be safely scanned
624                 # in full to satisfy an impossible condition on user or similar.
625                 if( $pattern && !$wgMiserMode ) {
626                         # use escapeLike to avoid expensive search patterns like 't%st%'
627                         $safetitle = $this->mDb->escapeLike( $title->getDBkey() );
628                         $this->mConds['log_namespace'] = $ns;
629                         $this->mConds[] = "log_title LIKE '$safetitle%'";
630                         $this->pattern = $pattern;
631                 } else {
632                         $this->mConds['log_namespace'] = $ns;
633                         $this->mConds['log_title'] = $title->getDBkey();
634                 }
635         }
636
637         public function getQueryInfo() {
638                 $this->mConds[] = 'user_id = log_user';
639                 # Don't use the wrong logging index
640                 if( $this->title || $this->pattern || $this->user ) {
641                         $index = array( 'USE INDEX' => array( 'logging' => array('page_time','user_time') ) );
642                 } else if( $this->type ) {
643                         $index = array( 'USE INDEX' => array( 'logging' => 'type_time' ) );
644                 } else {
645                         $index = array( 'USE INDEX' => array( 'logging' => 'times' ) );
646                 }
647                 return array(
648                         'tables' => array( 'logging', 'user' ),
649                         'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace', 'log_title', 'log_params',
650                                 'log_comment', 'log_id', 'log_deleted', 'log_timestamp', 'user_name', 'user_editcount' ),
651                         'conds' => $this->mConds,
652                         'options' => $index
653                 );
654         }
655
656         function getIndexField() {
657                 return 'log_timestamp';
658         }
659
660         public function getStartBody() {
661                 wfProfileIn( __METHOD__ );
662                 # Do a link batch query
663                 if( $this->getNumRows() > 0 ) {
664                         $lb = new LinkBatch;
665                         while( $row = $this->mResult->fetchObject() ) {
666                                 $lb->add( $row->log_namespace, $row->log_title );
667                                 $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
668                                 $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
669                         }
670                         $lb->execute();
671                         $this->mResult->seek( 0 );
672                 }
673                 wfProfileOut( __METHOD__ );
674                 return '';
675         }
676
677         public function formatRow( $row ) {
678                 return $this->mLogEventsList->logLine( $row );
679         }
680
681         public function getType() {
682                 return $this->type;
683         }
684
685         public function getUser() {
686                 return $this->user;
687         }
688
689         public function getPage() {
690                 return $this->title;
691         }
692
693         public function getPattern() {
694                 return $this->pattern;
695         }
696
697         public function getYear() {
698                 return $this->mYear;
699         }
700
701         public function getMonth() {
702                 return $this->mMonth;
703         }
704 }
705
706 /**
707  * @deprecated
708  * @ingroup SpecialPage
709  */
710 class LogReader {
711         var $pager;
712         /**
713          * @param $request WebRequest: for internal use use a FauxRequest object to pass arbitrary parameters.
714          */
715         function __construct( $request ) {
716                 global $wgUser, $wgOut;
717                 wfDeprecated(__METHOD__);
718                 # Get parameters
719                 $type = $request->getVal( 'type' );
720                 $user = $request->getText( 'user' );
721                 $title = $request->getText( 'page' );
722                 $pattern = $request->getBool( 'pattern' );
723                 $year = $request->getIntOrNull( 'year' );
724                 $month = $request->getIntOrNull( 'month' );
725                 # Don't let the user get stuck with a certain date
726                 $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
727                 if( $skip ) {
728                         $year = '';
729                         $month = '';
730                 }
731                 # Use new list class to output results
732                 $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
733                 $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month );
734         }
735
736         /**
737         * Is there at least one row?
738         * @return bool
739         */
740         public function hasRows() {
741                 return isset($this->pager) ? ($this->pager->getNumRows() > 0) : false;
742         }
743 }
744
745 /**
746  * @deprecated
747  * @ingroup SpecialPage
748  */
749 class LogViewer {
750         const NO_ACTION_LINK = 1;
751
752         /**
753          * LogReader object
754          */
755         var $reader;
756
757         /**
758          * @param &$reader LogReader: where to get our data from
759          * @param $flags Integer: Bitwise combination of flags:
760          *     LogEventsList::NO_ACTION_LINK   Don't show restore/unblock/block links
761          */
762         function __construct( &$reader, $flags = 0 ) {
763                 global $wgUser;
764                 wfDeprecated(__METHOD__);
765                 $this->reader =& $reader;
766                 $this->reader->pager->mLogEventsList->flags = $flags;
767                 # Aliases for shorter code...
768                 $this->pager =& $this->reader->pager;
769                 $this->list =& $this->reader->pager->mLogEventsList;
770         }
771
772         /**
773          * Take over the whole output page in $wgOut with the log display.
774          */
775         public function show() {
776                 # Set title and add header
777                 $this->list->showHeader( $pager->getType() );
778                 # Show form options
779                 $this->list->showOptions( $this->pager->getType(), $this->pager->getUser(), $this->pager->getPage(),
780                         $this->pager->getPattern(), $this->pager->getYear(), $this->pager->getMonth() );
781                 # Insert list
782                 $logBody = $this->pager->getBody();
783                 if( $logBody ) {
784                         $wgOut->addHTML(
785                                 $this->pager->getNavigationBar() .
786                                 $this->list->beginLogEventsList() .
787                                 $logBody .
788                                 $this->list->endLogEventsList() .
789                                 $this->pager->getNavigationBar()
790                         );
791                 } else {
792                         $wgOut->addWikiMsg( 'logempty' );
793                 }
794         }
795
796         /**
797          * Output just the list of entries given by the linked LogReader,
798          * with extraneous UI elements. Use for displaying log fragments in
799          * another page (eg at Special:Undelete)
800          * @param $out OutputPage: where to send output
801          */
802         public function showList( &$out ) {
803                 $logBody = $this->pager->getBody();
804                 if( $logBody ) {
805                         $out->addHTML(
806                                 $this->list->beginLogEventsList() .
807                                 $logBody .
808                                 $this->list->endLogEventsList()
809                         );
810                 } else {
811                         $out->addWikiMsg( 'logempty' );
812                 }
813         }
814 }