Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

Customising the CMS /

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo, swaiba

Virtual Page with Custom Fields


Go to End


6 Posts   5263 Views

Avatar
Lloydy

Community Member, 3 Posts

11 June 2009 at 2:48pm

We've been struggling with getting Virtual Pages working on our SilverStripe install. We can successfully use the built in SilverStripe Virtual Page just fine. The challenge is that we are using a number of custom fields to build a right column menu and a bunch of other parts of the page. When we create the Virtual Pages though it only copies over the Content and Title fields, not all of our custom fields.

So what I've done is I have copied the Sapphire code for Virtual Pages to make our own "Virtual Content Page" for handling this. I'm at the point where I have the "Virtual Content Page" working in exactly the same way as the default "Virtual Page" works. If I uncomment the code below it even shows the needed fields:

		/*
		$fields->addFieldToTab('Root.Content.Main', new TextField('BannerImage'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip1'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip2'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip3'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip4'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip5'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('BizToolkit'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('Licenses'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('Workshops'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('Books'), '');
		*/

But I'm stumped as to how I would make those fields populate from the original page and be linked to the original page (In the same way as the content field is).

I've done a heap of searching and the closest I've been able to find to my problem is this topic:
http://ssorg.bigbird.silverstripe.com/archive/show/33497

I'm sure this is within spitting distance of a solution and would be INCREDIBLY thankful to anyone that could help point me in the direction of a solution. I've included both the content of ContentPage.php (Where the extra fields are defined for normal pages) and the full contents of VirtualContentPage.php (A clone of the Sapphire version with a few of my own tweaks).

Contents of /mysite/code/ContentPage.php

<?php
/**
 * Defines the ContentPage page type
 */
class ContentPage extends Page {
   static $db = array(
   'BannerImage' => 'Text',
   'TopTip1' => 'HTMLText',
   'TopTip2' => 'HTMLText',
   'TopTip3' => 'HTMLText',
   'TopTip4' => 'HTMLText',
   'TopTip5' => 'HTMLText',
   'BizToolkit' => 'HTMLText',
   'Licenses' => 'HTMLText',
   'Workshops' => 'HTMLText',
   'Books' => 'HTMLText'
	);
   static $has_one = array(
   	);
   
   function getCMSFields() {
   $fields = parent::getCMSFields();
 
   $fields->addFieldToTab('Root.Content.Main', new TextField('BannerImage'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('TopTip1'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('TopTip2'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('TopTip3'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('TopTip4'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('TopTip5'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('BizToolkit'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('Licenses'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('Workshops'), '');
   $fields->addFieldToTab('Root.Content.Main', new TextField('Books'), '');
    	
   return $fields;
}
}
 
class ContentPage_Controller extends Page_Controller {
 
}
 
?>

Contents of /mysite/code/VirtualContentPage.php

<?php

/**
 * @package cms
 */

/**
* Virtual Page creates an instance of a  page, with the same fields that the original page had, but readonly.
* This allows you can have a page in mulitple places in the site structure, with different children without duplicating the content
* Note: This Only duplicates $db fields and not the $has_one etc.. 
* @package cms
*/
class VirtualContentPage extends Page {

	static $add_action = "Virtual content page (another page's content)";
	
	static $icon = array("cms/images/treeicons/page-shortcut-gold","file");
	
	public static $virtualFields;
	
	static $has_one = array(
		"CopyContentFrom" => "SiteTree",
		'BannerImage' => 'Text',
   		'TopTip1' => 'HTMLText',
   		'TopTip2' => 'HTMLText',
   		'TopTip3' => 'HTMLText',
   		'TopTip4' => 'HTMLText',
   		'TopTip5' => 'HTMLText',
   		'BizToolkit' => 'HTMLText',
   		'Licenses' => 'HTMLText',
   		'Workshops' => 'HTMLText',
	   	'Books' => 'HTMLText'
	);
	
	static $db = array(
		"VersionID" => "Int",
	);
	
	/** 
	 * Generates the array of fields required for the page type.
	 */
	function getVirtualFields() {
		$nonVirtualFields = array(
			"SecurityTypeID",
			"OwnerID",
			"AssignedToID",
			"RequestedByID",
			"URLSegment",
			"Sort",
			"Status",
			'ShowInMenus',
			'ShowInSearch'
		);

		$allFields = $this->db();
		if($hasOne = $this->has_one()) foreach($hasOne as $link) $allFields[$link . 'ID'] = "Int";
		foreach($allFields as $field => $type) {
			if(!in_array($field, $nonVirtualFields)) $virtualFields[] = $field;
		}
		
		return $virtualFields;
	}

	function ContentSource() {
		return $this->CopyContentFrom();
	}
	
		
	/**
	 * Generate the CMS fields from the fields from the original page.
	 */
	function getCMSFields($cms = null) {
		$fields = parent::getCMSFields($cms);
		
		// Setup the linking to the original page.
		$copyContentFromField = new TreeDropdownField(
			"CopyContentFromID", 
			_t('VirtualContentPage.CHOOSE', "Choose a page to link to"), 
			"SiteTree"
		);
		$copyContentFromField->setFilterFunction(create_function('$item', 'return $item->ClassName != "VirtualContentPage";'));
		
		// Setup virtual fields
		if($virtualFields = $this->getVirtualFields()) {
			$roTransformation = new ReadonlyTransformation();
			foreach($virtualFields as $virtualField) {
				if($fields->dataFieldByName($virtualField))
				{
					$fields->replaceField($virtualField, $fields->dataFieldByName($virtualField)->transform($roTransformation));
					//$fields->addFieldToTab('Root.Content.Main', $fields->dataFieldByName($virtualField), '');
				}
			}
		}
		
		// Add fields to the tab
		$fields->addFieldToTab("Root.Content.Main", 
			new HeaderField(_t('VirtualContentPage.HEADER', "This is a virtual content page")), 
			"Title"
		);
		$fields->addFieldToTab("Root.Content.Main", $copyContentFromField, "Title");
		
		/*
		$fields->addFieldToTab('Root.Content.Main', new TextField('BannerImage'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip1'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip2'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip3'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip4'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('TopTip5'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('BizToolkit'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('Licenses'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('Workshops'), '');
		$fields->addFieldToTab('Root.Content.Main', new TextField('Books'), '');
		*/
		
		// Create links back to the original object in the CMS
		if($this->CopyContentFromID) {
			$linkToContent = "<a class=\"cmsEditlink\" href=\"admin/show/$this->CopyContentFromID\">" . 
				_t('VirtualContentPage.EDITCONTENT', 'click here to edit the content') . "</a>";
			$fields->addFieldToTab("Root.Content.Main", new LabelField($linkToContent, null, true), "Title");
		}
	
		return $fields;
	}
	
	/** 
	 * We have to change it to copy all the content from the original page first.
	 */
	function onBeforeWrite() {
		// Don't do this stuff when we're publishing
		if(!$this->extension_instances['Versioned']->migratingVersion) {
	 		if(isset($this->changed['CopyContentFromID']) && $this->changed['CopyContentFromID'] 
	 					&& $this->CopyContentFromID != 0 && $this->class == 'VirtualContentPage' ) {
				$CopyContentFromID = $this->CopyContentFromID;
				$source = DataObject::get_one("SiteTree","`SiteTree`.`ID`='$CopyContentFromID'");
				$this->copyFrom($source);
				$this->URLSegment = $source->URLSegment . '-' . $this->ID;			
			}
		}
		
		parent::onBeforeWrite();
	}
	/**
	 * Ensure we have an up-to-date version of everything.
	 */
	function copyFrom($source) {
		if($source) {
			foreach($this->getVirtualFields() AS $virtualField)
				$this->$virtualField = $source->$virtualField;
		}
	}
}

/**
 * Controller for the virtual page.
 * @package cms
 */
class VirtualContentPage_Controller extends Page_Controller {

	/**
	 * Reloads the content if the version is different ;-)
	 */
	function reloadContent() {
		$this->failover->copyFrom($this->failover->CopyContentFrom());
		$this->failover->write();
		return;
	}
	
	/**
	 * When the VirtualContentPage is loaded, check to see if the versions are the same
	 * if not, reload the content.
	 * NOTE: Virtual page must have a container object of subclass of sitetree.
	 * We can't load the content without an ID or record to copy it from.
	 */
	function init(){
		if($this->record->ID){
			if($this->record->VersionID != $this->failover->CopyContentFrom()->Version){
				$this->reloadContent();
				$this->VersionID = $this->failover->CopyContentFrom()->VersionID;
			}
		}
		parent::init();
	}

	function loadcontentall() {
		$pages = DataObject::get("VirtualContentPage");
		foreach($pages as $page) {
			$page->copyFrom($page->CopyContentFrom());
			$page->write();
			$page->publish("Stage", "Live");
			echo "<li>Published $page->URLSegment";
		}
	}
}

?>

Thanks in advance!

Avatar
Lloydy

Community Member, 3 Posts

15 June 2009 at 1:25pm

Bump? Still looking for help with this one. If anybody could suggest anything that would be awesome...

Avatar
Lloydy

Community Member, 3 Posts

21 June 2009 at 12:59pm

Anybody? :(

Avatar
Artyom

Community Member, 22 Posts

3 April 2010 at 11:32pm

This is a known problem w virtual pages... and the SS guys don't seem to want to fix it.

The solutions I've found are essentially to return the *actual* childpage in a list, for example, which the other posts document.

Personally, I think this is a really serious design flaw in the framework as it is. It should be able to somehow hand non-tree structures that allow this sort of thing.

Avatar
Stuie

Community Member, 1 Post

21 June 2014 at 3:16am

Has SS done anything about this yet. Noticing the last post was 2010

Avatar
chasevida

Community Member, 7 Posts

9 April 2015 at 3:11pm

Edited: 09/04/2015 3:12pm

I'm guessing this is still an issue five years on. We're having the same troubles and hacking workarounds.