Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

VersionedTest::testUniqueIndexes()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 34
Code Lines 23

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 34
rs 8.5806
cc 4
eloc 23
nc 4
nop 0
1
<?php
2
3
/**
4
 * @package framework
5
 * @subpackage tests
6
 */
7
class VersionedTest extends SapphireTest {
8
9
	protected static $fixture_file = 'VersionedTest.yml';
10
11
	protected $extraDataObjects = array(
12
		'VersionedTest_DataObject',
13
		'VersionedTest_Subclass',
14
		'VersionedTest_AnotherSubclass',
15
		'VersionedTest_RelatedWithoutVersion',
16
		'VersionedTest_SingleStage',
17
		'VersionedTest_WithIndexes',
18
		'VersionedTest_PublicStage',
19
		'VersionedTest_PublicViaExtension',
20
	);
21
22
	protected $requiredExtensions = array(
23
		"VersionedTest_DataObject" => array('Versioned'),
24
		"VersionedTest_WithIndexes" => array('Versioned'),
25
	);
26
27
	public function testUniqueIndexes() {
28
		$tableExpectations = array(
29
			'VersionedTest_WithIndexes' =>
30
				array('value' => true, 'message' => 'Unique indexes are unique in main table'),
31
			'VersionedTest_WithIndexes_versions' =>
32
				array('value' => false, 'message' => 'Unique indexes are no longer unique in _versions table'),
33
			'VersionedTest_WithIndexes_Live' =>
34
				array('value' => false, 'message' => 'Unique indexes are no longer unique in _Live table'),
35
		);
36
37
		// Test each table's performance
38
		foreach ($tableExpectations as $tableName => $expectation) {
39
			$indexes = DB::get_schema()->indexList($tableName);
40
41
			// Check for presence of all unique indexes
42
			$indexColumns = array_map(function($index) {
43
				return $index['value'];
44
			}, $indexes);
45
			sort($indexColumns);
46
			$expectedColumns = array('"UniqA"', '"UniqS"');
47
			$this->assertEquals(
48
					array_values($expectedColumns),
49
					array_values(array_intersect($indexColumns, $expectedColumns)),
50
					"$tableName has both indexes");
51
52
			// Check unique -> non-unique conversion
53
			foreach ($indexes as $indexKey => $indexSpec) {
54
				if (in_array($indexSpec['value'], $expectedColumns)) {
55
					$isUnique = $indexSpec['type'] === 'unique';
56
					$this->assertEquals($isUnique, $expectation['value'], $expectation['message']);
57
}
58
			}
59
		}
60
	}
61
62
	public function testDeletingOrphanedVersions() {
63
		$obj = new VersionedTest_Subclass();
64
		$obj->ExtraField = 'Foo'; // ensure that child version table gets written
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
65
		$obj->write();
66
		$obj->publish('Stage', 'Live');
67
68
		$obj->ExtraField = 'Bar'; // ensure that child version table gets written
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
69
		$obj->write();
70
		$obj->publish('Stage', 'Live');
71
72
		$versions = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
73
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
74
75
		$this->assertGreaterThan(0, $versions, 'At least 1 version exists in the history of the page');
76
77
		// Force orphaning of all versions created earlier, only on parent record.
78
		// The child versiones table should still have the correct relationship
79
		DB::query("DELETE FROM \"VersionedTest_DataObject_versions\" WHERE \"RecordID\" = $obj->ID");
80
81
		// insert a record with no primary key (ID)
82
		DB::query("INSERT INTO \"VersionedTest_DataObject_versions\" (\"RecordID\") VALUES ($obj->ID)");
83
84
		// run the script which should clean that up
85
		$obj->augmentDatabase();
86
87
		$versions = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
88
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
89
		$this->assertEquals(0, $versions, 'Orphaned versions on child tables are removed');
90
91
		// test that it doesn't delete records that we need
92
		$obj->write();
93
		$obj->publish('Stage', 'Live');
94
95
		$count = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
96
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
97
		$obj->augmentDatabase();
98
99
		$count2 = DB::query("SELECT COUNT(*) FROM \"VersionedTest_Subclass_versions\""
100
			. " WHERE \"RecordID\" = '$obj->ID'")->value();
101
102
		$this->assertEquals($count, $count2);
103
	}
104
105
	public function testDuplicate() {
106
		$obj1 = new VersionedTest_Subclass();
107
		$obj1->ExtraField = 'Foo';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
108
		$obj1->write(); // version 1
109
		$obj1->publish('Stage', 'Live');
110
		$obj1->ExtraField = 'Foo2';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
111
		$obj1->write(); // version 2
112
113
		// Make duplicate
114
		$obj2 = $obj1->duplicate();
115
116
		// Check records differ
117
		$this->assertNotEquals($obj1->ID, $obj2->ID);
118
		$this->assertEquals(2, $obj1->Version);
119
		$this->assertEquals(1, $obj2->Version);
120
	}
121
122
	public function testForceChangeUpdatesVersion() {
123
		$obj = new VersionedTest_DataObject();
124
		$obj->Name = "test";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<VersionedTest_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...
125
		$obj->write();
126
127
		$oldVersion = $obj->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<VersionedTest_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...
128
		$obj->forceChange();
129
		$obj->write();
130
131
		$this->assertTrue(
132
			($obj->Version > $oldVersion),
133
			"A object Version is increased when just calling forceChange() without any other changes"
134
		);
135
	}
136
137
	/**
138
	 * Test Versioned::get_including_deleted()
139
	 */
140
	public function testGetIncludingDeleted() {
141
		// Get all ids of pages
142
		$allPageIDs = DataObject::get(
143
			'VersionedTest_DataObject',
144
			"\"ParentID\" = 0", "\"VersionedTest_DataObject\".\"ID\" ASC"
145
		)->column('ID');
146
147
		// Modify a page, ensuring that the Version ID and Record ID will differ,
148
		// and then subsequently delete it
149
		$targetPage = $this->objFromFixture('VersionedTest_DataObject', 'page3');
150
		$targetPage->Content = 'To be deleted';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<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...
151
		$targetPage->write();
152
		$targetPage->delete();
153
154
		// Get all items, ignoring deleted
155
		$remainingPages = DataObject::get("VersionedTest_DataObject", "\"ParentID\" = 0",
156
			"\"VersionedTest_DataObject\".\"ID\" ASC");
157
		// Check that page 3 has gone
158
		$this->assertNotNull($remainingPages);
159
		$this->assertEquals(array("Page 1", "Page 2", "Subclass Page 1"), $remainingPages->column('Title'));
160
161
		// Get all including deleted
162
		$allPages = Versioned::get_including_deleted("VersionedTest_DataObject", "\"ParentID\" = 0",
163
			"\"VersionedTest_DataObject\".\"ID\" ASC");
164
		// Check that page 3 is still there
165
		$this->assertEquals(array("Page 1", "Page 2", "Page 3", "Subclass Page 1"), $allPages->column('Title'));
166
167
		// Check that the returned pages have the correct IDs
168
		$this->assertEquals($allPageIDs, $allPages->column('ID'));
169
170
		// Check that this still works if we switch to reading the other stage
171
		Versioned::reading_stage("Live");
172
		$allPages = Versioned::get_including_deleted("VersionedTest_DataObject", "\"ParentID\" = 0",
173
			"\"VersionedTest_DataObject\".\"ID\" ASC");
174
		$this->assertEquals(array("Page 1", "Page 2", "Page 3", "Subclass Page 1"), $allPages->column('Title'));
175
176
		// Check that the returned pages still have the correct IDs
177
		$this->assertEquals($allPageIDs, $allPages->column('ID'));
178
	}
179
180
	public function testVersionedFieldsAdded() {
181
		$obj = new VersionedTest_DataObject();
182
		// Check that the Version column is added as a full-fledged column
183
		$this->assertInstanceOf('Int', $obj->dbObject('Version'));
184
185
		$obj2 = new VersionedTest_Subclass();
186
		// Check that the Version column is added as a full-fledged column
187
		$this->assertInstanceOf('Int', $obj2->dbObject('Version'));
188
	}
189
190
	public function testVersionedFieldsNotInCMS() {
191
		$obj = new VersionedTest_DataObject();
192
193
		// the version field in cms causes issues with Versioned::augmentWrite()
194
		$this->assertNull($obj->getCMSFields()->dataFieldByName('Version'));
195
	}
196
197
	public function testPublishCreateNewVersion() {
198
		$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
199
		$page1->Content = 'orig';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<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...
200
		$page1->write();
201
		$firstVersion = $page1->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
202
		$page1->publish('Stage', 'Live', false);
203
		$this->assertEquals(
204
			$firstVersion,
205
			$page1->Version,
206
			'publish() with $createNewVersion=FALSE does not create a new version'
207
		);
208
209
		$page1->Content = 'changed';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<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...
210
		$page1->write();
211
		$secondVersion = $page1->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
212
		$this->assertTrue($firstVersion < $secondVersion, 'write creates new version');
213
214
		$page1->publish('Stage', 'Live', true);
215
		$thirdVersion = Versioned::get_latest_version('VersionedTest_DataObject', $page1->ID)->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
216
		$liveVersion = Versioned::get_versionnumber_by_stage('VersionedTest_DataObject', 'Live', $page1->ID);
217
		$stageVersion = Versioned::get_versionnumber_by_stage('VersionedTest_DataObject', 'Stage', $page1->ID);
218
		$this->assertTrue(
219
			$secondVersion < $thirdVersion,
220
			'publish() with $createNewVersion=TRUE creates a new version'
221
		);
222
		$this->assertEquals(
223
			$liveVersion,
224
			$thirdVersion,
225
			'publish() with $createNewVersion=TRUE publishes to live'
226
		);
227
		$this->assertEquals(
228
			$stageVersion,
229
			$secondVersion,
230
			'publish() with $createNewVersion=TRUE does not affect stage'
231
		);
232
	}
233
234
	public function testRollbackTo() {
235
		$page1 = $this->objFromFixture('VersionedTest_AnotherSubclass', 'subclass1');
236
		$page1->Content = 'orig';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<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...
237
		$page1->write();
238
		$page1->publish('Stage', 'Live');
239
		$origVersion = $page1->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
240
241
		$page1->Content = 'changed';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<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...
242
		$page1->write();
243
		$page1->publish('Stage', 'Live');
244
		$changedVersion = $page1->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
245
246
		$page1->doRollbackTo($origVersion);
247
		$page1 = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', array(
0 ignored issues
show
Documentation introduced by
array('"VersionedTest_Da...ID" = ?' => $page1->ID) is of type array<string,integer,{"\...\"ID\" = ?":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
			'"VersionedTest_DataObject"."ID" = ?' => $page1->ID
249
		));
250
251
		$this->assertTrue($page1->Version == $changedVersion + 1, 'Create a new higher version number');
252
		$this->assertEquals('orig', $page1->Content, 'Copies the content from the old version');
253
254
		// check db entries
255
		$version = DB::prepared_query("SELECT MAX(\"Version\") FROM \"VersionedTest_DataObject_versions\" WHERE \"RecordID\" = ?",
256
			array($page1->ID)
257
		)->value();
258
		$this->assertEquals($page1->Version, $version, 'Correct entry in VersionedTest_DataObject_versions');
259
260
		$version = DB::prepared_query("SELECT MAX(\"Version\") FROM \"VersionedTest_AnotherSubclass_versions\" WHERE \"RecordID\" = ?",
261
			array($page1->ID)
262
		)->value();
263
		$this->assertEquals($page1->Version, $version, 'Correct entry in VersionedTest_AnotherSubclass_versions');
264
	}
265
266
	public function testDeleteFromStage() {
267
		$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
268
		$pageID = $page1->ID;
269
270
		$page1->Content = 'orig';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<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...
271
		$page1->write();
272
		$page1->publish('Stage', 'Live');
273
274
		$this->assertEquals(1,
275
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value());
276
		$this->assertEquals(1,
277
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value());
278
279
		$page1->deleteFromStage('Live');
280
281
		// Confirm that deleteFromStage() doesn't manipulate the original record
282
		$this->assertEquals($pageID, $page1->ID);
283
284
		$this->assertEquals(1,
285
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value());
286
		$this->assertEquals(0,
287
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value());
288
289
		$page1->delete();
290
291
		$this->assertEquals(0, $page1->ID);
292
		$this->assertEquals(0,
293
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value());
294
		$this->assertEquals(0,
295
			DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value());
296
	}
297
298 View Code Duplication
	public function testWritingNewToStage() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
299
		$origStage = Versioned::current_stage();
300
301
		Versioned::reading_stage("Stage");
302
		$page = new VersionedTest_DataObject();
303
		$page->Title = "testWritingNewToStage";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_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...
304
		$page->URLSegment = "testWritingNewToStage";
0 ignored issues
show
Documentation introduced by
The property URLSegment does not exist on object<VersionedTest_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...
305
		$page->write();
306
307
		$live = Versioned::get_by_stage('VersionedTest_DataObject', 'Live', array(
0 ignored issues
show
Documentation introduced by
array('"VersionedTest_Da...ve"."ID"' => $page->ID) is of type array<string,integer,{"\...e\".\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
308
			'"VersionedTest_DataObject_Live"."ID"' => $page->ID
309
		));
310
		$this->assertEquals(0, $live->count());
311
312
		$stage = Versioned::get_by_stage('VersionedTest_DataObject', 'Stage',array(
0 ignored issues
show
Documentation introduced by
array('"VersionedTest_Da...ct"."ID"' => $page->ID) is of type array<string,integer,{"\...t\".\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
313
			'"VersionedTest_DataObject"."ID"' => $page->ID
314
		));
315
		$this->assertEquals(1, $stage->count());
316
		$this->assertEquals($stage->First()->Title, 'testWritingNewToStage');
317
318
		Versioned::reading_stage($origStage);
319
	}
320
321
	/**
322
	 * This tests for the situation described in the ticket #5596.
323
	 * Writing new Page to live first creates a row in VersionedTest_DataObject table (to get the new ID),
324
	 * then "changes it's mind" in Versioned and writes VersionedTest_DataObject_Live. It does not remove
325
	 * the VersionedTest_DataObject record though.
326
	 */
327 View Code Duplication
	public function testWritingNewToLive() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
328
		$origStage = Versioned::current_stage();
329
330
		Versioned::reading_stage("Live");
331
		$page = new VersionedTest_DataObject();
332
		$page->Title = "testWritingNewToLive";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_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...
333
		$page->URLSegment = "testWritingNewToLive";
0 ignored issues
show
Documentation introduced by
The property URLSegment does not exist on object<VersionedTest_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...
334
		$page->write();
335
336
		$live = Versioned::get_by_stage('VersionedTest_DataObject', 'Live',array(
0 ignored issues
show
Documentation introduced by
array('"VersionedTest_Da...ve"."ID"' => $page->ID) is of type array<string,integer,{"\...e\".\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
337
			'"VersionedTest_DataObject_Live"."ID"' => $page->ID
338
		));
339
		$this->assertEquals(1, $live->count());
340
		$this->assertEquals($live->First()->Title, 'testWritingNewToLive');
341
342
		$stage = Versioned::get_by_stage('VersionedTest_DataObject', 'Stage',array(
0 ignored issues
show
Documentation introduced by
array('"VersionedTest_Da...ct"."ID"' => $page->ID) is of type array<string,integer,{"\...t\".\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
343
			'"VersionedTest_DataObject"."ID"' => $page->ID
344
		));
345
		$this->assertEquals(0, $stage->count());
346
347
		Versioned::reading_stage($origStage);
348
	}
349
350
	/**
351
	 * Tests DataObject::hasOwnTableDatabaseField
352
	 */
353
	public function testHasOwnTableDatabaseFieldWithVersioned() {
354
		$noversion    = new DataObject();
355
		$versioned    = new VersionedTest_DataObject();
356
		$versionedSub = new VersionedTest_Subclass();
357
		$versionedAno = new VersionedTest_AnotherSubclass();
358
		$versionField = new VersionedTest_UnversionedWithField();
359
360
		$this->assertFalse(
361
			(bool) $noversion->hasOwnTableDatabaseField('Version'),
362
			'Plain models have no version field.'
363
		);
364
		$this->assertEquals(
365
			'Int', $versioned->hasOwnTableDatabaseField('Version'),
366
			'The versioned ext adds an Int version field.'
367
		);
368
		$this->assertEquals(
369
			null,
370
			$versionedSub->hasOwnTableDatabaseField('Version'),
371
			'Sub-classes of a versioned model don\'t have a Version field.'
372
		);
373
		$this->assertEquals(
374
			null,
375
			$versionedAno->hasOwnTableDatabaseField('Version'),
376
			'Sub-classes of a versioned model don\'t have a Version field.'
377
		);
378
		$this->assertEquals(
379
			'Varchar', $versionField->hasOwnTableDatabaseField('Version'),
380
			'Models w/o Versioned can have their own Version field.'
381
		);
382
	}
383
384
	/**
385
	 * Test that SQLQuery::queriedTables() applies the version-suffixes properly.
386
	 */
387
	public function testQueriedTables() {
388
		Versioned::reading_stage('Live');
389
390
		$this->assertEquals(array(
391
			'VersionedTest_DataObject_Live',
392
			'VersionedTest_Subclass_Live',
393
		), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables());
394
	}
395
396
	public function testGetVersionWhenClassnameChanged() {
397
		$obj = new VersionedTest_DataObject;
398
		$obj->Name = "test";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<VersionedTest_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...
399
		$obj->write();
400
		$obj->Name = "test2";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<VersionedTest_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...
401
		$obj->ClassName = "VersionedTest_Subclass";
402
		$obj->write();
403
		$subclassVersion = $obj->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<VersionedTest_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...
404
405
		$obj->Name = "test3";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<VersionedTest_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...
406
		$obj->ClassName = "VersionedTest_DataObject";
407
		$obj->write();
408
409
		// We should be able to pass the subclass and still get the correct class back
410
		$obj2 = Versioned::get_version("VersionedTest_Subclass", $obj->ID, $subclassVersion);
411
		$this->assertInstanceOf("VersionedTest_Subclass", $obj2);
412
		$this->assertEquals("test2", $obj2->Name);
413
414
		$obj3 = Versioned::get_latest_version("VersionedTest_Subclass", $obj->ID);
415
		$this->assertEquals("test3", $obj3->Name);
416
		$this->assertInstanceOf("VersionedTest_DataObject", $obj3);
417
418
	}
419
420
	public function testArchiveVersion() {
421
422
		// In 2005 this file was created
423
		SS_Datetime::set_mock_now('2005-01-01 00:00:00');
424
		$testPage = new VersionedTest_Subclass();
425
		$testPage->Title = 'Archived page';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_Subclass>. 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...
426
		$testPage->Content = 'This is the content from 2005';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_Subclass>. 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...
427
		$testPage->ExtraField = '2005';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
428
		$testPage->write();
429
430
		// In 2007 we updated it
431
		SS_Datetime::set_mock_now('2007-01-01 00:00:00');
432
		$testPage->Content = "It's 2007 already!";
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_Subclass>. 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...
433
		$testPage->ExtraField = '2007';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
434
		$testPage->write();
435
436
		// In 2009 we updated it again
437
		SS_Datetime::set_mock_now('2009-01-01 00:00:00');
438
		$testPage->Content = "I'm enjoying 2009";
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_Subclass>. 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...
439
		$testPage->ExtraField = '2009';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
440
		$testPage->write();
441
442
		// End mock, back to the present day:)
443
		SS_Datetime::clear_mock_now();
444
445
		// Test 1 - 2006 Content
446
		singleton('VersionedTest_Subclass')->flushCache(true);
447
		Versioned::set_reading_mode('Archive.2006-01-01 00:00:00');
448
		$testPage2006 = DataObject::get('VersionedTest_Subclass')->filter(array('Title' => 'Archived page'))->first();
449
		$this->assertInstanceOf("VersionedTest_Subclass", $testPage2006);
450
		$this->assertEquals("2005", $testPage2006->ExtraField);
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
451
		$this->assertEquals("This is the content from 2005", $testPage2006->Content);
452
453
		// Test 2 - 2008 Content
454
		singleton('VersionedTest_Subclass')->flushCache(true);
455
		Versioned::set_reading_mode('Archive.2008-01-01 00:00:00');
456
		$testPage2008 = DataObject::get('VersionedTest_Subclass')->filter(array('Title' => 'Archived page'))->first();
457
		$this->assertInstanceOf("VersionedTest_Subclass", $testPage2008);
458
		$this->assertEquals("2007", $testPage2008->ExtraField);
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
459
		$this->assertEquals("It's 2007 already!", $testPage2008->Content);
460
461
		// Test 3 - Today
462
		singleton('VersionedTest_Subclass')->flushCache(true);
463
		Versioned::set_reading_mode('Stage.Stage');
464
		$testPageCurrent = DataObject::get('VersionedTest_Subclass')->filter(array('Title' => 'Archived page'))
465
			->first();
466
		$this->assertInstanceOf("VersionedTest_Subclass", $testPageCurrent);
467
		$this->assertEquals("2009", $testPageCurrent->ExtraField);
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
468
		$this->assertEquals("I'm enjoying 2009", $testPageCurrent->Content);
469
	}
470
471
	public function testAllVersions()
472
	{
473
		// In 2005 this file was created
474
		SS_Datetime::set_mock_now('2005-01-01 00:00:00');
475
		$testPage = new VersionedTest_Subclass();
476
		$testPage->Title = 'Archived page';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_Subclass>. 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...
477
		$testPage->Content = 'This is the content from 2005';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_Subclass>. 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...
478
		$testPage->ExtraField = '2005';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
479
		$testPage->write();
480
481
		// In 2007 we updated it
482
		SS_Datetime::set_mock_now('2007-01-01 00:00:00');
483
		$testPage->Content = "It's 2007 already!";
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_Subclass>. 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...
484
		$testPage->ExtraField = '2007';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
485
		$testPage->write();
486
487
		// Check both versions are returned
488
		$versions = Versioned::get_all_versions('VersionedTest_Subclass', $testPage->ID);
489
		$content = array();
490
		$extraFields = array();
491
		foreach($versions as $version)
492
		{
493
			$content[] = $version->Content;
494
			$extraFields[] = $version->ExtraField;
495
		}
496
497
		$this->assertEquals($versions->Count(), 2, 'All versions returned');
498
		$this->assertEquals($content, array('This is the content from 2005', "It's 2007 already!"),
499
			'Version fields returned');
500
		$this->assertEquals($extraFields, array('2005', '2007'), 'Version fields returned');
501
502
		// In 2009 we updated it again
503
		SS_Datetime::set_mock_now('2009-01-01 00:00:00');
504
		$testPage->Content = "I'm enjoying 2009";
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_Subclass>. 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...
505
		$testPage->ExtraField = '2009';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
506
		$testPage->write();
507
508
		// End mock, back to the present day:)
509
		SS_Datetime::clear_mock_now();
510
511
		$versions = Versioned::get_all_versions('VersionedTest_Subclass', $testPage->ID);
512
		$content = array();
513
		$extraFields = array();
514
		foreach($versions as $version)
515
		{
516
			$content[] = $version->Content;
517
			$extraFields[] = $version->ExtraField;
518
		}
519
520
		$this->assertEquals($versions->Count(), 3, 'Additional all versions returned');
521
		$this->assertEquals($content,
522
			array('This is the content from 2005', "It's 2007 already!", "I'm enjoying 2009"),
523
			'Additional version fields returned');
524
		$this->assertEquals($extraFields, array('2005', '2007', '2009'), 'Additional version fields returned');
525
	}
526
527
	public function testArchiveRelatedDataWithoutVersioned() {
528
		SS_Datetime::set_mock_now('2009-01-01 00:00:00');
529
530
		$relatedData = new VersionedTest_RelatedWithoutVersion();
531
		$relatedData->Name = 'Related Data';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<VersionedTest_RelatedWithoutVersion>. 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...
532
		$relatedDataId = $relatedData->write();
533
534
		$testData = new VersionedTest_DataObject();
535
		$testData->Title = 'Test';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_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...
536
		$testData->Content = 'Before Content';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_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...
537
		$testData->Related()->add($relatedData);
538
		$id = $testData->write();
539
540
		SS_Datetime::set_mock_now('2010-01-01 00:00:00');
541
		$testData->Content = 'After Content';
0 ignored issues
show
Documentation introduced by
The property Content does not exist on object<VersionedTest_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...
542
		$testData->write();
543
544
		Versioned::reading_archived_date('2009-01-01 19:00:00');
545
546
		$fetchedData = VersionedTest_DataObject::get()->byId($id);
547
		$this->assertEquals('Before Content', $fetchedData->Content, 'We see the correct content of the older version');
548
549
		$relatedData = VersionedTest_RelatedWithoutVersion::get()->byId($relatedDataId);
550
		$this->assertEquals(
551
			1,
552
			$relatedData->Related()->count(),
553
			'We have a relation, with no version table, querying it still works'
554
		);
555
	}
556
557
	public function testVersionedWithSingleStage() {
558
		$tables = DB::table_list();
559
		$this->assertContains(
560
			'versionedtest_singlestage',
561
			array_keys($tables),
562
			'Contains base table'
563
		);
564
		$this->assertContains(
565
			'versionedtest_singlestage_versions',
566
			array_keys($tables),
567
			'Contains versions table'
568
		);
569
		$this->assertNotContains(
570
			'versionedtest_singlestage_live',
571
			array_keys($tables),
572
			'Does not contain separate table with _Live suffix'
573
		);
574
		$this->assertNotContains(
575
			'versionedtest_singlestage_stage',
576
			array_keys($tables),
577
			'Does not contain separate table with _Stage suffix'
578
		);
579
580
		Versioned::reading_stage("Stage");
581
		$obj = new VersionedTest_SingleStage(array('Name' => 'MyObj'));
582
		$obj->write();
583
		$this->assertNotNull(
584
			VersionedTest_SingleStage::get()->byID($obj->ID),
585
			'Writes to and reads from default stage if its set explicitly'
586
		);
587
588
		Versioned::reading_stage("Live");
589
		$obj = new VersionedTest_SingleStage(array('Name' => 'MyObj'));
590
		$obj->write();
591
		$this->assertNotNull(
592
			VersionedTest_SingleStage::get()->byID($obj->ID),
593
			'Writes to and reads from default stage even if a non-matching stage is set'
594
		);
595
	}
596
597
	/**
598
	 * Test that publishing processes respects lazy loaded fields
599
	 */
600
	public function testLazyLoadFields() {
601
		$originalMode = Versioned::get_reading_mode();
602
603
		// Generate staging record and retrieve it from stage in live mode
604
		Versioned::reading_stage('Stage');
605
		$obj = new VersionedTest_Subclass();
606
		$obj->Name = 'bob';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<VersionedTest_Subclass>. 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...
607
		$obj->ExtraField = 'Field Value';
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
608
		$obj->write();
609
		$objID = $obj->ID;
610
		$filter = sprintf('"VersionedTest_DataObject"."ID" = \'%d\'', Convert::raw2sql($objID));
611
		Versioned::reading_stage('Live');
612
613
		// Check fields are unloaded prior to access
614
		$objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false);
615
		$lazyFields = $objLazy->getQueriedDatabaseFields();
616
		$this->assertTrue(isset($lazyFields['ExtraField_Lazy']));
617
		$this->assertEquals('VersionedTest_Subclass', $lazyFields['ExtraField_Lazy']);
618
619
		// Check lazy loading works when viewing a Stage object in Live mode
620
		$this->assertEquals('Field Value', $objLazy->ExtraField);
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
621
622
		// Test that writeToStage respects lazy loaded fields
623
		$objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false);
624
		$objLazy->writeToStage('Live');
625
		$objLive = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Live', $filter, false);
626
		$liveLazyFields = $objLive->getQueriedDatabaseFields();
627
628
		// Check fields are unloaded prior to access
629
		$this->assertTrue(isset($liveLazyFields['ExtraField_Lazy']));
630
		$this->assertEquals('VersionedTest_Subclass', $liveLazyFields['ExtraField_Lazy']);
631
632
		// Check that live record has original value
633
		$this->assertEquals('Field Value', $objLive->ExtraField);
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
634
635
		Versioned::set_reading_mode($originalMode);
636
	}
637
638
	/**
639
	 * Tests that reading mode persists between requests
640
	 */
641
	public function testReadingPersistent() {
642
		$session = Injector::inst()->create('Session', array());
643
		$adminID = $this->logInWithPermission('ADMIN');
644
		$session->inst_set('loggedInAs', $adminID);
645
646
		// Set to stage
647
		Director::test('/?stage=Stage', null, $session);
648
		$this->assertEquals(
649
				'Stage.Stage',
650
				$session->inst_get('readingMode'),
651
				'Check querystring changes reading mode to Stage'
652
		);
653
		Director::test('/', null, $session);
654
		$this->assertEquals(
655
				'Stage.Stage',
656
				$session->inst_get('readingMode'),
657
				'Check that subsequent requests in the same session remain in Stage mode'
658
		);
659
660
		// Test live persists
661
		Director::test('/?stage=Live', null, $session);
662
		$this->assertEquals(
663
				'Stage.Live',
664
				$session->inst_get('readingMode'),
665
				'Check querystring changes reading mode to Live'
666
		);
667
		Director::test('/', null, $session);
668
		$this->assertEquals(
669
				'Stage.Live',
670
				$session->inst_get('readingMode'),
671
				'Check that subsequent requests in the same session remain in Live mode'
672
		);
673
674
		// Test that session doesn't redundantly store the default stage if it doesn't need to
675
		$session2 = Injector::inst()->create('Session', array());
676
		$session2->inst_set('loggedInAs', $adminID);
677
		Director::test('/', null, $session2);
678
		$this->assertArrayNotHasKey('readingMode', $session2->inst_changedData());
679
		Director::test('/?stage=Live', null, $session2);
680
		$this->assertArrayNotHasKey('readingMode', $session2->inst_changedData());
681
682
		// Test choose_site_stage
683
		unset($_GET['stage']);
684
		unset($_GET['archiveDate']);
685
		Session::set('readingMode', 'Stage.Stage');
686
		Versioned::choose_site_stage();
687
		$this->assertEquals('Stage.Stage', Versioned::get_reading_mode());
688
		Session::set('readingMode', 'Archive.2014-01-01');
689
		Versioned::choose_site_stage();
690
		$this->assertEquals('Archive.2014-01-01', Versioned::get_reading_mode());
691
		Session::clear('readingMode');
692
		Versioned::choose_site_stage();
693
		$this->assertEquals('Stage.Live', Versioned::get_reading_mode());
694
	}
695
696
	/**
697
	 * Test that stage parameter is blocked by non-administrative users
698
	 */
699
	public function testReadingModeSecurity() {
700
		$this->setExpectedException('SS_HTTPResponse_Exception');
701
		$session = Injector::inst()->create('Session', array());
702
		$result = Director::test('/?stage=Stage', null, $session);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
703
	}
704
705
	/**
706
	 * Ensures that the latest version of a record is the expected value
707
	 *
708
	 * @param DataObject $record
709
	 * @param int $version
710
	 */
711
	protected function assertRecordHasLatestVersion($record, $version) {
712
		foreach(ClassInfo::ancestry(get_class($record), true) as $table) {
713
			$versionForClass = DB::prepared_query(
714
				$sql = "SELECT MAX(\"Version\") FROM \"{$table}_versions\" WHERE \"RecordID\" = ?",
715
				array($record->ID)
716
			)->value();
717
			$this->assertEquals($version, $versionForClass, "That the table $table has the latest version $version");
718
		}
719
	}
720
721
	/**
722
	 * Tests that multi-table dataobjects are correctly versioned
723
	 */
724
	public function testWriteToStage() {
725
		// Test subclass with versioned extension directly added
726
		$record = VersionedTest_Subclass::create();
727
		$record->Title = "Test A";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_Subclass>. 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...
728
		$record->ExtraField = "Test A";
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
729
		$record->writeToStage("Stage");
730
		$this->assertRecordHasLatestVersion($record, 1);
731
		$record->publish("Stage", "Live");
732
		$this->assertRecordHasLatestVersion($record, 1);
733
		$record->Title = "Test A2";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_Subclass>. 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...
734
		$record->ExtraField = "Test A2";
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
735
		$record->writeToStage("Stage");
736
		$this->assertRecordHasLatestVersion($record, 2);
737
738
		// Test subclass without changes to base class
739
		$record = VersionedTest_Subclass::create();
740
		$record->ExtraField = "Test B";
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
741
		$record->writeToStage("Stage");
742
		$this->assertRecordHasLatestVersion($record, 1);
743
		$record->publish("Stage", "Live");
744
		$this->assertRecordHasLatestVersion($record, 1);
745
		$record->ExtraField = "Test B2";
0 ignored issues
show
Bug introduced by
The property ExtraField does not seem to exist. Did you mean many_many_extraFields?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
746
		$record->writeToStage("Stage");
747
		$this->assertRecordHasLatestVersion($record, 2);
748
749
		// Test subclass without changes to sub class
750
		$record = VersionedTest_Subclass::create();
751
		$record->Title = "Test C";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_Subclass>. 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...
752
		$record->writeToStage("Stage");
753
		$this->assertRecordHasLatestVersion($record, 1);
754
		$record->publish("Stage", "Live");
755
		$this->assertRecordHasLatestVersion($record, 1);
756
		$record->Title = "Test C2";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_Subclass>. 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...
757
		$record->writeToStage("Stage");
758
		$this->assertRecordHasLatestVersion($record, 2);
759
760
		// Test subclass with versioned extension only added to the base clases
761
		$record = VersionedTest_AnotherSubclass::create();
762
		$record->Title = "Test A";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_AnotherSubclass>. 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...
763
		$record->AnotherField = "Test A";
0 ignored issues
show
Documentation introduced by
The property AnotherField does not exist on object<VersionedTest_AnotherSubclass>. 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...
764
		$record->writeToStage("Stage");
765
		$this->assertRecordHasLatestVersion($record, 1);
766
		$record->publish("Stage", "Live");
767
		$this->assertRecordHasLatestVersion($record, 1);
768
		$record->Title = "Test A2";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_AnotherSubclass>. 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...
769
		$record->AnotherField = "Test A2";
0 ignored issues
show
Documentation introduced by
The property AnotherField does not exist on object<VersionedTest_AnotherSubclass>. 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...
770
		$record->writeToStage("Stage");
771
		$this->assertRecordHasLatestVersion($record, 2);
772
773
774
		// Test subclass without changes to base class
775
		$record = VersionedTest_AnotherSubclass::create();
776
		$record->AnotherField = "Test B";
0 ignored issues
show
Documentation introduced by
The property AnotherField does not exist on object<VersionedTest_AnotherSubclass>. 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...
777
		$record->writeToStage("Stage");
778
		$this->assertRecordHasLatestVersion($record, 1);
779
		$record->publish("Stage", "Live");
780
		$this->assertRecordHasLatestVersion($record, 1);
781
		$record->AnotherField = "Test B2";
0 ignored issues
show
Documentation introduced by
The property AnotherField does not exist on object<VersionedTest_AnotherSubclass>. 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...
782
		$record->writeToStage("Stage");
783
		$this->assertRecordHasLatestVersion($record, 2);
784
785
		// Test subclass without changes to sub class
786
		$record = VersionedTest_AnotherSubclass::create();
787
		$record->Title = "Test C";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_AnotherSubclass>. 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...
788
		$record->writeToStage("Stage");
789
		$this->assertRecordHasLatestVersion($record, 1);
790
		$record->publish("Stage", "Live");
791
		$this->assertRecordHasLatestVersion($record, 1);
792
		$record->Title = "Test C2";
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedTest_AnotherSubclass>. 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...
793
		$record->writeToStage("Stage");
794
		$this->assertRecordHasLatestVersion($record, 2);
795
	}
796
797
	public function testVersionedHandlesRenamedDataObjectFields(){
798
		Config::inst()->remove('VersionedTest_RelatedWithoutVersion','db','Name','Varchar');
799
800
		Config::inst()->update('VersionedTest_RelatedWithoutVersion','db',array(
801
			"NewField" => "Varchar",
802
		));
803
804
		VersionedTest_RelatedWithoutVersion::add_extension("Versioned('Stage', 'Live')");
805
		$this->resetDBSchema(true);
806
		$testData = new VersionedTest_RelatedWithoutVersion();
807
		$testData->NewField = 'Test';
0 ignored issues
show
Documentation introduced by
The property NewField does not exist on object<VersionedTest_RelatedWithoutVersion>. 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...
808
		$testData->write();
809
	}
810
811
	public function testCanView() {
812
		$public1ID = $this->idFromFixture('VersionedTest_PublicStage', 'public1');
813
		$public2ID = $this->idFromFixture('VersionedTest_PublicViaExtension', 'public2');
814
		$privateID = $this->idFromFixture('VersionedTest_DataObject', 'page1');
815
816
		// Test that all (and only) public pages are viewable in stage mode
817
		Session::clear("loggedInAs");
818
		Versioned::reading_stage('Stage');
819
		$public1 = Versioned::get_one_by_stage('VersionedTest_PublicStage', 'Stage', array('"ID"' => $public1ID));
0 ignored issues
show
Documentation introduced by
array('"ID"' => $public1ID) is of type array<string,integer,{"\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
820
		$public2 = Versioned::get_one_by_stage('VersionedTest_PublicViaExtension', 'Stage', array('"ID"' => $public2ID));
0 ignored issues
show
Documentation introduced by
array('"ID"' => $public2ID) is of type array<string,integer,{"\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
821
		$private = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', array('"ID"' => $privateID));
0 ignored issues
show
Documentation introduced by
array('"ID"' => $privateID) is of type array<string,integer,{"\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
822
823
		$this->assertTrue($public1->canView());
824
		$this->assertTrue($public2->canView());
825
		$this->assertFalse($private->canView());
826
827
		// Adjusting the current stage should not allow objects loaded in stage to be viewable
828
		Versioned::reading_stage('Live');
829
		$this->assertTrue($public1->canView());
830
		$this->assertTrue($public2->canView());
831
		$this->assertFalse($private->canView());
832
833
		// Writing the private page to live should be fine though
834
		$private->publish("Stage", "Live");
835
		$privateLive = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Live', array('"ID"' => $privateID));
0 ignored issues
show
Documentation introduced by
array('"ID"' => $privateID) is of type array<string,integer,{"\"ID\"":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
836
		$this->assertTrue($private->canView());
837
		$this->assertTrue($privateLive->canView());
838
839
		// But if the private version becomes different to the live version, it's once again disallowed
840
		Versioned::reading_stage('Stage');
841
		$private->Title = 'Secret Title';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<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...
842
		$private->write();
843
		$this->assertFalse($private->canView());
844
		$this->assertTrue($privateLive->canView());
845
846
		// And likewise, viewing a live page (when mode is draft) should be ok
847
		Versioned::reading_stage('Stage');
848
		$this->assertFalse($private->canView());
849
		$this->assertTrue($privateLive->canView());
850
851
		// Logging in as admin should allow all permissions
852
		$this->logInWithPermission('ADMIN');
853
		Versioned::reading_stage('Stage');
854
		$this->assertTrue($public1->canView());
855
		$this->assertTrue($public2->canView());
856
		$this->assertTrue($private->canView());
857
	}
858
859
860
	public function testCanViewStage() {
861
		$public = $this->objFromFixture('VersionedTest_PublicStage', 'public1');
862
		$private = $this->objFromFixture('VersionedTest_DataObject', 'page1');
863
		Session::clear("loggedInAs");
864
		Versioned::reading_stage('Stage');
865
866
		// Test that all (and only) public pages are viewable in stage mode
867
		// Unpublished records are not viewable in live regardless of permissions
868
		$this->assertTrue($public->canViewStage('Stage'));
869
		$this->assertFalse($private->canViewStage('Stage'));
870
		$this->assertFalse($public->canViewStage('Live'));
871
		$this->assertFalse($private->canViewStage('Live'));
872
873
		// Writing records to live should make both stage and live modes viewable
874
		$private->publish("Stage", "Live");
875
		$public->publish("Stage", "Live");
876
		$this->assertTrue($public->canViewStage('Stage'));
877
		$this->assertTrue($private->canViewStage('Stage'));
878
		$this->assertTrue($public->canViewStage('Live'));
879
		$this->assertTrue($private->canViewStage('Live'));
880
881
		// If the draft mode changes, the live mode remains public, although the updated
882
		// draft mode is secured for non-public records.
883
		$private->Title = 'Secret Title';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<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...
884
		$private->write();
885
		$public->Title = 'Public Title';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<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...
886
		$public->write();
887
		$this->assertTrue($public->canViewStage('Stage'));
888
		$this->assertFalse($private->canViewStage('Stage'));
889
		$this->assertTrue($public->canViewStage('Live'));
890
		$this->assertTrue($private->canViewStage('Live'));
891
	}
892
}
893
894
895
/**
896
 * @package framework
897
 * @subpackage tests
898
 */
899
class VersionedTest_DataObject extends DataObject implements TestOnly {
900
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
901
		"Name" => "Varchar",
902
		'Title' => 'Varchar',
903
		'Content' => 'HTMLText'
904
	);
905
906
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
907
		"Versioned('Stage', 'Live')"
908
	);
909
910
	private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
911
		'Parent' => 'VersionedTest_DataObject'
912
	);
913
914
	private static $many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
915
		'Related' => 'VersionedTest_RelatedWithoutVersion'
916
	);
917
918
919 View Code Duplication
	public function canView($member = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
920
		$extended = $this->extendedCan(__FUNCTION__, $member);
921
		if($extended !== null) {
922
			return $extended;
923
		}
924
		return true;
925
	}
926
}
927
928
class VersionedTest_WithIndexes extends DataObject implements TestOnly {
929
930
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
931
		'UniqA' => 'Int',
932
		'UniqS' => 'Int',
933
	);
934
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
935
		"Versioned('Stage', 'Live')"
936
	);
937
	private static $indexes = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
938
		'UniqS_idx' => 'unique ("UniqS")',
939
		'UniqA_idx' => array('type' => 'unique', 'name' => 'UniqA_idx', 'value' => '"UniqA"',),
940
	);
941
942
}
943
944
/**
945
 * @package framework
946
 * @subpackage tests
947
 */
948
class VersionedTest_RelatedWithoutVersion extends DataObject implements TestOnly {
949
950
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
951
		'Name' => 'Varchar'
952
	);
953
954
	private static $belongs_many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
955
		'Related' => 'VersionedTest_DataObject'
956
	);
957
958
}
959
960
/**
961
 * @package framework
962
 * @subpackage tests
963
 */
964
class VersionedTest_Subclass extends VersionedTest_DataObject implements TestOnly {
965
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
966
		"ExtraField" => "Varchar",
967
	);
968
}
969
970
/**
971
 * @package framework
972
 * @subpackage tests
973
 */
974
class VersionedTest_AnotherSubclass extends VersionedTest_DataObject implements TestOnly {
975
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
976
		"AnotherField" => "Varchar"
977
	);
978
}
979
980
/**
981
 * @package framework
982
 * @subpackage tests
983
 */
984
class VersionedTest_UnversionedWithField extends DataObject implements TestOnly {
985
	private static $db = array('Version' => 'Varchar(255)');
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
986
}
987
988
class VersionedTest_SingleStage extends DataObject implements TestOnly {
989
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
990
		'Name' => 'Varchar'
991
	);
992
993
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
994
		'Versioned("Stage")'
995
	);
996
}
997
998
/**
999
 * Versioned dataobject with public stage mode
1000
 */
1001
class VersionedTest_PublicStage extends DataObject implements TestOnly {
1002
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
1003
		'Title' => 'Varchar'
1004
	);
1005
1006
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
1007
		"Versioned('Stage', 'Live')"
1008
	);
1009
1010 View Code Duplication
	public function canView($member = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1011
		$extended = $this->extendedCan(__FUNCTION__, $member);
1012
		if($extended !== null) {
1013
			return $extended;
1014
		}
1015
		return true;
1016
	}
1017
1018
	public function canViewVersioned($member = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $member 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...
1019
		// All non-live modes are public
1020
		return true;
1021
	}
1022
}
1023
1024
/**
1025
 * Public access is provided via extension rather than overriding canViewVersioned
1026
 */
1027
class VersionedTest_PublicViaExtension extends DataObject implements TestOnly {
1028
1029 View Code Duplication
	public function canView($member = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1030
		$extended = $this->extendedCan(__FUNCTION__, $member);
1031
		if($extended !== null) {
1032
			return $extended;
1033
		}
1034
		return true;
1035
	}
1036
1037
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
1038
		'Title' => 'Varchar'
1039
	);
1040
1041
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
1042
		"Versioned('Stage', 'Live')",
1043
		"VersionedTest_PublicExtension"
1044
	);
1045
}
1046
1047
/**
1048
 * Alters stage mode of extended object to be public
1049
 */
1050
class VersionedTest_PublicExtension extends DataExtension implements TestOnly {
1051
	public function canViewNonLive($member = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $member 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...
1052
		return true;
1053
	}
1054
}
1055