MediaWiki  1.27.2
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 
558  public static function showLogExtract(
559  &$out, $types = [], $page = '', $user = '', $param = []
560  ) {
561  $defaultParameters = [
562  'lim' => 25,
563  'conds' => [],
564  'showIfEmpty' => true,
565  'msgKey' => [ '' ],
566  'wrap' => "$1",
567  'flags' => 0,
568  'useRequestParams' => false,
569  'useMaster' => false,
570  ];
571  # The + operator appends elements of remaining keys from the right
572  # handed array to the left handed, whereas duplicated keys are NOT overwritten.
573  $param += $defaultParameters;
574  # Convert $param array to individual variables
575  $lim = $param['lim'];
576  $conds = $param['conds'];
577  $showIfEmpty = $param['showIfEmpty'];
578  $msgKey = $param['msgKey'];
579  $wrap = $param['wrap'];
580  $flags = $param['flags'];
581  $useRequestParams = $param['useRequestParams'];
582  if ( !is_array( $msgKey ) ) {
583  $msgKey = [ $msgKey ];
584  }
585 
586  if ( $out instanceof OutputPage ) {
587  $context = $out->getContext();
588  } else {
590  }
591 
592  # Insert list of top 50 (or top $lim) items
593  $loglist = new LogEventsList( $context, null, $flags );
594  $pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
595  if ( !$useRequestParams ) {
596  # Reset vars that may have been taken from the request
597  $pager->mLimit = 50;
598  $pager->mDefaultLimit = 50;
599  $pager->mOffset = "";
600  $pager->mIsBackwards = false;
601  }
602 
603  if ( $param['useMaster'] ) {
604  $pager->mDb = wfGetDB( DB_MASTER );
605  }
606  if ( isset( $param['offset'] ) ) { # Tell pager to ignore WebRequest offset
607  $pager->setOffset( $param['offset'] );
608  }
609 
610  if ( $lim > 0 ) {
611  $pager->mLimit = $lim;
612  }
613  // Fetch the log rows and build the HTML if needed
614  $logBody = $pager->getBody();
615  $numRows = $pager->getNumRows();
616 
617  $s = '';
618 
619  if ( $logBody ) {
620  if ( $msgKey[0] ) {
621  $dir = $context->getLanguage()->getDir();
622  $lang = $context->getLanguage()->getHtmlCode();
623 
624  $s = Xml::openElement( 'div', [
625  'class' => "mw-warning-with-logexcerpt mw-content-$dir",
626  'dir' => $dir,
627  'lang' => $lang,
628  ] );
629 
630  if ( count( $msgKey ) == 1 ) {
631  $s .= $context->msg( $msgKey[0] )->parseAsBlock();
632  } else { // Process additional arguments
633  $args = $msgKey;
634  array_shift( $args );
635  $s .= $context->msg( $msgKey[0], $args )->parseAsBlock();
636  }
637  }
638  $s .= $loglist->beginLogEventsList() .
639  $logBody .
640  $loglist->endLogEventsList();
641  } elseif ( $showIfEmpty ) {
642  $s = Html::rawElement( 'div', [ 'class' => 'mw-warning-logempty' ],
643  $context->msg( 'logempty' )->parse() );
644  }
645 
646  if ( $numRows > $pager->mLimit ) { # Show "Full log" link
647  $urlParam = [];
648  if ( $page instanceof Title ) {
649  $urlParam['page'] = $page->getPrefixedDBkey();
650  } elseif ( $page != '' ) {
651  $urlParam['page'] = $page;
652  }
653 
654  if ( $user != '' ) {
655  $urlParam['user'] = $user;
656  }
657 
658  if ( !is_array( $types ) ) { # Make it an array, if it isn't
659  $types = [ $types ];
660  }
661 
662  # If there is exactly one log type, we can link to Special:Log?type=foo
663  if ( count( $types ) == 1 ) {
664  $urlParam['type'] = $types[0];
665  }
666 
667  $s .= Linker::link(
668  SpecialPage::getTitleFor( 'Log' ),
669  $context->msg( 'log-fulllog' )->escaped(),
670  [],
671  $urlParam
672  );
673  }
674 
675  if ( $logBody && $msgKey[0] ) {
676  $s .= '</div>';
677  }
678 
679  if ( $wrap != '' ) { // Wrap message in html
680  $s = str_replace( '$1', $s, $wrap );
681  }
682 
683  /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
684  if ( Hooks::run( 'LogEventsListShowLogExtract', [ &$s, $types, $page, $user, $param ] ) ) {
685  // $out can be either an OutputPage object or a String-by-reference
686  if ( $out instanceof OutputPage ) {
687  $out->addHTML( $s );
688  } else {
689  $out = $s;
690  }
691  }
692 
693  return $numRows;
694  }
695 
704  public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
705  global $wgLogRestrictions;
706 
707  if ( $audience != 'public' && $user === null ) {
708  global $wgUser;
709  $user = $wgUser;
710  }
711 
712  // Reset the array, clears extra "where" clauses when $par is used
713  $hiddenLogs = [];
714 
715  // Don't show private logs to unprivileged users
716  foreach ( $wgLogRestrictions as $logType => $right ) {
717  if ( $audience == 'public' || !$user->isAllowed( $right ) ) {
718  $hiddenLogs[] = $logType;
719  }
720  }
721  if ( count( $hiddenLogs ) == 1 ) {
722  return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
723  } elseif ( $hiddenLogs ) {
724  return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
725  }
726 
727  return false;
728  }
729 }
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:568
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:1798
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:762
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:1418
getLanguage()
Get the Language object.
$wgScript
The URL path to index.php.
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known', 'noclasses'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:264
static buildTagFilterSelector($selected= '', $fullForm=false, Title $title=null, $ooui=false)
Build a text box to select a change tag.
Definition: ChangeTags.php:662
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.
Definition: SpecialPage.php:75
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:210
if(!isset($args[0])) $lang
const NO_EXTRA_USER_LINKS
static newFromEntry(LogEntry $entry)
Constructs a new formatter suitable for given entry.
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 after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a message
Definition: hooks.txt:1924
static hidden($name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:759
Class for generating HTML