ManualLogEntry.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <?php
  2. /**
  3. * Contains a class for dealing with manual log entries
  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. * @author Niklas Laxström
  22. * @license GPL-2.0-or-later
  23. * @since 1.19
  24. */
  25. use MediaWiki\ChangeTags\Taggable;
  26. use MediaWiki\Linker\LinkTarget;
  27. use MediaWiki\User\UserIdentity;
  28. use Wikimedia\Rdbms\IDatabase;
  29. use Wikimedia\Assert\Assert;
  30. /**
  31. * Class for creating new log entries and inserting them into the database.
  32. *
  33. * @since 1.19
  34. */
  35. class ManualLogEntry extends LogEntryBase implements Taggable {
  36. /** @var string Type of log entry */
  37. protected $type;
  38. /** @var string Sub type of log entry */
  39. protected $subtype;
  40. /** @var array Parameters for log entry */
  41. protected $parameters = [];
  42. /** @var array */
  43. protected $relations = [];
  44. /** @var User Performer of the action for the log entry */
  45. protected $performer;
  46. /** @var Title Target title for the log entry */
  47. protected $target;
  48. /** @var string Timestamp of creation of the log entry */
  49. protected $timestamp;
  50. /** @var string Comment for the log entry */
  51. protected $comment = '';
  52. /** @var int A rev id associated to the log entry */
  53. protected $revId = 0;
  54. /** @var string[] Change tags add to the log entry */
  55. protected $tags = [];
  56. /** @var int Deletion state of the log entry */
  57. protected $deleted;
  58. /** @var int ID of the log entry */
  59. protected $id;
  60. /** @var bool Can this log entry be patrolled? */
  61. protected $isPatrollable = false;
  62. /** @var bool Whether this is a legacy log entry */
  63. protected $legacy = false;
  64. /**
  65. * @since 1.19
  66. * @param string $type
  67. * @param string $subtype
  68. */
  69. public function __construct( $type, $subtype ) {
  70. $this->type = $type;
  71. $this->subtype = $subtype;
  72. }
  73. /**
  74. * Set extra log parameters.
  75. *
  76. * You can pass params to the log action message by prefixing the keys with
  77. * a number and optional type, using colons to separate the fields. The
  78. * numbering should start with number 4, the first three parameters are
  79. * hardcoded for every message.
  80. *
  81. * If you want to store stuff that should not be available in messages, don't
  82. * prefix the array key with a number and don't use the colons.
  83. *
  84. * Example:
  85. * $entry->setParameters(
  86. * '4::color' => 'blue',
  87. * '5:number:count' => 3000,
  88. * 'animal' => 'dog'
  89. * );
  90. *
  91. * @since 1.19
  92. * @param array $parameters Associative array
  93. */
  94. public function setParameters( $parameters ) {
  95. $this->parameters = $parameters;
  96. }
  97. /**
  98. * Declare arbitrary tag/value relations to this log entry.
  99. * These can be used to filter log entries later on.
  100. *
  101. * @param array $relations Map of (tag => (list of values|value))
  102. * @since 1.22
  103. */
  104. public function setRelations( array $relations ) {
  105. $this->relations = $relations;
  106. }
  107. /**
  108. * Set the user that performed the action being logged.
  109. *
  110. * @since 1.19
  111. * @param UserIdentity $performer
  112. */
  113. public function setPerformer( UserIdentity $performer ) {
  114. $this->performer = User::newFromIdentity( $performer );
  115. }
  116. /**
  117. * Set the title of the object changed.
  118. *
  119. * @since 1.19
  120. * @param LinkTarget $target
  121. */
  122. public function setTarget( LinkTarget $target ) {
  123. $this->target = Title::newFromLinkTarget( $target );
  124. }
  125. /**
  126. * Set the timestamp of when the logged action took place.
  127. *
  128. * @since 1.19
  129. * @param string $timestamp
  130. */
  131. public function setTimestamp( $timestamp ) {
  132. $this->timestamp = $timestamp;
  133. }
  134. /**
  135. * Set a comment associated with the action being logged.
  136. *
  137. * @since 1.19
  138. * @param string $comment
  139. */
  140. public function setComment( $comment ) {
  141. $this->comment = $comment;
  142. }
  143. /**
  144. * Set an associated revision id.
  145. *
  146. * For example, the ID of the revision that was inserted to mark a page move
  147. * or protection, file upload, etc.
  148. *
  149. * @since 1.27
  150. * @param int $revId
  151. */
  152. public function setAssociatedRevId( $revId ) {
  153. $this->revId = $revId;
  154. }
  155. /**
  156. * Set change tags for the log entry.
  157. *
  158. * Passing `null` means the same as empty array,
  159. * for compatibility with WikiPage::doUpdateRestrictions().
  160. *
  161. * @since 1.27
  162. * @param string|string[]|null $tags
  163. * @deprecated since 1.33 Please use addTags() instead
  164. */
  165. public function setTags( $tags ) {
  166. if ( $this->tags ) {
  167. wfDebug( 'Overwriting existing ManualLogEntry tags' );
  168. }
  169. $this->tags = [];
  170. $this->addTags( $tags );
  171. }
  172. /**
  173. * Add change tags for the log entry
  174. *
  175. * @since 1.33
  176. * @param string|string[]|null $tags Tags to apply
  177. */
  178. public function addTags( $tags ) {
  179. if ( $tags === null ) {
  180. return;
  181. }
  182. if ( is_string( $tags ) ) {
  183. $tags = [ $tags ];
  184. }
  185. Assert::parameterElementType( 'string', $tags, 'tags' );
  186. $this->tags = array_unique( array_merge( $this->tags, $tags ) );
  187. }
  188. /**
  189. * Set whether this log entry should be made patrollable
  190. * This shouldn't depend on config, only on whether there is full support
  191. * in the software for patrolling this log entry.
  192. * False by default
  193. *
  194. * @since 1.27
  195. * @param bool $patrollable
  196. */
  197. public function setIsPatrollable( $patrollable ) {
  198. $this->isPatrollable = (bool)$patrollable;
  199. }
  200. /**
  201. * Set the 'legacy' flag
  202. *
  203. * @since 1.25
  204. * @param bool $legacy
  205. */
  206. public function setLegacy( $legacy ) {
  207. $this->legacy = $legacy;
  208. }
  209. /**
  210. * Set the 'deleted' flag.
  211. *
  212. * @since 1.19
  213. * @param int $deleted One of LogPage::DELETED_* bitfield constants
  214. */
  215. public function setDeleted( $deleted ) {
  216. $this->deleted = $deleted;
  217. }
  218. /**
  219. * Insert the entry into the `logging` table.
  220. *
  221. * @param IDatabase|null $dbw
  222. * @return int ID of the log entry
  223. * @throws MWException
  224. */
  225. public function insert( IDatabase $dbw = null ) {
  226. $dbw = $dbw ?: wfGetDB( DB_MASTER );
  227. if ( $this->timestamp === null ) {
  228. $this->timestamp = wfTimestampNow();
  229. }
  230. // Trim spaces on user supplied text
  231. $comment = trim( $this->getComment() );
  232. $params = $this->getParameters();
  233. $relations = $this->relations;
  234. // Additional fields for which there's no space in the database table schema
  235. $revId = $this->getAssociatedRevId();
  236. if ( $revId ) {
  237. $params['associated_rev_id'] = $revId;
  238. $relations['associated_rev_id'] = $revId;
  239. }
  240. $data = [
  241. 'log_type' => $this->getType(),
  242. 'log_action' => $this->getSubtype(),
  243. 'log_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
  244. 'log_namespace' => $this->getTarget()->getNamespace(),
  245. 'log_title' => $this->getTarget()->getDBkey(),
  246. 'log_page' => $this->getTarget()->getArticleID(),
  247. 'log_params' => LogEntryBase::makeParamBlob( $params ),
  248. ];
  249. if ( isset( $this->deleted ) ) {
  250. $data['log_deleted'] = $this->deleted;
  251. }
  252. $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $comment );
  253. $data += ActorMigration::newMigration()
  254. ->getInsertValues( $dbw, 'log_user', $this->getPerformer() );
  255. $dbw->insert( 'logging', $data, __METHOD__ );
  256. $this->id = $dbw->insertId();
  257. $rows = [];
  258. foreach ( $relations as $tag => $values ) {
  259. if ( !strlen( $tag ) ) {
  260. throw new MWException( "Got empty log search tag." );
  261. }
  262. if ( !is_array( $values ) ) {
  263. $values = [ $values ];
  264. }
  265. foreach ( $values as $value ) {
  266. $rows[] = [
  267. 'ls_field' => $tag,
  268. 'ls_value' => $value,
  269. 'ls_log_id' => $this->id
  270. ];
  271. }
  272. }
  273. if ( count( $rows ) ) {
  274. $dbw->insert( 'log_search', $rows, __METHOD__, [ 'IGNORE' ] );
  275. }
  276. return $this->id;
  277. }
  278. /**
  279. * Get a RecentChanges object for the log entry
  280. *
  281. * @param int $newId
  282. * @return RecentChange
  283. * @since 1.23
  284. */
  285. public function getRecentChange( $newId = 0 ) {
  286. $formatter = LogFormatter::newFromEntry( $this );
  287. $context = RequestContext::newExtraneousContext( $this->getTarget() );
  288. $formatter->setContext( $context );
  289. $logpage = SpecialPage::getTitleFor( 'Log', $this->getType() );
  290. $user = $this->getPerformer();
  291. $ip = "";
  292. if ( $user->isAnon() ) {
  293. // "MediaWiki default" and friends may have
  294. // no IP address in their name
  295. if ( IP::isIPAddress( $user->getName() ) ) {
  296. $ip = $user->getName();
  297. }
  298. }
  299. return RecentChange::newLogEntry(
  300. $this->getTimestamp(),
  301. $logpage,
  302. $user,
  303. $formatter->getPlainActionText(),
  304. $ip,
  305. $this->getType(),
  306. $this->getSubtype(),
  307. $this->getTarget(),
  308. $this->getComment(),
  309. LogEntryBase::makeParamBlob( $this->getParameters() ),
  310. $newId,
  311. $formatter->getIRCActionComment(), // Used for IRC feeds
  312. $this->getAssociatedRevId(), // Used for e.g. moves and uploads
  313. $this->getIsPatrollable()
  314. );
  315. }
  316. /**
  317. * Publish the log entry.
  318. *
  319. * @param int $newId Id of the log entry.
  320. * @param string $to One of: rcandudp (default), rc, udp
  321. */
  322. public function publish( $newId, $to = 'rcandudp' ) {
  323. $canAddTags = true;
  324. // FIXME: this code should be removed once all callers properly call publish()
  325. if ( $to === 'udp' && !$newId && !$this->getAssociatedRevId() ) {
  326. \MediaWiki\Logger\LoggerFactory::getInstance( 'logging' )->warning(
  327. 'newId and/or revId must be set when calling ManualLogEntry::publish()',
  328. [
  329. 'newId' => $newId,
  330. 'to' => $to,
  331. 'revId' => $this->getAssociatedRevId(),
  332. // pass a new exception to register the stack trace
  333. 'exception' => new RuntimeException()
  334. ]
  335. );
  336. $canAddTags = false;
  337. }
  338. DeferredUpdates::addCallableUpdate(
  339. function () use ( $newId, $to, $canAddTags ) {
  340. $log = new LogPage( $this->getType() );
  341. if ( !$log->isRestricted() ) {
  342. Hooks::runWithoutAbort( 'ManualLogEntryBeforePublish', [ $this ] );
  343. $rc = $this->getRecentChange( $newId );
  344. if ( $to === 'rc' || $to === 'rcandudp' ) {
  345. // save RC, passing tags so they are applied there
  346. $rc->addTags( $this->getTags() );
  347. $rc->save( $rc::SEND_NONE );
  348. } else {
  349. $tags = $this->getTags();
  350. if ( $tags && $canAddTags ) {
  351. $revId = $this->getAssociatedRevId();
  352. ChangeTags::addTags(
  353. $tags,
  354. null,
  355. $revId > 0 ? $revId : null,
  356. $newId > 0 ? $newId : null
  357. );
  358. }
  359. }
  360. if ( $to === 'udp' || $to === 'rcandudp' ) {
  361. $rc->notifyRCFeeds();
  362. }
  363. }
  364. },
  365. DeferredUpdates::POSTSEND,
  366. wfGetDB( DB_MASTER )
  367. );
  368. }
  369. public function getType() {
  370. return $this->type;
  371. }
  372. public function getSubtype() {
  373. return $this->subtype;
  374. }
  375. public function getParameters() {
  376. return $this->parameters;
  377. }
  378. /**
  379. * @return User
  380. */
  381. public function getPerformer() {
  382. return $this->performer;
  383. }
  384. /**
  385. * @return Title
  386. */
  387. public function getTarget() {
  388. return $this->target;
  389. }
  390. public function getTimestamp() {
  391. $ts = $this->timestamp ?? wfTimestampNow();
  392. return wfTimestamp( TS_MW, $ts );
  393. }
  394. public function getComment() {
  395. return $this->comment;
  396. }
  397. /**
  398. * @since 1.27
  399. * @return int
  400. */
  401. public function getAssociatedRevId() {
  402. return $this->revId;
  403. }
  404. /**
  405. * @since 1.27
  406. * @return string[]
  407. */
  408. public function getTags() {
  409. return $this->tags;
  410. }
  411. /**
  412. * Whether this log entry is patrollable
  413. *
  414. * @since 1.27
  415. * @return bool
  416. */
  417. public function getIsPatrollable() {
  418. return $this->isPatrollable;
  419. }
  420. /**
  421. * @since 1.25
  422. * @return bool
  423. */
  424. public function isLegacy() {
  425. return $this->legacy;
  426. }
  427. public function getDeleted() {
  428. return (int)$this->deleted;
  429. }
  430. }