1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
|
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Storage;
use MediaWiki\Content\Content;
use MediaWiki\Revision\MutableRevisionSlots;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionSlots;
use MediaWiki\Revision\SlotRecord;
/**
* Value object representing a modification of revision slots.
*
* @since 1.32
*/
class RevisionSlotsUpdate {
/**
* @var SlotRecord[] modified slots, using the slot role as the key.
*/
private $modifiedSlots = [];
/**
* @var bool[] removed roles, stored in the keys of the array.
*/
private $removedRoles = [];
/**
* Constructs a RevisionSlotsUpdate representing the update that turned $parentSlots
* into $newSlots. If $parentSlots is not given, $newSlots is assumed to come from a
* page's first revision.
*
* @param RevisionSlots $newSlots
* @param RevisionSlots|null $parentSlots
*
* @return RevisionSlotsUpdate
*/
public static function newFromRevisionSlots(
RevisionSlots $newSlots,
?RevisionSlots $parentSlots = null
) {
$modified = $newSlots->getSlots();
$removed = [];
if ( $parentSlots ) {
foreach ( $parentSlots->getSlots() as $role => $slot ) {
if ( !isset( $modified[$role] ) ) {
$removed[] = $role;
} elseif ( $slot->hasSameContent( $modified[$role] ) ) {
// Unset slots that had the same content in the parent revision from $modified.
unset( $modified[$role] );
}
}
}
return new RevisionSlotsUpdate( $modified, $removed );
}
/**
* Constructs a RevisionSlotsUpdate representing the update of $parentSlots
* when changing $newContent. If a slot has the same content in $newContent
* as in $parentSlots, that slot is considered inherited and thus omitted from
* the resulting RevisionSlotsUpdate.
*
* In contrast to newFromRevisionSlots(), slots in $parentSlots that are not present
* in $newContent are not considered removed. They are instead assumed to be inherited.
*
* @param Content[] $newContent The new content, using slot roles as array keys.
* @param RevisionSlots|null $parentSlots
*
* @return RevisionSlotsUpdate
*/
public static function newFromContent( array $newContent, ?RevisionSlots $parentSlots = null ) {
$modified = [];
foreach ( $newContent as $role => $content ) {
$slot = SlotRecord::newUnsaved( $role, $content );
if ( $parentSlots
&& $parentSlots->hasSlot( $role )
&& $slot->hasSameContent( $parentSlots->getSlot( $role ) )
) {
// Skip slots that had the same content in the parent revision from $modified.
continue;
}
$modified[$role] = $slot;
}
return new RevisionSlotsUpdate( $modified );
}
/**
* @param SlotRecord[] $modifiedSlots
* @param string[] $removedRoles
*/
public function __construct( array $modifiedSlots = [], array $removedRoles = [] ) {
foreach ( $modifiedSlots as $slot ) {
$this->modifySlot( $slot );
}
foreach ( $removedRoles as $role ) {
$this->removeSlot( $role );
}
}
/**
* Returns a list of modified slot roles, that is, roles modified by calling modifySlot(),
* and not later removed by calling removeSlot().
*
* Note that slots in modified roles may still be inherited slots. This is for instance
* the case when the RevisionSlotsUpdate objects represents some kind of rollback
* operation, in which slots that existed in an earlier revision are restored in
* a new revision.
*
* @return string[]
*/
public function getModifiedRoles() {
return array_keys( $this->modifiedSlots );
}
/**
* Returns a list of removed slot roles, that is, roles removed by calling removeSlot(),
* and not later re-introduced by calling modifySlot().
*
* @return string[]
*/
public function getRemovedRoles() {
return array_keys( $this->removedRoles );
}
/**
* Returns a list of all slot roles that modified or removed.
*
* @return string[]
*/
public function getTouchedRoles() {
return array_merge( $this->getModifiedRoles(), $this->getRemovedRoles() );
}
/**
* Sets the given slot to be modified.
* If a slot with the same role is already present, it is replaced.
*
* The roles used with modifySlot() will be returned from getModifiedRoles(),
* unless overwritten with removeSlot().
*
* @param SlotRecord $slot
*/
public function modifySlot( SlotRecord $slot ) {
$role = $slot->getRole();
// XXX: We should perhaps require this to be an unsaved slot!
unset( $this->removedRoles[$role] );
$this->modifiedSlots[$role] = $slot;
}
/**
* Sets the content for the slot with the given role to be modified.
* If a slot with the same role is already present, it is replaced.
*
* @param string $role
* @param Content $content
*/
public function modifyContent( $role, Content $content ) {
$slot = SlotRecord::newUnsaved( $role, $content );
$this->modifySlot( $slot );
}
/**
* Remove the slot for the given role, discontinue the corresponding stream.
*
* The roles used with removeSlot() will be returned from getRemovedSlots(),
* unless overwritten with modifySlot().
*
* @param string $role
*/
public function removeSlot( $role ) {
unset( $this->modifiedSlots[$role] );
$this->removedRoles[$role] = true;
}
/**
* Returns the SlotRecord associated with the given role, if the slot with that role
* was modified (and not again removed).
*
* @note If the SlotRecord returned by this method returns a non-inherited slot,
* the content of that slot may or may not already have PST applied. Methods
* that take a RevisionSlotsUpdate as a parameter should specify whether they
* expect PST to already have been applied to all slots. Inherited slots
* should never have PST applied again.
*
* @param string $role The role name of the desired slot
*
* @throws RevisionAccessException if the slot does not exist or was removed.
* @return SlotRecord
*/
public function getModifiedSlot( $role ) {
if ( isset( $this->modifiedSlots[$role] ) ) {
return $this->modifiedSlots[$role];
} else {
throw new RevisionAccessException(
'No such slot: {role}',
[ 'role' => $role ]
);
}
}
/**
* Returns whether getModifiedSlot() will return a SlotRecord for the given role.
*
* Will return true for the role names returned by getModifiedRoles(), false otherwise.
*
* @param string $role The role name of the desired slot
*
* @return bool
*/
public function isModifiedSlot( $role ) {
return isset( $this->modifiedSlots[$role] );
}
/**
* Returns whether the given role is to be removed from the page.
*
* Will return true for the role names returned by getRemovedRoles(), false otherwise.
*
* @param string $role The role name of the desired slot
*
* @return bool
*/
public function isRemovedSlot( $role ) {
return isset( $this->removedRoles[$role] );
}
/**
* Returns true if $other represents the same update - that is,
* if all methods defined by RevisionSlotsUpdate when called on $this or $other
* will yield the same result when called with the same parameters.
*
* SlotRecords for the same role are compared based on their model and content.
*
* @param RevisionSlotsUpdate $other
* @return bool
*/
public function hasSameUpdates( RevisionSlotsUpdate $other ) {
// NOTE: use != not !==, since the order of entries is not significant!
if ( $this->getModifiedRoles() != $other->getModifiedRoles() ) {
return false;
}
if ( $this->getRemovedRoles() != $other->getRemovedRoles() ) {
return false;
}
foreach ( $this->getModifiedRoles() as $role ) {
$s = $this->getModifiedSlot( $role );
$t = $other->getModifiedSlot( $role );
if ( !$s->hasSameContent( $t ) ) {
return false;
}
}
return true;
}
/**
* Applies this update to the given MutableRevisionSlots, setting all modified slots,
* and removing all removed roles.
*
* @param MutableRevisionSlots $slots
*/
public function apply( MutableRevisionSlots $slots ) {
foreach ( $this->getModifiedRoles() as $role ) {
$slots->setSlot( $this->getModifiedSlot( $role ) );
}
foreach ( $this->getRemovedRoles() as $role ) {
$slots->removeSlot( $role );
}
}
}
|