These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Contains classes for formatting log entries |
||
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 | * @file |
||
21 | * @author Niklas Laxström |
||
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
||
23 | * @since 1.19 |
||
24 | */ |
||
25 | |||
26 | /** |
||
27 | * Implements the default log formatting. |
||
28 | * |
||
29 | * Can be overridden by subclassing and setting: |
||
30 | * |
||
31 | * $wgLogActionsHandlers['type/subtype'] = 'class'; or |
||
32 | * $wgLogActionsHandlers['type/*'] = 'class'; |
||
33 | * |
||
34 | * @since 1.19 |
||
35 | */ |
||
36 | class LogFormatter { |
||
37 | // Audience options for viewing usernames, comments, and actions |
||
38 | const FOR_PUBLIC = 1; |
||
39 | const FOR_THIS_USER = 2; |
||
40 | |||
41 | // Static-> |
||
42 | |||
43 | /** |
||
44 | * Constructs a new formatter suitable for given entry. |
||
45 | * @param LogEntry $entry |
||
46 | * @return LogFormatter |
||
47 | */ |
||
48 | public static function newFromEntry( LogEntry $entry ) { |
||
49 | global $wgLogActionsHandlers; |
||
50 | $fulltype = $entry->getFullType(); |
||
51 | $wildcard = $entry->getType() . '/*'; |
||
52 | $handler = ''; |
||
53 | |||
54 | if ( isset( $wgLogActionsHandlers[$fulltype] ) ) { |
||
55 | $handler = $wgLogActionsHandlers[$fulltype]; |
||
56 | } elseif ( isset( $wgLogActionsHandlers[$wildcard] ) ) { |
||
57 | $handler = $wgLogActionsHandlers[$wildcard]; |
||
58 | } |
||
59 | |||
60 | if ( $handler !== '' && is_string( $handler ) && class_exists( $handler ) ) { |
||
61 | return new $handler( $entry ); |
||
62 | } |
||
63 | |||
64 | return new LegacyLogFormatter( $entry ); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Handy shortcut for constructing a formatter directly from |
||
69 | * database row. |
||
70 | * @param stdClass|array $row |
||
71 | * @see DatabaseLogEntry::getSelectQueryData |
||
72 | * @return LogFormatter |
||
73 | */ |
||
74 | public static function newFromRow( $row ) { |
||
75 | return self::newFromEntry( DatabaseLogEntry::newFromRow( $row ) ); |
||
76 | } |
||
77 | |||
78 | // Nonstatic-> |
||
79 | |||
80 | /** @var LogEntryBase */ |
||
81 | protected $entry; |
||
82 | |||
83 | /** @var int Constant for handling log_deleted */ |
||
84 | protected $audience = self::FOR_PUBLIC; |
||
85 | |||
86 | /** @var IContextSource Context for logging */ |
||
87 | public $context; |
||
88 | |||
89 | /** @var bool Whether to output user tool links */ |
||
90 | protected $linkFlood = false; |
||
91 | |||
92 | /** |
||
93 | * Set to true if we are constructing a message text that is going to |
||
94 | * be included in page history or send to IRC feed. Links are replaced |
||
95 | * with plaintext or with [[pagename]] kind of syntax, that is parsed |
||
96 | * by page histories and IRC feeds. |
||
97 | * @var string |
||
98 | */ |
||
99 | protected $plaintext = false; |
||
100 | |||
101 | /** @var string */ |
||
102 | protected $irctext = false; |
||
103 | |||
104 | protected function __construct( LogEntry $entry ) { |
||
105 | $this->entry = $entry; |
||
106 | $this->context = RequestContext::getMain(); |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Replace the default context |
||
111 | * @param IContextSource $context |
||
112 | */ |
||
113 | public function setContext( IContextSource $context ) { |
||
114 | $this->context = $context; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Set the visibility restrictions for displaying content. |
||
119 | * If set to public, and an item is deleted, then it will be replaced |
||
120 | * with a placeholder even if the context user is allowed to view it. |
||
121 | * @param int $audience Const self::FOR_THIS_USER or self::FOR_PUBLIC |
||
122 | */ |
||
123 | public function setAudience( $audience ) { |
||
124 | $this->audience = ( $audience == self::FOR_THIS_USER ) |
||
125 | ? self::FOR_THIS_USER |
||
126 | : self::FOR_PUBLIC; |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Check if a log item can be displayed |
||
131 | * @param int $field LogPage::DELETED_* constant |
||
132 | * @return bool |
||
133 | */ |
||
134 | protected function canView( $field ) { |
||
135 | if ( $this->audience == self::FOR_THIS_USER ) { |
||
136 | return LogEventsList::userCanBitfield( |
||
137 | $this->entry->getDeleted(), $field, $this->context->getUser() ); |
||
138 | } else { |
||
139 | return !$this->entry->isDeleted( $field ); |
||
140 | } |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * If set to true, will produce user tool links after |
||
145 | * the user name. This should be replaced with generic |
||
146 | * CSS/JS solution. |
||
147 | * @param bool $value |
||
148 | */ |
||
149 | public function setShowUserToolLinks( $value ) { |
||
150 | $this->linkFlood = $value; |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * Ugly hack to produce plaintext version of the message. |
||
155 | * Usually you also want to set extraneous request context |
||
156 | * to avoid formatting for any particular user. |
||
157 | * @see getActionText() |
||
158 | * @return string Plain text |
||
159 | */ |
||
160 | public function getPlainActionText() { |
||
161 | $this->plaintext = true; |
||
162 | $text = $this->getActionText(); |
||
163 | $this->plaintext = false; |
||
164 | |||
165 | return $text; |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * Even uglier hack to maintain backwards compatibilty with IRC bots |
||
170 | * (bug 34508). |
||
171 | * @see getActionText() |
||
172 | * @return string Text |
||
173 | */ |
||
174 | public function getIRCActionComment() { |
||
175 | $actionComment = $this->getIRCActionText(); |
||
176 | $comment = $this->entry->getComment(); |
||
177 | |||
178 | if ( $comment != '' ) { |
||
179 | if ( $actionComment == '' ) { |
||
180 | $actionComment = $comment; |
||
181 | } else { |
||
182 | $actionComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment; |
||
183 | } |
||
184 | } |
||
185 | |||
186 | return $actionComment; |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Even uglier hack to maintain backwards compatibilty with IRC bots |
||
191 | * (bug 34508). |
||
192 | * @see getActionText() |
||
193 | * @return string Text |
||
194 | */ |
||
195 | public function getIRCActionText() { |
||
196 | global $wgContLang; |
||
197 | |||
198 | $this->plaintext = true; |
||
199 | $this->irctext = true; |
||
200 | |||
201 | $entry = $this->entry; |
||
202 | $parameters = $entry->getParameters(); |
||
203 | // @see LogPage::actionText() |
||
204 | // Text of title the action is aimed at. |
||
205 | $target = $entry->getTarget()->getPrefixedText(); |
||
206 | $text = null; |
||
207 | switch ( $entry->getType() ) { |
||
208 | case 'move': |
||
209 | switch ( $entry->getSubtype() ) { |
||
210 | case 'move': |
||
211 | $movesource = $parameters['4::target']; |
||
212 | $text = wfMessage( '1movedto2' ) |
||
213 | ->rawParams( $target, $movesource )->inContentLanguage()->escaped(); |
||
214 | break; |
||
215 | case 'move_redir': |
||
216 | $movesource = $parameters['4::target']; |
||
217 | $text = wfMessage( '1movedto2_redir' ) |
||
218 | ->rawParams( $target, $movesource )->inContentLanguage()->escaped(); |
||
219 | break; |
||
220 | case 'move-noredirect': |
||
221 | break; |
||
222 | case 'move_redir-noredirect': |
||
223 | break; |
||
224 | } |
||
225 | break; |
||
226 | |||
227 | View Code Duplication | case 'delete': |
|
228 | switch ( $entry->getSubtype() ) { |
||
229 | case 'delete': |
||
230 | $text = wfMessage( 'deletedarticle' ) |
||
231 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
232 | break; |
||
233 | case 'restore': |
||
234 | $text = wfMessage( 'undeletedarticle' ) |
||
235 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
236 | break; |
||
237 | // @codingStandardsIgnoreStart Long line |
||
238 | //case 'revision': // Revision deletion |
||
239 | //case 'event': // Log deletion |
||
240 | // see https://github.com/wikimedia/mediawiki/commit/a9c243b7b5289dad204278dbe7ed571fd914e395 |
||
241 | //default: |
||
242 | // @codingStandardsIgnoreEnd |
||
243 | } |
||
244 | break; |
||
245 | |||
246 | case 'patrol': |
||
247 | // @codingStandardsIgnoreStart Long line |
||
248 | // https://github.com/wikimedia/mediawiki/commit/1a05f8faf78675dc85984f27f355b8825b43efff |
||
249 | // @codingStandardsIgnoreEnd |
||
250 | // Create a diff link to the patrolled revision |
||
251 | if ( $entry->getSubtype() === 'patrol' ) { |
||
252 | $diffLink = htmlspecialchars( |
||
253 | wfMessage( 'patrol-log-diff', $parameters['4::curid'] ) |
||
254 | ->inContentLanguage()->text() ); |
||
255 | $text = wfMessage( 'patrol-log-line', $diffLink, "[[$target]]", "" ) |
||
256 | ->inContentLanguage()->text(); |
||
257 | } else { |
||
258 | // broken?? |
||
259 | } |
||
260 | break; |
||
261 | |||
262 | case 'protect': |
||
263 | switch ( $entry->getSubtype() ) { |
||
264 | case 'protect': |
||
265 | $text = wfMessage( 'protectedarticle' ) |
||
266 | ->rawParams( $target . ' ' . $parameters['4::description'] )->inContentLanguage()->escaped(); |
||
267 | break; |
||
268 | case 'unprotect': |
||
269 | $text = wfMessage( 'unprotectedarticle' ) |
||
270 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
271 | break; |
||
272 | case 'modify': |
||
273 | $text = wfMessage( 'modifiedarticleprotection' ) |
||
274 | ->rawParams( $target . ' ' . $parameters['4::description'] )->inContentLanguage()->escaped(); |
||
275 | break; |
||
276 | case 'move_prot': |
||
277 | $text = wfMessage( 'movedarticleprotection' ) |
||
278 | ->rawParams( $target, $parameters['4::oldtitle'] )->inContentLanguage()->escaped(); |
||
279 | break; |
||
280 | } |
||
281 | break; |
||
282 | |||
283 | case 'newusers': |
||
284 | switch ( $entry->getSubtype() ) { |
||
285 | case 'newusers': |
||
286 | case 'create': |
||
287 | $text = wfMessage( 'newuserlog-create-entry' ) |
||
288 | ->inContentLanguage()->escaped(); |
||
289 | break; |
||
290 | case 'create2': |
||
291 | case 'byemail': |
||
292 | $text = wfMessage( 'newuserlog-create2-entry' ) |
||
293 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
294 | break; |
||
295 | case 'autocreate': |
||
296 | $text = wfMessage( 'newuserlog-autocreate-entry' ) |
||
297 | ->inContentLanguage()->escaped(); |
||
298 | break; |
||
299 | } |
||
300 | break; |
||
301 | |||
302 | View Code Duplication | case 'upload': |
|
303 | switch ( $entry->getSubtype() ) { |
||
304 | case 'upload': |
||
305 | $text = wfMessage( 'uploadedimage' ) |
||
306 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
307 | break; |
||
308 | case 'overwrite': |
||
309 | $text = wfMessage( 'overwroteimage' ) |
||
310 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
311 | break; |
||
312 | } |
||
313 | break; |
||
314 | |||
315 | case 'rights': |
||
316 | View Code Duplication | if ( count( $parameters['4::oldgroups'] ) ) { |
|
317 | $oldgroups = implode( ', ', $parameters['4::oldgroups'] ); |
||
318 | } else { |
||
319 | $oldgroups = wfMessage( 'rightsnone' )->inContentLanguage()->escaped(); |
||
320 | } |
||
321 | View Code Duplication | if ( count( $parameters['5::newgroups'] ) ) { |
|
322 | $newgroups = implode( ', ', $parameters['5::newgroups'] ); |
||
323 | } else { |
||
324 | $newgroups = wfMessage( 'rightsnone' )->inContentLanguage()->escaped(); |
||
325 | } |
||
326 | switch ( $entry->getSubtype() ) { |
||
327 | case 'rights': |
||
328 | $text = wfMessage( 'rightslogentry' ) |
||
329 | ->rawParams( $target, $oldgroups, $newgroups )->inContentLanguage()->escaped(); |
||
330 | break; |
||
331 | case 'autopromote': |
||
332 | $text = wfMessage( 'rightslogentry-autopromote' ) |
||
333 | ->rawParams( $target, $oldgroups, $newgroups )->inContentLanguage()->escaped(); |
||
334 | break; |
||
335 | } |
||
336 | break; |
||
337 | |||
338 | case 'merge': |
||
339 | $text = wfMessage( 'pagemerge-logentry' ) |
||
340 | ->rawParams( $target, $parameters['4::dest'], $parameters['5::mergepoint'] ) |
||
341 | ->inContentLanguage()->escaped(); |
||
342 | break; |
||
343 | |||
344 | case 'block': |
||
345 | switch ( $entry->getSubtype() ) { |
||
346 | case 'block': |
||
347 | // Keep compatibility with extensions by checking for |
||
348 | // new key (5::duration/6::flags) or old key (0/optional 1) |
||
349 | if ( $entry->isLegacy() ) { |
||
350 | $rawDuration = $parameters[0]; |
||
351 | $rawFlags = isset( $parameters[1] ) ? $parameters[1] : ''; |
||
352 | } else { |
||
353 | $rawDuration = $parameters['5::duration']; |
||
354 | $rawFlags = $parameters['6::flags']; |
||
355 | } |
||
356 | $duration = $wgContLang->translateBlockExpiry( $rawDuration ); |
||
357 | $flags = BlockLogFormatter::formatBlockFlags( $rawFlags, $wgContLang ); |
||
358 | $text = wfMessage( 'blocklogentry' ) |
||
359 | ->rawParams( $target, $duration, $flags )->inContentLanguage()->escaped(); |
||
360 | break; |
||
361 | case 'unblock': |
||
362 | $text = wfMessage( 'unblocklogentry' ) |
||
363 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
364 | break; |
||
365 | case 'reblock': |
||
366 | $duration = $wgContLang->translateBlockExpiry( $parameters['5::duration'] ); |
||
367 | $flags = BlockLogFormatter::formatBlockFlags( $parameters['6::flags'], $wgContLang ); |
||
368 | $text = wfMessage( 'reblock-logentry' ) |
||
369 | ->rawParams( $target, $duration, $flags )->inContentLanguage()->escaped(); |
||
370 | break; |
||
371 | } |
||
372 | break; |
||
373 | |||
374 | View Code Duplication | case 'import': |
|
375 | switch ( $entry->getSubtype() ) { |
||
376 | case 'upload': |
||
377 | $text = wfMessage( 'import-logentry-upload' ) |
||
378 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
379 | break; |
||
380 | case 'interwiki': |
||
381 | $text = wfMessage( 'import-logentry-interwiki' ) |
||
382 | ->rawParams( $target )->inContentLanguage()->escaped(); |
||
383 | break; |
||
384 | } |
||
385 | break; |
||
386 | // case 'suppress' --private log -- aaron (so we know who to blame in a few years :-D) |
||
387 | // default: |
||
388 | } |
||
389 | if ( is_null( $text ) ) { |
||
390 | $text = $this->getPlainActionText(); |
||
391 | } |
||
392 | |||
393 | $this->plaintext = false; |
||
394 | $this->irctext = false; |
||
395 | |||
396 | return $text; |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Gets the log action, including username. |
||
401 | * @return string HTML |
||
402 | */ |
||
403 | public function getActionText() { |
||
404 | if ( $this->canView( LogPage::DELETED_ACTION ) ) { |
||
405 | $element = $this->getActionMessage(); |
||
406 | if ( $element instanceof Message ) { |
||
407 | $element = $this->plaintext ? $element->text() : $element->escaped(); |
||
408 | } |
||
409 | if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) { |
||
410 | $element = $this->styleRestricedElement( $element ); |
||
411 | } |
||
412 | } else { |
||
413 | $sep = $this->msg( 'word-separator' ); |
||
414 | $sep = $this->plaintext ? $sep->text() : $sep->escaped(); |
||
415 | $performer = $this->getPerformerElement(); |
||
416 | $element = $performer . $sep . $this->getRestrictedElement( 'rev-deleted-event' ); |
||
417 | } |
||
418 | |||
419 | return $element; |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * Returns a sentence describing the log action. Usually |
||
424 | * a Message object is returned, but old style log types |
||
425 | * and entries might return pre-escaped HTML string. |
||
426 | * @return Message|string Pre-escaped HTML |
||
427 | */ |
||
428 | protected function getActionMessage() { |
||
429 | $message = $this->msg( $this->getMessageKey() ); |
||
430 | $message->params( $this->getMessageParameters() ); |
||
431 | |||
432 | return $message; |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Returns a key to be used for formatting the action sentence. |
||
437 | * Default is logentry-TYPE-SUBTYPE for modern logs. Legacy log |
||
438 | * types will use custom keys, and subclasses can also alter the |
||
439 | * key depending on the entry itself. |
||
440 | * @return string Message key |
||
441 | */ |
||
442 | protected function getMessageKey() { |
||
443 | $type = $this->entry->getType(); |
||
444 | $subtype = $this->entry->getSubtype(); |
||
445 | |||
446 | return "logentry-$type-$subtype"; |
||
447 | } |
||
448 | |||
449 | /** |
||
450 | * Returns extra links that comes after the action text, like "revert", etc. |
||
451 | * |
||
452 | * @return string |
||
453 | */ |
||
454 | public function getActionLinks() { |
||
455 | return ''; |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * Extracts the optional extra parameters for use in action messages. |
||
460 | * The array indexes start from number 3. |
||
461 | * @return array |
||
462 | */ |
||
463 | protected function extractParameters() { |
||
464 | $entry = $this->entry; |
||
465 | $params = []; |
||
466 | |||
467 | if ( $entry->isLegacy() ) { |
||
468 | foreach ( $entry->getParameters() as $index => $value ) { |
||
469 | $params[$index + 3] = $value; |
||
470 | } |
||
471 | } |
||
472 | |||
473 | // Filter out parameters which are not in format #:foo |
||
474 | foreach ( $entry->getParameters() as $key => $value ) { |
||
475 | if ( strpos( $key, ':' ) === false ) { |
||
476 | continue; |
||
477 | } |
||
478 | list( $index, $type, ) = explode( ':', $key, 3 ); |
||
479 | if ( ctype_digit( $index ) ) { |
||
480 | $params[$index - 1] = $this->formatParameterValue( $type, $value ); |
||
481 | } |
||
482 | } |
||
483 | |||
484 | /* Message class doesn't like non consecutive numbering. |
||
485 | * Fill in missing indexes with empty strings to avoid |
||
486 | * incorrect renumbering. |
||
487 | */ |
||
488 | if ( count( $params ) ) { |
||
489 | $max = max( array_keys( $params ) ); |
||
490 | // index 0 to 2 are added in getMessageParameters |
||
491 | for ( $i = 3; $i < $max; $i++ ) { |
||
492 | if ( !isset( $params[$i] ) ) { |
||
493 | $params[$i] = ''; |
||
494 | } |
||
495 | } |
||
496 | } |
||
497 | |||
498 | return $params; |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * Formats parameters intented for action message from |
||
503 | * array of all parameters. There are three hardcoded |
||
504 | * parameters (array is zero-indexed, this list not): |
||
505 | * - 1: user name with premade link |
||
506 | * - 2: usable for gender magic function |
||
507 | * - 3: target page with premade link |
||
508 | * @return array |
||
509 | */ |
||
510 | protected function getMessageParameters() { |
||
511 | if ( isset( $this->parsedParameters ) ) { |
||
512 | return $this->parsedParameters; |
||
513 | } |
||
514 | |||
515 | $entry = $this->entry; |
||
516 | $params = $this->extractParameters(); |
||
517 | $params[0] = Message::rawParam( $this->getPerformerElement() ); |
||
518 | $params[1] = $this->canView( LogPage::DELETED_USER ) ? $entry->getPerformer()->getName() : ''; |
||
519 | $params[2] = Message::rawParam( $this->makePageLink( $entry->getTarget() ) ); |
||
520 | |||
521 | // Bad things happens if the numbers are not in correct order |
||
522 | ksort( $params ); |
||
523 | |||
524 | $this->parsedParameters = $params; |
||
525 | return $this->parsedParameters; |
||
526 | } |
||
527 | |||
528 | /** |
||
529 | * Formats parameters values dependent to their type |
||
530 | * @param string $type The type of the value. |
||
531 | * Valid are currently: |
||
532 | * * - (empty) or plain: The value is returned as-is |
||
533 | * * raw: The value will be added to the log message |
||
534 | * as raw parameter (e.g. no escaping) |
||
535 | * Use this only if there is no other working |
||
536 | * type like user-link or title-link |
||
537 | * * msg: The value is a message-key, the output is |
||
538 | * the message in user language |
||
539 | * * msg-content: The value is a message-key, the output |
||
540 | * is the message in content language |
||
541 | * * user: The value is a user name, e.g. for GENDER |
||
542 | * * user-link: The value is a user name, returns a |
||
543 | * link for the user |
||
544 | * * title: The value is a page title, |
||
545 | * returns name of page |
||
546 | * * title-link: The value is a page title, |
||
547 | * returns link to this page |
||
548 | * * number: Format value as number |
||
549 | * * list: Format value as a comma-separated list |
||
550 | * @param mixed $value The parameter value that should be formatted |
||
551 | * @return string|array Formated value |
||
552 | * @since 1.21 |
||
553 | */ |
||
554 | protected function formatParameterValue( $type, $value ) { |
||
555 | $saveLinkFlood = $this->linkFlood; |
||
556 | |||
557 | switch ( strtolower( trim( $type ) ) ) { |
||
558 | case 'raw': |
||
559 | $value = Message::rawParam( $value ); |
||
560 | break; |
||
561 | case 'list': |
||
562 | $value = $this->context->getLanguage()->commaList( $value ); |
||
563 | break; |
||
564 | case 'msg': |
||
565 | $value = $this->msg( $value )->text(); |
||
566 | break; |
||
567 | case 'msg-content': |
||
568 | $value = $this->msg( $value )->inContentLanguage()->text(); |
||
569 | break; |
||
570 | case 'number': |
||
571 | $value = Message::numParam( $value ); |
||
572 | break; |
||
573 | case 'user': |
||
574 | $user = User::newFromName( $value ); |
||
575 | $value = $user->getName(); |
||
576 | break; |
||
577 | case 'user-link': |
||
578 | $this->setShowUserToolLinks( false ); |
||
579 | |||
580 | $user = User::newFromName( $value ); |
||
581 | $value = Message::rawParam( $this->makeUserLink( $user ) ); |
||
0 ignored issues
–
show
|
|||
582 | |||
583 | $this->setShowUserToolLinks( $saveLinkFlood ); |
||
584 | break; |
||
585 | case 'title': |
||
586 | $title = Title::newFromText( $value ); |
||
587 | $value = $title->getPrefixedText(); |
||
588 | break; |
||
589 | case 'title-link': |
||
590 | $title = Title::newFromText( $value ); |
||
591 | $value = Message::rawParam( $this->makePageLink( $title ) ); |
||
592 | break; |
||
593 | case 'plain': |
||
594 | // Plain text, nothing to do |
||
595 | default: |
||
596 | // Catch other types and use the old behavior (return as-is) |
||
597 | } |
||
598 | |||
599 | return $value; |
||
600 | } |
||
601 | |||
602 | /** |
||
603 | * Helper to make a link to the page, taking the plaintext |
||
604 | * value in consideration. |
||
605 | * @param Title $title The page |
||
606 | * @param array $parameters Query parameters |
||
607 | * @param string|null $html Linktext of the link as raw html |
||
608 | * @throws MWException |
||
609 | * @return string |
||
610 | */ |
||
611 | protected function makePageLink( Title $title = null, $parameters = [], $html = null ) { |
||
612 | View Code Duplication | if ( !$this->plaintext ) { |
|
613 | $link = Linker::link( $title, $html, [], $parameters ); |
||
614 | } else { |
||
615 | if ( !$title instanceof Title ) { |
||
616 | throw new MWException( "Expected title, got null" ); |
||
617 | } |
||
618 | $link = '[[' . $title->getPrefixedText() . ']]'; |
||
619 | } |
||
620 | |||
621 | return $link; |
||
622 | } |
||
623 | |||
624 | /** |
||
625 | * Provides the name of the user who performed the log action. |
||
626 | * Used as part of log action message or standalone, depending |
||
627 | * which parts of the log entry has been hidden. |
||
628 | * @return string |
||
629 | */ |
||
630 | View Code Duplication | public function getPerformerElement() { |
|
631 | if ( $this->canView( LogPage::DELETED_USER ) ) { |
||
632 | $performer = $this->entry->getPerformer(); |
||
633 | $element = $this->makeUserLink( $performer ); |
||
634 | if ( $this->entry->isDeleted( LogPage::DELETED_USER ) ) { |
||
635 | $element = $this->styleRestricedElement( $element ); |
||
636 | } |
||
637 | } else { |
||
638 | $element = $this->getRestrictedElement( 'rev-deleted-user' ); |
||
639 | } |
||
640 | |||
641 | return $element; |
||
642 | } |
||
643 | |||
644 | /** |
||
645 | * Gets the user provided comment |
||
646 | * @return string HTML |
||
647 | */ |
||
648 | View Code Duplication | public function getComment() { |
|
649 | if ( $this->canView( LogPage::DELETED_COMMENT ) ) { |
||
650 | $comment = Linker::commentBlock( $this->entry->getComment() ); |
||
651 | // No hard coded spaces thanx |
||
652 | $element = ltrim( $comment ); |
||
653 | if ( $this->entry->isDeleted( LogPage::DELETED_COMMENT ) ) { |
||
654 | $element = $this->styleRestricedElement( $element ); |
||
655 | } |
||
656 | } else { |
||
657 | $element = $this->getRestrictedElement( 'rev-deleted-comment' ); |
||
658 | } |
||
659 | |||
660 | return $element; |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Helper method for displaying restricted element. |
||
665 | * @param string $message |
||
666 | * @return string HTML or wiki text |
||
667 | */ |
||
668 | protected function getRestrictedElement( $message ) { |
||
669 | if ( $this->plaintext ) { |
||
670 | return $this->msg( $message )->text(); |
||
671 | } |
||
672 | |||
673 | $content = $this->msg( $message )->escaped(); |
||
674 | $attribs = [ 'class' => 'history-deleted' ]; |
||
675 | |||
676 | return Html::rawElement( 'span', $attribs, $content ); |
||
677 | } |
||
678 | |||
679 | /** |
||
680 | * Helper method for styling restricted element. |
||
681 | * @param string $content |
||
682 | * @return string HTML or wiki text |
||
683 | */ |
||
684 | protected function styleRestricedElement( $content ) { |
||
685 | if ( $this->plaintext ) { |
||
686 | return $content; |
||
687 | } |
||
688 | $attribs = [ 'class' => 'history-deleted' ]; |
||
689 | |||
690 | return Html::rawElement( 'span', $attribs, $content ); |
||
691 | } |
||
692 | |||
693 | /** |
||
694 | * Shortcut for wfMessage which honors local context. |
||
695 | * @param string $key |
||
696 | * @return Message |
||
697 | */ |
||
698 | protected function msg( $key ) { |
||
699 | return $this->context->msg( $key ); |
||
700 | } |
||
701 | |||
702 | protected function makeUserLink( User $user, $toolFlags = 0 ) { |
||
703 | if ( $this->plaintext ) { |
||
704 | $element = $user->getName(); |
||
705 | } else { |
||
706 | $element = Linker::userLink( |
||
707 | $user->getId(), |
||
708 | $user->getName() |
||
709 | ); |
||
710 | |||
711 | if ( $this->linkFlood ) { |
||
712 | $element .= Linker::userToolLinks( |
||
713 | $user->getId(), |
||
714 | $user->getName(), |
||
715 | true, // redContribsWhenNoEdits |
||
716 | $toolFlags, |
||
717 | $user->getEditCount() |
||
718 | ); |
||
719 | } |
||
720 | } |
||
721 | |||
722 | return $element; |
||
723 | } |
||
724 | |||
725 | /** |
||
726 | * @return array Array of titles that should be preloaded with LinkBatch |
||
727 | */ |
||
728 | public function getPreloadTitles() { |
||
729 | return []; |
||
730 | } |
||
731 | |||
732 | /** |
||
733 | * @return array Output of getMessageParameters() for testing |
||
734 | */ |
||
735 | public function getMessageParametersForTesting() { |
||
736 | // This function was added because getMessageParameters() is |
||
737 | // protected and a change from protected to public caused |
||
738 | // problems with extensions |
||
739 | return $this->getMessageParameters(); |
||
740 | } |
||
741 | |||
742 | /** |
||
743 | * Get the array of parameters, converted from legacy format if necessary. |
||
744 | * @since 1.25 |
||
745 | * @return array |
||
746 | */ |
||
747 | protected function getParametersForApi() { |
||
748 | return $this->entry->getParameters(); |
||
749 | } |
||
750 | |||
751 | /** |
||
752 | * Format parameters for API output |
||
753 | * |
||
754 | * The result array should generally map named keys to values. Index and |
||
755 | * type should be omitted, e.g. "4::foo" should be returned as "foo" in the |
||
756 | * output. Values should generally be unformatted. |
||
757 | * |
||
758 | * Renames or removals of keys besides from the legacy numeric format to |
||
759 | * modern named style should be avoided. Any renames should be announced to |
||
760 | * the mediawiki-api-announce mailing list. |
||
761 | * |
||
762 | * @since 1.25 |
||
763 | * @return array |
||
764 | */ |
||
765 | public function formatParametersForApi() { |
||
766 | $logParams = []; |
||
767 | foreach ( $this->getParametersForApi() as $key => $value ) { |
||
768 | $vals = explode( ':', $key, 3 ); |
||
769 | if ( count( $vals ) !== 3 ) { |
||
770 | $logParams[$key] = $value; |
||
771 | continue; |
||
772 | } |
||
773 | $logParams += $this->formatParameterValueForApi( $vals[2], $vals[1], $value ); |
||
774 | } |
||
775 | ApiResult::setIndexedTagName( $logParams, 'param' ); |
||
776 | ApiResult::setArrayType( $logParams, 'assoc' ); |
||
777 | |||
778 | return $logParams; |
||
779 | } |
||
780 | |||
781 | /** |
||
782 | * Format a single parameter value for API output |
||
783 | * |
||
784 | * @since 1.25 |
||
785 | * @param string $name |
||
786 | * @param string $type |
||
787 | * @param string $value |
||
788 | * @return array |
||
789 | */ |
||
790 | protected function formatParameterValueForApi( $name, $type, $value ) { |
||
791 | $type = strtolower( trim( $type ) ); |
||
792 | switch ( $type ) { |
||
793 | case 'bool': |
||
794 | $value = (bool)$value; |
||
795 | break; |
||
796 | |||
797 | case 'number': |
||
798 | if ( ctype_digit( $value ) || is_int( $value ) ) { |
||
799 | $value = (int)$value; |
||
800 | } else { |
||
801 | $value = (float)$value; |
||
802 | } |
||
803 | break; |
||
804 | |||
805 | case 'array': |
||
806 | case 'assoc': |
||
807 | case 'kvp': |
||
808 | if ( is_array( $value ) ) { |
||
809 | ApiResult::setArrayType( $value, $type ); |
||
810 | } |
||
811 | break; |
||
812 | |||
813 | case 'timestamp': |
||
814 | $value = wfTimestamp( TS_ISO_8601, $value ); |
||
815 | break; |
||
816 | |||
817 | case 'msg': |
||
818 | case 'msg-content': |
||
819 | $msg = $this->msg( $value ); |
||
820 | if ( $type === 'msg-content' ) { |
||
821 | $msg->inContentLanguage(); |
||
822 | } |
||
823 | $value = []; |
||
824 | $value["{$name}_key"] = $msg->getKey(); |
||
825 | if ( $msg->getParams() ) { |
||
826 | $value["{$name}_params"] = $msg->getParams(); |
||
827 | } |
||
828 | $value["{$name}_text"] = $msg->text(); |
||
829 | return $value; |
||
830 | |||
831 | case 'title': |
||
832 | case 'title-link': |
||
833 | $title = Title::newFromText( $value ); |
||
834 | if ( $title ) { |
||
835 | $value = []; |
||
836 | ApiQueryBase::addTitleInfo( $value, $title, "{$name}_" ); |
||
837 | } |
||
838 | return $value; |
||
839 | |||
840 | case 'user': |
||
841 | case 'user-link': |
||
842 | $user = User::newFromName( $value ); |
||
843 | if ( $user ) { |
||
844 | $value = $user->getName(); |
||
845 | } |
||
846 | break; |
||
847 | |||
848 | default: |
||
849 | // do nothing |
||
850 | break; |
||
851 | } |
||
852 | |||
853 | return [ $name => $value ]; |
||
854 | } |
||
855 | } |
||
856 | |||
857 | /** |
||
858 | * This class formats all log entries for log types |
||
859 | * which have not been converted to the new system. |
||
860 | * This is not about old log entries which store |
||
861 | * parameters in a different format - the new |
||
862 | * LogFormatter classes have code to support formatting |
||
863 | * those too. |
||
864 | * @since 1.19 |
||
865 | */ |
||
866 | class LegacyLogFormatter extends LogFormatter { |
||
867 | /** |
||
868 | * Backward compatibility for extension changing the comment from |
||
869 | * the LogLine hook. This will be set by the first call on getComment(), |
||
870 | * then it might be modified by the hook when calling getActionLinks(), |
||
871 | * so that the modified value will be returned when calling getComment() |
||
872 | * a second time. |
||
873 | * |
||
874 | * @var string|null |
||
875 | */ |
||
876 | private $comment = null; |
||
877 | |||
878 | /** |
||
879 | * Cache for the result of getActionLinks() so that it does not need to |
||
880 | * run multiple times depending on the order that getComment() and |
||
881 | * getActionLinks() are called. |
||
882 | * |
||
883 | * @var string|null |
||
884 | */ |
||
885 | private $revert = null; |
||
886 | |||
887 | public function getComment() { |
||
888 | if ( $this->comment === null ) { |
||
889 | $this->comment = parent::getComment(); |
||
890 | } |
||
891 | |||
892 | // Make sure we execute the LogLine hook so that we immediately return |
||
893 | // the correct value. |
||
894 | if ( $this->revert === null ) { |
||
895 | $this->getActionLinks(); |
||
896 | } |
||
897 | |||
898 | return $this->comment; |
||
899 | } |
||
900 | |||
901 | protected function getActionMessage() { |
||
902 | $entry = $this->entry; |
||
903 | $action = LogPage::actionText( |
||
904 | $entry->getType(), |
||
905 | $entry->getSubtype(), |
||
906 | $entry->getTarget(), |
||
907 | $this->plaintext ? null : $this->context->getSkin(), |
||
908 | (array)$entry->getParameters(), |
||
909 | !$this->plaintext // whether to filter [[]] links |
||
910 | ); |
||
911 | |||
912 | $performer = $this->getPerformerElement(); |
||
913 | if ( !$this->irctext ) { |
||
914 | $sep = $this->msg( 'word-separator' ); |
||
915 | $sep = $this->plaintext ? $sep->text() : $sep->escaped(); |
||
916 | $action = $performer . $sep . $action; |
||
917 | } |
||
918 | |||
919 | return $action; |
||
920 | } |
||
921 | |||
922 | public function getActionLinks() { |
||
923 | if ( $this->revert !== null ) { |
||
924 | return $this->revert; |
||
925 | } |
||
926 | |||
927 | if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) { |
||
928 | $this->revert = ''; |
||
929 | return $this->revert; |
||
930 | } |
||
931 | |||
932 | $title = $this->entry->getTarget(); |
||
933 | $type = $this->entry->getType(); |
||
934 | $subtype = $this->entry->getSubtype(); |
||
935 | |||
936 | // Do nothing. The implementation is handled by the hook modifiying the |
||
937 | // passed-by-ref parameters. This also changes the default value so that |
||
938 | // getComment() and getActionLinks() do not call them indefinitely. |
||
939 | $this->revert = ''; |
||
940 | |||
941 | // This is to populate the $comment member of this instance so that it |
||
942 | // can be modified when calling the hook just below. |
||
943 | if ( $this->comment === null ) { |
||
944 | $this->getComment(); |
||
945 | } |
||
946 | |||
947 | $params = $this->entry->getParameters(); |
||
948 | |||
949 | Hooks::run( 'LogLine', [ $type, $subtype, $title, $params, |
||
950 | &$this->comment, &$this->revert, $this->entry->getTimestamp() ] ); |
||
951 | |||
952 | return $this->revert; |
||
953 | } |
||
954 | } |
||
955 |
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.