RevisionSlotsUpdate.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. /**
  3. * Value object representing a modification of revision slots.
  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 Content;
  24. use MediaWiki\Revision\MutableRevisionSlots;
  25. use MediaWiki\Revision\RevisionAccessException;
  26. use MediaWiki\Revision\RevisionSlots;
  27. use MediaWiki\Revision\SlotRecord;
  28. /**
  29. * Value object representing a modification of revision slots.
  30. *
  31. * @since 1.32
  32. */
  33. class RevisionSlotsUpdate {
  34. /**
  35. * @var SlotRecord[] modified slots, using the slot role as the key.
  36. */
  37. private $modifiedSlots = [];
  38. /**
  39. * @var bool[] removed roles, stored in the keys of the array.
  40. */
  41. private $removedRoles = [];
  42. /**
  43. * Constructs a RevisionSlotsUpdate representing the update that turned $parentSlots
  44. * into $newSlots. If $parentSlots is not given, $newSlots is assumed to come from a
  45. * page's first revision.
  46. *
  47. * @param RevisionSlots $newSlots
  48. * @param RevisionSlots|null $parentSlots
  49. *
  50. * @return RevisionSlotsUpdate
  51. */
  52. public static function newFromRevisionSlots(
  53. RevisionSlots $newSlots,
  54. RevisionSlots $parentSlots = null
  55. ) {
  56. $modified = $newSlots->getSlots();
  57. $removed = [];
  58. if ( $parentSlots ) {
  59. foreach ( $parentSlots->getSlots() as $role => $slot ) {
  60. if ( !isset( $modified[$role] ) ) {
  61. $removed[] = $role;
  62. } elseif ( $slot->hasSameContent( $modified[$role] ) ) {
  63. // Unset slots that had the same content in the parent revision from $modified.
  64. unset( $modified[$role] );
  65. }
  66. }
  67. }
  68. return new RevisionSlotsUpdate( $modified, $removed );
  69. }
  70. /**
  71. * Constructs a RevisionSlotsUpdate representing the update of $parentSlots
  72. * when changing $newContent. If a slot has the same content in $newContent
  73. * as in $parentSlots, that slot is considered inherited and thus omitted from
  74. * the resulting RevisionSlotsUpdate.
  75. *
  76. * In contrast to newFromRevisionSlots(), slots in $parentSlots that are not present
  77. * in $newContent are not considered removed. They are instead assumed to be inherited.
  78. *
  79. * @param Content[] $newContent The new content, using slot roles as array keys.
  80. *
  81. * @return RevisionSlotsUpdate
  82. */
  83. public static function newFromContent( array $newContent, RevisionSlots $parentSlots = null ) {
  84. $modified = [];
  85. foreach ( $newContent as $role => $content ) {
  86. $slot = SlotRecord::newUnsaved( $role, $content );
  87. if ( $parentSlots
  88. && $parentSlots->hasSlot( $role )
  89. && $slot->hasSameContent( $parentSlots->getSlot( $role ) )
  90. ) {
  91. // Skip slots that had the same content in the parent revision from $modified.
  92. continue;
  93. }
  94. $modified[$role] = $slot;
  95. }
  96. return new RevisionSlotsUpdate( $modified );
  97. }
  98. /**
  99. * @param SlotRecord[] $modifiedSlots
  100. * @param string[] $removedRoles
  101. */
  102. public function __construct( array $modifiedSlots = [], array $removedRoles = [] ) {
  103. foreach ( $modifiedSlots as $slot ) {
  104. $this->modifySlot( $slot );
  105. }
  106. foreach ( $removedRoles as $role ) {
  107. $this->removeSlot( $role );
  108. }
  109. }
  110. /**
  111. * Returns a list of modified slot roles, that is, roles modified by calling modifySlot(),
  112. * and not later removed by calling removeSlot().
  113. *
  114. * Note that slots in modified roles may still be inherited slots. This is for instance
  115. * the case when the RevisionSlotsUpdate objects represents some kind of rollback
  116. * operation, in which slots that existed in an earlier revision are restored in
  117. * a new revision.
  118. *
  119. * @return string[]
  120. */
  121. public function getModifiedRoles() {
  122. return array_keys( $this->modifiedSlots );
  123. }
  124. /**
  125. * Returns a list of removed slot roles, that is, roles removed by calling removeSlot(),
  126. * and not later re-introduced by calling modifySlot().
  127. *
  128. * @return string[]
  129. */
  130. public function getRemovedRoles() {
  131. return array_keys( $this->removedRoles );
  132. }
  133. /**
  134. * Returns a list of all slot roles that modified or removed.
  135. *
  136. * @return string[]
  137. */
  138. public function getTouchedRoles() {
  139. return array_merge( $this->getModifiedRoles(), $this->getRemovedRoles() );
  140. }
  141. /**
  142. * Sets the given slot to be modified.
  143. * If a slot with the same role is already present, it is replaced.
  144. *
  145. * The roles used with modifySlot() will be returned from getModifiedRoles(),
  146. * unless overwritten with removeSlot().
  147. *
  148. * @param SlotRecord $slot
  149. */
  150. public function modifySlot( SlotRecord $slot ) {
  151. $role = $slot->getRole();
  152. // XXX: We should perhaps require this to be an unsaved slot!
  153. unset( $this->removedRoles[$role] );
  154. $this->modifiedSlots[$role] = $slot;
  155. }
  156. /**
  157. * Sets the content for the slot with the given role to be modified.
  158. * If a slot with the same role is already present, it is replaced.
  159. *
  160. * @param string $role
  161. * @param Content $content
  162. */
  163. public function modifyContent( $role, Content $content ) {
  164. $slot = SlotRecord::newUnsaved( $role, $content );
  165. $this->modifySlot( $slot );
  166. }
  167. /**
  168. * Remove the slot for the given role, discontinue the corresponding stream.
  169. *
  170. * The roles used with removeSlot() will be returned from getRemovedSlots(),
  171. * unless overwritten with modifySlot().
  172. *
  173. * @param string $role
  174. */
  175. public function removeSlot( $role ) {
  176. unset( $this->modifiedSlots[$role] );
  177. $this->removedRoles[$role] = true;
  178. }
  179. /**
  180. * Returns the SlotRecord associated with the given role, if the slot with that role
  181. * was modified (and not again removed).
  182. *
  183. * @note If the SlotRecord returned by this method returns a non-inherited slot,
  184. * the content of that slot may or may not already have PST applied. Methods
  185. * that take a RevisionSlotsUpdate as a parameter should specify whether they
  186. * expect PST to already have been applied to all slots. Inherited slots
  187. * should never have PST applied again.
  188. *
  189. * @param string $role The role name of the desired slot
  190. *
  191. * @throws RevisionAccessException if the slot does not exist or was removed.
  192. * @return SlotRecord
  193. */
  194. public function getModifiedSlot( $role ) {
  195. if ( isset( $this->modifiedSlots[$role] ) ) {
  196. return $this->modifiedSlots[$role];
  197. } else {
  198. throw new RevisionAccessException( 'No such slot: ' . $role );
  199. }
  200. }
  201. /**
  202. * Returns whether getModifiedSlot() will return a SlotRecord for the given role.
  203. *
  204. * Will return true for the role names returned by getModifiedRoles(), false otherwise.
  205. *
  206. * @param string $role The role name of the desired slot
  207. *
  208. * @return bool
  209. */
  210. public function isModifiedSlot( $role ) {
  211. return isset( $this->modifiedSlots[$role] );
  212. }
  213. /**
  214. * Returns whether the given role is to be removed from the page.
  215. *
  216. * Will return true for the role names returned by getRemovedRoles(), false otherwise.
  217. *
  218. * @param string $role The role name of the desired slot
  219. *
  220. * @return bool
  221. */
  222. public function isRemovedSlot( $role ) {
  223. return isset( $this->removedRoles[$role] );
  224. }
  225. /**
  226. * Returns true if $other represents the same update - that is,
  227. * if all methods defined by RevisionSlotsUpdate when called on $this or $other
  228. * will yield the same result when called with the same parameters.
  229. *
  230. * SlotRecords for the same role are compared based on their model and content.
  231. *
  232. * @param RevisionSlotsUpdate $other
  233. * @return bool
  234. */
  235. public function hasSameUpdates( RevisionSlotsUpdate $other ) {
  236. // NOTE: use != not !==, since the order of entries is not significant!
  237. if ( $this->getModifiedRoles() != $other->getModifiedRoles() ) {
  238. return false;
  239. }
  240. if ( $this->getRemovedRoles() != $other->getRemovedRoles() ) {
  241. return false;
  242. }
  243. foreach ( $this->getModifiedRoles() as $role ) {
  244. $s = $this->getModifiedSlot( $role );
  245. $t = $other->getModifiedSlot( $role );
  246. if ( !$s->hasSameContent( $t ) ) {
  247. return false;
  248. }
  249. }
  250. return true;
  251. }
  252. /**
  253. * Applies this update to the given MutableRevisionSlots, setting all modified slots,
  254. * and removing all removed roles.
  255. *
  256. * @param MutableRevisionSlots $slots
  257. */
  258. public function apply( MutableRevisionSlots $slots ) {
  259. foreach ( $this->getModifiedRoles() as $role ) {
  260. $slots->setSlot( $this->getModifiedSlot( $role ) );
  261. }
  262. foreach ( $this->getRemovedRoles() as $role ) {
  263. $slots->removeSlot( $role );
  264. }
  265. }
  266. }