28use InvalidArgumentException;
76 private $linkRenderer;
95 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->
getAuthority() );
97 $this->linkRenderer = $linkRenderer;
100 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
101 $this->logFormatterFactory = $services->getLogFormatterFactory();
110 if ( $this->linkRenderer !==
null ) {
111 return $this->linkRenderer;
128 public function showOptions( $type =
'', $year = 0, $month = 0, $day = 0 ) {
129 $formDescriptor = [];
132 $formDescriptor[
'type'] = $this->getTypeMenuDesc();
133 $formDescriptor[
'user'] = [
134 'class' => HTMLUserTextField::class,
135 'label-message' =>
'specialloguserlabel',
141 $formDescriptor[
'page'] = [
142 'class' => HTMLTitleTextField::class,
143 'label-message' =>
'speciallogtitlelabel',
150 $formDescriptor[
'pattern'] = [
152 'label-message' =>
'log-title-wildcard',
158 $extraInputsDescriptor = $this->getExtraInputsDesc( $type );
159 if ( $extraInputsDescriptor ) {
160 $formDescriptor[
'extra' ] = $extraInputsDescriptor;
164 $formDescriptor[
'date'] = [
166 'label-message' =>
'date',
167 'default' => $year && $month && $day ? sprintf(
"%04d-%02d-%02d", $year, $month, $day ) :
'',
171 $formDescriptor[
'tagfilter'] = [
172 'type' =>
'tagfilter',
173 'name' =>
'tagfilter',
174 'label-message' =>
'tag-filter',
176 $formDescriptor[
'tagInvert'] = [
178 'name' =>
'tagInvert',
179 'label-message' =>
'invert',
180 'hide-if' => [
'===',
'tagfilter',
'' ],
184 if ( $type ===
'' ) {
185 $formDescriptor[
'filters'] = $this->getFiltersDesc();
190 if ( isset( $allowedActions[$type] ) ) {
191 $formDescriptor[
'subtype'] = $this->getActionSelectorDesc( $type, $allowedActions[$type] );
194 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
197 ->setSubmitTextMsg(
'logeventslist-submit' )
199 ->setWrapperLegendMsg(
'log' )
200 ->setFormIdentifier(
'logeventslist',
true )
202 ->setSubmitCallback(
static function ( $formData, $form ) {
204 (
new LogPage( $formData[
'type'] ) )->getDescription()
205 ->
setContext( $form->getContext() )->parseAsBlock()
210 $result = $htmlForm->prepareForm()->trySubmit();
211 $htmlForm->displayForm( $result );
212 return $result ===
true || ( $result instanceof
Status && $result->
isGood() );
218 private function getFiltersDesc() {
221 foreach ( $filters as $type => $val ) {
222 $optionsMsg[
"logeventslist-{$type}-log"] = $type;
225 'class' => HTMLMultiSelectField::class,
226 'label-message' =>
'logeventslist-more-filters',
228 'options-messages' => $optionsMsg,
229 'default' => array_keys( array_intersect( $filters, [
false ] ) ),
236 private function getTypeMenuDesc() {
240 $page =
new LogPage( $type );
241 $pageText = $page->getName()->text();
242 if ( in_array( $pageText, $typesByName ) ) {
243 LoggerFactory::getInstance(
'translation-problem' )->error(
244 'The log type {log_type_one} has the same translation as {log_type_two} for {lang}. ' .
245 '{log_type_one} will not be displayed in the drop down menu on Special:Log.',
247 'log_type_one' => $type,
248 'log_type_two' => array_search( $pageText, $typesByName ),
254 if ( $this->
getAuthority()->isAllowed( $page->getRestriction() ) ) {
255 $typesByName[$type] = $pageText;
259 asort( $typesByName );
262 $public = $typesByName[
''];
263 unset( $typesByName[
''] );
264 $typesByName = [
'' => $public ] + $typesByName;
267 'class' => HTMLSelectField::class,
269 'options' => array_flip( $typesByName ),
277 private function getExtraInputsDesc( $type ) {
278 if ( $type ===
'suppress' ) {
281 'label-message' =>
'revdelete-offender',
282 'name' =>
'offender',
287 $formDescriptor = [];
288 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
290 return $formDescriptor;
300 private function getActionSelectorDesc( $type, $actions ) {
301 $actionOptions = [
'log-action-filter-all' =>
'' ];
303 foreach ( $actions as $value => $_ ) {
304 $msgKey =
"log-action-filter-$type-$value";
305 $actionOptions[ $msgKey ] = $value;
309 'class' => HTMLSelectField::class,
311 'options-messages' => $actionOptions,
312 'label-message' =>
'log-action-filter-' . $type,
320 return "<ul class='mw-logevent-loglines'>\n";
336 $formatter = $this->logFormatterFactory->newFromEntry( $entry );
337 $formatter->setContext( $this->
getContext() );
338 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
341 $entry->getTimestamp(),
349 [
'logid' => $entry->getId() ]
352 $action = $formatter->getActionText();
354 if ( $this->flags & self::NO_ACTION_LINK ) {
357 $revert = $formatter->getActionLinks();
358 if ( $revert !=
'' ) {
359 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
363 $comment = $formatter->getComment();
366 $del = $this->getShowHideLinks( $row );
369 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
370 $this->tagsCache->makeKey(
372 $this->getUser()->getName(),
373 $this->getLanguage()->getCode()
375 fn () => ChangeTags::formatSummaryRow(
381 $classes = array_merge(
382 [
'mw-logline-' . $entry->getType() ],
386 'data-mw-logid' => $entry->getId(),
387 'data-mw-logaction' => $entry->getFullType(),
389 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
392 $ret .= Html::openElement(
'span', [
'class' =>
'mw-logevent-tool' ] );
396 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
397 $attribs = array_filter( $attribs,
398 [ Sanitizer::class,
'isReservedDataAttribute' ],
401 $ret .= Html::closeElement(
'span' );
402 $attribs[
'class'] = $classes;
404 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
411 private function getShowHideLinks( $row ) {
413 if ( $this->flags == self::NO_ACTION_LINK ) {
418 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
419 return Html::check(
'ids[' . $row->log_id .
']',
false );
423 if ( $row->log_type ==
'suppress' ) {
430 if ( $authority->isAllowed(
'deletedhistory' ) ) {
431 $canHide = $authority->isAllowed(
'deletelogentry' );
432 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
433 !$authority->isAllowed(
'suppressrevision' );
435 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
436 if ( $row->log_deleted || $canHide ) {
438 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
441 $del = Html::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
443 $del = Html::check(
'ids[' . $row->log_id .
']',
false );
448 $del = Linker::revDeleteLinkDisabled( $canHide );
453 'ids' => $row->log_id,
455 $del = Linker::revDeleteLink(
458 $canHide && !$canViewThisSuppressedEntry
475 $match = is_array( $type ) ?
476 in_array( $row->log_type, $type ) : $row->log_type == $type;
478 $match = is_array( $action ) ?
479 in_array( $row->log_action, $action ) : $row->log_action == $action;
509 if ( $bitfield & $field ) {
511 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
513 return $performer->
isAllowed(
'deletedhistory' );
529 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
541 return ( $row->log_deleted & $field ) == $field;
572 &$out, $types = [], $page =
'', $user =
'', $param = []
574 $defaultParameters = [
577 'showIfEmpty' =>
true,
581 'useRequestParams' =>
false,
582 'useMaster' =>
false,
583 'extraUrlParams' =>
false,
584 'footerHtmlItems' => []
586 # The + operator appends elements of remaining keys from the right
587 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
588 $param += $defaultParameters;
589 # Convert $param array to individual variables
590 $lim = $param[
'lim'];
591 $conds = $param[
'conds'];
592 $showIfEmpty = $param[
'showIfEmpty'];
593 $msgKey = $param[
'msgKey'];
594 $wrap = $param[
'wrap'];
596 $extraUrlParams = $param[
'extraUrlParams'];
598 $useRequestParams = $param[
'useRequestParams'];
600 if ( !is_array( $msgKey ) ) {
601 $msgKey = [ $msgKey ];
607 $context = $out->getContext();
609 $context = RequestContext::getMain();
614 $linkRenderer = $services->getLinkRenderer();
616 # Insert list of top 50 (or top $lim) items
631 $services->getLinkBatchFactory(),
632 $services->getActorNormalization(),
633 $services->getLogFormatterFactory()
635 if ( !$useRequestParams ) {
636 # Reset vars that may have been taken from the request
638 $pager->mDefaultLimit = 50;
639 $pager->mOffset =
"";
640 $pager->mIsBackwards =
false;
643 if ( $param[
'useMaster'] ) {
644 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
647 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore
WebRequest offset
648 $pager->setOffset( $param[
'offset'] );
653 $pager->mLimit = $lim;
656 $logBody = $pager->getBody();
657 $numRows = $pager->getNumRows();
660 $footerHtmlItems = [];
665 $msg = $context->msg( ...$msgKey );
669 $s .= $msg->parseAsBlock();
671 $s .= $loglist->beginLogEventsList() .
673 $loglist->endLogEventsList();
675 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
676 } elseif ( $showIfEmpty ) {
677 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
678 $context->msg(
'logempty' )->parse() );
683 $pageName = $titleFormatter->getPrefixedDBkey( $page );
684 } elseif ( $page !=
'' ) {
690 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
693 $urlParam[
'page'] = $pageName;
697 $urlParam[
'user'] = $user;
700 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
704 # If there is exactly one log type, we can link to Special:Log?type=foo
705 if ( count( $types ) == 1 ) {
706 $urlParam['type
'] = $types[0];
709 // @phan-suppress-next-line PhanSuspiciousValueComparison
710 if ( $extraUrlParams !== false ) {
711 $urlParam = array_merge( $urlParam, $extraUrlParams );
714 $footerHtmlItems[] = $linkRenderer->makeKnownLink(
715 SpecialPage::getTitleFor( 'Log
' ),
716 $context->msg( 'log-fulllog
' )->text(),
721 if ( $param['footerHtmlItems
'] ) {
722 $footerHtmlItems = array_merge( $footerHtmlItems, $param['footerHtmlItems
'] );
724 if ( $logBody && $footerHtmlItems ) {
725 $s .= '<ul
class=
"mw-logevent-footer">
';
726 foreach ( $footerHtmlItems as $item ) {
727 $s .= Html::rawElement( 'li
', [], $item );
732 if ( $logBody && $msgKey[0] ) {
733 // TODO: The condition above is weird. Should this be done in any other cases?
734 // Or is it always true in practice?
736 // Mark as interface language (T60685)
737 $dir = $context->getLanguage()->getDir();
738 $lang = $context->getLanguage()->getHtmlCode();
739 $s = Html::rawElement( 'div
', [
740 'class' => "mw-content-$dir",
745 // Wrap in warning box
746 $s = Html::warningBox(
748 'mw-warning-with-logexcerpt
'
750 // Add styles for warning box
751 $context->getOutput()->addModuleStyles( 'mediawiki.codex.messagebox.styles
' );
754 // @phan-suppress-next-line PhanSuspiciousValueComparison
755 if ( $wrap != '' ) { // Wrap message in html
756 $s = str_replace( '$1
', $s, $wrap );
759 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
760 $hookRunner =
new HookRunner( $services->getHookContainer() );
785 if ( $audience !=
'public' && $performer ===
null ) {
786 throw new InvalidArgumentException(
787 'A User object must be given when checking for a user audience.'
795 foreach ( $logRestrictions as $logType => $right ) {
796 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
797 $hiddenLogs[] = $logType;
800 if ( count( $hiddenLogs ) == 1 ) {
801 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
802 } elseif ( $hiddenLogs ) {
803 return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) .
')';
834 $appliesToTitle =
false;
836 $blockTargetName =
'';
838 DatabaseBlockStore::AUTO_NONE );
839 foreach ( $blocks as $block ) {
840 if ( $block->appliesToTitle( $title ) ) {
841 $appliesToTitle =
true;
843 $blockTargetName = $block->getTargetName();
845 ':' . $blockTargetName;
850 if ( !count( $blocks ) || !$appliesToTitle ) {
853 $msgKey = count( $blocks ) === 1
854 ?
'blocked-notice-logextract' :
'blocked-notice-logextract-multi';
857 'showIfEmpty' =>
false,
860 $user->getName(), # Support GENDER in notice
864 if ( count( $blocks ) > 1 ) {
865 $params[
'footerHtmlItems'] = [
868 $localizer->
msg(
'blocked-notice-list-link' )->text(),
870 [
'wpTarget' => $blockTargetName ]
877 return $outString ?:
null;
882class_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.