Update documentation for log related classes
[lhc/web/wiklou.git] / includes / logging / LogPage.php
1 <?php
2 /**
3 * Contain log classes
4 *
5 * Copyright © 2002, 2004 Brion Vibber <brion@pobox.com>
6 * http://www.mediawiki.org/
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 *
23 * @file
24 */
25
26 /**
27 * Class to simplify the use of log pages.
28 * The logs are now kept in a table which is easier to manage and trim
29 * than ever-growing wiki pages.
30 *
31 */
32 class LogPage {
33 const DELETED_ACTION = 1;
34 const DELETED_COMMENT = 2;
35 const DELETED_USER = 4;
36 const DELETED_RESTRICTED = 8;
37
38 // Convenience fields
39 const SUPPRESSED_USER = 12;
40 const SUPPRESSED_ACTION = 9;
41
42 /** @var bool */
43 var $updateRecentChanges;
44
45 /** @var bool */
46 var $sendToUDP;
47
48 /** @var string One of '', 'block', 'protect', 'rights', 'delete',
49 * 'upload', 'move'
50 */
51 var $type;
52
53 /** @var string One of '', 'block', 'protect', 'rights', 'delete',
54 * 'upload', 'move', 'move_redir' */
55 var $action;
56
57 /** @var string Comment associated with action */
58 var $comment;
59
60 /** @var string Blob made of a parameters array */
61 var $params;
62
63 /** @var User The user doing the action */
64 var $doer;
65
66 /** @var Title */
67 var $target;
68
69 /**
70 * Constructor
71 *
72 * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
73 * 'upload', 'move'
74 * @param bool $rc Whether to update recent changes as well as the logging table
75 * @param string $udp Pass 'UDP' to send to the UDP feed if NOT sent to RC
76 */
77 public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
78 $this->type = $type;
79 $this->updateRecentChanges = $rc;
80 $this->sendToUDP = ( $udp == 'UDP' );
81 }
82
83 /**
84 * @return int log_id of the inserted log entry
85 */
86 protected function saveContent() {
87 global $wgLogRestrictions;
88
89 $dbw = wfGetDB( DB_MASTER );
90 $log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
91
92 $this->timestamp = $now = wfTimestampNow();
93 $data = array(
94 'log_id' => $log_id,
95 'log_type' => $this->type,
96 'log_action' => $this->action,
97 'log_timestamp' => $dbw->timestamp( $now ),
98 'log_user' => $this->doer->getId(),
99 'log_user_text' => $this->doer->getName(),
100 'log_namespace' => $this->target->getNamespace(),
101 'log_title' => $this->target->getDBkey(),
102 'log_page' => $this->target->getArticleID(),
103 'log_comment' => $this->comment,
104 'log_params' => $this->params
105 );
106 $dbw->insert( 'logging', $data, __METHOD__ );
107 $newId = !is_null( $log_id ) ? $log_id : $dbw->insertId();
108
109 # And update recentchanges
110 if ( $this->updateRecentChanges ) {
111 $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
112
113 RecentChange::notifyLog(
114 $now, $titleObj, $this->doer, $this->getRcComment(), '',
115 $this->type, $this->action, $this->target, $this->comment,
116 $this->params, $newId, $this->getRcCommentIRC()
117 );
118 } elseif ( $this->sendToUDP ) {
119 # Don't send private logs to UDP
120 if ( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
121 return $newId;
122 }
123
124 # Notify external application via UDP.
125 # We send this to IRC but do not want to add it the RC table.
126 $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
127 $rc = RecentChange::newLogEntry(
128 $now, $titleObj, $this->doer, $this->getRcComment(), '',
129 $this->type, $this->action, $this->target, $this->comment,
130 $this->params, $newId, $this->getRcCommentIRC()
131 );
132 $rc->notifyRC2UDP();
133 }
134
135 return $newId;
136 }
137
138 /**
139 * Get the RC comment from the last addEntry() call
140 *
141 * @return string
142 */
143 public function getRcComment() {
144 $rcComment = $this->actionText;
145
146 if ( $this->comment != '' ) {
147 if ( $rcComment == '' ) {
148 $rcComment = $this->comment;
149 } else {
150 $rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
151 $this->comment;
152 }
153 }
154
155 return $rcComment;
156 }
157
158 /**
159 * Get the RC comment from the last addEntry() call for IRC
160 *
161 * @return string
162 */
163 public function getRcCommentIRC() {
164 $rcComment = $this->ircActionText;
165
166 if ( $this->comment != '' ) {
167 if ( $rcComment == '' ) {
168 $rcComment = $this->comment;
169 } else {
170 $rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
171 $this->comment;
172 }
173 }
174
175 return $rcComment;
176 }
177
178 /**
179 * Get the comment from the last addEntry() call
180 */
181 public function getComment() {
182 return $this->comment;
183 }
184
185 /**
186 * Get the list of valid log types
187 *
188 * @return array of strings
189 */
190 public static function validTypes() {
191 global $wgLogTypes;
192
193 return $wgLogTypes;
194 }
195
196 /**
197 * Is $type a valid log type
198 *
199 * @param string $type Log type to check
200 * @return bool
201 */
202 public static function isLogType( $type ) {
203 return in_array( $type, LogPage::validTypes() );
204 }
205
206 /**
207 * Get the name for the given log type
208 *
209 * @param string $type Log type
210 * @return string Log name
211 * @deprecated in 1.19, warnings in 1.21. Use getName()
212 */
213 public static function logName( $type ) {
214 global $wgLogNames;
215
216 if ( isset( $wgLogNames[$type] ) ) {
217 return str_replace( '_', ' ', wfMessage( $wgLogNames[$type] )->text() );
218 } else {
219 // Bogus log types? Perhaps an extension was removed.
220 return $type;
221 }
222 }
223
224 /**
225 * Get the log header for the given log type
226 *
227 * @todo handle missing log types
228 * @param string $type logtype
229 * @return string Header text of this logtype
230 * @deprecated in 1.19, warnings in 1.21. Use getDescription()
231 */
232 public static function logHeader( $type ) {
233 global $wgLogHeaders;
234
235 return wfMessage( $wgLogHeaders[$type] )->parse();
236 }
237
238 /**
239 * Generate text for a log entry.
240 * Only LogFormatter should call this function.
241 *
242 * @param string $type log type
243 * @param string $action log action
244 * @param Title|null $title Title object or null
245 * @param Skin|null $skin Skin object or null. If null, we want to use the wiki
246 * content language, since that will go to the IRC feed.
247 * @param array $params parameters
248 * @param bool $filterWikilinks Whether to filter wiki links
249 * @return string HTML
250 */
251 public static function actionText( $type, $action, $title = null, $skin = null,
252 $params = array(), $filterWikilinks = false
253 ) {
254 global $wgLang, $wgContLang, $wgLogActions;
255
256 if ( is_null( $skin ) ) {
257 $langObj = $wgContLang;
258 $langObjOrNull = null;
259 } else {
260 $langObj = $wgLang;
261 $langObjOrNull = $wgLang;
262 }
263
264 $key = "$type/$action";
265
266 if ( isset( $wgLogActions[$key] ) ) {
267 if ( is_null( $title ) ) {
268 $rv = wfMessage( $wgLogActions[$key] )->inLanguage( $langObj )->escaped();
269 } else {
270 $titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
271
272 if ( count( $params ) == 0 ) {
273 $rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )
274 ->inLanguage( $langObj )->escaped();
275 } else {
276 $details = '';
277 array_unshift( $params, $titleLink );
278
279 // User suppression
280 if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
281 if ( $skin ) {
282 // Localize the duration, and add a tooltip
283 // in English to help visitors from other wikis.
284 // The lrm is needed to make sure that the number
285 // is shown on the correct side of the tooltip text.
286 $durationTooltip = '&lrm;' . htmlspecialchars( $params[1] );
287 $params[1] = "<span class='blockExpiry' title='$durationTooltip'>" .
288 $wgLang->translateBlockExpiry( $params[1] ) . '</span>';
289 } else {
290 $params[1] = $wgContLang->translateBlockExpiry( $params[1] );
291 }
292
293 $params[2] = isset( $params[2] ) ?
294 self::formatBlockFlags( $params[2], $langObj ) : '';
295 // Page protections
296 } elseif ( $type == 'protect' && count( $params ) == 3 ) {
297 // Restrictions and expiries
298 if ( $skin ) {
299 $details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
300 } else {
301 $details .= " {$params[1]}";
302 }
303
304 // Cascading flag...
305 if ( $params[2] ) {
306 $text = wfMessage( 'protect-summary-cascade' )
307 ->inLanguage( $langObj )->text();
308 $details .= ' ';
309 $details .= wfMessage( 'brackets', $text )->inLanguage( $langObj )->text();
310
311 }
312 }
313
314 $rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )
315 ->inLanguage( $langObj )->escaped() . $details;
316 }
317 }
318 } else {
319 global $wgLogActionsHandlers;
320
321 if ( isset( $wgLogActionsHandlers[$key] ) ) {
322 $args = func_get_args();
323 $rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
324 } else {
325 wfDebug( "LogPage::actionText - unknown action $key\n" );
326 $rv = "$action";
327 }
328 }
329
330 // For the perplexed, this feature was added in r7855 by Erik.
331 // The feature was added because we liked adding [[$1]] in our log entries
332 // but the log entries are parsed as Wikitext on RecentChanges but as HTML
333 // on Special:Log. The hack is essentially that [[$1]] represented a link
334 // to the title in question. The first parameter to the HTML version (Special:Log)
335 // is that link in HTML form, and so this just gets rid of the ugly [[]].
336 // However, this is a horrible hack and it doesn't work like you expect if, say,
337 // you want to link to something OTHER than the title of the log entry.
338 // The real problem, which Erik was trying to fix (and it sort-of works now) is
339 // that the same messages are being treated as both wikitext *and* HTML.
340 if ( $filterWikilinks ) {
341 $rv = str_replace( '[[', '', $rv );
342 $rv = str_replace( ']]', '', $rv );
343 }
344
345 return $rv;
346 }
347
348 /**
349 * @todo Document
350 * @param string $type
351 * @param Language|null $lang
352 * @param Title $title
353 * @param array $params
354 * @return string
355 */
356 protected static function getTitleLink( $type, $lang, $title, &$params ) {
357 if ( !$lang ) {
358 return $title->getPrefixedText();
359 }
360
361 switch ( $type ) {
362 case 'move':
363 $titleLink = Linker::link(
364 $title,
365 htmlspecialchars( $title->getPrefixedText() ),
366 array(),
367 array( 'redirect' => 'no' )
368 );
369
370 $targetTitle = Title::newFromText( $params[0] );
371
372 if ( !$targetTitle ) {
373 # Workaround for broken database
374 $params[0] = htmlspecialchars( $params[0] );
375 } else {
376 $params[0] = Linker::link(
377 $targetTitle,
378 htmlspecialchars( $params[0] )
379 );
380 }
381 break;
382 case 'block':
383 if ( substr( $title->getText(), 0, 1 ) == '#' ) {
384 $titleLink = $title->getText();
385 } else {
386 // @todo Store the user identifier in the parameters
387 // to make this faster for future log entries
388 $id = User::idFromName( $title->getText() );
389 $titleLink = Linker::userLink( $id, $title->getText() )
390 . Linker::userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
391 }
392 break;
393 case 'merge':
394 $titleLink = Linker::link(
395 $title,
396 $title->getPrefixedText(),
397 array(),
398 array( 'redirect' => 'no' )
399 );
400 $params[0] = Linker::link(
401 Title::newFromText( $params[0] ),
402 htmlspecialchars( $params[0] )
403 );
404 $params[1] = $lang->timeanddate( $params[1] );
405 break;
406 default:
407 if ( $title->isSpecialPage() ) {
408 list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
409
410 # Use the language name for log titles, rather than Log/X
411 if ( $name == 'Log' ) {
412 $logPage = new LogPage( $par );
413 $titleLink = Linker::link( $title, $logPage->getName()->escaped() );
414 $titleLink = wfMessage( 'parentheses' )
415 ->inLanguage( $lang )
416 ->rawParams( $titleLink )
417 ->escaped();
418 } else {
419 $titleLink = Linker::link( $title );
420 }
421 } else {
422 $titleLink = Linker::link( $title );
423 }
424 }
425
426 return $titleLink;
427 }
428
429 /**
430 * Add a log entry
431 *
432 * @param string $action one of '', 'block', 'protect', 'rights', 'delete',
433 * 'upload', 'move', 'move_redir'
434 * @param Title $target Title object
435 * @param string $comment description associated
436 * @param array $params parameters passed later to wfMessage function
437 * @param null|int|User $doer The user doing the action. null for $wgUser
438 *
439 * @return int log_id of the inserted log entry
440 */
441 public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
442 global $wgContLang;
443
444 if ( !is_array( $params ) ) {
445 $params = array( $params );
446 }
447
448 if ( $comment === null ) {
449 $comment = '';
450 }
451
452 # Trim spaces on user supplied text
453 $comment = trim( $comment );
454
455 # Truncate for whole multibyte characters.
456 $comment = $wgContLang->truncate( $comment, 255 );
457
458 $this->action = $action;
459 $this->target = $target;
460 $this->comment = $comment;
461 $this->params = LogPage::makeParamBlob( $params );
462
463 if ( $doer === null ) {
464 global $wgUser;
465 $doer = $wgUser;
466 } elseif ( !is_object( $doer ) ) {
467 $doer = User::newFromId( $doer );
468 }
469
470 $this->doer = $doer;
471
472 $logEntry = new ManualLogEntry( $this->type, $action );
473 $logEntry->setTarget( $target );
474 $logEntry->setPerformer( $doer );
475 $logEntry->setParameters( $params );
476
477 $formatter = LogFormatter::newFromEntry( $logEntry );
478 $context = RequestContext::newExtraneousContext( $target );
479 $formatter->setContext( $context );
480
481 $this->actionText = $formatter->getPlainActionText();
482 $this->ircActionText = $formatter->getIRCActionText();
483
484 return $this->saveContent();
485 }
486
487 /**
488 * Add relations to log_search table
489 *
490 * @param string $field
491 * @param array $values
492 * @param int $logid
493 * @return bool
494 */
495 public function addRelations( $field, $values, $logid ) {
496 if ( !strlen( $field ) || empty( $values ) ) {
497 return false; // nothing
498 }
499
500 $data = array();
501
502 foreach ( $values as $value ) {
503 $data[] = array(
504 'ls_field' => $field,
505 'ls_value' => $value,
506 'ls_log_id' => $logid
507 );
508 }
509
510 $dbw = wfGetDB( DB_MASTER );
511 $dbw->insert( 'log_search', $data, __METHOD__, 'IGNORE' );
512
513 return true;
514 }
515
516 /**
517 * Create a blob from a parameter array
518 *
519 * @param array $params
520 * @return string
521 */
522 public static function makeParamBlob( $params ) {
523 return implode( "\n", $params );
524 }
525
526 /**
527 * Extract a parameter array from a blob
528 *
529 * @param string $blob
530 * @return array
531 */
532 public static function extractParams( $blob ) {
533 if ( $blob === '' ) {
534 return array();
535 } else {
536 return explode( "\n", $blob );
537 }
538 }
539
540 /**
541 * Convert a comma-delimited list of block log flags
542 * into a more readable (and translated) form
543 *
544 * @param string $flags Flags to format
545 * @param Language $lang
546 * @return string
547 */
548 public static function formatBlockFlags( $flags, $lang ) {
549 $flags = trim( $flags );
550 if ( $flags === '' ) {
551 return ''; //nothing to do
552 }
553 $flags = explode( ',', $flags );
554 $flagsCount = count( $flags );
555
556 for ( $i = 0; $i < $flagsCount; $i++ ) {
557 $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
558 }
559
560 return wfMessage( 'parentheses' )->inLanguage( $lang )
561 ->rawParams( $lang->commaList( $flags ) )->escaped();
562 }
563
564 /**
565 * Translate a block log flag if possible
566 *
567 * @param int $flag Flag to translate
568 * @param Language $lang Language object to use
569 * @return string
570 */
571 public static function formatBlockFlag( $flag, $lang ) {
572 static $messages = array();
573
574 if ( !isset( $messages[$flag] ) ) {
575 $messages[$flag] = htmlspecialchars( $flag ); // Fallback
576
577 // For grepping. The following core messages can be used here:
578 // * block-log-flags-angry-autoblock
579 // * block-log-flags-anononly
580 // * block-log-flags-hiddenname
581 // * block-log-flags-noautoblock
582 // * block-log-flags-nocreate
583 // * block-log-flags-noemail
584 // * block-log-flags-nousertalk
585 $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
586
587 if ( $msg->exists() ) {
588 $messages[$flag] = $msg->escaped();
589 }
590 }
591
592 return $messages[$flag];
593 }
594
595 /**
596 * Name of the log.
597 * @return Message
598 * @since 1.19
599 */
600 public function getName() {
601 global $wgLogNames;
602
603 // BC
604 if ( isset( $wgLogNames[$this->type] ) ) {
605 $key = $wgLogNames[$this->type];
606 } else {
607 $key = 'log-name-' . $this->type;
608 }
609
610 return wfMessage( $key );
611 }
612
613 /**
614 * Description of this log type.
615 * @return Message
616 * @since 1.19
617 */
618 public function getDescription() {
619 global $wgLogHeaders;
620 // BC
621 if ( isset( $wgLogHeaders[$this->type] ) ) {
622 $key = $wgLogHeaders[$this->type];
623 } else {
624 $key = 'log-description-' . $this->type;
625 }
626
627 return wfMessage( $key );
628 }
629
630 /**
631 * Returns the right needed to read this log type.
632 * @return string
633 * @since 1.19
634 */
635 public function getRestriction() {
636 global $wgLogRestrictions;
637 if ( isset( $wgLogRestrictions[$this->type] ) ) {
638 $restriction = $wgLogRestrictions[$this->type];
639 } else {
640 // '' always returns true with $user->isAllowed()
641 $restriction = '';
642 }
643
644 return $restriction;
645 }
646
647 /**
648 * Tells if this log is not viewable by all.
649 * @return bool
650 * @since 1.19
651 */
652 public function isRestricted() {
653 $restriction = $this->getRestriction();
654
655 return $restriction !== '' && $restriction !== '*';
656 }
657 }