28use InvalidArgumentException;
77 private $linkRenderer;
96 $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->
getAuthority() );
98 $this->linkRenderer = $linkRenderer;
101 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
102 $this->logFormatterFactory = $services->getLogFormatterFactory();
111 if ( $this->linkRenderer !==
null ) {
112 return $this->linkRenderer;
129 public function showOptions( $type =
'', $year = 0, $month = 0, $day = 0 ) {
130 $formDescriptor = [];
133 $formDescriptor[
'type'] = $this->getTypeMenuDesc();
134 $formDescriptor[
'user'] = [
135 'class' => HTMLUserTextField::class,
136 'label-message' =>
'specialloguserlabel',
142 $formDescriptor[
'page'] = [
143 'class' => HTMLTitleTextField::class,
144 'label-message' =>
'speciallogtitlelabel',
151 $formDescriptor[
'pattern'] = [
153 'label-message' =>
'log-title-wildcard',
159 $extraInputsDescriptor = $this->getExtraInputsDesc( $type );
160 if ( $extraInputsDescriptor ) {
161 $formDescriptor[
'extra' ] = $extraInputsDescriptor;
165 $formDescriptor[
'date'] = [
167 'label-message' =>
'date',
168 'default' => $year && $month && $day ? sprintf(
"%04d-%02d-%02d", $year, $month, $day ) :
'',
172 $formDescriptor[
'tagfilter'] = [
173 'type' =>
'tagfilter',
174 'name' =>
'tagfilter',
175 'label-message' =>
'tag-filter',
177 $formDescriptor[
'tagInvert'] = [
179 'name' =>
'tagInvert',
180 'label-message' =>
'invert',
181 'hide-if' => [
'===',
'tagfilter',
'' ],
185 if ( $type ===
'' ) {
186 $formDescriptor[
'filters'] = $this->getFiltersDesc();
191 if ( isset( $allowedActions[$type] ) ) {
192 $formDescriptor[
'subtype'] = $this->getActionSelectorDesc( $type, $allowedActions[$type] );
195 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
198 ->setSubmitTextMsg(
'logeventslist-submit' )
200 ->setWrapperLegendMsg(
'log' )
201 ->setFormIdentifier(
'logeventslist',
true )
203 ->setSubmitCallback(
static function ( $formData, $form ) {
205 (
new LogPage( $formData[
'type'] ) )->getDescription()
206 ->
setContext( $form->getContext() )->parseAsBlock()
211 $result = $htmlForm->prepareForm()->trySubmit();
212 $htmlForm->displayForm( $result );
213 return $result ===
true || ( $result instanceof
Status && $result->
isGood() );
219 private function getFiltersDesc() {
222 foreach ( $filters as $type => $val ) {
223 $optionsMsg[
"logeventslist-{$type}-log"] = $type;
226 'class' => HTMLMultiSelectField::class,
227 'label-message' =>
'logeventslist-more-filters',
229 'options-messages' => $optionsMsg,
230 'default' => array_keys( array_intersect( $filters, [
false ] ) ),
237 private function getTypeMenuDesc() {
241 $page =
new LogPage( $type );
242 $pageText = $page->getName()->text();
243 if ( in_array( $pageText, $typesByName ) ) {
244 LoggerFactory::getInstance(
'translation-problem' )->error(
245 'The log type {log_type_one} has the same translation as {log_type_two} for {lang}. ' .
246 '{log_type_one} will not be displayed in the drop down menu on Special:Log.',
248 'log_type_one' => $type,
249 'log_type_two' => array_search( $pageText, $typesByName ),
255 if ( $this->
getAuthority()->isAllowed( $page->getRestriction() ) ) {
256 $typesByName[$type] = $pageText;
260 asort( $typesByName );
263 $public = $typesByName[
''];
264 unset( $typesByName[
''] );
265 $typesByName = [
'' => $public ] + $typesByName;
268 'class' => HTMLSelectField::class,
270 'options' => array_flip( $typesByName ),
278 private function getExtraInputsDesc( $type ) {
279 if ( $type ===
'suppress' ) {
282 'label-message' =>
'revdelete-offender',
283 'name' =>
'offender',
288 $formDescriptor = [];
289 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
291 return $formDescriptor;
301 private function getActionSelectorDesc( $type, $actions ) {
302 $actionOptions = [
'log-action-filter-all' =>
'' ];
304 foreach ( $actions as $value => $_ ) {
305 $msgKey =
"log-action-filter-$type-$value";
306 $actionOptions[ $msgKey ] = $value;
310 'class' => HTMLSelectField::class,
312 'options-messages' => $actionOptions,
313 'label-message' =>
'log-action-filter-' . $type,
321 return "<ul class='mw-logevent-loglines'>\n";
337 $formatter = $this->logFormatterFactory->newFromEntry( $entry );
338 $formatter->setContext( $this->
getContext() );
339 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
342 $entry->getTimestamp(),
350 [
'logid' => $entry->getId() ]
353 $action = $formatter->getActionText();
355 if ( $this->flags & self::NO_ACTION_LINK ) {
358 $revert = $formatter->getActionLinks();
359 if ( $revert !=
'' ) {
360 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
364 $comment = $formatter->getComment();
367 $del = $this->getShowHideLinks( $row );
370 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
371 $this->tagsCache->makeKey(
373 $this->getUser()->getName(),
374 $this->getLanguage()->getCode()
376 fn () => ChangeTags::formatSummaryRow(
382 $classes = array_merge(
383 [
'mw-logline-' . $entry->getType() ],
387 'data-mw-logid' => $entry->getId(),
388 'data-mw-logaction' => $entry->getFullType(),
390 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
393 $ret .= Html::openElement(
'span', [
'class' =>
'mw-logevent-tool' ] );
397 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
398 $attribs = array_filter( $attribs,
399 [ Sanitizer::class,
'isReservedDataAttribute' ],
402 $ret .= Html::closeElement(
'span' );
403 $attribs[
'class'] = $classes;
405 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
412 private function getShowHideLinks( $row ) {
414 if ( $this->flags == self::NO_ACTION_LINK ) {
419 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
423 [
'name' =>
'ids[' . $row->log_id .
']' ]
428 if ( $row->log_type ==
'suppress' ) {
435 if ( $authority->isAllowed(
'deletedhistory' ) ) {
436 $canHide = $authority->isAllowed(
'deletelogentry' );
437 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
438 !$authority->isAllowed(
'suppressrevision' );
440 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
441 if ( $row->log_deleted || $canHide ) {
443 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
446 $del =
Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
451 [
'name' =>
'ids[' . $row->log_id .
']' ]
457 $del = Linker::revDeleteLinkDisabled( $canHide );
462 'ids' => $row->log_id,
464 $del = Linker::revDeleteLink(
467 $canHide && !$canViewThisSuppressedEntry
484 $match = is_array( $type ) ?
485 in_array( $row->log_type, $type ) : $row->log_type == $type;
487 $match = is_array( $action ) ?
488 in_array( $row->log_action, $action ) : $row->log_action == $action;
518 if ( $bitfield & $field ) {
520 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
522 return $performer->
isAllowed(
'deletedhistory' );
538 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
550 return ( $row->log_deleted & $field ) == $field;
581 &$out, $types = [], $page =
'', $user =
'', $param = []
583 $defaultParameters = [
586 'showIfEmpty' =>
true,
590 'useRequestParams' =>
false,
591 'useMaster' =>
false,
592 'extraUrlParams' =>
false,
593 'footerHtmlItems' => []
595 # The + operator appends elements of remaining keys from the right
596 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
597 $param += $defaultParameters;
598 # Convert $param array to individual variables
599 $lim = $param[
'lim'];
600 $conds = $param[
'conds'];
601 $showIfEmpty = $param[
'showIfEmpty'];
602 $msgKey = $param[
'msgKey'];
603 $wrap = $param[
'wrap'];
605 $extraUrlParams = $param[
'extraUrlParams'];
607 $useRequestParams = $param[
'useRequestParams'];
609 if ( !is_array( $msgKey ) ) {
610 $msgKey = [ $msgKey ];
616 $context = $out->getContext();
618 $context = RequestContext::getMain();
623 $linkRenderer = $services->getLinkRenderer();
625 # Insert list of top 50 (or top $lim) items
640 $services->getLinkBatchFactory(),
641 $services->getActorNormalization(),
642 $services->getLogFormatterFactory()
644 if ( !$useRequestParams ) {
645 # Reset vars that may have been taken from the request
647 $pager->mDefaultLimit = 50;
648 $pager->mOffset =
"";
649 $pager->mIsBackwards =
false;
652 if ( $param[
'useMaster'] ) {
653 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
656 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore
WebRequest offset
657 $pager->setOffset( $param[
'offset'] );
662 $pager->mLimit = $lim;
665 $logBody = $pager->getBody();
666 $numRows = $pager->getNumRows();
669 $footerHtmlItems = [];
674 $msg = $context->msg( ...$msgKey );
678 $s .= $msg->parseAsBlock();
680 $s .= $loglist->beginLogEventsList() .
682 $loglist->endLogEventsList();
684 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
685 } elseif ( $showIfEmpty ) {
686 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
687 $context->msg(
'logempty' )->parse() );
692 $pageName = $titleFormatter->getPrefixedDBkey( $page );
693 } elseif ( $page !=
'' ) {
699 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
702 $urlParam[
'page'] = $pageName;
706 $urlParam[
'user'] = $user;
709 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
713 # If there is exactly one log type, we can link to Special:Log?type=foo
714 if ( count( $types ) == 1 ) {
715 $urlParam['type
'] = $types[0];
718 / @phan-suppress-next-line PhanSuspiciousValueComparison
719 if ( $extraUrlParams !== false ) {
720 $urlParam = array_merge( $urlParam, $extraUrlParams );
723 $footerHtmlItems[] = $linkRenderer->makeKnownLink(
724 SpecialPage::getTitleFor( 'Log
' ),
725 $context->msg( 'log-fulllog
' )->text(),
730 if ( $param['footerHtmlItems
'] ) {
731 $footerHtmlItems = array_merge( $footerHtmlItems, $param['footerHtmlItems
'] );
733 if ( $logBody && $footerHtmlItems ) {
734 $s .= '<ul
class=
"mw-logevent-footer">
';
735 foreach ( $footerHtmlItems as $item ) {
736 $s .= Html::rawElement( 'li
', [], $item );
741 if ( $logBody && $msgKey[0] ) {
742 / TODO: The condition above is weird. Should this be done in any other cases?
743 / Or is it always true in practice?
745 / Mark as interface language (T60685)
746 $dir = $context->getLanguage()->getDir();
747 $lang = $context->getLanguage()->getHtmlCode();
748 $s = Html::rawElement( 'div
', [
749 'class' => "mw-content-$dir",
754 / Wrap in warning box
755 $s = Html::warningBox(
757 'mw-warning-with-logexcerpt
'
759 / Add styles for warning box
760 $context->getOutput()->addModuleStyles( 'mediawiki.codex.messagebox.styles
' );
763 / @phan-suppress-next-line PhanSuspiciousValueComparison
764 if ( $wrap != '' ) { / Wrap message in html
765 $s = str_replace( '$1
', $s, $wrap );
768 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
769 $hookRunner =
new HookRunner( $services->getHookContainer() );
794 if ( $audience !=
'public' && $performer ===
null ) {
795 throw new InvalidArgumentException(
796 'A User object must be given when checking for a user audience.'
804 foreach ( $logRestrictions as $logType => $right ) {
805 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
806 $hiddenLogs[] = $logType;
809 if ( count( $hiddenLogs ) == 1 ) {
810 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
811 } elseif ( $hiddenLogs ) {
812 return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) .
')';
843 $appliesToTitle =
false;
845 $blockTargetName =
'';
847 DatabaseBlockStore::AUTO_NONE );
848 foreach ( $blocks as $block ) {
849 if ( $block->appliesToTitle( $title ) ) {
850 $appliesToTitle =
true;
852 $blockTargetName = $block->getTargetName();
854 ':' . $blockTargetName;
859 if ( !count( $blocks ) || !$appliesToTitle ) {
862 $msgKey = count( $blocks ) === 1
863 ?
'blocked-notice-logextract' :
'blocked-notice-logextract-multi';
866 'showIfEmpty' =>
false,
869 $user->getName(), # Support GENDER in notice
873 if ( count( $blocks ) > 1 ) {
874 $params[
'footerHtmlItems'] = [
877 $localizer->
msg(
'blocked-notice-list-link' )->text(),
879 [
'wpTarget' => $blockTargetName ]
886 return $outString ?:
null;
891class_alias( LogEventsList::class,
'LogEventsList' );
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)
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.