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

ChangeSetItem::hasChange()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Versioning;
4
5
use SilverStripe\Admin\CMSPreviewable;
6
use SilverStripe\Assets\Thumbnail;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\ORM\ArrayList;
9
use SilverStripe\ORM\DataList;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\ORM\ManyManyList;
12
use SilverStripe\ORM\SS_List;
13
use SilverStripe\ORM\UnexpectedDataException;
14
use SilverStripe\Security\Member;
15
use SilverStripe\Security\Permission;
16
use BadMethodCallException;
17
use Exception;
18
19
/**
20
 * A single line in a changeset
21
 *
22
 * @property string $Added
23
 * @property string $ObjectClass The _base_ data class for the referenced DataObject
24
 * @property int $ObjectID The numeric ID for the referenced object
25
 * @method ManyManyList ReferencedBy() List of explicit items that require this change
26
 * @method ManyManyList References() List of implicit items required by this change
27
 * @method ChangeSet ChangeSet()
28
 */
29
class ChangeSetItem extends DataObject implements Thumbnail
30
{
31
32
    const EXPLICITLY = 'explicitly';
33
34
    const IMPLICITLY = 'implicitly';
35
36
    /** Represents an object deleted */
37
    const CHANGE_DELETED = 'deleted';
38
39
    /** Represents an object which was modified */
40
    const CHANGE_MODIFIED = 'modified';
41
42
    /** Represents an object added */
43
    const CHANGE_CREATED = 'created';
44
45
    /** Represents an object which hasn't been changed directly, but owns a modified many_many relationship. */
46
    //const CHANGE_MANYMANY = 'manymany';
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
47
48
    private static $table_name = 'ChangeSetItem';
49
50
    /**
51
     * Represents that an object has not yet been changed, but
52
     * should be included in this changeset as soon as any changes exist
53
     */
54
    const CHANGE_NONE = 'none';
55
56
    private static $db = array(
57
        'VersionBefore' => 'Int',
58
        'VersionAfter'  => 'Int',
59
        'Added'         => "Enum('explicitly, implicitly', 'implicitly')"
60
    );
61
62
    private static $has_one = array(
63
        'ChangeSet' => 'SilverStripe\ORM\Versioning\ChangeSet',
64
        'Object'    => 'SilverStripe\ORM\DataObject',
65
    );
66
67
    private static $many_many = array(
68
        'ReferencedBy' => 'SilverStripe\ORM\Versioning\ChangeSetItem'
69
    );
70
71
    private static $belongs_many_many = array(
72
        'References' => 'ChangeSetItem.ReferencedBy'
73
    );
74
75
    private static $indexes = array(
76
        'ObjectUniquePerChangeSet' => array(
77
            'type' => 'unique',
78
            'value' => '"ObjectID", "ObjectClass", "ChangeSetID"'
79
        )
80
    );
81
82
    public function onBeforeWrite()
83
    {
84
        // Make sure ObjectClass refers to the base data class in the case of old or wrong code
85
        $this->ObjectClass = $this->getSchema()->baseDataClass($this->ObjectClass);
86
        parent::onBeforeWrite();
87
    }
88
89
    public function getTitle()
90
    {
91
        // Get title of modified object
92
        $object = $this->getObjectLatestVersion();
93
        if ($object) {
94
            return $object->getTitle();
95
        }
96
        return $this->i18n_singular_name() . ' #' . $this->ID;
97
    }
98
99
    /**
100
     * Get a thumbnail for this object
101
     *
102
     * @param int $width Preferred width of the thumbnail
103
     * @param int $height Preferred height of the thumbnail
104
     * @return string URL to the thumbnail, if available
105
     */
106
    public function ThumbnailURL($width, $height)
107
    {
108
        $object = $this->getObjectLatestVersion();
109
        if ($object instanceof Thumbnail) {
110
            return $object->ThumbnailURL($width, $height);
111
        }
112
        return null;
113
    }
114
115
    /**
116
     * Get the type of change: none, created, deleted, modified, manymany
117
     * @return string
118
     * @throws UnexpectedDataException
119
     */
120
    public function getChangeType()
121
    {
122
        if (!class_exists($this->ObjectClass)) {
123
            throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
124
        }
125
126
        // Get change versions
127
        if ($this->VersionBefore || $this->VersionAfter) {
128
            $draftVersion = $this->VersionAfter; // After publishing draft was written to stage
0 ignored issues
show
Documentation introduced by
The property VersionAfter does not exist on object<SilverStripe\ORM\Versioning\ChangeSetItem>. 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...
129
            $liveVersion = $this->VersionBefore; // The live version before the publish
0 ignored issues
show
Documentation introduced by
The property VersionBefore does not exist on object<SilverStripe\ORM\Versioning\ChangeSetItem>. 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...
130
        } else {
131
            $draftVersion = Versioned::get_versionnumber_by_stage(
132
                $this->ObjectClass,
133
                Versioned::DRAFT,
134
                $this->ObjectID,
135
                false
136
            );
137
            $liveVersion = Versioned::get_versionnumber_by_stage(
138
                $this->ObjectClass,
139
                Versioned::LIVE,
140
                $this->ObjectID,
141
                false
142
            );
143
        }
144
145
        // Version comparisons
146
        if ($draftVersion == $liveVersion) {
147
            return self::CHANGE_NONE;
148
        } elseif (!$liveVersion) {
149
            return self::CHANGE_CREATED;
150
        } elseif (!$draftVersion) {
151
            return self::CHANGE_DELETED;
152
        } else {
153
            return self::CHANGE_MODIFIED;
154
        }
155
    }
156
157
    /**
158
     * Find version of this object in the given stage
159
     *
160
     * @param string $stage
161
     * @return DataObject|Versioned
162
     * @throws UnexpectedDataException
163
     */
164
    protected function getObjectInStage($stage)
165
    {
166
        if (!class_exists($this->ObjectClass)) {
167
            throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
168
        }
169
170
        return Versioned::get_by_stage($this->ObjectClass, $stage)->byID($this->ObjectID);
171
    }
172
173
    /**
174
     * Find latest version of this object
175
     * @return DataObject|Versioned
176
     * @throws UnexpectedDataException
177
     */
178
    protected function getObjectLatestVersion()
179
    {
180
        if (!class_exists($this->ObjectClass)) {
181
            throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
182
        }
183
184
        return Versioned::get_latest_version($this->ObjectClass, $this->ObjectID);
185
    }
186
187
    /**
188
     * Get all implicit objects for this change
189
     *
190
     * @return SS_List
191
     */
192
    public function findReferenced()
193
    {
194
        if ($this->getChangeType() === ChangeSetItem::CHANGE_DELETED) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
195
            // If deleted from stage, need to look at live record
196
            $record = $this->getObjectInStage(Versioned::LIVE);
197
            if ($record) {
198
                return $record->findOwners(false);
199
            }
200
        } else {
201
            // If changed on stage, look at owned objects there
202
            $record = $this->getObjectInStage(Versioned::DRAFT);
203
            if ($record) {
204
                return $record->findOwned()->filterByCallback(function ($owned) {
205
                    /** @var Versioned|DataObject $owned */
206
                    return $owned->stagesDiffer(Versioned::DRAFT, Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method stagesDiffer 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...
207
                });
208
            }
209
        }
210
        // Empty set
211
        return new ArrayList();
212
    }
213
214
    /**
215
     * Publish this item, then close it.
216
     *
217
     * Note: Unlike Versioned::doPublish() and Versioned::doUnpublish, this action is not recursive.
218
     */
219
    public function publish()
220
    {
221
        if (!class_exists($this->ObjectClass)) {
222
            throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
223
        }
224
225
        // Logical checks prior to publish
226
        if (!$this->canPublish()) {
227
            throw new Exception("The current member does not have permission to publish this ChangeSetItem.");
228
        }
229
        if ($this->VersionBefore || $this->VersionAfter) {
230
            throw new BadMethodCallException("This ChangeSetItem has already been published");
231
        }
232
233
        // Record state changed
234
        $this->VersionAfter = Versioned::get_versionnumber_by_stage(
0 ignored issues
show
Documentation introduced by
The property VersionAfter does not exist on object<SilverStripe\ORM\Versioning\ChangeSetItem>. 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...
235
            $this->ObjectClass,
236
            Versioned::DRAFT,
237
            $this->ObjectID,
238
            false
239
        );
240
        $this->VersionBefore = Versioned::get_versionnumber_by_stage(
0 ignored issues
show
Documentation introduced by
The property VersionBefore does not exist on object<SilverStripe\ORM\Versioning\ChangeSetItem>. 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...
241
            $this->ObjectClass,
242
            Versioned::LIVE,
243
            $this->ObjectID,
244
            false
245
        );
246
247
        switch ($this->getChangeType()) {
248
            case static::CHANGE_NONE: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
249
                break;
250
            }
251
            case static::CHANGE_DELETED: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
252
                // Non-recursive delete
253
                $object = $this->getObjectInStage(Versioned::LIVE);
254
                $object->deleteFromStage(Versioned::LIVE);
255
                break;
256
            }
257
            case static::CHANGE_MODIFIED:
258
            case static::CHANGE_CREATED: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
259
                // Non-recursive publish
260
                $object = $this->getObjectInStage(Versioned::DRAFT);
261
                $object->publishSingle();
262
                break;
263
            }
264
        }
265
266
        $this->write();
267
    }
268
269
    /**
270
     * Once this item (and all owned objects) are published, unlink
271
     * all disowned objects
272
     */
273
    public function unlinkDisownedObjects()
274
    {
275
        $object = $this->getObjectInStage(Versioned::DRAFT);
276
        if ($object) {
277
            $object->unlinkDisownedObjects(Versioned::DRAFT, Versioned::LIVE);
278
        }
279
    }
280
281
    /** Reverts this item, then close it. **/
282
    public function revert()
283
    {
284
        user_error('Not implemented', E_USER_ERROR);
285
    }
286
287
    public function canView($member = null)
288
    {
289
        return $this->can(__FUNCTION__, $member);
290
    }
291
292
    public function canEdit($member = null)
293
    {
294
        return $this->can(__FUNCTION__, $member);
295
    }
296
297
    public function canCreate($member = null, $context = array())
298
    {
299
        return $this->can(__FUNCTION__, $member, $context);
300
    }
301
302
    public function canDelete($member = null)
303
    {
304
        return $this->can(__FUNCTION__, $member);
305
    }
306
307
    /**
308
     * Check if the BeforeVersion of this changeset can be restored to draft
309
     *
310
     * @param Member $member
311
     * @return bool
312
     */
313
    public function canRevert($member)
314
    {
315
        // Just get the best version as this object may not even exist on either stage anymore.
316
        /** @var Versioned|DataObject $object */
317
        $object = $this->getObjectLatestVersion();
318
        if (!$object) {
319
            return false;
320
        }
321
322
        // Check change type
323
        switch ($this->getChangeType()) {
324
            case static::CHANGE_CREATED: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
325
                // Revert creation by deleting from stage
326
                return $object->canDelete($member);
327
            }
328
            default: {
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
329
                // All other actions are typically editing draft stage
330
                return $object->canEdit($member);
331
            }
332
        }
333
    }
334
335
    /**
336
     * Check if this ChangeSetItem can be published
337
     *
338
     * @param Member $member
339
     * @return bool
340
     */
341
    public function canPublish($member = null)
342
    {
343
        // Check canMethod to invoke on object
344
        switch ($this->getChangeType()) {
345
            case static::CHANGE_DELETED: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
346
                /** @var Versioned|DataObject $object */
347
                $object = Versioned::get_by_stage($this->ObjectClass, Versioned::LIVE)->byID($this->ObjectID);
348
                if ($object) {
349
                    return $object->canUnpublish($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 341 can also be of type object<SilverStripe\Security\Member>; however, SilverStripe\ORM\Version...rsioned::canUnpublish() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
The method canUnpublish 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...
350
                }
351
                break;
352
            }
353
            default: {
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
354
                /** @var Versioned|DataObject $object */
355
                $object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID);
356
                if ($object) {
357
                    return $object->canPublish($member);
0 ignored issues
show
Bug introduced by
The method canPublish 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...
358
                }
359
                break;
360
            }
361
        }
362
363
        return true;
364
    }
365
366
    /**
367
     * Determine if this item has changes
368
     *
369
     * @return bool
370
     */
371
    public function hasChange()
372
    {
373
        return $this->getChangeType() !== ChangeSetItem::CHANGE_NONE;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
374
    }
375
376
    /**
377
     * Default permissions for this ChangeSetItem
378
     *
379
     * @param string $perm
380
     * @param Member $member
381
     * @param array $context
382
     * @return bool
383
     */
384
    public function can($perm, $member = null, $context = array())
385
    {
386
        if (!$member) {
387
            $member = Member::currentUser();
388
        }
389
390
        // Allow extensions to bypass default permissions, but only if
391
        // each change can be individually published.
392
        $extended = $this->extendedCan($perm, $member, $context);
0 ignored issues
show
Bug introduced by
It seems like $member can be null; however, extendedCan() 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...
393
        if ($extended !== null) {
394
            return $extended;
395
        }
396
397
        // Default permissions
398
        return (bool)Permission::checkMember($member, ChangeSet::config()->required_permission);
399
    }
400
401
    /**
402
     * Get the ChangeSetItems that reference a passed DataObject
403
     *
404
     * @param DataObject $object
405
     * @return DataList
406
     */
407
    public static function get_for_object($object)
408
    {
409
        return ChangeSetItem::get()->filter([
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
410
            'ObjectID' => $object->ID,
411
            'ObjectClass' => $object->baseClass(),
412
        ]);
413
    }
414
415
    /**
416
     * Get the ChangeSetItems that reference a passed DataObject
417
     *
418
     * @param int $objectID The ID of the object
419
     * @param string $objectClass The class of the object (or any parent class)
420
     * @return DataList
421
     */
422
    public static function get_for_object_by_id($objectID, $objectClass)
423
    {
424
        return ChangeSetItem::get()->filter([
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
425
            'ObjectID' => $objectID,
426
            'ObjectClass' => static::getSchema()->baseDataClass($objectClass)
427
        ]);
428
    }
429
430
    /**
431
     * Gets the list of modes this record can be previewed in.
432
     *
433
     * {@link https://tools.ietf.org/html/draft-kelly-json-hal-07#section-5}
434
     *
435
     * @return array Map of links in acceptable HAL format
436
     */
437
    public function getPreviewLinks()
438
    {
439
        $links = [];
440
441
        // Preview draft
442
        $stage = $this->getObjectInStage(Versioned::DRAFT);
443
        if ($stage instanceof CMSPreviewable && $stage->canView() && ($link = $stage->PreviewLink())) {
444
            $links[Versioned::DRAFT] = [
445
                'href' => Controller::join_links($link, '?stage=' . Versioned::DRAFT),
446
                'type' => $stage->getMimeType(),
447
            ];
448
        }
449
450
        // Preview live
451
        $live = $this->getObjectInStage(Versioned::LIVE);
452
        if ($live instanceof CMSPreviewable && $live->canView() && ($link = $live->PreviewLink())) {
453
            $links[Versioned::LIVE] = [
454
                'href' => Controller::join_links($link, '?stage=' . Versioned::LIVE),
455
                'type' => $live->getMimeType(),
456
            ];
457
        }
458
459
        return $links;
460
    }
461
462
    /**
463
     * Get edit link for this item
464
     *
465
     * @return string
466
     */
467
    public function CMSEditLink()
468
    {
469
        $link = $this->getObjectInStage(Versioned::DRAFT);
470
        if ($link instanceof CMSPreviewable) {
471
            return $link->CMSEditLink();
472
        }
473
        return null;
474
    }
475
}
476