Completed
Pull Request — master (#5157)
by Damian
11:28
created

VersionedOwnershipTest::testRecursiveArchive()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 29
rs 8.8571
cc 1
eloc 16
nc 1
nop 0
1
<?php
2
3
/**
4
 * Tests ownership API of versioned DataObjects
5
 */
6
class VersionedOwnershipTest extends SapphireTest {
7
8
	protected $extraDataObjects = array(
9
		'VersionedOwnershipTest_Object',
10
		'VersionedOwnershipTest_Subclass',
11
		'VersionedOwnershipTest_Related',
12
		'VersionedOwnershipTest_Attachment',
13
		'VersionedOwnershipTest_RelatedMany',
14
	);
15
16
	protected static $fixture_file = 'VersionedOwnershipTest.yml';
17
18
	public function setUp()
19
	{
20
		parent::setUp();
21
22
		Versioned::set_stage(Versioned::DRAFT);
23
24
		// Automatically publish any object named *_published
25
		foreach($this->getFixtureFactory()->getFixtures() as $class => $fixtures) {
26
			foreach($fixtures as $name => $id) {
27
				if(stripos($name, '_published') !== false) {
28
					/** @var Versioned|DataObject $object */
29
					$object = DataObject::get($class)->byID($id);
30
					$object->publish('Stage', 'Live');
0 ignored issues
show
Bug introduced by
The method publish does only exist in Versioned, but not in DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
31
				}
32
			}
33
		}
34
	}
35
36
	/**
37
	 * Virtual "sleep" that doesn't actually slow execution, only advances SS_DateTime::now()
38
	 *
39
	 * @param int $minutes
40
	 */
41
	protected function sleep($minutes) {
42
		$now = SS_Datetime::now();
43
		$date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue());
44
		$date->modify("+{$minutes} minutes");
45
		SS_Datetime::set_mock_now($date->format('Y-m-d H:i:s'));
46
	}
47
48
	/**
49
	 * Test basic findOwned() in stage mode
50
	 */
51
	public function testFindOwned() {
52
		/** @var VersionedOwnershipTest_Subclass $subclass1 */
53
		$subclass1 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
54
		$this->assertDOSEquals(
55
			[
56
				['Title' => 'Related 1'],
57
				['Title' => 'Attachment 1'],
58
				['Title' => 'Attachment 2'],
59
				['Title' => 'Attachment 5'],
60
				['Title' => 'Related Many 1'],
61
				['Title' => 'Related Many 2'],
62
				['Title' => 'Related Many 3'],
63
			],
64
			$subclass1->findOwned()
65
		);
66
67
		// Non-recursive search
68
		$this->assertDOSEquals(
69
			[
70
				['Title' => 'Related 1'],
71
				['Title' => 'Related Many 1'],
72
				['Title' => 'Related Many 2'],
73
				['Title' => 'Related Many 3'],
74
			],
75
			$subclass1->findOwned(false)
76
		);
77
78
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
79
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
80
		$this->assertDOSEquals(
81
			[
82
				['Title' => 'Related 2'],
83
				['Title' => 'Attachment 3'],
84
				['Title' => 'Attachment 4'],
85
				['Title' => 'Attachment 5'],
86
				['Title' => 'Related Many 4'],
87
			],
88
			$subclass2->findOwned()
89
		);
90
91
		// Non-recursive search
92
		$this->assertDOSEquals(
93
			[
94
				['Title' => 'Related 2'],
95
				['Title' => 'Related Many 4'],
96
			],
97
			$subclass2->findOwned(false)
98
		);
99
100
		/** @var VersionedOwnershipTest_Related $related1 */
101
		$related1 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related1');
102
		$this->assertDOSEquals(
103
			[
104
				['Title' => 'Attachment 1'],
105
				['Title' => 'Attachment 2'],
106
				['Title' => 'Attachment 5'],
107
			],
108
			$related1->findOwned()
109
		);
110
111
		/** @var VersionedOwnershipTest_Related $related2 */
112
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
113
		$this->assertDOSEquals(
114
			[
115
				['Title' => 'Attachment 3'],
116
				['Title' => 'Attachment 4'],
117
				['Title' => 'Attachment 5'],
118
			],
119
			$related2->findOwned()
120
		);
121
	}
122
123
	/**
124
	 * Test findOwners
125
	 */
126
	public function testFindOwners() {
127
		/** @var VersionedOwnershipTest_Attachment $attachment1 */
128
		$attachment1 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment1');
129
		$this->assertDOSEquals(
130
			[
131
				['Title' => 'Related 1'],
132
				['Title' => 'Subclass 1'],
133
			],
134
			$attachment1->findOwners()
135
		);
136
137
		// Non-recursive search
138
		$this->assertDOSEquals(
139
			[
140
				['Title' => 'Related 1'],
141
			],
142
			$attachment1->findOwners(false)
143
		);
144
145
		/** @var VersionedOwnershipTest_Attachment $attachment5 */
146
		$attachment5 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment5_published');
147
		$this->assertDOSEquals(
148
			[
149
				['Title' => 'Related 1'],
150
				['Title' => 'Related 2'],
151
				['Title' => 'Subclass 1'],
152
				['Title' => 'Subclass 2'],
153
			],
154
			$attachment5->findOwners()
155
		);
156
157
		// Non-recursive
158
		$this->assertDOSEquals(
159
			[
160
				['Title' => 'Related 1'],
161
				['Title' => 'Related 2'],
162
			],
163
			$attachment5->findOwners(false)
164
		);
165
166
		/** @var VersionedOwnershipTest_Related $related1 */
167
		$related1 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related1');
168
		$this->assertDOSEquals(
169
			[
170
				['Title' => 'Subclass 1'],
171
			],
172
			$related1->findOwners()
173
		);
174
	}
175
176
	/**
177
	 * Test findOwners on Live stage
178
	 */
179
	public function testFindOwnersLive() {
180
		// Modify a few records on stage
181
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
182
		$related2->Title .= ' Modified';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
183
		$related2->write();
184
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
185
		$attachment3->Title .= ' Modified';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
186
		$attachment3->write();
187
		$attachment4 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment4_published');
188
		$attachment4->delete();
189
		$subclass2ID = $this->idFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
190
191
		// Check that stage record is ok
192
		/** @var VersionedOwnershipTest_Subclass $subclass2Stage */
193
		$subclass2Stage = \Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 'Stage')->byID($subclass2ID);
194
		$this->assertDOSEquals(
195
			[
196
				['Title' => 'Related 2 Modified'],
197
				['Title' => 'Attachment 3 Modified'],
198
				['Title' => 'Attachment 5'],
199
				['Title' => 'Related Many 4'],
200
			],
201
			$subclass2Stage->findOwned()
202
		);
203
204
		// Non-recursive
205
		$this->assertDOSEquals(
206
			[
207
				['Title' => 'Related 2 Modified'],
208
				['Title' => 'Related Many 4'],
209
			],
210
			$subclass2Stage->findOwned(false)
211
		);
212
213
		// Live records are unchanged
214
		/** @var VersionedOwnershipTest_Subclass $subclass2Live */
215
		$subclass2Live = \Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 'Live')->byID($subclass2ID);
216
		$this->assertDOSEquals(
217
			[
218
				['Title' => 'Related 2'],
219
				['Title' => 'Attachment 3'],
220
				['Title' => 'Attachment 4'],
221
				['Title' => 'Attachment 5'],
222
				['Title' => 'Related Many 4'],
223
			],
224
			$subclass2Live->findOwned()
225
		);
226
227
		// Test non-recursive
228
		$this->assertDOSEquals(
229
			[
230
				['Title' => 'Related 2'],
231
				['Title' => 'Related Many 4'],
232
			],
233
			$subclass2Live->findOwned(false)
234
		);
235
	}
236
237
	/**
238
	 * Test that objects are correctly published recursively
239
	 */
240
	public function testRecursivePublish() {
241
		/** @var VersionedOwnershipTest_Subclass $parent */
242
		$parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
243
		$parentID = $parent->ID;
244
		$banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
245
		$banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published');
246
		$banner2ID = $banner2->ID;
247
248
		// Modify, Add, and Delete banners on stage
249
		$banner1->Title = 'Renamed Banner 1';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
250
		$banner1->write();
251
252
		$banner2->delete();
253
254
		$banner4 = new VersionedOwnershipTest_RelatedMany();
255
		$banner4->Title = 'New Banner';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedOwnershipTest_RelatedMany>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
256
		$parent->Banners()->add($banner4);
257
258
		// Check state of objects before publish
259
		$oldLiveBanners = [
260
			['Title' => 'Related Many 1'],
261
			['Title' => 'Related Many 2'], // Will be deleted
262
			// `Related Many 3` isn't published
263
		];
264
		$newBanners = [
265
			['Title' => 'Renamed Banner 1'], // Renamed
266
			['Title' => 'Related Many 3'], // Published without changes
267
			['Title' => 'New Banner'], // Created
268
		];
269
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
270
			->byID($parentID);
271
		$this->assertDOSEquals($newBanners, $parentDraft->Banners());
272
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
273
			->byID($parentID);
274
		$this->assertDOSEquals($oldLiveBanners, $parentLive->Banners());
275
276
		// On publishing of owner, all children should now be updated
277
		$parent->doPublish();
278
279
		// Now check each object has the correct state
280
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
281
			->byID($parentID);
282
		$this->assertDOSEquals($newBanners, $parentDraft->Banners());
283
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
284
			->byID($parentID);
285
		$this->assertDOSEquals($newBanners, $parentLive->Banners());
286
287
		// Check that the deleted banner hasn't actually been deleted from the live stage,
288
		// but in fact has been unlinked.
289
		$banner2Live = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::LIVE)
290
			->byID($banner2ID);
291
		$this->assertEmpty($banner2Live->PageID);
292
	}
293
294
	/**
295
	 * Test that owning objects get unpublished as needed
296
	 */
297
	public function testRecursiveUnpublish() {
298
		// Unsaved objects can't be unpublished
299
		$unsaved = new VersionedOwnershipTest_Subclass();
300
		$this->assertFalse($unsaved->doUnpublish());
301
302
		// Draft-only objects can't be unpublished
303
		/** @var VersionedOwnershipTest_RelatedMany $banner3Unpublished */
304
		$banner3Unpublished = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany3');
305
		$this->assertFalse($banner3Unpublished->doUnpublish());
306
307
		// First test: mid-level unpublish; We expect that owners should be unpublished, but not
308
		// owned objects, nor other siblings shared by the same owner.
309
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
310
		/** @var VersionedOwnershipTest_Attachment $attachment3 */
311
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
312
		/** @var VersionedOwnershipTest_RelatedMany $relatedMany4 */
313
		$relatedMany4 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany4_published');
314
		/** @var VersionedOwnershipTest_Related $related2 */
315
		$this->assertTrue($related2->doUnpublish());
316
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
317
318
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
319
		$this->assertFalse($subclass2->isPublished()); // Owner IS unpublished
320
		$this->assertTrue($attachment3->isPublished()); // Owned object is NOT unpublished
321
		$this->assertTrue($relatedMany4->isPublished()); // Owned object by owner is NOT unpublished
322
323
		// Second test: multi-level unpublish should recursively cascade down all owning objects
324
		// Publish related2 again
325
		$subclass2->doPublish();
326
		$this->assertTrue($subclass2->isPublished());
327
		$this->assertTrue($related2->isPublished());
328
		$this->assertTrue($attachment3->isPublished());
329
330
		// Unpublish leaf node
331
		$this->assertTrue($attachment3->doUnpublish());
332
333
		// Now all owning objects (only) are unpublished
334
		$this->assertFalse($attachment3->isPublished()); // Unpublished because we just unpublished it
335
		$this->assertFalse($related2->isPublished()); // Unpublished because it owns attachment3
336
		$this->assertFalse($subclass2->isPublished()); // Unpublished ecause it owns related2
337
		$this->assertTrue($relatedMany4->isPublished()); // Stays live because recursion only affects owners not owned.
338
	}
339
340
	public function testRecursiveArchive() {
341
		// When archiving an object, any published owners should be unpublished at the same time
342
		// but NOT achived
343
344
		/** @var VersionedOwnershipTest_Attachment $attachment3 */
345
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
346
		$attachment3ID = $attachment3->ID;
347
		$this->assertTrue($attachment3->doArchive());
348
349
		// This object is on neither stage nor live
350
		$stageAttachment = Versioned::get_by_stage('VersionedOwnershipTest_Attachment', Versioned::DRAFT)
351
			->byID($attachment3ID);
352
		$liveAttachment = Versioned::get_by_stage('VersionedOwnershipTest_Attachment', Versioned::LIVE)
353
			->byID($attachment3ID);
354
		$this->assertEmpty($stageAttachment);
355
		$this->assertEmpty($liveAttachment);
356
357
		// Owning object is unpublished only
358
		/** @var VersionedOwnershipTest_Related $stageOwner */
359
		$stageOwner = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
360
		$this->assertTrue($stageOwner->isOnDraft());
361
		$this->assertFalse($stageOwner->isPublished());
362
363
		// Bottom level owning object is also unpublished
364
		/** @var VersionedOwnershipTest_Subclass $stageTopOwner */
365
		$stageTopOwner = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
366
		$this->assertTrue($stageTopOwner->isOnDraft());
367
		$this->assertFalse($stageTopOwner->isPublished());
368
	}
369
370
	public function testRecursiveRevertToLive() {
371
		/** @var VersionedOwnershipTest_Subclass $parent */
372
		$parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
373
		$parentID = $parent->ID;
374
		$banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
375
		$banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published');
376
		$banner2ID = $banner2->ID;
377
378
		// Modify, Add, and Delete banners on stage
379
		$banner1->Title = 'Renamed Banner 1';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
380
		$banner1->write();
381
382
		$banner2->delete();
383
384
		$banner4 = new VersionedOwnershipTest_RelatedMany();
385
		$banner4->Title = 'New Banner';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedOwnershipTest_RelatedMany>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
386
		$banner4->write();
387
		$parent->Banners()->add($banner4);
388
389
		// Check state of objects before publish
390
		$liveBanners = [
391
			['Title' => 'Related Many 1'],
392
			['Title' => 'Related Many 2'],
393
		];
394
		$modifiedBanners = [
395
			['Title' => 'Renamed Banner 1'], // Renamed
396
			['Title' => 'Related Many 3'], // Published without changes
397
			['Title' => 'New Banner'], // Created
398
		];
399
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
400
			->byID($parentID);
401
		$this->assertDOSEquals($modifiedBanners, $parentDraft->Banners());
402
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
403
			->byID($parentID);
404
		$this->assertDOSEquals($liveBanners, $parentLive->Banners());
405
406
		// When reverting parent, all records should be put back on stage
407
		$this->assertTrue($parent->doRevertToLive());
408
409
		// Now check each object has the correct state
410
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
411
			->byID($parentID);
412
		$this->assertDOSEquals($liveBanners, $parentDraft->Banners());
413
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
414
			->byID($parentID);
415
		$this->assertDOSEquals($liveBanners, $parentLive->Banners());
416
417
		// Check that the newly created banner, even though it still exist, has been
418
		// unlinked from the reverted draft record
419
		/** @var VersionedOwnershipTest_RelatedMany $banner4Draft */
420
		$banner4Draft = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::DRAFT)
421
			->byID($banner4->ID);
422
		$this->assertTrue($banner4Draft->isOnDraft());
423
		$this->assertFalse($banner4Draft->isPublished());
424
		$this->assertEmpty($banner4Draft->PageID);
425
	}
426
427
	/**
428
	 * Test that rolling back to a single version works recursively
429
	 */
430
	public function testRecursiveRollback() {
431
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
432
		$this->sleep(1);
433
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
434
435
		// Create a few new versions
436
		$versions = [];
437
		for($version = 1; $version <= 3; $version++) {
438
			// Write owned objects
439
			$this->sleep(1);
440
			foreach($subclass2->findOwned(true) as $obj) {
441
				$obj->Title .= " - v{$version}";
442
				$obj->write();
443
			}
444
			// Write parent
445
			$this->sleep(1);
446
			$subclass2->Title .= " - v{$version}";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
447
			$subclass2->write();
448
			$versions[$version] = $subclass2->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
449
		}
450
451
452
		// Check reverting to first version
453
		$this->sleep(1);
454
		$subclass2->doRollbackTo($versions[1]);
455
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
456
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
457
			->byID($subclass2->ID);
458
		$this->assertEquals('Subclass 2 - v1', $subclass2Draft->Title);
459
		$this->assertDOSEquals(
460
			[
461
				['Title' => 'Related 2 - v1'],
462
				['Title' => 'Attachment 3 - v1'],
463
				['Title' => 'Attachment 4 - v1'],
464
				['Title' => 'Attachment 5 - v1'],
465
				['Title' => 'Related Many 4 - v1'],
466
			],
467
			$subclass2Draft->findOwned(true)
468
		);
469
470
		// Check rolling forward to a later version
471
		$this->sleep(1);
472
		$subclass2->doRollbackTo($versions[3]);
473
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
474
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
475
			->byID($subclass2->ID);
476
		$this->assertEquals('Subclass 2 - v1 - v2 - v3', $subclass2Draft->Title);
477
		$this->assertDOSEquals(
478
			[
479
				['Title' => 'Related 2 - v1 - v2 - v3'],
480
				['Title' => 'Attachment 3 - v1 - v2 - v3'],
481
				['Title' => 'Attachment 4 - v1 - v2 - v3'],
482
				['Title' => 'Attachment 5 - v1 - v2 - v3'],
483
				['Title' => 'Related Many 4 - v1 - v2 - v3'],
484
			],
485
			$subclass2Draft->findOwned(true)
486
		);
487
488
		// And rolling back one version
489
		$this->sleep(1);
490
		$subclass2->doRollbackTo($versions[2]);
491
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
492
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
493
			->byID($subclass2->ID);
494
		$this->assertEquals('Subclass 2 - v1 - v2', $subclass2Draft->Title);
495
		$this->assertDOSEquals(
496
			[
497
				['Title' => 'Related 2 - v1 - v2'],
498
				['Title' => 'Attachment 3 - v1 - v2'],
499
				['Title' => 'Attachment 4 - v1 - v2'],
500
				['Title' => 'Attachment 5 - v1 - v2'],
501
				['Title' => 'Related Many 4 - v1 - v2'],
502
			],
503
			$subclass2Draft->findOwned(true)
504
		);
505
	}
506
507
}
508
509
/**
510
 * @mixin Versioned
511
 */
512
class VersionedOwnershipTest_Object extends DataObject implements TestOnly {
513
	private static $extensions = array(
514
		'Versioned',
515
	);
516
517
	private static $db = array(
518
		'Title' => 'Varchar(255)',
519
		'Content' => 'Text',
520
	);
521
}
522
523
/**
524
 * Object which:
525
 * - owns a has_one object
526
 * - owns has_many objects
527
 */
528
class VersionedOwnershipTest_Subclass extends VersionedOwnershipTest_Object implements TestOnly {
529
	private static $db = array(
530
		'Description' => 'Text',
531
	);
532
533
	private static $has_one = array(
534
		'Related' => 'VersionedOwnershipTest_Related',
535
	);
536
537
	private static $has_many = array(
538
		'Banners' => 'VersionedOwnershipTest_RelatedMany'
539
	);
540
541
	private static $owns = array(
542
		'Related',
543
		'Banners',
544
	);
545
}
546
547
/**
548
 * Object which:
549
 * - owned by has_many objects
550
 * - owns many_many Objects
551
 *
552
 * @mixin Versioned
553
 */
554
class VersionedOwnershipTest_Related extends DataObject implements TestOnly {
555
	private static $extensions = array(
556
		'Versioned',
557
	);
558
559
	private static $db = array(
560
		'Title' => 'Varchar(255)',
561
	);
562
563
	private static $has_many = array(
564
		'Parents' => 'VersionedOwnershipTest_Subclass.Related',
565
	);
566
567
	private static $owned_by = array(
568
		'Parents',
569
	);
570
571
	private static $many_many = array(
572
		// Note : Currently unversioned, take care
573
		'Attachments' => 'VersionedOwnershipTest_Attachment',
574
	);
575
576
	private static $owns = array(
577
		'Attachments',
578
	);
579
}
580
581
/**
582
 * Object which is owned by a has_one object
583
 *
584
 * @mixin Versioned
585
 */
586
class VersionedOwnershipTest_RelatedMany extends DataObject implements TestOnly {
587
	private static $extensions = array(
588
		'Versioned',
589
	);
590
591
	private static $db = array(
592
		'Title' => 'Varchar(255)',
593
	);
594
595
	private static $has_one = array(
596
		'Page' => 'VersionedOwnershipTest_Subclass'
597
	);
598
599
	private static $owned_by = array(
600
		'Page'
601
	);
602
}
603
604
/**
605
 * @mixin Versioned
606
 */
607
class VersionedOwnershipTest_Attachment extends DataObject implements TestOnly {
608
609
	private static $extensions = array(
610
		'Versioned',
611
	);
612
613
	private static $db = array(
614
		'Title' => 'Varchar(255)',
615
	);
616
617
	private static $belongs_many_many = array(
618
		'AttachedTo' => 'VersionedOwnershipTest_Related.Attachments'
619
	);
620
621
	private static $owned_by = array(
622
		'AttachedTo'
623
	);
624
}
625