Completed
Push — campaignadmin-error-check ( ad9c82 )
by Sam
10:29
created

ChangeSetItem::getObjectLatestVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 7
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 0
dl 7
loc 7
rs 9.4285
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\DataList;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\ORM\ManyManyList;
11
use SilverStripe\ORM\SS_List;
12
use SilverStripe\ORM\UnexpectedDataException;
13
use SilverStripe\Security\Member;
14
use SilverStripe\Security\Permission;
15
use BadMethodCallException;
16
use Exception;
17
18
/**
19
 * A single line in a changeset
20
 *
21
 * @property string $Added
22
 * @property string $ObjectClass The _base_ data class for the referenced DataObject
23
 * @property int $ObjectID The numeric ID for the referenced object
24
 * @method ManyManyList ReferencedBy() List of explicit items that require this change
25
 * @method ManyManyList References() List of implicit items required by this change
26
 * @method ChangeSet ChangeSet()
27
 */
28
class ChangeSetItem extends DataObject implements Thumbnail {
29
30
	const EXPLICITLY = 'explicitly';
31
32
	const IMPLICITLY = 'implicitly';
33
34
	/** Represents an object deleted */
35
	const CHANGE_DELETED = 'deleted';
36
37
	/** Represents an object which was modified */
38
	const CHANGE_MODIFIED = 'modified';
39
40
	/** Represents an object added */
41
	const CHANGE_CREATED = 'created';
42
43
	/** Represents an object which hasn't been changed directly, but owns a modified many_many relationship. */
44
	//const CHANGE_MANYMANY = 'manymany';
45
46
	private static $table_name = 'ChangeSetItem';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
47
48
	/**
49
	 * Represents that an object has not yet been changed, but
50
	 * should be included in this changeset as soon as any changes exist
51
	 */
52
	const CHANGE_NONE = 'none';
53
54
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
55
		'VersionBefore' => 'Int',
56
		'VersionAfter'  => 'Int',
57
		'Added'         => "Enum('explicitly, implicitly', 'implicitly')"
58
	);
59
60
	private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
61
		'ChangeSet' => 'SilverStripe\ORM\Versioning\ChangeSet',
62
		'Object'    => 'SilverStripe\ORM\DataObject',
63
	);
64
65
	private static $many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
66
		'ReferencedBy' => 'SilverStripe\ORM\Versioning\ChangeSetItem'
67
	);
68
69
	private static $belongs_many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
70
		'References' => 'ChangeSetItem.ReferencedBy'
71
	);
72
73
	private static $indexes = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
74
		'ObjectUniquePerChangeSet' => array(
75
			'type' => 'unique',
76
			'value' => '"ObjectID", "ObjectClass", "ChangeSetID"'
77
		)
78
	);
79
80
	public function onBeforeWrite() {
81
		// Make sure ObjectClass refers to the base data class in the case of old or wrong code
82
		$this->ObjectClass = $this->getSchema()->baseDataClass($this->ObjectClass);
83
		parent::onBeforeWrite();
84
	}
85
86
	public function getTitle() {
87
		// Get title of modified object
88
		$object = $this->getObjectLatestVersion();
89
		if($object) {
90
			return $object->getTitle();
91
		}
92
		return $this->i18n_singular_name() . ' #' . $this->ID;
93
	}
94
95
	/**
96
	 * Get a thumbnail for this object
97
	 *
98
	 * @param int $width Preferred width of the thumbnail
99
	 * @param int $height Preferred height of the thumbnail
100
	 * @return string URL to the thumbnail, if available
101
	 */
102
	public function ThumbnailURL($width, $height) {
103
		$object = $this->getObjectLatestVersion();
104
		if($object instanceof Thumbnail) {
105
			return $object->ThumbnailURL($width, $height);
106
		}
107
		return null;
108
	}
109
110
	/**
111
	 * Get the type of change: none, created, deleted, modified, manymany
112
	 *
113
	 * @return string
114
	 */
115
	public function getChangeType() {
116
		if(!class_exists($this->ObjectClass)) {
117
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
118
		}
119
120
		// Get change versions
121
		if($this->VersionBefore || $this->VersionAfter) {
122
			$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...
123
			$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...
124
		} else {
125
			$draftVersion = Versioned::get_versionnumber_by_stage(
126
				$this->ObjectClass, Versioned::DRAFT, $this->ObjectID, false
127
			);
128
			$liveVersion = Versioned::get_versionnumber_by_stage(
129
				$this->ObjectClass, Versioned::LIVE, $this->ObjectID, false
130
			);
131
		}
132
133
		// Version comparisons
134
		if ($draftVersion == $liveVersion) {
135
			return self::CHANGE_NONE;
136
		} elseif (!$liveVersion) {
137
			return self::CHANGE_CREATED;
138
		} elseif (!$draftVersion) {
139
			return self::CHANGE_DELETED;
140
		} else {
141
			return self::CHANGE_MODIFIED;
142
		}
143
	}
144
145
	/**
146
	 * Find version of this object in the given stage
147
	 *
148
	 * @param string $stage
149
	 * @return Versioned|DataObject
150
	 */
151 View Code Duplication
	protected function getObjectInStage($stage) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
		if(!class_exists($this->ObjectClass)) {
153
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
154
		}
155
156
		return Versioned::get_by_stage($this->ObjectClass, $stage)->byID($this->ObjectID);
157
	}
158
159
	/**
160
	 * Find latest version of this object
161
	 *
162
	 * @return Versioned|DataObject
163
	 */
164 View Code Duplication
	protected function getObjectLatestVersion() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
		if(!class_exists($this->ObjectClass)) {
166
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
167
		}
168
169
		return Versioned::get_latest_version($this->ObjectClass, $this->ObjectID);
170
	}
171
172
	/**
173
	 * Get all implicit objects for this change
174
	 *
175
	 * @return SS_List
176
	 */
177
	public function findReferenced() {
178
		if($this->getChangeType() === ChangeSetItem::CHANGE_DELETED) {
179
			// If deleted from stage, need to look at live record
180
			return $this->getObjectInStage(Versioned::LIVE)->findOwners(false);
181
		} else {
182
			// If changed on stage, look at owned objects there
183
			return $this->getObjectInStage(Versioned::DRAFT)->findOwned()->filterByCallback(function ($owned) {
184
				/** @var Versioned|DataObject $owned */
185
				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...
186
			});
187
		}
188
	}
189
190
	/**
191
	 * Publish this item, then close it.
192
	 *
193
	 * Note: Unlike Versioned::doPublish() and Versioned::doUnpublish, this action is not recursive.
194
	 */
195
	public function publish() {
196
		if(!class_exists($this->ObjectClass)) {
197
			throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
198
		}
199
200
		// Logical checks prior to publish
201
		if(!$this->canPublish()) {
202
			throw new Exception("The current member does not have permission to publish this ChangeSetItem.");
203
		}
204
		if($this->VersionBefore || $this->VersionAfter) {
205
			throw new BadMethodCallException("This ChangeSetItem has already been published");
206
		}
207
208
		// Record state changed
209
		$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...
210
			$this->ObjectClass, Versioned::DRAFT, $this->ObjectID, false
211
		);
212
		$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...
213
			$this->ObjectClass, Versioned::LIVE, $this->ObjectID, false
214
		);
215
216
		switch($this->getChangeType()) {
217
			case static::CHANGE_NONE: {
218
				break;
219
			}
220
			case static::CHANGE_DELETED: {
221
				// Non-recursive delete
222
				$object = $this->getObjectInStage(Versioned::LIVE);
223
				$object->deleteFromStage(Versioned::LIVE);
224
				break;
225
			}
226
			case static::CHANGE_MODIFIED:
227
			case static::CHANGE_CREATED: {
228
				// Non-recursive publish
229
				$object = $this->getObjectInStage(Versioned::DRAFT);
230
				$object->publishSingle();
231
				break;
232
			}
233
		}
234
235
		$this->write();
236
	}
237
238
	/** Reverts this item, then close it. **/
239
	public function revert() {
240
		user_error('Not implemented', E_USER_ERROR);
241
	}
242
243
	public function canView($member = null) {
244
		return $this->can(__FUNCTION__, $member);
245
	}
246
247
	public function canEdit($member = null) {
248
		return $this->can(__FUNCTION__, $member);
249
	}
250
251
	public function canCreate($member = null, $context = array()) {
252
		return $this->can(__FUNCTION__, $member, $context);
253
	}
254
255
	public function canDelete($member = null) {
256
		return $this->can(__FUNCTION__, $member);
257
	}
258
259
	/**
260
	 * Check if the BeforeVersion of this changeset can be restored to draft
261
	 *
262
	 * @param Member $member
263
	 * @return bool
264
	 */
265
	public function canRevert($member) {
266
		// Just get the best version as this object may not even exist on either stage anymore.
267
		/** @var Versioned|DataObject $object */
268
		$object = $this->getObjectLatestVersion();
269
		if(!$object) {
270
			return false;
271
		}
272
273
		// Check change type
274
		switch($this->getChangeType()) {
275
			case static::CHANGE_CREATED: {
276
				// Revert creation by deleting from stage
277
				if(!$object->canDelete($member)) {
278
					return false;
279
				}
280
				break;
281
			}
282
			default: {
283
				// All other actions are typically editing draft stage
284
				if(!$object->canEdit($member)) {
285
					return false;
286
				}
287
				break;
288
			}
289
		}
290
291
		// If object can be published/unpublished let extensions deny
292
		return $this->can(__FUNCTION__, $member);
293
	}
294
295
	/**
296
	 * Check if this ChangeSetItem can be published
297
	 *
298
	 * @param Member $member
299
	 * @return bool
300
	 */
301
	public function canPublish($member = null) {
302
		// Check canMethod to invoke on object
303
		switch($this->getChangeType()) {
304 View Code Duplication
			case static::CHANGE_DELETED: {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
305
				/** @var Versioned|DataObject $object */
306
				$object = Versioned::get_by_stage($this->ObjectClass, Versioned::LIVE)->byID($this->ObjectID);
307
				if(!$object || !$object->canUnpublish($member)) {
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 301 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...
308
					return false;
309
				}
310
				break;
311
			}
312 View Code Duplication
			default: {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
				/** @var Versioned|DataObject $object */
314
				$object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID);
315
				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...
316
					return false;
317
				}
318
				break;
319
			}
320
		}
321
322
		// If object can be published/unpublished let extensions deny
323
		return $this->can(__FUNCTION__, $member);
324
	}
325
326
	/**
327
	 * Default permissions for this ChangeSetItem
328
	 *
329
	 * @param string $perm
330
	 * @param Member $member
331
	 * @param array $context
332
	 * @return bool
333
	 */
334 View Code Duplication
	public function can($perm, $member = null, $context = array()) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
		if(!$member) {
336
			$member = Member::currentUser();
337
		}
338
339
		// Allow extensions to bypass default permissions, but only if
340
		// each change can be individually published.
341
		$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...
342
		if($extended !== null) {
343
			return $extended;
344
		}
345
346
		// Default permissions
347
		return (bool)Permission::checkMember($member, ChangeSet::config()->required_permission);
348
	}
349
350
	/**
351
	 * Get the ChangeSetItems that reference a passed DataObject
352
	 *
353
	 * @param DataObject $object
354
	 * @return DataList
355
	 */
356
	public static function get_for_object($object) {
357
		return ChangeSetItem::get()->filter([
358
			'ObjectID' => $object->ID,
359
			'ObjectClass' => $object->baseClass(),
360
		]);
361
	}
362
363
	/**
364
	 * Get the ChangeSetItems that reference a passed DataObject
365
	 *
366
	 * @param int $objectID The ID of the object
367
	 * @param string $objectClass The class of the object (or any parent class)
368
	 * @return DataList
369
	 */
370
	public static function get_for_object_by_id($objectID, $objectClass) {
371
		return ChangeSetItem::get()->filter([
372
			'ObjectID' => $objectID,
373
			'ObjectClass' => static::getSchema()->baseDataClass($objectClass)
374
		]);
375
	}
376
377
	/**
378
	 * Gets the list of modes this record can be previewed in.
379
	 *
380
	 * {@link https://tools.ietf.org/html/draft-kelly-json-hal-07#section-5}
381
	 *
382
	 * @return array Map of links in acceptable HAL format
383
	 */
384
	public function getPreviewLinks() {
385
		$links = [];
386
387
		// Preview draft
388
		$stage = $this->getObjectInStage(Versioned::DRAFT);
389 View Code Duplication
		if($stage instanceof CMSPreviewable && $stage->canView() && ($link = $stage->PreviewLink())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
390
			$links[Versioned::DRAFT] = [
391
				'href' => Controller::join_links($link, '?stage=' . Versioned::DRAFT),
392
				'type' => $stage->getMimeType(),
393
			];
394
		}
395
396
		// Preview live
397
		$live = $this->getObjectInStage(Versioned::LIVE);
398 View Code Duplication
		if($live instanceof CMSPreviewable && $live->canView() && ($link = $live->PreviewLink())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
399
			$links[Versioned::LIVE] = [
400
				'href' => Controller::join_links($link, '?stage=' . Versioned::LIVE),
401
				'type' => $live->getMimeType(),
402
			];
403
		}
404
405
		return $links;
406
	}
407
408
	/**
409
	 * Get edit link for this item
410
	 *
411
	 * @return string
412
	 */
413
	public function CMSEditLink()
414
	{
415
		$link = $this->getObjectInStage(Versioned::DRAFT);
416
		if($link instanceof CMSPreviewable) {
417
			return $link->CMSEditLink();
418
		}
419
		return null;
420
	}
421
}
422