14use InvalidArgumentException;
34use MediaWiki\Pager\LogPager;
45use UnexpectedValueException;
68 private $linkRenderer;
89 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->
getAuthority() );
91 $this->linkRenderer = $linkRenderer;
94 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
95 $this->logFormatterFactory = $services->getLogFormatterFactory();
97 $this->tempUserConfig = $services->getTempUserConfig();
105 if ( $this->linkRenderer !==
null ) {
106 return $this->linkRenderer;
124 public function showOptions( $type =
'', $year = 0, $month = 0, $day = 0, $username =
'' ) {
125 $formDescriptor = [];
128 $formDescriptor[
'type'] = $this->getTypeMenuDesc();
129 $formDescriptor[
'user'] = [
130 'class' => HTMLUserTextField::class,
131 'label-message' =>
'specialloguserlabel',
137 $formDescriptor[
'page'] = [
138 'class' => HTMLTitleTextField::class,
139 'label-message' =>
'speciallogtitlelabel',
146 $formDescriptor[
'pattern'] = [
148 'label-message' =>
'log-title-wildcard',
154 $extraInputsDescriptor = $this->getExtraInputsDesc( $type, $username );
159 if ( $extraInputsDescriptor ) {
160 if ( isset( $extraInputsDescriptor[0] ) && is_array( $extraInputsDescriptor[0] ) ) {
161 foreach ( $extraInputsDescriptor as $i => $input ) {
162 $formDescriptor[
'extra_' . $i ] = $input;
165 $formDescriptor[
'extra' ] = $extraInputsDescriptor;
170 $formDescriptor[
'date'] = [
172 'label-message' =>
'date',
173 'default' => $year && $month && $day ? sprintf(
"%04d-%02d-%02d", $year, $month, $day ) :
'',
177 $formDescriptor[
'tagfilter'] = [
178 'type' =>
'tagfilter',
179 'name' =>
'tagfilter',
180 'label-message' =>
'tag-filter',
182 $formDescriptor[
'tagInvert'] = [
184 'name' =>
'tagInvert',
185 'label-message' =>
'invert',
186 'hide-if' => [
'===',
'tagfilter',
'' ],
190 if ( $type ===
'' ) {
191 $formDescriptor[
'filters'] = $this->getFiltersDesc();
196 if ( isset( $allowedActions[$type] ) ) {
197 $formDescriptor[
'subtype'] = $this->getActionSelectorDesc( $type, $allowedActions[$type] );
200 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
203 ->setSubmitTextMsg(
'logeventslist-submit' )
205 ->setWrapperLegendMsg(
'log' )
206 ->setFormIdentifier(
'logeventslist',
true )
208 ->setSubmitCallback(
static function ( $formData, $form ) {
210 (
new LogPage( $formData[
'type'] ) )->getDescription()
211 ->
setContext( $form->getContext() )->parseAsBlock()
216 $result = $htmlForm->prepareForm()->trySubmit();
217 $htmlForm->displayForm( $result );
218 return $result ===
true || ( $result instanceof
Status && $result->
isGood() );
224 private function getFiltersDesc() {
227 foreach ( $filters as $type => $val ) {
228 $optionsMsg[
"logeventslist-{$type}-log"] = $type;
231 'class' => HTMLMultiSelectField::class,
232 'label-message' =>
'logeventslist-more-filters',
234 'options-messages' => $optionsMsg,
235 'default' => array_keys( array_intersect( $filters, [
false ] ) ),
242 private function getTypeMenuDesc() {
246 $page =
new LogPage( $type );
247 $pageText = $page->getName()->text();
248 if ( in_array( $pageText, $typesByName ) ) {
249 LoggerFactory::getInstance(
'translation-problem' )->error(
250 'The log type {log_type_one} has the same translation as {log_type_two} for {lang}. ' .
251 '{log_type_one} will not be displayed in the drop down menu on Special:Log.',
253 'log_type_one' => $type,
254 'log_type_two' => array_search( $pageText, $typesByName ),
260 if ( $this->
getAuthority()->isAllowed( $page->getRestriction() ) ) {
261 $typesByName[$type] = $pageText;
265 asort( $typesByName );
268 $public = $typesByName[
''];
269 unset( $typesByName[
''] );
270 $typesByName = [
'' => $public ] + $typesByName;
273 'class' => HTMLSelectField::class,
275 'options' => array_flip( $typesByName ),
284 private function getExtraInputsDesc( $type, $username ) {
285 $formDescriptor = [];
287 if ( $type ===
'suppress' ) {
288 $formDescriptor[] = [
290 'label-message' =>
'revdelete-offender',
291 'name' =>
'offender',
293 return $formDescriptor;
296 if ( $this->tempUserConfig->isKnown() ) {
300 $fieldType =
'hidden';
301 if ( $type ===
'newusers' || $type ===
'' ) {
302 $fieldType =
'check';
304 $formDescriptor[] = [
305 'type' => $fieldType,
306 'label-message' =>
'newusers-excludetempacct',
307 'name' =>
'excludetempacct',
308 'default' => !$this->tempUserConfig->isTempName( $username ),
314 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
316 return $formDescriptor;
325 private function getActionSelectorDesc( $type, $actions ) {
326 $actionOptions = [
'log-action-filter-all' =>
'' ];
328 foreach ( $actions as $value => $_ ) {
329 $msgKey =
"log-action-filter-$type-$value";
330 $actionOptions[ $msgKey ] = $value;
334 'class' => HTMLSelectField::class,
336 'options-messages' => $actionOptions,
337 'label-message' =>
'log-action-filter-' . $type,
345 return "<ul class='mw-logevent-loglines'>\n";
361 $formatter = $this->logFormatterFactory->newFromEntry( $entry );
362 $formatter->setContext( $this->
getContext() );
363 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
366 $entry->getTimestamp(),
374 [
'logid' => $entry->getId() ]
377 $action = $formatter->getActionText();
379 if ( $this->flags & self::NO_ACTION_LINK ) {
382 $revert = $formatter->getActionLinks();
383 if ( $revert !=
'' ) {
384 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
388 $comment = $formatter->getComment();
391 $del = $this->getShowHideLinks( $row );
394 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
395 $this->tagsCache->makeKey(
397 $this->getUser()->getName(),
398 $this->getLanguage()->getCode()
400 fn () => ChangeTags::formatSummaryRow(
406 $classes = [
'mw-logline-' . $entry->getType(), ...$newClasses ];
408 'data-mw-logid' => $entry->getId(),
409 'data-mw-logaction' => $entry->getFullType(),
411 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
414 $ret .= Html::openElement(
'span', [
'class' =>
'mw-logevent-tool' ] );
418 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
419 $attribs = array_filter( $attribs,
420 Sanitizer::isReservedDataAttribute( ... ),
423 $ret .= Html::closeElement(
'span' );
424 $attribs[
'class'] = $classes;
426 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
433 private function getShowHideLinks( $row ) {
435 if ( $this->flags == self::NO_ACTION_LINK ) {
440 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
441 return Html::check(
'ids[' . $row->log_id .
']',
false );
445 if ( $row->log_type ==
'suppress' ) {
452 if ( $authority->isAllowed(
'deletedhistory' ) ) {
453 $canHide = $authority->isAllowed(
'deletelogentry' );
454 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
455 !$authority->isAllowed(
'suppressrevision' );
457 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
458 if ( $row->log_deleted || $canHide ) {
460 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
463 $del = Html::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
465 $del = Html::check(
'ids[' . $row->log_id .
']',
false );
470 $del = Linker::revDeleteLinkDisabled( $canHide );
475 'ids' => $row->log_id,
477 $del = Linker::revDeleteLink(
480 $canHide && !$canViewThisSuppressedEntry
497 $match = is_array( $type ) ?
498 in_array( $row->log_type, $type ) : $row->log_type == $type;
500 $match = is_array( $action ) ?
501 in_array( $row->log_action, $action ) : $row->log_action == $action;
531 if ( $bitfield & $field ) {
533 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
535 return $performer->
isAllowed(
'deletedhistory' );
551 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
563 return ( $row->log_deleted & $field ) == $field;
595 &$out, $types = [], $pages =
'', $user =
'', $param = []
597 $defaultParameters = [
600 'showIfEmpty' =>
true,
604 'useRequestParams' =>
false,
605 'useMaster' =>
false,
606 'extraUrlParams' =>
false,
607 'footerHtmlItems' => []
609 # The + operator appends elements of remaining keys from the right
610 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
611 $param += $defaultParameters;
612 # Convert $param array to individual variables
613 $lim = $param[
'lim'];
614 $conds = $param[
'conds'];
615 $showIfEmpty = $param[
'showIfEmpty'];
616 $msgKey = $param[
'msgKey'];
617 $wrap = $param[
'wrap'];
619 $extraUrlParams = $param[
'extraUrlParams'];
621 $useRequestParams = $param[
'useRequestParams'];
622 if ( !is_array( $msgKey ) ) {
623 $msgKey = [ $msgKey ];
629 $context = $out->getContext();
631 $context = RequestContext::getMain();
636 $linkRenderer = $services->getLinkRenderer();
638 if ( !is_array( $pages ) ) {
642 # Insert list of top 50 (or top $lim) items
644 $pager =
new LogPager(
657 $services->getLinkBatchFactory(),
658 $services->getActorNormalization(),
659 $services->getLogFormatterFactory()
661 if ( !$useRequestParams ) {
662 # Reset vars that may have been taken from the request
664 $pager->mDefaultLimit = 50;
665 $pager->mOffset =
"";
666 $pager->mIsBackwards =
false;
669 if ( $param[
'useMaster'] ) {
670 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
673 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore
WebRequest offset
674 $pager->setOffset( $param[
'offset'] );
678 $pager->mLimit = $lim;
681 $logBody = $pager->getBody();
682 $numRows = $pager->getNumRows();
685 $footerHtmlItems = [];
690 $msg = $context->msg( ...$msgKey );
692 $msg->page( $pages[0] );
694 $s .= $msg->parseAsBlock();
696 $s .= $loglist->beginLogEventsList() .
698 $loglist->endLogEventsList();
700 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
701 } elseif ( $showIfEmpty ) {
702 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
703 $context->msg(
'logempty' )->parse() );
707 foreach ( $pages as $page ) {
710 $pageNames[] = $titleFormatter->getPrefixedDBkey( $page );
711 } elseif ( $page !=
'' ) {
712 $pageNames[] = $page;
716 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
719 $urlParam[
'page'] = count( $pageNames ) > 1 ? $pageNames : $pageNames[0];
723 $urlParam[
'user'] = $user;
726 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
730 # If there is exactly one log type, we can link to Special:Log?type=foo
731 if ( count( $types ) == 1 ) {
732 $urlParam['type
'] = $types[0];
735 if ( $extraUrlParams !== false ) {
736 $urlParam = array_merge( $urlParam, $extraUrlParams );
739 $footerHtmlItems[] = $linkRenderer->makeKnownLink(
740 SpecialPage::getTitleFor( 'Log
' ),
741 $context->msg( 'log-fulllog
' )->text(),
746 if ( $param['footerHtmlItems
'] ) {
747 $footerHtmlItems = array_merge( $footerHtmlItems, $param['footerHtmlItems
'] );
749 if ( $logBody && $footerHtmlItems ) {
750 $s .= '<ul
class=
"mw-logevent-footer">
';
751 foreach ( $footerHtmlItems as $item ) {
752 $s .= Html::rawElement( 'li
', [], $item );
757 if ( $logBody && $msgKey[0] ) {
758 // TODO: The condition above is weird. Should this be done in any other cases?
759 // Or is it always true in practice?
761 // Mark as interface language (T60685)
762 $dir = $context->getLanguage()->getDir();
763 $lang = $context->getLanguage()->getHtmlCode();
764 $s = Html::rawElement( 'div
', [
765 'class' => "mw-content-$dir",
770 // Wrap in warning box
771 $s = Html::warningBox(
773 'mw-warning-with-logexcerpt
'
775 // Add styles for warning box
776 $context->getOutput()->addModuleStyles( 'mediawiki.codex.messagebox.styles
' );
779 if ( $wrap != '' ) { // Wrap message in html
780 $s = str_replace( '$1
', $s, $wrap );
783 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
784 $hookRunner =
new HookRunner( $services->getHookContainer() );
786 $s, $types, $pageNames, $user, $param
811 if ( $audience !=
'public' && $performer ===
null ) {
812 throw new InvalidArgumentException(
813 'A User object must be given when checking for a user audience.'
821 foreach ( $logRestrictions as $logType => $right ) {
822 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
823 $hiddenLogs[] = $logType;
826 if ( count( $hiddenLogs ) == 1 ) {
827 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
828 } elseif ( $hiddenLogs ) {
829 return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) .
')';
866 array|callable $additionalParams = []
874 $userOrRange = IPUtils::isValidRange( $user->getName() ) ? $user->getName() : $user;
878 $userOrRange, $userOrRange,
false, DatabaseBlockStore::AUTO_NONE
880 if ( !count( $blocks ) ) {
884 $isAnon = !$user->isRegistered();
885 $appliesToTitle =
false;
886 $logTargetPages = [];
888 $matchingIpFound =
false;
889 $newestBlockTimestamp =
null;
891 foreach ( $blocks as $block ) {
892 if ( $title ===
null || $block->appliesToTitle( $title ) ) {
893 $appliesToTitle =
true;
895 $blockTargetName = $block->getTargetName();
898 if ( $block->isSitewide() ) {
913 $isExactIpMatch = $isAnon && $user->getName() === $blockTargetName;
914 if ( ( $isExactIpMatch || !$matchingIpFound ) && (
915 $newestBlockTimestamp ===
null ||
916 $block->getTimestamp() > $newestBlockTimestamp ||
917 ( $block->getTimestamp() === $newestBlockTimestamp && $block->getId() > $blockId )
919 $newestBlockTimestamp = $block->getTimestamp();
920 $blockId = $block->getId();
924 if ( $isExactIpMatch ) {
925 $matchingIpFound =
true;
932 if ( !$appliesToTitle ) {
936 if ( count( $blocks ) === 1 ) {
938 $msgKey = $sitewide ?
939 'blocked-notice-logextract-anon' :
940 'blocked-notice-logextract-anon-partial';
942 $msgKey = $sitewide ?
943 'blocked-notice-logextract' :
944 'blocked-notice-logextract-partial';
948 $msgKey =
'blocked-notice-logextract-anon-multi';
950 $msgKey =
'blocked-notice-logextract-multi';
959 $orCondsForBlockId = [];
960 $orCondsForBlockId[] = $dbr->expr(
964 IExpression::NOT_LIKE,
967 if ( $blockId !==
null ) {
968 $orCondsForBlockId[] = $dbr->expr(
974 $conds = [ $dbr->makeList( $orCondsForBlockId,
LIST_OR ) ];
979 'showIfEmpty' =>
false,
988 if ( count( $blocks ) > 1 ) {
989 $params[
'footerHtmlItems'] = [
992 $localizer->
msg(
'blocked-notice-list-link' )->text(),
994 [
'wpTarget' => $user->getName() ]
999 if ( is_callable( $additionalParams ) ) {
1000 $extraParams = $additionalParams( [
1003 'blocks' => $blocks,
1004 'sitewide' => $sitewide,
1005 'logTargetPages' => $logTargetPages
1007 if ( !is_array( $extraParams ) ) {
1008 throw new UnexpectedValueException(
1009 'The callable $additionalParams must return an array, ' . gettype( $extraParams ) .
' given'
1012 $params += $extraParams;
1014 $params += $additionalParams;
1019 return $outString ?:
null;
1024class_alias( LogEventsList::class,
'LogEventsList' );
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
setContext(IContextSource $context)
getContext()
Get the base IContextSource object.
Group all the pieces relevant to the context of a request into one instance.
Implements a text input field for page titles.
Implements a text input field for user names.
static newFromRow( $row)
Constructs new LogEntry from database result row.
Class to simplify the use of log pages.
static validTypes()
Get the list of valid log types.
A class containing constants representing the names of configuration variables.
const LogRestrictions
Name constant for the LogRestrictions setting, for use with Config::get()
const ActionFilteredLogs
Name constant for the ActionFilteredLogs setting, for use with Config::get()
const FilterLogTypes
Name constant for the FilterLogTypes setting, for use with Config::get()
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
This is one of the Core classes and should be read at least once by any new developers.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Interface for objects which can provide a MediaWiki context on request.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.