SpecialLog.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php
  2. /**
  3. * Implements Special:Log
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup SpecialPage
  22. */
  23. use MediaWiki\MediaWikiServices;
  24. use Wikimedia\Timestamp\TimestampException;
  25. /**
  26. * A special page that lists log entries
  27. *
  28. * @ingroup SpecialPage
  29. */
  30. class SpecialLog extends SpecialPage {
  31. public function __construct() {
  32. parent::__construct( 'Log' );
  33. }
  34. public function execute( $par ) {
  35. $this->setHeaders();
  36. $this->outputHeader();
  37. $out = $this->getOutput();
  38. $out->addModules( 'mediawiki.userSuggest' );
  39. $out->addModuleStyles( 'mediawiki.interface.helpers.styles' );
  40. $this->addHelpLink( 'Help:Log' );
  41. $opts = new FormOptions;
  42. $opts->add( 'type', '' );
  43. $opts->add( 'user', '' );
  44. $opts->add( 'page', '' );
  45. $opts->add( 'pattern', false );
  46. $opts->add( 'year', null, FormOptions::INTNULL );
  47. $opts->add( 'month', null, FormOptions::INTNULL );
  48. $opts->add( 'day', null, FormOptions::INTNULL );
  49. $opts->add( 'tagfilter', '' );
  50. $opts->add( 'offset', '' );
  51. $opts->add( 'dir', '' );
  52. $opts->add( 'offender', '' );
  53. $opts->add( 'subtype', '' );
  54. $opts->add( 'logid', '' );
  55. // Set values
  56. $opts->fetchValuesFromRequest( $this->getRequest() );
  57. if ( $par !== null ) {
  58. $this->parseParams( $opts, (string)$par );
  59. }
  60. // Set date values
  61. $dateString = $this->getRequest()->getVal( 'wpdate' );
  62. if ( !empty( $dateString ) ) {
  63. try {
  64. $dateStamp = MWTimestamp::getInstance( $dateString . ' 00:00:00' );
  65. } catch ( TimestampException $e ) {
  66. // If users provide an invalid date, silently ignore it
  67. // instead of letting an exception bubble up (T201411)
  68. $dateStamp = false;
  69. }
  70. if ( $dateStamp ) {
  71. $opts->setValue( 'year', (int)$dateStamp->format( 'Y' ) );
  72. $opts->setValue( 'month', (int)$dateStamp->format( 'm' ) );
  73. $opts->setValue( 'day', (int)$dateStamp->format( 'd' ) );
  74. }
  75. }
  76. # Don't let the user get stuck with a certain date
  77. if ( $opts->getValue( 'offset' ) || $opts->getValue( 'dir' ) == 'prev' ) {
  78. $opts->setValue( 'year', '' );
  79. $opts->setValue( 'month', '' );
  80. }
  81. // If the user doesn't have the right permission to view the specific
  82. // log type, throw a PermissionsError
  83. // If the log type is invalid, just show all public logs
  84. $logRestrictions = $this->getConfig()->get( 'LogRestrictions' );
  85. $type = $opts->getValue( 'type' );
  86. if ( !LogPage::isLogType( $type ) ) {
  87. $opts->setValue( 'type', '' );
  88. } elseif ( isset( $logRestrictions[$type] )
  89. && !MediaWikiServices::getInstance()
  90. ->getPermissionManager()
  91. ->userHasRight( $this->getUser(), $logRestrictions[$type] )
  92. ) {
  93. throw new PermissionsError( $logRestrictions[$type] );
  94. }
  95. # Handle type-specific inputs
  96. $qc = [];
  97. if ( $opts->getValue( 'type' ) == 'suppress' ) {
  98. $offenderName = $opts->getValue( 'offender' );
  99. $offender = empty( $offenderName ) ? null : User::newFromName( $offenderName, false );
  100. if ( $offender ) {
  101. $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
  102. }
  103. } else {
  104. // Allow extensions to add relations to their search types
  105. Hooks::run(
  106. 'SpecialLogAddLogSearchRelations',
  107. [ $opts->getValue( 'type' ), $this->getRequest(), &$qc ]
  108. );
  109. }
  110. # Some log types are only for a 'User:' title but we might have been given
  111. # only the username instead of the full title 'User:username'. This part try
  112. # to lookup for a user by that name and eventually fix user input. See T3697.
  113. if ( in_array( $opts->getValue( 'type' ), self::getLogTypesOnUser() ) ) {
  114. # ok we have a type of log which expect a user title.
  115. $target = Title::newFromText( $opts->getValue( 'page' ) );
  116. if ( $target && $target->getNamespace() === NS_MAIN ) {
  117. # User forgot to add 'User:', we are adding it for him
  118. $opts->setValue( 'page',
  119. Title::makeTitleSafe( NS_USER, $opts->getValue( 'page' ) )
  120. );
  121. }
  122. }
  123. $this->show( $opts, $qc );
  124. }
  125. /**
  126. * List log type for which the target is a user
  127. * Thus if the given target is in NS_MAIN we can alter it to be an NS_USER
  128. * Title user instead.
  129. *
  130. * @since 1.25
  131. * @return array
  132. */
  133. public static function getLogTypesOnUser() {
  134. static $types = null;
  135. if ( $types !== null ) {
  136. return $types;
  137. }
  138. $types = [
  139. 'block',
  140. 'newusers',
  141. 'rights',
  142. ];
  143. Hooks::run( 'GetLogTypesOnUser', [ &$types ] );
  144. return $types;
  145. }
  146. /**
  147. * Return an array of subpages that this special page will accept.
  148. *
  149. * @return string[] subpages
  150. */
  151. public function getSubpagesForPrefixSearch() {
  152. $subpages = LogPage::validTypes();
  153. $subpages[] = 'all';
  154. sort( $subpages );
  155. return $subpages;
  156. }
  157. /**
  158. * Set options based on the subpage title parts:
  159. * - One part that is a valid log type: Special:Log/logtype
  160. * - Two parts: Special:Log/logtype/username
  161. * - Otherwise, assume the whole subpage is a username.
  162. *
  163. * @param FormOptions $opts
  164. * @param string $par
  165. */
  166. private function parseParams( FormOptions $opts, $par ) {
  167. # Get parameters
  168. $par = $par ?? '';
  169. $parms = explode( '/', $par );
  170. $symsForAll = [ '*', 'all' ];
  171. if ( $parms[0] != '' &&
  172. ( in_array( $par, LogPage::validTypes() ) || in_array( $par, $symsForAll ) )
  173. ) {
  174. $opts->setValue( 'type', $par );
  175. } elseif ( count( $parms ) == 2 ) {
  176. $opts->setValue( 'type', $parms[0] );
  177. $opts->setValue( 'user', $parms[1] );
  178. } elseif ( $par != '' ) {
  179. $opts->setValue( 'user', $par );
  180. }
  181. }
  182. private function show( FormOptions $opts, array $extraConds ) {
  183. # Create a LogPager item to get the results and a LogEventsList item to format them...
  184. $loglist = new LogEventsList(
  185. $this->getContext(),
  186. $this->getLinkRenderer(),
  187. LogEventsList::USE_CHECKBOXES
  188. );
  189. $pager = new LogPager(
  190. $loglist,
  191. $opts->getValue( 'type' ),
  192. $opts->getValue( 'user' ),
  193. $opts->getValue( 'page' ),
  194. $opts->getValue( 'pattern' ),
  195. $extraConds,
  196. $opts->getValue( 'year' ),
  197. $opts->getValue( 'month' ),
  198. $opts->getValue( 'day' ),
  199. $opts->getValue( 'tagfilter' ),
  200. $opts->getValue( 'subtype' ),
  201. $opts->getValue( 'logid' )
  202. );
  203. $this->addHeader( $opts->getValue( 'type' ) );
  204. # Set relevant user
  205. if ( $pager->getPerformer() ) {
  206. $performerUser = User::newFromName( $pager->getPerformer(), false );
  207. $this->getSkin()->setRelevantUser( $performerUser );
  208. }
  209. # Show form options
  210. $loglist->showOptions(
  211. $pager->getType(),
  212. $pager->getPerformer(),
  213. $pager->getPage(),
  214. $pager->getPattern(),
  215. $pager->getYear(),
  216. $pager->getMonth(),
  217. $pager->getDay(),
  218. $pager->getFilterParams(),
  219. $pager->getTagFilter(),
  220. $pager->getAction()
  221. );
  222. # Insert list
  223. $logBody = $pager->getBody();
  224. if ( $logBody ) {
  225. $this->getOutput()->addHTML(
  226. $pager->getNavigationBar() .
  227. $this->getActionButtons(
  228. $loglist->beginLogEventsList() .
  229. $logBody .
  230. $loglist->endLogEventsList()
  231. ) .
  232. $pager->getNavigationBar()
  233. );
  234. } else {
  235. $this->getOutput()->addWikiMsg( 'logempty' );
  236. }
  237. }
  238. private function getActionButtons( $formcontents ) {
  239. $user = $this->getUser();
  240. $canRevDelete = MediaWikiServices::getInstance()
  241. ->getPermissionManager()
  242. ->userHasAllRights( $user, 'deletedhistory', 'deletelogentry' );
  243. $showTagEditUI = ChangeTags::showTagEditingUI( $user );
  244. # If the user doesn't have the ability to delete log entries nor edit tags,
  245. # don't bother showing them the button(s).
  246. if ( !$canRevDelete && !$showTagEditUI ) {
  247. return $formcontents;
  248. }
  249. # Show button to hide log entries and/or edit change tags
  250. $s = Html::openElement(
  251. 'form',
  252. [ 'action' => wfScript(), 'id' => 'mw-log-deleterevision-submit' ]
  253. ) . "\n";
  254. $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
  255. $s .= Html::hidden( 'type', 'logging' ) . "\n";
  256. $buttons = '';
  257. if ( $canRevDelete ) {
  258. $buttons .= Html::element(
  259. 'button',
  260. [
  261. 'type' => 'submit',
  262. 'name' => 'revisiondelete',
  263. 'value' => '1',
  264. 'class' => "deleterevision-log-submit mw-log-deleterevision-button"
  265. ],
  266. $this->msg( 'showhideselectedlogentries' )->text()
  267. ) . "\n";
  268. }
  269. if ( $showTagEditUI ) {
  270. $buttons .= Html::element(
  271. 'button',
  272. [
  273. 'type' => 'submit',
  274. 'name' => 'editchangetags',
  275. 'value' => '1',
  276. 'class' => "editchangetags-log-submit mw-log-editchangetags-button"
  277. ],
  278. $this->msg( 'log-edit-tags' )->text()
  279. ) . "\n";
  280. }
  281. $buttons .= ( new ListToggle( $this->getOutput() ) )->getHTML();
  282. $s .= $buttons . $formcontents . $buttons;
  283. $s .= Html::closeElement( 'form' );
  284. return $s;
  285. }
  286. /**
  287. * Set page title and show header for this log type
  288. * @param string $type
  289. * @since 1.19
  290. */
  291. protected function addHeader( $type ) {
  292. $page = new LogPage( $type );
  293. $this->getOutput()->setPageTitle( $page->getName() );
  294. $this->getOutput()->addHTML( $page->getDescription()
  295. ->setContext( $this->getContext() )->parseAsBlock() );
  296. }
  297. protected function getGroupName() {
  298. return 'changes';
  299. }
  300. }