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(
'error' )->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 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
383 $attribs = array_filter( $attribs,
384 [ Sanitizer::class,
'isReservedDataAttribute' ],
387 $attribs[
'class'] = $classes;
389 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
396 private function getShowHideLinks( $row ) {
398 if ( $this->flags == self::NO_ACTION_LINK ) {
403 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
407 [
'name' =>
'ids[' . $row->log_id .
']' ]
412 if ( $row->log_type ==
'suppress' ) {
419 if ( $authority->isAllowed(
'deletedhistory' ) ) {
420 $canHide = $authority->isAllowed(
'deletelogentry' );
421 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
422 !$authority->isAllowed(
'suppressrevision' );
423 $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
424 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
425 if ( $row->log_deleted || $canHide ) {
427 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
429 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
430 $del = Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
435 [
'name' =>
'ids[' . $row->log_id .
']' ]
440 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
441 $del = Linker::revDeleteLinkDisabled( $canHide );
444 'target' => SpecialPage::getTitleFor(
'Log', $row->log_type )->getPrefixedDBkey(),
446 'ids' => $row->log_id,
448 $del = Linker::revDeleteLink(
451 $canHide && !$canViewThisSuppressedEntry
468 $match = is_array( $type ) ?
469 in_array( $row->log_type, $type ) : $row->log_type == $type;
471 $match = is_array( $action ) ?
472 in_array( $row->log_action, $action ) : $row->log_action == $action;
488 return self::userCanBitfield( $row->log_deleted, $field, $performer ) &&
489 self::userCanViewLogType( $row->log_type, $performer );
502 if ( $bitfield & $field ) {
503 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
504 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
506 return $performer->
isAllowed(
'deletedhistory' );
521 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
522 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
534 return ( $row->log_deleted & $field ) == $field;
563 &$out, $types = [], $page =
'', $user =
'', $param = []
565 $defaultParameters = [
568 'showIfEmpty' =>
true,
572 'useRequestParams' =>
false,
573 'useMaster' =>
false,
574 'extraUrlParams' =>
false,
576 # The + operator appends elements of remaining keys from the right
577 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
578 $param += $defaultParameters;
579 # Convert $param array to individual variables
580 $lim = $param[
'lim'];
581 $conds = $param[
'conds'];
582 $showIfEmpty = $param[
'showIfEmpty'];
583 $msgKey = $param[
'msgKey'];
584 $wrap = $param[
'wrap'];
585 $flags = $param[
'flags'];
586 $extraUrlParams = $param[
'extraUrlParams'];
588 $useRequestParams = $param[
'useRequestParams'];
590 if ( !is_array( $msgKey ) ) {
591 $msgKey = [ $msgKey ];
597 $context = $out->getContext();
599 $context = RequestContext::getMain();
602 $services = MediaWikiServices::getInstance();
604 $linkRenderer = $services->getLinkRenderer();
606 # Insert list of top 50 (or top $lim) items
607 $loglist =
new LogEventsList( $context, $linkRenderer, $flags );
621 $services->getLinkBatchFactory(),
622 $services->getActorNormalization(),
623 $services->getLogFormatterFactory()
626 if ( !$useRequestParams ) {
627 # Reset vars that may have been taken from the request
629 $pager->mDefaultLimit = 50;
630 $pager->mOffset =
"";
631 $pager->mIsBackwards =
false;
635 if ( $param[
'useMaster'] ) {
636 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
639 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore WebRequest offset
640 $pager->setOffset( $param[
'offset'] );
645 $pager->mLimit = $lim;
648 $logBody = $pager->getBody();
649 $numRows = $pager->getNumRows();
656 $msg = $context->msg( ...$msgKey );
660 $s .= $msg->parseAsBlock();
662 $s .= $loglist->beginLogEventsList() .
664 $loglist->endLogEventsList();
666 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
668 } elseif ( $showIfEmpty ) {
669 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
670 $context->msg(
'logempty' )->parse() );
674 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
675 $pageName = $titleFormatter->getPrefixedDBkey( $page );
676 } elseif ( $page !=
'' ) {
682 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
685 $urlParam[
'page'] = $pageName;
689 $urlParam[
'user'] = $user;
692 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
696 # If there is exactly one log type, we can link to Special:Log?type=foo
697 if ( count( $types ) == 1 ) {
698 $urlParam['type
'] = $types[0];
701 // @phan-suppress-next-line PhanSuspiciousValueComparison
702 if ( $extraUrlParams !== false ) {
703 $urlParam = array_merge( $urlParam, $extraUrlParams );
706 $s .= $linkRenderer->makeKnownLink(
707 SpecialPage::getTitleFor( 'Log
' ),
708 $context->msg( 'log-fulllog
' )->text(),
714 if ( $logBody && $msgKey[0] ) {
715 // TODO: The condition above is weird. Should this be done in any other cases?
716 // Or is it always true in practice?
718 // Mark as interface language (T60685)
719 $dir = $context->getLanguage()->getDir();
720 $lang = $context->getLanguage()->getHtmlCode();
721 $s = Html::rawElement( 'div
', [
722 'class' => "mw-content-$dir",
727 // Wrap in warning box
728 $s = Html::warningBox(
730 'mw-warning-with-logexcerpt
'
734 // @phan-suppress-next-line PhanSuspiciousValueComparison, PhanRedundantCondition
735 if ( $wrap != '' ) { // Wrap message in html
736 $s = str_replace( '$1
', $s, $wrap );
739 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
740 $hookRunner =
new HookRunner( $services->getHookContainer() );
763 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
765 if ( $audience !=
'public' && $performer ===
null ) {
766 throw new InvalidArgumentException(
767 'A User object must be given when checking for a user audience.'
775 foreach ( $logRestrictions as $logType => $right ) {
776 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
777 $hiddenLogs[] = $logType;
780 if ( count( $hiddenLogs ) == 1 ) {
781 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
782 } elseif ( $hiddenLogs ) {
783 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.
static getExcludeClause( $db, $audience='public', Authority $performer=null)
SQL clause to skip forbidden log types for this user.
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,...
__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.