LogPage.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <?php
  2. #
  3. # Copyright (C) 2002, 2004 Brion Vibber <brion@pobox.com>
  4. # http://www.mediawiki.org/
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License along
  17. # with this program; if not, write to the Free Software Foundation, Inc.,
  18. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. # http://www.gnu.org/copyleft/gpl.html
  20. /**
  21. * Contain log classes
  22. * @file
  23. */
  24. /**
  25. * Class to simplify the use of log pages.
  26. * The logs are now kept in a table which is easier to manage and trim
  27. * than ever-growing wiki pages.
  28. *
  29. */
  30. class LogPage {
  31. const DELETED_ACTION = 1;
  32. const DELETED_COMMENT = 2;
  33. const DELETED_USER = 4;
  34. const DELETED_RESTRICTED = 8;
  35. /* @access private */
  36. var $type, $action, $comment, $params, $target, $doer;
  37. /* @acess public */
  38. var $updateRecentChanges, $sendToUDP;
  39. /**
  40. * Constructor
  41. *
  42. * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
  43. * 'upload', 'move'
  44. * @param bool $rc Whether to update recent changes as well as the logging table
  45. * @param bool $udp Whether to send to the UDP feed if NOT sent to RC
  46. */
  47. public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
  48. $this->type = $type;
  49. $this->updateRecentChanges = $rc;
  50. $this->sendToUDP = ($udp == 'UDP');
  51. }
  52. protected function saveContent() {
  53. global $wgLogRestrictions;
  54. $dbw = wfGetDB( DB_MASTER );
  55. $log_id = $dbw->nextSequenceValue( 'log_log_id_seq' );
  56. $this->timestamp = $now = wfTimestampNow();
  57. $data = array(
  58. 'log_id' => $log_id,
  59. 'log_type' => $this->type,
  60. 'log_action' => $this->action,
  61. 'log_timestamp' => $dbw->timestamp( $now ),
  62. 'log_user' => $this->doer->getId(),
  63. 'log_namespace' => $this->target->getNamespace(),
  64. 'log_title' => $this->target->getDBkey(),
  65. 'log_comment' => $this->comment,
  66. 'log_params' => $this->params
  67. );
  68. $dbw->insert( 'logging', $data, __METHOD__ );
  69. $newId = !is_null($log_id) ? $log_id : $dbw->insertId();
  70. # And update recentchanges
  71. if( $this->updateRecentChanges ) {
  72. $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
  73. RecentChange::notifyLog( $now, $titleObj, $this->doer, $this->getRcComment(), '', $this->type,
  74. $this->action, $this->target, $this->comment, $this->params, $newId );
  75. } else if( $this->sendToUDP ) {
  76. # Don't send private logs to UDP
  77. if( isset($wgLogRestrictions[$this->type]) && $wgLogRestrictions[$this->type] !='*' ) {
  78. return true;
  79. }
  80. # Notify external application via UDP.
  81. # We send this to IRC but do not want to add it the RC table.
  82. $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
  83. $rc = RecentChange::newLogEntry( $now, $titleObj, $this->doer, $this->getRcComment(), '',
  84. $this->type, $this->action, $this->target, $this->comment, $this->params, $newId );
  85. $rc->notifyRC2UDP();
  86. }
  87. return true;
  88. }
  89. /**
  90. * Get the RC comment from the last addEntry() call
  91. */
  92. public function getRcComment() {
  93. $rcComment = $this->actionText;
  94. if( '' != $this->comment ) {
  95. if ($rcComment == '')
  96. $rcComment = $this->comment;
  97. else
  98. $rcComment .= wfMsgForContent( 'colon-separator' ) . $this->comment;
  99. }
  100. return $rcComment;
  101. }
  102. /**
  103. * Get the comment from the last addEntry() call
  104. */
  105. public function getComment() {
  106. return $this->comment;
  107. }
  108. /**
  109. * @static
  110. */
  111. public static function validTypes() {
  112. global $wgLogTypes;
  113. return $wgLogTypes;
  114. }
  115. /**
  116. * @static
  117. */
  118. public static function isLogType( $type ) {
  119. return in_array( $type, LogPage::validTypes() );
  120. }
  121. /**
  122. * @static
  123. */
  124. public static function logName( $type ) {
  125. global $wgLogNames, $wgMessageCache;
  126. if( isset( $wgLogNames[$type] ) ) {
  127. $wgMessageCache->loadAllMessages();
  128. return str_replace( '_', ' ', wfMsg( $wgLogNames[$type] ) );
  129. } else {
  130. // Bogus log types? Perhaps an extension was removed.
  131. return $type;
  132. }
  133. }
  134. /**
  135. * @todo handle missing log types
  136. * @param string $type logtype
  137. * @return string Headertext of this logtype
  138. */
  139. public static function logHeader( $type ) {
  140. global $wgLogHeaders, $wgMessageCache;
  141. $wgMessageCache->loadAllMessages();
  142. return wfMsgExt($wgLogHeaders[$type],array('parseinline'));
  143. }
  144. /**
  145. * @static
  146. * @return HTML string
  147. */
  148. public static function actionText( $type, $action, $title = NULL, $skin = NULL,
  149. $params = array(), $filterWikilinks = false )
  150. {
  151. global $wgLang, $wgContLang, $wgLogActions, $wgMessageCache;
  152. $wgMessageCache->loadAllMessages();
  153. $key = "$type/$action";
  154. # Defer patrol log to PatrolLog class
  155. if( $key == 'patrol/patrol' ) {
  156. return PatrolLog::makeActionText( $title, $params, $skin );
  157. }
  158. if( isset( $wgLogActions[$key] ) ) {
  159. if( is_null( $title ) ) {
  160. $rv = wfMsg( $wgLogActions[$key] );
  161. } else {
  162. $titleLink = self::getTitleLink( $type, $skin, $title, $params );
  163. if( $key == 'rights/rights' ) {
  164. if( $skin ) {
  165. $rightsnone = wfMsg( 'rightsnone' );
  166. foreach ( $params as &$param ) {
  167. $groupArray = array_map( 'trim', explode( ',', $param ) );
  168. $groupArray = array_map( array( 'User', 'getGroupName' ), $groupArray );
  169. $param = $wgLang->listToText( $groupArray );
  170. }
  171. } else {
  172. $rightsnone = wfMsgForContent( 'rightsnone' );
  173. }
  174. if( !isset( $params[0] ) || trim( $params[0] ) == '' )
  175. $params[0] = $rightsnone;
  176. if( !isset( $params[1] ) || trim( $params[1] ) == '' )
  177. $params[1] = $rightsnone;
  178. }
  179. if( count( $params ) == 0 ) {
  180. if ( $skin ) {
  181. $rv = wfMsg( $wgLogActions[$key], $titleLink );
  182. } else {
  183. $rv = wfMsgForContent( $wgLogActions[$key], $titleLink );
  184. }
  185. } else {
  186. $details = '';
  187. array_unshift( $params, $titleLink );
  188. if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
  189. if ( $skin ) {
  190. $params[1] = '<span title="' . htmlspecialchars( $params[1] ). '">' .
  191. $wgLang->translateBlockExpiry( $params[1] ) . '</span>';
  192. } else {
  193. $params[1] = $wgContLang->translateBlockExpiry( $params[1] );
  194. }
  195. $params[2] = isset( $params[2] ) ?
  196. self::formatBlockFlags( $params[2], is_null( $skin ) ) : '';
  197. } else if ( $type == 'protect' && count($params) == 3 ) {
  198. $details .= " {$params[1]}"; // restrictions and expiries
  199. if( $params[2] ) {
  200. if ( $skin ) {
  201. $details .= ' ['.wfMsg('protect-summary-cascade').']';
  202. } else {
  203. $details .= ' ['.wfMsgForContent('protect-summary-cascade').']';
  204. }
  205. }
  206. } else if ( $type == 'move' && count( $params ) == 3 ) {
  207. if( $params[2] ) {
  208. if ( $skin ) {
  209. $details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']';
  210. } else {
  211. $details .= ' [' . wfMsgForContent( 'move-redirect-suppressed' ) . ']';
  212. }
  213. }
  214. }
  215. $rv = wfMsgReal( $wgLogActions[$key], $params, true, !$skin ) . $details;
  216. }
  217. }
  218. } else {
  219. global $wgLogActionsHandlers;
  220. if( isset( $wgLogActionsHandlers[$key] ) ) {
  221. $args = func_get_args();
  222. $rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
  223. } else {
  224. wfDebug( "LogPage::actionText - unknown action $key\n" );
  225. $rv = "$action";
  226. }
  227. }
  228. // For the perplexed, this feature was added in r7855 by Erik.
  229. // The feature was added because we liked adding [[$1]] in our log entries
  230. // but the log entries are parsed as Wikitext on RecentChanges but as HTML
  231. // on Special:Log. The hack is essentially that [[$1]] represented a link
  232. // to the title in question. The first parameter to the HTML version (Special:Log)
  233. // is that link in HTML form, and so this just gets rid of the ugly [[]].
  234. // However, this is a horrible hack and it doesn't work like you expect if, say,
  235. // you want to link to something OTHER than the title of the log entry.
  236. // The real problem, which Erik was trying to fix (and it sort-of works now) is
  237. // that the same messages are being treated as both wikitext *and* HTML.
  238. if( $filterWikilinks ) {
  239. $rv = str_replace( "[[", "", $rv );
  240. $rv = str_replace( "]]", "", $rv );
  241. }
  242. return $rv;
  243. }
  244. protected static function getTitleLink( $type, $skin, $title, &$params ) {
  245. global $wgLang, $wgContLang;
  246. if( !$skin ) {
  247. return $title->getPrefixedText();
  248. }
  249. switch( $type ) {
  250. case 'move':
  251. $titleLink = $skin->makeLinkObj( $title,
  252. htmlspecialchars( $title->getPrefixedText() ), 'redirect=no' );
  253. $targetTitle = Title::newFromText( $params[0] );
  254. if ( !$targetTitle ) {
  255. # Workaround for broken database
  256. $params[0] = htmlspecialchars( $params[0] );
  257. } else {
  258. $params[0] = $skin->makeLinkObj( $targetTitle, htmlspecialchars( $params[0] ) );
  259. }
  260. break;
  261. case 'block':
  262. if( substr( $title->getText(), 0, 1 ) == '#' ) {
  263. $titleLink = $title->getText();
  264. } else {
  265. // TODO: Store the user identifier in the parameters
  266. // to make this faster for future log entries
  267. $id = User::idFromName( $title->getText() );
  268. $titleLink = $skin->userLink( $id, $title->getText() )
  269. . $skin->userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
  270. }
  271. break;
  272. case 'rights':
  273. $text = $wgContLang->ucfirst( $title->getText() );
  274. $titleLink = $skin->makeLinkObj( Title::makeTitle( NS_USER, $text ) );
  275. break;
  276. case 'merge':
  277. $titleLink = $skin->makeLinkObj( $title, $title->getPrefixedText(), 'redirect=no' );
  278. $params[0] = $skin->makeLinkObj( Title::newFromText( $params[0] ), htmlspecialchars( $params[0] ) );
  279. $params[1] = $wgLang->timeanddate( $params[1] );
  280. break;
  281. default:
  282. if( $title->getNamespace() == NS_SPECIAL ) {
  283. list( $name, $par ) = SpecialPage::resolveAliasWithSubpage( $title->getDBKey() );
  284. # Use the language name for log titles, rather than Log/X
  285. if( $name == 'Log' ) {
  286. $titleLink = '('.$skin->makeLinkObj( $title, LogPage::logName( $par ) ).')';
  287. } else {
  288. $titleLink = $skin->makeLinkObj( $title );
  289. }
  290. } else {
  291. $titleLink = $skin->makeLinkObj( $title );
  292. }
  293. }
  294. return $titleLink;
  295. }
  296. /**
  297. * Add a log entry
  298. * @param string $action one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir'
  299. * @param object &$target A title object.
  300. * @param string $comment Description associated
  301. * @param array $params Parameters passed later to wfMsg.* functions
  302. * @param User $doer The user doing the action
  303. */
  304. public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
  305. if ( !is_array( $params ) ) {
  306. $params = array( $params );
  307. }
  308. $this->action = $action;
  309. $this->target = $target;
  310. $this->comment = $comment;
  311. $this->params = LogPage::makeParamBlob( $params );
  312. if ($doer === null) {
  313. global $wgUser;
  314. $doer = $wgUser;
  315. } elseif (!is_object( $doer ) ) {
  316. $doer = User::newFromId( $doer );
  317. }
  318. $this->doer = $doer;
  319. $this->actionText = LogPage::actionText( $this->type, $action, $target, NULL, $params );
  320. return $this->saveContent();
  321. }
  322. /**
  323. * Create a blob from a parameter array
  324. * @static
  325. */
  326. public static function makeParamBlob( $params ) {
  327. return implode( "\n", $params );
  328. }
  329. /**
  330. * Extract a parameter array from a blob
  331. * @static
  332. */
  333. public static function extractParams( $blob ) {
  334. if ( $blob === '' ) {
  335. return array();
  336. } else {
  337. return explode( "\n", $blob );
  338. }
  339. }
  340. /**
  341. * Convert a comma-delimited list of block log flags
  342. * into a more readable (and translated) form
  343. *
  344. * @param $flags Flags to format
  345. * @param $forContent Whether to localize the message depending of the user
  346. * language
  347. * @return string
  348. */
  349. public static function formatBlockFlags( $flags, $forContent = false ) {
  350. global $wgLang;
  351. $flags = explode( ',', trim( $flags ) );
  352. if( count( $flags ) > 0 ) {
  353. for( $i = 0; $i < count( $flags ); $i++ )
  354. $flags[$i] = self::formatBlockFlag( $flags[$i], $forContent );
  355. return '(' . $wgLang->commaList( $flags ) . ')';
  356. } else {
  357. return '';
  358. }
  359. }
  360. /**
  361. * Translate a block log flag if possible
  362. *
  363. * @param $flag Flag to translate
  364. * @param $forContent Whether to localize the message depending of the user
  365. * language
  366. * @return string
  367. */
  368. public static function formatBlockFlag( $flag, $forContent = false ) {
  369. static $messages = array();
  370. if( !isset( $messages[$flag] ) ) {
  371. $k = 'block-log-flags-' . $flag;
  372. if( $forContent )
  373. $msg = wfMsgForContent( $k );
  374. else
  375. $msg = wfMsg( $k );
  376. $messages[$flag] = htmlspecialchars( wfEmptyMsg( $k, $msg ) ? $flag : $msg );
  377. }
  378. return $messages[$flag];
  379. }
  380. }
  381. /**
  382. * Aliases for backwards compatibility with 1.6
  383. */
  384. define( 'MW_LOG_DELETED_ACTION', LogPage::DELETED_ACTION );
  385. define( 'MW_LOG_DELETED_USER', LogPage::DELETED_USER );
  386. define( 'MW_LOG_DELETED_COMMENT', LogPage::DELETED_COMMENT );
  387. define( 'MW_LOG_DELETED_RESTRICTED', LogPage::DELETED_RESTRICTED );