MediaWiki REL1_34
LogEventsList.php
Go to the documentation of this file.
1<?php
29
31 const NO_ACTION_LINK = 1;
33 const USE_CHECKBOXES = 4;
34
35 public $flags;
36
41 protected $mDefaultQuery;
42
46 protected $showTagEditUI;
47
51 protected $allowedActions = null;
52
57
68 public function __construct( $context, $linkRenderer = null, $flags = 0 ) {
69 if ( $context instanceof IContextSource ) {
70 $this->setContext( $context );
71 } else {
72 // Old parameters, $context should be a Skin object
73 $this->setContext( $context->getContext() );
74 }
75
76 $this->flags = $flags;
77 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getUser() );
78 if ( $linkRenderer instanceof LinkRenderer ) {
79 $this->linkRenderer = $linkRenderer;
80 }
81 }
82
87 protected function getLinkRenderer() {
88 if ( $this->linkRenderer !== null ) {
90 } else {
91 return MediaWikiServices::getInstance()->getLinkRenderer();
92 }
93 }
94
111 public function showOptions( $types = [], $user = '', $page = '', $pattern = false, $year = 0,
112 $month = 0, $day = 0, $filter = null, $tagFilter = '', $action = null
113 ) {
114 // For B/C, we take strings, but make sure they are converted...
115 $types = ( $types === '' ) ? [] : (array)$types;
116
117 $formDescriptor = [];
118
119 // Basic selectors
120 $formDescriptor['type'] = $this->getTypeMenuDesc( $types );
121 $formDescriptor['user'] = $this->getUserInputDesc( $user );
122 $formDescriptor['page'] = $this->getTitleInputDesc( $page );
123
124 // Add extra inputs if any
125 // This could either be a form descriptor array or a string with raw HTML.
126 // We need it to work in both cases and show a deprecation warning if it
127 // is a string. See T199495.
128 $extraInputsDescriptor = $this->getExtraInputsDesc( $types );
129 if (
130 is_array( $extraInputsDescriptor ) &&
131 !empty( $extraInputsDescriptor )
132 ) {
133 $formDescriptor[ 'extra' ] = $extraInputsDescriptor;
134 } elseif (
135 is_string( $extraInputsDescriptor ) &&
136 $extraInputsDescriptor !== ''
137 ) {
138 // We'll add this to the footer of the form later
139 $extraInputsString = $extraInputsDescriptor;
140 wfDeprecated( '$input in LogEventsListGetExtraInputs hook', '1.32' );
141 }
142
143 // Title pattern, if allowed
144 if ( !$this->getConfig()->get( 'MiserMode' ) ) {
145 $formDescriptor['pattern'] = $this->getTitlePatternDesc( $pattern );
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-raw' => $this->msg( 'tag-filter' )->parse(),
160 ];
161
162 // Filter links
163 if ( $filter ) {
164 $formDescriptor['filters'] = $this->getFiltersDesc( $filter );
165 }
166
167 // Action filter
168 if (
169 $action !== null &&
170 $this->allowedActions !== null &&
171 count( $this->allowedActions ) > 0
172 ) {
173 $formDescriptor['subtype'] = $this->getActionSelectorDesc( $types, $action );
174 }
175
176 $context = new DerivativeContext( $this->getContext() );
177 $context->setTitle( SpecialPage::getTitleFor( 'Log' ) ); // Remove subpage
178 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $context );
179 $htmlForm
180 ->setSubmitText( $this->msg( 'logeventslist-submit' )->text() )
181 ->setMethod( 'get' )
182 ->setWrapperLegendMsg( 'log' );
183
184 // TODO This will should be removed at some point. See T199495.
185 if ( isset( $extraInputsString ) ) {
186 $htmlForm->addFooterText( Html::rawElement(
187 'div',
188 null,
189 $extraInputsString
190 ) );
191 }
192
193 $htmlForm->prepareForm()->displayForm( false );
194 }
195
200 private function getFiltersDesc( $filter ) {
201 $optionsMsg = [];
202 $default = [];
203 foreach ( $filter as $type => $val ) {
204 $optionsMsg["logeventslist-{$type}-log"] = $type;
205
206 if ( $val === false ) {
207 $default[] = $type;
208 }
209 }
210 return [
211 'class' => 'HTMLMultiSelectField',
212 'label-message' => 'logeventslist-more-filters',
213 'flatlist' => true,
214 'options-messages' => $optionsMsg,
215 'default' => $default,
216 ];
217 }
218
223 private function getTypeMenuDesc( $queryTypes ) {
224 $queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : '';
225
226 $typesByName = []; // Temporary array
227 // First pass to load the log names
228 foreach ( LogPage::validTypes() as $type ) {
229 $page = new LogPage( $type );
230 $restriction = $page->getRestriction();
231 if ( MediaWikiServices::getInstance()
233 ->userHasRight( $this->getUser(), $restriction )
234 ) {
235 $typesByName[$type] = $page->getName()->text();
236 }
237 }
238
239 // Second pass to sort by name
240 asort( $typesByName );
241
242 // Always put "All public logs" on top
243 $public = $typesByName[''];
244 unset( $typesByName[''] );
245 $typesByName = [ '' => $public ] + $typesByName;
246
247 return [
248 'class' => 'HTMLSelectField',
249 'name' => 'type',
250 'options' => array_flip( $typesByName ),
251 'default' => $queryType,
252 ];
253 }
254
259 private function getUserInputDesc( $user ) {
260 return [
261 'class' => 'HTMLUserTextField',
262 'label-message' => 'specialloguserlabel',
263 'name' => 'user',
264 'default' => $user,
265 ];
266 }
267
272 private function getTitleInputDesc( $title ) {
273 return [
274 'class' => 'HTMLTitleTextField',
275 'label-message' => 'speciallogtitlelabel',
276 'name' => 'page',
277 'required' => false
278 ];
279 }
280
285 private function getTitlePatternDesc( $pattern ) {
286 return [
287 'type' => 'check',
288 'label-message' => 'log-title-wildcard',
289 'name' => 'pattern',
290 ];
291 }
292
297 private function getExtraInputsDesc( $types ) {
298 if ( count( $types ) == 1 ) {
299 if ( $types[0] == 'suppress' ) {
300 return [
301 'type' => 'text',
302 'label-message' => 'revdelete-offender',
303 'name' => 'offender',
304 ];
305 } else {
306 // Allow extensions to add their own extra inputs
307 // This could be an array or string. See T199495.
308 $input = ''; // Deprecated
309 $formDescriptor = [];
310 Hooks::run( 'LogEventsListGetExtraInputs', [ $types[0], $this, &$input, &$formDescriptor ] );
311
312 return empty( $formDescriptor ) ? $input : $formDescriptor;
313 }
314 }
315
316 return [];
317 }
318
325 private function getActionSelectorDesc( $types, $action ) {
326 $actionOptions = [];
327 $actionOptions[ 'log-action-filter-all' ] = '';
328
329 foreach ( $this->allowedActions as $value ) {
330 $msgKey = 'log-action-filter-' . $types[0] . '-' . $value;
331 $actionOptions[ $msgKey ] = $value;
332 }
333
334 return [
335 'class' => 'HTMLSelectField',
336 'name' => 'subtype',
337 'options-messages' => $actionOptions,
338 'default' => $action,
339 'label' => $this->msg( 'log-action-filter-' . $types[0] )->text(),
340 ];
341 }
342
349 public function setAllowedActions( $actions ) {
350 $this->allowedActions = $actions;
351 }
352
356 public function beginLogEventsList() {
357 return "<ul>\n";
358 }
359
363 public function endLogEventsList() {
364 return "</ul>\n";
365 }
366
371 public function logLine( $row ) {
372 $entry = DatabaseLogEntry::newFromRow( $row );
373 $formatter = LogFormatter::newFromEntry( $entry );
374 $formatter->setContext( $this->getContext() );
375 $formatter->setLinkRenderer( $this->getLinkRenderer() );
376 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
377
378 $time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
379 $entry->getTimestamp(), $this->getUser() ) );
380
381 $action = $formatter->getActionText();
382
383 if ( $this->flags & self::NO_ACTION_LINK ) {
384 $revert = '';
385 } else {
386 $revert = $formatter->getActionLinks();
387 if ( $revert != '' ) {
388 $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
389 }
390 }
391
392 $comment = $formatter->getComment();
393
394 // Some user can hide log items and have review links
395 $del = $this->getShowHideLinks( $row );
396
397 // Any tags...
398 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
399 $row->ts_tags,
400 'logevent',
401 $this->getContext()
402 );
403 $classes = array_merge(
404 [ 'mw-logline-' . $entry->getType() ],
405 $newClasses
406 );
407 $attribs = [
408 'data-mw-logid' => $entry->getId(),
409 'data-mw-logaction' => $entry->getFullType(),
410 ];
411 $ret = "$del $time $action $comment $revert $tagDisplay";
412
413 // Let extensions add data
414 Hooks::run( 'LogEventsListLineEnding', [ $this, &$ret, $entry, &$classes, &$attribs ] );
415 $attribs = array_filter( $attribs,
416 [ Sanitizer::class, 'isReservedDataAttribute' ],
417 ARRAY_FILTER_USE_KEY
418 );
419 $attribs['class'] = implode( ' ', $classes );
420
421 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
422 }
423
428 private function getShowHideLinks( $row ) {
429 // We don't want to see the links and
430 if ( $this->flags == self::NO_ACTION_LINK ) {
431 return '';
432 }
433
434 $user = $this->getUser();
435
436 // If change tag editing is available to this user, return the checkbox
437 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
438 return Xml::check(
439 'showhiderevisions',
440 false,
441 [ 'name' => 'ids[' . $row->log_id . ']' ]
442 );
443 }
444
445 // no one can hide items from the suppress log.
446 if ( $row->log_type == 'suppress' ) {
447 return '';
448 }
449
450 $del = '';
451 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
452 // Don't show useless checkbox to people who cannot hide log entries
453 if ( $permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
454 $canHide = $permissionManager->userHasRight( $user, 'deletelogentry' );
455 $canViewSuppressedOnly = $permissionManager->userHasRight( $user, 'viewsuppressed' ) &&
456 !$permissionManager->userHasRight( $user, 'suppressrevision' );
457 $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
458 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
459 if ( $row->log_deleted || $canHide ) {
460 // Show checkboxes instead of links.
461 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
462 // If event was hidden from sysops
463 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
464 $del = Xml::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
465 } else {
466 $del = Xml::check(
467 'showhiderevisions',
468 false,
469 [ 'name' => 'ids[' . $row->log_id . ']' ]
470 );
471 }
472 } else {
473 // If event was hidden from sysops
474 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
475 $del = Linker::revDeleteLinkDisabled( $canHide );
476 } else {
477 $query = [
478 'target' => SpecialPage::getTitleFor( 'Log', $row->log_type )->getPrefixedDBkey(),
479 'type' => 'logging',
480 'ids' => $row->log_id,
481 ];
483 $query,
484 $entryIsSuppressed,
485 $canHide && !$canViewThisSuppressedEntry
486 );
487 }
488 }
489 }
490 }
491
492 return $del;
493 }
494
502 public static function typeAction( $row, $type, $action, $right = '' ) {
503 $match = is_array( $type ) ?
504 in_array( $row->log_type, $type ) : $row->log_type == $type;
505 if ( $match ) {
506 $match = is_array( $action ) ?
507 in_array( $row->log_action, $action ) : $row->log_action == $action;
508 if ( $match && $right ) {
509 global $wgUser;
510 $match = MediaWikiServices::getInstance()
511 ->getPermissionManager()
512 ->userHasRight( $wgUser, $right );
513 }
514 }
515
516 return $match;
517 }
518
528 public static function userCan( $row, $field, User $user = null ) {
529 return self::userCanBitfield( $row->log_deleted, $field, $user ) &&
530 self::userCanViewLogType( $row->log_type, $user );
531 }
532
542 public static function userCanBitfield( $bitfield, $field, User $user = null ) {
543 if ( $bitfield & $field ) {
544 if ( $user === null ) {
545 global $wgUser;
546 $user = $wgUser;
547 }
548 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
549 $permissions = [ 'suppressrevision', 'viewsuppressed' ];
550 } else {
551 $permissions = [ 'deletedhistory' ];
552 }
553 $permissionlist = implode( ', ', $permissions );
554 wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
555 return MediaWikiServices::getInstance()
556 ->getPermissionManager()
557 ->userHasAnyRight( $user, ...$permissions );
558 }
559 return true;
560 }
561
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] ) && !MediaWikiServices::getInstance()
578 ->userHasRight( $user, $logRestrictions[$type] )
579 ) {
580 return false;
581 }
582 return true;
583 }
584
590 public static function isDeleted( $row, $field ) {
591 return ( $row->log_deleted & $field ) == $field;
592 }
593
619 public static function showLogExtract(
620 &$out, $types = [], $page = '', $user = '', $param = []
621 ) {
622 $defaultParameters = [
623 'lim' => 25,
624 'conds' => [],
625 'showIfEmpty' => true,
626 'msgKey' => [ '' ],
627 'wrap' => "$1",
628 'flags' => 0,
629 'useRequestParams' => false,
630 'useMaster' => false,
631 'extraUrlParams' => false,
632 ];
633 # The + operator appends elements of remaining keys from the right
634 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
635 $param += $defaultParameters;
636 # Convert $param array to individual variables
637 $lim = $param['lim'];
638 $conds = $param['conds'];
639 $showIfEmpty = $param['showIfEmpty'];
640 $msgKey = $param['msgKey'];
641 $wrap = $param['wrap'];
642 $flags = $param['flags'];
643 $extraUrlParams = $param['extraUrlParams'];
644
645 $useRequestParams = $param['useRequestParams'];
646 if ( !is_array( $msgKey ) ) {
647 $msgKey = [ $msgKey ];
648 }
649
650 if ( $out instanceof OutputPage ) {
651 $context = $out->getContext();
652 } else {
653 $context = RequestContext::getMain();
654 }
655
656 // FIXME: Figure out how to inject this
657 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
658
659 # Insert list of top 50 (or top $lim) items
660 $loglist = new LogEventsList( $context, $linkRenderer, $flags );
661 $pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
662 if ( !$useRequestParams ) {
663 # Reset vars that may have been taken from the request
664 $pager->mLimit = 50;
665 $pager->mDefaultLimit = 50;
666 $pager->mOffset = "";
667 $pager->mIsBackwards = false;
668 }
669
670 if ( $param['useMaster'] ) {
671 $pager->mDb = wfGetDB( DB_MASTER );
672 }
673 if ( isset( $param['offset'] ) ) { # Tell pager to ignore WebRequest offset
674 $pager->setOffset( $param['offset'] );
675 }
676
677 if ( $lim > 0 ) {
678 $pager->mLimit = $lim;
679 }
680 // Fetch the log rows and build the HTML if needed
681 $logBody = $pager->getBody();
682 $numRows = $pager->getNumRows();
683
684 $s = '';
685
686 if ( $logBody ) {
687 if ( $msgKey[0] ) {
688 $dir = $context->getLanguage()->getDir();
689 $lang = $context->getLanguage()->getHtmlCode();
690
691 $s = Xml::openElement( 'div', [
692 'class' => "mw-warning-with-logexcerpt mw-content-$dir",
693 'dir' => $dir,
694 'lang' => $lang,
695 ] );
696
697 if ( count( $msgKey ) == 1 ) {
698 $s .= $context->msg( $msgKey[0] )->parseAsBlock();
699 } else { // Process additional arguments
700 $args = $msgKey;
701 array_shift( $args );
702 $s .= $context->msg( $msgKey[0], $args )->parseAsBlock();
703 }
704 }
705 $s .= $loglist->beginLogEventsList() .
706 $logBody .
707 $loglist->endLogEventsList();
708 // add styles for change tags
709 $context->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
710 } elseif ( $showIfEmpty ) {
711 $s = Html::rawElement( 'div', [ 'class' => 'mw-warning-logempty' ],
712 $context->msg( 'logempty' )->parse() );
713 }
714
715 if ( $numRows > $pager->mLimit ) { # Show "Full log" link
716 $urlParam = [];
717 if ( $page instanceof Title ) {
718 $urlParam['page'] = $page->getPrefixedDBkey();
719 } elseif ( $page != '' ) {
720 $urlParam['page'] = $page;
721 }
722
723 if ( $user != '' ) {
724 $urlParam['user'] = $user;
725 }
726
727 if ( !is_array( $types ) ) { # Make it an array, if it isn't
728 $types = [ $types ];
729 }
730
731 # If there is exactly one log type, we can link to Special:Log?type=foo
732 if ( count( $types ) == 1 ) {
733 $urlParam['type'] = $types[0];
734 }
735
736 if ( $extraUrlParams !== false ) {
737 $urlParam = array_merge( $urlParam, $extraUrlParams );
738 }
739
740 $s .= $linkRenderer->makeKnownLink(
741 SpecialPage::getTitleFor( 'Log' ),
742 $context->msg( 'log-fulllog' )->text(),
743 [],
744 $urlParam
745 );
746 }
747
748 if ( $logBody && $msgKey[0] ) {
749 $s .= '</div>';
750 }
751
752 if ( $wrap != '' ) { // Wrap message in html
753 $s = str_replace( '$1', $s, $wrap );
754 }
755
756 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
757 if ( Hooks::run( 'LogEventsListShowLogExtract', [ &$s, $types, $page, $user, $param ] ) ) {
758 // $out can be either an OutputPage object or a String-by-reference
759 if ( $out instanceof OutputPage ) {
760 $out->addHTML( $s );
761 } else {
762 $out = $s;
763 }
764 }
765
766 return $numRows;
767 }
768
777 public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
778 global $wgLogRestrictions;
779
780 if ( $audience != 'public' && $user === null ) {
781 global $wgUser;
782 $user = $wgUser;
783 }
784
785 // Reset the array, clears extra "where" clauses when $par is used
786 $hiddenLogs = [];
787
788 // Don't show private logs to unprivileged users
789 foreach ( $wgLogRestrictions as $logType => $right ) {
790 if ( $audience == 'public' || !MediaWikiServices::getInstance()
792 ->userHasRight( $user, $right )
793 ) {
794 $hiddenLogs[] = $logType;
795 }
796 }
797 if ( count( $hiddenLogs ) == 1 ) {
798 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
799 } elseif ( $hiddenLogs ) {
800 return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
801 }
802
803 return false;
804 }
805}
getPermissionManager()
$wgLogRestrictions
This restricts log access to those who have a certain right Users without this will not see it in the...
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if( $line===false) $args
Definition cdb.php:64
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
static showTagEditingUI(User $user)
Indicate whether change tag editing UI is relevant.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
static newFromRow( $row)
Constructs new LogEntry from database result row.
An IContextSource implementation which will inherit context from another source but allow individual ...
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2176
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2154
LinkRenderer null $linkRenderer
static typeAction( $row, $type, $action, $right='')
const NO_EXTRA_USER_LINKS
getTitlePatternDesc( $pattern)
static getExcludeClause( $db, $audience='public', User $user=null)
SQL clause to skip forbidden log types for this user.
getShowHideLinks( $row)
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
showOptions( $types=[], $user='', $page='', $pattern=false, $year=0, $month=0, $day=0, $filter=null, $tagFilter='', $action=null)
Show options for the log list.
getExtraInputsDesc( $types)
getTitleInputDesc( $title)
static userCan( $row, $field, User $user=null)
Determine if the current user is allowed to view a particular field of this log row,...
__construct( $context, $linkRenderer=null, $flags=0)
The first two parameters used to be $skin and $out, but now only a context is needed,...
getFiltersDesc( $filter)
setAllowedActions( $actions)
Sets the action types allowed for log filtering To one action type may correspond several log_actions...
getTypeMenuDesc( $queryTypes)
static userCanViewLogType( $type, User $user=null)
Determine if the current user is allowed to view a particular field of this log row,...
getActionSelectorDesc( $types, $action)
Drop down menu for selection of actions that can be used to filter the log.
static userCanBitfield( $bitfield, $field, User $user=null)
Determine if the current user is allowed to view a particular field of this log row,...
getUserInputDesc( $user)
static isDeleted( $row, $field)
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
Class to simplify the use of log pages.
Definition LogPage.php:33
const DELETED_RESTRICTED
Definition LogPage.php:37
static validTypes()
Get the list of valid log types.
Definition LogPage.php:198
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
This is one of the Core classes and should be read at least once by any new developers.
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Interface for objects which can provide a MediaWiki context on request.
msg( $key,... $params)
This is the method for getting translated interface messages.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
$filter
const DB_MASTER
Definition defines.php:26
if(!isset( $args[0])) $lang