66 private $linkRenderer;
82 public function __construct( $context, $linkRenderer =
null, $flags = 0 ) {
84 $this->flags = $flags;
87 $this->linkRenderer = $linkRenderer;
89 $services = MediaWikiServices::getInstance();
90 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
91 $this->logFormatterFactory = $services->getLogFormatterFactory();
100 if ( $this->linkRenderer !==
null ) {
101 return $this->linkRenderer;
103 return MediaWikiServices::getInstance()->getLinkRenderer();
118 public function showOptions( $type =
'', $year = 0, $month = 0, $day = 0 ) {
119 $formDescriptor = [];
122 $formDescriptor[
'type'] = $this->getTypeMenuDesc();
123 $formDescriptor[
'user'] = [
124 'class' => HTMLUserTextField::class,
125 'label-message' =>
'specialloguserlabel',
131 $formDescriptor[
'page'] = [
132 'class' => HTMLTitleTextField::class,
133 'label-message' =>
'speciallogtitlelabel',
139 if ( !$this->
getConfig()->
get( MainConfigNames::MiserMode ) ) {
140 $formDescriptor[
'pattern'] = [
142 'label-message' =>
'log-title-wildcard',
148 $extraInputsDescriptor = $this->getExtraInputsDesc( $type );
149 if ( $extraInputsDescriptor ) {
150 $formDescriptor[
'extra' ] = $extraInputsDescriptor;
154 $formDescriptor[
'date'] = [
156 'label-message' =>
'date',
157 'default' => $year && $month && $day ? sprintf(
"%04d-%02d-%02d", $year, $month, $day ) :
'',
161 $formDescriptor[
'tagfilter'] = [
162 'type' =>
'tagfilter',
163 'name' =>
'tagfilter',
164 'label-message' =>
'tag-filter',
166 $formDescriptor[
'tagInvert'] = [
168 'name' =>
'tagInvert',
169 'label-message' =>
'invert',
170 'hide-if' => [
'===',
'tagfilter',
'' ],
174 if ( $type ===
'' ) {
175 $formDescriptor[
'filters'] = $this->getFiltersDesc();
179 $allowedActions = $this->
getConfig()->get( MainConfigNames::ActionFilteredLogs );
180 if ( isset( $allowedActions[$type] ) ) {
181 $formDescriptor[
'subtype'] = $this->getActionSelectorDesc( $type, $allowedActions[$type] );
184 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
186 ->setTitle( SpecialPage::getTitleFor(
'Log' ) )
187 ->setSubmitTextMsg(
'logeventslist-submit' )
189 ->setWrapperLegendMsg(
'log' )
190 ->setFormIdentifier(
'logeventslist',
true )
192 ->setSubmitCallback(
static function ( $formData, $form ) {
194 (
new LogPage( $formData[
'type'] ) )->getDescription()
195 ->
setContext( $form->getContext() )->parseAsBlock()
200 $result = $htmlForm->prepareForm()->trySubmit();
201 $htmlForm->displayForm( $result );
202 return $result ===
true || ( $result instanceof
Status && $result->
isGood() );
208 private function getFiltersDesc() {
210 $filters = $this->getConfig()->get( MainConfigNames::FilterLogTypes );
211 foreach ( $filters as $type => $val ) {
212 $optionsMsg[
"logeventslist-{$type}-log"] = $type;
215 'class' => HTMLMultiSelectField::class,
216 'label-message' =>
'logeventslist-more-filters',
218 'options-messages' => $optionsMsg,
219 'default' => array_keys( array_intersect( $filters, [
false ] ) ),
226 private function getTypeMenuDesc() {
229 foreach ( LogPage::validTypes() as $type ) {
231 $pageText = $page->getName()->text();
232 if ( in_array( $pageText, $typesByName ) ) {
233 LoggerFactory::getInstance(
'translation-problem' )->error(
234 'The log type {log_type_one} has the same translation as {log_type_two} for {lang}. ' .
235 '{log_type_one} will not be displayed in the drop down menu on Special:Log.',
237 'log_type_one' => $type,
238 'log_type_two' => array_search( $pageText, $typesByName ),
244 if ( $this->
getAuthority()->isAllowed( $page->getRestriction() ) ) {
245 $typesByName[$type] = $pageText;
249 asort( $typesByName );
252 $public = $typesByName[
''];
253 unset( $typesByName[
''] );
254 $typesByName = [
'' => $public ] + $typesByName;
257 'class' => HTMLSelectField::class,
259 'options' => array_flip( $typesByName ),
267 private function getExtraInputsDesc( $type ) {
268 if ( $type ===
'suppress' ) {
271 'label-message' =>
'revdelete-offender',
272 'name' =>
'offender',
277 $formDescriptor = [];
278 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
280 return $formDescriptor;
290 private function getActionSelectorDesc( $type, $actions ) {
291 $actionOptions = [
'log-action-filter-all' =>
'' ];
293 foreach ( $actions as $value => $_ ) {
294 $msgKey =
"log-action-filter-$type-$value";
295 $actionOptions[ $msgKey ] = $value;
299 'class' => HTMLSelectField::class,
301 'options-messages' => $actionOptions,
302 'label-message' =>
'log-action-filter-' . $type,
310 return "<ul class='mw-logevent-loglines'>\n";
325 $entry = DatabaseLogEntry::newFromRow( $row );
326 $formatter = $this->logFormatterFactory->newFromEntry( $entry );
327 $formatter->setContext( $this->
getContext() );
328 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
331 $entry->getTimestamp(),
336 SpecialPage::getTitleValueFor(
'Log' ),
339 [
'logid' => $entry->getId() ]
342 $action = $formatter->getActionText();
344 if ( $this->flags & self::NO_ACTION_LINK ) {
347 $revert = $formatter->getActionLinks();
348 if ( $revert !=
'' ) {
349 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
353 $comment = $formatter->getComment();
356 $del = $this->getShowHideLinks( $row );
359 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
360 $this->tagsCache->makeKey(
362 $this->getUser()->getName(),
363 $this->getLanguage()->getCode()
371 $classes = array_merge(
372 [
'mw-logline-' . $entry->getType() ],
376 'data-mw-logid' => $entry->getId(),
377 'data-mw-logaction' => $entry->getFullType(),
379 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
382 $ret .= Html::openElement(
'span', [
'class' =>
'mw-logevent-tool' ] );
386 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
387 $attribs = array_filter( $attribs,
388 [ Sanitizer::class,
'isReservedDataAttribute' ],
391 $ret .= Html::closeElement(
'span' );
392 $attribs[
'class'] = $classes;
394 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
401 private function getShowHideLinks( $row ) {
403 if ( $this->flags == self::NO_ACTION_LINK ) {
408 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
412 [
'name' =>
'ids[' . $row->log_id .
']' ]
417 if ( $row->log_type ==
'suppress' ) {
424 if ( $authority->isAllowed(
'deletedhistory' ) ) {
425 $canHide = $authority->isAllowed(
'deletelogentry' );
426 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
427 !$authority->isAllowed(
'suppressrevision' );
428 $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
429 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
430 if ( $row->log_deleted || $canHide ) {
432 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
434 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
435 $del = Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
440 [
'name' =>
'ids[' . $row->log_id .
']' ]
445 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
446 $del = Linker::revDeleteLinkDisabled( $canHide );
449 'target' => SpecialPage::getTitleFor(
'Log', $row->log_type )->getPrefixedDBkey(),
451 'ids' => $row->log_id,
453 $del = Linker::revDeleteLink(
456 $canHide && !$canViewThisSuppressedEntry
473 $match = is_array( $type ) ?
474 in_array( $row->log_type, $type ) : $row->log_type == $type;
476 $match = is_array( $action ) ?
477 in_array( $row->log_action, $action ) : $row->log_action == $action;
493 return self::userCanBitfield( $row->log_deleted, $field, $performer ) &&
494 self::userCanViewLogType( $row->log_type, $performer );
507 if ( $bitfield & $field ) {
508 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
509 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
511 return $performer->
isAllowed(
'deletedhistory' );
526 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
527 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
539 return ( $row->log_deleted & $field ) == $field;
568 &$out, $types = [], $page =
'', $user =
'', $param = []
570 $defaultParameters = [
573 'showIfEmpty' =>
true,
577 'useRequestParams' =>
false,
578 'useMaster' =>
false,
579 'extraUrlParams' =>
false,
581 # The + operator appends elements of remaining keys from the right
582 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
583 $param += $defaultParameters;
584 # Convert $param array to individual variables
585 $lim = $param[
'lim'];
586 $conds = $param[
'conds'];
587 $showIfEmpty = $param[
'showIfEmpty'];
588 $msgKey = $param[
'msgKey'];
589 $wrap = $param[
'wrap'];
590 $flags = $param[
'flags'];
591 $extraUrlParams = $param[
'extraUrlParams'];
593 $useRequestParams = $param[
'useRequestParams'];
595 if ( !is_array( $msgKey ) ) {
596 $msgKey = [ $msgKey ];
602 $context = $out->getContext();
604 $context = RequestContext::getMain();
607 $services = MediaWikiServices::getInstance();
609 $linkRenderer = $services->getLinkRenderer();
611 # Insert list of top 50 (or top $lim) items
612 $loglist =
new LogEventsList( $context, $linkRenderer, $flags );
626 $services->getLinkBatchFactory(),
627 $services->getActorNormalization(),
628 $services->getLogFormatterFactory()
631 if ( !$useRequestParams ) {
632 # Reset vars that may have been taken from the request
634 $pager->mDefaultLimit = 50;
635 $pager->mOffset =
"";
636 $pager->mIsBackwards =
false;
640 if ( $param[
'useMaster'] ) {
641 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
644 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore WebRequest offset
645 $pager->setOffset( $param[
'offset'] );
650 $pager->mLimit = $lim;
653 $logBody = $pager->getBody();
654 $numRows = $pager->getNumRows();
661 $msg = $context->msg( ...$msgKey );
665 $s .= $msg->parseAsBlock();
667 $s .= $loglist->beginLogEventsList() .
669 $loglist->endLogEventsList();
671 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
673 } elseif ( $showIfEmpty ) {
674 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
675 $context->msg(
'logempty' )->parse() );
679 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
680 $pageName = $titleFormatter->getPrefixedDBkey( $page );
681 } elseif ( $page !=
'' ) {
687 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
690 $urlParam[
'page'] = $pageName;
694 $urlParam[
'user'] = $user;
697 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
701 # If there is exactly one log type, we can link to Special:Log?type=foo
702 if ( count( $types ) == 1 ) {
703 $urlParam['type
'] = $types[0];
706 // @phan-suppress-next-line PhanSuspiciousValueComparison
707 if ( $extraUrlParams !== false ) {
708 $urlParam = array_merge( $urlParam, $extraUrlParams );
711 $s .= $linkRenderer->makeKnownLink(
712 SpecialPage::getTitleFor( 'Log
' ),
713 $context->msg( 'log-fulllog
' )->text(),
719 if ( $logBody && $msgKey[0] ) {
720 // TODO: The condition above is weird. Should this be done in any other cases?
721 // Or is it always true in practice?
723 // Mark as interface language (T60685)
724 $dir = $context->getLanguage()->getDir();
725 $lang = $context->getLanguage()->getHtmlCode();
726 $s = Html::rawElement( 'div
', [
727 'class' => "mw-content-$dir",
732 // Wrap in warning box
733 $s = Html::warningBox(
735 'mw-warning-with-logexcerpt
'
737 // Add styles for warning box
738 $context->getOutput()->addModuleStyles( 'mediawiki.codex.messagebox.styles
' );
741 // @phan-suppress-next-line PhanSuspiciousValueComparison, PhanRedundantCondition
742 if ( $wrap != '' ) { // Wrap message in html
743 $s = str_replace( '$1
', $s, $wrap );
746 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
747 $hookRunner =
new HookRunner( $services->getHookContainer() );
770 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
772 if ( $audience !=
'public' && $performer ===
null ) {
773 throw new InvalidArgumentException(
774 'A User object must be given when checking for a user audience.'
782 foreach ( $logRestrictions as $logType => $right ) {
783 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
784 $hiddenLogs[] = $logType;
787 if ( count( $hiddenLogs ) == 1 ) {
788 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
789 } elseif ( $hiddenLogs ) {
790 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.
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,...
static getExcludeClause( $db, $audience='public', ?Authority $performer=null)
SQL clause to skip forbidden log types for this user.
__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)
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.
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.
Interface for objects which can provide a MediaWiki context on request.