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