LogEventsList.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. <?php
  2. /**
  3. * Contain classes to list log entries
  4. *
  5. * Copyright © 2004 Brion Vibber <brion@pobox.com>
  6. * https://www.mediawiki.org/
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program; if not, write to the Free Software Foundation, Inc.,
  20. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. * http://www.gnu.org/copyleft/gpl.html
  22. *
  23. * @file
  24. */
  25. use MediaWiki\Linker\LinkRenderer;
  26. use MediaWiki\MediaWikiServices;
  27. use Wikimedia\Rdbms\IDatabase;
  28. class LogEventsList extends ContextSource {
  29. const NO_ACTION_LINK = 1;
  30. const NO_EXTRA_USER_LINKS = 2;
  31. const USE_CHECKBOXES = 4;
  32. public $flags;
  33. /**
  34. * @var array
  35. * @deprecated since 1.34, no longer used.
  36. */
  37. protected $mDefaultQuery;
  38. /**
  39. * @var bool
  40. */
  41. protected $showTagEditUI;
  42. /**
  43. * @var array
  44. */
  45. protected $allowedActions = null;
  46. /**
  47. * @var LinkRenderer|null
  48. */
  49. private $linkRenderer;
  50. /**
  51. * The first two parameters used to be $skin and $out, but now only a context
  52. * is needed, that's why there's a second unused parameter.
  53. *
  54. * @param IContextSource|Skin $context Context to use; formerly it was
  55. * a Skin object. Use of Skin is deprecated.
  56. * @param LinkRenderer|null $linkRenderer previously unused
  57. * @param int $flags Can be a combination of self::NO_ACTION_LINK,
  58. * self::NO_EXTRA_USER_LINKS or self::USE_CHECKBOXES.
  59. */
  60. public function __construct( $context, $linkRenderer = null, $flags = 0 ) {
  61. if ( $context instanceof IContextSource ) {
  62. $this->setContext( $context );
  63. } else {
  64. // Old parameters, $context should be a Skin object
  65. $this->setContext( $context->getContext() );
  66. }
  67. $this->flags = $flags;
  68. $this->showTagEditUI = ChangeTags::showTagEditingUI( $this->getUser() );
  69. if ( $linkRenderer instanceof LinkRenderer ) {
  70. $this->linkRenderer = $linkRenderer;
  71. }
  72. }
  73. /**
  74. * @since 1.30
  75. * @return LinkRenderer
  76. */
  77. protected function getLinkRenderer() {
  78. if ( $this->linkRenderer !== null ) {
  79. return $this->linkRenderer;
  80. } else {
  81. return MediaWikiServices::getInstance()->getLinkRenderer();
  82. }
  83. }
  84. /**
  85. * Show options for the log list
  86. *
  87. * @param array|string $types
  88. * @param string $user
  89. * @param string $page
  90. * @param bool $pattern
  91. * @param int|string $year Use 0 to start with no year preselected.
  92. * @param int|string $month A month in the 1..12 range. Use 0 to start with no month
  93. * preselected.
  94. * @param int|string $day A day in the 1..31 range. Use 0 to start with no month
  95. * preselected.
  96. * @param array|null $filter
  97. * @param string $tagFilter Tag to select by default
  98. * @param string|null $action
  99. */
  100. public function showOptions( $types = [], $user = '', $page = '', $pattern = false, $year = 0,
  101. $month = 0, $day = 0, $filter = null, $tagFilter = '', $action = null
  102. ) {
  103. // For B/C, we take strings, but make sure they are converted...
  104. $types = ( $types === '' ) ? [] : (array)$types;
  105. $formDescriptor = [];
  106. // Basic selectors
  107. $formDescriptor['type'] = $this->getTypeMenuDesc( $types );
  108. $formDescriptor['user'] = $this->getUserInputDesc( $user );
  109. $formDescriptor['page'] = $this->getTitleInputDesc( $page );
  110. // Add extra inputs if any
  111. // This could either be a form descriptor array or a string with raw HTML.
  112. // We need it to work in both cases and show a deprecation warning if it
  113. // is a string. See T199495.
  114. $extraInputsDescriptor = $this->getExtraInputsDesc( $types );
  115. if (
  116. is_array( $extraInputsDescriptor ) &&
  117. !empty( $extraInputsDescriptor )
  118. ) {
  119. $formDescriptor[ 'extra' ] = $extraInputsDescriptor;
  120. } elseif (
  121. is_string( $extraInputsDescriptor ) &&
  122. $extraInputsDescriptor !== ''
  123. ) {
  124. // We'll add this to the footer of the form later
  125. $extraInputsString = $extraInputsDescriptor;
  126. wfDeprecated( '$input in LogEventsListGetExtraInputs hook', '1.32' );
  127. }
  128. // Title pattern, if allowed
  129. if ( !$this->getConfig()->get( 'MiserMode' ) ) {
  130. $formDescriptor['pattern'] = $this->getTitlePatternDesc( $pattern );
  131. }
  132. // Date menu
  133. $formDescriptor['date'] = [
  134. 'type' => 'date',
  135. 'label-message' => 'date',
  136. 'default' => $year && $month && $day ? sprintf( "%04d-%02d-%02d", $year, $month, $day ) : '',
  137. ];
  138. // Tag filter
  139. $formDescriptor['tagfilter'] = [
  140. 'type' => 'tagfilter',
  141. 'name' => 'tagfilter',
  142. 'label-raw' => $this->msg( 'tag-filter' )->parse(),
  143. ];
  144. // Filter links
  145. if ( $filter ) {
  146. $formDescriptor['filters'] = $this->getFiltersDesc( $filter );
  147. }
  148. // Action filter
  149. if (
  150. $action !== null &&
  151. $this->allowedActions !== null &&
  152. count( $this->allowedActions ) > 0
  153. ) {
  154. $formDescriptor['subtype'] = $this->getActionSelectorDesc( $types, $action );
  155. }
  156. $context = new DerivativeContext( $this->getContext() );
  157. $context->setTitle( SpecialPage::getTitleFor( 'Log' ) ); // Remove subpage
  158. $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $context );
  159. $htmlForm
  160. ->setSubmitText( $this->msg( 'logeventslist-submit' )->text() )
  161. ->setMethod( 'get' )
  162. ->setWrapperLegendMsg( 'log' );
  163. // TODO This will should be removed at some point. See T199495.
  164. if ( isset( $extraInputsString ) ) {
  165. $htmlForm->addFooterText( Html::rawElement(
  166. 'div',
  167. null,
  168. $extraInputsString
  169. ) );
  170. }
  171. $htmlForm->prepareForm()->displayForm( false );
  172. }
  173. /**
  174. * @param array $filter
  175. * @return array Form descriptor
  176. */
  177. private function getFiltersDesc( $filter ) {
  178. $options = [];
  179. $default = [];
  180. foreach ( $filter as $type => $val ) {
  181. $message = $this->msg( "logeventslist-{$type}-log" );
  182. // FIXME: Remove this check once T199657 is fully resolved.
  183. if ( !$message->exists() ) {
  184. $message = $this->msg( "log-show-hide-{$type}" )->params( $this->msg( 'show' )->text() );
  185. }
  186. $options[ $message->text() ] = $type;
  187. if ( $val === false ) {
  188. $default[] = $type;
  189. }
  190. }
  191. return [
  192. 'class' => 'HTMLMultiSelectField',
  193. 'label-message' => 'logeventslist-more-filters',
  194. 'flatlist' => true,
  195. 'options' => $options,
  196. 'default' => $default,
  197. ];
  198. }
  199. /**
  200. * @param array $queryTypes
  201. * @return array Form descriptor
  202. */
  203. private function getTypeMenuDesc( $queryTypes ) {
  204. $queryType = count( $queryTypes ) == 1 ? $queryTypes[0] : '';
  205. $typesByName = []; // Temporary array
  206. // First pass to load the log names
  207. foreach ( LogPage::validTypes() as $type ) {
  208. $page = new LogPage( $type );
  209. $restriction = $page->getRestriction();
  210. if ( MediaWikiServices::getInstance()
  211. ->getPermissionManager()
  212. ->userHasRight( $this->getUser(), $restriction )
  213. ) {
  214. $typesByName[$type] = $page->getName()->text();
  215. }
  216. }
  217. // Second pass to sort by name
  218. asort( $typesByName );
  219. // Always put "All public logs" on top
  220. $public = $typesByName[''];
  221. unset( $typesByName[''] );
  222. $typesByName = [ '' => $public ] + $typesByName;
  223. return [
  224. 'class' => 'HTMLSelectField',
  225. 'name' => 'type',
  226. 'options' => array_flip( $typesByName ),
  227. 'default' => $queryType,
  228. ];
  229. }
  230. /**
  231. * @param string $user
  232. * @return array Form descriptor
  233. */
  234. private function getUserInputDesc( $user ) {
  235. return [
  236. 'class' => 'HTMLUserTextField',
  237. 'label-message' => 'specialloguserlabel',
  238. 'name' => 'user',
  239. 'default' => $user,
  240. ];
  241. }
  242. /**
  243. * @param string $title
  244. * @return array Form descriptor
  245. */
  246. private function getTitleInputDesc( $title ) {
  247. return [
  248. 'class' => 'HTMLTitleTextField',
  249. 'label-message' => 'speciallogtitlelabel',
  250. 'name' => 'page',
  251. 'required' => false
  252. ];
  253. }
  254. /**
  255. * @param bool $pattern
  256. * @return array Form descriptor
  257. */
  258. private function getTitlePatternDesc( $pattern ) {
  259. return [
  260. 'type' => 'check',
  261. 'label-message' => 'log-title-wildcard',
  262. 'name' => 'pattern',
  263. ];
  264. }
  265. /**
  266. * @param array $types
  267. * @return array|string Form descriptor or string with HTML
  268. */
  269. private function getExtraInputsDesc( $types ) {
  270. if ( count( $types ) == 1 ) {
  271. if ( $types[0] == 'suppress' ) {
  272. return [
  273. 'type' => 'text',
  274. 'label-message' => 'revdelete-offender',
  275. 'name' => 'offender',
  276. ];
  277. } else {
  278. // Allow extensions to add their own extra inputs
  279. // This could be an array or string. See T199495.
  280. $input = ''; // Deprecated
  281. $formDescriptor = [];
  282. Hooks::run( 'LogEventsListGetExtraInputs', [ $types[0], $this, &$input, &$formDescriptor ] );
  283. return empty( $formDescriptor ) ? $input : $formDescriptor;
  284. }
  285. }
  286. return [];
  287. }
  288. /**
  289. * Drop down menu for selection of actions that can be used to filter the log
  290. * @param array $types
  291. * @param string $action
  292. * @return array Form descriptor
  293. */
  294. private function getActionSelectorDesc( $types, $action ) {
  295. $actionOptions = [];
  296. $actionOptions[ 'log-action-filter-all' ] = '';
  297. foreach ( $this->allowedActions as $value ) {
  298. $msgKey = 'log-action-filter-' . $types[0] . '-' . $value;
  299. $actionOptions[ $msgKey ] = $value;
  300. }
  301. return [
  302. 'class' => 'HTMLSelectField',
  303. 'name' => 'subtype',
  304. 'options-messages' => $actionOptions,
  305. 'default' => $action,
  306. 'label' => $this->msg( 'log-action-filter-' . $types[0] )->text(),
  307. ];
  308. }
  309. /**
  310. * Sets the action types allowed for log filtering
  311. * To one action type may correspond several log_actions
  312. * @param array $actions
  313. * @since 1.27
  314. */
  315. public function setAllowedActions( $actions ) {
  316. $this->allowedActions = $actions;
  317. }
  318. /**
  319. * @return string
  320. */
  321. public function beginLogEventsList() {
  322. return "<ul>\n";
  323. }
  324. /**
  325. * @return string
  326. */
  327. public function endLogEventsList() {
  328. return "</ul>\n";
  329. }
  330. /**
  331. * @param stdClass $row A single row from the result set
  332. * @return string Formatted HTML list item
  333. */
  334. public function logLine( $row ) {
  335. $entry = DatabaseLogEntry::newFromRow( $row );
  336. $formatter = LogFormatter::newFromEntry( $entry );
  337. $formatter->setContext( $this->getContext() );
  338. $formatter->setLinkRenderer( $this->getLinkRenderer() );
  339. $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
  340. $time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
  341. $entry->getTimestamp(), $this->getUser() ) );
  342. $action = $formatter->getActionText();
  343. if ( $this->flags & self::NO_ACTION_LINK ) {
  344. $revert = '';
  345. } else {
  346. $revert = $formatter->getActionLinks();
  347. if ( $revert != '' ) {
  348. $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
  349. }
  350. }
  351. $comment = $formatter->getComment();
  352. // Some user can hide log items and have review links
  353. $del = $this->getShowHideLinks( $row );
  354. // Any tags...
  355. list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
  356. $row->ts_tags,
  357. 'logevent',
  358. $this->getContext()
  359. );
  360. $classes = array_merge(
  361. [ 'mw-logline-' . $entry->getType() ],
  362. $newClasses
  363. );
  364. $attribs = [
  365. 'data-mw-logid' => $entry->getId(),
  366. 'data-mw-logaction' => $entry->getFullType(),
  367. ];
  368. $ret = "$del $time $action $comment $revert $tagDisplay";
  369. // Let extensions add data
  370. Hooks::run( 'LogEventsListLineEnding', [ $this, &$ret, $entry, &$classes, &$attribs ] );
  371. $attribs = array_filter( $attribs,
  372. [ Sanitizer::class, 'isReservedDataAttribute' ],
  373. ARRAY_FILTER_USE_KEY
  374. );
  375. $attribs['class'] = implode( ' ', $classes );
  376. return Html::rawElement( 'li', $attribs, $ret ) . "\n";
  377. }
  378. /**
  379. * @param stdClass $row
  380. * @return string
  381. */
  382. private function getShowHideLinks( $row ) {
  383. // We don't want to see the links and
  384. if ( $this->flags == self::NO_ACTION_LINK ) {
  385. return '';
  386. }
  387. $user = $this->getUser();
  388. // If change tag editing is available to this user, return the checkbox
  389. if ( $this->flags & self::USE_CHECKBOXES && $this->showTagEditUI ) {
  390. return Xml::check(
  391. 'showhiderevisions',
  392. false,
  393. [ 'name' => 'ids[' . $row->log_id . ']' ]
  394. );
  395. }
  396. // no one can hide items from the suppress log.
  397. if ( $row->log_type == 'suppress' ) {
  398. return '';
  399. }
  400. $del = '';
  401. $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
  402. // Don't show useless checkbox to people who cannot hide log entries
  403. if ( $permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
  404. $canHide = $permissionManager->userHasRight( $user, 'deletelogentry' );
  405. $canViewSuppressedOnly = $permissionManager->userHasRight( $user, 'viewsuppressed' ) &&
  406. !$permissionManager->userHasRight( $user, 'suppressrevision' );
  407. $entryIsSuppressed = self::isDeleted( $row, LogPage::DELETED_RESTRICTED );
  408. $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
  409. if ( $row->log_deleted || $canHide ) {
  410. // Show checkboxes instead of links.
  411. if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
  412. // If event was hidden from sysops
  413. if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
  414. $del = Xml::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
  415. } else {
  416. $del = Xml::check(
  417. 'showhiderevisions',
  418. false,
  419. [ 'name' => 'ids[' . $row->log_id . ']' ]
  420. );
  421. }
  422. } else {
  423. // If event was hidden from sysops
  424. if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
  425. $del = Linker::revDeleteLinkDisabled( $canHide );
  426. } else {
  427. $query = [
  428. 'target' => SpecialPage::getTitleFor( 'Log', $row->log_type )->getPrefixedDBkey(),
  429. 'type' => 'logging',
  430. 'ids' => $row->log_id,
  431. ];
  432. $del = Linker::revDeleteLink(
  433. $query,
  434. $entryIsSuppressed,
  435. $canHide && !$canViewThisSuppressedEntry
  436. );
  437. }
  438. }
  439. }
  440. }
  441. return $del;
  442. }
  443. /**
  444. * @param stdClass $row
  445. * @param string|array $type
  446. * @param string|array $action
  447. * @param string $right
  448. * @return bool
  449. */
  450. public static function typeAction( $row, $type, $action, $right = '' ) {
  451. $match = is_array( $type ) ?
  452. in_array( $row->log_type, $type ) : $row->log_type == $type;
  453. if ( $match ) {
  454. $match = is_array( $action ) ?
  455. in_array( $row->log_action, $action ) : $row->log_action == $action;
  456. if ( $match && $right ) {
  457. global $wgUser;
  458. $match = MediaWikiServices::getInstance()
  459. ->getPermissionManager()
  460. ->userHasRight( $wgUser, $right );
  461. }
  462. }
  463. return $match;
  464. }
  465. /**
  466. * Determine if the current user is allowed to view a particular
  467. * field of this log row, if it's marked as deleted and/or restricted log type.
  468. *
  469. * @param stdClass $row
  470. * @param int $field
  471. * @param User|null $user User to check, or null to use $wgUser
  472. * @return bool
  473. */
  474. public static function userCan( $row, $field, User $user = null ) {
  475. return self::userCanBitfield( $row->log_deleted, $field, $user ) &&
  476. self::userCanViewLogType( $row->log_type, $user );
  477. }
  478. /**
  479. * Determine if the current user is allowed to view a particular
  480. * field of this log row, if it's marked as deleted.
  481. *
  482. * @param int $bitfield Current field
  483. * @param int $field
  484. * @param User|null $user User to check, or null to use $wgUser
  485. * @return bool
  486. */
  487. public static function userCanBitfield( $bitfield, $field, User $user = null ) {
  488. if ( $bitfield & $field ) {
  489. if ( $user === null ) {
  490. global $wgUser;
  491. $user = $wgUser;
  492. }
  493. if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
  494. $permissions = [ 'suppressrevision', 'viewsuppressed' ];
  495. } else {
  496. $permissions = [ 'deletedhistory' ];
  497. }
  498. $permissionlist = implode( ', ', $permissions );
  499. wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
  500. return MediaWikiServices::getInstance()
  501. ->getPermissionManager()
  502. ->userHasAnyRight( $user, ...$permissions );
  503. }
  504. return true;
  505. }
  506. /**
  507. * Determine if the current user is allowed to view a particular
  508. * field of this log row, if it's marked as restricted log type.
  509. *
  510. * @param stdClass $type
  511. * @param User|null $user User to check, or null to use $wgUser
  512. * @return bool
  513. */
  514. public static function userCanViewLogType( $type, User $user = null ) {
  515. if ( $user === null ) {
  516. global $wgUser;
  517. $user = $wgUser;
  518. }
  519. $logRestrictions = MediaWikiServices::getInstance()->getMainConfig()->get( 'LogRestrictions' );
  520. if ( isset( $logRestrictions[$type] ) && !MediaWikiServices::getInstance()
  521. ->getPermissionManager()
  522. ->userHasRight( $user, $logRestrictions[$type] )
  523. ) {
  524. return false;
  525. }
  526. return true;
  527. }
  528. /**
  529. * @param stdClass $row
  530. * @param int $field One of DELETED_* bitfield constants
  531. * @return bool
  532. */
  533. public static function isDeleted( $row, $field ) {
  534. return ( $row->log_deleted & $field ) == $field;
  535. }
  536. /**
  537. * Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey)
  538. *
  539. * @param OutputPage|string &$out
  540. * @param string|array $types Log types to show
  541. * @param string|Title $page The page title to show log entries for
  542. * @param string $user The user who made the log entries
  543. * @param array $param Associative Array with the following additional options:
  544. * - lim Integer Limit of items to show, default is 50
  545. * - conds Array Extra conditions for the query
  546. * (e.g. 'log_action != ' . $dbr->addQuotes( 'revision' ))
  547. * - showIfEmpty boolean Set to false if you don't want any output in case the loglist is empty
  548. * if set to true (default), "No matching items in log" is displayed if loglist is empty
  549. * - msgKey Array If you want a nice box with a message, set this to the key of the message.
  550. * First element is the message key, additional optional elements are parameters for the key
  551. * that are processed with wfMessage
  552. * - offset Set to overwrite offset parameter in WebRequest
  553. * set to '' to unset offset
  554. * - wrap String Wrap the message in html (usually something like "<div ...>$1</div>").
  555. * - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS)
  556. * - useRequestParams boolean Set true to use Pager-related parameters in the WebRequest
  557. * - useMaster boolean Use master DB
  558. * - extraUrlParams array|bool Additional url parameters for "full log" link (if it is shown)
  559. * @return int Number of total log items (not limited by $lim)
  560. */
  561. public static function showLogExtract(
  562. &$out, $types = [], $page = '', $user = '', $param = []
  563. ) {
  564. $defaultParameters = [
  565. 'lim' => 25,
  566. 'conds' => [],
  567. 'showIfEmpty' => true,
  568. 'msgKey' => [ '' ],
  569. 'wrap' => "$1",
  570. 'flags' => 0,
  571. 'useRequestParams' => false,
  572. 'useMaster' => false,
  573. 'extraUrlParams' => false,
  574. ];
  575. # The + operator appends elements of remaining keys from the right
  576. # handed array to the left handed, whereas duplicated keys are NOT overwritten.
  577. $param += $defaultParameters;
  578. # Convert $param array to individual variables
  579. $lim = $param['lim'];
  580. $conds = $param['conds'];
  581. $showIfEmpty = $param['showIfEmpty'];
  582. $msgKey = $param['msgKey'];
  583. $wrap = $param['wrap'];
  584. $flags = $param['flags'];
  585. $extraUrlParams = $param['extraUrlParams'];
  586. $useRequestParams = $param['useRequestParams'];
  587. if ( !is_array( $msgKey ) ) {
  588. $msgKey = [ $msgKey ];
  589. }
  590. if ( $out instanceof OutputPage ) {
  591. $context = $out->getContext();
  592. } else {
  593. $context = RequestContext::getMain();
  594. }
  595. // FIXME: Figure out how to inject this
  596. $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
  597. # Insert list of top 50 (or top $lim) items
  598. $loglist = new LogEventsList( $context, $linkRenderer, $flags );
  599. $pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
  600. if ( !$useRequestParams ) {
  601. # Reset vars that may have been taken from the request
  602. $pager->mLimit = 50;
  603. $pager->mDefaultLimit = 50;
  604. $pager->mOffset = "";
  605. $pager->mIsBackwards = false;
  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. if ( $lim > 0 ) {
  614. $pager->mLimit = $lim;
  615. }
  616. // Fetch the log rows and build the HTML if needed
  617. $logBody = $pager->getBody();
  618. $numRows = $pager->getNumRows();
  619. $s = '';
  620. if ( $logBody ) {
  621. if ( $msgKey[0] ) {
  622. $dir = $context->getLanguage()->getDir();
  623. $lang = $context->getLanguage()->getHtmlCode();
  624. $s = Xml::openElement( 'div', [
  625. 'class' => "mw-warning-with-logexcerpt mw-content-$dir",
  626. 'dir' => $dir,
  627. 'lang' => $lang,
  628. ] );
  629. if ( count( $msgKey ) == 1 ) {
  630. $s .= $context->msg( $msgKey[0] )->parseAsBlock();
  631. } else { // Process additional arguments
  632. $args = $msgKey;
  633. array_shift( $args );
  634. $s .= $context->msg( $msgKey[0], $args )->parseAsBlock();
  635. }
  636. }
  637. $s .= $loglist->beginLogEventsList() .
  638. $logBody .
  639. $loglist->endLogEventsList();
  640. // add styles for change tags
  641. $context->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
  642. } elseif ( $showIfEmpty ) {
  643. $s = Html::rawElement( 'div', [ 'class' => 'mw-warning-logempty' ],
  644. $context->msg( 'logempty' )->parse() );
  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. if ( $user != '' ) {
  654. $urlParam['user'] = $user;
  655. }
  656. if ( !is_array( $types ) ) { # Make it an array, if it isn't
  657. $types = [ $types ];
  658. }
  659. # If there is exactly one log type, we can link to Special:Log?type=foo
  660. if ( count( $types ) == 1 ) {
  661. $urlParam['type'] = $types[0];
  662. }
  663. if ( $extraUrlParams !== false ) {
  664. $urlParam = array_merge( $urlParam, $extraUrlParams );
  665. }
  666. $s .= $linkRenderer->makeKnownLink(
  667. SpecialPage::getTitleFor( 'Log' ),
  668. $context->msg( 'log-fulllog' )->text(),
  669. [],
  670. $urlParam
  671. );
  672. }
  673. if ( $logBody && $msgKey[0] ) {
  674. $s .= '</div>';
  675. }
  676. if ( $wrap != '' ) { // Wrap message in html
  677. $s = str_replace( '$1', $s, $wrap );
  678. }
  679. /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
  680. if ( Hooks::run( 'LogEventsListShowLogExtract', [ &$s, $types, $page, $user, $param ] ) ) {
  681. // $out can be either an OutputPage object or a String-by-reference
  682. if ( $out instanceof OutputPage ) {
  683. $out->addHTML( $s );
  684. } else {
  685. $out = $s;
  686. }
  687. }
  688. return $numRows;
  689. }
  690. /**
  691. * SQL clause to skip forbidden log types for this user
  692. *
  693. * @param IDatabase $db
  694. * @param string $audience Public/user
  695. * @param User|null $user User to check, or null to use $wgUser
  696. * @return string|bool String on success, false on failure.
  697. */
  698. public static function getExcludeClause( $db, $audience = 'public', User $user = null ) {
  699. global $wgLogRestrictions;
  700. if ( $audience != 'public' && $user === null ) {
  701. global $wgUser;
  702. $user = $wgUser;
  703. }
  704. // Reset the array, clears extra "where" clauses when $par is used
  705. $hiddenLogs = [];
  706. // Don't show private logs to unprivileged users
  707. foreach ( $wgLogRestrictions as $logType => $right ) {
  708. if ( $audience == 'public' || !MediaWikiServices::getInstance()
  709. ->getPermissionManager()
  710. ->userHasRight( $user, $right )
  711. ) {
  712. $hiddenLogs[] = $logType;
  713. }
  714. }
  715. if ( count( $hiddenLogs ) == 1 ) {
  716. return 'log_type != ' . $db->addQuotes( $hiddenLogs[0] );
  717. } elseif ( $hiddenLogs ) {
  718. return 'log_type NOT IN (' . $db->makeList( $hiddenLogs ) . ')';
  719. }
  720. return false;
  721. }
  722. }