MutableRevisionRecord.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /**
  3. * Mutable RevisionRecord implementation, for building new revision entries programmatically.
  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. */
  22. namespace MediaWiki\Storage;
  23. use CommentStoreComment;
  24. use Content;
  25. use InvalidArgumentException;
  26. use MediaWiki\User\UserIdentity;
  27. use MWException;
  28. use Title;
  29. use Wikimedia\Assert\Assert;
  30. /**
  31. * Mutable RevisionRecord implementation, for building new revision entries programmatically.
  32. * Provides setters for all fields.
  33. *
  34. * @since 1.31
  35. */
  36. class MutableRevisionRecord extends RevisionRecord {
  37. /**
  38. * Returns an incomplete MutableRevisionRecord which uses $parent as its
  39. * parent revision, and inherits all slots form it. If saved unchanged,
  40. * the new revision will act as a null-revision.
  41. *
  42. * @param RevisionRecord $parent
  43. * @param CommentStoreComment $comment
  44. * @param UserIdentity $user
  45. * @param string $timestamp
  46. *
  47. * @return MutableRevisionRecord
  48. */
  49. public static function newFromParentRevision(
  50. RevisionRecord $parent,
  51. CommentStoreComment $comment,
  52. UserIdentity $user,
  53. $timestamp
  54. ) {
  55. // TODO: ideally, we wouldn't need a Title here
  56. $title = Title::newFromLinkTarget( $parent->getPageAsLinkTarget() );
  57. $rev = new MutableRevisionRecord( $title, $parent->getWikiId() );
  58. $rev->setComment( $comment );
  59. $rev->setUser( $user );
  60. $rev->setTimestamp( $timestamp );
  61. foreach ( $parent->getSlotRoles() as $role ) {
  62. $slot = $parent->getSlot( $role, self::RAW );
  63. $rev->inheritSlot( $slot );
  64. }
  65. $rev->setPageId( $parent->getPageId() );
  66. $rev->setParentId( $parent->getId() );
  67. return $rev;
  68. }
  69. /**
  70. * @note Avoid calling this constructor directly. Use the appropriate methods
  71. * in RevisionStore instead.
  72. *
  73. * @param Title $title The title of the page this Revision is associated with.
  74. * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
  75. * or false for the local site.
  76. *
  77. * @throws MWException
  78. */
  79. function __construct( Title $title, $wikiId = false ) {
  80. $slots = new MutableRevisionSlots();
  81. parent::__construct( $title, $slots, $wikiId );
  82. $this->mSlots = $slots; // redundant, but nice for static analysis
  83. }
  84. /**
  85. * @param int $parentId
  86. */
  87. public function setParentId( $parentId ) {
  88. Assert::parameterType( 'integer', $parentId, '$parentId' );
  89. $this->mParentId = $parentId;
  90. }
  91. /**
  92. * Sets the given slot. If a slot with the same role is already present in the revision,
  93. * it is replaced.
  94. *
  95. * @note This can only be used with a fresh "unattached" SlotRecord. Calling code that has a
  96. * SlotRecord from another revision should use inheritSlot(). Calling code that has access to
  97. * a Content object can use setContent().
  98. *
  99. * @note This may cause the slot meta-data for the revision to be lazy-loaded.
  100. *
  101. * @note Calling this method will cause the revision size and hash to be re-calculated upon
  102. * the next call to getSize() and getSha1(), respectively.
  103. *
  104. * @param SlotRecord $slot
  105. */
  106. public function setSlot( SlotRecord $slot ) {
  107. if ( $slot->hasRevision() && $slot->getRevision() !== $this->getId() ) {
  108. throw new InvalidArgumentException(
  109. 'The given slot must be an unsaved, unattached one. '
  110. . 'This slot is already attached to revision ' . $slot->getRevision() . '. '
  111. . 'Use inheritSlot() instead to preserve a slot from a previous revision.'
  112. );
  113. }
  114. $this->mSlots->setSlot( $slot );
  115. $this->resetAggregateValues();
  116. }
  117. /**
  118. * "Inherits" the given slot's content.
  119. *
  120. * If a slot with the same role is already present in the revision, it is replaced.
  121. *
  122. * @note This may cause the slot meta-data for the revision to be lazy-loaded.
  123. *
  124. * @param SlotRecord $parentSlot
  125. */
  126. public function inheritSlot( SlotRecord $parentSlot ) {
  127. $slot = SlotRecord::newInherited( $parentSlot );
  128. $this->setSlot( $slot );
  129. }
  130. /**
  131. * Sets the content for the slot with the given role.
  132. *
  133. * If a slot with the same role is already present in the revision, it is replaced.
  134. * Calling code that has access to a SlotRecord can use inheritSlot() instead.
  135. *
  136. * @note This may cause the slot meta-data for the revision to be lazy-loaded.
  137. *
  138. * @note Calling this method will cause the revision size and hash to be re-calculated upon
  139. * the next call to getSize() and getSha1(), respectively.
  140. *
  141. * @param string $role
  142. * @param Content $content
  143. */
  144. public function setContent( $role, Content $content ) {
  145. $this->mSlots->setContent( $role, $content );
  146. $this->resetAggregateValues();
  147. }
  148. /**
  149. * Removes the slot with the given role from this revision.
  150. * This effectively ends the "stream" with that role on the revision's page.
  151. * Future revisions will no longer inherit this slot, unless it is added back explicitly.
  152. *
  153. * @note This may cause the slot meta-data for the revision to be lazy-loaded.
  154. *
  155. * @note Calling this method will cause the revision size and hash to be re-calculated upon
  156. * the next call to getSize() and getSha1(), respectively.
  157. *
  158. * @param string $role
  159. */
  160. public function removeSlot( $role ) {
  161. $this->mSlots->removeSlot( $role );
  162. $this->resetAggregateValues();
  163. }
  164. /**
  165. * @param CommentStoreComment $comment
  166. */
  167. public function setComment( CommentStoreComment $comment ) {
  168. $this->mComment = $comment;
  169. }
  170. /**
  171. * Set revision hash, for optimization. Prevents getSha1() from re-calculating the hash.
  172. *
  173. * @note This should only be used if the calling code is sure that the given hash is correct
  174. * for the revision's content, and there is no chance of the content being manipulated
  175. * later. When in doubt, this method should not be called.
  176. *
  177. * @param string $sha1 SHA1 hash as a base36 string.
  178. */
  179. public function setSha1( $sha1 ) {
  180. Assert::parameterType( 'string', $sha1, '$sha1' );
  181. $this->mSha1 = $sha1;
  182. }
  183. /**
  184. * Set nominal revision size, for optimization. Prevents getSize() from re-calculating the size.
  185. *
  186. * @note This should only be used if the calling code is sure that the given size is correct
  187. * for the revision's content, and there is no chance of the content being manipulated
  188. * later. When in doubt, this method should not be called.
  189. *
  190. * @param int $size nominal size in bogo-bytes
  191. */
  192. public function setSize( $size ) {
  193. Assert::parameterType( 'integer', $size, '$size' );
  194. $this->mSize = $size;
  195. }
  196. /**
  197. * @param int $visibility
  198. */
  199. public function setVisibility( $visibility ) {
  200. Assert::parameterType( 'integer', $visibility, '$visibility' );
  201. $this->mDeleted = $visibility;
  202. }
  203. /**
  204. * @param string $timestamp A timestamp understood by wfTimestamp
  205. */
  206. public function setTimestamp( $timestamp ) {
  207. Assert::parameterType( 'string', $timestamp, '$timestamp' );
  208. $this->mTimestamp = wfTimestamp( TS_MW, $timestamp );
  209. }
  210. /**
  211. * @param bool $minorEdit
  212. */
  213. public function setMinorEdit( $minorEdit ) {
  214. Assert::parameterType( 'boolean', $minorEdit, '$minorEdit' );
  215. $this->mMinorEdit = $minorEdit;
  216. }
  217. /**
  218. * Set the revision ID.
  219. *
  220. * MCR migration note: this replaces Revision::setId()
  221. *
  222. * @warning Use this with care, especially when preparing a revision for insertion
  223. * into the database! The revision ID should only be fixed in special cases
  224. * like preserving the original ID when restoring a revision.
  225. *
  226. * @param int $id
  227. */
  228. public function setId( $id ) {
  229. Assert::parameterType( 'integer', $id, '$id' );
  230. $this->mId = $id;
  231. }
  232. /**
  233. * Sets the user identity associated with the revision
  234. *
  235. * @param UserIdentity $user
  236. */
  237. public function setUser( UserIdentity $user ) {
  238. $this->mUser = $user;
  239. }
  240. /**
  241. * @param int $pageId
  242. */
  243. public function setPageId( $pageId ) {
  244. Assert::parameterType( 'integer', $pageId, '$pageId' );
  245. if ( $this->mTitle->exists() && $pageId !== $this->mTitle->getArticleID() ) {
  246. throw new InvalidArgumentException(
  247. 'The given Title does not belong to page ID ' . $this->mPageId
  248. );
  249. }
  250. $this->mPageId = $pageId;
  251. }
  252. /**
  253. * Returns the nominal size of this revision.
  254. *
  255. * MCR migration note: this replaces Revision::getSize
  256. *
  257. * @return int The nominal size, may be computed on the fly if not yet known.
  258. */
  259. public function getSize() {
  260. // If not known, re-calculate and remember. Will be reset when slots change.
  261. if ( $this->mSize === null ) {
  262. $this->mSize = $this->mSlots->computeSize();
  263. }
  264. return $this->mSize;
  265. }
  266. /**
  267. * Returns the base36 sha1 of this revision.
  268. *
  269. * MCR migration note: this replaces Revision::getSha1
  270. *
  271. * @return string The revision hash, may be computed on the fly if not yet known.
  272. */
  273. public function getSha1() {
  274. // If not known, re-calculate and remember. Will be reset when slots change.
  275. if ( $this->mSha1 === null ) {
  276. $this->mSha1 = $this->mSlots->computeSha1();
  277. }
  278. return $this->mSha1;
  279. }
  280. /**
  281. * Invalidate cached aggregate values such as hash and size.
  282. */
  283. private function resetAggregateValues() {
  284. $this->mSize = null;
  285. $this->mSha1 = null;
  286. }
  287. }