14use InvalidArgumentException;
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 ),
285 private function getExtraInputsDesc( $type, $username ) {
286 $formDescriptor = [];
288 if ( $type ===
'suppress' ) {
289 $formDescriptor[] = [
291 'label-message' =>
'revdelete-offender',
292 'name' =>
'offender',
294 return $formDescriptor;
297 if ( $this->tempUserConfig->isKnown() ) {
301 $fieldType =
'hidden';
302 if ( $type ===
'newusers' || $type ===
'' ) {
303 $fieldType =
'check';
305 $formDescriptor[] = [
306 'type' => $fieldType,
307 'label-message' =>
'newusers-excludetempacct',
308 'name' =>
'excludetempacct',
309 'default' => !$this->tempUserConfig->isTempName( $username ),
315 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
317 return $formDescriptor;
326 private function getActionSelectorDesc( $type, $actions ) {
327 $actionOptions = [
'log-action-filter-all' =>
'' ];
329 foreach ( $actions as $value => $_ ) {
330 $msgKey =
"log-action-filter-$type-$value";
331 $actionOptions[ $msgKey ] = $value;
335 'class' => HTMLSelectField::class,
337 'options-messages' => $actionOptions,
338 'label-message' =>
'log-action-filter-' . $type,
346 return "<ul class='mw-logevent-loglines'>\n";
362 $formatter = $this->logFormatterFactory->newFromEntry( $entry );
363 $formatter->setContext( $this->
getContext() );
364 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
367 $entry->getTimestamp(),
375 [
'logid' => $entry->getId() ]
378 $action = $formatter->getActionText();
380 if ( $this->flags & self::NO_ACTION_LINK ) {
383 $revert = $formatter->getActionLinks();
384 if ( $revert !=
'' ) {
385 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
389 $comment = $formatter->getComment();
392 $del = $this->getShowHideLinks( $row );
395 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
396 $this->tagsCache->makeKey(
398 $this->getUser()->getName(),
399 $this->getLanguage()->getCode()
401 fn () => ChangeTags::formatSummaryRow(
407 $classes = [
'mw-logline-' . $entry->getType(), ...$newClasses ];
409 'data-mw-logid' => $entry->getId(),
410 'data-mw-logaction' => $entry->getFullType(),
412 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
415 $ret .= Html::openElement(
'span', [
'class' =>
'mw-logevent-tool' ] );
419 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
420 $attribs = array_filter( $attribs,
421 Sanitizer::isReservedDataAttribute( ... ),
424 $ret .= Html::closeElement(
'span' );
425 $attribs[
'class'] = $classes;
427 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
434 private function getShowHideLinks( $row ) {
436 if ( $this->flags == self::NO_ACTION_LINK ) {
441 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
442 return Html::check(
'ids[' . $row->log_id .
']',
false );
446 if ( $row->log_type ==
'suppress' ) {
453 if ( $authority->isAllowed(
'deletedhistory' ) ) {
454 $canHide = $authority->isAllowed(
'deletelogentry' );
455 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
456 !$authority->isAllowed(
'suppressrevision' );
458 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
459 if ( $row->log_deleted || $canHide ) {
461 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
464 $del = Html::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
466 $del = Html::check(
'ids[' . $row->log_id .
']',
false );
471 $del = Linker::revDeleteLinkDisabled( $canHide );
476 'ids' => $row->log_id,
478 $del = Linker::revDeleteLink(
481 $canHide && !$canViewThisSuppressedEntry
498 $match = is_array( $type ) ?
499 in_array( $row->log_type, $type ) : $row->log_type == $type;
501 $match = is_array( $action ) ?
502 in_array( $row->log_action, $action ) : $row->log_action == $action;
532 if ( $bitfield & $field ) {
534 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
536 return $performer->
isAllowed(
'deletedhistory' );
552 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
564 return ( $row->log_deleted & $field ) == $field;
596 &$out, $types = [], $pages =
'', $user =
'', $param = []
598 $defaultParameters = [
601 'showIfEmpty' =>
true,
605 'useRequestParams' =>
false,
606 'useMaster' =>
false,
607 'extraUrlParams' =>
false,
608 'footerHtmlItems' => []
610 # The + operator appends elements of remaining keys from the right
611 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
612 $param += $defaultParameters;
613 # Convert $param array to individual variables
614 $lim = $param[
'lim'];
615 $conds = $param[
'conds'];
616 $showIfEmpty = $param[
'showIfEmpty'];
617 $msgKey = $param[
'msgKey'];
618 $wrap = $param[
'wrap'];
620 $extraUrlParams = $param[
'extraUrlParams'];
622 $useRequestParams = $param[
'useRequestParams'];
623 if ( !is_array( $msgKey ) ) {
624 $msgKey = [ $msgKey ];
630 $context = $out->getContext();
632 $context = RequestContext::getMain();
637 $linkRenderer = $services->getLinkRenderer();
639 if ( !is_array( $pages ) ) {
643 # Insert list of top 50 (or top $lim) items
658 $services->getLinkBatchFactory(),
659 $services->getActorNormalization(),
660 $services->getLogFormatterFactory()
662 if ( !$useRequestParams ) {
663 # Reset vars that may have been taken from the request
665 $pager->mDefaultLimit = 50;
666 $pager->mOffset =
"";
667 $pager->mIsBackwards =
false;
670 if ( $param[
'useMaster'] ) {
671 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
674 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore
WebRequest offset
675 $pager->setOffset( $param[
'offset'] );
679 $pager->mLimit = $lim;
682 $logBody = $pager->getBody();
683 $numRows = $pager->getNumRows();
686 $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, string|false $wikiId=WikiAwareEntity::LOCAL)
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.