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

ChangeSetTest::testHasChanges()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 0
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SebastianBergmann\Comparator\ComparisonFailure;
6
use SilverStripe\ORM\DataObject;
7
use SilverStripe\ORM\Tests\ChangeSetTest\BaseObject;
8
use SilverStripe\ORM\Tests\ChangeSetTest\MidObject;
9
use SilverStripe\ORM\Versioning\ChangeSet;
10
use SilverStripe\ORM\Versioning\ChangeSetItem;
11
use SilverStripe\ORM\Versioning\Versioned;
12
use SilverStripe\Dev\SapphireTest;
13
use SilverStripe\Control\Session;
14
use PHPUnit_Framework_ExpectationFailedException;
15
16
/**
17
 * Test {@see ChangeSet} and {@see ChangeSetItem} models
18
 */
19
class ChangeSetTest extends SapphireTest
20
{
21
22
    protected static $fixture_file = 'ChangeSetTest.yml';
23
24
    protected $extraDataObjects = [
25
        ChangeSetTest\BaseObject::class,
26
        ChangeSetTest\MidObject::class,
27
        ChangeSetTest\EndObject::class,
28
        ChangeSetTest\EndObjectChild::class,
29
    ];
30
31
    /**
32
     * Automatically publish all objects
33
     */
34
    protected function publishAllFixtures()
35
    {
36
        $this->logInWithPermission('ADMIN');
37
        foreach ($this->fixtureFactory->getFixtures() as $class => $fixtures) {
38
            foreach ($fixtures as $handle => $id) {
39
                /**
40
 * @var Versioned|DataObject $object
41
*/
42
                $object = $this->objFromFixture($class, $handle);
43
                $object->publishSingle();
0 ignored issues
show
Bug introduced by
The method publishSingle 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...
44
            }
45
        }
46
    }
47
48
    /**
49
     * Check that the changeset includes the given items
50
     *
51
     * @param ChangeSet $cs
52
     * @param array     $match Array of object fixture keys with change type values
53
     */
54
    protected function assertChangeSetLooksLike($cs, $match)
55
    {
56
        $items = $cs->Changes()->toArray();
57
58
        foreach ($match as $key => $mode) {
59
            list($class, $identifier) = explode('.', $key);
60
            $object = $this->objFromFixture($class, $identifier);
61
62
            foreach ($items as $i => $item) {
63
                if ($item->ObjectClass == $object->baseClass()
64
                    && $item->ObjectID == $object->ID
65
                    && $item->Added == $mode
66
                ) {
67
                    unset($items[$i]);
68
                    continue 2;
69
                }
70
            }
71
72
            throw new PHPUnit_Framework_ExpectationFailedException(
73
                'Change set didn\'t include expected item',
74
                new ComparisonFailure(array('Class' => $class, 'ID' => $object->ID, 'Added' => $mode), null, "$key => $mode", '')
75
            );
76
        }
77
78
        if (count($items)) {
79
            $extra = [];
80
            foreach ($items as $item) {
81
                $extra[] = ['Class' => $item->ObjectClass, 'ID' => $item->ObjectID, 'Added' => $item->Added, 'ChangeType' => $item->getChangeType()];
82
            }
83
            throw new PHPUnit_Framework_ExpectationFailedException(
84
                'Change set included items that weren\'t expected',
85
                new ComparisonFailure(array(), $extra, '', print_r($extra, true))
86
            );
87
        }
88
    }
89
90
    public function testAddObject()
91
    {
92
        $cs = new ChangeSet();
93
        $cs->write();
94
95
        $cs->addObject($this->objFromFixture(ChangeSetTest\EndObject::class, 'end1'));
0 ignored issues
show
Bug introduced by
It seems like $this->objFromFixture(\S...dObject::class, 'end1') can be null; however, addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
96
        $cs->addObject($this->objFromFixture(ChangeSetTest\EndObjectChild::class, 'endchild1'));
0 ignored issues
show
Bug introduced by
It seems like $this->objFromFixture(\S...ld::class, 'endchild1') can be null; however, addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
97
98
        $this->assertChangeSetLooksLike(
99
            $cs,
100
            [
101
            ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::EXPLICITLY,
102
            ChangeSetTest\EndObjectChild::class.'.endchild1' => ChangeSetItem::EXPLICITLY
103
            ]
104
        );
105
    }
106
107
    public function testRepeatedSyncIsNOP()
108
    {
109
        $this->publishAllFixtures();
110
111
        $cs = new ChangeSet();
112
        $cs->write();
113
114
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
115
        $cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 114 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
116
117
        $cs->sync();
118
        $this->assertChangeSetLooksLike(
119
            $cs,
120
            [
121
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
122
            ]
123
        );
124
125
        $cs->sync();
126
        $this->assertChangeSetLooksLike(
127
            $cs,
128
            [
129
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
130
            ]
131
        );
132
    }
133
134
    public function testSync()
135
    {
136
        $this->publishAllFixtures();
137
138
        $cs = new ChangeSet();
139
        $cs->write();
140
141
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
142
143
        $cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 141 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
144
        $cs->sync();
145
146
        $this->assertChangeSetLooksLike(
147
            $cs,
148
            [
149
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
150
            ]
151
        );
152
153
        $end = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
154
        $end->Baz = 3;
0 ignored issues
show
Documentation introduced by
The property Baz 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...
155
        $end->write();
156
157
        $cs->sync();
158
159
        $this->assertChangeSetLooksLike(
160
            $cs,
161
            [
162
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY,
163
            ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::IMPLICITLY
164
            ]
165
        );
166
167
        $baseItem = ChangeSetItem::get_for_object($base)->first();
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 141 can be null; however, SilverStripe\ORM\Version...tItem::get_for_object() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
168
        $endItem = ChangeSetItem::get_for_object($end)->first();
169
170
        $this->assertEquals(
171
            [$baseItem->ID],
172
            $endItem->ReferencedBy()->column("ID")
173
        );
174
175
        $this->assertDOSEquals(
176
            [
177
            [
178
                'Added' => ChangeSetItem::EXPLICITLY,
179
                'ObjectClass' => ChangeSetTest\BaseObject::class,
180
                'ObjectID' => $base->ID,
181
                'ChangeSetID' => $cs->ID
182
            ]
183
            ],
184
            $endItem->ReferencedBy()
185
        );
186
    }
187
188
    /**
189
     * Test that sync includes implicit items
190
     */
191
    public function testIsSynced()
192
    {
193
        $this->publishAllFixtures();
194
195
        $cs = new ChangeSet();
196
        $cs->write();
197
198
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
199
        $cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 198 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
200
201
        $cs->sync();
202
        $this->assertChangeSetLooksLike(
203
            $cs,
204
            [
205
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
206
            ]
207
        );
208
        $this->assertTrue($cs->isSynced());
209
210
        $end = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
211
        $end->Baz = 3;
0 ignored issues
show
Documentation introduced by
The property Baz 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...
212
        $end->write();
213
        $this->assertFalse($cs->isSynced());
214
215
        $cs->sync();
216
217
        $this->assertChangeSetLooksLike(
218
            $cs,
219
            [
220
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY,
221
            ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::IMPLICITLY
222
            ]
223
        );
224
        $this->assertTrue($cs->isSynced());
225
    }
226
227
    public function testCanPublish()
228
    {
229
        // Create changeset containing all items (unpublished)
230
        $this->logInWithPermission('ADMIN');
231
        $changeSet = new ChangeSet();
232
        $changeSet->write();
233
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
234
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 233 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
235
        $changeSet->sync();
236
        $this->assertEquals(5, $changeSet->Changes()->count());
237
238
        // Test un-authenticated user cannot publish
239
        Session::clear("loggedInAs");
240
        $this->assertFalse($changeSet->canPublish());
241
242
        // campaign admin only permission doesn't grant publishing rights
243
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
244
        $this->assertFalse($changeSet->canPublish());
245
246
        // With model publish permissions only publish is allowed
247
        $this->logInWithPermission('PERM_canPublish');
248
        $this->assertTrue($changeSet->canPublish());
249
250
        // Test user with the necessary minimum permissions can login
251
        $this->logInWithPermission(
252
            [
253
            'CMS_ACCESS_CampaignAdmin',
254
            'PERM_canPublish'
255
            ]
256
        );
257
        $this->assertTrue($changeSet->canPublish());
258
    }
259
260
    public function testHasChanges()
261
    {
262
        // Create changeset containing all items (unpublished)
263
        Versioned::set_stage(Versioned::DRAFT);
264
        $this->logInWithPermission('ADMIN');
265
        $changeSet = new ChangeSet();
266
        $changeSet->write();
267
        $base = new ChangeSetTest\BaseObject();
268
        $base->Foo = 1;
0 ignored issues
show
Documentation introduced by
The property Foo does not exist on object<SilverStripe\ORM\...angeSetTest\BaseObject>. 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...
269
        $base->write();
270
        $changeSet->addObject($base);
271
272
        // New changeset with changes can be published
273
        $this->assertTrue($changeSet->canPublish());
274
        $this->assertTrue($changeSet->hasChanges());
275
276
        // Writing the record to live dissolves the changes in this changeset
277
        $base->publishSingle();
278
        $this->assertTrue($changeSet->canPublish());
279
        $this->assertFalse($changeSet->hasChanges());
280
281
        // Changeset can be safely published without error
282
        $changeSet->publish();
283
    }
284
285
    public function testCanRevert()
286
    {
287
        $this->markTestSkipped("Requires ChangeSet::revert to be implemented first");
288
    }
289
290
    public function testCanEdit()
291
    {
292
        // Create changeset containing all items (unpublished)
293
        $this->logInWithPermission('ADMIN');
294
        $changeSet = new ChangeSet();
295
        $changeSet->write();
296
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
297
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 296 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
298
        $changeSet->sync();
299
        $this->assertEquals(5, $changeSet->Changes()->count());
300
301
        // Check canEdit
302
        Session::clear("loggedInAs");
303
        $this->assertFalse($changeSet->canEdit());
304
        $this->logInWithPermission('SomeWrongPermission');
305
        $this->assertFalse($changeSet->canEdit());
306
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
307
        $this->assertTrue($changeSet->canEdit());
308
    }
309
310
    public function testCanCreate()
311
    {
312
        // Check canCreate
313
        Session::clear("loggedInAs");
314
        $this->assertFalse(ChangeSet::singleton()->canCreate());
315
        $this->logInWithPermission('SomeWrongPermission');
316
        $this->assertFalse(ChangeSet::singleton()->canCreate());
317
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
318
        $this->assertTrue(ChangeSet::singleton()->canCreate());
319
    }
320
321
    public function testCanDelete()
322
    {
323
        // Create changeset containing all items (unpublished)
324
        $this->logInWithPermission('ADMIN');
325
        $changeSet = new ChangeSet();
326
        $changeSet->write();
327
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
328
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 327 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
329
        $changeSet->sync();
330
        $this->assertEquals(5, $changeSet->Changes()->count());
331
332
        // Check canDelete
333
        Session::clear("loggedInAs");
334
        $this->assertFalse($changeSet->canDelete());
335
        $this->logInWithPermission('SomeWrongPermission');
336
        $this->assertFalse($changeSet->canDelete());
337
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
338
        $this->assertTrue($changeSet->canDelete());
339
    }
340
341
    public function testCanView()
342
    {
343
        // Create changeset containing all items (unpublished)
344
        $this->logInWithPermission('ADMIN');
345
        $changeSet = new ChangeSet();
346
        $changeSet->write();
347
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
348
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 347 can be null; however, SilverStripe\ORM\Versioning\ChangeSet::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
349
        $changeSet->sync();
350
        $this->assertEquals(5, $changeSet->Changes()->count());
351
352
        // Check canView
353
        Session::clear("loggedInAs");
354
        $this->assertFalse($changeSet->canView());
355
        $this->logInWithPermission('SomeWrongPermission');
356
        $this->assertFalse($changeSet->canView());
357
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
358
        $this->assertTrue($changeSet->canView());
359
    }
360
361
    public function testPublish()
362
    {
363
        $this->publishAllFixtures();
364
365
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
366
        $baseID = $base->ID;
367
        $baseBefore = $base->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...
368
        $end1 = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
369
        $end1ID = $end1->ID;
370
        $end1Before = $end1->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...
371
372
        // Create a new changest
373
        $changeset = new ChangeSet();
374
        $changeset->write();
375
        $changeset->addObject($base);
376
        $changeset->addObject($end1);
377
378
        // Make a lot of changes
379
        // - ChangeSetTest_Base.base modified
380
        // - ChangeSetTest_End.end1 deleted
381
        // - new ChangeSetTest_Mid added
382
        $base->Foo = 343;
0 ignored issues
show
Documentation introduced by
The property Foo 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...
383
        $base->write();
384
        $baseAfter = $base->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...
385
        $midNew = new ChangeSetTest\MidObject();
386
        $midNew->Bar = 39;
0 ignored issues
show
Documentation introduced by
The property Bar does not exist on object<SilverStripe\ORM\...hangeSetTest\MidObject>. 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...
387
        $midNew->write();
388
        $midNewID = $midNew->ID;
389
        $midNewAfter = $midNew->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<SilverStripe\ORM\...hangeSetTest\MidObject>. 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...
390
        $end1->delete();
391
392
        $changeset->addObject($midNew);
393
394
        // Publish
395
        $this->logInWithPermission('ADMIN');
396
        $this->assertTrue($changeset->canPublish());
397
        $this->assertTrue($changeset->isSynced());
398
        $changeset->publish();
399
        $this->assertEquals(ChangeSet::STATE_PUBLISHED, $changeset->State);
400
401
        // Check each item has the correct before/after version applied
402
        $baseChange = $changeset->Changes()->filter(
403
            [
404
            'ObjectClass' => ChangeSetTest\BaseObject::class,
405
            'ObjectID' => $baseID,
406
            ]
407
        )->first();
408
        $this->assertEquals((int)$baseBefore, (int)$baseChange->VersionBefore);
409
        $this->assertEquals((int)$baseAfter, (int)$baseChange->VersionAfter);
410
        $this->assertEquals((int)$baseChange->VersionBefore + 1, (int)$baseChange->VersionAfter);
411
        $this->assertEquals(
412
            (int)$baseChange->VersionAfter,
413
            (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\BaseObject::class, Versioned::LIVE, $baseID)
414
        );
415
416
        $end1Change = $changeset->Changes()->filter(
417
            [
418
            'ObjectClass' => ChangeSetTest\EndObject::class,
419
            'ObjectID' => $end1ID,
420
            ]
421
        )->first();
422
        $this->assertEquals((int)$end1Before, (int)$end1Change->VersionBefore);
423
        $this->assertEquals(0, (int)$end1Change->VersionAfter);
424
        $this->assertEquals(
425
            0,
426
            (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\EndObject::class, Versioned::LIVE, $end1ID)
427
        );
428
429
        $midNewChange = $changeset->Changes()->filter(
430
            [
431
            'ObjectClass' => ChangeSetTest\MidObject::class,
432
            'ObjectID' => $midNewID,
433
            ]
434
        )->first();
435
        $this->assertEquals(0, (int)$midNewChange->VersionBefore);
436
        $this->assertEquals((int)$midNewAfter, (int)$midNewChange->VersionAfter);
437
        $this->assertEquals(
438
            (int)$midNewAfter,
439
            (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\MidObject::class, Versioned::LIVE, $midNewID)
440
        );
441
442
        // Test trying to re-publish is blocked
443
        $this->setExpectedException(
444
            'BadMethodCallException',
445
            "ChangeSet can't be published if it has been already published or reverted."
446
        );
447
        $changeset->publish();
448
    }
449
450
    /**
451
     * Ensure that related objects are disassociated on live
452
     */
453
    public function testUnlinkDisassociated()
454
    {
455
        $this->publishAllFixtures();
456
        /**
457
 * @var BaseObject $base
458
*/
459
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
460
        /**
461
 * @var MidObject $mid1 $mid2
462
*/
463
        $mid1 = $this->objFromFixture(ChangeSetTest\MidObject::class, 'mid1');
464
        $mid2 = $this->objFromFixture(ChangeSetTest\MidObject::class, 'mid2');
465
466
        // Remove mid1 from stage
467
        $this->assertEquals($base->ID, $mid1->BaseID);
468
        $this->assertEquals($base->ID, $mid2->BaseID);
469
        $mid1->deleteFromStage(Versioned::DRAFT);
470
471
        // Publishing recursively should unlinkd this object
472
        $changeset = new ChangeSet();
473
        $changeset->write();
474
        $changeset->addObject($base);
475
476
        // Assert changeset only contains root object
477
        $this->assertChangeSetLooksLike(
478
            $changeset,
479
            [
480
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
481
            ]
482
        );
483
484
        $changeset->publish();
485
486
        // mid1 on live exists, but has BaseID set to zero
487
        $mid1Live = Versioned::get_by_stage(ChangeSetTest\MidObject::class, Versioned::LIVE)
488
            ->byID($mid1->ID);
489
        $this->assertNotNull($mid1Live);
490
        $this->assertEquals($mid1->ID, $mid1Live->ID);
491
        $this->assertEquals(0, $mid1Live->BaseID);
492
493
        // mid2 on live exists and retains BaseID
494
        $mid2Live = Versioned::get_by_stage(ChangeSetTest\MidObject::class, Versioned::LIVE)
495
            ->byID($mid2->ID);
496
        $this->assertNotNull($mid2Live);
497
        $this->assertEquals($mid2->ID, $mid2Live->ID);
498
        $this->assertEquals($base->ID, $mid2Live->BaseID);
499
    }
500
}
501