Completed
Push — master ( c6c71f...1e53f2 )
by Hamish
11:24 queued 33s
created

VersionedOwnershipTest_CustomRelation   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 24
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 1
c 1
b 0
f 0
lcom 0
cbo 3
dl 0
loc 24
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A Pages() 0 4 1
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
		'VersionedOwnershipTest_Page',
15
		'VersionedOwnershipTest_Banner',
16
		'VersionedOwnershipTest_Image',
17
		'VersionedOwnershipTest_CustomRelation',
18
	);
19
20
	protected static $fixture_file = 'VersionedOwnershipTest.yml';
21
22
	public function setUp() {
23
		parent::setUp();
24
25
		Versioned::set_stage(Versioned::DRAFT);
26
27
		// Automatically publish any object named *_published
28
		foreach($this->getFixtureFactory()->getFixtures() as $class => $fixtures) {
29
			foreach($fixtures as $name => $id) {
30
				if(stripos($name, '_published') !== false) {
31
					/** @var Versioned|DataObject $object */
32
					$object = DataObject::get($class)->byID($id);
33
					$object->publish(Versioned::DRAFT, Versioned::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...
34
				}
35
			}
36
		}
37
	}
38
39
	/**
40
	 * Virtual "sleep" that doesn't actually slow execution, only advances SS_DateTime::now()
41
	 *
42
	 * @param int $minutes
43
	 */
44
	protected function sleep($minutes) {
45
		$now = SS_Datetime::now();
46
		$date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue());
47
		$date->modify("+{$minutes} minutes");
48
		SS_Datetime::set_mock_now($date->format('Y-m-d H:i:s'));
49
	}
50
51
	/**
52
	 * Test basic findOwned() in stage mode
53
	 */
54
	public function testFindOwned() {
55
		/** @var VersionedOwnershipTest_Subclass $subclass1 */
56
		$subclass1 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
57
		$this->assertDOSEquals(
58
			[
59
				['Title' => 'Related 1'],
60
				['Title' => 'Attachment 1'],
61
				['Title' => 'Attachment 2'],
62
				['Title' => 'Attachment 5'],
63
				['Title' => 'Related Many 1'],
64
				['Title' => 'Related Many 2'],
65
				['Title' => 'Related Many 3'],
66
			],
67
			$subclass1->findOwned()
68
		);
69
70
		// Non-recursive search
71
		$this->assertDOSEquals(
72
			[
73
				['Title' => 'Related 1'],
74
				['Title' => 'Related Many 1'],
75
				['Title' => 'Related Many 2'],
76
				['Title' => 'Related Many 3'],
77
			],
78
			$subclass1->findOwned(false)
79
		);
80
81
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
82
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
83
		$this->assertDOSEquals(
84
			[
85
				['Title' => 'Related 2'],
86
				['Title' => 'Attachment 3'],
87
				['Title' => 'Attachment 4'],
88
				['Title' => 'Attachment 5'],
89
				['Title' => 'Related Many 4'],
90
			],
91
			$subclass2->findOwned()
92
		);
93
94
		// Non-recursive search
95
		$this->assertDOSEquals(
96
			[
97
				['Title' => 'Related 2'],
98
				['Title' => 'Related Many 4'],
99
			],
100
			$subclass2->findOwned(false)
101
		);
102
103
		/** @var VersionedOwnershipTest_Related $related1 */
104
		$related1 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related1');
105
		$this->assertDOSEquals(
106
			[
107
				['Title' => 'Attachment 1'],
108
				['Title' => 'Attachment 2'],
109
				['Title' => 'Attachment 5'],
110
			],
111
			$related1->findOwned()
112
		);
113
114
		/** @var VersionedOwnershipTest_Related $related2 */
115
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
116
		$this->assertDOSEquals(
117
			[
118
				['Title' => 'Attachment 3'],
119
				['Title' => 'Attachment 4'],
120
				['Title' => 'Attachment 5'],
121
			],
122
			$related2->findOwned()
123
		);
124
	}
125
126
	/**
127
	 * Test findOwners
128
	 */
129
	public function testFindOwners() {
130
		/** @var VersionedOwnershipTest_Attachment $attachment1 */
131
		$attachment1 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment1');
132
		$this->assertDOSEquals(
133
			[
134
				['Title' => 'Related 1'],
135
				['Title' => 'Subclass 1'],
136
			],
137
			$attachment1->findOwners()
138
		);
139
140
		// Non-recursive search
141
		$this->assertDOSEquals(
142
			[
143
				['Title' => 'Related 1'],
144
			],
145
			$attachment1->findOwners(false)
146
		);
147
148
		/** @var VersionedOwnershipTest_Attachment $attachment5 */
149
		$attachment5 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment5_published');
150
		$this->assertDOSEquals(
151
			[
152
				['Title' => 'Related 1'],
153
				['Title' => 'Related 2'],
154
				['Title' => 'Subclass 1'],
155
				['Title' => 'Subclass 2'],
156
			],
157
			$attachment5->findOwners()
158
		);
159
160
		// Non-recursive
161
		$this->assertDOSEquals(
162
			[
163
				['Title' => 'Related 1'],
164
				['Title' => 'Related 2'],
165
			],
166
			$attachment5->findOwners(false)
167
		);
168
169
		/** @var VersionedOwnershipTest_Related $related1 */
170
		$related1 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related1');
171
		$this->assertDOSEquals(
172
			[
173
				['Title' => 'Subclass 1'],
174
			],
175
			$related1->findOwners()
176
		);
177
	}
178
179
	/**
180
	 * Test findOwners on Live stage
181
	 */
182
	public function testFindOwnersLive() {
183
		// Modify a few records on stage
184
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
185
		$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...
186
		$related2->write();
187
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
188
		$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...
189
		$attachment3->write();
190
		$attachment4 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment4_published');
191
		$attachment4->delete();
192
		$subclass2ID = $this->idFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
193
194
		// Check that stage record is ok
195
		/** @var VersionedOwnershipTest_Subclass $subclass2Stage */
196
		$subclass2Stage = \Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 'Stage')->byID($subclass2ID);
197
		$this->assertDOSEquals(
198
			[
199
				['Title' => 'Related 2 Modified'],
200
				['Title' => 'Attachment 3 Modified'],
201
				['Title' => 'Attachment 5'],
202
				['Title' => 'Related Many 4'],
203
			],
204
			$subclass2Stage->findOwned()
205
		);
206
207
		// Non-recursive
208
		$this->assertDOSEquals(
209
			[
210
				['Title' => 'Related 2 Modified'],
211
				['Title' => 'Related Many 4'],
212
			],
213
			$subclass2Stage->findOwned(false)
214
		);
215
216
		// Live records are unchanged
217
		/** @var VersionedOwnershipTest_Subclass $subclass2Live */
218
		$subclass2Live = \Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 'Live')->byID($subclass2ID);
219
		$this->assertDOSEquals(
220
			[
221
				['Title' => 'Related 2'],
222
				['Title' => 'Attachment 3'],
223
				['Title' => 'Attachment 4'],
224
				['Title' => 'Attachment 5'],
225
				['Title' => 'Related Many 4'],
226
			],
227
			$subclass2Live->findOwned()
228
		);
229
230
		// Test non-recursive
231
		$this->assertDOSEquals(
232
			[
233
				['Title' => 'Related 2'],
234
				['Title' => 'Related Many 4'],
235
			],
236
			$subclass2Live->findOwned(false)
237
		);
238
	}
239
240
	/**
241
	 * Test that objects are correctly published recursively
242
	 */
243
	public function testRecursivePublish() {
244
		/** @var VersionedOwnershipTest_Subclass $parent */
245
		$parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
246
		$parentID = $parent->ID;
247
		$banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
248
		$banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published');
249
		$banner2ID = $banner2->ID;
250
251
		// Modify, Add, and Delete banners on stage
252
		$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...
253
		$banner1->write();
254
255
		$banner2->delete();
256
257
		$banner4 = new VersionedOwnershipTest_RelatedMany();
258
		$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...
259
		$parent->Banners()->add($banner4);
260
261
		// Check state of objects before publish
262
		$oldLiveBanners = [
263
			['Title' => 'Related Many 1'],
264
			['Title' => 'Related Many 2'], // Will be deleted
265
			// `Related Many 3` isn't published
266
		];
267
		$newBanners = [
268
			['Title' => 'Renamed Banner 1'], // Renamed
269
			['Title' => 'Related Many 3'], // Published without changes
270
			['Title' => 'New Banner'], // Created
271
		];
272
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
273
			->byID($parentID);
274
		$this->assertDOSEquals($newBanners, $parentDraft->Banners());
275
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
276
			->byID($parentID);
277
		$this->assertDOSEquals($oldLiveBanners, $parentLive->Banners());
278
279
		// On publishing of owner, all children should now be updated
280
		$parent->doPublish();
281
282
		// Now check each object has the correct state
283
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
284
			->byID($parentID);
285
		$this->assertDOSEquals($newBanners, $parentDraft->Banners());
286
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
287
			->byID($parentID);
288
		$this->assertDOSEquals($newBanners, $parentLive->Banners());
289
290
		// Check that the deleted banner hasn't actually been deleted from the live stage,
291
		// but in fact has been unlinked.
292
		$banner2Live = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::LIVE)
293
			->byID($banner2ID);
294
		$this->assertEmpty($banner2Live->PageID);
295
	}
296
297
	/**
298
	 * Test that owning objects get unpublished as needed
299
	 */
300
	public function testRecursiveUnpublish() {
301
		// Unsaved objects can't be unpublished
302
		$unsaved = new VersionedOwnershipTest_Subclass();
303
		$this->assertFalse($unsaved->doUnpublish());
304
305
		// Draft-only objects can't be unpublished
306
		/** @var VersionedOwnershipTest_RelatedMany $banner3Unpublished */
307
		$banner3Unpublished = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany3');
308
		$this->assertFalse($banner3Unpublished->doUnpublish());
309
310
		// First test: mid-level unpublish; We expect that owners should be unpublished, but not
311
		// owned objects, nor other siblings shared by the same owner.
312
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
313
		/** @var VersionedOwnershipTest_Attachment $attachment3 */
314
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
315
		/** @var VersionedOwnershipTest_RelatedMany $relatedMany4 */
316
		$relatedMany4 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany4_published');
317
		/** @var VersionedOwnershipTest_Related $related2 */
318
		$this->assertTrue($related2->doUnpublish());
319
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
320
321
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
322
		$this->assertFalse($subclass2->isPublished()); // Owner IS unpublished
323
		$this->assertTrue($attachment3->isPublished()); // Owned object is NOT unpublished
324
		$this->assertTrue($relatedMany4->isPublished()); // Owned object by owner is NOT unpublished
325
326
		// Second test: multi-level unpublish should recursively cascade down all owning objects
327
		// Publish related2 again
328
		$subclass2->doPublish();
329
		$this->assertTrue($subclass2->isPublished());
330
		$this->assertTrue($related2->isPublished());
331
		$this->assertTrue($attachment3->isPublished());
332
333
		// Unpublish leaf node
334
		$this->assertTrue($attachment3->doUnpublish());
335
336
		// Now all owning objects (only) are unpublished
337
		$this->assertFalse($attachment3->isPublished()); // Unpublished because we just unpublished it
338
		$this->assertFalse($related2->isPublished()); // Unpublished because it owns attachment3
339
		$this->assertFalse($subclass2->isPublished()); // Unpublished ecause it owns related2
340
		$this->assertTrue($relatedMany4->isPublished()); // Stays live because recursion only affects owners not owned.
341
	}
342
343
	public function testRecursiveArchive() {
344
		// When archiving an object, any published owners should be unpublished at the same time
345
		// but NOT achived
346
347
		/** @var VersionedOwnershipTest_Attachment $attachment3 */
348
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
349
		$attachment3ID = $attachment3->ID;
350
		$this->assertTrue($attachment3->doArchive());
351
352
		// This object is on neither stage nor live
353
		$stageAttachment = Versioned::get_by_stage('VersionedOwnershipTest_Attachment', Versioned::DRAFT)
354
			->byID($attachment3ID);
355
		$liveAttachment = Versioned::get_by_stage('VersionedOwnershipTest_Attachment', Versioned::LIVE)
356
			->byID($attachment3ID);
357
		$this->assertEmpty($stageAttachment);
358
		$this->assertEmpty($liveAttachment);
359
360
		// Owning object is unpublished only
361
		/** @var VersionedOwnershipTest_Related $stageOwner */
362
		$stageOwner = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
363
		$this->assertTrue($stageOwner->isOnDraft());
364
		$this->assertFalse($stageOwner->isPublished());
365
366
		// Bottom level owning object is also unpublished
367
		/** @var VersionedOwnershipTest_Subclass $stageTopOwner */
368
		$stageTopOwner = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
369
		$this->assertTrue($stageTopOwner->isOnDraft());
370
		$this->assertFalse($stageTopOwner->isPublished());
371
	}
372
373
	public function testRecursiveRevertToLive() {
374
		/** @var VersionedOwnershipTest_Subclass $parent */
375
		$parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
376
		$parentID = $parent->ID;
377
		$banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
378
		$banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published');
379
		$banner2ID = $banner2->ID;
380
381
		// Modify, Add, and Delete banners on stage
382
		$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...
383
		$banner1->write();
384
385
		$banner2->delete();
386
387
		$banner4 = new VersionedOwnershipTest_RelatedMany();
388
		$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...
389
		$banner4->write();
390
		$parent->Banners()->add($banner4);
391
392
		// Check state of objects before publish
393
		$liveBanners = [
394
			['Title' => 'Related Many 1'],
395
			['Title' => 'Related Many 2'],
396
		];
397
		$modifiedBanners = [
398
			['Title' => 'Renamed Banner 1'], // Renamed
399
			['Title' => 'Related Many 3'], // Published without changes
400
			['Title' => 'New Banner'], // Created
401
		];
402
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
403
			->byID($parentID);
404
		$this->assertDOSEquals($modifiedBanners, $parentDraft->Banners());
405
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
406
			->byID($parentID);
407
		$this->assertDOSEquals($liveBanners, $parentLive->Banners());
408
409
		// When reverting parent, all records should be put back on stage
410
		$this->assertTrue($parent->doRevertToLive());
411
412
		// Now check each object has the correct state
413
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
414
			->byID($parentID);
415
		$this->assertDOSEquals($liveBanners, $parentDraft->Banners());
416
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
417
			->byID($parentID);
418
		$this->assertDOSEquals($liveBanners, $parentLive->Banners());
419
420
		// Check that the newly created banner, even though it still exist, has been
421
		// unlinked from the reverted draft record
422
		/** @var VersionedOwnershipTest_RelatedMany $banner4Draft */
423
		$banner4Draft = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::DRAFT)
424
			->byID($banner4->ID);
425
		$this->assertTrue($banner4Draft->isOnDraft());
426
		$this->assertFalse($banner4Draft->isPublished());
427
		$this->assertEmpty($banner4Draft->PageID);
428
	}
429
430
	/**
431
	 * Test that rolling back to a single version works recursively
432
	 */
433
	public function testRecursiveRollback() {
434
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
435
		$this->sleep(1);
436
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
437
438
		// Create a few new versions
439
		$versions = [];
440
		for($version = 1; $version <= 3; $version++) {
441
			// Write owned objects
442
			$this->sleep(1);
443
			foreach($subclass2->findOwned(true) as $obj) {
444
				$obj->Title .= " - v{$version}";
445
				$obj->write();
446
			}
447
			// Write parent
448
			$this->sleep(1);
449
			$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...
450
			$subclass2->write();
451
			$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...
452
		}
453
454
455
		// Check reverting to first version
456
		$this->sleep(1);
457
		$subclass2->doRollbackTo($versions[1]);
458
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
459
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
460
			->byID($subclass2->ID);
461
		$this->assertEquals('Subclass 2 - v1', $subclass2Draft->Title);
462
		$this->assertDOSEquals(
463
			[
464
				['Title' => 'Related 2 - v1'],
465
				['Title' => 'Attachment 3 - v1'],
466
				['Title' => 'Attachment 4 - v1'],
467
				['Title' => 'Attachment 5 - v1'],
468
				['Title' => 'Related Many 4 - v1'],
469
			],
470
			$subclass2Draft->findOwned(true)
471
		);
472
473
		// Check rolling forward to a later version
474
		$this->sleep(1);
475
		$subclass2->doRollbackTo($versions[3]);
476
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
477
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
478
			->byID($subclass2->ID);
479
		$this->assertEquals('Subclass 2 - v1 - v2 - v3', $subclass2Draft->Title);
480
		$this->assertDOSEquals(
481
			[
482
				['Title' => 'Related 2 - v1 - v2 - v3'],
483
				['Title' => 'Attachment 3 - v1 - v2 - v3'],
484
				['Title' => 'Attachment 4 - v1 - v2 - v3'],
485
				['Title' => 'Attachment 5 - v1 - v2 - v3'],
486
				['Title' => 'Related Many 4 - v1 - v2 - v3'],
487
			],
488
			$subclass2Draft->findOwned(true)
489
		);
490
491
		// And rolling back one version
492
		$this->sleep(1);
493
		$subclass2->doRollbackTo($versions[2]);
494
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
495
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
496
			->byID($subclass2->ID);
497
		$this->assertEquals('Subclass 2 - v1 - v2', $subclass2Draft->Title);
498
		$this->assertDOSEquals(
499
			[
500
				['Title' => 'Related 2 - v1 - v2'],
501
				['Title' => 'Attachment 3 - v1 - v2'],
502
				['Title' => 'Attachment 4 - v1 - v2'],
503
				['Title' => 'Attachment 5 - v1 - v2'],
504
				['Title' => 'Related Many 4 - v1 - v2'],
505
			],
506
			$subclass2Draft->findOwned(true)
507
		);
508
	}
509
510
	/**
511
	 * Test that you can find owners without owned_by being defined explicitly
512
	 */
513
	public function testInferedOwners() {
514
		// Make sure findOwned() works
515
		/** @var VersionedOwnershipTest_Page $page1 */
516
		$page1 = $this->objFromFixture('VersionedOwnershipTest_Page', 'page1_published');
517
		/** @var VersionedOwnershipTest_Page $page2 */
518
		$page2 = $this->objFromFixture('VersionedOwnershipTest_Page', 'page2_published');
519
		$this->assertDOSEquals(
520
			[
521
				['Title' => 'Banner 1'],
522
				['Title' => 'Image 1'],
523
				['Title' => 'Custom 1'],
524
			],
525
			$page1->findOwned()
526
		);
527
		$this->assertDOSEquals(
528
			[
529
				['Title' => 'Banner 2'],
530
				['Title' => 'Banner 3'],
531
				['Title' => 'Image 1'],
532
				['Title' => 'Image 2'],
533
				['Title' => 'Custom 2'],
534
			],
535
			$page2->findOwned()
536
		);
537
538
		// Check that findOwners works
539
		/** @var VersionedOwnershipTest_Image $image1 */
540
		$image1 = $this->objFromFixture('VersionedOwnershipTest_Image', 'image1_published');
541
		/** @var VersionedOwnershipTest_Image $image2 */
542
		$image2 = $this->objFromFixture('VersionedOwnershipTest_Image', 'image2_published');
543
544
		$this->assertDOSEquals(
545
			[
546
				['Title' => 'Banner 1'],
547
				['Title' => 'Banner 2'],
548
				['Title' => 'Page 1'],
549
				['Title' => 'Page 2'],
550
			],
551
			$image1->findOwners()
552
		);
553
		$this->assertDOSEquals(
554
			[
555
				['Title' => 'Banner 1'],
556
				['Title' => 'Banner 2'],
557
			],
558
			$image1->findOwners(false)
559
		);
560
		$this->assertDOSEquals(
561
			[
562
				['Title' => 'Banner 3'],
563
				['Title' => 'Page 2'],
564
			],
565
			$image2->findOwners()
566
		);
567
		$this->assertDOSEquals(
568
			[
569
				['Title' => 'Banner 3'],
570
			],
571
			$image2->findOwners(false)
572
		);
573
574
		// Test custom relation can findOwners()
575
		/** @var VersionedOwnershipTest_CustomRelation $custom1 */
576
		$custom1 = $this->objFromFixture('VersionedOwnershipTest_CustomRelation', 'custom1_published');
577
		$this->assertDOSEquals(
578
			[['Title' => 'Page 1']],
579
			$custom1->findOwners()
580
		);
581
582
	}
583
584
}
585
586
/**
587
 * @mixin Versioned
588
 */
589
class VersionedOwnershipTest_Object extends DataObject implements TestOnly {
590
	private static $extensions = array(
591
		'Versioned',
592
	);
593
594
	private static $db = array(
595
		'Title' => 'Varchar(255)',
596
		'Content' => 'Text',
597
	);
598
}
599
600
/**
601
 * Object which:
602
 * - owns a has_one object
603
 * - owns has_many objects
604
 */
605
class VersionedOwnershipTest_Subclass extends VersionedOwnershipTest_Object implements TestOnly {
606
	private static $db = array(
607
		'Description' => 'Text',
608
	);
609
610
	private static $has_one = array(
611
		'Related' => 'VersionedOwnershipTest_Related',
612
	);
613
614
	private static $has_many = array(
615
		'Banners' => 'VersionedOwnershipTest_RelatedMany'
616
	);
617
618
	private static $owns = array(
619
		'Related',
620
		'Banners',
621
	);
622
}
623
624
/**
625
 * Object which:
626
 * - owned by has_many objects
627
 * - owns many_many Objects
628
 *
629
 * @mixin Versioned
630
 */
631
class VersionedOwnershipTest_Related extends DataObject implements TestOnly {
632
	private static $extensions = array(
633
		'Versioned',
634
	);
635
636
	private static $db = array(
637
		'Title' => 'Varchar(255)',
638
	);
639
640
	private static $has_many = array(
641
		'Parents' => 'VersionedOwnershipTest_Subclass.Related',
642
	);
643
644
	private static $owned_by = array(
645
		'Parents',
646
	);
647
648
	private static $many_many = array(
649
		// Note : Currently unversioned, take care
650
		'Attachments' => 'VersionedOwnershipTest_Attachment',
651
	);
652
653
	private static $owns = array(
654
		'Attachments',
655
	);
656
}
657
658
/**
659
 * Object which is owned by a has_one object
660
 *
661
 * @mixin Versioned
662
 */
663
class VersionedOwnershipTest_RelatedMany extends DataObject implements TestOnly {
664
	private static $extensions = array(
665
		'Versioned',
666
	);
667
668
	private static $db = array(
669
		'Title' => 'Varchar(255)',
670
	);
671
672
	private static $has_one = array(
673
		'Page' => 'VersionedOwnershipTest_Subclass'
674
	);
675
676
	private static $owned_by = array(
677
		'Page'
678
	);
679
}
680
681
/**
682
 * @mixin Versioned
683
 */
684
class VersionedOwnershipTest_Attachment extends DataObject implements TestOnly {
685
686
	private static $extensions = array(
687
		'Versioned',
688
	);
689
690
	private static $db = array(
691
		'Title' => 'Varchar(255)',
692
	);
693
694
	private static $belongs_many_many = array(
695
		'AttachedTo' => 'VersionedOwnershipTest_Related.Attachments'
696
	);
697
698
	private static $owned_by = array(
699
		'AttachedTo'
700
	);
701
}
702
703
/**
704
 * Page which owns a lits of banners
705
 *
706
 * @mixin Versioned
707
 */
708
class VersionedOwnershipTest_Page extends DataObject implements TestOnly {
709
	private static $extensions = array(
710
		'Versioned',
711
	);
712
713
	private static $db = array(
714
		'Title' => 'Varchar(255)',
715
	);
716
717
	private static $many_many = array(
718
		'Banners' => 'VersionedOwnershipTest_Banner',
719
	);
720
721
	private static $owns = array(
722
		'Banners',
723
		'Custom'
724
	);
725
726
	/**
727
	 * All custom objects with the same number. E.g. 'Page 1' owns 'Custom 1'
728
	 *
729
	 * @return DataList
730
	 */
731
	public function Custom() {
732
		$title = str_replace('Page', 'Custom', $this->Title);
733
		return VersionedOwnershipTest_CustomRelation::get()
734
			->filter('Title', $title);
735
	}
736
}
737
738
/**
739
 * Banner which doesn't declare its belongs_many_many, but owns an Image
740
 *
741
 * @mixin Versioned
742
 */
743
class VersionedOwnershipTest_Banner extends DataObject implements TestOnly {
744
	private static $extensions = array(
745
		'Versioned',
746
	);
747
748
	private static $db = array(
749
		'Title' => 'Varchar(255)',
750
	);
751
752
	private static $has_one = array(
753
		'Image' => 'VersionedOwnershipTest_Image',
754
	);
755
756
	private static $owns = array(
757
		'Image',
758
	);
759
}
760
761
762
/**
763
 * Object which is owned via a custom PHP method rather than DB relation
764
 *
765
 * @mixin Versioned
766
 */
767
class VersionedOwnershipTest_CustomRelation extends DataObject implements TestOnly {
768
	private static $extensions = array(
769
		'Versioned',
770
	);
771
772
	private static $db = array(
773
		'Title' => 'Varchar(255)',
774
	);
775
776
	private static $owned_by = array(
777
		'Pages'
778
	);
779
780
	/**
781
	 * All pages with the same number. E.g. 'Page 1' owns 'Custom 1'
782
	 *
783
	 * @return DataList
784
	 */
785
	public function Pages() {
786
		$title = str_replace('Custom', 'Page', $this->Title);
787
		return VersionedOwnershipTest_Page::get()->filter('Title', $title);
788
	}
789
790
}
791
792
/**
793
 * Simple versioned dataobject
794
 *
795
 * @mixin Versioned
796
 */
797
class VersionedOwnershipTest_Image extends DataObject implements TestOnly {
798
	private static $extensions = array(
799
		'Versioned',
800
	);
801
802
	private static $db = array(
803
		'Title' => 'Varchar(255)',
804
	);
805
}
806