65 private $linkRenderer;
79 public function __construct( $context, $linkRenderer =
null, $flags = 0 ) {
84 $this->linkRenderer = $linkRenderer;
86 $this->hookRunner =
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
95 if ( $this->linkRenderer !==
null ) {
96 return $this->linkRenderer;
98 return MediaWikiServices::getInstance()->getLinkRenderer();
113 public function showOptions( $type =
'', $year = 0, $month = 0, $day = 0 ) {
114 $formDescriptor = [];
117 $formDescriptor[
'type'] = $this->getTypeMenuDesc();
118 $formDescriptor[
'user'] = [
119 'class' => HTMLUserTextField::class,
120 'label-message' =>
'specialloguserlabel',
126 $formDescriptor[
'page'] = [
127 'class' => HTMLTitleTextField::class,
128 'label-message' =>
'speciallogtitlelabel',
134 if ( !$this->
getConfig()->
get( MainConfigNames::MiserMode ) ) {
135 $formDescriptor[
'pattern'] = [
137 'label-message' =>
'log-title-wildcard',
143 $extraInputsDescriptor = $this->getExtraInputsDesc( $type );
144 if ( $extraInputsDescriptor ) {
145 $formDescriptor[
'extra' ] = $extraInputsDescriptor;
149 $formDescriptor[
'date'] = [
151 'label-message' =>
'date',
152 'default' => $year && $month && $day ? sprintf(
"%04d-%02d-%02d", $year, $month, $day ) :
'',
156 $formDescriptor[
'tagfilter'] = [
157 'type' =>
'tagfilter',
158 'name' =>
'tagfilter',
159 'label-message' =>
'tag-filter',
161 $formDescriptor[
'tagInvert'] = [
163 'name' =>
'tagInvert',
164 'label-message' =>
'invert',
165 'hide-if' => [
'===',
'tagfilter',
'' ],
169 if ( $type ===
'' ) {
170 $formDescriptor[
'filters'] = $this->getFiltersDesc();
174 $allowedActions = $this->
getConfig()->get( MainConfigNames::ActionFilteredLogs );
175 if ( isset( $allowedActions[$type] ) ) {
176 $formDescriptor[
'subtype'] = $this->getActionSelectorDesc( $type, $allowedActions[$type] );
179 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
181 ->setTitle( SpecialPage::getTitleFor(
'Log' ) )
182 ->setSubmitTextMsg(
'logeventslist-submit' )
184 ->setWrapperLegendMsg(
'log' )
185 ->setFormIdentifier(
'logeventslist',
true )
187 ->setSubmitCallback(
static function ( $formData, $form ) {
189 (
new LogPage( $formData[
'type'] ) )->getDescription()
190 ->
setContext( $form->getContext() )->parseAsBlock()
195 $result = $htmlForm->prepareForm()->trySubmit();
196 $htmlForm->displayForm( $result );
197 return $result ===
true || ( $result instanceof
Status && $result->
isGood() );
203 private function getFiltersDesc() {
205 $filters = $this->getConfig()->get( MainConfigNames::FilterLogTypes );
206 foreach ( $filters as $type => $val ) {
207 $optionsMsg[
"logeventslist-{$type}-log"] = $type;
210 'class' => HTMLMultiSelectField::class,
211 'label-message' =>
'logeventslist-more-filters',
213 'options-messages' => $optionsMsg,
214 'default' => array_keys( array_intersect( $filters, [
false ] ) ),
221 private function getTypeMenuDesc() {
224 foreach ( LogPage::validTypes() as $type ) {
226 $pageText = $page->getName()->text();
227 if ( in_array( $pageText, $typesByName ) ) {
228 LoggerFactory::getInstance(
'error' )->error(
229 'The log type {log_type_one} has the same translation as {log_type_two} for {lang}. ' .
230 '{log_type_one} will not be displayed in the drop down menu on Special:Log.',
232 'log_type_one' => $type,
233 'log_type_two' => array_search( $pageText, $typesByName ),
239 if ( $this->
getAuthority()->isAllowed( $page->getRestriction() ) ) {
240 $typesByName[$type] = $pageText;
244 asort( $typesByName );
247 $public = $typesByName[
''];
248 unset( $typesByName[
''] );
249 $typesByName = [
'' => $public ] + $typesByName;
252 'class' => HTMLSelectField::class,
254 'options' => array_flip( $typesByName ),
262 private function getExtraInputsDesc( $type ) {
263 if ( $type ===
'suppress' ) {
266 'label-message' =>
'revdelete-offender',
267 'name' =>
'offender',
272 $formDescriptor = [];
273 $this->hookRunner->onLogEventsListGetExtraInputs( $type, $this, $unused, $formDescriptor );
275 return $formDescriptor;
285 private function getActionSelectorDesc( $type, $actions ) {
286 $actionOptions = [
'log-action-filter-all' =>
'' ];
288 foreach ( $actions as $value => $_ ) {
289 $msgKey =
"log-action-filter-$type-$value";
290 $actionOptions[ $msgKey ] = $value;
294 'class' => HTMLSelectField::class,
296 'options-messages' => $actionOptions,
297 'label-message' =>
'log-action-filter-' . $type,
305 return "<ul class='mw-logevent-loglines'>\n";
320 $entry = DatabaseLogEntry::newFromRow( $row );
321 $formatter = LogFormatter::newFromEntry( $entry );
322 $formatter->setContext( $this->
getContext() );
324 $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
327 $entry->getTimestamp(),
332 SpecialPage::getTitleValueFor(
'Log' ),
335 [
'logid' => $entry->getId() ]
338 $action = $formatter->getActionText();
340 if ( $this->flags & self::NO_ACTION_LINK ) {
343 $revert = $formatter->getActionLinks();
344 if ( $revert !=
'' ) {
345 $revert =
'<span class="mw-logevent-actionlink">' . $revert .
'</span>';
349 $comment = $formatter->getComment();
352 $del = $this->getShowHideLinks( $row );
355 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
356 $this->tagsCache->makeKey(
358 $this->getUser()->getName(),
359 $this->getLanguage()->getCode()
367 $classes = array_merge(
368 [
'mw-logline-' . $entry->getType() ],
372 'data-mw-logid' => $entry->getId(),
373 'data-mw-logaction' => $entry->getFullType(),
375 $ret =
"$del $timeLink $action $comment $revert $tagDisplay";
378 $this->hookRunner->onLogEventsListLineEnding( $this, $ret, $entry, $classes, $attribs );
379 $attribs = array_filter( $attribs,
380 [ Sanitizer::class,
'isReservedDataAttribute' ],
383 $attribs[
'class'] = $classes;
385 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";
392 private function getShowHideLinks( $row ) {
394 if ( $this->flags == self::NO_ACTION_LINK ) {
399 if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
403 [
'name' =>
'ids[' . $row->log_id .
']' ]
408 if ( $row->log_type ==
'suppress' ) {
415 if ( $authority->isAllowed(
'deletedhistory' ) ) {
416 $canHide = $authority->isAllowed(
'deletelogentry' );
417 $canViewSuppressedOnly = $authority->isAllowed(
'viewsuppressed' ) &&
418 !$authority->isAllowed(
'suppressrevision' );
419 $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
420 $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
421 if ( $row->log_deleted || $canHide ) {
423 if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
425 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
426 $del = Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
431 [
'name' =>
'ids[' . $row->log_id .
']' ]
436 if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $authority ) ) {
437 $del = Linker::revDeleteLinkDisabled( $canHide );
440 'target' => SpecialPage::getTitleFor(
'Log', $row->log_type )->getPrefixedDBkey(),
442 'ids' => $row->log_id,
444 $del = Linker::revDeleteLink(
447 $canHide && !$canViewThisSuppressedEntry
464 $match = is_array( $type ) ?
465 in_array( $row->log_type, $type ) : $row->log_type == $type;
467 $match = is_array( $action ) ?
468 in_array( $row->log_action, $action ) : $row->log_action == $action;
484 return self::userCanBitfield( $row->log_deleted, $field, $performer ) &&
485 self::userCanViewLogType( $row->log_type, $performer );
498 if ( $bitfield & $field ) {
499 if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
500 return $performer->
isAllowedAny(
'suppressrevision',
'viewsuppressed' );
502 return $performer->
isAllowed(
'deletedhistory' );
517 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
518 if ( isset( $logRestrictions[$type] ) && !$performer->
isAllowed( $logRestrictions[$type] ) ) {
530 return ( $row->log_deleted & $field ) == $field;
559 &$out, $types = [], $page =
'', $user =
'', $param = []
561 $defaultParameters = [
564 'showIfEmpty' =>
true,
568 'useRequestParams' =>
false,
569 'useMaster' =>
false,
570 'extraUrlParams' =>
false,
572 # The + operator appends elements of remaining keys from the right
573 # handed array to the left handed, whereas duplicated keys are NOT overwritten.
574 $param += $defaultParameters;
575 # Convert $param array to individual variables
576 $lim = $param[
'lim'];
577 $conds = $param[
'conds'];
578 $showIfEmpty = $param[
'showIfEmpty'];
579 $msgKey = $param[
'msgKey'];
580 $wrap = $param[
'wrap'];
582 $extraUrlParams = $param[
'extraUrlParams'];
584 $useRequestParams = $param[
'useRequestParams'];
586 if ( !is_array( $msgKey ) ) {
587 $msgKey = [ $msgKey ];
593 $context = $out->getContext();
595 $context = RequestContext::getMain();
598 $services = MediaWikiServices::getInstance();
600 $linkRenderer = $services->getLinkRenderer();
602 # Insert list of top 50 (or top $lim) items
617 $services->getLinkBatchFactory(),
618 $services->getActorNormalization()
621 if ( !$useRequestParams ) {
622 # Reset vars that may have been taken from the request
624 $pager->mDefaultLimit = 50;
625 $pager->mOffset =
"";
626 $pager->mIsBackwards =
false;
630 if ( $param[
'useMaster'] ) {
631 $pager->mDb = $services->getConnectionProvider()->getPrimaryDatabase();
634 if ( isset( $param[
'offset'] ) ) { # Tell pager to ignore WebRequest offset
635 $pager->setOffset( $param[
'offset'] );
640 $pager->mLimit = $lim;
643 $logBody = $pager->getBody();
644 $numRows = $pager->getNumRows();
651 $msg = $context->msg( ...$msgKey );
655 $s .= $msg->parseAsBlock();
657 $s .= $loglist->beginLogEventsList() .
659 $loglist->endLogEventsList();
661 $context->getOutput()->addModuleStyles(
'mediawiki.interface.helpers.styles' );
663 } elseif ( $showIfEmpty ) {
664 $s = Html::rawElement(
'div', [
'class' =>
'mw-warning-logempty' ],
665 $context->msg(
'logempty' )->parse() );
669 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
670 $pageName = $titleFormatter->getPrefixedDBkey( $page );
671 } elseif ( $page !=
'' ) {
677 if ( $numRows > $pager->mLimit ) { # Show
"Full log" link
680 $urlParam[
'page'] = $pageName;
684 $urlParam[
'user'] = $user;
687 if ( !is_array( $types ) ) { # Make it an array,
if it isn
't
691 # If there is exactly one log type, we can link to Special:Log?type=foo
692 if ( count( $types ) == 1 ) {
693 $urlParam['type
'] = $types[0];
696 // @phan-suppress-next-line PhanSuspiciousValueComparison
697 if ( $extraUrlParams !== false ) {
698 $urlParam = array_merge( $urlParam, $extraUrlParams );
701 $s .= $linkRenderer->makeKnownLink(
702 SpecialPage::getTitleFor( 'Log
' ),
703 $context->msg( 'log-fulllog
' )->text(),
709 if ( $logBody && $msgKey[0] ) {
710 // TODO: The condition above is weird. Should this be done in any other cases?
711 // Or is it always true in practice?
713 // Mark as interface language (T60685)
714 $dir = $context->getLanguage()->getDir();
715 $lang = $context->getLanguage()->getHtmlCode();
716 $s = Html::rawElement( 'div
', [
717 'class' => "mw-content-$dir",
722 // Wrap in warning box
723 $s = Html::warningBox(
725 'mw-warning-with-logexcerpt
'
729 // @phan-suppress-next-line PhanSuspiciousValueComparison, PhanRedundantCondition
730 if ( $wrap != '' ) { // Wrap message in html
731 $s = str_replace( '$1
', $s, $wrap );
734 /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
735 $hookRunner =
new HookRunner( $services->getHookContainer() );
758 $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LogRestrictions );
760 if ( $audience !=
'public' && $performer ===
null ) {
761 throw new InvalidArgumentException(
762 'A User object must be given when checking for a user audience.'
770 foreach ( $logRestrictions as $logType => $right ) {
771 if ( $audience ==
'public' || !$performer->isAllowed( $right ) ) {
772 $hiddenLogs[] = $logType;
775 if ( count( $hiddenLogs ) == 1 ) {
776 return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
777 } elseif ( $hiddenLogs ) {
778 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)
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.