Completed
Push — master ( b0e0e9...f4bf0c )
by Will
08:32
created

VersionedOwnershipTest   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 631
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 0
loc 631
rs 9.9111
c 0
b 0
f 0
wmc 16
lcom 1
cbo 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 17 4
A sleep() 0 7 1
A testFindOwned() 0 72 1
A testFindOwners() 0 50 1
A testFindOwnersLive() 0 58 1
B testRecursivePublish() 0 96 1
B testRecursiveUnpublish() 0 43 1
B testRecursiveArchive() 0 30 1
A testRecursiveRevertToLive() 0 57 1
B testRecursiveRollback() 0 77 3
A testInferedOwners() 0 70 1
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\ORM\Versioning\ChangeSet;
6
use SilverStripe\ORM\Versioning\ChangeSetItem;
7
use SilverStripe\ORM\Versioning\Versioned;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\ORM\FieldType\DBDatetime;
10
use SilverStripe\Dev\SapphireTest;
11
use DateTime;
12
13
/**
14
 * Tests ownership API of versioned DataObjects
15
 */
16
class VersionedOwnershipTest extends SapphireTest
17
{
18
19
    protected $extraDataObjects = array(
20
        VersionedOwnershipTest\TestObject::class,
21
        VersionedOwnershipTest\Subclass::class,
22
        VersionedOwnershipTest\Related::class,
23
        VersionedOwnershipTest\Attachment::class,
24
        VersionedOwnershipTest\RelatedMany::class,
25
        VersionedOwnershipTest\TestPage::class,
26
        VersionedOwnershipTest\Banner::class,
27
        VersionedOwnershipTest\Image::class,
28
        VersionedOwnershipTest\CustomRelation::class,
29
    );
30
31
    protected static $fixture_file = 'VersionedOwnershipTest.yml';
32
33
    public function setUp()
34
    {
35
        parent::setUp();
36
37
        Versioned::set_stage(Versioned::DRAFT);
38
39
        // Automatically publish any object named *_published
40
        foreach ($this->getFixtureFactory()->getFixtures() as $class => $fixtures) {
41
            foreach ($fixtures as $name => $id) {
42
                if (stripos($name, '_published') !== false) {
43
                    /** @var Versioned|DataObject $object */
44
                    $object = DataObject::get($class)->byID($id);
45
                    $object->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method copyVersionToStage does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\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...
46
                }
47
            }
48
        }
49
    }
50
51
    /**
52
     * Virtual "sleep" that doesn't actually slow execution, only advances DBDateTime::now()
53
     *
54
     * @param int $minutes
55
     */
56
    protected function sleep($minutes)
57
    {
58
        $now = DBDatetime::now();
59
        $date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue());
60
        $date->modify("+{$minutes} minutes");
61
        DBDatetime::set_mock_now($date->format('Y-m-d H:i:s'));
62
    }
63
64
    /**
65
     * Test basic findOwned() in stage mode
66
     */
67
    public function testFindOwned()
68
    {
69
        /** @var VersionedOwnershipTest\Subclass $subclass1 */
70
        $subclass1 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass1_published');
71
        $this->assertDOSEquals(
72
            [
73
                ['Title' => 'Related 1'],
74
                ['Title' => 'Attachment 1'],
75
                ['Title' => 'Attachment 2'],
76
                ['Title' => 'Attachment 5'],
77
                ['Title' => 'Related Many 1'],
78
                ['Title' => 'Related Many 2'],
79
                ['Title' => 'Related Many 3'],
80
            ],
81
            $subclass1->findOwned()
82
        );
83
84
        // Non-recursive search
85
        $this->assertDOSEquals(
86
            [
87
                ['Title' => 'Related 1'],
88
                ['Title' => 'Related Many 1'],
89
                ['Title' => 'Related Many 2'],
90
                ['Title' => 'Related Many 3'],
91
            ],
92
            $subclass1->findOwned(false)
93
        );
94
95
        /** @var VersionedOwnershipTest\Subclass $subclass2 */
96
        $subclass2 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
97
        $this->assertDOSEquals(
98
            [
99
                ['Title' => 'Related 2'],
100
                ['Title' => 'Attachment 3'],
101
                ['Title' => 'Attachment 4'],
102
                ['Title' => 'Attachment 5'],
103
                ['Title' => 'Related Many 4'],
104
            ],
105
            $subclass2->findOwned()
106
        );
107
108
        // Non-recursive search
109
        $this->assertDOSEquals(
110
            [
111
                ['Title' => 'Related 2'],
112
                ['Title' => 'Related Many 4'],
113
            ],
114
            $subclass2->findOwned(false)
115
        );
116
117
        /** @var VersionedOwnershipTest\Related $related1 */
118
        $related1 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related1');
119
        $this->assertDOSEquals(
120
            [
121
                ['Title' => 'Attachment 1'],
122
                ['Title' => 'Attachment 2'],
123
                ['Title' => 'Attachment 5'],
124
            ],
125
            $related1->findOwned()
126
        );
127
128
        /** @var VersionedOwnershipTest\Related $related2 */
129
        $related2 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
130
        $this->assertDOSEquals(
131
            [
132
                ['Title' => 'Attachment 3'],
133
                ['Title' => 'Attachment 4'],
134
                ['Title' => 'Attachment 5'],
135
            ],
136
            $related2->findOwned()
137
        );
138
    }
139
140
    /**
141
     * Test findOwners
142
     */
143
    public function testFindOwners()
144
    {
145
        /** @var VersionedOwnershipTest\Attachment $attachment1 */
146
        $attachment1 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment1');
147
        $this->assertDOSEquals(
148
            [
149
                ['Title' => 'Related 1'],
150
                ['Title' => 'Subclass 1'],
151
            ],
152
            $attachment1->findOwners()
153
        );
154
155
        // Non-recursive search
156
        $this->assertDOSEquals(
157
            [
158
                ['Title' => 'Related 1'],
159
            ],
160
            $attachment1->findOwners(false)
161
        );
162
163
        /** @var VersionedOwnershipTest\Attachment $attachment5 */
164
        $attachment5 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment5_published');
165
        $this->assertDOSEquals(
166
            [
167
                ['Title' => 'Related 1'],
168
                ['Title' => 'Related 2'],
169
                ['Title' => 'Subclass 1'],
170
                ['Title' => 'Subclass 2'],
171
            ],
172
            $attachment5->findOwners()
173
        );
174
175
        // Non-recursive
176
        $this->assertDOSEquals(
177
            [
178
                ['Title' => 'Related 1'],
179
                ['Title' => 'Related 2'],
180
            ],
181
            $attachment5->findOwners(false)
182
        );
183
184
        /** @var VersionedOwnershipTest\Related $related1 */
185
        $related1 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related1');
186
        $this->assertDOSEquals(
187
            [
188
                ['Title' => 'Subclass 1'],
189
            ],
190
            $related1->findOwners()
191
        );
192
    }
193
194
    /**
195
     * Test findOwners on Live stage
196
     */
197
    public function testFindOwnersLive()
198
    {
199
        // Modify a few records on stage
200
        $related2 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
201
        $related2->Title .= ' Modified';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\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...
202
        $related2->write();
203
        $attachment3 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment3_published');
204
        $attachment3->Title .= ' Modified';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\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...
205
        $attachment3->write();
206
        $attachment4 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment4_published');
207
        $attachment4->delete();
208
        $subclass2ID = $this->idFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
209
210
        // Check that stage record is ok
211
        /** @var VersionedOwnershipTest\Subclass $subclass2Stage */
212
        $subclass2Stage = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, 'Stage')->byID($subclass2ID);
213
        $this->assertDOSEquals(
214
            [
215
                ['Title' => 'Related 2 Modified'],
216
                ['Title' => 'Attachment 3 Modified'],
217
                ['Title' => 'Attachment 5'],
218
                ['Title' => 'Related Many 4'],
219
            ],
220
            $subclass2Stage->findOwned()
221
        );
222
223
        // Non-recursive
224
        $this->assertDOSEquals(
225
            [
226
                ['Title' => 'Related 2 Modified'],
227
                ['Title' => 'Related Many 4'],
228
            ],
229
            $subclass2Stage->findOwned(false)
230
        );
231
232
        // Live records are unchanged
233
        /** @var VersionedOwnershipTest\Subclass $subclass2Live */
234
        $subclass2Live = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, 'Live')->byID($subclass2ID);
235
        $this->assertDOSEquals(
236
            [
237
                ['Title' => 'Related 2'],
238
                ['Title' => 'Attachment 3'],
239
                ['Title' => 'Attachment 4'],
240
                ['Title' => 'Attachment 5'],
241
                ['Title' => 'Related Many 4'],
242
            ],
243
            $subclass2Live->findOwned()
244
        );
245
246
        // Test non-recursive
247
        $this->assertDOSEquals(
248
            [
249
                ['Title' => 'Related 2'],
250
                ['Title' => 'Related Many 4'],
251
            ],
252
            $subclass2Live->findOwned(false)
253
        );
254
    }
255
256
    /**
257
     * Test that objects are correctly published recursively
258
     */
259
    public function testRecursivePublish()
260
    {
261
        /** @var VersionedOwnershipTest\Subclass $parent */
262
        $parent = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass1_published');
263
        $parentID = $parent->ID;
264
        $banner1 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany1_published');
265
        $banner2 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany2_published');
266
        $banner2ID = $banner2->ID;
267
268
        // Modify, Add, and Delete banners on stage
269
        $banner1->Title = 'Renamed Banner 1';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\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...
270
        $banner1->write();
271
272
        $banner2->delete();
273
274
        $banner4 = new VersionedOwnershipTest\RelatedMany();
275
        $banner4->Title = 'New Banner';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\...ershipTest\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...
276
        $parent->Banners()->add($banner4);
277
278
        // Check state of objects before publish
279
        $oldLiveBanners = [
280
            ['Title' => 'Related Many 1'],
281
            ['Title' => 'Related Many 2'], // Will be unlinked (but not deleted)
282
            // `Related Many 3` isn't published
283
        ];
284
        $newBanners = [
285
            ['Title' => 'Renamed Banner 1'], // Renamed
286
            ['Title' => 'Related Many 3'], // Published without changes
287
            ['Title' => 'New Banner'], // Created
288
        ];
289
        $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
290
            ->byID($parentID);
291
        $this->assertDOSEquals($newBanners, $parentDraft->Banners());
292
        $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
293
            ->byID($parentID);
294
        $this->assertDOSEquals($oldLiveBanners, $parentLive->Banners());
295
296
        // On publishing of owner, all children should now be updated
297
        $now = DBDatetime::now();
298
        DBDatetime::set_mock_now($now); // Lock 'now' to predictable time
299
        $parent->publishRecursive();
300
301
        // Now check each object has the correct state
302
        $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
303
            ->byID($parentID);
304
        $this->assertDOSEquals($newBanners, $parentDraft->Banners());
305
        $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
306
            ->byID($parentID);
307
        $this->assertDOSEquals($newBanners, $parentLive->Banners());
308
309
        // Check that the deleted banner hasn't actually been deleted from the live stage,
310
        // but in fact has been unlinked.
311
        $banner2Live = Versioned::get_by_stage(VersionedOwnershipTest\RelatedMany::class, Versioned::LIVE)
312
            ->byID($banner2ID);
313
        $this->assertEmpty($banner2Live->PageID);
314
315
        // Test that a changeset was created
316
        /** @var ChangeSet $changeset */
317
        $changeset = ChangeSet::get()->sort('"ChangeSet"."ID" DESC')->first();
318
        $this->assertNotEmpty($changeset);
319
320
        // Test that this changeset is inferred
321
        $this->assertTrue((bool)$changeset->IsInferred);
322
        $this->assertEquals(
323
            "Generated by publish of 'Subclass 1' at ".$now->Nice(),
324
            $changeset->getTitle()
325
        );
326
327
        // Test that this changeset contains all items
328
        $this->assertDOSContains(
329
            [
330
                [
331
                    'ObjectID' => $parent->ID,
332
                    'ObjectClass' => $parent->baseClass(),
333
                    'Added' => ChangeSetItem::EXPLICITLY
334
                ],
335
                [
336
                    'ObjectID' => $banner1->ID,
337
                    'ObjectClass' => $banner1->baseClass(),
338
                    'Added' => ChangeSetItem::IMPLICITLY
339
                ],
340
                [
341
                    'ObjectID' => $banner4->ID,
342
                    'ObjectClass' => $banner4->baseClass(),
343
                    'Added' => ChangeSetItem::IMPLICITLY
344
                ]
345
            ],
346
            $changeset->Changes()
347
        );
348
349
        // Objects that are unlinked should not need to be a part of the changeset
350
        $this->assertNotDOSContains(
351
            [[ 'ObjectID' => $banner2ID, 'ObjectClass' => $banner2->baseClass() ]],
352
            $changeset->Changes()
353
        );
354
    }
355
356
    /**
357
     * Test that owning objects get unpublished as needed
358
     */
359
    public function testRecursiveUnpublish()
360
    {
361
        // Unsaved objects can't be unpublished
362
        $unsaved = new VersionedOwnershipTest\Subclass();
363
        $this->assertFalse($unsaved->doUnpublish());
364
365
        // Draft-only objects can't be unpublished
366
        /** @var VersionedOwnershipTest\RelatedMany $banner3Unpublished */
367
        $banner3Unpublished = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany3');
368
        $this->assertFalse($banner3Unpublished->doUnpublish());
369
370
        // First test: mid-level unpublish; We expect that owners should be unpublished, but not
371
        // owned objects, nor other siblings shared by the same owner.
372
        $related2 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
373
        /** @var VersionedOwnershipTest\Attachment $attachment3 */
374
        $attachment3 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment3_published');
375
        /** @var VersionedOwnershipTest\RelatedMany $relatedMany4 */
376
        $relatedMany4 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany4_published');
377
        /** @var VersionedOwnershipTest\Related $related2 */
378
        $this->assertTrue($related2->doUnpublish());
379
        $subclass2 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
380
381
        /** @var VersionedOwnershipTest\Subclass $subclass2 */
382
        $this->assertFalse($subclass2->isPublished()); // Owner IS unpublished
383
        $this->assertTrue($attachment3->isPublished()); // Owned object is NOT unpublished
384
        $this->assertTrue($relatedMany4->isPublished()); // Owned object by owner is NOT unpublished
385
386
        // Second test: multi-level unpublish should recursively cascade down all owning objects
387
        // Publish related2 again
388
        $subclass2->publishRecursive();
389
        $this->assertTrue($subclass2->isPublished());
390
        $this->assertTrue($related2->isPublished());
391
        $this->assertTrue($attachment3->isPublished());
392
393
        // Unpublish leaf node
394
        $this->assertTrue($attachment3->doUnpublish());
395
396
        // Now all owning objects (only) are unpublished
397
        $this->assertFalse($attachment3->isPublished()); // Unpublished because we just unpublished it
398
        $this->assertFalse($related2->isPublished()); // Unpublished because it owns attachment3
399
        $this->assertFalse($subclass2->isPublished()); // Unpublished ecause it owns related2
400
        $this->assertTrue($relatedMany4->isPublished()); // Stays live because recursion only affects owners not owned.
401
    }
402
403
    public function testRecursiveArchive()
404
    {
405
        // When archiving an object, any published owners should be unpublished at the same time
406
        // but NOT achived
407
408
        /** @var VersionedOwnershipTest\Attachment $attachment3 */
409
        $attachment3 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment3_published');
410
        $attachment3ID = $attachment3->ID;
411
        $this->assertTrue($attachment3->doArchive());
412
413
        // This object is on neither stage nor live
414
        $stageAttachment = Versioned::get_by_stage(VersionedOwnershipTest\Attachment::class, Versioned::DRAFT)
415
            ->byID($attachment3ID);
416
        $liveAttachment = Versioned::get_by_stage(VersionedOwnershipTest\Attachment::class, Versioned::LIVE)
417
            ->byID($attachment3ID);
418
        $this->assertEmpty($stageAttachment);
419
        $this->assertEmpty($liveAttachment);
420
421
        // Owning object is unpublished only
422
        /** @var VersionedOwnershipTest\Related $stageOwner */
423
        $stageOwner = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
424
        $this->assertTrue($stageOwner->isOnDraft());
425
        $this->assertFalse($stageOwner->isPublished());
426
427
        // Bottom level owning object is also unpublished
428
        /** @var VersionedOwnershipTest\Subclass $stageTopOwner */
429
        $stageTopOwner = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
430
        $this->assertTrue($stageTopOwner->isOnDraft());
431
        $this->assertFalse($stageTopOwner->isPublished());
432
    }
433
434
    public function testRecursiveRevertToLive()
435
    {
436
        /** @var VersionedOwnershipTest\Subclass $parent */
437
        $parent = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass1_published');
438
        $parentID = $parent->ID;
439
        $banner1 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany1_published');
440
        $banner2 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany2_published');
441
        $banner2ID = $banner2->ID;
442
443
        // Modify, Add, and Delete banners on stage
444
        $banner1->Title = 'Renamed Banner 1';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\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...
445
        $banner1->write();
446
447
        $banner2->delete();
448
449
        $banner4 = new VersionedOwnershipTest\RelatedMany();
450
        $banner4->Title = 'New Banner';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\...ershipTest\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...
451
        $banner4->write();
452
        $parent->Banners()->add($banner4);
453
454
        // Check state of objects before publish
455
        $liveBanners = [
456
            ['Title' => 'Related Many 1'],
457
            ['Title' => 'Related Many 2'],
458
        ];
459
        $modifiedBanners = [
460
            ['Title' => 'Renamed Banner 1'], // Renamed
461
            ['Title' => 'Related Many 3'], // Published without changes
462
            ['Title' => 'New Banner'], // Created
463
        ];
464
        $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
465
            ->byID($parentID);
466
        $this->assertDOSEquals($modifiedBanners, $parentDraft->Banners());
467
        $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
468
            ->byID($parentID);
469
        $this->assertDOSEquals($liveBanners, $parentLive->Banners());
470
471
        // When reverting parent, all records should be put back on stage
472
        $this->assertTrue($parent->doRevertToLive());
473
474
        // Now check each object has the correct state
475
        $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
476
            ->byID($parentID);
477
        $this->assertDOSEquals($liveBanners, $parentDraft->Banners());
478
        $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
479
            ->byID($parentID);
480
        $this->assertDOSEquals($liveBanners, $parentLive->Banners());
481
482
        // Check that the newly created banner, even though it still exist, has been
483
        // unlinked from the reverted draft record
484
        /** @var VersionedOwnershipTest\RelatedMany $banner4Draft */
485
        $banner4Draft = Versioned::get_by_stage(VersionedOwnershipTest\RelatedMany::class, Versioned::DRAFT)
486
            ->byID($banner4->ID);
487
        $this->assertTrue($banner4Draft->isOnDraft());
488
        $this->assertFalse($banner4Draft->isPublished());
489
        $this->assertEmpty($banner4Draft->PageID);
490
    }
491
492
    /**
493
     * Test that rolling back to a single version works recursively
494
     */
495
    public function testRecursiveRollback()
496
    {
497
        /** @var VersionedOwnershipTest\Subclass $subclass2 */
498
        $this->sleep(1);
499
        $subclass2 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
500
501
        // Create a few new versions
502
        $versions = [];
503
        for ($version = 1; $version <= 3; $version++) {
504
            // Write owned objects
505
            $this->sleep(1);
506
            foreach ($subclass2->findOwned(true) as $obj) {
507
                $obj->Title .= " - v{$version}";
508
                $obj->write();
509
            }
510
            // Write parent
511
            $this->sleep(1);
512
            $subclass2->Title .= " - v{$version}";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\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...
513
            $subclass2->write();
514
            $versions[$version] = $subclass2->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<SilverStripe\ORM\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...
515
        }
516
517
518
        // Check reverting to first version
519
        $this->sleep(1);
520
        $subclass2->doRollbackTo($versions[1]);
521
        /** @var VersionedOwnershipTest\Subclass $subclass2Draft */
522
        $subclass2Draft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
523
            ->byID($subclass2->ID);
524
        $this->assertEquals('Subclass 2 - v1', $subclass2Draft->Title);
525
        $this->assertDOSEquals(
526
            [
527
                ['Title' => 'Related 2 - v1'],
528
                ['Title' => 'Attachment 3 - v1'],
529
                ['Title' => 'Attachment 4 - v1'],
530
                ['Title' => 'Attachment 5 - v1'],
531
                ['Title' => 'Related Many 4 - v1'],
532
            ],
533
            $subclass2Draft->findOwned(true)
534
        );
535
536
        // Check rolling forward to a later version
537
        $this->sleep(1);
538
        $subclass2->doRollbackTo($versions[3]);
539
        /** @var VersionedOwnershipTest\Subclass $subclass2Draft */
540
        $subclass2Draft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
541
            ->byID($subclass2->ID);
542
        $this->assertEquals('Subclass 2 - v1 - v2 - v3', $subclass2Draft->Title);
543
        $this->assertDOSEquals(
544
            [
545
                ['Title' => 'Related 2 - v1 - v2 - v3'],
546
                ['Title' => 'Attachment 3 - v1 - v2 - v3'],
547
                ['Title' => 'Attachment 4 - v1 - v2 - v3'],
548
                ['Title' => 'Attachment 5 - v1 - v2 - v3'],
549
                ['Title' => 'Related Many 4 - v1 - v2 - v3'],
550
            ],
551
            $subclass2Draft->findOwned(true)
552
        );
553
554
        // And rolling back one version
555
        $this->sleep(1);
556
        $subclass2->doRollbackTo($versions[2]);
557
        /** @var VersionedOwnershipTest\Subclass $subclass2Draft */
558
        $subclass2Draft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
559
            ->byID($subclass2->ID);
560
        $this->assertEquals('Subclass 2 - v1 - v2', $subclass2Draft->Title);
561
        $this->assertDOSEquals(
562
            [
563
                ['Title' => 'Related 2 - v1 - v2'],
564
                ['Title' => 'Attachment 3 - v1 - v2'],
565
                ['Title' => 'Attachment 4 - v1 - v2'],
566
                ['Title' => 'Attachment 5 - v1 - v2'],
567
                ['Title' => 'Related Many 4 - v1 - v2'],
568
            ],
569
            $subclass2Draft->findOwned(true)
570
        );
571
    }
572
573
    /**
574
     * Test that you can find owners without owned_by being defined explicitly
575
     */
576
    public function testInferedOwners()
577
    {
578
        // Make sure findOwned() works
579
        /** @var VersionedOwnershipTest\TestPage $page1 */
580
        $page1 = $this->objFromFixture(VersionedOwnershipTest\TestPage::class, 'page1_published');
581
        /** @var VersionedOwnershipTest\TestPage $page2 */
582
        $page2 = $this->objFromFixture(VersionedOwnershipTest\TestPage::class, 'page2_published');
583
        $this->assertDOSEquals(
584
            [
585
                ['Title' => 'Banner 1'],
586
                ['Title' => 'Image 1'],
587
                ['Title' => 'Custom 1'],
588
            ],
589
            $page1->findOwned()
590
        );
591
        $this->assertDOSEquals(
592
            [
593
                ['Title' => 'Banner 2'],
594
                ['Title' => 'Banner 3'],
595
                ['Title' => 'Image 1'],
596
                ['Title' => 'Image 2'],
597
                ['Title' => 'Custom 2'],
598
            ],
599
            $page2->findOwned()
600
        );
601
602
        // Check that findOwners works
603
        /** @var VersionedOwnershipTest\Image $image1 */
604
        $image1 = $this->objFromFixture(VersionedOwnershipTest\Image::class, 'image1_published');
605
        /** @var VersionedOwnershipTest\Image $image2 */
606
        $image2 = $this->objFromFixture(VersionedOwnershipTest\Image::class, 'image2_published');
607
608
        $this->assertDOSEquals(
609
            [
610
                ['Title' => 'Banner 1'],
611
                ['Title' => 'Banner 2'],
612
                ['Title' => 'Page 1'],
613
                ['Title' => 'Page 2'],
614
            ],
615
            $image1->findOwners()
616
        );
617
        $this->assertDOSEquals(
618
            [
619
                ['Title' => 'Banner 1'],
620
                ['Title' => 'Banner 2'],
621
            ],
622
            $image1->findOwners(false)
623
        );
624
        $this->assertDOSEquals(
625
            [
626
                ['Title' => 'Banner 3'],
627
                ['Title' => 'Page 2'],
628
            ],
629
            $image2->findOwners()
630
        );
631
        $this->assertDOSEquals(
632
            [
633
                ['Title' => 'Banner 3'],
634
            ],
635
            $image2->findOwners(false)
636
        );
637
638
        // Test custom relation can findOwners()
639
        /** @var VersionedOwnershipTest\CustomRelation $custom1 */
640
        $custom1 = $this->objFromFixture(VersionedOwnershipTest\CustomRelation::class, 'custom1_published');
641
        $this->assertDOSEquals(
642
            [['Title' => 'Page 1']],
643
            $custom1->findOwners()
644
        );
645
    }
646
}
647