59 private $linkRenderer;
73 public function __construct( $context, $linkRenderer =
null, $flags = 0 ) {
78 $this->linkRenderer = $linkRenderer;
80 $this->hookRunner =
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
89 if ( $this->linkRenderer !==
null ) {
90 return $this->linkRenderer;
92 return MediaWikiServices::getInstance()->getLinkRenderer();
107 public function showOptions( $type =
'', $year = 0, $month = 0, $day = 0 ) {
108 $formDescriptor = [];
111 $formDescriptor[
'type'] = $this->getTypeMenuDesc();
112 $formDescriptor[
'user'] = [
113 'class' => HTMLUserTextField::class,
114 'label-message' =>
'specialloguserlabel',
120 $formDescriptor[
'page'] = [
121 'class' => HTMLTitleTextField::class,
122 'label-message' =>
'speciallogtitlelabel',
128 if ( !$this->
getConfig()->
get( MainConfigNames::MiserMode ) ) {
129 $formDescriptor[
'pattern'] = [
131 'label-message' =>
'log-title-wildcard',
137 $extraInputsDescriptor = $this->getExtraInputsDesc( $type );
138 if ( $extraInputsDescriptor ) {
139 $formDescriptor[
'extra' ] = $extraInputsDescriptor;
143 $formDescriptor[
'date'] = [
145 'label-message' =>
'date',
146 'default' => $year && $month && $day ? sprintf(
"%04d-%02d-%02d", $year, $month, $day ) :
'',
150 $formDescriptor[
'tagfilter'] = [
151 'type' =>
'tagfilter',
152 'name' =>
'tagfilter',
153 'label-message' =>
'tag-filter',
155 $formDescriptor[
'tagInvert'] = [
157 'name' =>
'tagInvert',
158 'label-message' =>
'invert',
159 'hide-if' => [
'===',
'tagfilter',
'' ],
163 if ( $type ===
'' ) {
164 $formDescriptor[
'filters'] = $this->getFiltersDesc();
168 $allowedActions = $this->
getConfig()->get( MainConfigNames::ActionFilteredLogs );
169 if ( isset( $allowedActions[$type] ) ) {
170 $formDescriptor[
'subtype'] = $this->getActionSelectorDesc( $type, $allowedActions[$type] );
173 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
175 ->setTitle( SpecialPage::getTitleFor(
'Log' ) )
176 ->setSubmitTextMsg(
'logeventslist-submit' )
178 ->setWrapperLegendMsg(
'log' )
179 ->setFormIdentifier(
'logeventslist',
true )
181 ->setSubmitCallback(
static function ( $formData, $form ) {
183 (
new LogPage( $formData[
'type'] ) )->getDescription()
184 ->
setContext( $form->getContext() )->parseAsBlock()
189 $result = $htmlForm->prepareForm()->trySubmit();
190 $htmlForm->displayForm( $result );
191 return $result ===
true || ( $result instanceof
Status && $result->
isGood() );
197 private function getFiltersDesc() {
199 $filters = $this->getConfig()->get( MainConfigNames::FilterLogTypes );
200 foreach ( $filters as $type => $val ) {
201 $optionsMsg[
"logeventslist-{$type}-log"] = $type;
204 'class' => HTMLMultiSelectField::class,
205 'label-message' =>
'logeventslist-more-filters',
207 'options-messages' => $optionsMsg,
208 'default' => array_keys( array_intersect( $filters, [
false ] ) ),
215 private function getTypeMenuDesc() {
218 foreach ( LogPage::validTypes() as $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.',
226 'log_type_one' => $type,
227 'log_type_two' => array_search( $pageText, $typesByName ),
233 if ( $this->
getAuthority()->isAllowed( $page->getRestriction() ) ) {
234 $typesByName[$type] = $pageText;
238 asort( $typesByName );
241 $public = $typesByName[
''];
242 unset( $typesByName[
''] );
243 $typesByName = [
'' => $public ] + $typesByName;
246 'class' => HTMLSelectField::class,
248 'options' => array_flip( $typesByName ),
256 private function getExtraInputsDesc( $type ) {
257 if ( $type ===
'suppress' ) {
260 'label-message' =>
'revdelete-offender',
261 'name' =>
'offender',
266 $formDescriptor = [];
267 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
269 return $formDescriptor;
279 private function getActionSelectorDesc( $type, $actions ) {
280 $actionOptions = [
'log-action-filter-all' =>
'' ];
282 foreach ( $actions as $value => $_ ) {
283 $msgKey =
"log-action-filter-$type-$value";
284 $actionOptions[ $msgKey ] = $value;
288 'class' => HTMLSelectField::class,
290 'options-messages' => $actionOptions,
291 'label-message' =>
'log-action-filter-' . $type,
299 return "<ul class='mw-logevent-loglines'>\n";
314 $entry = DatabaseLogEntry::newFromRow( $row );
315 $formatter = LogFormatter::newFromEntry( $entry );
316 $formatter->setContext( $this->
getContext() );
318 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
321 $entry->getTimestamp(),
326 SpecialPage::getTitleValueFor(
'Log' ),
329 [
'logid' => $entry->getId() ]
332 $action = $formatter->getActionText();
334 if ( $this->flags & self::NO_ACTION_LINK ) {
337 $revert = $formatter->getActionLinks();
338 if ( $revert !=
'' ) {
339 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
343 $comment = $formatter->getComment();
346 $del = $this->getShowHideLinks( $row );
349 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
350 $this->tagsCache->makeKey(
352 $this->getUser()->getName(),
353 $this->getLanguage()->getCode()
361 $classes = array_merge(
362 [
'mw-logline-' . $entry->getType() ],
366 'data-mw-logid' => $entry->getId(),
367 'data-mw-logaction' => $entry->getFullType(),
369 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
372 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
373 $attribs = array_filter( $attribs,
374 [ Sanitizer::class,
'isReservedDataAttribute' ],
377 $attribs[
'class'] = $classes;
379 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
386 private function getShowHideLinks( $row ) {
388 if ( $this->flags == self::NO_ACTION_LINK ) {
393 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
397 [
'name' =>
'ids[' . $row->log_id .
']' ]
402 if ( $row->log_type ==
'suppress' ) {
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 ) {
417 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
419 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
420 $del =
Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
425 [
'name' =>
'ids[' . $row->log_id .
']' ]
430 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
431 $del = Linker::revDeleteLinkDisabled( $canHide );
434 'target' => SpecialPage::getTitleFor(
'Log', $row->log_type )->getPrefixedDBkey(),
436 'ids' => $row->log_id,
438 $del = Linker::revDeleteLink(
441 $canHide && !$canViewThisSuppressedEntry
458 $match = is_array( $type ) ?
459 in_array( $row->log_type, $type ) : $row->log_type == $type;
461 $match = is_array( $action ) ?
462 in_array( $row->log_action, $action ) : $row->log_action == $action;
478 return self::userCanBitfield( $row->log_deleted, $field, $performer ) &&
479 self::userCanViewLogType( $row->log_type, $performer );
492 if ( $bitfield & $field ) {
493 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
494 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
496 return $performer->
isAllowed(
'deletedhistory' );
511 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
512 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
524 return ( $row->log_deleted & $field ) == $field;
553 &$out, $types = [], $page =
'', $user =
'', $param = []
555 $defaultParameters = [
558 'showIfEmpty' =>
true,
562 'useRequestParams' =>
false,
563 'useMaster' =>
false,
564 'extraUrlParams' =>
false,
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'];
576 $extraUrlParams = $param[
'extraUrlParams'];
578 $useRequestParams = $param[
'useRequestParams'];
580 if ( !is_array( $msgKey ) ) {
581 $msgKey = [ $msgKey ];
585 $context = $out->getContext();
587 $context = RequestContext::getMain();
590 $services = MediaWikiServices::getInstance();
592 $linkRenderer = $services->getLinkRenderer();
594 # Insert list of top 50 (or top $lim) items
609 $services->getLinkBatchFactory(),
610 $services->getActorNormalization()
613 if ( !$useRequestParams ) {
614 # Reset vars that may have been taken from the request
616 $pager->mDefaultLimit = 50;
617 $pager->mOffset =
"";
618 $pager->mIsBackwards =
false;
622 if ( $param[
'useMaster'] ) {
623 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
626 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore WebRequest offset
627 $pager->setOffset( $param[
'offset'] );
632 $pager->mLimit = $lim;
635 $logBody = $pager->getBody();
636 $numRows = $pager->getNumRows();
643 $msg = $context->msg( ...$msgKey );
647 $s .= $msg->parseAsBlock();
649 $s .= $loglist->beginLogEventsList() .
651 $loglist->endLogEventsList();
653 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
655 } elseif ( $showIfEmpty ) {
656 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
657 $context->msg(
'logempty' )->parse() );
661 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
662 $pageName = $titleFormatter->getPrefixedDBkey( $page );
663 } elseif ( $page !=
'' ) {
669 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
672 $urlParam[
'page'] = $pageName;
676 $urlParam[
'user'] = $user;
679 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
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];
688 // @phan-suppress-next-line PhanSuspiciousValueComparison
689 if ( $extraUrlParams !== false ) {
690 $urlParam = array_merge( $urlParam, $extraUrlParams );
693 $s .= $linkRenderer->makeKnownLink(
694 SpecialPage::getTitleFor( 'Log
' ),
695 $context->msg( 'log-fulllog
' )->text(),
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?
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",
714 // Wrap in warning box
715 $s = Html::warningBox(
717 'mw-warning-with-logexcerpt
'
721 // @phan-suppress-next-line PhanSuspiciousValueComparison, PhanRedundantCondition
722 if ( $wrap != '' ) { // Wrap message in html
723 $s = str_replace( '$1
', $s, $wrap );
726 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
727 $hookRunner =
new HookRunner( $services->getHookContainer() );
750 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
752 if ( $audience !=
'public' && $performer ===
null ) {
753 throw new InvalidArgumentException(
754 'A User object must be given when checking for a user audience.'
762 foreach ( $logRestrictions as $logType => $right ) {
763 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
764 $hiddenLogs[] = $logType;
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 ) .
')';
const NO_EXTRA_USER_LINKS
static typeAction( $row, $type, $action)
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
static getExcludeClause( $db, $audience='public', Authority $performer=null)
SQL clause to skip forbidden log types for this user.
showOptions( $type='', $year=0, $month=0, $day=0)
Show options for the log list.
static userCan( $row, $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this log row,...
static userCanBitfield( $bitfield, $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this log row,...
__construct( $context, $linkRenderer=null, $flags=0)
static userCanViewLogType( $type, Authority $performer)
Determine if the current user is allowed to view a particular field of this log row,...
static isDeleted( $row, $field)
Class to simplify the use of log pages.
Store key-value entries in a size-limited in-memory LRU cache.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
setContext(IContextSource $context)
Group all the pieces relevant to the context of a request into one instance.
A class containing constants representing the names of configuration variables.
This is one of the Core classes and should be read at least once by any new developers.
Parent class for all special pages.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Interface for objects which can provide a MediaWiki context on request.