Completed
Push — master ( 60247a...406ff5 )
by Ingo
24s
created

Versioned::mergeRelatedObjects()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 13
nc 7
nop 2
dl 0
loc 25
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Versioning;
4
5
use SilverStripe\Control\HTTPRequest;
6
use SilverStripe\Control\Session;
7
use SilverStripe\Control\Director;
8
use SilverStripe\Control\Cookie;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\ClassInfo;
11
use SilverStripe\Core\Object;
12
use SilverStripe\Dev\Deprecation;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\ORM\DataQuery;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\ORM\DB;
17
use SilverStripe\ORM\ArrayList;
18
use SilverStripe\ORM\DataList;
19
use SilverStripe\ORM\DataExtension;
20
use SilverStripe\ORM\Queries\SQLSelect;
21
use SilverStripe\ORM\Queries\SQLUpdate;
22
use SilverStripe\Security\Member;
23
use SilverStripe\Security\Permission;
24
use SilverStripe\View\TemplateGlobalProvider;
25
use InvalidArgumentException;
26
use LogicException;
27
28
/**
29
 * The Versioned extension allows your DataObjects to have several versions,
30
 * allowing you to rollback changes and view history. An example of this is
31
 * the pages used in the CMS.
32
 *
33
 * @property int $Version
34
 * @property DataObject|Versioned $owner
35
 */
36
class Versioned extends DataExtension implements TemplateGlobalProvider {
37
38
	/**
39
	 * Versioning mode for this object.
40
	 * Note: Not related to the current versioning mode in the state / session
41
	 * Will be one of 'StagedVersioned' or 'Versioned';
42
	 *
43
	 * @var string
44
	 */
45
	protected $mode;
46
47
	/**
48
	 * The default reading mode
49
	 */
50
	const DEFAULT_MODE = 'Stage.Live';
51
52
	/**
53
	 * Constructor arg to specify that staging is active on this record.
54
	 * 'Staging' implies that 'Versioning' is also enabled.
55
	 */
56
	const STAGEDVERSIONED = 'StagedVersioned';
57
58
	/**
59
	 * Constructor arg to specify that versioning only is active on this record.
60
	 */
61
	const VERSIONED = 'Versioned';
62
63
	/**
64
	 * The Public stage.
65
	 */
66
	const LIVE = 'Live';
67
68
	/**
69
	 * The draft (default) stage
70
	 */
71
	const DRAFT = 'Stage';
72
73
	/**
74
	 * A version that a DataObject should be when it is 'migrating',
75
	 * that is, when it is in the process of moving from one stage to another.
76
	 * @var string
77
	 */
78
	public $migratingVersion;
79
80
	/**
81
	 * A cache used by get_versionnumber_by_stage().
82
	 * Clear through {@link flushCache()}.
83
	 *
84
	 * @var array
85
	 */
86
	protected static $cache_versionnumber;
87
88
	/**
89
	 * Current reading mode
90
	 *
91
	 * @var string
92
	 */
93
	protected static $reading_mode = null;
94
95
	/**
96
	 * @var Boolean Flag which is temporarily changed during the write() process
97
	 * to influence augmentWrite() behaviour. If set to TRUE, no new version will be created
98
	 * for the following write. Needs to be public as other classes introspect this state
99
	 * during the write process in order to adapt to this versioning behaviour.
100
	 */
101
	public $_nextWriteWithoutVersion = false;
102
103
	/**
104
	 * Additional database columns for the new
105
	 * "_versions" table. Used in {@link augmentDatabase()}
106
	 * and all Versioned calls extending or creating
107
	 * SELECT statements.
108
	 *
109
	 * @var array $db_for_versions_table
110
	 */
111
	private static $db_for_versions_table = array(
112
		"RecordID" => "Int",
113
		"Version" => "Int",
114
		"WasPublished" => "Boolean",
115
		"AuthorID" => "Int",
116
		"PublisherID" => "Int"
117
	);
118
119
	/**
120
	 * @var array
121
	 */
122
	private static $db = array(
123
		'Version' => 'Int'
124
	);
125
126
	/**
127
	 * Used to enable or disable the prepopulation of the version number cache.
128
	 * Defaults to true.
129
	 *
130
	 * @config
131
	 * @var boolean
132
	 */
133
	private static $prepopulate_versionnumber_cache = true;
134
135
	/**
136
	 * Additional database indexes for the new
137
	 * "_versions" table. Used in {@link augmentDatabase()}.
138
	 *
139
	 * @var array $indexes_for_versions_table
140
	 */
141
	private static $indexes_for_versions_table = array(
142
		'RecordID_Version' => '("RecordID","Version")',
143
		'RecordID' => true,
144
		'Version' => true,
145
		'AuthorID' => true,
146
		'PublisherID' => true,
147
	);
148
149
150
	/**
151
	 * An array of DataObject extensions that may require versioning for extra tables
152
	 * The array value is a set of suffixes to form these table names, assuming a preceding '_'.
153
	 * E.g. if Extension1 creates a new table 'Class_suffix1'
154
	 * and Extension2 the tables 'Class_suffix2' and 'Class_suffix3':
155
	 *
156
	 * 	$versionableExtensions = array(
157
	 * 		'Extension1' => 'suffix1',
158
	 * 		'Extension2' => array('suffix2', 'suffix3'),
159
	 * 	);
160
	 *
161
	 * This can also be manipulated by updating the current loaded config
162
	 *
163
	 * SiteTree:
164
	 *   versionableExtensions:
165
	 *     - Extension1:
166
	 *       - suffix1
167
	 *       - suffix2
168
	 *     - Extension2:
169
	 *       - suffix1
170
	 *       - suffix2
171
	 *
172
	 * or programatically:
173
	 *
174
	 *  Config::inst()->update($this->owner->class, 'versionableExtensions',
175
	 *  array('Extension1' => 'suffix1', 'Extension2' => array('suffix2', 'suffix3')));
176
	 *
177
	 *
178
	 * Your extension must implement VersionableExtension interface in order to
179
	 * apply custom tables for versioned.
180
	 *
181
	 * @config
182
	 * @var array
183
	 */
184
	private static $versionableExtensions = [];
185
186
	/**
187
	 * Permissions necessary to view records outside of the live stage (e.g. archive / draft stage).
188
	 *
189
	 * @config
190
	 * @var array
191
	 */
192
	private static $non_live_permissions = array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_CMSMain', 'VIEW_DRAFT_CONTENT');
193
194
	/**
195
	 * List of relationships on this object that are "owned" by this object.
196
	 * Owership in the context of versioned objects is a relationship where
197
	 * the publishing of owning objects requires the publishing of owned objects.
198
	 *
199
	 * E.g. A page owns a set of banners, as in order for the page to be published, all
200
	 * banners on this page must also be published for it to be visible.
201
	 *
202
	 * Typically any object and its owned objects should be visible in the same edit view.
203
	 * E.g. a page and {@see GridField} of banners.
204
	 *
205
	 * Page hierarchy is typically not considered an ownership relationship.
206
	 *
207
	 * Ownership is recursive; If A owns B and B owns C then A owns C.
208
	 *
209
	 * @config
210
	 * @var array List of has_many or many_many relationships owned by this object.
211
	 */
212
	private static $owns = array();
213
214
	/**
215
	 * Opposing relationship to owns config; Represents the objects which
216
	 * own the current object.
217
	 *
218
	 * @var array
219
	 */
220
	private static $owned_by = array();
221
222
	/**
223
	 * Reset static configuration variables to their default values.
224
	 */
225
	public static function reset() {
226
		self::$reading_mode = '';
227
		Session::clear('readingMode');
228
	}
229
230
	/**
231
	 * Amend freshly created DataQuery objects with versioned-specific
232
	 * information.
233
	 *
234
	 * @param SQLSelect $query
235
	 * @param DataQuery $dataQuery
236
	 */
237
	public function augmentDataQueryCreation(SQLSelect &$query, DataQuery &$dataQuery) {
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
238
		$parts = explode('.', Versioned::get_reading_mode());
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...
239
240
		if($parts[0] == 'Archive') {
241
			$dataQuery->setQueryParam('Versioned.mode', 'archive');
242
			$dataQuery->setQueryParam('Versioned.date', $parts[1]);
243
		} else if($parts[0] == 'Stage' && $this->hasStages()) {
244
			$dataQuery->setQueryParam('Versioned.mode', 'stage');
245
			$dataQuery->setQueryParam('Versioned.stage', $parts[1]);
246
		}
247
	}
248
249
	/**
250
	 * Construct a new Versioned object.
251
	 *
252
	 * @var string $mode One of "StagedVersioned" or "Versioned".
253
	 */
254
	public function __construct($mode = self::STAGEDVERSIONED) {
255
		parent::__construct();
256
257
		// Handle deprecated behaviour
258
		if($mode === 'Stage' && func_num_args() === 1) {
259
			Deprecation::notice("5.0", "Versioned now takes a mode as a single parameter");
260
			$mode = static::VERSIONED;
261
		} elseif(is_array($mode) || func_num_args() > 1) {
262
			Deprecation::notice("5.0", "Versioned now takes a mode as a single parameter");
263
			$mode = func_num_args() > 1 || count($mode) > 1
264
				? static::STAGEDVERSIONED
265
				: static::VERSIONED;
266
		}
267
268
		if(!in_array($mode, array(static::STAGEDVERSIONED, static::VERSIONED))) {
269
			throw new InvalidArgumentException("Invalid mode: {$mode}");
270
		}
271
272
		$this->mode = $mode;
273
	}
274
275
	/**
276
	 * Cache of version to modified dates for this objects
277
	 *
278
	 * @var array
279
	 */
280
	protected $versionModifiedCache = array();
281
282
	/**
283
	 * Get modified date for the given version
284
	 *
285
	 * @param int $version
286
	 * @return string
287
	 */
288
	protected function getLastEditedForVersion($version) {
289
		// Cache key
290
		$baseTable = $this->baseTable();
291
		$id = $this->owner->ID;
292
		$key = "{$baseTable}#{$id}/{$version}";
293
294
		// Check cache
295
		if(isset($this->versionModifiedCache[$key])) {
296
			return $this->versionModifiedCache[$key];
297
		}
298
299
		// Build query
300
		$table = "\"{$baseTable}_versions\"";
301
		$query = SQLSelect::create('"LastEdited"', $table)
302
			->addWhere([
303
				"{$table}.\"RecordID\"" => $id,
304
				"{$table}.\"Version\"" => $version
305
			]);
306
		$date = $query->execute()->value();
307
		if($date) {
308
			$this->versionModifiedCache[$key] = $date;
309
		}
310
		return $date;
311
	}
312
313
	/**
314
	 * Updates query parameters of relations attached to versioned dataobjects
315
	 *
316
	 * @param array $params
317
	 */
318
	public function updateInheritableQueryParams(&$params) {
319
		// Skip if versioned isn't set
320
		if(!isset($params['Versioned.mode'])) {
321
			return;
322
		}
323
324
		// Adjust query based on original selection criterea
325
		switch($params['Versioned.mode']) {
326
			case 'all_versions': {
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...
327
				// Versioned.mode === all_versions doesn't inherit very well, so default to stage
328
				$params['Versioned.mode'] = 'stage';
329
				$params['Versioned.stage'] = static::DRAFT;
330
				break;
331
			}
332
			case 'version': {
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...
333
				// If we selected this object from a specific version, we need
334
				// to find the date this version was published, and ensure
335
				// inherited queries select from that date.
336
				$version = $params['Versioned.version'];
337
				$date = $this->getLastEditedForVersion($version);
338
339
				// Filter related objects at the same date as this version
340
				unset($params['Versioned.version']);
341
				if($date) {
342
					$params['Versioned.mode'] = 'archive';
343
					$params['Versioned.date'] = $date;
344
				} else {
345
					// Fallback to default
346
					$params['Versioned.mode'] = 'stage';
347
					$params['Versioned.stage'] = static::DRAFT;
348
				}
349
				break;
350
			}
351
		}
352
	}
353
354
	/**
355
	 * Augment the the SQLSelect that is created by the DataQuery
356
	 *
357
	 * See {@see augmentLazyLoadFields} for lazy-loading applied prior to this.
358
	 *
359
	 * @param SQLSelect $query
360
	 * @param DataQuery $dataQuery
361
	 * @throws InvalidArgumentException
362
	 */
363
	public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) {
364
		if(!$dataQuery || !$dataQuery->getQueryParam('Versioned.mode')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dataQuery->getQueryParam('Versioned.mode') of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
365
			return;
366
		}
367
368
		$baseTable = $this->baseTable();
369
		$versionedMode = $dataQuery->getQueryParam('Versioned.mode');
370
		switch($versionedMode) {
371
		// Reading a specific stage (Stage or Live)
372
		case 'stage':
373
			// Check if we need to rewrite this table
374
			$stage = $dataQuery->getQueryParam('Versioned.stage');
375
			if(!$this->hasStages() || $stage === static::DRAFT) {
376
				break;
377
			}
378
			// Rewrite all tables to select from the live version
379
			foreach($query->getFrom() as $table => $dummy) {
380
				if(!$this->isTableVersioned($table)) {
381
					continue;
382
				}
383
				$stageTable = $this->stageTable($table, $stage);
384
				$query->renameTable($table, $stageTable);
385
			}
386
			break;
387
388
		// Reading a specific stage, but only return items that aren't in any other stage
389
		case 'stage_unique':
390
			if(!$this->hasStages()) {
391
				break;
392
			}
393
394
			$stage = $dataQuery->getQueryParam('Versioned.stage');
395
			// Recurse to do the default stage behavior (must be first, we rely on stage renaming happening before
396
			// below)
397
			$dataQuery->setQueryParam('Versioned.mode', 'stage');
398
			$this->augmentSQL($query, $dataQuery);
399
			$dataQuery->setQueryParam('Versioned.mode', 'stage_unique');
400
401
			// Now exclude any ID from any other stage. Note that we double rename to avoid the regular stage rename
402
			// renaming all subquery references to be Versioned.stage
403
			foreach([static::DRAFT, static::LIVE] as $excluding) {
404
				if ($excluding == $stage) {
405
					continue;
406
				}
407
408
				$tempName = 'ExclusionarySource_'.$excluding;
409
				$excludingTable = $this->baseTable($excluding);
410
411
				$query->addWhere('"'.$baseTable.'"."ID" NOT IN (SELECT "ID" FROM "'.$tempName.'")');
412
				$query->renameTable($tempName, $excludingTable);
413
			}
414
			break;
415
416
		// Return all version instances
417
		case 'archive':
418
		case 'all_versions':
419
		case 'latest_versions':
420
		case 'version':
421
			foreach($query->getFrom() as $alias => $join) {
422
				if(!$this->isTableVersioned($alias)) {
423
					continue;
424
				}
425
426
				if($alias != $baseTable) {
427
					// Make sure join includes version as well
428
					$query->setJoinFilter(
429
						$alias,
430
						"\"{$alias}_versions\".\"RecordID\" = \"{$baseTable}_versions\".\"RecordID\""
431
						. " AND \"{$alias}_versions\".\"Version\" = \"{$baseTable}_versions\".\"Version\""
432
					);
433
				}
434
				$query->renameTable($alias, $alias . '_versions');
435
			}
436
437
			// Add all <basetable>_versions columns
438
			foreach(Config::inst()->get('SilverStripe\ORM\Versioning\Versioned', 'db_for_versions_table') as $name => $type) {
439
				$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, $name), $name);
440
			}
441
442
			// Alias the record ID as the row ID, and ensure ID filters are aliased correctly
443
			$query->selectField("\"{$baseTable}_versions\".\"RecordID\"", "ID");
444
			$query->replaceText("\"{$baseTable}_versions\".\"ID\"", "\"{$baseTable}_versions\".\"RecordID\"");
445
446
			// However, if doing count, undo rewrite of "ID" column
447
			$query->replaceText(
448
				"count(DISTINCT \"{$baseTable}_versions\".\"RecordID\")",
449
				"count(DISTINCT \"{$baseTable}_versions\".\"ID\")"
450
			);
451
452
			// Add additional versioning filters
453
			switch($versionedMode) {
454
				case 'archive': {
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...
455
					$date = $dataQuery->getQueryParam('Versioned.date');
456
					if(!$date) {
457
						throw new InvalidArgumentException("Invalid archive date");
458
					}
459
					// Link to the version archived on that date
460
					$query->addWhere([
461
						"\"{$baseTable}_versions\".\"Version\" IN
462
						(SELECT LatestVersion FROM
463
							(SELECT
464
								\"{$baseTable}_versions\".\"RecordID\",
465
								MAX(\"{$baseTable}_versions\".\"Version\") AS LatestVersion
466
								FROM \"{$baseTable}_versions\"
467
								WHERE \"{$baseTable}_versions\".\"LastEdited\" <= ?
468
								GROUP BY \"{$baseTable}_versions\".\"RecordID\"
469
							) AS \"{$baseTable}_versions_latest\"
470
							WHERE \"{$baseTable}_versions_latest\".\"RecordID\" = \"{$baseTable}_versions\".\"RecordID\"
471
						)" => $date
472
					]);
473
					break;
474
				}
475
				case 'latest_versions': {
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...
476
					// Return latest version instances, regardless of whether they are on a particular stage
477
					// This provides "show all, including deleted" functonality
478
					$query->addWhere(
479
						"\"{$baseTable}_versions\".\"Version\" IN
480
						(SELECT LatestVersion FROM
481
							(SELECT
482
								\"{$baseTable}_versions\".\"RecordID\",
483
								MAX(\"{$baseTable}_versions\".\"Version\") AS LatestVersion
484
								FROM \"{$baseTable}_versions\"
485
								GROUP BY \"{$baseTable}_versions\".\"RecordID\"
486
							) AS \"{$baseTable}_versions_latest\"
487
							WHERE \"{$baseTable}_versions_latest\".\"RecordID\" = \"{$baseTable}_versions\".\"RecordID\"
488
						)"
489
					);
490
					break;
491
				}
492
				case 'version': {
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...
493
					// If selecting a specific version, filter it here
494
					$version = $dataQuery->getQueryParam('Versioned.version');
495
					if(!$version) {
496
						throw new InvalidArgumentException("Invalid version");
497
					}
498
					$query->addWhere([
499
						"\"{$baseTable}_versions\".\"Version\"" => $version
500
					]);
501
					break;
502
				}
503
				case 'all_versions':
504
				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...
505
					// If all versions are requested, ensure that records are sorted by this field
506
					$query->addOrderBy(sprintf('"%s_versions"."%s"', $baseTable, 'Version'));
507
					break;
508
				}
509
			}
510
			break;
511
		default:
512
			throw new InvalidArgumentException("Bad value for query parameter Versioned.mode: "
513
				. $dataQuery->getQueryParam('Versioned.mode'));
514
		}
515
	}
516
517
	/**
518
	 * Determine if the given versioned table is a part of the sub-tree of the current dataobject
519
	 * This helps prevent rewriting of other tables that get joined in, in particular, many_many tables
520
	 *
521
	 * @param string $table
522
	 * @return bool True if this table should be versioned
523
	 */
524
	protected function isTableVersioned($table) {
525
		$schema = DataObject::getSchema();
526
		$tableClass = $schema->tableClass($table);
527
		if(empty($tableClass)) {
528
			return false;
529
		}
530
531
		// Check that this class belongs to the same tree
532
		$baseClass = $schema->baseDataClass($this->owner);
533
		if(!is_a($tableClass, $baseClass, true)) {
534
			return false;
535
		}
536
537
		// Check that this isn't a derived table
538
		// (e.g. _Live, or a many_many table)
539
		$mainTable = $schema->tableName($tableClass);
540
		if($mainTable !== $table) {
541
			return false;
542
		}
543
544
		return true;
545
	}
546
547
	/**
548
	 * For lazy loaded fields requiring extra sql manipulation, ie versioning.
549
	 *
550
	 * @param SQLSelect $query
551
	 * @param DataQuery $dataQuery
552
	 * @param DataObject $dataObject
553
	 */
554
	public function augmentLoadLazyFields(SQLSelect &$query, DataQuery &$dataQuery = null, $dataObject) {
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
555
		// The VersionedMode local variable ensures that this decorator only applies to
556
		// queries that have originated from the Versioned object, and have the Versioned
557
		// metadata set on the query object. This prevents regular queries from
558
		// accidentally querying the *_versions tables.
559
		$versionedMode = $dataObject->getSourceQueryParam('Versioned.mode');
560
		$modesToAllowVersioning = array('all_versions', 'latest_versions', 'archive', 'version');
561
		if(
562
			!empty($dataObject->Version) &&
563
			(!empty($versionedMode) && in_array($versionedMode,$modesToAllowVersioning))
564
		) {
565
			// This will ensure that augmentSQL will select only the same version as the owner,
566
			// regardless of how this object was initially selected
567
			$versionColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'Version');
0 ignored issues
show
Bug introduced by
The method getSchema does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
568
			$dataQuery->where([
0 ignored issues
show
Bug introduced by
It seems like $dataQuery is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
569
				$versionColumn => $dataObject->Version
570
			]);
571
			$dataQuery->setQueryParam('Versioned.mode', 'all_versions');
572
		}
573
	}
574
575
	public function augmentDatabase() {
576
		$owner = $this->owner;
577
		$class = get_class($owner);
578
		$baseTable = $this->baseTable();
579
		$classTable = $owner->getSchema()->tableName($owner);
0 ignored issues
show
Bug introduced by
The method getSchema does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
580
581
		$isRootClass = $class === $owner->baseClass();
0 ignored issues
show
Bug introduced by
The method baseClass does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
582
583
		// Build a list of suffixes whose tables need versioning
584
		$allSuffixes = array();
585
		$versionableExtensions = $owner->config()->versionableExtensions;
0 ignored issues
show
Bug introduced by
The method config does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
586
		if(count($versionableExtensions)){
587
			foreach ($versionableExtensions as $versionableExtension => $suffixes) {
588
				if ($owner->hasExtension($versionableExtension)) {
0 ignored issues
show
Bug introduced by
The method hasExtension does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
589
					foreach ((array)$suffixes as $suffix) {
590
						$allSuffixes[$suffix] = $versionableExtension;
591
					}
592
				}
593
			}
594
		}
595
596
		// Add the default table with an empty suffix to the list (table name = class name)
597
		$allSuffixes[''] = null;
598
599
		foreach ($allSuffixes as $suffix => $extension) {
600
			// Check tables for this build
601
			if ($suffix) {
602
				$suffixBaseTable = "{$baseTable}_{$suffix}";
603
				$suffixTable = "{$classTable}_{$suffix}";
604
			}  else {
605
				$suffixBaseTable = $baseTable;
606
				$suffixTable = $classTable;
607
			}
608
609
			$fields = DataObject::database_fields($owner->class);
610
			unset($fields['ID']);
611
			if($fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
612
				$options = Config::inst()->get($owner->class, 'create_table_options', Config::FIRST_SET);
613
				$indexes = $owner->databaseIndexes();
0 ignored issues
show
Bug introduced by
The method databaseIndexes does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
614
				$extensionClass = $allSuffixes[$suffix];
615
				if ($suffix && ($extension = $owner->getExtensionInstance($extensionClass))) {
0 ignored issues
show
Bug introduced by
The method getExtensionInstance does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
616
					if (!$extension instanceof VersionableExtension) {
617
						throw new LogicException(
618
							"Extension {$extensionClass} must implement VersionableExtension"
619
						);
620
					}
621
					// Allow versionable extension to customise table fields and indexes
622
					$extension->setOwner($owner);
623
					if ($extension->isVersionedTable($suffixTable)) {
624
						$extension->updateVersionableFields($suffix, $fields, $indexes);
625
					}
626
					$extension->clearOwner();
627
				}
628
629
				// Build _Live table
630
				if($this->hasStages()) {
631
					$liveTable = $this->stageTable($suffixTable, static::LIVE);
632
					DB::require_table($liveTable, $fields, $indexes, false, $options);
633
				}
634
635
				// Build _versions table
636
				//Unique indexes will not work on versioned tables, so we'll convert them to standard indexes:
637
				$nonUniqueIndexes = $this->uniqueToIndex($indexes);
638
				if($isRootClass) {
639
					// Create table for all versions
640
					$versionFields = array_merge(
641
						Config::inst()->get('SilverStripe\ORM\Versioning\Versioned', 'db_for_versions_table'),
642
						(array)$fields
643
					);
644
					$versionIndexes = array_merge(
645
						Config::inst()->get('SilverStripe\ORM\Versioning\Versioned', 'indexes_for_versions_table'),
646
						(array)$nonUniqueIndexes
647
					);
648
				} else {
649
					// Create fields for any tables of subclasses
650
					$versionFields = array_merge(
651
						array(
652
							"RecordID" => "Int",
653
							"Version" => "Int",
654
						),
655
						(array)$fields
656
					);
657
					$versionIndexes = array_merge(
658
						array(
659
							'RecordID_Version' => array('type' => 'unique', 'value' => '"RecordID","Version"'),
660
							'RecordID' => true,
661
							'Version' => true,
662
						),
663
						(array)$nonUniqueIndexes
664
					);
665
				}
666
667
				// Cleanup any orphans
668
				$this->cleanupVersionedOrphans("{$suffixBaseTable}_versions", "{$suffixTable}_versions");
669
670
				// Build versions table
671
				DB::require_table("{$suffixTable}_versions", $versionFields, $versionIndexes, true, $options);
672
			} else {
673
				DB::dont_require_table("{$suffixTable}_versions");
674
				if($this->hasStages()) {
675
					$liveTable = $this->stageTable($suffixTable, static::LIVE);
676
					DB::dont_require_table($liveTable);
677
				}
678
			}
679
		}
680
	}
681
682
	/**
683
	 * Cleanup orphaned records in the _versions table
684
	 *
685
	 * @param string $baseTable base table to use as authoritative source of records
686
	 * @param string $childTable Sub-table to clean orphans from
687
	 */
688
	protected function cleanupVersionedOrphans($baseTable, $childTable) {
689
		// Skip if child table doesn't exist
690
		if(!DB::get_schema()->hasTable($childTable)) {
691
			return;
692
		}
693
		// Skip if tables are the same
694
		if($childTable === $baseTable) {
695
			return;
696
		}
697
698
		// Select all orphaned version records
699
		$orphanedQuery = SQLSelect::create()
700
			->selectField("\"{$childTable}\".\"ID\"")
701
			->setFrom("\"{$childTable}\"");
702
703
		// If we have a parent table limit orphaned records
704
		// to only those that exist in this
705
		if(DB::get_schema()->hasTable($baseTable)) {
706
			$orphanedQuery
707
				->addLeftJoin(
708
					$baseTable,
709
					"\"{$childTable}\".\"RecordID\" = \"{$baseTable}\".\"RecordID\"
710
					AND \"{$childTable}\".\"Version\" = \"{$baseTable}\".\"Version\""
711
				)
712
				->addWhere("\"{$baseTable}\".\"ID\" IS NULL");
713
		}
714
715
		$count = $orphanedQuery->count();
716
		if($count > 0) {
717
			DB::alteration_message("Removing {$count} orphaned versioned records", "deleted");
718
			$ids = $orphanedQuery->execute()->column();
719
			foreach($ids as $id) {
720
				DB::prepared_query("DELETE FROM \"{$childTable}\" WHERE \"ID\" = ?", array($id));
721
			}
722
		}
723
	}
724
725
	/**
726
	 * Helper for augmentDatabase() to find unique indexes and convert them to non-unique
727
	 *
728
	 * @param array $indexes The indexes to convert
729
	 * @return array $indexes
730
	 */
731
	private function uniqueToIndex($indexes) {
732
		$unique_regex = '/unique/i';
733
		$results = array();
734
		foreach ($indexes as $key => $index) {
735
			$results[$key] = $index;
736
737
			// support string descriptors
738
			if (is_string($index)) {
739
				if (preg_match($unique_regex, $index)) {
740
					$results[$key] = preg_replace($unique_regex, 'index', $index);
741
				}
742
			}
743
744
			// canonical, array-based descriptors
745
			elseif (is_array($index)) {
746
				if (strtolower($index['type']) == 'unique') {
747
					$results[$key]['type'] = 'index';
748
				}
749
			}
750
		}
751
		return $results;
752
	}
753
754
	/**
755
	 * Generates a ($table)_version DB manipulation and injects it into the current $manipulation
756
	 *
757
	 * @param array $manipulation Source manipulation data
758
	 * @param string $class Class
759
	 * @param string $table Table Table for this class
760
	 * @param int $recordID ID of record to version
761
	 */
762
	protected function augmentWriteVersioned(&$manipulation, $class, $table, $recordID) {
763
		$baseDataClass = DataObject::getSchema()->baseDataClass($class);
764
		$baseDataTable = DataObject::getSchema()->tableName($baseDataClass);
765
766
		// Set up a new entry in (table)_versions
767
		$newManipulation = array(
768
			"command" => "insert",
769
			"fields" => isset($manipulation[$table]['fields']) ? $manipulation[$table]['fields'] : [],
770
			"class" => $class,
771
		);
772
773
		// Add any extra, unchanged fields to the version record.
774
		$data = DB::prepared_query("SELECT * FROM \"{$table}\" WHERE \"ID\" = ?", array($recordID))->record();
775
776
		if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
777
			$fields = DataObject::database_fields($class);
778
779
			if (is_array($fields)) {
780
				$data = array_intersect_key($data, $fields);
781
782
				foreach ($data as $k => $v) {
783
					// If the value is not set at all in the manipulation currently, use the existing value from the database
784
					if (!array_key_exists($k, $newManipulation['fields'])) {
785
						$newManipulation['fields'][$k] = $v;
786
					}
787
				}
788
			}
789
		}
790
791
		// Ensure that the ID is instead written to the RecordID field
792
		$newManipulation['fields']['RecordID'] = $recordID;
793
		unset($newManipulation['fields']['ID']);
794
795
		// Generate next version ID to use
796
		$nextVersion = 0;
797
		if($recordID) {
798
			$nextVersion = DB::prepared_query("SELECT MAX(\"Version\") + 1
799
				FROM \"{$baseDataTable}_versions\" WHERE \"RecordID\" = ?",
800
				array($recordID)
801
			)->value();
802
		}
803
		$nextVersion = $nextVersion ?: 1;
804
805
		if($class === $baseDataClass) {
806
			// Write AuthorID for baseclass
807
			$userID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
808
			$newManipulation['fields']['AuthorID'] = $userID;
809
810
			// Update main table version if not previously known
811
			$manipulation[$table]['fields']['Version'] = $nextVersion;
812
		}
813
814
		// Update _versions table manipulation
815
		$newManipulation['fields']['Version'] = $nextVersion;
816
		$manipulation["{$table}_versions"] = $newManipulation;
817
	}
818
819
	/**
820
	 * Rewrite the given manipulation to update the selected (non-default) stage
821
	 *
822
	 * @param array $manipulation Source manipulation data
823
	 * @param string $table Name of table
824
	 * @param int $recordID ID of record to version
825
	 */
826
	protected function augmentWriteStaged(&$manipulation, $table, $recordID) {
827
		// If the record has already been inserted in the (table), get rid of it.
828
		if($manipulation[$table]['command'] == 'insert') {
829
			DB::prepared_query(
830
				"DELETE FROM \"{$table}\" WHERE \"ID\" = ?",
831
				array($recordID)
832
			);
833
		}
834
835
		$newTable = $this->stageTable($table, Versioned::get_stage());
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...
836
		$manipulation[$newTable] = $manipulation[$table];
837
		unset($manipulation[$table]);
838
	}
839
840
841
	public function augmentWrite(&$manipulation) {
842
		// get Version number from base data table on write
843
		$version = null;
844
		$owner = $this->owner;
845
		$baseDataTable = DataObject::getSchema()->baseDataTable($owner);
846
		if(isset($manipulation[$baseDataTable]['fields'])) {
847
			if ($this->migratingVersion) {
848
				$manipulation[$baseDataTable]['fields']['Version'] = $this->migratingVersion;
849
			}
850
			if (isset($manipulation[$baseDataTable]['fields']['Version'])) {
851
				$version = $manipulation[$baseDataTable]['fields']['Version'];
852
			}
853
		}
854
855
		// Update all tables
856
		$tables = array_keys($manipulation);
857
		foreach($tables as $table) {
858
859
			// Make sure that the augmented write is being applied to a table that can be versioned
860
			$class = isset($manipulation[$table]['class']) ? $manipulation[$table]['class'] : null;
861
			if(!$class || !$this->canBeVersioned($class) ) {
862
				unset($manipulation[$table]);
863
				continue;
864
			}
865
866
			// Get ID field
867
			$id = $manipulation[$table]['id']
868
				? $manipulation[$table]['id']
869
				: $manipulation[$table]['fields']['ID'];
870
			if(!$id) {
871
				user_error("Couldn't find ID in " . var_export($manipulation[$table], true), E_USER_ERROR);
872
			}
873
874
			if($version < 0 || $this->_nextWriteWithoutVersion) {
875
				// Putting a Version of -1 is a signal to leave the version table alone, despite their being no version
876
				unset($manipulation[$table]['fields']['Version']);
877
			} elseif(empty($version)) {
878
				// If we haven't got a version #, then we're creating a new version.
879
				// Otherwise, we're just copying a version to another table
880
				$this->augmentWriteVersioned($manipulation, $class, $table, $id);
881
			}
882
883
			// Remove "Version" column from subclasses of baseDataClass
884
			if(!$this->hasVersionField($table)) {
885
				unset($manipulation[$table]['fields']['Version']);
886
			}
887
888
			// Grab a version number - it should be the same across all tables.
889
			if(isset($manipulation[$table]['fields']['Version'])) {
890
				$thisVersion = $manipulation[$table]['fields']['Version'];
891
			}
892
893
			// If we're editing Live, then use (table)_Live instead of (table)
894
			if($this->hasStages() && static::get_stage() === static::LIVE) {
895
				$this->augmentWriteStaged($manipulation, $table, $id);
896
			}
897
		}
898
899
		// Clear the migration flag
900
		if($this->migratingVersion) {
901
			$this->migrateVersion(null);
902
		}
903
904
		// Add the new version # back into the data object, for accessing
905
		// after this write
906
		if(isset($thisVersion)) {
907
			$owner->Version = str_replace("'","", $thisVersion);
908
		}
909
	}
910
911
	/**
912
	 * Perform a write without affecting the version table.
913
	 * On objects without versioning.
914
	 *
915
	 * @return int The ID of the record
916
	 */
917
	public function writeWithoutVersion() {
918
		$this->_nextWriteWithoutVersion = true;
919
920
		return $this->owner->write();
0 ignored issues
show
Bug introduced by
The method write does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
921
	}
922
923
	/**
924
	 *
925
	 */
926
	public function onAfterWrite() {
927
		$this->_nextWriteWithoutVersion = false;
928
	}
929
930
	/**
931
	 * If a write was skipped, then we need to ensure that we don't leave a
932
	 * migrateVersion() value lying around for the next write.
933
	 */
934
	public function onAfterSkippedWrite() {
935
		$this->migrateVersion(null);
936
	}
937
938
	/**
939
	 * Find all objects owned by the current object.
940
	 * Note that objects will only be searched in the same stage as the given record.
941
	 *
942
	 * @param bool $recursive True if recursive
943
	 * @param ArrayList $list Optional list to add items to
944
	 * @return ArrayList list of objects
945
	 */
946
	public function findOwned($recursive = true, $list = null)
947
	{
948
		// Find objects in these relationships
949
		return $this->findRelatedObjects('owns', $recursive, $list);
950
	}
951
952
	/**
953
	 * Find objects which own this object.
954
	 * Note that objects will only be searched in the same stage as the given record.
955
	 *
956
	 * @param bool $recursive True if recursive
957
	 * @param ArrayList $list Optional list to add items to
958
	 * @return ArrayList list of objects
959
	 */
960
	public function findOwners($recursive = true, $list = null) {
961
		if (!$list) {
962
			$list = new ArrayList();
963
		}
964
965
		// Build reverse lookup for ownership
966
		// @todo - Cache this more intelligently
967
		$rules = $this->lookupReverseOwners();
968
969
		// Hand off to recursive method
970
		return $this->findOwnersRecursive($recursive, $list, $rules);
971
	}
972
973
	/**
974
	 * Find objects which own this object.
975
	 * Note that objects will only be searched in the same stage as the given record.
976
	 *
977
	 * @param bool $recursive True if recursive
978
	 * @param ArrayList $list List to add items to
979
	 * @param array $lookup List of reverse lookup rules for owned objects
980
	 * @return ArrayList list of objects
981
	 */
982
	public function findOwnersRecursive($recursive, $list, $lookup) {
983
		// First pass: find objects that are explicitly owned_by (e.g. custom relationships)
984
		$owners = $this->findRelatedObjects('owned_by', false);
985
986
		// Second pass: Find owners via reverse lookup list
987
		foreach($lookup as $ownedClass => $classLookups) {
988
			// Skip owners of other objects
989
			if(!is_a($this->owner, $ownedClass)) {
990
				continue;
991
			}
992
			foreach($classLookups as $classLookup) {
993
				// Merge new owners into this object's owners
994
				$ownerClass = $classLookup['class'];
995
				$ownerRelation = $classLookup['relation'];
996
				$result = $this->owner->inferReciprocalComponent($ownerClass, $ownerRelation);
0 ignored issues
show
Bug introduced by
The method inferReciprocalComponent does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
997
				$this->mergeRelatedObjects($owners, $result);
998
			}
999
		}
1000
1001
		// Merge all objects into the main list
1002
		$newItems = $this->mergeRelatedObjects($list, $owners);
1003
1004
		// If recursing, iterate over all newly added items
1005
		if($recursive) {
1006
			foreach($newItems as $item) {
1007
				/** @var Versioned|DataObject $item */
1008
				$item->findOwnersRecursive(true, $list, $lookup);
0 ignored issues
show
Bug introduced by
The method findOwnersRecursive 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...
1009
			}
1010
		}
1011
1012
		return $list;
1013
	}
1014
1015
	/**
1016
	 * Find a list of classes, each of which with a list of methods to invoke
1017
	 * to lookup owners.
1018
	 *
1019
	 * @return array
1020
	 */
1021
	protected function lookupReverseOwners() {
1022
		// Find all classes with 'owns' config
1023
		$lookup = array();
1024
		foreach(ClassInfo::subclassesFor('SilverStripe\ORM\DataObject') as $class) {
1025
			// Ensure this class is versioned
1026
			if(!Object::has_extension($class, 'SilverStripe\ORM\Versioning\Versioned')) {
1027
				continue;
1028
			}
1029
1030
			// Check owned objects for this class
1031
			$owns = Config::inst()->get($class, 'owns', Config::UNINHERITED);
1032
			if(empty($owns)) {
1033
				continue;
1034
			}
1035
1036
			$instance = DataObject::singleton($class);
1037
			foreach($owns as $owned) {
1038
				// Find owned class
1039
				$ownedClass = $instance->getRelationClass($owned);
1040
				// Skip custom methods that don't have db relationsm
1041
				if(!$ownedClass) {
1042
					continue;
1043
				}
1044
				if($ownedClass === 'SilverStripe\ORM\DataObject') {
1045
					throw new LogicException(sprintf(
1046
						"Relation %s on class %s cannot be owned as it is polymorphic",
1047
						$owned, $class
1048
					));
1049
				}
1050
1051
				// Add lookup for owned class
1052
				if(!isset($lookup[$ownedClass])) {
1053
					$lookup[$ownedClass] = array();
1054
				}
1055
				$lookup[$ownedClass][] = [
1056
					'class' => $class,
1057
					'relation' => $owned
1058
				];
1059
			}
1060
		}
1061
		return $lookup;
1062
	}
1063
1064
1065
	/**
1066
	 * Find objects in the given relationships, merging them into the given list
1067
	 *
1068
	 * @param array $source Config property to extract relationships from
1069
	 * @param bool $recursive True if recursive
1070
	 * @param ArrayList $list Optional list to add items to
1071
	 * @return ArrayList The list
1072
	 */
1073
	public function findRelatedObjects($source, $recursive = true, $list = null)
1074
	{
1075
		if (!$list) {
1076
			$list = new ArrayList();
1077
		}
1078
1079
		// Skip search for unsaved records
1080
		$owner = $this->owner;
1081
		if(!$owner->isInDB()) {
0 ignored issues
show
Bug introduced by
The method isInDB does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1082
			return $list;
1083
		}
1084
1085
		$relationships = $owner->config()->{$source};
0 ignored issues
show
Bug introduced by
The method config does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1086
		foreach($relationships as $relationship) {
1087
			// Warn if invalid config
1088
			if(!$owner->hasMethod($relationship)) {
0 ignored issues
show
Bug introduced by
The method hasMethod does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1089
				trigger_error(sprintf(
1090
					"Invalid %s config value \"%s\" on object on class \"%s\"",
1091
					$source,
1092
					$relationship,
1093
					$owner->class
1094
				), E_USER_WARNING);
1095
				continue;
1096
			}
1097
1098
			// Inspect value of this relationship
1099
			$items = $owner->{$relationship}();
1100
1101
			// Merge any new item
1102
			$newItems = $this->mergeRelatedObjects($list, $items);
1103
1104
			// Recurse if necessary
1105
			if($recursive) {
1106
				foreach($newItems as $item) {
1107
					/** @var Versioned|DataObject $item */
1108
					$item->findRelatedObjects($source, true, $list);
0 ignored issues
show
Bug introduced by
The method findRelatedObjects 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...
1109
				}
1110
			}
1111
		}
1112
		return $list;
1113
	}
1114
1115
	/**
1116
	 * Helper method to merge owned/owning items into a list.
1117
	 * Items already present in the list will be skipped.
1118
	 *
1119
	 * @param ArrayList $list Items to merge into
1120
	 * @param mixed $items List of new items to merge
1121
	 * @return ArrayList List of all newly added items that did not already exist in $list
1122
	 */
1123
	protected function mergeRelatedObjects($list, $items) {
1124
		$added = new ArrayList();
1125
		if(!$items) {
1126
			return $added;
1127
		}
1128
		if($items instanceof DataObject) {
1129
			$items = array($items);
1130
		}
1131
1132
		/** @var Versioned|DataObject $item */
1133
		foreach($items as $item) {
1134
			// Identify item
1135
			$itemKey = $item->class . '/' . $item->ID;
1136
1137
			// Skip unsaved, unversioned, or already checked objects
1138
			if(!$item->isInDB() || !$item->has_extension('SilverStripe\ORM\Versioning\Versioned') || isset($list[$itemKey])) {
1139
				continue;
1140
			}
1141
1142
			// Save record
1143
			$list[$itemKey] = $item;
1144
			$added[$itemKey] = $item;
1145
		}
1146
		return $added;
1147
	}
1148
1149
	/**
1150
	 * This function should return true if the current user can publish this record.
1151
	 * It can be overloaded to customise the security model for an application.
1152
	 *
1153
	 * Denies permission if any of the following conditions is true:
1154
	 * - canPublish() on any extension returns false
1155
	 * - canEdit() returns false
1156
	 *
1157
	 * @param Member $member
1158
	 * @return bool True if the current user can publish this record.
1159
	 */
1160
	public function canPublish($member = null) {
1161
		// Skip if invoked by extendedCan()
1162
		if(func_num_args() > 4) {
1163
			return null;
1164
		}
1165
1166
		if(!$member) {
1167
			$member = Member::currentUser();
1168
		}
1169
1170
		if(Permission::checkMember($member, "ADMIN")) {
1171
			return true;
1172
		}
1173
1174
		// Standard mechanism for accepting permission changes from extensions
1175
		$owner = $this->owner;
1176
		$extended = $owner->extendedCan('canPublish', $member);
0 ignored issues
show
Bug introduced by
The method extendedCan does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
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...
1177
		if($extended !== null) {
1178
			return $extended;
1179
		}
1180
1181
		// Default to relying on edit permission
1182
		return $owner->canEdit($member);
1183
	}
1184
1185
	/**
1186
	 * Check if the current user can delete this record from live
1187
	 *
1188
	 * @param null $member
1189
	 * @return mixed
1190
	 */
1191
	public function canUnpublish($member = null) {
1192
		// Skip if invoked by extendedCan()
1193
		if(func_num_args() > 4) {
1194
			return null;
1195
		}
1196
1197
		if(!$member) {
1198
			$member = Member::currentUser();
1199
		}
1200
1201
		if(Permission::checkMember($member, "ADMIN")) {
1202
			return true;
1203
		}
1204
1205
		// Standard mechanism for accepting permission changes from extensions
1206
		$owner = $this->owner;
1207
		$extended = $owner->extendedCan('canUnpublish', $member);
0 ignored issues
show
Bug introduced by
The method extendedCan does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
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...
1208
		if($extended !== null) {
1209
			return $extended;
1210
		}
1211
1212
		// Default to relying on canPublish
1213
		return $owner->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...
1214
	}
1215
1216
	/**
1217
	 * Check if the current user is allowed to archive this record.
1218
	 * If extended, ensure that both canDelete and canUnpublish are extended also
1219
	 *
1220
	 * @param Member $member
1221
	 * @return bool
1222
	 */
1223
	public function canArchive($member = null) {
1224
		// Skip if invoked by extendedCan()
1225
		if(func_num_args() > 4) {
1226
			return null;
1227
		}
1228
1229
		if(!$member) {
1230
            $member = Member::currentUser();
1231
        }
1232
1233
		if(Permission::checkMember($member, "ADMIN")) {
1234
			return true;
1235
		}
1236
1237
		// Standard mechanism for accepting permission changes from extensions
1238
		$owner = $this->owner;
1239
		$extended = $owner->extendedCan('canArchive', $member);
0 ignored issues
show
Bug introduced by
The method extendedCan does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
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...
1240
		if($extended !== null) {
1241
            return $extended;
1242
        }
1243
1244
		// Check if this record can be deleted from stage
1245
        if(!$owner->canDelete($member)) {
1246
            return false;
1247
        }
1248
1249
        // Check if we can delete from live
1250
        if(!$owner->canUnpublish($member)) {
0 ignored issues
show
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...
Bug introduced by
It seems like $member 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?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1251
            return false;
1252
        }
1253
1254
		return true;
1255
	}
1256
1257
	/**
1258
	 * Check if the user can revert this record to live
1259
	 *
1260
	 * @param Member $member
1261
	 * @return bool
1262
	 */
1263
	public function canRevertToLive($member = null) {
1264
		$owner = $this->owner;
1265
1266
		// Skip if invoked by extendedCan()
1267
		if(func_num_args() > 4) {
1268
			return null;
1269
		}
1270
1271
		// Can't revert if not on live
1272
		if(!$owner->isPublished()) {
0 ignored issues
show
Bug introduced by
The method isPublished 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...
1273
			return false;
1274
		}
1275
1276
		if(!$member) {
1277
            $member = Member::currentUser();
1278
        }
1279
1280
		if(Permission::checkMember($member, "ADMIN")) {
1281
			return true;
1282
		}
1283
1284
		// Standard mechanism for accepting permission changes from extensions
1285
		$extended = $owner->extendedCan('canRevertToLive', $member);
0 ignored issues
show
Bug introduced by
The method extendedCan does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
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...
1286
		if($extended !== null) {
1287
            return $extended;
1288
        }
1289
1290
		// Default to canEdit
1291
		return $owner->canEdit($member);
1292
	}
1293
1294
	/**
1295
	 * Extend permissions to include additional security for objects that are not published to live.
1296
	 *
1297
	 * @param Member $member
1298
	 * @return bool|null
1299
	 */
1300
	public function canView($member = null) {
1301
		// Invoke default version-gnostic canView
1302
		if ($this->owner->canViewVersioned($member) === false) {
0 ignored issues
show
Bug introduced by
The method canViewVersioned 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...
1303
			return false;
1304
		}
1305
		return null;
1306
	}
1307
1308
	/**
1309
	 * Determine if there are any additional restrictions on this object for the given reading version.
1310
	 *
1311
	 * Override this in a subclass to customise any additional effect that Versioned applies to canView.
1312
	 *
1313
	 * This is expected to be called by canView, and thus is only responsible for denying access if
1314
	 * the default canView would otherwise ALLOW access. Thus it should not be called in isolation
1315
	 * as an authoritative permission check.
1316
	 *
1317
	 * This has the following extension points:
1318
	 *  - canViewDraft is invoked if Mode = stage and Stage = stage
1319
	 *  - canViewArchived is invoked if Mode = archive
1320
	 *
1321
	 * @param Member $member
1322
	 * @return bool False is returned if the current viewing mode denies visibility
1323
	 */
1324
	public function canViewVersioned($member = null) {
1325
		// Bypass when live stage
1326
		$owner = $this->owner;
1327
		$mode = $owner->getSourceQueryParam("Versioned.mode");
0 ignored issues
show
Bug introduced by
The method getSourceQueryParam does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1328
		$stage = $owner->getSourceQueryParam("Versioned.stage");
1329
		if ($mode === 'stage' && $stage === static::LIVE) {
1330
			return true;
1331
		}
1332
1333
		// Bypass if site is unsecured
1334
		if (Session::get('unsecuredDraftSite')) {
1335
			return true;
1336
		}
1337
1338
		// Bypass if record doesn't have a live stage
1339
		if(!$this->hasStages()) {
1340
			return true;
1341
		}
1342
1343
		// If we weren't definitely loaded from live, and we can't view non-live content, we need to
1344
		// check to make sure this version is the live version and so can be viewed
1345
		$latestVersion = Versioned::get_versionnumber_by_stage($owner->class, static::LIVE, $owner->ID);
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...
1346
		if ($latestVersion == $owner->Version) {
1347
			// Even if this is loaded from a non-live stage, this is the live version
1348
			return true;
1349
		}
1350
1351
		// Extend versioned behaviour
1352
		$extended = $owner->extendedCan('canViewNonLive', $member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 1324 can be null; however, SilverStripe\ORM\DataObject::extendedCan() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Bug introduced by
The method extendedCan does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1353
		if($extended !== null) {
1354
			return (bool)$extended;
1355
		}
1356
1357
		// Fall back to default permission check
1358
		$permissions = Config::inst()->get($owner->class, 'non_live_permissions', Config::FIRST_SET);
1359
		$check = Permission::checkMember($member, $permissions);
1360
		return (bool)$check;
1361
	}
1362
1363
	/**
1364
	 * Determines canView permissions for the latest version of this object on a specific stage.
1365
	 * Usually the stage is read from {@link Versioned::current_stage()}.
1366
	 *
1367
	 * This method should be invoked by user code to check if a record is visible in the given stage.
1368
	 *
1369
	 * This method should not be called via ->extend('canViewStage'), but rather should be
1370
	 * overridden in the extended class.
1371
	 *
1372
	 * @param string $stage
1373
	 * @param Member $member
1374
	 * @return bool
1375
	 */
1376
	public function canViewStage($stage = 'Live', $member = null) {
1377
		$oldMode = Versioned::get_reading_mode();
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...
1378
		Versioned::set_stage($stage);
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...
1379
1380
		$owner = $this->owner;
1381
		$versionFromStage = DataObject::get($owner->class)->byID($owner->ID);
1382
1383
		Versioned::set_reading_mode($oldMode);
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...
1384
		return $versionFromStage ? $versionFromStage->canView($member) : false;
1385
	}
1386
1387
	/**
1388
	 * Determine if a class is supporting the Versioned extensions (e.g.
1389
	 * $table_versions does exists).
1390
	 *
1391
	 * @param string $class Class name
1392
	 * @return boolean
1393
	 */
1394
	public function canBeVersioned($class) {
1395
		return ClassInfo::exists($class)
1396
			&& is_subclass_of($class, 'SilverStripe\ORM\DataObject')
1397
			&& DataObject::has_own_table($class);
1398
	}
1399
1400
	/**
1401
	 * Check if a certain table has the 'Version' field.
1402
	 *
1403
	 * @param string $table Table name
1404
	 *
1405
	 * @return boolean Returns false if the field isn't in the table, true otherwise
1406
	 */
1407
	public function hasVersionField($table) {
1408
		// Base table has version field
1409
		$class = DataObject::getSchema()->tableClass($table);
1410
		return $class === DataObject::getSchema()->baseDataClass($class);
1411
	}
1412
1413
	/**
1414
	 * @param string $table
1415
	 *
1416
	 * @return string
1417
	 */
1418
	public function extendWithSuffix($table) {
1419
		$owner = $this->owner;
1420
		$versionableExtensions = $owner->config()->versionableExtensions;
0 ignored issues
show
Bug introduced by
The method config does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1421
1422
		if(count($versionableExtensions)){
1423
			foreach ($versionableExtensions as $versionableExtension => $suffixes) {
1424
				if ($owner->hasExtension($versionableExtension)) {
0 ignored issues
show
Bug introduced by
The method hasExtension does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1425
					$ext = $owner->getExtensionInstance($versionableExtension);
0 ignored issues
show
Bug introduced by
The method getExtensionInstance does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1426
					$ext->setOwner($owner);
1427
					$table = $ext->extendWithSuffix($table);
1428
					$ext->clearOwner();
1429
				}
1430
			}
1431
		}
1432
1433
		return $table;
1434
	}
1435
1436
	/**
1437
	 * Determines if the current draft version is the same as live
1438
	 *
1439
	 * @return bool
1440
	 */
1441
	public function latestPublished() {
1442
		// Get the root data object class - this will have the version field
1443
		$owner = $this->owner;
1444
		$draftTable = $this->baseTable();
1445
		$liveTable = $this->stageTable($draftTable, static::LIVE);
1446
1447
		return DB::prepared_query("SELECT \"$draftTable\".\"Version\" = \"$liveTable\".\"Version\" FROM \"$draftTable\"
1448
			 INNER JOIN \"$liveTable\" ON \"$draftTable\".\"ID\" = \"$liveTable\".\"ID\"
1449
			 WHERE \"$draftTable\".\"ID\" = ?",
1450
			array($owner->ID)
1451
		)->value();
1452
	}
1453
1454
	/**
1455
	 * @deprecated 4.0..5.0
1456
	 */
1457
	public function doPublish() {
1458
		Deprecation::notice('5.0', 'Use publishRecursive instead');
1459
		return $this->owner->publishRecursive();
0 ignored issues
show
Bug introduced by
The method publishRecursive 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...
1460
	}
1461
1462
	/**
1463
	 * Publish this object and all owned objects to Live
1464
	 *
1465
	 * @return bool
1466
	 */
1467
	public function publishRecursive() {
1468
		$owner = $this->owner;
1469
		if(!$owner->publishSingle()) {
0 ignored issues
show
Bug introduced by
The method publishSingle does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1470
			return false;
1471
		}
1472
1473
		// Publish owned objects
1474
		foreach ($owner->findOwned(false) as $object) {
0 ignored issues
show
Bug introduced by
The method findOwned 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...
1475
			/** @var Versioned|DataObject $object */
1476
			$object->publishRecursive();
0 ignored issues
show
Bug introduced by
The method publishRecursive 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...
1477
		}
1478
1479
		// Unlink any objects disowned as a result of this action
1480
		// I.e. objects which aren't owned anymore by this record, but are by the old live record
1481
		$owner->unlinkDisownedObjects(Versioned::DRAFT, Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method unlinkDisownedObjects 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...
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...
1482
1483
		return true;
1484
	}
1485
1486
	/**
1487
	 * Publishes this object to Live, but doesn't publish owned objects.
1488
	 *
1489
	 * @return bool True if publish was successful
1490
	 */
1491
	public function publishSingle() {
1492
		$owner = $this->owner;
1493
		if(!$owner->canPublish()) {
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...
1494
			return false;
1495
		}
1496
1497
		$owner->invokeWithExtensions('onBeforePublish');
0 ignored issues
show
Bug introduced by
The method invokeWithExtensions does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1498
		$owner->write();
0 ignored issues
show
Bug introduced by
The method write does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1499
		$owner->copyVersionToStage(static::DRAFT, static::LIVE);
0 ignored issues
show
Bug introduced by
The method copyVersionToStage 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...
1500
		$owner->invokeWithExtensions('onAfterPublish');
1501
		return true;
1502
	}
1503
1504
	/**
1505
	 * Set foreign keys of has_many objects to 0 where those objects were
1506
	 * disowned as a result of a partial publish / unpublish.
1507
	 * I.e. this object and its owned objects were recently written to $targetStage,
1508
	 * but deleted objects were not.
1509
	 *
1510
	 * Note that this operation does not create any new Versions
1511
	 *
1512
	 * @param string $sourceStage Objects in this stage will not be unlinked.
1513
	 * @param string $targetStage Objects which exist in this stage but not $sourceStage
1514
	 * will be unlinked.
1515
	 */
1516
	public function unlinkDisownedObjects($sourceStage, $targetStage) {
1517
		$owner = $this->owner;
1518
1519
		// after publishing, objects which used to be owned need to be
1520
		// dis-connected from this object (set ForeignKeyID = 0)
1521
		$owns = $owner->config()->owns;
0 ignored issues
show
Bug introduced by
The method config does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1522
		$hasMany = $owner->config()->has_many;
1523
		if(empty($owns) || empty($hasMany)) {
1524
			return;
1525
		}
1526
1527
		$ownedHasMany = array_intersect($owns, array_keys($hasMany));
1528
		foreach($ownedHasMany as $relationship) {
1529
			// Find metadata on relationship
1530
			$joinClass = $owner->hasManyComponent($relationship);
0 ignored issues
show
Bug introduced by
The method hasManyComponent does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1531
			$joinField = $owner->getRemoteJoinField($relationship, 'has_many', $polymorphic);
0 ignored issues
show
Bug introduced by
The variable $polymorphic does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The method getRemoteJoinField does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1532
			$idField = $polymorphic ? "{$joinField}ID" : $joinField;
1533
			$joinTable = DataObject::getSchema()->tableForField($joinClass, $idField);
1534
1535
			// Generate update query which will unlink disowned objects
1536
			$targetTable = $this->stageTable($joinTable, $targetStage);
1537
			$disowned = new SQLUpdate("\"{$targetTable}\"");
1538
			$disowned->assign("\"{$idField}\"", 0);
1539
			$disowned->addWhere(array(
1540
				"\"{$targetTable}\".\"{$idField}\"" => $owner->ID
1541
			));
1542
1543
			// Build exclusion list (items to owned objects we need to keep)
1544
			$sourceTable = $this->stageTable($joinTable, $sourceStage);
1545
			$owned = new SQLSelect("\"{$sourceTable}\".\"ID\"", "\"{$sourceTable}\"");
1546
			$owned->addWhere(array(
1547
				"\"{$sourceTable}\".\"{$idField}\"" => $owner->ID
1548
			));
1549
1550
			// Apply class condition if querying on polymorphic has_one
1551
			if($polymorphic) {
1552
				$disowned->assign("\"{$joinField}Class\"", null);
1553
				$disowned->addWhere(array(
1554
					"\"{$targetTable}\".\"{$joinField}Class\"" => get_class($owner)
1555
				));
1556
				$owned->addWhere(array(
1557
					"\"{$sourceTable}\".\"{$joinField}Class\"" => get_class($owner)
1558
				));
1559
			}
1560
1561
			// Merge queries and perform unlink
1562
			$ownedSQL = $owned->sql($ownedParams);
1563
			$disowned->addWhere(array(
1564
				"\"{$targetTable}\".\"ID\" NOT IN ({$ownedSQL})" => $ownedParams
1565
			));
1566
1567
			$owner->extend('updateDisownershipQuery', $disowned, $sourceStage, $targetStage, $relationship);
0 ignored issues
show
Bug introduced by
The method extend does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1568
1569
			$disowned->execute();
1570
		}
1571
	}
1572
1573
	/**
1574
	 * Removes the record from both live and stage
1575
	 *
1576
	 * @return bool Success
1577
	 */
1578
	public function doArchive() {
1579
		$owner = $this->owner;
1580
		if(!$owner->canArchive()) {
0 ignored issues
show
Bug introduced by
The method canArchive 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...
1581
			return false;
1582
		}
1583
1584
		$owner->invokeWithExtensions('onBeforeArchive', $this);
0 ignored issues
show
Bug introduced by
The method invokeWithExtensions does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1585
		$owner->doUnpublish();
0 ignored issues
show
Bug introduced by
The method doUnpublish 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...
1586
		$owner->delete();
0 ignored issues
show
Bug introduced by
The method delete does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1587
		$owner->invokeWithExtensions('onAfterArchive', $this);
1588
1589
		return true;
1590
	}
1591
1592
	/**
1593
	 * Removes this record from the live site
1594
	 *
1595
	 * @return bool Flag whether the unpublish was successful
1596
	 */
1597
	public function doUnpublish() {
1598
		$owner = $this->owner;
1599
		if(!$owner->canUnpublish()) {
0 ignored issues
show
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...
1600
			return false;
1601
		}
1602
1603
		// Skip if this record isn't saved
1604
		if(!$owner->isInDB()) {
0 ignored issues
show
Bug introduced by
The method isInDB does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1605
			return false;
1606
		}
1607
1608
		// Skip if this record isn't on live
1609
		if(!$owner->isPublished()) {
0 ignored issues
show
Bug introduced by
The method isPublished 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...
1610
			return false;
1611
		}
1612
1613
		$owner->invokeWithExtensions('onBeforeUnpublish');
0 ignored issues
show
Bug introduced by
The method invokeWithExtensions does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1614
1615
		$origReadingMode = static::get_reading_mode();
1616
		static::set_stage(static::LIVE);
1617
1618
		// This way our ID won't be unset
1619
		$clone = clone $owner;
1620
		$clone->delete();
0 ignored issues
show
Bug introduced by
The method delete does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1621
1622
		static::set_reading_mode($origReadingMode);
1623
1624
		$owner->invokeWithExtensions('onAfterUnpublish');
1625
		return true;
1626
	}
1627
1628
	/**
1629
	 * Trigger unpublish of owning objects
1630
	 */
1631
	public function onAfterUnpublish() {
1632
		$owner = $this->owner;
1633
1634
		// Any objects which owned (and thus relied on the unpublished object) are now unpublished automatically.
1635
		foreach ($owner->findOwners(false) as $object) {
0 ignored issues
show
Bug introduced by
The method findOwners 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...
1636
			/** @var Versioned|DataObject $object */
1637
			$object->doUnpublish();
0 ignored issues
show
Bug introduced by
The method doUnpublish 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...
1638
		}
1639
	}
1640
1641
1642
	/**
1643
	 * Revert the draft changes: replace the draft content with the content on live
1644
	 *
1645
	 * @return bool True if the revert was successful
1646
	 */
1647
	public function doRevertToLive() {
1648
		$owner = $this->owner;
1649
		if(!$owner->canRevertToLive()) {
0 ignored issues
show
Bug introduced by
The method canRevertToLive 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...
1650
			return false;
1651
		}
1652
1653
		$owner->invokeWithExtensions('onBeforeRevertToLive');
0 ignored issues
show
Bug introduced by
The method invokeWithExtensions does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1654
		$owner->copyVersionToStage(static::LIVE, static::DRAFT, false);
0 ignored issues
show
Bug introduced by
The method copyVersionToStage 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...
1655
		$owner->invokeWithExtensions('onAfterRevertToLive');
1656
		return true;
1657
	}
1658
1659
	/**
1660
	 * Trigger revert of all owned objects to stage
1661
	 */
1662
	public function onAfterRevertToLive() {
1663
		$owner = $this->owner;
1664
		/** @var Versioned|DataObject $liveOwner */
1665
		$liveOwner = static::get_by_stage(get_class($owner), static::LIVE)
1666
			->byID($owner->ID);
1667
1668
		// Revert any owned objects from the live stage only
1669
		foreach ($liveOwner->findOwned(false) as $object) {
0 ignored issues
show
Bug introduced by
The method findOwned 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...
1670
			/** @var Versioned|DataObject $object */
1671
			$object->doRevertToLive();
0 ignored issues
show
Bug introduced by
The method doRevertToLive 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...
1672
		}
1673
1674
		// Unlink any objects disowned as a result of this action
1675
		// I.e. objects which aren't owned anymore by this record, but are by the old draft record
1676
		$owner->unlinkDisownedObjects(Versioned::LIVE, Versioned::DRAFT);
0 ignored issues
show
Bug introduced by
The method unlinkDisownedObjects 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...
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...
1677
	}
1678
1679
	/**
1680
	 * @deprecated 4.0..5.0
1681
	 */
1682
	public function publish($fromStage, $toStage, $createNewVersion = false) {
1683
		Deprecation::notice('5.0', 'Use copyVersionToStage instead');
1684
		$this->owner->copyVersionToStage($fromStage, $toStage, $createNewVersion);
0 ignored issues
show
Bug introduced by
The method copyVersionToStage 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...
1685
	}
1686
1687
	/**
1688
	 * Move a database record from one stage to the other.
1689
	 *
1690
	 * @param int|string $fromStage Place to copy from.  Can be either a stage name or a version number.
1691
	 * @param string $toStage Place to copy to.  Must be a stage name.
1692
	 * @param bool $createNewVersion Set this to true to create a new version number.
1693
	 * By default, the existing version number will be copied over.
1694
	 */
1695
	public function copyVersionToStage($fromStage, $toStage, $createNewVersion = false) {
1696
		$owner = $this->owner;
1697
		$owner->invokeWithExtensions('onBeforeVersionedPublish', $fromStage, $toStage, $createNewVersion);
0 ignored issues
show
Bug introduced by
The method invokeWithExtensions does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1698
1699
		$baseClass = $owner->baseClass();
0 ignored issues
show
Bug introduced by
The method baseClass does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
1700
		$baseTable = $owner->baseTable();
1701
1702
		/** @var Versioned|DataObject $from */
1703
		if(is_numeric($fromStage)) {
1704
			$from = Versioned::get_version($baseClass, $owner->ID, $fromStage);
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...
1705
		} else {
1706
			$owner->flushCache();
1707
			$from = Versioned::get_one_by_stage($baseClass, $fromStage, array(
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...
1708
				"\"{$baseTable}\".\"ID\" = ?" => $owner->ID
1709
			));
1710
		}
1711
		if(!$from) {
1712
			throw new InvalidArgumentException("Can't find {$baseClass}#{$owner->ID} in stage {$fromStage}");
1713
		}
1714
1715
		$from->forceChange();
1716
		if($createNewVersion) {
1717
			// Clear version to be automatically created on write
1718
			$from->Version = null;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1719
		} else {
1720
			$from->migrateVersion($from->Version);
1721
1722
			// Mark this version as having been published at some stage
1723
			$publisherID = isset(Member::currentUser()->ID) ? Member::currentUser()->ID : 0;
1724
			$extTable = $this->extendWithSuffix($baseTable);
1725
			DB::prepared_query("UPDATE \"{$extTable}_versions\"
1726
				SET \"WasPublished\" = ?, \"PublisherID\" = ?
1727
				WHERE \"RecordID\" = ? AND \"Version\" = ?",
1728
				array(1, $publisherID, $from->ID, $from->Version)
1729
			);
1730
		}
1731
1732
		// Change to new stage, write, and revert state
1733
		$oldMode = Versioned::get_reading_mode();
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...
1734
		Versioned::set_stage($toStage);
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...
1735
1736
		// Migrate stage prior to write
1737
		$from->setSourceQueryParam('Versioned.mode', 'stage');
1738
		$from->setSourceQueryParam('Versioned.stage', $toStage);
1739
1740
		$conn = DB::get_conn();
1741
		if(method_exists($conn, 'allowPrimaryKeyEditing')) {
1742
			$conn->allowPrimaryKeyEditing($baseTable, true);
1743
			$from->write();
1744
			$conn->allowPrimaryKeyEditing($baseTable, false);
1745
		} else {
1746
			$from->write();
1747
		}
1748
1749
		$from->destroy();
1750
1751
		Versioned::set_reading_mode($oldMode);
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...
1752
1753
		$owner->invokeWithExtensions('onAfterVersionedPublish', $fromStage, $toStage, $createNewVersion);
1754
	}
1755
1756
	/**
1757
	 * Set the migrating version.
1758
	 *
1759
	 * @param string $version The version.
1760
	 */
1761
	public function migrateVersion($version) {
1762
		$this->migratingVersion = $version;
1763
	}
1764
1765
	/**
1766
	 * Compare two stages to see if they're different.
1767
	 *
1768
	 * Only checks the version numbers, not the actual content.
1769
	 *
1770
	 * @param string $stage1 The first stage to check.
1771
	 * @param string $stage2
1772
	 * @return bool
1773
	 */
1774
	public function stagesDiffer($stage1, $stage2) {
1775
		$table1 = $this->baseTable($stage1);
1776
		$table2 = $this->baseTable($stage2);
1777
		$id = $this->owner->ID ?: $this->owner->OldID;
1778
		if (!$id) {
1779
			return true;
1780
		}
1781
1782
		// We test for equality - if one of the versions doesn't exist, this
1783
		// will be false.
1784
1785
		// TODO: DB Abstraction: if statement here:
1786
		$stagesAreEqual = DB::prepared_query(
1787
			"SELECT CASE WHEN \"$table1\".\"Version\"=\"$table2\".\"Version\" THEN 1 ELSE 0 END
1788
			 FROM \"$table1\" INNER JOIN \"$table2\" ON \"$table1\".\"ID\" = \"$table2\".\"ID\"
1789
			 AND \"$table1\".\"ID\" = ?",
1790
			array($id)
1791
		)->value();
1792
1793
		return !$stagesAreEqual;
1794
	}
1795
1796
	/**
1797
	 * @param string $filter
1798
	 * @param string $sort
1799
	 * @param string $limit
1800
	 * @param string $join Deprecated, use leftJoin($table, $joinClause) instead
1801
	 * @param string $having
1802
	 * @return ArrayList
1803
	 */
1804
	public function Versions($filter = "", $sort = "", $limit = "", $join = "", $having = "") {
1805
		return $this->allVersions($filter, $sort, $limit, $join, $having);
1806
	}
1807
1808
	/**
1809
	 * Return a list of all the versions available.
1810
	 *
1811
	 * @param  string $filter
1812
	 * @param  string $sort
1813
	 * @param  string $limit
1814
	 * @param  string $join @deprecated use leftJoin($table, $joinClause) instead
1815
	 * @param  string $having @deprecated
1816
	 * @return ArrayList
1817
	 */
1818
	public function allVersions($filter = "", $sort = "", $limit = "", $join = "", $having = "") {
1819
		// Make sure the table names are not postfixed (e.g. _Live)
1820
		$oldMode = static::get_reading_mode();
1821
		static::set_stage(static::DRAFT);
1822
1823
		$owner = $this->owner;
1824
		$list = DataObject::get(get_class($owner), $filter, $sort, $join, $limit);
1825
		if($having) {
1826
			// @todo - This method doesn't exist on DataList
1827
			$list->having($having);
1828
		}
1829
1830
		$query = $list->dataQuery()->query();
1831
1832
		$baseTable = null;
1833
		foreach($query->getFrom() as $table => $tableJoin) {
1834
			if(is_string($tableJoin) && $tableJoin[0] == '"') {
1835
				$baseTable = str_replace('"','',$tableJoin);
1836
			} elseif(is_string($tableJoin) && substr($tableJoin,0,5) != 'INNER') {
1837
				$query->setFrom(array(
1838
					$table => "LEFT JOIN \"$table\" ON \"$table\".\"RecordID\"=\"{$baseTable}_versions\".\"RecordID\""
1839
						. " AND \"$table\".\"Version\" = \"{$baseTable}_versions\".\"Version\""
1840
				));
1841
			}
1842
			$query->renameTable($table, $table . '_versions');
1843
		}
1844
1845
		// Add all <basetable>_versions columns
1846
		foreach(Config::inst()->get('SilverStripe\ORM\Versioning\Versioned', 'db_for_versions_table') as $name => $type) {
1847
			$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, $name), $name);
1848
		}
1849
1850
		$query->addWhere(array(
1851
			"\"{$baseTable}_versions\".\"RecordID\" = ?" => $owner->ID
1852
		));
1853
		$query->setOrderBy(($sort) ? $sort
1854
			: "\"{$baseTable}_versions\".\"LastEdited\" DESC, \"{$baseTable}_versions\".\"Version\" DESC");
1855
1856
		$records = $query->execute();
1857
		$versions = new ArrayList();
1858
1859
		foreach($records as $record) {
1860
			$versions->push(new Versioned_Version($record));
1861
		}
1862
1863
		Versioned::set_reading_mode($oldMode);
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...
1864
		return $versions;
1865
	}
1866
1867
	/**
1868
	 * Compare two version, and return the diff between them.
1869
	 *
1870
	 * @param string $from The version to compare from.
1871
	 * @param string $to The version to compare to.
1872
	 *
1873
	 * @return DataObject
1874
	 */
1875
	public function compareVersions($from, $to) {
1876
		$owner = $this->owner;
1877
		$fromRecord = Versioned::get_version($owner->class, $owner->ID, $from);
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...
1878
		$toRecord = Versioned::get_version($owner->class, $owner->ID, $to);
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...
1879
1880
		$diff = new DataDifferencer($fromRecord, $toRecord);
1881
1882
		return $diff->diffedData();
1883
	}
1884
1885
	/**
1886
	 * Return the base table - the class that directly extends DataObject.
1887
	 *
1888
	 * Protected so it doesn't conflict with DataObject::baseTable()
1889
	 *
1890
	 * @param string $stage
1891
	 * @return string
1892
	 */
1893
	protected function baseTable($stage = null) {
1894
		$baseTable = $this->owner->baseTable();
1895
		return $this->stageTable($baseTable, $stage);
1896
	}
1897
1898
	/**
1899
	 * Given a table and stage determine the table name.
1900
	 *
1901
	 * Note: Stages this asset does not exist in will default to the draft table.
1902
	 *
1903
	 * @param string $table Main table
1904
	 * @param string $stage
1905
	 * @return string Staged table name
1906
	 */
1907
	public function stageTable($table, $stage) {
1908
		if($this->hasStages() && $stage === static::LIVE) {
1909
			return "{$table}_{$stage}";
1910
		}
1911
		return $table;
1912
	}
1913
1914
	//-----------------------------------------------------------------------------------------------//
1915
1916
1917
	/**
1918
	 * Determine if the current user is able to set the given site stage / archive
1919
	 *
1920
	 * @param HTTPRequest $request
1921
	 * @return bool
1922
	 */
1923
	public static function can_choose_site_stage($request) {
1924
		// Request is allowed if stage isn't being modified
1925
		if((!$request->getVar('stage') || $request->getVar('stage') === static::LIVE)
1926
			&& !$request->getVar('archiveDate')
1927
		) {
1928
			return true;
1929
		}
1930
1931
		// Check permissions with member ID in session.
1932
		$member = Member::currentUser();
1933
		$permissions = Config::inst()->get(get_called_class(), 'non_live_permissions');
1934
		return $member && Permission::checkMember($member, $permissions);
1935
	}
1936
1937
	/**
1938
	 * Choose the stage the site is currently on.
1939
	 *
1940
	 * If $_GET['stage'] is set, then it will use that stage, and store it in
1941
	 * the session.
1942
	 *
1943
	 * if $_GET['archiveDate'] is set, it will use that date, and store it in
1944
	 * the session.
1945
	 *
1946
	 * If neither of these are set, it checks the session, otherwise the stage
1947
	 * is set to 'Live'.
1948
	 */
1949
	public static function choose_site_stage() {
0 ignored issues
show
Coding Style introduced by
choose_site_stage uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1950
		// Check any pre-existing session mode
1951
		$preexistingMode = Session::get('readingMode');
1952
1953
		// Determine the reading mode
1954
		if(isset($_GET['stage'])) {
1955
			$stage = ucfirst(strtolower($_GET['stage']));
1956
			if(!in_array($stage, array(static::DRAFT, static::LIVE))) {
1957
				$stage = static::LIVE;
1958
			}
1959
			$mode = 'Stage.' . $stage;
1960
		} elseif (isset($_GET['archiveDate']) && strtotime($_GET['archiveDate'])) {
1961
			$mode = 'Archive.' . $_GET['archiveDate'];
1962
		} elseif($preexistingMode) {
1963
			$mode = $preexistingMode;
1964
		} else {
1965
			$mode = static::DEFAULT_MODE;
1966
		}
1967
1968
		// Save reading mode
1969
		Versioned::set_reading_mode($mode);
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...
1970
1971
		// Try not to store the mode in the session if not needed
1972
		if(($preexistingMode && $preexistingMode !== $mode)
1973
			|| (!$preexistingMode && $mode !== static::DEFAULT_MODE)
1974
		) {
1975
			Session::set('readingMode', $mode);
1976
		}
1977
1978
		if(!headers_sent() && !Director::is_cli()) {
1979
			if(Versioned::get_stage() == 'Live') {
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...
1980
				// clear the cookie if it's set
1981
				if(Cookie::get('bypassStaticCache')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \SilverStripe\Control\Co...et('bypassStaticCache') of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1982
					Cookie::force_expiry('bypassStaticCache', null, null, false, true /* httponly */);
1983
				}
1984
			} else {
1985
				// set the cookie if it's cleared
1986
				if(!Cookie::get('bypassStaticCache')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \SilverStripe\Control\Co...et('bypassStaticCache') of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1987
					Cookie::set('bypassStaticCache', '1', 0, null, null, false, true /* httponly */);
1988
				}
1989
			}
1990
		}
1991
	}
1992
1993
	/**
1994
	 * Set the current reading mode.
1995
	 *
1996
	 * @param string $mode
1997
	 */
1998
	public static function set_reading_mode($mode) {
1999
		self::$reading_mode = $mode;
2000
	}
2001
2002
	/**
2003
	 * Get the current reading mode.
2004
	 *
2005
	 * @return string
2006
	 */
2007
	public static function get_reading_mode() {
2008
		return self::$reading_mode;
2009
	}
2010
2011
	/**
2012
	 * Get the current reading stage.
2013
	 *
2014
	 * @return string
2015
	 */
2016
	public static function get_stage() {
2017
		$parts = explode('.', Versioned::get_reading_mode());
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...
2018
2019
		if($parts[0] == 'Stage') {
2020
			return $parts[1];
2021
		}
2022
		return null;
2023
	}
2024
2025
	/**
2026
	 * Get the current archive date.
2027
	 *
2028
	 * @return string
2029
	 */
2030
	public static function current_archived_date() {
2031
		$parts = explode('.', Versioned::get_reading_mode());
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...
2032
		if($parts[0] == 'Archive') {
2033
			return $parts[1];
2034
		}
2035
		return null;
2036
	}
2037
2038
	/**
2039
	 * Set the reading stage.
2040
	 *
2041
	 * @param string $stage New reading stage.
2042
	 * @throws InvalidArgumentException
2043
	 */
2044
	public static function set_stage($stage) {
2045
		if(!in_array($stage, [static::LIVE, static::DRAFT])) {
2046
			throw new \InvalidArgumentException("Invalid stage name \"{$stage}\"");
2047
		}
2048
		static::set_reading_mode('Stage.' . $stage);
2049
	}
2050
2051
	/**
2052
	 * Set the reading archive date.
2053
	 *
2054
	 * @param string $date New reading archived date.
2055
	 */
2056
	public static function reading_archived_date($date) {
2057
		Versioned::set_reading_mode('Archive.' . $date);
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...
2058
	}
2059
2060
2061
	/**
2062
	 * Get a singleton instance of a class in the given stage.
2063
	 *
2064
	 * @param string $class The name of the class.
2065
	 * @param string $stage The name of the stage.
2066
	 * @param string $filter A filter to be inserted into the WHERE clause.
2067
	 * @param boolean $cache Use caching.
2068
	 * @param string $sort A sort expression to be inserted into the ORDER BY clause.
2069
	 *
2070
	 * @return DataObject
2071
	 */
2072
	public static function get_one_by_stage($class, $stage, $filter = '', $cache = true, $sort = '') {
0 ignored issues
show
Unused Code introduced by
The parameter $cache is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2073
		// TODO: No identity cache operating
2074
		$items = static::get_by_stage($class, $stage, $filter, $sort, null, 1);
2075
2076
		return $items->first();
2077
	}
2078
2079
	/**
2080
	 * Gets the current version number of a specific record.
2081
	 *
2082
	 * @param string $class
2083
	 * @param string $stage
2084
	 * @param int $id
2085
	 * @param boolean $cache
2086
	 *
2087
	 * @return int
2088
	 */
2089
	public static function get_versionnumber_by_stage($class, $stage, $id, $cache = true) {
2090
		$baseClass = DataObject::getSchema()->baseDataClass($class);
2091
		$stageTable = DataObject::getSchema()->tableName($baseClass);
2092
		if($stage === static::LIVE) {
2093
			$stageTable .= "_{$stage}";
2094
		}
2095
2096
		// cached call
2097
		if($cache && isset(self::$cache_versionnumber[$baseClass][$stage][$id])) {
2098
			return self::$cache_versionnumber[$baseClass][$stage][$id];
2099
		}
2100
2101
		// get version as performance-optimized SQL query (gets called for each record in the sitetree)
2102
		$version = DB::prepared_query(
2103
			"SELECT \"Version\" FROM \"$stageTable\" WHERE \"ID\" = ?",
2104
			array($id)
2105
		)->value();
2106
2107
		// cache value (if required)
2108
		if($cache) {
2109
			if(!isset(self::$cache_versionnumber[$baseClass])) {
2110
				self::$cache_versionnumber[$baseClass] = array();
2111
			}
2112
2113
			if(!isset(self::$cache_versionnumber[$baseClass][$stage])) {
2114
				self::$cache_versionnumber[$baseClass][$stage] = array();
2115
			}
2116
2117
			self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
2118
		}
2119
2120
		return $version;
2121
	}
2122
2123
	/**
2124
	 * Pre-populate the cache for Versioned::get_versionnumber_by_stage() for
2125
	 * a list of record IDs, for more efficient database querying.  If $idList
2126
	 * is null, then every record will be pre-cached.
2127
	 *
2128
	 * @param string $class
2129
	 * @param string $stage
2130
	 * @param array $idList
2131
	 */
2132
	public static function prepopulate_versionnumber_cache($class, $stage, $idList = null) {
2133
		if (!Config::inst()->get('SilverStripe\ORM\Versioning\Versioned', 'prepopulate_versionnumber_cache')) {
2134
			return;
2135
		}
2136
		$filter = "";
2137
		$parameters = array();
2138
		if($idList) {
2139
			// Validate the ID list
2140
			foreach($idList as $id) {
2141
				if(!is_numeric($id)) {
2142
					user_error("Bad ID passed to Versioned::prepopulate_versionnumber_cache() in \$idList: " . $id,
2143
					E_USER_ERROR);
2144
				}
2145
			}
2146
			$filter = 'WHERE "ID" IN ('.DB::placeholders($idList).')';
2147
			$parameters = $idList;
2148
		}
2149
2150
		/** @var Versioned|DataObject $singleton */
2151
		$singleton = DataObject::singleton($class);
2152
		$baseClass = $singleton->baseClass();
0 ignored issues
show
Bug introduced by
The method baseClass does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2153
		$baseTable = $singleton->baseTable();
2154
		$stageTable = $singleton->stageTable($baseTable, $stage);
0 ignored issues
show
Bug introduced by
The method stageTable 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...
2155
2156
		$versions = DB::prepared_query("SELECT \"ID\", \"Version\" FROM \"$stageTable\" $filter", $parameters)->map();
2157
2158
		foreach($versions as $id => $version) {
2159
			self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
2160
		}
2161
	}
2162
2163
	/**
2164
	 * Get a set of class instances by the given stage.
2165
	 *
2166
	 * @param string $class The name of the class.
2167
	 * @param string $stage The name of the stage.
2168
	 * @param string $filter A filter to be inserted into the WHERE clause.
2169
	 * @param string $sort A sort expression to be inserted into the ORDER BY clause.
2170
	 * @param string $join Deprecated, use leftJoin($table, $joinClause) instead
2171
	 * @param int $limit A limit on the number of records returned from the database.
2172
	 * @param string $containerClass The container class for the result set (default is DataList)
2173
	 *
2174
	 * @return DataList A modified DataList designated to the specified stage
2175
	 */
2176
	public static function get_by_stage(
2177
		$class, $stage, $filter = '', $sort = '', $join = '', $limit = null, $containerClass = 'SilverStripe\ORM\DataList'
2178
	) {
2179
		$result = DataObject::get($class, $filter, $sort, $join, $limit, $containerClass);
2180
		return $result->setDataQueryParam(array(
2181
			'Versioned.mode' => 'stage',
2182
			'Versioned.stage' => $stage
2183
		));
2184
	}
2185
2186
	/**
2187
	 * Delete this record from the given stage
2188
	 *
2189
	 * @param string $stage
2190
	 */
2191
	public function deleteFromStage($stage) {
2192
		$oldMode = Versioned::get_reading_mode();
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...
2193
		Versioned::set_stage($stage);
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...
2194
		$owner = $this->owner;
2195
		$clone = clone $owner;
2196
		$clone->delete();
0 ignored issues
show
Bug introduced by
The method delete does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2197
		Versioned::set_reading_mode($oldMode);
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...
2198
2199
		// Fix the version number cache (in case you go delete from stage and then check ExistsOnLive)
2200
		$baseClass = $owner->baseClass();
0 ignored issues
show
Bug introduced by
The method baseClass does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2201
		self::$cache_versionnumber[$baseClass][$stage][$owner->ID] = null;
2202
	}
2203
2204
	/**
2205
	 * Write the given record to the draft stage
2206
	 *
2207
	 * @param string $stage
2208
	 * @param boolean $forceInsert
2209
	 * @return int The ID of the record
2210
	 */
2211
	public function writeToStage($stage, $forceInsert = false) {
2212
		$oldMode = Versioned::get_reading_mode();
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...
2213
		Versioned::set_stage($stage);
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...
2214
2215
		$owner = $this->owner;
2216
		$owner->forceChange();
0 ignored issues
show
Bug introduced by
The method forceChange does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2217
		$result = $owner->write(false, $forceInsert);
0 ignored issues
show
Bug introduced by
The method write does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2218
		Versioned::set_reading_mode($oldMode);
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...
2219
2220
		return $result;
2221
	}
2222
2223
	/**
2224
	 * Roll the draft version of this record to match the published record.
2225
	 * Caution: Doesn't overwrite the object properties with the rolled back version.
2226
	 *
2227
	 * {@see doRevertToLive()} to reollback to live
2228
	 *
2229
	 * @param int $version Version number
2230
	 */
2231
	public function doRollbackTo($version) {
2232
		$owner = $this->owner;
2233
		$owner->extend('onBeforeRollback', $version);
0 ignored issues
show
Bug introduced by
The method extend does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2234
		$owner->copyVersionToStage($version, static::DRAFT, true);
0 ignored issues
show
Bug introduced by
The method copyVersionToStage 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...
2235
		$owner->writeWithoutVersion();
0 ignored issues
show
Bug introduced by
The method writeWithoutVersion 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...
2236
		$owner->extend('onAfterRollback', $version);
2237
	}
2238
2239
	public function onAfterRollback($version) {
2240
		// Find record at this version
2241
		$baseClass = DataObject::getSchema()->baseDataClass($this->owner);
2242
		/** @var Versioned|DataObject $recordVersion */
2243
		$recordVersion = static::get_version($baseClass, $this->owner->ID, $version);
2244
2245
		// Note that unlike other publishing actions, rollback is NOT recursive;
2246
		// The owner collects all objects and writes them back using writeToStage();
2247
		foreach ($recordVersion->findOwned() as $object) {
0 ignored issues
show
Bug introduced by
The method findOwned 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...
2248
			/** @var Versioned|DataObject $object */
2249
			$object->writeToStage(static::DRAFT);
0 ignored issues
show
Bug introduced by
The method writeToStage 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...
2250
		}
2251
	}
2252
2253
	/**
2254
	 * Return the latest version of the given record.
2255
	 *
2256
	 * @param string $class
2257
	 * @param int $id
2258
	 * @return DataObject
2259
	 */
2260
	public static function get_latest_version($class, $id) {
2261
		$baseClass = DataObject::getSchema()->baseDataClass($class);
2262
		$list = DataList::create($baseClass)
2263
			->setDataQueryParam("Versioned.mode", "latest_versions");
2264
2265
		return $list->byID($id);
2266
	}
2267
2268
	/**
2269
	 * Returns whether the current record is the latest one.
2270
	 *
2271
	 * @todo Performance - could do this directly via SQL.
2272
	 *
2273
	 * @see get_latest_version()
2274
	 * @see latestPublished
2275
	 *
2276
	 * @return boolean
2277
	 */
2278
	public function isLatestVersion() {
2279
		$owner = $this->owner;
2280
		if(!$owner->isInDB()) {
0 ignored issues
show
Bug introduced by
The method isInDB does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Versioning\Versioned.

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...
2281
			return false;
2282
		}
2283
2284
		$version = static::get_latest_version($owner->class, $owner->ID);
2285
		return ($version->Version == $owner->Version);
2286
	}
2287
2288
	/**
2289
	 * Check if this record exists on live
2290
	 *
2291
	 * @return bool
2292
	 */
2293
	public function isPublished() {
2294
		$id = $this->owner->ID ?: $this->owner->OldID;
2295
		if(!$id) {
2296
			return false;
2297
		}
2298
2299
		// Non-staged objects are considered "published" if saved
2300
		if(!$this->hasStages()) {
2301
			return true;
2302
		}
2303
2304
		$table = $this->baseTable(static::LIVE);
2305
		$result = DB::prepared_query(
2306
			"SELECT COUNT(*) FROM \"{$table}\" WHERE \"{$table}\".\"ID\" = ?",
2307
			array($id)
2308
		);
2309
		return (bool)$result->value();
2310
	}
2311
2312
	/**
2313
	 * Check if page doesn't exist on any stage, but used to be
2314
	 *
2315
	 * @return bool
2316
	 */
2317
	public function isArchived() {
2318
		$id = $this->owner->ID ?: $this->owner->OldID;
2319
		return $id && !$this->isOnDraft() && !$this->isPublished();
2320
	}
2321
2322
	/**
2323
	 * Check if this record exists on the draft stage
2324
	 *
2325
	 * @return bool
2326
	 */
2327
	public function isOnDraft() {
2328
		$id = $this->owner->ID ?: $this->owner->OldID;
2329
		if(!$id) {
2330
			return false;
2331
		}
2332
2333
		$table = $this->baseTable();
2334
		$result = DB::prepared_query(
2335
			"SELECT COUNT(*) FROM \"{$table}\" WHERE \"{$table}\".\"ID\" = ?",
2336
			array($id)
2337
		);
2338
		return (bool)$result->value();
2339
	}
2340
2341
    /**
2342
     * Compares current draft with live version, and returns true if no draft version of this page exists  but the page
2343
     * is still published (eg, after triggering "Delete from draft site" in the CMS).
2344
     *
2345
     * @return bool
2346
     */
2347
    public function isOnLiveOnly() {
2348
        return $this->isPublished() && !$this->isOnDraft();
2349
    }
2350
2351
    /**
2352
     * Compares current draft with live version, and returns true if no live version exists, meaning the page was never
2353
     * published.
2354
     *
2355
     * @return bool
2356
     */
2357
    public function isOnDraftOnly() {
2358
        return $this->isOnDraft() && !$this->isPublished();
2359
    }
2360
2361
    /**
2362
     * Compares current draft with live version, and returns true if these versions differ, meaning there have been
2363
     * unpublished changes to the draft site.
2364
     *
2365
     * @return bool
2366
     */
2367
    public function isModifiedOnDraft() {
2368
        return $this->isOnDraft() && $this->stagesDiffer(Versioned::DRAFT, Versioned::LIVE);
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...
2369
    }
2370
2371
	/**
2372
	 * Return the equivalent of a DataList::create() call, querying the latest
2373
	 * version of each record stored in the (class)_versions tables.
2374
	 *
2375
	 * In particular, this will query deleted records as well as active ones.
2376
	 *
2377
	 * @param string $class
2378
	 * @param string $filter
2379
	 * @param string $sort
2380
	 * @return DataList
2381
	 */
2382
	public static function get_including_deleted($class, $filter = "", $sort = "") {
2383
		$list = DataList::create($class)
2384
			->where($filter)
2385
			->sort($sort)
2386
			->setDataQueryParam("Versioned.mode", "latest_versions");
2387
2388
		return $list;
2389
	}
2390
2391
	/**
2392
	 * Return the specific version of the given id.
2393
	 *
2394
	 * Caution: The record is retrieved as a DataObject, but saving back
2395
	 * modifications via write() will create a new version, rather than
2396
	 * modifying the existing one.
2397
	 *
2398
	 * @param string $class
2399
	 * @param int $id
2400
	 * @param int $version
2401
	 *
2402
	 * @return DataObject
2403
	 */
2404
	public static function get_version($class, $id, $version) {
2405
		$baseClass = DataObject::getSchema()->baseDataClass($class);
2406
		$list = DataList::create($baseClass)
2407
			->setDataQueryParam([
2408
				"Versioned.mode" => 'version',
2409
				"Versioned.version" => $version
2410
			]);
2411
2412
		return $list->byID($id);
2413
	}
2414
2415
	/**
2416
	 * Return a list of all versions for a given id.
2417
	 *
2418
	 * @param string $class
2419
	 * @param int $id
2420
	 *
2421
	 * @return DataList
2422
	 */
2423
	public static function get_all_versions($class, $id) {
2424
		$list = DataList::create($class)
2425
			->filter('ID', $id)
2426
			->setDataQueryParam('Versioned.mode', 'all_versions');
2427
2428
		return $list;
2429
	}
2430
2431
	/**
2432
	 * @param array $labels
2433
	 */
2434
	public function updateFieldLabels(&$labels) {
2435
		$labels['Versions'] = _t('Versioned.has_many_Versions', 'Versions', 'Past Versions of this record');
2436
	}
2437
2438
	/**
2439
	 * @param FieldList $fields
2440
	 */
2441
	public function updateCMSFields(FieldList $fields) {
2442
		// remove the version field from the CMS as this should be left
2443
		// entirely up to the extension (not the cms user).
2444
		$fields->removeByName('Version');
2445
	}
2446
2447
	/**
2448
	 * Ensure version ID is reset to 0 on duplicate
2449
	 *
2450
	 * @param DataObject $source Record this was duplicated from
2451
	 * @param bool $doWrite
2452
	 */
2453
	public function onBeforeDuplicate($source, $doWrite) {
0 ignored issues
show
Unused Code introduced by
The parameter $source is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $doWrite is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2454
		$this->owner->Version = 0;
2455
	}
2456
2457
	public function flushCache() {
2458
		self::$cache_versionnumber = array();
2459
	}
2460
2461
	/**
2462
	 * Return a piece of text to keep DataObject cache keys appropriately specific.
2463
	 *
2464
	 * @return string
2465
	 */
2466
	public function cacheKeyComponent() {
2467
		return 'versionedmode-'.static::get_reading_mode();
2468
	}
2469
2470
	/**
2471
	 * Returns an array of possible stages.
2472
	 *
2473
	 * @return array
2474
	 */
2475
	public function getVersionedStages() {
2476
		if($this->hasStages()) {
2477
			return [static::DRAFT, static::LIVE];
2478
		} else {
2479
			return [static::DRAFT];
2480
		}
2481
	}
2482
2483
	public static function get_template_global_variables() {
2484
		return array(
2485
			'CurrentReadingMode' => 'get_reading_mode'
2486
		);
2487
	}
2488
2489
	/**
2490
	 * Check if this object has stages
2491
	 *
2492
	 * @return bool True if this object is staged
2493
	 */
2494
	public function hasStages() {
2495
		return $this->mode === static::STAGEDVERSIONED;
2496
	}
2497
}
2498