Completed
Pull Request — master (#6724)
by Damian
09:07
created

ChangeSetTest::testDescription()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 10
rs 9.4285
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 testDescription()
108
    {
109
        $cs = new ChangeSet();
110
        $cs->write();
111
        $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...
112
        $this->assertEquals('one item', $cs->getDescription());
113
114
        $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...
115
        $this->assertEquals('2 items', $cs->getDescription());
116
    }
117
118
    public function testRepeatedSyncIsNOP()
119
    {
120
        $this->publishAllFixtures();
121
122
        $cs = new ChangeSet();
123
        $cs->write();
124
125
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
126
        $cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 125 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...
127
128
        $cs->sync();
129
        $this->assertChangeSetLooksLike(
130
            $cs,
131
            [
132
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
133
            ]
134
        );
135
136
        $cs->sync();
137
        $this->assertChangeSetLooksLike(
138
            $cs,
139
            [
140
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
141
            ]
142
        );
143
    }
144
145
    public function testSync()
146
    {
147
        $this->publishAllFixtures();
148
149
        $cs = new ChangeSet();
150
        $cs->write();
151
152
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
153
154
        $cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 152 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...
155
        $cs->sync();
156
157
        $this->assertChangeSetLooksLike(
158
            $cs,
159
            [
160
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
161
            ]
162
        );
163
164
        $end = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
165
        $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...
166
        $end->write();
167
168
        $cs->sync();
169
170
        $this->assertChangeSetLooksLike(
171
            $cs,
172
            [
173
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY,
174
            ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::IMPLICITLY
175
            ]
176
        );
177
178
        $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 152 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...
179
        $endItem = ChangeSetItem::get_for_object($end)->first();
180
181
        $this->assertEquals(
182
            [$baseItem->ID],
183
            $endItem->ReferencedBy()->column("ID")
184
        );
185
186
        $this->assertDOSEquals(
187
            [
188
            [
189
                'Added' => ChangeSetItem::EXPLICITLY,
190
                'ObjectClass' => ChangeSetTest\BaseObject::class,
191
                'ObjectID' => $base->ID,
192
                'ChangeSetID' => $cs->ID
193
            ]
194
            ],
195
            $endItem->ReferencedBy()
196
        );
197
    }
198
199
    /**
200
     * Test that sync includes implicit items
201
     */
202
    public function testIsSynced()
203
    {
204
        $this->publishAllFixtures();
205
206
        $cs = new ChangeSet();
207
        $cs->write();
208
209
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
210
        $cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 209 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...
211
212
        $cs->sync();
213
        $this->assertChangeSetLooksLike(
214
            $cs,
215
            [
216
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
217
            ]
218
        );
219
        $this->assertTrue($cs->isSynced());
220
221
        $end = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
222
        $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...
223
        $end->write();
224
        $this->assertFalse($cs->isSynced());
225
226
        $cs->sync();
227
228
        $this->assertChangeSetLooksLike(
229
            $cs,
230
            [
231
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY,
232
            ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::IMPLICITLY
233
            ]
234
        );
235
        $this->assertTrue($cs->isSynced());
236
    }
237
238
    public function testCanPublish()
239
    {
240
        // Create changeset containing all items (unpublished)
241
        $this->logInWithPermission('ADMIN');
242
        $changeSet = new ChangeSet();
243
        $changeSet->write();
244
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
245
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 244 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...
246
        $changeSet->sync();
247
        $this->assertEquals(5, $changeSet->Changes()->count());
248
249
        // Test un-authenticated user cannot publish
250
        Session::clear("loggedInAs");
251
        $this->assertFalse($changeSet->canPublish());
252
253
        // campaign admin only permission doesn't grant publishing rights
254
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
255
        $this->assertFalse($changeSet->canPublish());
256
257
        // With model publish permissions only publish is allowed
258
        $this->logInWithPermission('PERM_canPublish');
259
        $this->assertTrue($changeSet->canPublish());
260
261
        // Test user with the necessary minimum permissions can login
262
        $this->logInWithPermission(
263
            [
264
            'CMS_ACCESS_CampaignAdmin',
265
            'PERM_canPublish'
266
            ]
267
        );
268
        $this->assertTrue($changeSet->canPublish());
269
    }
270
271
    public function testHasChanges()
272
    {
273
        // Create changeset containing all items (unpublished)
274
        Versioned::set_stage(Versioned::DRAFT);
275
        $this->logInWithPermission('ADMIN');
276
        $changeSet = new ChangeSet();
277
        $changeSet->write();
278
        $base = new ChangeSetTest\BaseObject();
279
        $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...
280
        $base->write();
281
        $changeSet->addObject($base);
282
283
        // New changeset with changes can be published
284
        $this->assertTrue($changeSet->canPublish());
285
        $this->assertTrue($changeSet->hasChanges());
286
287
        // Writing the record to live dissolves the changes in this changeset
288
        $base->publishSingle();
289
        $this->assertTrue($changeSet->canPublish());
290
        $this->assertFalse($changeSet->hasChanges());
291
292
        // Changeset can be safely published without error
293
        $changeSet->publish();
294
    }
295
296
    public function testCanRevert()
297
    {
298
        $this->markTestSkipped("Requires ChangeSet::revert to be implemented first");
299
    }
300
301
    public function testCanEdit()
302
    {
303
        // Create changeset containing all items (unpublished)
304
        $this->logInWithPermission('ADMIN');
305
        $changeSet = new ChangeSet();
306
        $changeSet->write();
307
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
308
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 307 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...
309
        $changeSet->sync();
310
        $this->assertEquals(5, $changeSet->Changes()->count());
311
312
        // Check canEdit
313
        Session::clear("loggedInAs");
314
        $this->assertFalse($changeSet->canEdit());
315
        $this->logInWithPermission('SomeWrongPermission');
316
        $this->assertFalse($changeSet->canEdit());
317
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
318
        $this->assertTrue($changeSet->canEdit());
319
    }
320
321
    public function testCanCreate()
322
    {
323
        // Check canCreate
324
        Session::clear("loggedInAs");
325
        $this->assertFalse(ChangeSet::singleton()->canCreate());
326
        $this->logInWithPermission('SomeWrongPermission');
327
        $this->assertFalse(ChangeSet::singleton()->canCreate());
328
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
329
        $this->assertTrue(ChangeSet::singleton()->canCreate());
330
    }
331
332
    public function testCanDelete()
333
    {
334
        // Create changeset containing all items (unpublished)
335
        $this->logInWithPermission('ADMIN');
336
        $changeSet = new ChangeSet();
337
        $changeSet->write();
338
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
339
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 338 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...
340
        $changeSet->sync();
341
        $this->assertEquals(5, $changeSet->Changes()->count());
342
343
        // Check canDelete
344
        Session::clear("loggedInAs");
345
        $this->assertFalse($changeSet->canDelete());
346
        $this->logInWithPermission('SomeWrongPermission');
347
        $this->assertFalse($changeSet->canDelete());
348
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
349
        $this->assertTrue($changeSet->canDelete());
350
    }
351
352
    public function testCanView()
353
    {
354
        // Create changeset containing all items (unpublished)
355
        $this->logInWithPermission('ADMIN');
356
        $changeSet = new ChangeSet();
357
        $changeSet->write();
358
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
359
        $changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture(\S...eObject::class, 'base') on line 358 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...
360
        $changeSet->sync();
361
        $this->assertEquals(5, $changeSet->Changes()->count());
362
363
        // Check canView
364
        Session::clear("loggedInAs");
365
        $this->assertFalse($changeSet->canView());
366
        $this->logInWithPermission('SomeWrongPermission');
367
        $this->assertFalse($changeSet->canView());
368
        $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
369
        $this->assertTrue($changeSet->canView());
370
    }
371
372
    public function testPublish()
373
    {
374
        $this->publishAllFixtures();
375
376
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
377
        $baseID = $base->ID;
378
        $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...
379
        $end1 = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
380
        $end1ID = $end1->ID;
381
        $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...
382
383
        // Create a new changest
384
        $changeset = new ChangeSet();
385
        $changeset->write();
386
        $changeset->addObject($base);
387
        $changeset->addObject($end1);
388
389
        // Make a lot of changes
390
        // - ChangeSetTest_Base.base modified
391
        // - ChangeSetTest_End.end1 deleted
392
        // - new ChangeSetTest_Mid added
393
        $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...
394
        $base->write();
395
        $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...
396
        $midNew = new ChangeSetTest\MidObject();
397
        $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...
398
        $midNew->write();
399
        $midNewID = $midNew->ID;
400
        $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...
401
        $end1->delete();
402
403
        $changeset->addObject($midNew);
404
405
        // Publish
406
        $this->logInWithPermission('ADMIN');
407
        $this->assertTrue($changeset->canPublish());
408
        $this->assertTrue($changeset->isSynced());
409
        $changeset->publish();
410
        $this->assertEquals(ChangeSet::STATE_PUBLISHED, $changeset->State);
411
412
        // Check each item has the correct before/after version applied
413
        $baseChange = $changeset->Changes()->filter(
414
            [
415
            'ObjectClass' => ChangeSetTest\BaseObject::class,
416
            'ObjectID' => $baseID,
417
            ]
418
        )->first();
419
        $this->assertEquals((int)$baseBefore, (int)$baseChange->VersionBefore);
420
        $this->assertEquals((int)$baseAfter, (int)$baseChange->VersionAfter);
421
        $this->assertEquals((int)$baseChange->VersionBefore + 1, (int)$baseChange->VersionAfter);
422
        $this->assertEquals(
423
            (int)$baseChange->VersionAfter,
424
            (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\BaseObject::class, Versioned::LIVE, $baseID)
425
        );
426
427
        $end1Change = $changeset->Changes()->filter(
428
            [
429
            'ObjectClass' => ChangeSetTest\EndObject::class,
430
            'ObjectID' => $end1ID,
431
            ]
432
        )->first();
433
        $this->assertEquals((int)$end1Before, (int)$end1Change->VersionBefore);
434
        $this->assertEquals(0, (int)$end1Change->VersionAfter);
435
        $this->assertEquals(
436
            0,
437
            (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\EndObject::class, Versioned::LIVE, $end1ID)
438
        );
439
440
        $midNewChange = $changeset->Changes()->filter(
441
            [
442
            'ObjectClass' => ChangeSetTest\MidObject::class,
443
            'ObjectID' => $midNewID,
444
            ]
445
        )->first();
446
        $this->assertEquals(0, (int)$midNewChange->VersionBefore);
447
        $this->assertEquals((int)$midNewAfter, (int)$midNewChange->VersionAfter);
448
        $this->assertEquals(
449
            (int)$midNewAfter,
450
            (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\MidObject::class, Versioned::LIVE, $midNewID)
451
        );
452
453
        // Test trying to re-publish is blocked
454
        $this->setExpectedException(
455
            'BadMethodCallException',
456
            "ChangeSet can't be published if it has been already published or reverted."
457
        );
458
        $changeset->publish();
459
    }
460
461
    /**
462
     * Ensure that related objects are disassociated on live
463
     */
464
    public function testUnlinkDisassociated()
465
    {
466
        $this->publishAllFixtures();
467
        /**
468
 * @var BaseObject $base
469
*/
470
        $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
471
        /**
472
 * @var MidObject $mid1 $mid2
473
*/
474
        $mid1 = $this->objFromFixture(ChangeSetTest\MidObject::class, 'mid1');
475
        $mid2 = $this->objFromFixture(ChangeSetTest\MidObject::class, 'mid2');
476
477
        // Remove mid1 from stage
478
        $this->assertEquals($base->ID, $mid1->BaseID);
479
        $this->assertEquals($base->ID, $mid2->BaseID);
480
        $mid1->deleteFromStage(Versioned::DRAFT);
481
482
        // Publishing recursively should unlinkd this object
483
        $changeset = new ChangeSet();
484
        $changeset->write();
485
        $changeset->addObject($base);
486
487
        // Assert changeset only contains root object
488
        $this->assertChangeSetLooksLike(
489
            $changeset,
490
            [
491
            ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
492
            ]
493
        );
494
495
        $changeset->publish();
496
497
        // mid1 on live exists, but has BaseID set to zero
498
        $mid1Live = Versioned::get_by_stage(ChangeSetTest\MidObject::class, Versioned::LIVE)
499
            ->byID($mid1->ID);
500
        $this->assertNotNull($mid1Live);
501
        $this->assertEquals($mid1->ID, $mid1Live->ID);
502
        $this->assertEquals(0, $mid1Live->BaseID);
503
504
        // mid2 on live exists and retains BaseID
505
        $mid2Live = Versioned::get_by_stage(ChangeSetTest\MidObject::class, Versioned::LIVE)
506
            ->byID($mid2->ID);
507
        $this->assertNotNull($mid2Live);
508
        $this->assertEquals($mid2->ID, $mid2Live->ID);
509
        $this->assertEquals($base->ID, $mid2Live->BaseID);
510
    }
511
}
512