LogPage.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <?php
  2. /**
  3. * Contain log classes
  4. *
  5. * Copyright © 2002, 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\MediaWikiServices;
  26. /**
  27. * Class to simplify the use of log pages.
  28. * The logs are now kept in a table which is easier to manage and trim
  29. * than ever-growing wiki pages.
  30. */
  31. class LogPage {
  32. const DELETED_ACTION = 1;
  33. const DELETED_COMMENT = 2;
  34. const DELETED_USER = 4;
  35. const DELETED_RESTRICTED = 8;
  36. // Convenience fields
  37. const SUPPRESSED_USER = self::DELETED_USER | self::DELETED_RESTRICTED;
  38. const SUPPRESSED_ACTION = self::DELETED_ACTION | self::DELETED_RESTRICTED;
  39. /** @var bool */
  40. public $updateRecentChanges;
  41. /** @var bool */
  42. public $sendToUDP;
  43. /** @var string Plaintext version of the message for IRC */
  44. private $ircActionText;
  45. /** @var string Plaintext version of the message */
  46. private $actionText;
  47. /** @var string One of '', 'block', 'protect', 'rights', 'delete',
  48. * 'upload', 'move'
  49. */
  50. private $type;
  51. /** @var string One of '', 'block', 'protect', 'rights', 'delete',
  52. * 'upload', 'move', 'move_redir'
  53. */
  54. private $action;
  55. /** @var string Comment associated with action */
  56. private $comment;
  57. /** @var string Blob made of a parameters array */
  58. private $params;
  59. /** @var User The user doing the action */
  60. private $doer;
  61. /** @var Title */
  62. private $target;
  63. /**
  64. * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
  65. * 'upload', 'move'
  66. * @param bool $rc Whether to update recent changes as well as the logging table
  67. * @param string $udp Pass 'UDP' to send to the UDP feed if NOT sent to RC
  68. */
  69. public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
  70. $this->type = $type;
  71. $this->updateRecentChanges = $rc;
  72. $this->sendToUDP = ( $udp == 'UDP' );
  73. }
  74. /**
  75. * @return int The log_id of the inserted log entry
  76. */
  77. protected function saveContent() {
  78. global $wgLogRestrictions;
  79. $dbw = wfGetDB( DB_MASTER );
  80. $now = wfTimestampNow();
  81. $data = [
  82. 'log_type' => $this->type,
  83. 'log_action' => $this->action,
  84. 'log_timestamp' => $dbw->timestamp( $now ),
  85. 'log_namespace' => $this->target->getNamespace(),
  86. 'log_title' => $this->target->getDBkey(),
  87. 'log_page' => $this->target->getArticleID(),
  88. 'log_params' => $this->params
  89. ];
  90. $data += MediaWikiServices::getInstance()->getCommentStore()->insert(
  91. $dbw,
  92. 'log_comment',
  93. $this->comment
  94. );
  95. $data += ActorMigration::newMigration()->getInsertValues( $dbw, 'log_user', $this->doer );
  96. $dbw->insert( 'logging', $data, __METHOD__ );
  97. $newId = $dbw->insertId();
  98. # And update recentchanges
  99. if ( $this->updateRecentChanges ) {
  100. $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
  101. RecentChange::notifyLog(
  102. $now, $titleObj, $this->doer, $this->getRcComment(), '',
  103. $this->type, $this->action, $this->target, $this->comment,
  104. $this->params, $newId, $this->getRcCommentIRC()
  105. );
  106. } elseif ( $this->sendToUDP ) {
  107. # Don't send private logs to UDP
  108. if ( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
  109. return $newId;
  110. }
  111. # Notify external application via UDP.
  112. # We send this to IRC but do not want to add it the RC table.
  113. $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
  114. $rc = RecentChange::newLogEntry(
  115. $now, $titleObj, $this->doer, $this->getRcComment(), '',
  116. $this->type, $this->action, $this->target, $this->comment,
  117. $this->params, $newId, $this->getRcCommentIRC()
  118. );
  119. $rc->notifyRCFeeds();
  120. }
  121. return $newId;
  122. }
  123. /**
  124. * Get the RC comment from the last addEntry() call
  125. *
  126. * @return string
  127. */
  128. public function getRcComment() {
  129. $rcComment = $this->actionText;
  130. if ( $this->comment != '' ) {
  131. if ( $rcComment == '' ) {
  132. $rcComment = $this->comment;
  133. } else {
  134. $rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
  135. $this->comment;
  136. }
  137. }
  138. return $rcComment;
  139. }
  140. /**
  141. * Get the RC comment from the last addEntry() call for IRC
  142. *
  143. * @return string
  144. */
  145. public function getRcCommentIRC() {
  146. $rcComment = $this->ircActionText;
  147. if ( $this->comment != '' ) {
  148. if ( $rcComment == '' ) {
  149. $rcComment = $this->comment;
  150. } else {
  151. $rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
  152. $this->comment;
  153. }
  154. }
  155. return $rcComment;
  156. }
  157. /**
  158. * Get the comment from the last addEntry() call
  159. * @return string
  160. */
  161. public function getComment() {
  162. return $this->comment;
  163. }
  164. /**
  165. * Get the list of valid log types
  166. *
  167. * @return array Array of strings
  168. */
  169. public static function validTypes() {
  170. global $wgLogTypes;
  171. return $wgLogTypes;
  172. }
  173. /**
  174. * Is $type a valid log type
  175. *
  176. * @param string $type Log type to check
  177. * @return bool
  178. */
  179. public static function isLogType( $type ) {
  180. return in_array( $type, self::validTypes() );
  181. }
  182. /**
  183. * Generate text for a log entry.
  184. * Only LogFormatter should call this function.
  185. *
  186. * @param string $type Log type
  187. * @param string $action Log action
  188. * @param Title|null $title Title object or null
  189. * @param Skin|null $skin Skin object or null. If null, we want to use the wiki
  190. * content language, since that will go to the IRC feed.
  191. * @param array $params Parameters
  192. * @param bool $filterWikilinks Whether to filter wiki links
  193. * @return string HTML
  194. */
  195. public static function actionText( $type, $action, $title = null, $skin = null,
  196. $params = [], $filterWikilinks = false
  197. ) {
  198. global $wgLang, $wgLogActions;
  199. if ( is_null( $skin ) ) {
  200. $langObj = MediaWikiServices::getInstance()->getContentLanguage();
  201. $langObjOrNull = null;
  202. } else {
  203. $langObj = $wgLang;
  204. $langObjOrNull = $wgLang;
  205. }
  206. $key = "$type/$action";
  207. if ( isset( $wgLogActions[$key] ) ) {
  208. if ( is_null( $title ) ) {
  209. $rv = wfMessage( $wgLogActions[$key] )->inLanguage( $langObj )->escaped();
  210. } else {
  211. $titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
  212. if ( count( $params ) == 0 ) {
  213. $rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )
  214. ->inLanguage( $langObj )->escaped();
  215. } else {
  216. array_unshift( $params, $titleLink );
  217. $rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )
  218. ->inLanguage( $langObj )->escaped();
  219. }
  220. }
  221. } else {
  222. global $wgLogActionsHandlers;
  223. if ( isset( $wgLogActionsHandlers[$key] ) ) {
  224. $args = func_get_args();
  225. $rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
  226. } else {
  227. wfDebug( "LogPage::actionText - unknown action $key\n" );
  228. $rv = "$action";
  229. }
  230. }
  231. // For the perplexed, this feature was added in r7855 by Erik.
  232. // The feature was added because we liked adding [[$1]] in our log entries
  233. // but the log entries are parsed as Wikitext on RecentChanges but as HTML
  234. // on Special:Log. The hack is essentially that [[$1]] represented a link
  235. // to the title in question. The first parameter to the HTML version (Special:Log)
  236. // is that link in HTML form, and so this just gets rid of the ugly [[]].
  237. // However, this is a horrible hack and it doesn't work like you expect if, say,
  238. // you want to link to something OTHER than the title of the log entry.
  239. // The real problem, which Erik was trying to fix (and it sort-of works now) is
  240. // that the same messages are being treated as both wikitext *and* HTML.
  241. if ( $filterWikilinks ) {
  242. $rv = str_replace( '[[', '', $rv );
  243. $rv = str_replace( ']]', '', $rv );
  244. }
  245. return $rv;
  246. }
  247. /**
  248. * @todo Document
  249. * @param string $type
  250. * @param Language|null $lang
  251. * @param Title $title
  252. * @param array &$params
  253. * @return string
  254. */
  255. protected static function getTitleLink( $type, $lang, $title, &$params ) {
  256. if ( !$lang ) {
  257. return $title->getPrefixedText();
  258. }
  259. $services = MediaWikiServices::getInstance();
  260. $linkRenderer = $services->getLinkRenderer();
  261. if ( $title->isSpecialPage() ) {
  262. list( $name, $par ) = $services->getSpecialPageFactory()->
  263. resolveAlias( $title->getDBkey() );
  264. # Use the language name for log titles, rather than Log/X
  265. if ( $name == 'Log' ) {
  266. $logPage = new LogPage( $par );
  267. $titleLink = $linkRenderer->makeLink( $title, $logPage->getName()->text() );
  268. $titleLink = wfMessage( 'parentheses' )
  269. ->inLanguage( $lang )
  270. ->rawParams( $titleLink )
  271. ->escaped();
  272. } else {
  273. $titleLink = $linkRenderer->makeLink( $title );
  274. }
  275. } else {
  276. $titleLink = $linkRenderer->makeLink( $title );
  277. }
  278. return $titleLink;
  279. }
  280. /**
  281. * Add a log entry
  282. *
  283. * @param string $action One of '', 'block', 'protect', 'rights', 'delete',
  284. * 'upload', 'move', 'move_redir'
  285. * @param Title $target
  286. * @param string $comment Description associated
  287. * @param array $params Parameters passed later to wfMessage function
  288. * @param null|int|User $doer The user doing the action. null for $wgUser
  289. *
  290. * @return int The log_id of the inserted log entry
  291. */
  292. public function addEntry( $action, $target, $comment, $params = [], $doer = null ) {
  293. if ( !is_array( $params ) ) {
  294. $params = [ $params ];
  295. }
  296. if ( $comment === null ) {
  297. $comment = '';
  298. }
  299. # Trim spaces on user supplied text
  300. $comment = trim( $comment );
  301. $this->action = $action;
  302. $this->target = $target;
  303. $this->comment = $comment;
  304. $this->params = self::makeParamBlob( $params );
  305. if ( $doer === null ) {
  306. global $wgUser;
  307. $doer = $wgUser;
  308. } elseif ( !is_object( $doer ) ) {
  309. $doer = User::newFromId( $doer );
  310. }
  311. $this->doer = $doer;
  312. $logEntry = new ManualLogEntry( $this->type, $action );
  313. $logEntry->setTarget( $target );
  314. $logEntry->setPerformer( $doer );
  315. $logEntry->setParameters( $params );
  316. // All log entries using the LogPage to insert into the logging table
  317. // are using the old logging system and therefore the legacy flag is
  318. // needed to say the LogFormatter the parameters have numeric keys
  319. $logEntry->setLegacy( true );
  320. $formatter = LogFormatter::newFromEntry( $logEntry );
  321. $context = RequestContext::newExtraneousContext( $target );
  322. $formatter->setContext( $context );
  323. $this->actionText = $formatter->getPlainActionText();
  324. $this->ircActionText = $formatter->getIRCActionText();
  325. return $this->saveContent();
  326. }
  327. /**
  328. * Add relations to log_search table
  329. *
  330. * @param string $field
  331. * @param array $values
  332. * @param int $logid
  333. * @return bool
  334. */
  335. public function addRelations( $field, $values, $logid ) {
  336. if ( !strlen( $field ) || empty( $values ) ) {
  337. return false;
  338. }
  339. $data = [];
  340. foreach ( $values as $value ) {
  341. $data[] = [
  342. 'ls_field' => $field,
  343. 'ls_value' => $value,
  344. 'ls_log_id' => $logid
  345. ];
  346. }
  347. $dbw = wfGetDB( DB_MASTER );
  348. $dbw->insert( 'log_search', $data, __METHOD__, [ 'IGNORE' ] );
  349. return true;
  350. }
  351. /**
  352. * Create a blob from a parameter array
  353. *
  354. * @param array $params
  355. * @return string
  356. */
  357. public static function makeParamBlob( $params ) {
  358. return implode( "\n", $params );
  359. }
  360. /**
  361. * Extract a parameter array from a blob
  362. *
  363. * @param string $blob
  364. * @return array
  365. */
  366. public static function extractParams( $blob ) {
  367. if ( $blob === '' ) {
  368. return [];
  369. } else {
  370. return explode( "\n", $blob );
  371. }
  372. }
  373. /**
  374. * Name of the log.
  375. * @return Message
  376. * @since 1.19
  377. */
  378. public function getName() {
  379. global $wgLogNames;
  380. // BC
  381. $key = $wgLogNames[$this->type] ?? 'log-name-' . $this->type;
  382. return wfMessage( $key );
  383. }
  384. /**
  385. * Description of this log type.
  386. * @return Message
  387. * @since 1.19
  388. */
  389. public function getDescription() {
  390. global $wgLogHeaders;
  391. // BC
  392. $key = $wgLogHeaders[$this->type] ?? 'log-description-' . $this->type;
  393. return wfMessage( $key );
  394. }
  395. /**
  396. * Returns the right needed to read this log type.
  397. * @return string
  398. * @since 1.19
  399. */
  400. public function getRestriction() {
  401. global $wgLogRestrictions;
  402. // '' always returns true with $user->isAllowed()
  403. return $wgLogRestrictions[$this->type] ?? '';
  404. }
  405. /**
  406. * Tells if this log is not viewable by all.
  407. * @return bool
  408. * @since 1.19
  409. */
  410. public function isRestricted() {
  411. $restriction = $this->getRestriction();
  412. return $restriction !== '' && $restriction !== '*';
  413. }
  414. }