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