Completed
Push — master ( a6ff96...8afff1 )
by Daniel
10:22
created

ChangeSetItem::findReferenced()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 0
dl 0
loc 20
rs 9.2
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
	const EXPLICITLY = 'explicitly';
32
33
	const IMPLICITLY = 'implicitly';
34
35
	/** Represents an object deleted */
36
	const CHANGE_DELETED = 'deleted';
37
38
	/** Represents an object which was modified */
39
	const CHANGE_MODIFIED = 'modified';
40
41
	/** Represents an object added */
42
	const CHANGE_CREATED = 'created';
43
44
	/** Represents an object which hasn't been changed directly, but owns a modified many_many relationship. */
45
	//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...
46
47
	private static $table_name = 'ChangeSetItem';
48
49
	/**
50
	 * Represents that an object has not yet been changed, but
51
	 * should be included in this changeset as soon as any changes exist
52
	 */
53
	const CHANGE_NONE = 'none';
54
55
	private static $db = array(
56
		'VersionBefore' => 'Int',
57
		'VersionAfter'  => 'Int',
58
		'Added'         => "Enum('explicitly, implicitly', 'implicitly')"
59
	);
60
61
	private static $has_one = array(
62
		'ChangeSet' => 'SilverStripe\ORM\Versioning\ChangeSet',
63
		'Object'    => 'SilverStripe\ORM\DataObject',
64
	);
65
66
	private static $many_many = array(
67
		'ReferencedBy' => 'SilverStripe\ORM\Versioning\ChangeSetItem'
68
	);
69
70
	private static $belongs_many_many = array(
71
		'References' => 'ChangeSetItem.ReferencedBy'
72
	);
73
74
	private static $indexes = array(
75
		'ObjectUniquePerChangeSet' => array(
76
			'type' => 'unique',
77
			'value' => '"ObjectID", "ObjectClass", "ChangeSetID"'
78
		)
79
	);
80
81
	public function onBeforeWrite() {
82
		// Make sure ObjectClass refers to the base data class in the case of old or wrong code
83
		$this->ObjectClass = $this->getSchema()->baseDataClass($this->ObjectClass);
84
		parent::onBeforeWrite();
85
	}
86
87
	public function getTitle() {
88
		// Get title of modified object
89
		$object = $this->getObjectLatestVersion();
90
		if($object) {
91
			return $object->getTitle();
92
		}
93
		return $this->i18n_singular_name() . ' #' . $this->ID;
94
	}
95
96
	/**
97
	 * Get a thumbnail for this object
98
	 *
99
	 * @param int $width Preferred width of the thumbnail
100
	 * @param int $height Preferred height of the thumbnail
101
	 * @return string URL to the thumbnail, if available
102
	 */
103
	public function ThumbnailURL($width, $height) {
104
		$object = $this->getObjectLatestVersion();
105
		if($object instanceof Thumbnail) {
106
			return $object->ThumbnailURL($width, $height);
107
		}
108
		return null;
109
	}
110
111
	/**
112
	 * Get the type of change: none, created, deleted, modified, manymany
113
	 * @return string
114
	 * @throws UnexpectedDataException
115
	 */
116
	public function getChangeType() {
117
		if(!class_exists($this->ObjectClass)) {
118
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
119
		}
120
121
		// Get change versions
122
		if($this->VersionBefore || $this->VersionAfter) {
123
			$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...
124
			$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...
125
		} else {
126
			$draftVersion = Versioned::get_versionnumber_by_stage(
127
				$this->ObjectClass, Versioned::DRAFT, $this->ObjectID, false
128
			);
129
			$liveVersion = Versioned::get_versionnumber_by_stage(
130
				$this->ObjectClass, Versioned::LIVE, $this->ObjectID, false
131
			);
132
		}
133
134
		// Version comparisons
135
		if ($draftVersion == $liveVersion) {
136
			return self::CHANGE_NONE;
137
		} elseif (!$liveVersion) {
138
			return self::CHANGE_CREATED;
139
		} elseif (!$draftVersion) {
140
			return self::CHANGE_DELETED;
141
		} else {
142
			return self::CHANGE_MODIFIED;
143
		}
144
	}
145
146
	/**
147
	 * Find version of this object in the given stage
148
	 *
149
	 * @param string $stage
150
	 * @return DataObject|Versioned
151
	 * @throws UnexpectedDataException
152
	 */
153
	protected function getObjectInStage($stage) {
154
		if(!class_exists($this->ObjectClass)) {
155
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
156
		}
157
158
		return Versioned::get_by_stage($this->ObjectClass, $stage)->byID($this->ObjectID);
159
	}
160
161
	/**
162
	 * Find latest version of this object
163
	 * @return DataObject|Versioned
164
	 * @throws UnexpectedDataException
165
	 */
166
	protected function getObjectLatestVersion() {
167
		if(!class_exists($this->ObjectClass)) {
168
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
169
		}
170
171
		return Versioned::get_latest_version($this->ObjectClass, $this->ObjectID);
172
	}
173
174
	/**
175
	 * Get all implicit objects for this change
176
	 *
177
	 * @return SS_List
178
	 */
179
	public function findReferenced() {
180
		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...
181
			// If deleted from stage, need to look at live record
182
			$record = $this->getObjectInStage(Versioned::LIVE);
183
			if ($record) {
184
				return $record->findOwners(false);
185
			}
186
		} else {
187
			// If changed on stage, look at owned objects there
188
			$record = $this->getObjectInStage(Versioned::DRAFT);
189
			if ($record) {
190
				return $record->findOwned()->filterByCallback(function ($owned) {
191
					/** @var Versioned|DataObject $owned */
192
					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...
193
				});
194
			}
195
		}
196
		// Empty set
197
		return new ArrayList();
198
	}
199
200
	/**
201
	 * Publish this item, then close it.
202
	 *
203
	 * Note: Unlike Versioned::doPublish() and Versioned::doUnpublish, this action is not recursive.
204
	 */
205
	public function publish() {
206
		if(!class_exists($this->ObjectClass)) {
207
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
208
		}
209
210
		// Logical checks prior to publish
211
		if(!$this->canPublish()) {
212
			throw new Exception("The current member does not have permission to publish this ChangeSetItem.");
213
		}
214
		if($this->VersionBefore || $this->VersionAfter) {
215
			throw new BadMethodCallException("This ChangeSetItem has already been published");
216
		}
217
218
		// Record state changed
219
		$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...
220
			$this->ObjectClass, Versioned::DRAFT, $this->ObjectID, false
221
		);
222
		$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...
223
			$this->ObjectClass, Versioned::LIVE, $this->ObjectID, false
224
		);
225
226
		switch($this->getChangeType()) {
227
			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...
228
				break;
229
			}
230
			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...
231
				// Non-recursive delete
232
				$object = $this->getObjectInStage(Versioned::LIVE);
233
				$object->deleteFromStage(Versioned::LIVE);
234
				break;
235
			}
236
			case static::CHANGE_MODIFIED:
237
			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...
238
				// Non-recursive publish
239
				$object = $this->getObjectInStage(Versioned::DRAFT);
240
				$object->publishSingle();
241
				break;
242
			}
243
		}
244
245
		$this->write();
246
	}
247
248
	/**
249
	 * Once this item (and all owned objects) are published, unlink
250
	 * all disowned objects
251
	 */
252
	public function unlinkDisownedObjects() {
253
		$object = $this->getObjectInStage(Versioned::DRAFT);
254
		if ($object) {
255
			$object->unlinkDisownedObjects(Versioned::DRAFT, Versioned::LIVE);
256
		}
257
	}
258
259
	/** Reverts this item, then close it. **/
260
	public function revert() {
261
		user_error('Not implemented', E_USER_ERROR);
262
	}
263
264
	public function canView($member = null) {
265
		return $this->can(__FUNCTION__, $member);
266
	}
267
268
	public function canEdit($member = null) {
269
		return $this->can(__FUNCTION__, $member);
270
	}
271
272
	public function canCreate($member = null, $context = array()) {
273
		return $this->can(__FUNCTION__, $member, $context);
274
	}
275
276
	public function canDelete($member = null) {
277
		return $this->can(__FUNCTION__, $member);
278
	}
279
280
	/**
281
	 * Check if the BeforeVersion of this changeset can be restored to draft
282
	 *
283
	 * @param Member $member
284
	 * @return bool
285
	 */
286
	public function canRevert($member) {
287
		// Just get the best version as this object may not even exist on either stage anymore.
288
		/** @var Versioned|DataObject $object */
289
		$object = $this->getObjectLatestVersion();
290
		if(!$object) {
291
			return false;
292
		}
293
294
		// Check change type
295
		switch($this->getChangeType()) {
296
			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...
297
				// Revert creation by deleting from stage
298
				if(!$object->canDelete($member)) {
299
					return false;
300
				}
301
				break;
302
			}
303
			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...
304
				// All other actions are typically editing draft stage
305
				if(!$object->canEdit($member)) {
306
					return false;
307
				}
308
				break;
309
			}
310
		}
311
312
		// If object can be published/unpublished let extensions deny
313
		return $this->can(__FUNCTION__, $member);
314
	}
315
316
	/**
317
	 * Check if this ChangeSetItem can be published
318
	 *
319
	 * @param Member $member
320
	 * @return bool
321
	 */
322
	public function canPublish($member = null) {
323
		// Check canMethod to invoke on object
324
		switch($this->getChangeType()) {
325
			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...
326
				/** @var Versioned|DataObject $object */
327
				$object = Versioned::get_by_stage($this->ObjectClass, Versioned::LIVE)->byID($this->ObjectID);
328
				if(!$object || !$object->canUnpublish($member)) {
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 322 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...
329
					return false;
330
				}
331
				break;
332
			}
333
			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...
334
				/** @var Versioned|DataObject $object */
335
				$object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID);
336
				if(!$object || !$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...
337
					return false;
338
				}
339
				break;
340
			}
341
		}
342
343
		// If object can be published/unpublished let extensions deny
344
		return $this->can(__FUNCTION__, $member);
345
	}
346
347
	/**
348
	 * Default permissions for this ChangeSetItem
349
	 *
350
	 * @param string $perm
351
	 * @param Member $member
352
	 * @param array $context
353
	 * @return bool
354
	 */
355
	public function can($perm, $member = null, $context = array()) {
356
		if(!$member) {
357
			$member = Member::currentUser();
358
		}
359
360
		// Allow extensions to bypass default permissions, but only if
361
		// each change can be individually published.
362
		$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...
363
		if($extended !== null) {
364
			return $extended;
365
		}
366
367
		// Default permissions
368
		return (bool)Permission::checkMember($member, ChangeSet::config()->required_permission);
369
	}
370
371
	/**
372
	 * Get the ChangeSetItems that reference a passed DataObject
373
	 *
374
	 * @param DataObject $object
375
	 * @return DataList
376
	 */
377
	public static function get_for_object($object) {
378
		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...
379
			'ObjectID' => $object->ID,
380
			'ObjectClass' => $object->baseClass(),
381
		]);
382
	}
383
384
	/**
385
	 * Get the ChangeSetItems that reference a passed DataObject
386
	 *
387
	 * @param int $objectID The ID of the object
388
	 * @param string $objectClass The class of the object (or any parent class)
389
	 * @return DataList
390
	 */
391
	public static function get_for_object_by_id($objectID, $objectClass) {
392
		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...
393
			'ObjectID' => $objectID,
394
			'ObjectClass' => static::getSchema()->baseDataClass($objectClass)
395
		]);
396
	}
397
398
	/**
399
	 * Gets the list of modes this record can be previewed in.
400
	 *
401
	 * {@link https://tools.ietf.org/html/draft-kelly-json-hal-07#section-5}
402
	 *
403
	 * @return array Map of links in acceptable HAL format
404
	 */
405
	public function getPreviewLinks() {
406
		$links = [];
407
408
		// Preview draft
409
		$stage = $this->getObjectInStage(Versioned::DRAFT);
410
		if($stage instanceof CMSPreviewable && $stage->canView() && ($link = $stage->PreviewLink())) {
411
			$links[Versioned::DRAFT] = [
412
				'href' => Controller::join_links($link, '?stage=' . Versioned::DRAFT),
413
				'type' => $stage->getMimeType(),
414
			];
415
		}
416
417
		// Preview live
418
		$live = $this->getObjectInStage(Versioned::LIVE);
419
		if($live instanceof CMSPreviewable && $live->canView() && ($link = $live->PreviewLink())) {
420
			$links[Versioned::LIVE] = [
421
				'href' => Controller::join_links($link, '?stage=' . Versioned::LIVE),
422
				'type' => $live->getMimeType(),
423
			];
424
		}
425
426
		return $links;
427
	}
428
429
	/**
430
	 * Get edit link for this item
431
	 *
432
	 * @return string
433
	 */
434
	public function CMSEditLink()
435
	{
436
		$link = $this->getObjectInStage(Versioned::DRAFT);
437
		if($link instanceof CMSPreviewable) {
438
			return $link->CMSEditLink();
439
		}
440
		return null;
441
	}
442
}
443