SWLChangeSet   F
last analyzed

Complexity

Total Complexity 94

Size/Duplication

Total Lines 740
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 94
lcom 2
cbo 8
dl 0
loc 740
ccs 0
cts 375
cp 0
rs 1.86
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A newFromDBResult() 0 41 2
A newFromArray() 0 32 5
F newFromSemanticData() 0 83 20
A findSingleDirectionChanges() 0 18 5
A __construct() 0 12 4
A hasUserDefinedProperties() 0 11 3
A hasChanges() 0 5 3
A getInsertions() 0 3 1
A getDeletions() 0 3 1
A getChanges() 0 3 1
A getSubject() 0 3 1
A addChange() 0 13 4
A addInsertion() 0 3 1
A addDeletion() 0 3 1
A getAllProperties() 0 7 1
A removeChangesForProperty() 0 5 1
A getAllPropertyChanges() 0 17 4
A toArray() 0 32 5
C writeToStore() 0 93 10
A getTitle() 0 7 2
A getEdit() 0 3 1
A setEdit() 0 3 1
A hasInsertion() 0 12 3
A hasDeletion() 0 12 3
A hasChange() 0 12 3
B mergeInChangeSet() 0 21 8

How to fix   Complexity   

Complex Class

Complex classes like SWLChangeSet often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SWLChangeSet, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * 
5
 * 
6
 * @since 0.1
7
 * 
8
 * @file SWL_ChangeSet.php
9
 * @ingroup SemanticWatchlist
10
 * 
11
 * @licence GNU GPL v3 or later
12
 * @author Jeroen De Dauw < [email protected] >
13
 */
14
class SWLChangeSet {
15
	
16
	/**
17
	 * The subject the changes apply to.
18
	 * 
19
	 * @var SMWDIWikiPage
20
	 */
21
	private $subject;
22
	
23
	/**
24
	 * Object holding semantic data that got inserted.
25
	 * 
26
	 * @var SMWSemanticData
27
	 */
28
	private $insertions;
29
	
30
	/**
31
	 * Object holding semantic data that got deleted.
32
	 * 
33
	 * @var SMWSemanticData
34
	 */	
35
	private $deletions;
36
	
37
	/**
38
	 * List of all changes(, not including insertions and deletions).
39
	 * 
40
	 * @var SWLPropertyChanges
41
	 */
42
	private $changes;
43
	
44
	/**
45
	 * DB ID of the change set (swl_sets.set_id).
46
	 * 
47
	 * @var integer
48
	 */
49
	private $id;
50
	
51
	/**
52
	 * The title of the page the changeset holds changes for.
53
	 * The title will be constructed from the subject of the SMWChangeSet
54
	 * the first time getTitle is called, so it should be accessed via this
55
	 * method.
56
	 * 
57
	 * @var Title or false
58
	 */
59
	private $title = false;
60
	
61
	/**
62
	 * The edit this set of changes belongs to.
63
	 * 
64
	 * @var SWLEdit
65
	 */
66
	private $edit;
67
	
68
	/**
69
	 * Creates and returns a new SWLChangeSet instance from a database result
70
	 * obtained by doing a select on swl_sets. 
71
	 * 
72
	 * @since 0.1
73
	 * 
74
	 * @param $set
75
	 * 
76
	 * @return SWLChangeSet
77
	 */
78
	public static function newFromDBResult( $set ) {
79
		$changeSet = new SWLChangeSet(
80
			SMWDIWikiPage::newFromTitle( Title::newFromID( $set->edit_page_id ) ),
0 ignored issues
show
Compatibility introduced by
\SMWDIWikiPage::newFromT...ID($set->edit_page_id)) of type object<SMW\DIWikiPage> is not a sub-type of object<SMWDIWikiPage>. It seems like you assume a child class of the class SMW\DIWikiPage to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
81
			null,
82
			null,
83
			null,
84
			$set->spe_set_id,
85
			new SWLEdit(
86
				$set->edit_page_id,
87
				$set->edit_user_name,
88
				$set->edit_time,
89
				$set->edit_id
90
			)
91
		);
92
		
93
		$dbr = wfGetDb( DB_REPLICA );
94
		
95
		$changes = $dbr->select(
96
			'swl_changes',
97
			array(
98
				'change_id',
99
				'change_property',
100
				'change_old_value',
101
				'change_new_value'
102
			),
103
			array(
104
				'change_set_id' => $set->spe_set_id
105
			)
106
		);
107
		
108
		foreach ( $changes as $change ) {
109
			$property = SMWDIProperty::doUnserialize( $change->change_property, '__pro' );
0 ignored issues
show
Unused Code introduced by
The call to SMWDIProperty::doUnserialize() has too many arguments starting with '__pro'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
110
			
111
			$changeSet->addChange(
112
				$property,
113
				SWLPropertyChange::newFromSerialization( $property, $change->change_old_value, $change->change_new_value )
114
			);
115
		}	
116
		
117
		return $changeSet;
118
	}
119
	
120
	/**
121
	 * Creates and returns a new SWLChangeSet instance from a database result
122
	 * obtained by doing a select on swl_sets. 
123
	 * 
124
	 * @since 0.1
125
	 * 
126
	 * @param array $changeSetArray
127
	 * 
128
	 * @return SWLChangeSet
129
	 */
130
	public static function newFromArray( array $changeSetArray ) {
131
		$changeSet = new SWLChangeSet(
132
			SMWDIWikiPage::newFromTitle( Title::newFromID( $changeSetArray['page_id'] ) ),
0 ignored issues
show
Compatibility introduced by
\SMWDIWikiPage::newFromT...geSetArray['page_id'])) of type object<SMW\DIWikiPage> is not a sub-type of object<SMWDIWikiPage>. It seems like you assume a child class of the class SMW\DIWikiPage to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
133
			null,
134
			null,
135
			null,
136
			$changeSetArray['id'],
137
			new SWLEdit(
138
				$changeSetArray['page_id'],
139
				$changeSetArray['user_name'],
140
				$changeSetArray['time'],
141
				$changeSetArray['editid']
142
			)
143
		);
144
145
		foreach ( $changeSetArray['changes'] as $propName => $changes ) {
146
			$property = SMWDIProperty::doUnserialize( $propName, '__pro' );
0 ignored issues
show
Unused Code introduced by
The call to SMWDIProperty::doUnserialize() has too many arguments starting with '__pro'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
147
148
			foreach ( $changes as $change ) {
149
				$changeSet->addChange(
150
					$property,
151
					SWLPropertyChange::newFromSerialization(
152
						$property,
153
						array_key_exists( 'old', $change ) ? $change['old'] : null,
154
						array_key_exists( 'new', $change ) ? $change['new'] : null
155
					)
156
				);					
157
			}
158
		}		
159
160
		return $changeSet;
161
	}
162
	
163
	/**
164
	 * Creates and returns a new SMWChangeSet from 2 SMWSemanticData objects.
165
	 * 
166
	 * @param SMWSemanticData $old
167
	 * @param SMWSemanticData $new
168
	 * @param array $filterProperties Optional list of properties (string serializations) to filter on. Null for no filtering.
169
	 * 
170
	 * @return SMWChangeSet
171
	 */
172
	public static function newFromSemanticData( SMWSemanticData $old, SMWSemanticData $new, array $filterProperties = null ) {
173
		$subject = $old->getSubject();
174
		
175
		if ( $subject != $new->getSubject() ) {
176
			return new self( $subject );
0 ignored issues
show
Compatibility introduced by
$subject of type object<SMW\DIWikiPage> is not a sub-type of object<SMWDIWikiPage>. It seems like you assume a child class of the class SMW\DIWikiPage to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
177
		}
178
		
179
		$changes = new SWLPropertyChanges();
180
		$insertions = new SMWSemanticData( $subject );
181
		$deletions = new SMWSemanticData( $subject );
182
		
183
		$oldProperties = array();
184
		$newProperties = array();
185
		
186
		foreach ( $old->getProperties() as $property ) {
187
			if ( is_null( $filterProperties ) || in_array( $property->getLabel(), $filterProperties ) ) {
188
				$oldProperties[] = $property;
189
			}
190
		}
191
		
192
		foreach ( $new->getProperties() as $property ) {
193
			if ( is_null( $filterProperties ) || in_array( $property->getLabel(), $filterProperties ) ) {
194
				$newProperties[] = $property;
195
			}
196
		}
197
		
198
		// Find the deletions.
199
		self::findSingleDirectionChanges( $deletions, $oldProperties, $old, $newProperties, $filterProperties );
0 ignored issues
show
Unused Code introduced by
The call to SWLChangeSet::findSingleDirectionChanges() has too many arguments starting with $filterProperties.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
200
		
201
		// Find the insertions.
202
		self::findSingleDirectionChanges( $insertions, $newProperties, $new, $oldProperties, $filterProperties );
0 ignored issues
show
Unused Code introduced by
The call to SWLChangeSet::findSingleDirectionChanges() has too many arguments starting with $filterProperties.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
203
		
204
		foreach ( $oldProperties as $propertyKey => /* SMWDIProperty */ $diProperty ) {
205
			$oldDataItems = array();
206
			$newDataItems = array();
207
			
208
			// Populate the data item arrays using keys that are their hash, so matches can be found.
209
			// Note: this code assumes there are no duplicates.
210
			foreach ( $old->getPropertyValues( $diProperty ) as /* SMWDataItem */ $dataItem ) {
211
				$oldDataItems[$dataItem->getHash()] = $dataItem;
212
			}
213
			foreach ( $new->getPropertyValues( $diProperty ) as /* SMWDataItem */ $dataItem ) {
214
				$newDataItems[$dataItem->getHash()] = $dataItem;
215
			}			
216
			
217
			$foundMatches = array();
218
			
219
			// Find values that are both in the old and new version.
220
			foreach ( array_keys( $oldDataItems ) as $hash ) {
221
				if ( array_key_exists( $hash, $newDataItems ) ) {
222
					$foundMatches[] = $hash;
223
				}
224
			}
225
			
226
			// Remove the values occuring in both sets, so only changes remain.
227
			foreach ( $foundMatches as $foundMatch ) {
228
				unset( $oldDataItems[$foundMatch] );
229
				unset( $newDataItems[$foundMatch] );
230
			}
231
			
232
			// Find which group is biggest, so it's easy to loop over all values of the smallest.
233
			$oldIsBigger = count( $oldDataItems ) > count ( $newDataItems );
234
			$bigGroup = $oldIsBigger ? $oldDataItems : $newDataItems;
235
			$smallGroup = $oldIsBigger ? $newDataItems : $oldDataItems;
236
			
237
			// Add all one-to-one changes.
238
			while ( $dataItem = array_shift( $smallGroup ) ) {
239
				$changes->addPropertyObjectChange( $diProperty, new SWLPropertyChange( $dataItem, array_shift( $bigGroup ) ) );
240
			}
241
			
242
			// If the bigger group is not-equal to the smaller one, items will be left,
243
			// that are either insertions or deletions, depending on the group.
244
			if ( count( $bigGroup > 0 ) ) {
245
				$semanticData = $oldIsBigger ? $deletions : $insertions;
246
				
247
				foreach ( $bigGroup as /* SMWDataItem */ $dataItem ) {
248
					$semanticData->addPropertyObjectValue( $diProperty, $dataItem );
249
				}				
250
			}
251
		}
252
		
253
		return new self( $subject, $changes, $insertions, $deletions );
0 ignored issues
show
Compatibility introduced by
$subject of type object<SMW\DIWikiPage> is not a sub-type of object<SMWDIWikiPage>. It seems like you assume a child class of the class SMW\DIWikiPage to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
254
	}
255
	
256
	/**
257
	 * Finds the inserts or deletions and adds them to the passed SMWSemanticData object.
258
	 * These values will also be removed from the first list of properties and their values,
259
	 * so it can be used for one-to-one change finding later on.  
260
	 * 
261
	 * @param SMWSemanticData $changeSet
262
	 * @param array $oldProperties
263
	 * @param SMWSemanticData $oldData
264
	 * @param array $newProperties
265
	 */
266
	private static function findSingleDirectionChanges( SMWSemanticData &$changeSet,
267
		array &$oldProperties, SMWSemanticData $oldData, array $newProperties ) {
268
		
269
		$deletionKeys = array();
270
		
271
		foreach ( $oldProperties as $propertyKey => /* SMWDIProperty */ $diProperty ) {
272
			if ( !array_key_exists( $propertyKey, $newProperties ) ) {
273
				foreach ( $oldData->getPropertyValues( $diProperty ) as /* SMWDataItem */ $dataItem ) {
274
					$changeSet->addPropertyObjectValue( $diProperty, $dataItem );
275
				}
276
				$deletionKeys[] = $propertyKey;
277
			}
278
		}
279
		
280
		foreach ( $deletionKeys as $key ) {
281
			unset( $oldProperties[$propertyKey] );
282
		}
283
	}
284
	
285
	/**
286
	 * Create a new instance of a change set.
287
	 * 
288
	 * @param SMWDIWikiPage $subject
289
	 * @param SWLPropertyChanges $changes Can be null
290
	 * @param SMWSemanticData $insertions Can be null
291
	 * @param SMWSemanticData $deletions Can be null
292
	 * @param integer $id Can be null
293
	 * @param SWLEdit $edit Can be null
294
	 */
295
	public function __construct( SMWDIWikiPage $subject, /* SWLPropertyChanges */ $changes = null,
296
		/* SMWSemanticData */ $insertions = null, /* SMWSemanticData */ $deletions = null,
297
		$id = null, /* SWLEdit */ $edit = null ) {
298
	
299
		$this->subject = $subject;
300
		$this->changes = is_null( $changes ) ? new SWLPropertyChanges() : $changes;
301
		$this->insertions = is_null( $insertions ) ? new SMWSemanticData( $subject ): $insertions;
302
		$this->deletions = is_null( $deletions ) ? new SMWSemanticData( $subject ): $deletions;
303
		
304
		$this->id = $id;
305
		$this->edit = $edit;
306
	}
307
	
308
	/**
309
	 * Rteurns if the change set contains (changes for) user defined properties.
310
	 * 
311
	 * @since 0.1
312
	 * 
313
	 * @return boolean
314
	 */
315
	public function hasUserDefinedProperties() {
316
		$properties = array();
317
		
318
		foreach ( $this->getAllProperties() as /* SMWDIProperty */ $property ) {
319
			if ( $property->isUserDefined() ) {
320
				$properties[] = $property;
321
			}
322
		}
323
		
324
		return count( $properties ) != 0;
325
	}
326
	
327
	/**
328
	 * Returns whether the set contains any changes.
329
	 * 
330
	 * @since 0.1
331
	 * 
332
	 * @return boolean
333
	 */
334
	public function hasChanges() {
335
		return $this->changes->hasChanges()
336
			|| $this->insertions->hasVisibleProperties()
337
			|| $this->deletions->hasVisibleProperties();
338
	}
339
	
340
	/**
341
	 * Returns a SMWSemanticData object holding all inserted SMWDataItem objects.
342
	 * 
343
	 * @return SMWSemanticData
344
	 */
345
	public function getInsertions() {
346
		return $this->insertions;
347
	}
348
	
349
	/**
350
	 * Returns a SMWSemanticData object holding all deleted SMWDataItem objects.
351
	 * 
352
	 * @return SMWSemanticData
353
	 */
354
	public function getDeletions() {
355
		return $this->deletions;
356
	}
357
	
358
	/**
359
	 * Returns a SWLPropertyChanges object holding all SWLPropertyChange objects.
360
	 * 
361
	 * @return SWLPropertyChanges
362
	 */	
363
	public function getChanges() {
364
		return $this->changes;
365
	}
366
	
367
	/**
368
	 * Returns the subject these changes apply to.
369
	 * 
370
	 * @return SMWDIWikiPage
371
	 */
372
	public function getSubject() {
373
		return $this->subject;		
374
	}
375
	
376
	/**
377
	 * Adds a SWLPropertyChange to the set for the specified SMWDIProperty.
378
	 * 
379
	 * @since 0.1
380
	 * 
381
	 * @param SMWDIProperty $property
382
	 * @param SWLPropertyChange $change
383
	 */
384
	public function addChange( SMWDIProperty $property, SWLPropertyChange $change ) {
385
		switch ( $change->getType() ) {
386
			case SWLPropertyChange::TYPE_UPDATE:
387
				$this->changes->addPropertyObjectChange( $property, $change );
388
				break;
389
			case SWLPropertyChange::TYPE_INSERT:
390
				$this->addInsertion( $property, $change->getNewValue() );
391
				break;
392
			case SWLPropertyChange::TYPE_DELETE:
393
				$this->addDeletion(  $property, $change->getOldValue()  );
394
				break;
395
		}
396
	}
397
	
398
	/**
399
	 * Adds a SMWDataItem representing an insertion to the set for the specified SMWDIProperty.
400
	 * 
401
	 * @since 0.1
402
	 * 
403
	 * @param SMWDIProperty $property
404
	 * @param SMWDataItem $dataItem
405
	 */
406
	public function addInsertion( SMWDIProperty $property, SMWDataItem $dataItem ) {
407
		$this->insertions->addPropertyObjectValue( $property, $dataItem );
408
	}
409
	
410
	/**
411
	 * Adds a SMWDataItem representing a deletion to the set for the specified SMWDIProperty.
412
	 * 
413
	 * @since 0.1
414
	 * 
415
	 * @param SMWDIProperty $property
416
	 * @param SMWDataItem $dataItem
417
	 */
418
	public function addDeletion( SMWDIProperty $property, SMWDataItem $dataItem ) {
419
		$this->deletions->addPropertyObjectValue( $property, $dataItem );
420
	}
421
	
422
	/**
423
	 * Returns a list of all properties.
424
	 * 
425
	 * @return array of SMWDIProperty
426
	 */
427
	public function getAllProperties() {
428
		return array_merge(
429
			$this->getChanges()->getProperties(),
430
			$this->getInsertions()->getProperties(),
431
			$this->getDeletions()->getProperties()
432
		);
433
	}
434
	
435
	/**
436
	 * Removes all changes for a certian property.
437
	 * 
438
	 * @param SMWDIProperty $property
439
	 */
440
	public function removeChangesForProperty( SMWDIProperty $property ) {
441
		$this->getChanges()->removeChangesForProperty( $property );
442
		$this->getInsertions()->removeDataForProperty( $property );
0 ignored issues
show
Bug introduced by
The method removeDataForProperty() does not seem to exist on object<SMWSemanticData>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
443
		$this->getDeletions()->removeDataForProperty( $property );
0 ignored issues
show
Bug introduced by
The method removeDataForProperty() does not seem to exist on object<SMWSemanticData>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
444
	}
445
	
446
	/**
447
	 * Returns a list of ALL changes, including isertions and deletions.
448
	 * 
449
	 * @param SMWDIProperty $property
450
	 * 
451
	 * @return array of SWLPropertyChange
452
	 */
453
	public function getAllPropertyChanges( SMWDIProperty $property ) {
454
		$changes = array();
455
		
456
		foreach ( $this->changes->getPropertyChanges( $property ) as /* SWLPropertyChange */ $change ) {
457
			$changes[] = $change;
458
		}
459
		
460
		foreach ( $this->insertions->getPropertyValues( $property ) as /* SMWDataItem */ $dataItem ) {
461
			$changes[] = new SWLPropertyChange( null, $dataItem );
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SMWDataItem>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
462
		}
463
464
		foreach ( $this->deletions->getPropertyValues( $property ) as /* SMWDataItem */ $dataItem ) {
465
			$changes[] = new SWLPropertyChange( $dataItem, null );
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SMWDataItem>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
466
		}			
467
		
468
		return $changes;
469
	}
470
	
471
	/**
472
	 * Serializes the object as an associative array, which can be passed
473
	 * to newFromArray to create a new instance.
474
	 * 
475
	 * @since 0.1
476
	 * 
477
	 * @return array
478
	 */
479
	public function toArray() {
480
 		$changeSet = array(
481
			'id' => $this->id,
482
			'user_name' => $this->edit->getUserName(),
483
			'page_id' => $this->edit->getPageID(),
484
			'time' => $this->edit->getTime(),
485
 			'editid' => $this->edit->getId(),
486
 			'changes' => array()
487
		);
488
		
489
		foreach ( $this->getAllProperties() as /* SMWDIProperty */ $property ) {
490
			$propChanges = array();
491
			
492
			foreach ( $this->getAllPropertyChanges( $property ) as /* SWLPropertyChange */ $change ) {
493
				$propChange = array();
494
				
495
				if ( is_object( $change->getOldValue() ) ) {
496
					$propChange['old'] = $change->getOldValue()->getSerialization();
497
				}
498
				
499
				if ( is_object( $change->getNewValue() ) ) {
500
					$propChange['new'] = $change->getNewValue()->getSerialization();
501
				}
502
503
				$propChanges[] = $propChange;
504
			}
505
			
506
			$changeSet['changes'][$property->getSerialization()] = $propChanges;
507
		}
508
		
509
		return $changeSet;
510
	}
511
	
512
	/**
513
	 * Save the change set to the database.
514
	 * 
515
	 * @since 0.1
516
	 * 
517
	 * @param array of SWLGroup
518
	 * @param integer $editId
519
	 * 
520
	 * @return integer ID of the inserted row (0 if nothing was inserted).
521
	 */
522
	public function writeToStore( array $groupsToAssociate, $editId ) {
523
		if ( !$this->hasUserDefinedProperties() ) {
524
			return 0;
525
		}
526
		
527
		wfRunHooks( 'SWLBeforeChangeSetInsert', array( &$this, &$groupsToAssociate, &$editId ) );
528
		
529
		$dbw = wfGetDB( DB_MASTER );
530
		
531
		$dbw->insert(
532
			'swl_sets',
533
			array( 'set_id' => null )
534
		);
535
		
536
		$id = $dbw->insertId();
537
		
538
		$dbw->insert(
539
			'swl_sets_per_edit',
540
			array(
541
				'spe_set_id' => $id,
542
				'spe_edit_id' => $editId
543
			)
544
		);
545
		
546
		$changes = array();
547
		
548
		foreach ( $this->getAllProperties() as /* SMWDIProperty */ $property ) {
549
			if ( $property->isUserDefined() ) {
550
				$propSerialization = $property->getSerialization();
551
			
552
				foreach ( $this->getChanges()->getPropertyChanges( $property ) as /* SWLPropertyChange */ $change ) {
553
					$changes[] = array(
554
						'property' => $propSerialization,
555
						'old' => $change->getOldValue()->getSerialization(),
556
						'new' => $change->getNewValue()->getSerialization()
557
					);
558
				}
559
560
				foreach ( $this->getInsertions()->getPropertyValues( $property ) as /* SMWDataItem */ $dataItem ) {
561
					$changes[] = array(
562
						'property' => $propSerialization,
563
						'old' => null,
564
						'new' => $dataItem->getSerialization()
565
					);
566
				}
567
568
				foreach ( $this->getDeletions()->getPropertyValues( $property ) as /* SMWDataItem */ $dataItem ) {
569
					$changes[] = array(
570
						'property' => $propSerialization,
571
						'old' => $dataItem->getSerialization(),
572
						'new' => null
573
					);
574
				}				
575
			}
576
		}
577
		
578
		$dbw->begin( __METHOD__ );
579
		
580
		foreach ( $changes as $change ) {
581
			if ( $change['property'] == '' ) {
582
				// When removing the last value for a property of a page,
583
				// for some reason it gets inserted for a property without
584
				// name, so skip that. Better to fix higher up though.
585
				continue;
586
			}
587
			
588
			$dbw->insert(
589
				'swl_changes',
590
				array(
591
					'change_set_id' => $id,
592
					'change_property' => $change['property'],
593
					'change_old_value' => $change['old'],
594
					'change_new_value' => $change['new']
595
				)
596
			);			
597
		}
598
		
599
		foreach ( $groupsToAssociate as /* SWLGroup */ $group ) {
600
			$dbw->insert(
601
				'swl_sets_per_group',
602
				array(
603
					'spg_group_id' => $group->getId(),
604
					'spg_set_id' => $id
605
				)
606
			);
607
		}
608
		
609
		$dbw->commit( __METHOD__ );
610
		
611
		wfRunHooks( 'SWLAfterChangeSetInsert', array( &$this, $groupsToAssociate, $editId ) );
612
		
613
		return $id;
614
	}
615
	
616
	/**
617
	 * Gets the title of the page these changes belong to.
618
	 * 
619
	 * @since 0.1
620
	 * 
621
	 * @return Title
622
	 */
623
	public function getTitle() {
624
		if ( $this->title === false ) {
625
			$this->title = Title::makeTitle( $this->getSubject()->getNamespace(), $this->getSubject()->getDBkey() );
626
		}
627
		
628
		return $this->title;
629
	}
630
	
631
	/**
632
	 * Gets the edit this set of changes belong to.
633
	 * 
634
	 * @since 0.1
635
	 * 
636
	 * @return SWLEdit
637
	 */
638
	public function getEdit() {
639
		return $this->edit;
640
	}
641
	
642
	/**
643
	 * Sets the edit this set of changes belong to.
644
	 * 
645
	 * @since 0.1
646
	 * 
647
	 * @param SWLEdit $edit
648
	 */
649
	public function setEdit( SWLEdit $edit ) {
650
		$this->edit = $edit;
651
	}
652
	
653
	/**
654
	 * Returns if a certain insertion is present in the set of changes.
655
	 * 
656
	 * @since 0.1
657
	 * 
658
	 * @param SMWDIProperty $property
659
	 * @param string $value
660
	 * 
661
	 * @return boolean
662
	 */
663
	public function hasInsertion( SMWDIProperty $property, $value ) {
664
		$has = false;
665
		
666
		foreach ( $this->insertions->getPropertyValues( $property ) as /* SMWDataItem */ $insertion ) {
667
			if ( $insertion->getSerialization() == $value ) {
668
				$has = true;
669
				break;
670
			}
671
		}
672
		
673
		return $has;
674
	}
675
	
676
	/**
677
	 * Returns if a certain insertion is present in the set of changes.
678
	 * 
679
	 * @since 0.1
680
	 * 
681
	 * @param SMWDIProperty $property
682
	 * @param string $value
683
	 * 
684
	 * @return boolean
685
	 */
686
	public function hasDeletion( SMWDIProperty $property, $value ) {
687
		$has = false;
688
		
689
		foreach ( $this->deletions->getPropertyValues( $property ) as /* SMWDataItem */ $deletion ) {
690
			if ( $deletion->getSerialization() == $value ) {
691
				$has = true;
692
				break;
693
			}
694
		}
695
		
696
		return $has;		
697
	}
698
	
699
	/**
700
	 * Returns if a certain change is present in the set of changes.
701
	 * 
702
	 * @since 0.1
703
	 * 
704
	 * @param SMWDIProperty $property
705
	 * @param SWLPropertyChange $change
706
	 * 
707
	 * @return boolean
708
	 */
709
	public function hasChange( SMWDIProperty $property, SWLPropertyChange $change ) {
710
		$has = false;
711
		
712
		foreach ( $this->changes->getPropertyChanges( $property ) as /* SWLPropertyChange */ $propChange ) {
713
			if ( $propChange->getSerialization() == $change->getSerialization() ) {
714
				$has = true;
715
				break;
716
			}
717
		}
718
		
719
		return $has;			
720
	}
721
	
722
	/**
723
	 * Merges in the changes of another change set.
724
	 * Duplicate changes are detected and only kept as a single change.
725
	 * This is usefull for merging sets with (possibly overlapping) changes belonging to a single edit.
726
	 * 
727
	 * @since 0.1
728
	 * 
729
	 * @param SWLChangeSet $set
730
	 */
731
	public function mergeInChangeSet( SWLChangeSet $set ) {
732
		foreach ( $set->getAllProperties() as $property ) {
733
			foreach ( $set->getChanges()->getPropertyChanges( $property ) as /* SWLPropertyChange */ $change ) {
734
				if ( !$this->hasChange( $property, $change ) ) {
735
					$this->addChange( $property, $change );
736
				}
737
			}
738
			
739
			foreach ( $set->getInsertions()->getPropertyValues( $property ) as /* SMWDataItem */ $dataItem ) {
740
				if ( !$this->hasInsertion( $property, $dataItem ) ) {
741
					$this->addInsertion( $property, $dataItem );
742
				}
743
			}
744
	
745
			foreach ( $set->getDeletions()->getPropertyValues( $property ) as /* SMWDataItem */ $dataItem ) {
746
				if ( !$this->hasInsertion( $property, $dataItem ) ) {
747
					$this->addDeletion( $property, $dataItem );
748
				}
749
			}		
750
		}
751
	}
752
	
753
}
754