MediaWiki  1.28.0
LogEventsList.php
Go to the documentation of this file.
1 <?php
27  const NO_ACTION_LINK = 1;
29  const USE_CHECKBOXES = 4;
30 
31  public $flags;
32 
36  protected $mDefaultQuery;
37 
41  protected $showTagEditUI;
42 
46  protected $allowedActions = null;
47 
59  public function __construct( $context, $unused = null, $flags = 0 ) {
60  if ( $context instanceof IContextSource ) {
61  $this->setContext( $context );
62  } else {
63  // Old parameters, $context should be a Skin object
64  $this->setContext( $context->getContext() );
65  }
66 
67  $this->flags = $flags;
68  $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getUser() );
69  }
70 
84  public function showOptions( $types = [], $user = '', $page = '', $pattern = '', $year = 0,
85  $month = 0, $filter = null, $tagFilter = '', $action = null
86  ) {
88 
90 
91  // For B/C, we take strings, but make sure they are converted...
92  $types = ( $types === '' ) ? [] : (array)$types;
93 
94  $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
95 
96  $html = Html::hidden( 'title', $title->getPrefixedDBkey() );
97 
98  // Basic selectors
99  $html .= $this->getTypeMenu( $types ) . "\n";
100  $html .= $this->getUserInput( $user ) . "\n";
101  $html .= $this->getTitleInput( $page ) . "\n";
102  $html .= $this->getExtraInputs( $types ) . "\n";
103 
104  // Title pattern, if allowed
105  if ( !$wgMiserMode ) {
106  $html .= $this->getTitlePattern( $pattern ) . "\n";
107  }
108 
109  // date menu
110  $html .= Xml::tags( 'p', null, Xml::dateMenu( (int)$year, (int)$month ) );
111 
112  // Tag filter
113  if ( $tagSelector ) {
114  $html .= Xml::tags( 'p', null, implode( '&#160;', $tagSelector ) );
115  }
116 
117  // Filter links
118  if ( $filter ) {
119  $html .= Xml::tags( 'p', null, $this->getFilterLinks( $filter ) );
120  }
121 
122  // Action filter
123  if ( $action !== null ) {
124  $html .= Xml::tags( 'p', null, $this->getActionSelector( $types, $action ) );
125  }
126 
127  // Submit button
128  $html .= Xml::submitButton( $this->msg( 'logeventslist-submit' )->text() );
129 
130  // Fieldset
131  $html = Xml::fieldset( $this->msg( 'log' )->text(), $html );
132 
133  // Form wrapping
134  $html = Xml::tags( 'form', [ 'action' => $wgScript, 'method' => 'get' ], $html );
135 
136  $this->getOutput()->addHTML( $html );
137  }
138 
143  private function getFilterLinks( $filter ) {
144  // show/hide links
145  $messages = [ $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ];
146  // Option value -> message mapping
147  $links = [];
148  $hiddens = ''; // keep track for "go" button
149  foreach ( $filter as $type => $val ) {
150  // Should the below assignment be outside the foreach?
151  // Then it would have to be copied. Not certain what is more expensive.
152  $query = $this->getDefaultQuery();
153  $queryKey = "hide_{$type}_log";
154 
155  $hideVal = 1 - intval( $val );
156  $query[$queryKey] = $hideVal;
157 
159  $this->getTitle(),
160  $messages[$hideVal],
161  [],
162  $query
163  );
164 
165  // Message: log-show-hide-patrol
166  $links[$type] = $this->msg( "log-show-hide-{$type}" )->rawParams( $link )->escaped();
167  $hiddens .= Html::hidden( "hide_{$type}_log", $val ) . "\n";
168  }
169 
170  // Build links
171  return '<small>' . $this->getLanguage()->pipeList( $links ) . '</small>' . $hiddens;
172  }
173 
174  private function getDefaultQuery() {
175  if ( !isset( $this->mDefaultQuery ) ) {
176  $this->mDefaultQuery = $this->getRequest()->getQueryValues();
177  unset( $this->mDefaultQuery['title'] );
178  unset( $this->mDefaultQuery['dir'] );
179  unset( $this->mDefaultQuery['offset'] );
180  unset( $this->mDefaultQuery['limit'] );
181  unset( $this->mDefaultQuery['order'] );
182  unset( $this->mDefaultQuery['month'] );
183  unset( $this->mDefaultQuery['year'] );
184  }
185 
186  return $this->mDefaultQuery;
187  }
188 
193  private function getTypeMenu( $queryTypes ) {
194  $queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : '';
195  $selector = $this->getTypeSelector();
196  $selector->setDefault( $queryType );
197 
198  return $selector->getHTML();
199  }
200 
206  public function getTypeSelector() {
207  $typesByName = []; // Temporary array
208  // First pass to load the log names
209  foreach ( LogPage::validTypes() as $type ) {
210  $page = new LogPage( $type );
211  $restriction = $page->getRestriction();
212  if ( $this->getUser()->isAllowed( $restriction ) ) {
213  $typesByName[$type] = $page->getName()->text();
214  }
215  }
216 
217  // Second pass to sort by name
218  asort( $typesByName );
219 
220  // Always put "All public logs" on top
221  $public = $typesByName[''];
222  unset( $typesByName[''] );
223  $typesByName = [ '' => $public ] + $typesByName;
224 
225  $select = new XmlSelect( 'type' );
226  foreach ( $typesByName as $type => $name ) {
227  $select->addOption( $name, $type );
228  }
229 
230  return $select;
231  }
232 
237  private function getUserInput( $user ) {
238  $label = Xml::inputLabel(
239  $this->msg( 'specialloguserlabel' )->text(),
240  'user',
241  'mw-log-user',
242  15,
243  $user,
244  [ 'class' => 'mw-autocomplete-user' ]
245  );
246 
247  return '<span class="mw-input-with-label">' . $label . '</span>';
248  }
249 
254  private function getTitleInput( $title ) {
255  $label = Xml::inputLabel(
256  $this->msg( 'speciallogtitlelabel' )->text(),
257  'page',
258  'mw-log-page',
259  20,
260  $title
261  );
262 
263  return '<span class="mw-input-with-label">' . $label . '</span>';
264  }
265 
270  private function getTitlePattern( $pattern ) {
271  return '<span class="mw-input-with-label">' .
272  Xml::checkLabel( $this->msg( 'log-title-wildcard' )->text(), 'pattern', 'pattern', $pattern ) .
273  '</span>';
274  }
275 
280  private function getExtraInputs( $types ) {
281  if ( count( $types ) == 1 ) {
282  if ( $types[0] == 'suppress' ) {
283  $offender = $this->getRequest()->getVal( 'offender' );
284  $user = User::newFromName( $offender, false );
285  if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) {
286  $offender = ''; // Blank field if invalid
287  }
288  return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender',
289  'mw-log-offender', 20, $offender );
290  } else {
291  // Allow extensions to add their own extra inputs
292  $input = '';
293  Hooks::run( 'LogEventsListGetExtraInputs', [ $types[0], $this, &$input ] );
294  return $input;
295  }
296  }
297 
298  return '';
299  }
300 
308  private function getActionSelector( $types, $action ) {
309  if ( $this->allowedActions === null || !count( $this->allowedActions ) ) {
310  return '';
311  }
312  $html = '';
313  $html .= Xml::label( wfMessage( 'log-action-filter-' . $types[0] )->text(),
314  'action-filter-' .$types[0] ) . "\n";
315  $select = new XmlSelect( 'subtype' );
316  $select->addOption( wfMessage( 'log-action-filter-all' )->text(), '' );
317  foreach ( $this->allowedActions as $value ) {
318  $msgKey = 'log-action-filter-' . $types[0] . '-' . $value;
319  $select->addOption( wfMessage( $msgKey )->text(), $value );
320  }
321  $select->setDefault( $action );
322  $html .= $select->getHTML();
323  return $html;
324  }
325 
332  public function setAllowedActions( $actions ) {
333  $this->allowedActions = $actions;
334  }
335 
339  public function beginLogEventsList() {
340  return "<ul>\n";
341  }
342 
346  public function endLogEventsList() {
347  return "</ul>\n";
348  }
349 
354  public function logLine( $row ) {
355  $entry = DatabaseLogEntry::newFromRow( $row );
356  $formatter = LogFormatter::newFromEntry( $entry );
357  $formatter->setContext( $this->getContext() );
358  $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
359 
360  $time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
361  $entry->getTimestamp(), $this->getUser() ) );
362 
363  $action = $formatter->getActionText();
364 
365  if ( $this->flags & self::NO_ACTION_LINK ) {
366  $revert = '';
367  } else {
368  $revert = $formatter->getActionLinks();
369  if ( $revert != '' ) {
370  $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
371  }
372  }
373 
374  $comment = $formatter->getComment();
375 
376  // Some user can hide log items and have review links
377  $del = $this->getShowHideLinks( $row );
378 
379  // Any tags...
380  list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
381  $row->ts_tags,
382  'logevent',
383  $this->getContext()
384  );
385  $classes = array_merge(
386  [ 'mw-logline-' . $entry->getType() ],
387  $newClasses
388  );
389 
390  return Html::rawElement( 'li', [ 'class' => $classes ],
391  "$del $time $action $comment $revert $tagDisplay" ) . "\n";
392  }
393 
398  private function getShowHideLinks( $row ) {
399  // We don't want to see the links and
400  if ( $this->flags == self::NO_ACTION_LINK ) {
401  return '';
402  }
403 
404  $user = $this->getUser();
405 
406  // If change tag editing is available to this user, return the checkbox
407  if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
408  return Xml::check(
409  'showhiderevisions',
410  false,
411  [ 'name' => 'ids[' . $row->log_id . ']' ]
412  );
413  }
414 
415  // no one can hide items from the suppress log.
416  if ( $row->log_type == 'suppress' ) {
417  return '';
418  }
419 
420  $del = '';
421  // Don't show useless checkbox to people who cannot hide log entries
422  if ( $user->isAllowed( 'deletedhistory' ) ) {
423  $canHide = $user->isAllowed( 'deletelogentry' );
424  $canViewSuppressedOnly = $user->isAllowed( 'viewsuppressed' ) &&
425  !$user->isAllowed( 'suppressrevision' );
426  $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
427  $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
428  if ( $row->log_deleted || $canHide ) {
429  // Show checkboxes instead of links.
430  if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
431  // If event was hidden from sysops
432  if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
433  $del = Xml::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
434  } else {
435  $del = Xml::check(
436  'showhiderevisions',
437  false,
438  [ 'name' => 'ids[' . $row->log_id . ']' ]
439  );
440  }
441  } else {
442  // If event was hidden from sysops
443  if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
444  $del = Linker::revDeleteLinkDisabled( $canHide );
445  } else {
446  $query = [
447  'target' => SpecialPage::getTitleFor( 'Log', $row->log_type )->getPrefixedDBkey(),
448  'type' => 'logging',
449  'ids' => $row->log_id,
450  ];
451  $del = Linker::revDeleteLink(
452  $query,
453  $entryIsSuppressed,
454  $canHide && !$canViewThisSuppressedEntry
455  );
456  }
457  }
458  }
459  }
460 
461  return $del;
462  }
463 
471  public static function typeAction( $row, $type, $action, $right = '' ) {
472  $match = is_array( $type ) ?
473  in_array( $row->log_type, $type ) : $row->log_type == $type;
474  if ( $match ) {
475  $match = is_array( $action ) ?
476  in_array( $row->log_action, $action ) : $row->log_action == $action;
477  if ( $match && $right ) {
478  global $wgUser;
479  $match = $wgUser->isAllowed( $right );
480  }
481  }
482 
483  return $match;
484  }
485 
495  public static function userCan( $row, $field, User $user = null ) {
496  return self::userCanBitfield( $row->log_deleted, $field, $user );
497  }
498 
508  public static function userCanBitfield( $bitfield, $field, User $user = null ) {
509  if ( $bitfield & $field ) {
510  if ( $user === null ) {
511  global $wgUser;
512  $user = $wgUser;
513  }
514  if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
515  $permissions = [ 'suppressrevision', 'viewsuppressed' ];
516  } else {
517  $permissions = [ 'deletedhistory' ];
518  }
519  $permissionlist = implode( ', ', $permissions );
520  wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
521  return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
522  }
523  return true;
524  }
525 
531  public static function isDeleted( $row, $field ) {
532  return ( $row->log_deleted & $field ) == $field;
533  }
534 
559  public static function showLogExtract(
560  &$out, $types = [], $page = '', $user = '', $param = []
561  ) {
562  $defaultParameters = [
563  'lim' => 25,
564  'conds' => [],
565  'showIfEmpty' => true,
566  'msgKey' => [ '' ],
567  'wrap' => "$1",
568  'flags' => 0,
569  'useRequestParams' => false,
570  'useMaster' => false,
571  'extraUrlParams' => false,
572  ];
573  # The + operator appends elements of remaining keys from the right
574  # handed array to the left handed, whereas duplicated keys are NOT overwritten.
575  $param += $defaultParameters;
576  # Convert $param array to individual variables
577  $lim = $param['lim'];
578  $conds = $param['conds'];
579  $showIfEmpty = $param['showIfEmpty'];
580  $msgKey = $param['msgKey'];
581  $wrap = $param['wrap'];
582  $flags = $param['flags'];
583  $extraUrlParams = $param['extraUrlParams'];
584 
585  $useRequestParams = $param['useRequestParams'];
586  if ( !is_array( $msgKey ) ) {
587  $msgKey = [ $msgKey ];
588  }
589 
590  if ( $out instanceof OutputPage ) {
591  $context = $out->getContext();
592  } else {
594  }
595 
596  # Insert list of top 50 (or top $lim) items
597  $loglist = new LogEventsList( $context, null, $flags );
598  $pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
599  if ( !$useRequestParams ) {
600  # Reset vars that may have been taken from the request
601  $pager->mLimit = 50;
602  $pager->mDefaultLimit = 50;
603  $pager->mOffset = "";
604  $pager->mIsBackwards = false;
605  }
606 
607  if ( $param['useMaster'] ) {
608  $pager->mDb = wfGetDB( DB_MASTER );
609  }
610  if ( isset( $param['offset'] ) ) { # Tell pager to ignore WebRequest offset
611  $pager->setOffset( $param['offset'] );
612  }
613 
614  if ( $lim > 0 ) {
615  $pager->mLimit = $lim;
616  }
617  // Fetch the log rows and build the HTML if needed
618  $logBody = $pager->getBody();
619  $numRows = $pager->getNumRows();
620 
621  $s = '';
622 
623  if ( $logBody ) {
624  if ( $msgKey[0] ) {
625  $dir = $context->getLanguage()->getDir();
626  $lang = $context->getLanguage()->getHtmlCode();
627 
628  $s = Xml::openElement( 'div', [
629  'class' => "mw-warning-with-logexcerpt mw-content-$dir",
630  'dir' => $dir,
631  'lang' => $lang,
632  ] );
633 
634  if ( count( $msgKey ) == 1 ) {
635  $s .= $context->msg( $msgKey[0] )->parseAsBlock();
636  } else { // Process additional arguments
637  $args = $msgKey;
638  array_shift( $args );
639  $s .= $context->msg( $msgKey[0], $args )->parseAsBlock();
640  }
641  }
642  $s .= $loglist->beginLogEventsList() .
643  $logBody .
644  $loglist->endLogEventsList();
645  } elseif ( $showIfEmpty ) {
646  $s = Html::rawElement( 'div', [ 'class' => 'mw-warning-logempty' ],
647  $context->msg( 'logempty' )->parse() );
648  }
649 
650  if ( $numRows > $pager->mLimit ) { # Show "Full log" link
651  $urlParam = [];
652  if ( $page instanceof Title ) {
653  $urlParam['page'] = $page->getPrefixedDBkey();
654  } elseif ( $page != '' ) {
655  $urlParam['page'] = $page;
656  }
657 
658  if ( $user != '' ) {
659  $urlParam['user'] = $user;
660  }
661 
662  if ( !is_array( $types ) ) { # Make it an array, if it isn't
663  $types = [ $types ];
664  }
665 
666  # If there is exactly one log type, we can link to Special:Log?type=foo
667  if ( count( $types ) == 1 ) {
668  $urlParam['type'] = $types[0];
669  }
670 
671  if ( $extraUrlParams !== false ) {
672  $urlParam = array_merge( $urlParam, $extraUrlParams );
673  }
674 
675  $s .= Linker::linkKnown(
676  SpecialPage::getTitleFor( 'Log' ),
677  $context->msg( 'log-fulllog' )->escaped(),
678  [],
679  $urlParam
680  );
681  }
682 
683  if ( $logBody && $msgKey[0] ) {
684  $s .= '</div>';
685  }
686 
687  if ( $wrap != '' ) { // Wrap message in html
688  $s = str_replace( '$1', $s, $wrap );
689  }
690 
691  /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
692  if ( Hooks::run( 'LogEventsListShowLogExtract', [ &$s, $types, $page, $user, $param ] ) ) {
693  // $out can be either an OutputPage object or a String-by-reference
694  if ( $out instanceof OutputPage ) {
695  $out->addHTML( $s );
696  } else {
697  $out = $s;
698  }
699  }
700 
701  return $numRows;
702  }
703 
712  public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
713  global $wgLogRestrictions;
714 
715  if ( $audience != 'public' && $user === null ) {
716  global $wgUser;
717  $user = $wgUser;
718  }
719 
720  // Reset the array, clears extra "where" clauses when $par is used
721  $hiddenLogs = [];
722 
723  // Don't show private logs to unprivileged users
724  foreach ( $wgLogRestrictions as $logType => $right ) {
725  if ( $audience == 'public' || !$user->isAllowed( $right ) ) {
726  $hiddenLogs[] = $logType;
727  }
728  }
729  if ( count( $hiddenLogs ) == 1 ) {
730  return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
731  } elseif ( $hiddenLogs ) {
732  return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
733  }
734 
735  return false;
736  }
737 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:525
setContext(IContextSource $context)
Set the IContextSource object.
#define the
table suitable for use with IDatabase::select()
getTitlePattern($pattern)
const USE_CHECKBOXES
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1936
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
Interface for objects which can provide a MediaWiki context on request.
getTypeMenu($queryTypes)
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:802
the array() calling protocol came about after MediaWiki 1.4rc1.
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1555
getLanguage()
Get the Language object.
$wgScript
The URL path to index.php.
static buildTagFilterSelector($selected= '', $ooui=false)
Build a text box to select a change tag.
Definition: ChangeTags.php:672
static userCan($row, $field, User $user=null)
Determine if the current user is allowed to view a particular field of this log row, if it's marked as deleted.
if(count($args)==0) $dir
static isDeleted($row, $field)
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...
Definition: SpecialPage.php:82
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg()
Get a Message object with context set.
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:209
if(!isset($args[0])) $lang
const NO_EXTRA_USER_LINKS
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
static hidden($name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:758
Class for generating HTML