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