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