Completed
Push — master ( 41d4aa...7dfe14 )
by Damian
02:52
created

VirtualPage::CopyContentFrom()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 22
rs 8.6737
cc 6
eloc 11
nc 11
nop 0
1
<?php
2
/**
3
* Virtual Page creates an instance of a  page, with the same fields that the original page had, but readonly.
4
* This allows you can have a page in mulitple places in the site structure, with different children without duplicating the content
5
* Note: This Only duplicates $db fields and not the $has_one etc.. 
6
* @package cms
7
*/
8
class VirtualPage extends Page {
9
10
	private static $description = 'Displays the content of another page';
11
	
12
	public static $virtualFields;
13
	
14
	/**
15
	 * @var Array Define fields that are not virtual - the virtual page must define these fields themselves.
16
	 * Note that anything in {@link self::config()->initially_copied_fields} is implicitly included in this list.
17
	 */
18
	private static $non_virtual_fields = array(
19
		"ID",
20
		"ClassName",
21
		"SecurityTypeID",
22
		"OwnerID",
23
		"URLSegment",
24
		"Sort",
25
		"Status",
26
		'ShowInMenus',
27
		// 'Locale'
28
		'ShowInSearch',
29
		'Version',
30
		"Embargo",
31
		"Expiry",
32
	);
33
	
34
	/**
35
	 * @var Array Define fields that are initially copied to virtual pages but left modifiable after that.
36
	 */
37
	private static $initially_copied_fields = array(
38
		'ShowInMenus',
39
		'ShowInSearch',
40
		'URLSegment',
41
	);
42
	
43
	private static $has_one = array(
44
		"CopyContentFrom" => "SiteTree",	
45
	);
46
	
47
	private static $db = array(
48
		"VersionID" => "Int",
49
	);
50
	
51
	/** 
52
	 * Generates the array of fields required for the page type.
53
	 */
54
	public function getVirtualFields() {
55
		$nonVirtualFields = array_merge(self::config()->non_virtual_fields, self::config()->initially_copied_fields);
56
		$record = $this->CopyContentFrom();
57
58
		$virtualFields = array();
59
		foreach($record->db() as $field => $type) {
0 ignored issues
show
Bug introduced by
The expression $record->db() of type array|string|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
60
			if(!in_array($field, $nonVirtualFields)) {
61
				$virtualFields[] = $field;
62
		}
63
		}
64
		return $virtualFields;
65
	}
66
67
	/**
68
	 * Returns the linked page, or failing that, a new object.
69
	 *
70
	 * Always returns a non-empty object
71
	 * 
72
	 * @return SiteTree 
73
	 */
74
	public function CopyContentFrom() {
75
		$copyContentFromID = $this->CopyContentFromID;
76
		if(!$copyContentFromID) {
77
			return new SiteTree();
78
		}
79
		
80
		if(!isset($this->components['CopyContentFrom'])) {
81
			$this->components['CopyContentFrom'] = DataObject::get_by_id("SiteTree", $copyContentFromID);
82
83
			// Don't let VirtualPages point to other VirtualPages
84
			if($this->components['CopyContentFrom'] instanceof VirtualPage) {
85
				$this->components['CopyContentFrom'] = null;
86
			}
87
				
88
			// has_one component semantics incidate than an empty object should be returned
89
			if(!$this->components['CopyContentFrom']) {
90
				$this->components['CopyContentFrom'] = new SiteTree();
91
			}
92
		}
93
		
94
		return $this->components['CopyContentFrom'] ? $this->components['CopyContentFrom'] : new SiteTree();
95
	}
96
	
97
	public function setCopyContentFromID($val) {
98
		if($val && DataObject::get_by_id('SiteTree', $val) instanceof VirtualPage) {
99
			$val = 0;
100
		}
101
		return $this->setField("CopyContentFromID", $val);
102
	}
103
 
104
	public function ContentSource() {
105
		return $this->CopyContentFrom();
106
	}
107
108
	/**
109
	 * For VirtualPage, add a canonical link tag linking to the original page
110
	 * See TRAC #6828 & http://support.google.com/webmasters/bin/answer.py?hl=en&answer=139394
111
	 *
112
	 * @param boolean $includeTitle Show default <title>-tag, set to false for custom templating
113
	 * @return string The XHTML metatags
114
	 */
115
	public function MetaTags($includeTitle = true) {
116
		$tags = parent::MetaTags($includeTitle);
117
		if ($this->CopyContentFrom()->ID) {
118
			$tags .= "<link rel=\"canonical\" href=\"{$this->CopyContentFrom()->Link()}\" />\n";
119
		}
120
		return $tags;
121
	}
122
	
123
	public function allowedChildren() {
124
		if($this->CopyContentFrom()) {
125
			return $this->CopyContentFrom()->allowedChildren();
126
		}
127
	}
128
	
129
	public function syncLinkTracking() {
130
		if($this->CopyContentFromID) {
131
			$this->HasBrokenLink = !(bool) DataObject::get_by_id('SiteTree', $this->CopyContentFromID);
132
		} else {
133
			$this->HasBrokenLink = true;
134
		}
135
	}
136
	
137
	/**
138
	 * We can only publish the page if there is a published source page
139
	 */
140
	public function canPublish($member = null) {
141
		return $this->isPublishable() && parent::canPublish($member);
142
	}
143
	
144
	/**
145
	 * Return true if we can delete this page from the live site, which is different from can
146
	 * we publish it.
147
	 */
148
	public function canDeleteFromLive($member = null) {
149
		return parent::canPublish($member);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canPublish() instead of canDeleteFromLive()). Are you sure this is correct? If so, you might want to change this to $this->canPublish().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
150
	}
151
	
152
	/**
153
	 * Returns true if is page is publishable by anyone at all
154
	 * Return false if the source page isn't published yet.
155
	 * 
156
	 * Note that isPublishable doesn't affect ete from live, only publish.
157
	 */
158
	public function isPublishable() {
159
		// No source
160
		if(!$this->CopyContentFrom() || !$this->CopyContentFrom()->ID) {
161
			return false;
162
		}
163
		
164
		// Unpublished source
165
		if(!Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->CopyContentFromID)) {
166
			return false;
167
		}
168
		
169
		// Default - publishable
170
		return true;
171
	}
172
		
173
	/**
174
	 * Generate the CMS fields from the fields from the original page.
175
	 */
176
	public function getCMSFields() {
177
		$fields = parent::getCMSFields();
178
		
179
		// Setup the linking to the original page.
180
		$copyContentFromField = new TreeDropdownField(
181
			"CopyContentFromID", 
182
			_t('VirtualPage.CHOOSE', "Linked Page"), 
183
			"SiteTree"
184
		);
185
		// filter doesn't let you select children of virtual pages as as source page
186
		//$copyContentFromField->setFilterFunction(create_function('$item', 'return !($item instanceof VirtualPage);'));
187
		
188
		// Setup virtual fields
189
		if($virtualFields = $this->getVirtualFields()) {
190
			$roTransformation = new ReadonlyTransformation();
191
			foreach($virtualFields as $virtualField) {
192
				if($fields->dataFieldByName($virtualField))
193
					$fields->replaceField($virtualField, $fields->dataFieldByName($virtualField)->transform($roTransformation));
194
			}
195
		}
196
197
		$msgs = array();
198
		
199
		$fields->addFieldToTab("Root.Main", $copyContentFromField, "Title");
200
		
201
		// Create links back to the original object in the CMS
202
		if($this->CopyContentFrom()->exists()) {
203
			$link = "<a class=\"cmsEditlink\" href=\"admin/pages/edit/show/$this->CopyContentFromID\">" 
204
				. _t('VirtualPage.EditLink', 'edit')
205
				. "</a>";
206
			$msgs[] = _t(
207
				'VirtualPage.HEADERWITHLINK', 
208
				"This is a virtual page copying content from \"{title}\" ({link})",
209
				array(
0 ignored issues
show
Documentation introduced by
array('title' => $this->...tle'), 'link' => $link) is of type array<string,*,{"title":"*","link":"string"}>, 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...
210
					'title' => $this->CopyContentFrom()->obj('Title'),
211
					'link' => $link
212
				)
213
			);
214
		} else {
215
			$msgs[] = _t('VirtualPage.HEADER', "This is a virtual page");
216
			$msgs[] = _t(
217
				'SITETREE.VIRTUALPAGEWARNING',
218
				'Please choose a linked page and save first in order to publish this page'
219
			);
220
		}
221
		if(
222
			$this->CopyContentFromID 
223
			&& !Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->CopyContentFromID)
224
		) {
225
			$msgs[] = _t(
226
				'SITETREE.VIRTUALPAGEDRAFTWARNING',
227
				'Please publish the linked page in order to publish the virtual page'
228
			);
229
		}
230
231
		$fields->addFieldToTab("Root.Main", 
232
			new LiteralField(
233
				'VirtualPageMessage',
234
				'<div class="message notice">' . implode('. ', $msgs) . '.</div>'
235
			),
236
			'CopyContentFromID'
237
		);
238
	
239
		return $fields;
240
	}
241
242
	public function getSettingsFields() {
243
		$fields = parent::getSettingsFields();
244
		if(!$this->CopyContentFrom()->exists()) {
245
			$fields->addFieldToTab("Root.Settings", 
246
				new LiteralField(
247
					'VirtualPageWarning',
248
					'<div class="message notice">'
249
					 . _t(
250
							'SITETREE.VIRTUALPAGEWARNINGSETTINGS',
251
							'Please choose a linked page in the main content fields in order to publish'
252
						)
253
					. '</div>'
254
				),
255
				'ClassName'
256
			);
257
		}
258
259
		return $fields;
260
	}
261
	
262
	/** 
263
	 * We have to change it to copy all the content from the original page first.
264
	 */
265
	public function onBeforeWrite() {
266
		$performCopyFrom = null;
0 ignored issues
show
Unused Code introduced by
$performCopyFrom 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...
267
268
		// Determine if we need to copy values.
269
		if(
270
			$this->extension_instances['Versioned']->migratingVersion
271
			&& Versioned::current_stage() == 'Live'
272
			&& $this->CopyContentFromID
273
		) {
274
			// On publication to live, copy from published source.
275
			$performCopyFrom = true;
276
		
277
			$stageSourceVersion = DB::prepared_query(
278
				'SELECT "Version" FROM "SiteTree" WHERE "ID" = ?',
279
				array($this->CopyContentFromID)
280
			)->value();
281
			$liveSourceVersion = DB::prepared_query(
282
				'SELECT "Version" FROM "SiteTree_Live" WHERE "ID" = ?',
283
				array($this->CopyContentFromID)
284
			)->value();
285
		
286
			// We're going to create a new VP record in SiteTree_versions because the published
287
			// version might not exist, unless we're publishing the latest version
288
			if($stageSourceVersion != $liveSourceVersion) {
289
				$this->extension_instances['Versioned']->migratingVersion = null;
290
			}
291
		} else {
292
			// On regular write, copy from draft source. This is only executed when the source page changes.
293
			$performCopyFrom = $this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID != 0;
294
		}
295
		
296
 		if($performCopyFrom && $this instanceof VirtualPage) {
297
			// This flush is needed because the get_one cache doesn't respect site version :-(
298
			singleton('SiteTree')->flushCache();
299
			// @todo Update get_one to support parameterised queries
300
			$source = DataObject::get_by_id("SiteTree", $this->CopyContentFromID);
301
			// Leave the updating of image tracking until after write, in case its a new record
302
			$this->copyFrom($source, false);
303
		}
304
		
305
		parent::onBeforeWrite();
306
	}
307
	
308
	public function onAfterWrite() {
309
		parent::onAfterWrite();
310
311
		// Don't do this stuff when we're publishing
312
		if(!$this->extension_instances['Versioned']->migratingVersion) {
313
	 		if(
314
				$this->isChanged('CopyContentFromID')
315
	 			&& $this->CopyContentFromID != 0 
316
				&& $this instanceof VirtualPage
317
			) {
318
				$this->updateImageTracking();
319
			}
320
		}
321
322
		// Check if page type has changed to a non-virtual page.
323
		// Caution: Relies on the fact that the current instance is still of the old page type.
324
		if($this->isChanged('ClassName', 2)) {
325
			$changed = $this->getChangedFields();
326
			$classBefore = $changed['ClassName']['before'];
327
			$classAfter = $changed['ClassName']['after'];
328
			if($classBefore != $classAfter) {
329
				// Remove all database rows for the old page type to avoid inconsistent data retrieval.
330
				// TODO This should apply to all page type changes, not only on VirtualPage - but needs
331
				// more comprehensive testing as its a destructive operation
332
				$removedTables = array_diff(ClassInfo::dataClassesFor($classBefore), ClassInfo::dataClassesFor($classAfter));
333
				if($removedTables) foreach($removedTables as $removedTable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $removedTables of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
334
					// Note: *_versions records are left intact
335
					foreach(array('', 'Live') as $stage) {
336
						if($stage) $removedTable = "{$removedTable}_{$stage}";
337
						DB::prepared_query("DELETE FROM \"$removedTable\" WHERE \"ID\" = ?", array($this->ID));
338
					}
339
				}
340
341
				// Also publish the change immediately to avoid inconsistent behaviour between
342
				// a non-virtual draft and a virtual live record (e.g. republishing the original record
343
				// shouldn't republish the - now unrelated - changes on the ex-VirtualPage draft).
344
				// Copies all stage fields to live as well.
345
				// @todo Update get_one to support parameterised queries
346
				$source = DataObject::get_by_id("SiteTree", $this->CopyContentFromID);
347
				$this->copyFrom($source);
348
				$this->publish('Stage', 'Live');
349
350
				// Change reference on instance (as well as removing the underlying database tables)
351
				$this->CopyContentFromID = 0;
352
			}
353
		}
354
	}
355
356
	public function validate() {
357
		$result = parent::validate();
358
359
		// "Can be root" validation
360
		$orig = $this->CopyContentFrom();
361 View Code Duplication
		if(!$orig->stat('can_be_root') && !$this->ParentID) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
362
			$result->error(
363
				_t(
364
					'VirtualPage.PageTypNotAllowedOnRoot', 
365
					'Original page type "{type}" is not allowed on the root level for this virtual page', 
366
					array('type' => $orig->i18n_singular_name())
0 ignored issues
show
Documentation introduced by
array('type' => $orig->i18n_singular_name()) is of type array<string,string,{"type":"string"}>, 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...
367
				),
368
				'CAN_BE_ROOT_VIRTUAL'
369
			);
370
		}
371
372
		return $result;
373
	}
374
	
375
	/**
376
	 * Ensure we have an up-to-date version of everything.
377
	 */
378
	public function copyFrom($source, $updateImageTracking = true) {
379
		if($source) {
380
			foreach($this->getVirtualFields() as $virtualField) {
381
				$this->$virtualField = $source->$virtualField;
382
			}
383
			
384
			// We also want to copy certain, but only if we're copying the source page for the first
385
			// time. After this point, the user is free to customise these for the virtual page themselves.
386
			if($this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID != 0) {
387
				foreach(self::config()->initially_copied_fields as $fieldName) {
388
					$this->$fieldName = $source->$fieldName;
389
				}
390
			}
391
			
392
			if($updateImageTracking) $this->updateImageTracking();
393
		}
394
	}
395
	
396
	public function updateImageTracking() {
397
		// Doesn't work on unsaved records
398
		if(!$this->ID) return;
399
400
		// Remove CopyContentFrom() from the cache
401
		unset($this->components['CopyContentFrom']);
402
		
403
		// Update ImageTracking
404
		$this->ImageTracking()->setByIdList($this->CopyContentFrom()->ImageTracking()->column('ID'));
0 ignored issues
show
Documentation Bug introduced by
The method ImageTracking does not exist on object<SiteTree>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
405
	}
406
407
	/**
408
	 * @param string $numChildrenMethod
409
	 * @return string
410
	 */
411
	public function CMSTreeClasses($numChildrenMethod="numChildren") {
412
		return parent::CMSTreeClasses($numChildrenMethod) . ' VirtualPage-' . $this->CopyContentFrom()->ClassName;
413
	}
414
	
415
	/**
416
	 * Allow attributes on the master page to pass
417
	 * through to the virtual page
418
	 *
419
	 * @param string $field 
420
	 * @return mixed
421
	 */
422
	public function __get($field) {
423
		if(parent::hasMethod($funcName = "get$field")) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (hasMethod() instead of __get()). Are you sure this is correct? If so, you might want to change this to $this->hasMethod().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
424
			return $this->$funcName();
425
		} else if(parent::hasField($field) || ($field === 'ID' && !$this->exists())) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (hasField() instead of __get()). Are you sure this is correct? If so, you might want to change this to $this->hasField().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
426
			return $this->getField($field);
427
		} else {
428
			return $this->copyContentFrom()->$field;
429
		}
430
	}
431
	
432
	/**
433
	 * Pass unrecognized method calls on to the original data object
434
	 *
435
	 * @param string $method 
436
	 * @param string $args
437
	 * @return mixed
438
	 */
439
	public function __call($method, $args) {
440
		if(parent::hasMethod($method)) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (hasMethod() instead of __call()). Are you sure this is correct? If so, you might want to change this to $this->hasMethod().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
441
			return parent::__call($method, $args);
442
		} else {
443
			return call_user_func_array(array($this->copyContentFrom(), $method), $args);
444
		}
445
	}
446
447
	/**
448
	 * @param string $field
449
	 * @return bool
450
	 */
451
	public function hasField($field) {
452
		if(parent::hasField($field)) {
453
			return true;
454
	}	
455
		return $this->CopyContentFrom()->hasField($field);
456
	}	
457
	/**
458
	 * Overwrite to also check for method on the original data object
459
	 *
460
	 * @param string $method 
461
	 * @return bool 
462
	 */
463
	public function hasMethod($method) {
464
		if(parent::hasMethod($method)) {
465
			return true;
466
	}
467
		return $this->CopyContentFrom()->hasMethod($method);
468
	}
469
470
	/**
471
	 * Return the "casting helper" (a piece of PHP code that when evaluated creates a casted value object) for a field
472
	 * on this object.
473
	 *
474
	 * @param string $field
475
	 * @return string
476
	 */
477
	public function castingHelper($field) {
478
		return $this
479
			->CopyContentFrom()
480
			->castingHelper($field);
481
	}
482
483
}
484
485
/**
486
 * Controller for the virtual page.
487
 * @package cms
488
 */
489
class VirtualPage_Controller extends Page_Controller {
490
	
491
	private static $allowed_actions = array(
492
		'loadcontentall' => 'ADMIN',
493
	);
494
	
495
	/**
496
	 * Reloads the content if the version is different ;-)
497
	 */
498
	public function reloadContent() {
499
		$this->failover->copyFrom($this->failover->CopyContentFrom());
500
		$this->failover->write();
501
		return;
502
	}
503
	
504
	public function getViewer($action) {
505
		$originalClass = get_class($this->CopyContentFrom());
506
		if ($originalClass == 'SiteTree') $name = 'Page_Controller';
507
		else $name = $originalClass."_Controller";
508
		$controller = new $name();
509
		return $controller->getViewer($action);
510
	}
511
	
512
	/**
513
	 * When the virtualpage is loaded, check to see if the versions are the same
514
	 * if not, reload the content.
515
	 * NOTE: Virtual page must have a container object of subclass of sitetree.
516
	 * We can't load the content without an ID or record to copy it from.
517
	 */
518
	public function init(){
519
		if(isset($this->record) && $this->record->ID){
520
			if($this->record->VersionID != $this->failover->CopyContentFrom()->Version){
521
				$this->reloadContent();
522
				$this->VersionID = $this->failover->CopyContentFrom()->VersionID;
523
			}
524
		}
525
		parent::init();
526
		$this->__call('init', array());
527
	}
528
529
	public function loadcontentall() {
530
		$pages = DataObject::get("VirtualPage");
531
		foreach($pages as $page) {
532
			$page->copyFrom($page->CopyContentFrom());
533
			$page->write();
534
			$page->publish("Stage", "Live");
535
			echo "<li>Published $page->URLSegment";
536
		}
537
	}
538
	
539
	/**
540
	 * Also check the original object's original controller for the method
541
	 *
542
	 * @param string $method 
543
	 * @return bool 
544
	 */
545
	public function hasMethod($method) {
546
		$haveIt = parent::hasMethod($method);
547
		if (!$haveIt) {	
548
			$originalClass = get_class($this->CopyContentFrom());
549
			if ($originalClass == 'SiteTree') $name = 'ContentController';
550
			else $name = $originalClass."_Controller";
551
			$controller = new $name($this->dataRecord->copyContentFrom());
552
			$haveIt = $controller->hasMethod($method);
553
		}
554
		return $haveIt;
555
	}
556
	
557
	/**
558
	 * Pass unrecognized method calls on to the original controller
559
	 *
560
	 * @param string $method 
561
	 * @param string $args
562
	 * @return mixed
563
	 *
564
	 * @throws Exception Any error other than a 'no method' error.
565
	 */
566
	public function __call($method, $args) {
567
		try {
568
			return parent::__call($method, $args);
569
		} catch (Exception $e) {
570
			// Hack... detect exception type. We really should use exception subclasses.
571
			// if the exception isn't a 'no method' error, rethrow it
572
			if ($e->getCode() !== 2175) {
573
				throw $e;
574
			}
575
576
			$original = $this->copyContentFrom();
577
			$controller = ModelAsController::controller_for($original);
578
579
			// Ensure request/response data is available on virtual controller
580
			$controller->setRequest($this->getRequest());
581
			$controller->response = $this->response; // @todo - replace with getter/setter in 3.3
582
583
			return call_user_func_array(array($controller, $method), $args);
584
		}
585
	}
586
}
587
588
589