Merge "LogEventsList: Add backwards-compatibility for log-show-hide messages"
[lhc/web/wiklou.git] / includes / logging / LogEventsList.php
1 <?php
2 /**
3 * Contain classes to list log entries
4 *
5 * Copyright © 2004 Brion Vibber <brion@pobox.com>
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 use MediaWiki\Linker\LinkRenderer;
27 use MediaWiki\MediaWikiServices;
28 use Wikimedia\Rdbms\IDatabase;
29
30 class LogEventsList extends ContextSource {
31 const NO_ACTION_LINK = 1;
32 const NO_EXTRA_USER_LINKS = 2;
33 const USE_CHECKBOXES = 4;
34
35 public $flags;
36
37 /**
38 * @var array
39 */
40 protected $mDefaultQuery;
41
42 /**
43 * @var bool
44 */
45 protected $showTagEditUI;
46
47 /**
48 * @var array
49 */
50 protected $allowedActions = null;
51
52 /**
53 * @var LinkRenderer|null
54 */
55 private $linkRenderer;
56
57 /**
58 * The first two parameters used to be $skin and $out, but now only a context
59 * is needed, that's why there's a second unused parameter.
60 *
61 * @param IContextSource|Skin $context Context to use; formerly it was
62 * a Skin object. Use of Skin is deprecated.
63 * @param LinkRenderer|null $linkRenderer previously unused
64 * @param int $flags Can be a combination of self::NO_ACTION_LINK,
65 * self::NO_EXTRA_USER_LINKS or self::USE_CHECKBOXES.
66 */
67 public function __construct( $context, $linkRenderer = null, $flags = 0 ) {
68 if ( $context instanceof IContextSource ) {
69 $this->setContext( $context );
70 } else {
71 // Old parameters, $context should be a Skin object
72 $this->setContext( $context->getContext() );
73 }
74
75 $this->flags = $flags;
76 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getUser() );
77 if ( $linkRenderer instanceof LinkRenderer ) {
78 $this->linkRenderer = $linkRenderer;
79 }
80 }
81
82 /**
83 * @since 1.30
84 * @return LinkRenderer
85 */
86 protected function getLinkRenderer() {
87 if ( $this->linkRenderer !== null ) {
88 return $this->linkRenderer;
89 } else {
90 return MediaWikiServices::getInstance()->getLinkRenderer();
91 }
92 }
93
94 /**
95 * Show options for the log list
96 *
97 * @param array|string $types
98 * @param string $user
99 * @param string $page
100 * @param bool $pattern
101 * @param int|string $year Use 0 to start with no year preselected.
102 * @param int|string $month A month in the 1..12 range. Use 0 to start with no month
103 * preselected.
104 * @param int|string $day A day in the 1..31 range. Use 0 to start with no month
105 * preselected.
106 * @param array|null $filter
107 * @param string $tagFilter Tag to select by default
108 * @param string|null $action
109 */
110 public function showOptions( $types = [], $user = '', $page = '', $pattern = false, $year = 0,
111 $month = 0, $day = 0, $filter = null, $tagFilter = '', $action = null
112 ) {
113 $title = SpecialPage::getTitleFor( 'Log' );
114
115 // For B/C, we take strings, but make sure they are converted...
116 $types = ( $types === '' ) ? [] : (array)$types;
117
118 $formDescriptor = [];
119
120 // Basic selectors
121 $formDescriptor['type'] = $this->getTypeMenuDesc( $types );
122 $formDescriptor['user'] = $this->getUserInputDesc( $user );
123 $formDescriptor['page'] = $this->getTitleInputDesc( $title );
124
125 // Add extra inputs if any
126 // This could either be a form descriptor array or a string with raw HTML.
127 // We need it to work in both cases and show a deprecation warning if it
128 // is a string. See T199495.
129 $extraInputsDescriptor = $this->getExtraInputsDesc( $types );
130 if (
131 is_array( $extraInputsDescriptor ) &&
132 !empty( $extraInputsDescriptor )
133 ) {
134 $formDescriptor[ 'extra' ] = $extraInputsDescriptor;
135 } elseif ( is_string( $extraInputsDescriptor ) ) {
136 // We'll add this to the footer of the form later
137 $extraInputsString = $extraInputsDescriptor;
138 wfDeprecated( 'Using $input in LogEventsListGetExtraInputs hook', '1.32' );
139 }
140
141 // Title pattern, if allowed
142 if ( !$this->getConfig()->get( 'MiserMode' ) ) {
143 $formDescriptor['pattern'] = $this->getTitlePatternDesc( $pattern );
144 }
145
146 // Date menu
147 $formDescriptor['date'] = [
148 'type' => 'date',
149 'label-message' => 'date'
150 ];
151
152 // Tag filter
153 $formDescriptor['tagfilter'] = [
154 'type' => 'tagfilter',
155 'name' => 'tagfilter',
156 'label-raw' => $this->msg( 'tag-filter' )->parse(),
157 ];
158
159 // Filter links
160 if ( $filter ) {
161 $formDescriptor['filters'] = $this->getFiltersDesc( $filter );
162 }
163
164 // Action filter
165 if (
166 $action !== null &&
167 $this->allowedActions !== null &&
168 count( $this->allowedActions ) > 0
169 ) {
170 $formDescriptor['subtype'] = $this->getActionSelectorDesc( $types, $action );
171 }
172
173 $htmlForm = new HTMLForm( $formDescriptor, $this->getContext() );
174 $htmlForm
175 ->setSubmitText( $this->msg( 'logeventslist-submit' )->text() )
176 ->setMethod( 'get' )
177 ->setWrapperLegendMsg( 'log' );
178
179 // TODO This will should be removed at some point. See T199495.
180 if ( isset( $extraInputsString ) ) {
181 $htmlForm->addFooterText( Html::rawElement(
182 'div',
183 null,
184 $extraInputsString
185 ) );
186 }
187
188 $htmlForm->prepareForm()->displayForm( false );
189 }
190
191 /**
192 * @param array $filter
193 * @return array Form descriptor
194 */
195 private function getFiltersDesc( $filter ) {
196 $options = [];
197 $default = [];
198 foreach ( $filter as $type => $val ) {
199 $message = $this->msg( "logeventslist-{$type}-log" );
200 // FIXME: Remove this check once T199657 is fully resolved.
201 if ( !$message->exists() ) {
202 $message = $this->msg( "log-show-hide-{$type}" )->params( $this->msg( 'show' )->text() );
203 }
204 $options[ $message->text() ] = $type;
205
206 if ( $val === 0 ) {
207 $default[] = $type;
208 }
209 }
210 return [
211 'class' => 'HTMLMultiSelectField',
212 'label-message' => 'logeventslist-more-filters',
213 'flatlist' => true,
214 'options' => $options,
215 'default' => $default,
216 ];
217 }
218
219 private function getDefaultQuery() {
220 if ( !isset( $this->mDefaultQuery ) ) {
221 $this->mDefaultQuery = $this->getRequest()->getQueryValues();
222 unset( $this->mDefaultQuery['title'] );
223 unset( $this->mDefaultQuery['dir'] );
224 unset( $this->mDefaultQuery['offset'] );
225 unset( $this->mDefaultQuery['limit'] );
226 unset( $this->mDefaultQuery['order'] );
227 unset( $this->mDefaultQuery['month'] );
228 unset( $this->mDefaultQuery['year'] );
229 }
230
231 return $this->mDefaultQuery;
232 }
233
234 /**
235 * @param array $queryTypes
236 * @return array Form descriptor
237 */
238 private function getTypeMenuDesc( $queryTypes ) {
239 $queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : '';
240
241 $typesByName = []; // Temporary array
242 // First pass to load the log names
243 foreach ( LogPage::validTypes() as $type ) {
244 $page = new LogPage( $type );
245 $restriction = $page->getRestriction();
246 if ( $this->getUser()->isAllowed( $restriction ) ) {
247 $typesByName[$type] = $page->getName()->text();
248 }
249 }
250
251 // Second pass to sort by name
252 asort( $typesByName );
253
254 // Always put "All public logs" on top
255 $public = $typesByName[''];
256 unset( $typesByName[''] );
257 $typesByName = [ '' => $public ] + $typesByName;
258
259 return [
260 'class' => 'HTMLSelectField',
261 'name' => 'type',
262 'options' => array_flip( $typesByName ),
263 'default' => $queryType,
264 ];
265 }
266
267 /**
268 * @param string $user
269 * @return array Form descriptor
270 */
271 private function getUserInputDesc( $user ) {
272 return [
273 'class' => 'HTMLUserTextField',
274 'label-message' => 'specialloguserlabel',
275 'name' => 'user',
276 ];
277 }
278
279 /**
280 * @param string $title
281 * @return array Form descriptor
282 */
283 private function getTitleInputDesc( $title ) {
284 return [
285 'class' => 'HTMLTitleTextField',
286 'label-message' => 'speciallogtitlelabel',
287 'name' => 'page',
288 'value' => $title,
289 'required' => false
290 ];
291 }
292
293 /**
294 * @param bool $pattern
295 * @return array Form descriptor
296 */
297 private function getTitlePatternDesc( $pattern ) {
298 return [
299 'type' => 'check',
300 'label-message' => 'log-title-wildcard',
301 'name' => 'pattern',
302 ];
303 }
304
305 /**
306 * @param array $types
307 * @return array|string Form descriptor or string with HTML
308 */
309 private function getExtraInputsDesc( $types ) {
310 if ( count( $types ) == 1 ) {
311 if ( $types[0] == 'suppress' ) {
312 $offender = $this->getRequest()->getVal( 'offender' );
313 $user = User::newFromName( $offender, false );
314 if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) {
315 $offender = ''; // Blank field if invalid
316 }
317 return [
318 'type' => 'text',
319 'label-message' => 'revdelete-offender',
320 'name' => 'offender',
321 'value' => $offender,
322 ];
323 } else {
324 // Allow extensions to add their own extra inputs
325 // This could be an array or string. See T199495.
326 $input = ''; // Deprecated
327 $formDescriptor = [];
328 Hooks::run( 'LogEventsListGetExtraInputs', [ $types[0], $this, &$input, &$formDescriptor ] );
329
330 return empty( $formDescriptor ) ? $input : $formDescriptor;
331 }
332 }
333
334 return [];
335 }
336
337 /**
338 * Drop down menu for selection of actions that can be used to filter the log
339 * @param array $types
340 * @param string $action
341 * @return array Form descriptor
342 */
343 private function getActionSelectorDesc( $types, $action ) {
344 $actionOptions = [];
345 $actionOptions[ 'log-action-filter-all' ] = '';
346
347 foreach ( $this->allowedActions as $value ) {
348 $msgKey = 'log-action-filter-' . $types[0] . '-' . $value;
349 $actionOptions[ $msgKey ] = $value;
350 }
351
352 return [
353 'class' => 'HTMLSelectField',
354 'name' => 'subtype',
355 'options-messages' => $actionOptions,
356 'default' => $action,
357 'label' => $this->msg( 'log-action-filter-' . $types[0] )->text(),
358 ];
359 }
360
361 /**
362 * Sets the action types allowed for log filtering
363 * To one action type may correspond several log_actions
364 * @param array $actions
365 * @since 1.27
366 */
367 public function setAllowedActions( $actions ) {
368 $this->allowedActions = $actions;
369 }
370
371 /**
372 * @return string
373 */
374 public function beginLogEventsList() {
375 return "<ul>\n";
376 }
377
378 /**
379 * @return string
380 */
381 public function endLogEventsList() {
382 return "</ul>\n";
383 }
384
385 /**
386 * @param stdClass $row A single row from the result set
387 * @return string Formatted HTML list item
388 */
389 public function logLine( $row ) {
390 $entry = DatabaseLogEntry::newFromRow( $row );
391 $formatter = LogFormatter::newFromEntry( $entry );
392 $formatter->setContext( $this->getContext() );
393 $formatter->setLinkRenderer( $this->getLinkRenderer() );
394 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
395
396 $time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
397 $entry->getTimestamp(), $this->getUser() ) );
398
399 $action = $formatter->getActionText();
400
401 if ( $this->flags & self::NO_ACTION_LINK ) {
402 $revert = '';
403 } else {
404 $revert = $formatter->getActionLinks();
405 if ( $revert != '' ) {
406 $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
407 }
408 }
409
410 $comment = $formatter->getComment();
411
412 // Some user can hide log items and have review links
413 $del = $this->getShowHideLinks( $row );
414
415 // Any tags...
416 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
417 $row->ts_tags,
418 'logevent',
419 $this->getContext()
420 );
421 $classes = array_merge(
422 [ 'mw-logline-' . $entry->getType() ],
423 $newClasses
424 );
425 $attribs = [
426 'data-mw-logid' => $entry->getId(),
427 'data-mw-logaction' => $entry->getFullType(),
428 ];
429 $ret = "$del $time $action $comment $revert $tagDisplay";
430
431 // Let extensions add data
432 Hooks::run( 'LogEventsListLineEnding', [ $this, &$ret, $entry, &$classes, &$attribs ] );
433 $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 'isReservedDataAttribute' ] );
434 $attribs['class'] = implode( ' ', $classes );
435
436 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
437 }
438
439 /**
440 * @param stdClass $row
441 * @return string
442 */
443 private function getShowHideLinks( $row ) {
444 // We don't want to see the links and
445 if ( $this->flags == self::NO_ACTION_LINK ) {
446 return '';
447 }
448
449 $user = $this->getUser();
450
451 // If change tag editing is available to this user, return the checkbox
452 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
453 return Xml::check(
454 'showhiderevisions',
455 false,
456 [ 'name' => 'ids[' . $row->log_id . ']' ]
457 );
458 }
459
460 // no one can hide items from the suppress log.
461 if ( $row->log_type == 'suppress' ) {
462 return '';
463 }
464
465 $del = '';
466 // Don't show useless checkbox to people who cannot hide log entries
467 if ( $user->isAllowed( 'deletedhistory' ) ) {
468 $canHide = $user->isAllowed( 'deletelogentry' );
469 $canViewSuppressedOnly = $user->isAllowed( 'viewsuppressed' ) &&
470 !$user->isAllowed( 'suppressrevision' );
471 $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
472 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
473 if ( $row->log_deleted || $canHide ) {
474 // Show checkboxes instead of links.
475 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
476 // If event was hidden from sysops
477 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
478 $del = Xml::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
479 } else {
480 $del = Xml::check(
481 'showhiderevisions',
482 false,
483 [ 'name' => 'ids[' . $row->log_id . ']' ]
484 );
485 }
486 } else {
487 // If event was hidden from sysops
488 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
489 $del = Linker::revDeleteLinkDisabled( $canHide );
490 } else {
491 $query = [
492 'target' => SpecialPage::getTitleFor( 'Log', $row->log_type )->getPrefixedDBkey(),
493 'type' => 'logging',
494 'ids' => $row->log_id,
495 ];
496 $del = Linker::revDeleteLink(
497 $query,
498 $entryIsSuppressed,
499 $canHide && !$canViewThisSuppressedEntry
500 );
501 }
502 }
503 }
504 }
505
506 return $del;
507 }
508
509 /**
510 * @param stdClass $row
511 * @param string|array $type
512 * @param string|array $action
513 * @param string $right
514 * @return bool
515 */
516 public static function typeAction( $row, $type, $action, $right = '' ) {
517 $match = is_array( $type ) ?
518 in_array( $row->log_type, $type ) : $row->log_type == $type;
519 if ( $match ) {
520 $match = is_array( $action ) ?
521 in_array( $row->log_action, $action ) : $row->log_action == $action;
522 if ( $match && $right ) {
523 global $wgUser;
524 $match = $wgUser->isAllowed( $right );
525 }
526 }
527
528 return $match;
529 }
530
531 /**
532 * Determine if the current user is allowed to view a particular
533 * field of this log row, if it's marked as deleted.
534 *
535 * @param stdClass $row
536 * @param int $field
537 * @param User|null $user User to check, or null to use $wgUser
538 * @return bool
539 */
540 public static function userCan( $row, $field, User $user = null ) {
541 return self::userCanBitfield( $row->log_deleted, $field, $user );
542 }
543
544 /**
545 * Determine if the current user is allowed to view a particular
546 * field of this log row, if it's marked as deleted.
547 *
548 * @param int $bitfield Current field
549 * @param int $field
550 * @param User|null $user User to check, or null to use $wgUser
551 * @return bool
552 */
553 public static function userCanBitfield( $bitfield, $field, User $user = null ) {
554 if ( $bitfield & $field ) {
555 if ( $user === null ) {
556 global $wgUser;
557 $user = $wgUser;
558 }
559 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
560 $permissions = [ 'suppressrevision', 'viewsuppressed' ];
561 } else {
562 $permissions = [ 'deletedhistory' ];
563 }
564 $permissionlist = implode( ', ', $permissions );
565 wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
566 return $user->isAllowedAny( ...$permissions );
567 }
568 return true;
569 }
570
571 /**
572 * @param stdClass $row
573 * @param int $field One of DELETED_* bitfield constants
574 * @return bool
575 */
576 public static function isDeleted( $row, $field ) {
577 return ( $row->log_deleted & $field ) == $field;
578 }
579
580 /**
581 * Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey)
582 *
583 * @param OutputPage|string &$out
584 * @param string|array $types Log types to show
585 * @param string|Title $page The page title to show log entries for
586 * @param string $user The user who made the log entries
587 * @param array $param Associative Array with the following additional options:
588 * - lim Integer Limit of items to show, default is 50
589 * - conds Array Extra conditions for the query
590 * (e.g. 'log_action != ' . $dbr->addQuotes( 'revision' ))
591 * - showIfEmpty boolean Set to false if you don't want any output in case the loglist is empty
592 * if set to true (default), "No matching items in log" is displayed if loglist is empty
593 * - msgKey Array If you want a nice box with a message, set this to the key of the message.
594 * First element is the message key, additional optional elements are parameters for the key
595 * that are processed with wfMessage
596 * - offset Set to overwrite offset parameter in WebRequest
597 * set to '' to unset offset
598 * - wrap String Wrap the message in html (usually something like "<div ...>$1</div>").
599 * - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS)
600 * - useRequestParams boolean Set true to use Pager-related parameters in the WebRequest
601 * - useMaster boolean Use master DB
602 * - extraUrlParams array|bool Additional url parameters for "full log" link (if it is shown)
603 * @return int Number of total log items (not limited by $lim)
604 */
605 public static function showLogExtract(
606 &$out, $types = [], $page = '', $user = '', $param = []
607 ) {
608 $defaultParameters = [
609 'lim' => 25,
610 'conds' => [],
611 'showIfEmpty' => true,
612 'msgKey' => [ '' ],
613 'wrap' => "$1",
614 'flags' => 0,
615 'useRequestParams' => false,
616 'useMaster' => false,
617 'extraUrlParams' => false,
618 ];
619 # The + operator appends elements of remaining keys from the right
620 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
621 $param += $defaultParameters;
622 # Convert $param array to individual variables
623 $lim = $param['lim'];
624 $conds = $param['conds'];
625 $showIfEmpty = $param['showIfEmpty'];
626 $msgKey = $param['msgKey'];
627 $wrap = $param['wrap'];
628 $flags = $param['flags'];
629 $extraUrlParams = $param['extraUrlParams'];
630
631 $useRequestParams = $param['useRequestParams'];
632 if ( !is_array( $msgKey ) ) {
633 $msgKey = [ $msgKey ];
634 }
635
636 if ( $out instanceof OutputPage ) {
637 $context = $out->getContext();
638 } else {
639 $context = RequestContext::getMain();
640 }
641
642 // FIXME: Figure out how to inject this
643 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
644
645 # Insert list of top 50 (or top $lim) items
646 $loglist = new LogEventsList( $context, $linkRenderer, $flags );
647 $pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
648 if ( !$useRequestParams ) {
649 # Reset vars that may have been taken from the request
650 $pager->mLimit = 50;
651 $pager->mDefaultLimit = 50;
652 $pager->mOffset = "";
653 $pager->mIsBackwards = false;
654 }
655
656 if ( $param['useMaster'] ) {
657 $pager->mDb = wfGetDB( DB_MASTER );
658 }
659 if ( isset( $param['offset'] ) ) { # Tell pager to ignore WebRequest offset
660 $pager->setOffset( $param['offset'] );
661 }
662
663 if ( $lim > 0 ) {
664 $pager->mLimit = $lim;
665 }
666 // Fetch the log rows and build the HTML if needed
667 $logBody = $pager->getBody();
668 $numRows = $pager->getNumRows();
669
670 $s = '';
671
672 if ( $logBody ) {
673 if ( $msgKey[0] ) {
674 $dir = $context->getLanguage()->getDir();
675 $lang = $context->getLanguage()->getHtmlCode();
676
677 $s = Xml::openElement( 'div', [
678 'class' => "mw-warning-with-logexcerpt mw-content-$dir",
679 'dir' => $dir,
680 'lang' => $lang,
681 ] );
682
683 if ( count( $msgKey ) == 1 ) {
684 $s .= $context->msg( $msgKey[0] )->parseAsBlock();
685 } else { // Process additional arguments
686 $args = $msgKey;
687 array_shift( $args );
688 $s .= $context->msg( $msgKey[0], $args )->parseAsBlock();
689 }
690 }
691 $s .= $loglist->beginLogEventsList() .
692 $logBody .
693 $loglist->endLogEventsList();
694 } elseif ( $showIfEmpty ) {
695 $s = Html::rawElement( 'div', [ 'class' => 'mw-warning-logempty' ],
696 $context->msg( 'logempty' )->parse() );
697 }
698
699 if ( $numRows > $pager->mLimit ) { # Show "Full log" link
700 $urlParam = [];
701 if ( $page instanceof Title ) {
702 $urlParam['page'] = $page->getPrefixedDBkey();
703 } elseif ( $page != '' ) {
704 $urlParam['page'] = $page;
705 }
706
707 if ( $user != '' ) {
708 $urlParam['user'] = $user;
709 }
710
711 if ( !is_array( $types ) ) { # Make it an array, if it isn't
712 $types = [ $types ];
713 }
714
715 # If there is exactly one log type, we can link to Special:Log?type=foo
716 if ( count( $types ) == 1 ) {
717 $urlParam['type'] = $types[0];
718 }
719
720 if ( $extraUrlParams !== false ) {
721 $urlParam = array_merge( $urlParam, $extraUrlParams );
722 }
723
724 $s .= $linkRenderer->makeKnownLink(
725 SpecialPage::getTitleFor( 'Log' ),
726 $context->msg( 'log-fulllog' )->text(),
727 [],
728 $urlParam
729 );
730 }
731
732 if ( $logBody && $msgKey[0] ) {
733 $s .= '</div>';
734 }
735
736 if ( $wrap != '' ) { // Wrap message in html
737 $s = str_replace( '$1', $s, $wrap );
738 }
739
740 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
741 if ( Hooks::run( 'LogEventsListShowLogExtract', [ &$s, $types, $page, $user, $param ] ) ) {
742 // $out can be either an OutputPage object or a String-by-reference
743 if ( $out instanceof OutputPage ) {
744 $out->addHTML( $s );
745 } else {
746 $out = $s;
747 }
748 }
749
750 return $numRows;
751 }
752
753 /**
754 * SQL clause to skip forbidden log types for this user
755 *
756 * @param IDatabase $db
757 * @param string $audience Public/user
758 * @param User|null $user User to check, or null to use $wgUser
759 * @return string|bool String on success, false on failure.
760 */
761 public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
762 global $wgLogRestrictions;
763
764 if ( $audience != 'public' && $user === null ) {
765 global $wgUser;
766 $user = $wgUser;
767 }
768
769 // Reset the array, clears extra "where" clauses when $par is used
770 $hiddenLogs = [];
771
772 // Don't show private logs to unprivileged users
773 foreach ( $wgLogRestrictions as $logType => $right ) {
774 if ( $audience == 'public' || !$user->isAllowed( $right ) ) {
775 $hiddenLogs[] = $logType;
776 }
777 }
778 if ( count( $hiddenLogs ) == 1 ) {
779 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
780 } elseif ( $hiddenLogs ) {
781 return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
782 }
783
784 return false;
785 }
786 }