Completed
Branch master (939199)
by
unknown
39:35
created

includes/logging/LogPage.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Contain log classes
4
 *
5
 * Copyright © 2002, 2004 Brion Vibber <[email protected]>
6
 * https://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
	public $updateRecentChanges;
44
45
	/** @var bool */
46
	public $sendToUDP;
47
48
	/** @var string Plaintext version of the message for IRC */
49
	private $ircActionText;
50
51
	/** @var string Plaintext version of the message */
52
	private $actionText;
53
54
	/** @var string One of '', 'block', 'protect', 'rights', 'delete',
55
	 *    'upload', 'move'
56
	 */
57
	private $type;
58
59
	/** @var string One of '', 'block', 'protect', 'rights', 'delete',
60
	 *   'upload', 'move', 'move_redir' */
61
	private $action;
62
63
	/** @var string Comment associated with action */
64
	private $comment;
65
66
	/** @var string Blob made of a parameters array */
67
	private $params;
68
69
	/** @var User The user doing the action */
70
	private $doer;
71
72
	/** @var Title */
73
	private $target;
74
75
	/**
76
	 * Constructor
77
	 *
78
	 * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
79
	 *   'upload', 'move'
80
	 * @param bool $rc Whether to update recent changes as well as the logging table
81
	 * @param string $udp Pass 'UDP' to send to the UDP feed if NOT sent to RC
82
	 */
83
	public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
84
		$this->type = $type;
85
		$this->updateRecentChanges = $rc;
86
		$this->sendToUDP = ( $udp == 'UDP' );
87
	}
88
89
	/**
90
	 * @return int The log_id of the inserted log entry
91
	 */
92
	protected function saveContent() {
93
		global $wgLogRestrictions;
94
95
		$dbw = wfGetDB( DB_MASTER );
96
		$log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
97
98
		// @todo FIXME private/protected/public property?
99
		$this->timestamp = $now = wfTimestampNow();
100
		$data = [
101
			'log_id' => $log_id,
102
			'log_type' => $this->type,
103
			'log_action' => $this->action,
104
			'log_timestamp' => $dbw->timestamp( $now ),
105
			'log_user' => $this->doer->getId(),
106
			'log_user_text' => $this->doer->getName(),
107
			'log_namespace' => $this->target->getNamespace(),
108
			'log_title' => $this->target->getDBkey(),
109
			'log_page' => $this->target->getArticleID(),
110
			'log_comment' => $this->comment,
111
			'log_params' => $this->params
112
		];
113
		$dbw->insert( 'logging', $data, __METHOD__ );
114
		$newId = !is_null( $log_id ) ? $log_id : $dbw->insertId();
115
116
		# And update recentchanges
117
		if ( $this->updateRecentChanges ) {
118
			$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
119
120
			RecentChange::notifyLog(
121
				$now, $titleObj, $this->doer, $this->getRcComment(), '',
0 ignored issues
show
It seems like $now defined by wfTimestampNow() on line 99 can also be of type false; however, RecentChange::notifyLog() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

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 returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
122
				$this->type, $this->action, $this->target, $this->comment,
123
				$this->params, $newId, $this->getRcCommentIRC()
124
			);
125
		} elseif ( $this->sendToUDP ) {
126
			# Don't send private logs to UDP
127
			if ( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
128
				return $newId;
129
			}
130
131
			# Notify external application via UDP.
132
			# We send this to IRC but do not want to add it the RC table.
133
			$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
134
			$rc = RecentChange::newLogEntry(
135
				$now, $titleObj, $this->doer, $this->getRcComment(), '',
0 ignored issues
show
It seems like $now defined by wfTimestampNow() on line 99 can also be of type false; however, RecentChange::newLogEntry() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

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 returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
136
				$this->type, $this->action, $this->target, $this->comment,
137
				$this->params, $newId, $this->getRcCommentIRC()
138
			);
139
			$rc->notifyRCFeeds();
140
		}
141
142
		return $newId;
143
	}
144
145
	/**
146
	 * Get the RC comment from the last addEntry() call
147
	 *
148
	 * @return string
149
	 */
150 View Code Duplication
	public function getRcComment() {
151
		$rcComment = $this->actionText;
152
153
		if ( $this->comment != '' ) {
154
			if ( $rcComment == '' ) {
155
				$rcComment = $this->comment;
156
			} else {
157
				$rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
158
					$this->comment;
159
			}
160
		}
161
162
		return $rcComment;
163
	}
164
165
	/**
166
	 * Get the RC comment from the last addEntry() call for IRC
167
	 *
168
	 * @return string
169
	 */
170 View Code Duplication
	public function getRcCommentIRC() {
171
		$rcComment = $this->ircActionText;
172
173
		if ( $this->comment != '' ) {
174
			if ( $rcComment == '' ) {
175
				$rcComment = $this->comment;
176
			} else {
177
				$rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
178
					$this->comment;
179
			}
180
		}
181
182
		return $rcComment;
183
	}
184
185
	/**
186
	 * Get the comment from the last addEntry() call
187
	 * @return string
188
	 */
189
	public function getComment() {
190
		return $this->comment;
191
	}
192
193
	/**
194
	 * Get the list of valid log types
195
	 *
196
	 * @return array Array of strings
197
	 */
198
	public static function validTypes() {
199
		global $wgLogTypes;
200
201
		return $wgLogTypes;
202
	}
203
204
	/**
205
	 * Is $type a valid log type
206
	 *
207
	 * @param string $type Log type to check
208
	 * @return bool
209
	 */
210
	public static function isLogType( $type ) {
211
		return in_array( $type, LogPage::validTypes() );
212
	}
213
214
	/**
215
	 * Generate text for a log entry.
216
	 * Only LogFormatter should call this function.
217
	 *
218
	 * @param string $type Log type
219
	 * @param string $action Log action
220
	 * @param Title|null $title Title object or null
221
	 * @param Skin|null $skin Skin object or null. If null, we want to use the wiki
222
	 *   content language, since that will go to the IRC feed.
223
	 * @param array $params Parameters
224
	 * @param bool $filterWikilinks Whether to filter wiki links
225
	 * @return string HTML
226
	 */
227
	public static function actionText( $type, $action, $title = null, $skin = null,
228
		$params = [], $filterWikilinks = false
229
	) {
230
		global $wgLang, $wgContLang, $wgLogActions;
231
232
		if ( is_null( $skin ) ) {
233
			$langObj = $wgContLang;
234
			$langObjOrNull = null;
235
		} else {
236
			$langObj = $wgLang;
237
			$langObjOrNull = $wgLang;
238
		}
239
240
		$key = "$type/$action";
241
242
		if ( isset( $wgLogActions[$key] ) ) {
243
			if ( is_null( $title ) ) {
244
				$rv = wfMessage( $wgLogActions[$key] )->inLanguage( $langObj )->escaped();
245
			} else {
246
				$titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
247
248
				if ( count( $params ) == 0 ) {
249
					$rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )
250
						->inLanguage( $langObj )->escaped();
251
				} else {
252
					array_unshift( $params, $titleLink );
253
254
					$rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )
255
							->inLanguage( $langObj )->escaped();
256
				}
257
			}
258
		} else {
259
			global $wgLogActionsHandlers;
260
261
			if ( isset( $wgLogActionsHandlers[$key] ) ) {
262
				$args = func_get_args();
263
				$rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
264
			} else {
265
				wfDebug( "LogPage::actionText - unknown action $key\n" );
266
				$rv = "$action";
267
			}
268
		}
269
270
		// For the perplexed, this feature was added in r7855 by Erik.
271
		// The feature was added because we liked adding [[$1]] in our log entries
272
		// but the log entries are parsed as Wikitext on RecentChanges but as HTML
273
		// on Special:Log. The hack is essentially that [[$1]] represented a link
274
		// to the title in question. The first parameter to the HTML version (Special:Log)
275
		// is that link in HTML form, and so this just gets rid of the ugly [[]].
276
		// However, this is a horrible hack and it doesn't work like you expect if, say,
277
		// you want to link to something OTHER than the title of the log entry.
278
		// The real problem, which Erik was trying to fix (and it sort-of works now) is
279
		// that the same messages are being treated as both wikitext *and* HTML.
280
		if ( $filterWikilinks ) {
281
			$rv = str_replace( '[[', '', $rv );
282
			$rv = str_replace( ']]', '', $rv );
283
		}
284
285
		return $rv;
286
	}
287
288
	/**
289
	 * @todo Document
290
	 * @param string $type
291
	 * @param Language|null $lang
292
	 * @param Title $title
293
	 * @param array $params
294
	 * @return string
295
	 */
296
	protected static function getTitleLink( $type, $lang, $title, &$params ) {
297
		if ( !$lang ) {
298
			return $title->getPrefixedText();
299
		}
300
301
		if ( $title->isSpecialPage() ) {
302
			list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
303
304
			# Use the language name for log titles, rather than Log/X
305
			if ( $name == 'Log' ) {
306
				$logPage = new LogPage( $par );
307
				$titleLink = Linker::link( $title, $logPage->getName()->escaped() );
308
				$titleLink = wfMessage( 'parentheses' )
309
					->inLanguage( $lang )
310
					->rawParams( $titleLink )
311
					->escaped();
312
			} else {
313
				$titleLink = Linker::link( $title );
314
			}
315
		} else {
316
			$titleLink = Linker::link( $title );
317
		}
318
319
		return $titleLink;
320
	}
321
322
	/**
323
	 * Add a log entry
324
	 *
325
	 * @param string $action One of '', 'block', 'protect', 'rights', 'delete',
326
	 *   'upload', 'move', 'move_redir'
327
	 * @param Title $target Title object
328
	 * @param string $comment Description associated
329
	 * @param array $params Parameters passed later to wfMessage function
330
	 * @param null|int|User $doer The user doing the action. null for $wgUser
331
	 *
332
	 * @return int The log_id of the inserted log entry
333
	 */
334
	public function addEntry( $action, $target, $comment, $params = [], $doer = null ) {
335
		global $wgContLang;
336
337
		if ( !is_array( $params ) ) {
338
			$params = [ $params ];
339
		}
340
341
		if ( $comment === null ) {
342
			$comment = '';
343
		}
344
345
		# Trim spaces on user supplied text
346
		$comment = trim( $comment );
347
348
		# Truncate for whole multibyte characters.
349
		$comment = $wgContLang->truncate( $comment, 255 );
350
351
		$this->action = $action;
352
		$this->target = $target;
353
		$this->comment = $comment;
354
		$this->params = LogPage::makeParamBlob( $params );
355
356
		if ( $doer === null ) {
357
			global $wgUser;
358
			$doer = $wgUser;
359
		} elseif ( !is_object( $doer ) ) {
360
			$doer = User::newFromId( $doer );
361
		}
362
363
		$this->doer = $doer;
364
365
		$logEntry = new ManualLogEntry( $this->type, $action );
366
		$logEntry->setTarget( $target );
367
		$logEntry->setPerformer( $doer );
368
		$logEntry->setParameters( $params );
369
		// All log entries using the LogPage to insert into the logging table
370
		// are using the old logging system and therefore the legacy flag is
371
		// needed to say the LogFormatter the parameters have numeric keys
372
		$logEntry->setLegacy( true );
373
374
		$formatter = LogFormatter::newFromEntry( $logEntry );
375
		$context = RequestContext::newExtraneousContext( $target );
376
		$formatter->setContext( $context );
377
378
		$this->actionText = $formatter->getPlainActionText();
379
		$this->ircActionText = $formatter->getIRCActionText();
380
381
		return $this->saveContent();
382
	}
383
384
	/**
385
	 * Add relations to log_search table
386
	 *
387
	 * @param string $field
388
	 * @param array $values
389
	 * @param int $logid
390
	 * @return bool
391
	 */
392
	public function addRelations( $field, $values, $logid ) {
393
		if ( !strlen( $field ) || empty( $values ) ) {
394
			return false; // nothing
395
		}
396
397
		$data = [];
398
399 View Code Duplication
		foreach ( $values as $value ) {
400
			$data[] = [
401
				'ls_field' => $field,
402
				'ls_value' => $value,
403
				'ls_log_id' => $logid
404
			];
405
		}
406
407
		$dbw = wfGetDB( DB_MASTER );
408
		$dbw->insert( 'log_search', $data, __METHOD__, 'IGNORE' );
409
410
		return true;
411
	}
412
413
	/**
414
	 * Create a blob from a parameter array
415
	 *
416
	 * @param array $params
417
	 * @return string
418
	 */
419
	public static function makeParamBlob( $params ) {
420
		return implode( "\n", $params );
421
	}
422
423
	/**
424
	 * Extract a parameter array from a blob
425
	 *
426
	 * @param string $blob
427
	 * @return array
428
	 */
429
	public static function extractParams( $blob ) {
430
		if ( $blob === '' ) {
431
			return [];
432
		} else {
433
			return explode( "\n", $blob );
434
		}
435
	}
436
437
	/**
438
	 * Name of the log.
439
	 * @return Message
440
	 * @since 1.19
441
	 */
442
	public function getName() {
443
		global $wgLogNames;
444
445
		// BC
446
		if ( isset( $wgLogNames[$this->type] ) ) {
447
			$key = $wgLogNames[$this->type];
448
		} else {
449
			$key = 'log-name-' . $this->type;
450
		}
451
452
		return wfMessage( $key );
453
	}
454
455
	/**
456
	 * Description of this log type.
457
	 * @return Message
458
	 * @since 1.19
459
	 */
460
	public function getDescription() {
461
		global $wgLogHeaders;
462
		// BC
463
		if ( isset( $wgLogHeaders[$this->type] ) ) {
464
			$key = $wgLogHeaders[$this->type];
465
		} else {
466
			$key = 'log-description-' . $this->type;
467
		}
468
469
		return wfMessage( $key );
470
	}
471
472
	/**
473
	 * Returns the right needed to read this log type.
474
	 * @return string
475
	 * @since 1.19
476
	 */
477
	public function getRestriction() {
478
		global $wgLogRestrictions;
479
		if ( isset( $wgLogRestrictions[$this->type] ) ) {
480
			$restriction = $wgLogRestrictions[$this->type];
481
		} else {
482
			// '' always returns true with $user->isAllowed()
483
			$restriction = '';
484
		}
485
486
		return $restriction;
487
	}
488
489
	/**
490
	 * Tells if this log is not viewable by all.
491
	 * @return bool
492
	 * @since 1.19
493
	 */
494
	public function isRestricted() {
495
		$restriction = $this->getRestriction();
496
497
		return $restriction !== '' && $restriction !== '*';
498
	}
499
}
500