<?xml version="1.0"?>
<rss version="2.0">
	<channel>
		<title>&lt;RSS Title&gt;</title>
		<link>http://www.ssbits.com/tutorials/</link>
		

		
		<item>
			<title>2.4 - Using Short Codes to embed a YouTube video</title>
			<link>http://www.ssbits.com/2-4-using-short-codes-to-embed-a-youtube-video/</link>
			<description>&lt;h2&gt;&lt;span style=&quot;font-family: Verdana, Lucida, sans-serif;&quot;&gt;What are shortcodes?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;SilverStripe 2.4 has introduced shortcodes to the CMS editor. Simply, the CMS user can now add short BBCode style code to the editor area and it can then be replaced using a predefined function. For example &lt;strong&gt;[link id=23]&lt;/strong&gt; could be replaces by the link to Page with an ID of 23. In fact that is exactly how internal links created in the links sidebar work in 2.4, preventing them from breaking when URLs change.&lt;/p&gt;
&lt;h2&gt;The aim of this tutorial&lt;/h2&gt;
&lt;p&gt;The aim of this tutorial is to allow the CMS user to enter the short code:&lt;/p&gt;
&lt;pre&gt;[YouTube id=3UTu6lV8ppY]&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre style=&quot;color: #000000; font-family: 'Courier New', Courier; font-size: 1.2em; margin-top: 2em; margin-right: 5em; margin-bottom: 2em; margin-left: 5em; display: block; padding-top: 0.5em; padding-right: 0.5em; padding-bottom: 0.5em; padding-left: 0.5em; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #eeeeee; background-position: initial initial; background-repeat: initial initial; &quot;&gt;[YouTube id=3UTu6lV8ppY]A video about SilverStripe[/YouTube]&lt;/pre&gt;
&lt;p&gt;we will also allow them to add more custom attributes. We will then use this to embed a YouTube video automatically for the user.&lt;/p&gt;
&lt;h2&gt;Short code structure&lt;/h2&gt;
&lt;p&gt;Shortcodes are made up of three parts, the actual ShortCode (YouTube) its arguments (id) and its content (the text surrounded buy the tags - 'A video about SilverStripe').&lt;/p&gt;
&lt;h2&gt;The Short Code Parser&lt;/h2&gt;
&lt;p&gt;The first thing we need to do is to set-up our parser function that will handle the tag, its arguments and its content.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;Page.php&lt;/strong&gt; we need to add our handler:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class Page extends SiteTree {
...
    public static function YouTubeShortCodeHandler($arguments,$content = null,$parser = null) {
    }
...
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$arguments&lt;/strong&gt; will be an associative array and &lt;strong&gt;$content&lt;/strong&gt; will be a string.&lt;/p&gt;
&lt;p&gt;We now need to set-up some default values for the YouTube video we are going to embed, such as size, height and quality. We need to also merge the defaults with the passed arguments and then return the embed code using a template that we will create next.&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;public static function YouTubeShortCodeHandler($arguments,$caption = null,$parser = null) {
	// first things first, if we dont have a video ID, then we don't need to
	// go any further
	if (!$arguments['id']) {
		return;
	}
	
	$customise = array();
	/*** SET DEFAULTS ***/
	$customise['YouTubeID'] = $arguments['id'];
	//play the video on page load
	$customise['autoplay'] = false;
	//set the caption
	$customise['caption'] = $caption ? Convert::raw2xml($caption) : false;
	//set dimensions
	$customise['width'] = 640;
	$customise['height'] = 385;
	
	//overide the defaults with the arguments supplied
	$customise = array_merge($customise,$arguments);
	
	//get our YouTube template
	$template = new SSViewer('YouTube');
	
	//return the customised template
	return $template-&amp;gt;process(new ArrayData($customise));
	
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;It is worth noting that the keys of the &lt;strong&gt;$arguments&lt;/strong&gt; array will always be in lowercase.&lt;/p&gt;
&lt;h2&gt;The Template&lt;/h2&gt;
&lt;p&gt;In your themes folder you can now add a template called &lt;strong&gt;YouTube.ss&lt;/strong&gt;; I have it in &lt;strong&gt;themes/mytheme/Includes&lt;/strong&gt; as this means I can also use it hard coded into my other page templates if I need to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YouTube.ss:&lt;/strong&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;line-height: normal; white-space: pre-wrap;&quot;&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;YouTube&quot;&amp;gt;
	
	&amp;lt;object  type=&quot;application/x-shockwave-flash&quot; data=&quot;http://www.youtube-nocookie.com/v/$YouTubeID&amp;lt;% if autoplay %&amp;gt;&amp;amp;autoplay=1&amp;lt;% end_if %&amp;gt; width=&quot;$width&quot; height=&quot;$height&quot;&amp;gt;
		&amp;lt;embed  name=&quot;movie&quot; value=&quot;http://www.youtube-nocookie.com/v/$YouTubeID&amp;lt;% if autoplay %&amp;gt;&amp;amp;autoplay=1&amp;lt;% end_if %&amp;gt;&quot; /&amp;gt;
	&amp;lt;/object&amp;gt;
	
	&amp;lt;p&amp;gt;$VideoCaption&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;	&lt;/textarea&gt; &lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This is XHTML valid YouTube embedding code which uses our custom parameters.&lt;/p&gt;
&lt;h2&gt;Registering the Parser&lt;/h2&gt;
&lt;p&gt;Finally, we need to register the parser in our _config.php file:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;...
//shorcodes
ShortcodeParser::get()-&amp;gt;register('YouTube',array('Page','YouTubeShortCodeHandler'));&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;That is it, we have now created our Short Code handler and when a CMS user wants to embed a video from YouTube, they can with ease.&lt;/p&gt;
&lt;h2&gt;Example usage&lt;/h2&gt;
&lt;pre&gt;&lt;span style=&quot;font-family: Verdana, Lucida, sans-serif;&quot;&gt;[YouTube id=4af1bFh3duo width=300 height=150 related=0]blah[/YouTube]&lt;/span&gt;&lt;/pre&gt;</description>
			<pubDate>Wed, 28 Apr 2010 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/2-4-using-short-codes-to-embed-a-youtube-video/</guid>
		</item>
		
		<item>
			<title>2.4 - Working with SiteConfig</title>
			<link>http://www.ssbits.com/2-4-working-with-siteconfig/</link>
			<description>&lt;p&gt;One of the great new features of 2.4 is the introduction of a Site Config page. This allows you to put all of those fields which are not page related, such as The sites title, root access permissions and even the current theme. The SiteConfig class is simply a dataobject and so can easily be extended to include fields, relationships and functions which you can then access from anywhere in your site.&lt;/p&gt;
&lt;p&gt;Accessing items in your SiteConfig is as easy as preceding each item with &lt;strong&gt;$SiteConfig&lt;/strong&gt;. For example this would display the Title field:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;h1&amp;gt;$SiteConfig.Title&amp;lt;/h1&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Or alternatively:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;% control SiteConfig %&amp;gt;
     &amp;lt;h1&amp;gt;$Title&amp;lt;/h1&amp;gt;
&amp;lt;% end_control %&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: Verdana, Lucida, sans-serif;&quot;&gt;Accessing SiteConfig from the Controller&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;So accessing it from the template is super simple and so it turns out is accessing it from a Controller:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;$this-&amp;gt;SiteConfig();&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Or alternatively:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;ContentController::SiteConfig()&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: Verdana, Lucida, sans-serif;&quot;&gt;Customizing SiteConfig&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;So of course there's not much point in the &lt;strong&gt;SiteConfig &lt;/strong&gt;unless we can add or own fields, relationships and functions. To do so we use the &lt;strong&gt;DataObjectDecorator&lt;/strong&gt; class which allows us to 'decorate' other classes with fields, functions and relationships. We need to decorate &lt;strong&gt;SiteConfig &lt;/strong&gt;instead of extend it with our own class because the core &lt;strong&gt;SiteConfig &lt;/strong&gt;class is the one that SilverStripe will look for. Here is an example of a &lt;strong&gt;CustomSiteConfig &lt;/strong&gt;class which extends &lt;strong&gt;DataObjectDecorator:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CustomSiteConfig.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
 
class CustomSiteConfig extends DataObjectDecorator {
	
	function extraStatics() {
		return array(
			'db' =&amp;gt; array(
				'Spiel' =&amp;gt; 'Text'
			),
			'has_one' =&amp;gt; array(
				'SiteLogo' =&amp;gt; 'Image'
			)
		);
	}
 
	public function updateCMSFields(FieldSet &amp;amp;$fields) {
		
		$fields-&amp;gt;addFieldToTab(&quot;Root.Main&quot;, new TextareaField(&quot;Spiel&quot;));
		
		$fields-&amp;gt;addFieldToTab(&quot;Root.Main&quot;, new ImageField(&quot;SiteLogo&quot;, &quot;Logo&quot;));
	}
	
	public function getPooSpiel(){
		return '&amp;lt;h3&amp;gt;Poo!&amp;lt;/h3&amp;gt;' . $this-&amp;gt;owner-&amp;gt;Spiel;
	}
}&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So here we are adding an extra field to &lt;strong&gt;$db&lt;/strong&gt; and a &lt;strong&gt;$has_one &lt;/strong&gt;relationship. We then add the appropriate form fields to the CMS and we also define a new function. The function is not particularly useful in this case and simple adds the header 'poo' to the start of our &lt;strong&gt;Spiel &lt;/strong&gt;field. However, notice that we use &lt;strong&gt;$this-&amp;gt;owner&lt;/strong&gt; to refer to the &lt;strong&gt;Spiel &lt;/strong&gt;field. This is the syntax you must use when extending a DataObject using the &lt;strong&gt;DataObjectDecorator&lt;/strong&gt;&amp;nbsp;so that SilverStripe refers to the object that is being extended rather than the Extention class it self.&lt;/p&gt;
&lt;p&gt;Now inorder to get SilverStripe to recognize this extention we need to add this line to our&lt;strong&gt; _config.php:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;DataObject::add_extension('SiteConfig', 'CustomSiteConfig');&lt;/textarea&gt; &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now in our template we can use these new fields like so:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;	$SiteConfig.SiteLogo
	
	&amp;lt;% control SiteConfig.SiteLogo %&amp;gt;
		$CroppedImage(100,100)
	&amp;lt;% end_control %&amp;gt;
	&amp;lt;p&amp;gt;$SiteConfig.Spiel&amp;lt;/p&amp;gt;
	&amp;lt;p&amp;gt;$SiteConfig.PooSpiel&amp;lt;/p&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: Verdana, Lucida, sans-serif;&quot;&gt;Using DataObjectManager (and ComplextableField) in SiteConfig&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;So although this should be as simple as using the DataObjectManager on a page, because we are effectively relating 2 DataObjects instead of a page and a DataObject, for some reason SilverStripe is unable to set the ParentID on the DataObject and so it doesn't work. There is however a workaround which is to extend the DataObjectManager class and add a function to explicitly set the sourceID. So to do this all we need to do is create a new class after our CustomSiteConfig class. We could do this in a seperate file, but as it is only used for the SiteConfig it makes sense to keep it there. We also need to adjust our DataObjectManger definition from usual to set the sourceID and make sure it picks up the correct controller:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CustomSiteConfig.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
 
class CustomSiteConfig extends DataObjectDecorator {
	
	function extraStatics() {
		return array(
			'has_many' =&amp;gt; array( 
            	'SomeDataObjects' =&amp;gt; 'DataObjectClass'
        	) 
      ); 
   }
   public function updateCMSFields(FieldSet &amp;amp;$fields) { 
       
      $manager = new SiteConfig_DataObjectManager( 
         $this-&amp;gt;owner, 
         'SomeDataObjects', 
         'DataObjectClass', 
         array('SomeField' =&amp;gt; 'Some Field'),
         'getCMSFields_forPopup'          
      );    
      $manager-&amp;gt;setSourceID($this-&amp;gt;owner-&amp;gt;ID);
      $fields-&amp;gt;addFieldToTab(&quot;Root.DOM&quot;, $manager); 
     
   	}
}
class SiteConfig_DataObjectManager extends DataObjectManager {
	function setSourceID($val) { 
		if (is_numeric($val)) { 
		$this-&amp;gt;sourceID = $val; 
		} 
	} 
	function sourceID() { 
		if (isset($this-&amp;gt;sourceID) &amp;amp;&amp;amp; $this-&amp;gt;sourceID !== null &amp;amp;&amp;amp; is_numeric($this-&amp;gt;sourceID)) { 
			return $this-&amp;gt;sourceID; 
		} 
		return parent::sourceID(); 
	}
}&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So as you can see instead of using $this we use $this-&amp;gt;owner to set the DOM controller. We also use the SiteConfig_DataObjectManager class that we define at the bottom so that we can call our custom setSourceID() function and&amp;nbsp;set the sourceID to $this-&amp;gt;owner-&amp;gt;ID, which is the ID of the SiteConfig.&lt;/p&gt;
&lt;p&gt;Special thanks to&amp;nbsp;&lt;a style=&quot;text-decoration: none; color: #2f98d4; font-family: Verdana, Lucida, sans-serif;&quot; href=&quot;http://www.betterbrief.com&quot;&gt;Dan Hensby&lt;/a&gt;&amp;nbsp;for providing the DOM extension code. You can see the forum post&amp;nbsp;&lt;a style=&quot;text-decoration: none; color: #2f98d4; font-family: Verdana, Lucida, sans-serif;&quot; href=&quot;http://www.silverstripe.org/dataobjectmanager-module-forum/show/282867?showPost=282896&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So there you have it, the new SiteConfig. Very useful and easy when you know how! :)&lt;/p&gt;
&lt;p&gt;Thanks to&amp;nbsp;&lt;a href=&quot;http://www.johannes-fischer.de/&quot;&gt;Johannes Fischer&lt;/a&gt;&amp;nbsp;for suggesting this post.&lt;/p&gt;</description>
			<pubDate>Mon, 19 Apr 2010 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/2-4-working-with-siteconfig/</guid>
		</item>
		
		<item>
			<title>Display DataObjects in an SEO friendly way</title>
			<link>http://www.ssbits.com/display-dataobjects-in-an-seo-friendly-way/</link>
			<description>&lt;p&gt;Regarding &lt;a title=&quot;Using SilverStripe URL Parameters to display DataObjects on a page&quot; href=&quot;http://www.ssbits.com/../using-silverstripe-url-parameters-to-display-dataobjects-on-a-page/&quot;&gt;this 'Need to know' tutorial&lt;/a&gt; from Aram, I would like to extend this principle by explaining how to display DataObjects in a SEO friendly way.&lt;/p&gt;
&lt;p&gt;There are several ways to do this. I will explain one I prefer the most, by using SiteTree::GenerateURLSegment();.&lt;/p&gt;
&lt;p&gt;First step is to add an extra field to the Database called URLSegment. If you use Aram's files in his &lt;a title=&quot;Using SilverStripe URL Parameters to display DataObjects on a page&quot; href=&quot;http://www.ssbits.com/../using-silverstripe-url-parameters-to-display-dataobjects-on-a-page&quot;&gt;tutorial on displaying DataObjects&lt;/a&gt;, your StaffMember $db array will look like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffMember.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaffMember extends Page{
static $db = array(
        'Name' =&amp;gt; 'Varchar(255)',
        'Description' =&amp;gt; 'Text',
        'URLSegment' =&amp;gt; 'Varchar(255)'
    );
.
.
.
.
}&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To add the URLSegment on save automatically, add the onBeforeWrite() method to the StaffMember class:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffMember.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaffMember extends Page{
.
.
.
.
public function onBeforeWrite(){
		if($this-&amp;gt;Name){
			$this-&amp;gt;URLSegment = SiteTree::GenerateURLSegment($this-&amp;gt;Name);
			if($object = DataObject::get_one($this-&amp;gt;ClassName, &quot;URLSegment='&quot;.$this-&amp;gt;URLSegment.&quot;' AND ID !=&quot;.$this-&amp;gt;ID)){
				$this-&amp;gt;URLSegment = $this-&amp;gt;URLSegment.'-'.$this-&amp;gt;ID;
			}
		} else {
			$this-&amp;gt;URLSegment = SiteTree::GenerateURLSegment($this-&amp;gt;ClassName.'-'.$this-&amp;gt;ID);
		}
		parent::onBeforeWrite();
	}
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;First we check for a Name to generate the URLSegment. If present, probably it is, we use SiteTree::GenerateURLSegment(); to create the pretty URL. To prevent duplicate urls, we check for a DataObject by getting a StaffMember with the newly created URLSegment and set an extra filter, so it is not getting itself by adding ID !=&quot;This-&amp;gt;ID&quot;. In this case I only want to know if there is one, and if so I just add the ID to the URLSegment, which is always unique.&lt;/p&gt;
&lt;p&gt;This is slightly different then the SiteTree::onBeforeWrite() handles the URLSegment, which counts for the same PageTitles. I prefer to keep it simple by adding the unique ID in case the URLSegment already exist.&lt;br /&gt;And finally, just in case when no Name is entered, we create an unique URLSegment, by using the ClassName and ID.&lt;/p&gt;
&lt;p&gt;The Next step is to change the StaffMember links in the StaffSideBar.ss file by replacing {$ID} to {$URLSegment} .&lt;/p&gt;
&lt;p&gt;The last step is to change the function getIndividualStaffMember() in StaffPage_Controller:&lt;/p&gt;
&lt;p&gt;mysite/code/StaffPage.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaffPage_Controller extends Page_Controller{
public function getIndividualStaffMember(){
        if($URLAction = Director::URLParam('Action')){
            $StaffMemberURLSegment = Convert::raw2xml($URLAction);
            if($StaffMemberURLSegment ){
                return DataObject::get_one('StaffMember', &quot;URLSegment='&quot;.$StaffMemberURLSegment.&quot;'&quot;);
            }
        }
    }
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Instead of getting the DataObject by id, we simply use DataObject::get_one() and filter by URLSegment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about searchengines and old urls?&lt;/strong&gt;&lt;br /&gt;There is one thing you have to keep in mind. The SiteTree is versioned. This means that when a page URLSegment is changed, and old page urls are accessed, Silverstripe will redirect the visitor with a 301 Permanently Moved header to the new page url. This is not the case when using my method on getting DataObjects by URLSegment. Each time the Name is changed, the URLSegment will change too, and this will cause old links become inaccessible!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use a 301 redirect&lt;/strong&gt;&lt;br /&gt;One way to solve this issue, is to just get the StaffMember by id and attach the URLSegment to the StaffMember link :&amp;nbsp; /{$ID}/{$URLSegment} .&lt;/p&gt;
&lt;p&gt;Your getIndividualStaffMember function in StaffPage_controller will then look like this :&lt;/p&gt;
&lt;p&gt;mysite/code/StaffPage.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;public function getIndividualStaffMember(){
        if($URLAction = Director::URLParam('Action')){
            $StaffMemberID = Convert::raw2xml($URLAction);
            if(is_numeric($StaffMemberID )){
                $object = DataObject::get_by_id('StaffMember', $StaffMemberID);
                if($object-&amp;gt;URLSegment != '' &amp;amp;&amp;amp; $object-&amp;gt;URLSegment != Director::URLParam('ID')){
                    Director::redirect($this-&amp;gt;URLSegment.'/'.$object-&amp;gt;ID.'/'.$object-&amp;gt;URLSegment,301);
                } else {
                    return $object;    
                }
            }
        }
    }&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Here we still get the StaffMember by ID, but we add a check if the URLSegment from the StaffMember is the same as the URLSegment in the link. The URLSegment is defined Director::URLParam('ID') .&amp;nbsp; If the URLSegment is not the same, we will use Director::redirect() to redirect to the new URLSegment with a 301 permanently moved header. This is important, so search engines will know the url is changed and the 'page' is moved.&lt;/p&gt;
&lt;p&gt;Of course you could switch the $ID and $URLSegment, so you move the less important number to the end of the url. In the getIndividualStaffMember function you just switch Director::URLParam('Action') and Director::URLParam('ID') .&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combine ID and URLSegment&lt;/strong&gt;&lt;br /&gt;If you don't want to use 2 URLParams, you could combine the ID and the URLSegment. To do this add a function URL() to the StaffMember DataObject:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/Staffmember.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;public function URL(){
        return $this-&amp;gt;ID.'-'.$this-&amp;gt;URLSegment;
    }&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;In StaffSidebar.ss replace {$ID} with {$URL} to display the combined URL. () you could also skip the function and use {$ID}-{$URLSegment} instead.&lt;/p&gt;
&lt;p&gt;Use this function in StaffPage_Controller to get the Member:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffPage.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;public function getIndividualStaffMember(){
        if($URLAction = Director::URLParam('Action')){
            $StaffMemberID = Convert::raw2xml($URLAction);
            $pos         = strpos($StaffMemberID, '-');
            $ID         = substr($StaffMemberID, 0,$pos);
            $URLSegment = substr($StaffMemberID, $pos + 1);
            
            if(is_numeric($ID )){
                $object = DataObject::get_by_id('StaffMember', $ID);
                if($object-&amp;gt;URLSegment != '' &amp;amp;&amp;amp; $object-&amp;gt;URLSegment != $URLSegment){
                    Director::redirect($this-&amp;gt;URLSegment.'/'.$object-&amp;gt;ID.'/'.$object-&amp;gt;URLSegment,301);
                } else {
                    return $object;    
                }
            }
        }
    }&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;We use strpos() to get the first position of - and use substr() to split the URL to the $ID and $URLSegment.&lt;/p&gt;
&lt;p&gt;Now, this tutorial got a little longer then I expected when I started to write it. This should give you some methods to use pretty urls to display DataObjects on a Page. Maybe next tutorial should be 'How to add Versioning to DataObjects' to access DataObjects by URLSegment without the use of $ID and with 301 redirects!&lt;/p&gt;</description>
			<pubDate>Wed, 10 Feb 2010 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/display-dataobjects-in-an-seo-friendly-way/</guid>
		</item>
		
		<item>
			<title>Adding Fields to Page Comments</title>
			<link>http://www.ssbits.com/adding-fields-to-page-comments/</link>
			<description>&lt;p&gt;This isn't as simple as it should be.&amp;nbsp; I am also sure that things could be done in a better way, please comment if you have improvments.&lt;/p&gt;
&lt;h2&gt;Override PageComments&lt;/h2&gt;
&lt;p&gt;You'll need to override the&lt;strong&gt; ContentController-&amp;gt;PageComments()&lt;/strong&gt; function. &amp;nbsp;This is essentially a copy and paste from sapphire/core/control/ContentController.php and change the reference to&amp;nbsp;PageCommentInterface to&amp;nbsp;PageCommentExtendedInterface, which you will create in the next step.&lt;br /&gt;&lt;strong&gt;mysite/code/Page.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class Page extends SiteTree {
/*...*/
}
class Page_Controller extends ContentController {
/*...*/
   /**
    * Returns a page comment system
    */
   function PageComments() {
      if($this-&amp;gt;data() &amp;amp;&amp;amp; $this-&amp;gt;data()-&amp;gt;ProvideComments) {
         return new PageCommentExtendedInterface($this, 'PageComments', $this-&amp;gt;data());
      } else {
         if(isset($_REQUEST['executeForm']) &amp;amp;&amp;amp; $_REQUEST['executeForm'] == 'PageComments.PostCommentForm') {
            echo &quot;Comments have been disabled for this page&quot;;
            die();
         }
      }
   }
/*...*/
} &lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Extending PageCommentInterface&lt;/h2&gt;
&lt;p&gt;PageCommentInterface is the Request Handler for page comments.&amp;nbsp; You will need to override the function PostCommentForm().&amp;nbsp; Unfortunately because of the way this function is written calling the parent on it and then adding the new fields is not possible (as far as I know), so you must copy the existing function from the parent class and then add your new field in the fields and then to load the new field from the session you'll also have to add your new field to the loadDataFrom array.&amp;nbsp; You are not done with this file yet, now you must also set the session for your new field.&amp;nbsp; To accomplish this you'll have to extend the internal class &lt;strong&gt;PageCommentInterface_Form&lt;/strong&gt; and the function &lt;strong&gt;postComment()&lt;/strong&gt;.&amp;nbsp; In this case you can load up your session and then just call the parent function.&amp;nbsp; Don't forget to change the reference to &lt;strong&gt;PageCommentInterface_Form&lt;/strong&gt; to your new class.&lt;br /&gt;&lt;strong&gt;mysite/code/PageCommentExtendedInterface.php&lt;/strong&gt;:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class PageCommentExtendedInterface extends PageCommentInterface {
    
   function PostCommentForm() {
    		$fields = new FieldSet(
			new HiddenField(&quot;ParentID&quot;, &quot;ParentID&quot;, $this-&amp;gt;page-&amp;gt;ID)
		);
		
		$member = Member::currentUser();
		
		if((self::$comments_require_login || self::$comments_require_permission) &amp;amp;&amp;amp; $member &amp;amp;&amp;amp; $member-&amp;gt;FirstName) {
			// note this was a ReadonlyField - which displayed the name in a span as well as the hidden field but
			// it was not saving correctly. Have changed it to a hidden field. It passes the data correctly but I 
			// believe the id of the form field is wrong.
			$fields-&amp;gt;push(new ReadonlyField(&quot;NameView&quot;, _t('PageCommentInterface.YOURNAME', 'Your name'), $member-&amp;gt;getName()));
			$fields-&amp;gt;push(new HiddenField(&quot;Name&quot;, &quot;&quot;, $member-&amp;gt;getName()));
		} else {
			$fields-&amp;gt;push(new TextField(&quot;Name&quot;, _t('PageCommentInterface.YOURNAME', 'Your name')));
		}
		// optional commenter EMAIL
        $fields-&amp;gt;push(new TextField(&quot;CommenterEmail&quot;, _t('PageCommentInterface.COMMENTEREMAIL', &quot;Email:&quot;)));
		// optional commenter URL
		$fields-&amp;gt;push(new TextField(&quot;CommenterURL&quot;, _t('PageCommentInterface.COMMENTERURL', &quot;Your website URL&quot;)));
		
		if(MathSpamProtection::isEnabled()){
			$fields-&amp;gt;push(new TextField(&quot;Math&quot;, sprintf(_t('PageCommentInterface.SPAMQUESTION', &quot;Spam protection question: %s&quot;), MathSpamProtection::getMathQuestion())));
		}				
		
		$fields-&amp;gt;push(new TextareaField(&quot;Comment&quot;, _t('PageCommentInterface.YOURCOMMENT', &quot;Comments&quot;)));
		
		$form = new NewPageCommentInterface_Form($this, &quot;PostCommentForm&quot;, $fields, new FieldSet(
			new FormAction(&quot;postcomment&quot;, _t('PageCommentInterface.POST', 'Post'))
		));
		
		// Set it so the user gets redirected back down to the form upon form fail
		$form-&amp;gt;setRedirectToFormOnValidationError(true);
		
		// Optional Spam Protection.
		if(class_exists('SpamProtectorManager')) {
			SpamProtectorManager::update_form($form, null, array('Name', 'CommenterURL', 'Comment'));
			self::set_use_ajax_commenting(false);
		}
		
		// Shall We use AJAX?
		if(self::$use_ajax_commenting) {
			Requirements::javascript(THIRDPARTY_DIR . '/behaviour.js');
			Requirements::javascript(THIRDPARTY_DIR . '/prototype.js');
			Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
			Requirements::javascript(CMS_DIR . '/javascript/PageCommentInterface.js');
		}
		
		// Load the data from Session
		$form-&amp;gt;loadDataFrom(array(
			&quot;Name&quot; =&amp;gt; Cookie::get(&quot;PageCommentInterface_Name&quot;),
			&quot;Comment&quot; =&amp;gt; Cookie::get(&quot;PageCommentInterface_Comment&quot;),
			&quot;CommenterEmail&quot; =&amp;gt; Cookie::get(&quot;PageCommentInterface_CommenterEmail&quot;),
			&quot;CommenterURL&quot; =&amp;gt; Cookie::get(&quot;PageCommentInterface_CommenterURL&quot;)	
		));
		
		return $form;
   }
}
/**
 * @package cms
 * @subpackage comments
 */
class NewPageCommentInterface_Form extends PageCommentInterface_Form {
	function postcomment($data) {
		Cookie::set(&quot;PageCommentInterface_CommenterEmail&quot;, $data['CommenterEmail']);
		return parent::postcomment($data);
	}
}
?&amp;gt;&lt;/textarea&gt;  &lt;/p&gt;
&lt;h2&gt;Setuping up your site configuration&lt;/h2&gt;
&lt;p&gt;You will be creating a decorator, you must include some code in your &lt;strong&gt;_config.php&lt;/strong&gt; to assign it.&amp;nbsp; You will also be creating an extension to an Admin section you will want to add a rule to redirect the comments action to your new extension, also remove the old admin section.&lt;br /&gt;&lt;strong&gt;mysite/_config.php&lt;/strong&gt;:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;DataObject::add_extension('PageComment','PageCommentDecorator');
Director::addRules(50, array(
	'admin/comments//$Action/$ID' =&amp;gt; 'CommentAdminExtension',
));
CMSMenu::remove_menu_item('CommentAdmin');&lt;/textarea&gt; &lt;br /&gt;(NOTE:&amp;nbsp; I tried to use LeftAndMainDecorator initially instead of extending CommentAdmin, but I ran into several issues) &lt;/p&gt;
&lt;h2&gt;Decorating PageComment&lt;/h2&gt;
&lt;p&gt;This could be as simple as just adding a new db static and pushing a new TextField onto CMSFields, however I took it a step further and set it up to send and email after someone posts a comment.&amp;nbsp; The email needs better formatting and it could use the admin email and the links to delete and mark as spam could be generated more dynamically.&amp;nbsp; This is not really part of the tutorial but I thought I would share.&lt;br /&gt;&lt;strong&gt;mysite/code/PageCommentDecorator.php&lt;/strong&gt;:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class PageCommentDecorator extends DataObjectDecorator  {
   function extraStatics() {
      return array(
         'db' =&amp;gt; array(
            &quot;CommenterEmail&quot; =&amp;gt; &quot;Varchar(255)&quot;,
         )
      );
   }
	public function updateCMSFields(FieldSet &amp;amp;$fields) {
        $fields-&amp;gt;push(new TextField('CommenterEmail', 'Commenter Email'));
	}
	function updateFieldLabels(&amp;amp;$labels) {
      parent::updateFieldLabels($labels);
      $labels['CommenterEmail'] = &quot;Commenter Email&quot;;
   }
   
   function onAfterWrite() {
        $post = DataObject::get_one('SiteTree', &quot;`SiteTree`.ID=&quot;.$this-&amp;gt;owner-&amp;gt;getField('ParentID'));
        $e = new Email(
            $this-&amp;gt;owner-&amp;gt;getField('CommenterEmail'),//From Address
            'comment@yoursite.com',//To Address - Currently hard coded should get admin address and/or posters address
            &quot;Posted a comment on &quot;.$post-&amp;gt;title,//Subject
            &quot;&quot;.$this-&amp;gt;owner-&amp;gt;getField('Name') . &quot; said &quot; . '&quot;' . $this-&amp;gt;owner-&amp;gt;getField('Comment') . '&quot;&amp;lt;br/&amp;gt;'.
            '&amp;lt;a href=&quot;http://yoursite.com/admin/comments/EditForm/field/Comments/item/' . $this-&amp;gt;owner-&amp;gt;getField('ID') . '?methodName=spam&quot;&amp;gt;Mark as SPAM&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;'.
            '&amp;lt;a href=&quot;http://yoursite.com/admin/comments/EditForm/field/Comments/item/' . $this-&amp;gt;owner-&amp;gt;getField('ID') . '/delete&quot;&amp;gt;DELETE&amp;lt;/a&amp;gt;'
            );//Body
        $e-&amp;gt;send();
	} 
}
?&amp;gt;&lt;/textarea&gt;  &lt;/p&gt;
&lt;h2&gt;Extending CommentAdmin&lt;/h2&gt;
&lt;p&gt;And Finally, if you want to see the new data fields in the backend you'll have to extend the CommentAdmin and override &lt;strong&gt;EditForm().&lt;/strong&gt; Again, I had to copy and paste the function from the source due to the way it was written.&amp;nbsp; You must add the table field and the PopupFields for your new data field.&lt;br /&gt;&lt;strong&gt;mysite/code/CommentAdminExtension&lt;/strong&gt;:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
/*Copied from CommentAdmin.php version 2.3.1
    - Added Email field
 */
class CommentAdminExtension extends CommentAdmin  {	
	public function EditForm() {
		$section = $this-&amp;gt;Section();
		
		if($section == 'approved') {
			$filter = 'IsSpam=0 AND NeedsModeration=0';
			$title = &quot;&amp;lt;h2&amp;gt;&quot;. _t('CommentAdmin.APPROVEDCOMMENTS', 'Approved Comments').&quot;&amp;lt;/h2&amp;gt;&quot;;
		} else if($section == 'unmoderated') {
			$filter = 'NeedsModeration=1';
			$title = &quot;&amp;lt;h2&amp;gt;&quot;._t('CommentAdmin.COMMENTSAWAITINGMODERATION', 'Comments Awaiting Moderation').&quot;&amp;lt;/h2&amp;gt;&quot;;
		} else {
			$filter = 'IsSpam=1';
			$title = &quot;&amp;lt;h2&amp;gt;&quot;._t('CommentAdmin.SPAM', 'Spam').&quot;&amp;lt;/h2&amp;gt;&quot;;
		}
		
		$filter .= ' AND ParentID&amp;gt;0';
		
		$tableFields = array(
			&quot;Name&quot; =&amp;gt; _t('CommentAdmin.AUTHOR', 'Author'),
			&quot;Comment&quot; =&amp;gt; _t('CommentAdmin.COMMENT', 'Comment'),
			&quot;CommenterEmail&quot; =&amp;gt; _t('CommentAdmin.COMMENTEREMAIL', 'Email'),
			&quot;PageTitle&quot; =&amp;gt; _t('CommentAdmin.PAGE', 'Page'),
			&quot;CommenterURL&quot; =&amp;gt; _t('CommentAdmin.COMMENTERURL', 'URL'),
			&quot;Created&quot; =&amp;gt; _t('CommentAdmin.DATEPOSTED', 'Date Posted')
		);	
		
		$popupFields = new FieldSet(
			new TextField('Name', _t('CommentAdmin.NAME', 'Name')),
			new TextField('CommenterEmail', _t('CommentAdmin.COMMENTEREMAIL', 'Email')),
			new TextField('CommenterURL', _t('CommentAdmin.COMMENTERURL', 'URL')),
			new TextareaField('Comment', _t('CommentAdmin.COMMENT', 'Comment'))
		);
		
		$idField = new HiddenField('ID', '', $section);
		$table = new CommentTableField($this, &quot;Comments&quot;, &quot;PageComment&quot;, $section, $tableFields, $popupFields, array($filter));
		$table-&amp;gt;setParentClass(false);
		
		$fields = new FieldSet(
			new TabSet(	'Root',
				new Tab(_t('CommentAdmin.COMMENTS', 'Comments'),
					new LiteralField(&quot;Title&quot;, $title),
					$idField,
					$table
				)
			)
		);
		
		$actions = new FieldSet();
		
		if($section == 'unmoderated') {
			$actions-&amp;gt;push(new FormAction('acceptmarked', _t('CommentAdmin.ACCEPT', 'Accept')));
		}
		
		if($section == 'approved' || $section == 'unmoderated') {
			$actions-&amp;gt;push(new FormAction('spammarked', _t('CommentAdmin.SPAMMARKED', 'Mark as spam')));
		}
		
		if($section == 'spam') {
			$actions-&amp;gt;push(new FormAction('hammarked', _t('CommentAdmin.MARKASNOTSPAM', 'Mark as not spam')));
		}
		
		$actions-&amp;gt;push(new FormAction('deletemarked', _t('CommentAdmin.DELETE', 'Delete')));
		
		if($section == 'spam') {
			$actions-&amp;gt;push(new FormAction('deleteall', _t('CommentAdmin.DELETEALL', 'Delete All')));
		}
		
		$form = new Form($this, &quot;EditForm&quot;, $fields, $actions);
		
		return $form;
	}
}
?&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;That is it.&amp;nbsp; I'd apreciate any feedback you have as this is my first tutorial.&amp;nbsp; I'm considering packaging this as a module, it would also be my first module.&amp;nbsp; I feel the code needs to be a bit better for a module so if you have any improvements don't hesitate contacting me.&lt;/p&gt;
&lt;p&gt;Special thanks to Mike Mannix for running through this and proving it works and is somewhat easy to follow, as well as fixing my horrible grammar. &amp;nbsp;Mike is working on his first Silverstripe site&amp;nbsp;&lt;a style=&quot;text-decoration: none; color: #2f98d4; font-family: Verdana, Lucida, sans-serif;&quot; href=&quot;http://elmbrew.com&quot;&gt;elmbrew.com&lt;/a&gt;, he completed the level one tutorials and then moved on to this. &amp;nbsp;It is a work in progress so keep an eye on it.&lt;/p&gt;</description>
			<pubDate>Thu, 04 Feb 2010 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/adding-fields-to-page-comments/</guid>
		</item>
		
		<item>
			<title>Using SilverStripe URL Parameters to display DataObjects on a page</title>
			<link>http://www.ssbits.com/using-silverstripe-url-parameters-to-display-dataobjects-on-a-page/</link>
			<description>&lt;p&gt;Many of you may have come across the SilverStripe URL parameters in which take the form &lt;strong&gt;$Action/$ID/$OtherID&lt;/strong&gt;.
These are very useful for creating pages that act as templates for a
DataObject. Lets say you had a Number of staff that you wanted to enter
into the CMS as DataObjects on a StaffPage page type. This would mean
that staff members would not have their own page on the site. In an
ideal world the &lt;strong&gt;StaffPage&lt;/strong&gt; itself would be able to display a
list of all staff member as well as each one individually, based on a
name passed as a parameter, for example &lt;strong&gt;www.yoursite.com/staff/jonny-cache&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;Well in this tutorial we are going to create a system just like this,
the only difference is that in order to keep things simple we will be
using the staff members ID as the parameter, so the URL would look like
this:&amp;nbsp; &lt;strong&gt;www.yoursite.com/staff/15&lt;/strong&gt;.&lt;strong&gt; &lt;/strong&gt;The
reason for doing it this
way is that we won't have to worry about creating a URL field for each
staff member, making sure it doesn't contain special chars and keeping
it unique, which would form a tutorial in it self. By suing the ID we
can be sure of all of this without having to do anything! On the
downside it doesn't make pretty URLs and it's not SEO friendly for the same reason. Well it
is only a tutorial after all!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Part 1: Creating the StaffMember DataObject&lt;/h2&gt;
&lt;p&gt;The
first thing we need to do is create the StaffMember it self. We are
going to keep this simple and just have a photo, their name and a short
description. Obviously you can make this as detailed as you like:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffMember.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class StaffMember extends DataObject {
	
	static $db = array(
		'Name' =&amp;gt; 'Varchar(255)',
		'Description' =&amp;gt; 'Text'
	);
	static $has_one = array(
		'StaffPage' =&amp;gt; 'StaffPage',
		'Photo' =&amp;gt; 'Image'
	);	
	
	public function getCMSFields_forPopup()
	{
		return new FieldSet(
			new TextField('Name'),
			new TextareaField('Description'),
			new ImageField('Photo')
			);
	}
	
	public function getThumb(){
		if($this-&amp;gt;PhotoID)
			return $this-&amp;gt;Photo()-&amp;gt;CMSThumbnail();
		else
			return '(No Photo)';
	}	
	
}&amp;nbsp;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;This should be pretty familiar to you, just a sandard DataObject class. The only thing that might be new is the &lt;strong&gt;getThumb()&lt;/strong&gt;
function. This is used to return a thumbnail of the photo to the
DataObjectManager so that you get a nice preview of the person in the
table.&lt;/p&gt;
&lt;h2&gt;Part 2: Creating our StaffPage&lt;/h2&gt;
&lt;p&gt;Now we need to create the StaffPage that is going to hold all our &lt;strong&gt;StaffMember&lt;/strong&gt;
DataObjects. Again this is pretty standard stuff, we create a &lt;strong&gt;has_many&lt;/strong&gt;
relationship and define a DataObjectManger as the fieldtype to manage
it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffPage.php&lt;/strong&gt;&lt;br /&gt;
&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class StaffPage extends Page {
	
    static $has_many = array(
	&quot;StaffMembers&quot; =&amp;gt; &quot;StaffMember&quot;,
    );
	
    function getCMSFields() {
	$fields = parent::getCMSFields();
	$dataobjectmanager = new DataObjectManager(
		$this,
		'StaffMembers',
		'StaffMember',
		array('Thumb' =&amp;gt; 'Photo', 'Name' =&amp;gt; 'Name', 'Description' =&amp;gt; 'Description'),
		'getCMSFields_forPopup'
	);
	$dataobjectmanager-&amp;gt;setPluralTitle(&quot;a member of staff&quot;);
	$fields-&amp;gt;addFieldToTab(&quot;Root.Content.StaffMembers&quot;, $dataobjectmanager);	
	return $fields;
    }
}
class StaffPage_Controller extends Page_Controller {
	
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;You could also use a complexTableField if you &lt;span style=&quot;text-decoration: line-through;&quot;&gt;are mad&lt;/span&gt; want to. Notice that in the field label definition we call &lt;strong&gt;Thumb&lt;/strong&gt; not &lt;strong&gt;getThumb&lt;/strong&gt;. This is making use of PHP's &lt;a href=&quot;http://php.net/manual/en/language.oop5.magic.php&quot; target=&quot;_blank&quot;&gt;magic Methods&lt;/a&gt;&amp;nbsp; which allows you to add &lt;strong&gt;get&lt;/strong&gt;
to the start of a function, without having to use it when calling from a template.&lt;/p&gt;
&lt;p&gt;Now lets also just create a basic template for our staff page that will display a list of all the &lt;strong&gt;StaffMembers&lt;/strong&gt; photos:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/blackcandy/templates/Layout/StaffPage.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;typography&quot;&amp;gt;
				
	&amp;lt;h2&amp;gt;$Title&amp;lt;/h2&amp;gt;
		
	&amp;lt;% control StaffMembers %&amp;gt;	
	$Photo.SetWidth(100)
	&amp;lt;% end_control %&amp;gt;		
&amp;lt;/div&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Part 3: The funky function&lt;/h2&gt;
&lt;p&gt;So we have our classes and a basic template which is displaying our staff members. Now what we need to do is create a function which checks to see if there is a URL parameter set and if there is checks the DataBase for a StaffMember with that ID, which if found would then be returned to the template to render. So here is the function which is placed in the &lt;strong&gt;StaffPage_Controller&lt;/strong&gt; class:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffPage.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;	
class StaffPage_Controller extends Page_Controller {
    
    public function  getIndividualStaffMember(){
		
		if($URLAction = Director::URLParam('Action')){
		
			$StaffMemberID = Convert::raw2xml($URLAction);
			
			if(is_numeric($StaffMemberID )){
				return DataObject::get_by_id('StaffMember', $StaffMemberID);
			}
		}
	
	}
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;So lets go though this function. The first line gets the URL parameter 'Action' (i.e. the first parameter) and tries to assign it to the variable $URLAction.If it fails we know that there is no action and we can return to the template. Next if there was an 'Action' defined then we 'sanitize' the argument to ensure there is no malicious code in there that could harm the database (if you are wondering why you need to sanitize, read&lt;a href=&quot;http://xkcd.com/327/&quot; target=&quot;_blank&quot;&gt; this&lt;/a&gt;). &lt;/p&gt;
&lt;p&gt;Finally we check that the argument is numeric before we pass it to &lt;strong&gt;DataObject::get_by_id()&lt;/strong&gt; which will try to fetch our staff DataObject. If the &lt;strong&gt;ID&lt;/strong&gt; is wrong, then it will just return false, which we can then use in our template to test whether to render an individual staff member or the list.&lt;/p&gt;
&lt;h2&gt;Part 4: Displaying it all in the template&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;So now lets take a look at our template and see how we can use this function to decide what to display:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/blackcandy/templates/Layout/StaffPage.ss&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;typography&quot;&amp;gt;
				
	&amp;lt;h2&amp;gt;$Title&amp;lt;/h2&amp;gt;
	
	&amp;lt;% if IndividualStaffMember %&amp;gt;
		&amp;lt;% control IndividualStaffMember %&amp;gt;
		&amp;lt;ul&amp;gt;
			&amp;lt;li&amp;gt;$Photo.SetWidth(100)&amp;lt;/li&amp;gt;
			&amp;lt;li&amp;gt;Name: $Name&amp;lt;/li&amp;gt;
			&amp;lt;li&amp;gt;Description: $Description&amp;lt;/li&amp;gt;
		&amp;lt;/ul&amp;gt;
		&amp;lt;% end_control %&amp;gt;
	&amp;lt;% else %&amp;gt;
		&amp;lt;% control StaffMembers %&amp;gt;	
			&amp;lt;a href=&quot;{$Top.Link}{$ID}&quot; &amp;gt;$Photo.SetWidth(100)&amp;lt;/a&amp;gt;
		&amp;lt;% end_control %&amp;gt;		
	&amp;lt;% end_if %&amp;gt;
&amp;lt;/div&lt;/textarea&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The function we created in the last step will return false either if there is no action parameter defined or if the defined action is not the ID of a StaffMember. So we first test whether there is an IndividualStaffMember to display which if there is we display some detailed info about that staff member, otherwise we simply display the list of staff all members, this time with a link to view that staff member in more detail. To construct the link we simple join the MenuPage's Link (by using $Top.Link which will jump out of the control loop and get the Link from the main page we are on) with the ID of the staff member in our control loop, giving us URLs like &lt;strong&gt;&quot;staff/3&quot;. &lt;/strong&gt;We also&lt;strong&gt; &lt;/strong&gt;wrap these variables in {} to make sure SilverStripe reads them properly.&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So now we have a page which will display our dataobjects individually as well as in a list. The last thing we need to do is make our sidebar read them too.&lt;/p&gt;
&lt;h2&gt;Part5: Creating the Sidebar&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;To display the DataObjects in the sidebar we need to replace our standard sidebar with a custom one:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/blackcandy/templates/Includes/StaffSidebar.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div id=&quot;Sidebar&quot; class=&quot;typography&quot;&amp;gt;
	&amp;lt;div class=&quot;sidebarBox&quot;&amp;gt;
 		&amp;lt;h3&amp;gt;&amp;lt;% control Level(1) %&amp;gt;$Title&amp;lt;% end_control %&amp;gt;&amp;lt;/h3&amp;gt;
  		
  		&amp;lt;ul id=&quot;Menu2&quot;&amp;gt;
		  	&amp;lt;% control StaffMembers %&amp;gt;	
		  		&amp;lt;li&amp;gt;&amp;lt;a href=&quot;{$Top.Link}/{$ID}&quot; mce_href=&quot;{$Top.Link}/{$ID}&quot; title=&quot;Go to the $Title.XML page&quot; class=&quot;$LinkingMode levela&quot;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;i&amp;gt;$Name&amp;lt;/i&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;  			 
  			&amp;lt;% end_control %&amp;gt;
  		&amp;lt;/ul&amp;gt;
		&amp;lt;div class=&quot;clear&quot;&amp;gt;&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
	&amp;lt;div class=&quot;sidebarBottom&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;So as you can see this looks very much like the standard blackcandy sidebar, the only difference being that we have replaced &lt;strong&gt;&amp;lt;% control Children %&amp;gt;&lt;/strong&gt; with &lt;strong&gt;&amp;lt;% control Staffmembers %&amp;gt;&lt;/strong&gt; which will get all our staff members and create links for each. Again we use the&lt;strong&gt; {$Top.Link}{$ID} &lt;/strong&gt;to generate the link. One thing you might notice is that because we are using a single page for all of these, the $LinkingMode will no longer work. So to remedy this lets create our own function &lt;strong&gt;LinkingMode()&lt;/strong&gt; inside the &lt;strong&gt;StaffMember &lt;/strong&gt;DataObject:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/StaffMember.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaffMember extends DataObject {
	
.
.
.
.
	
	public function getLinkingMode(){
		if($this-&amp;gt;ID == Director::URLParam('Action')){
			return 'current';
		}
		else{
			return 'link';
		}
	}
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;All this function does is compare the current Action parameter with it's own ID. If they match it returns 'current' otherwise 'link' just like the $LinkingMode for pages!&lt;/p&gt;
&lt;p&gt;The last thing left to do is include the sidebar on our template:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/blackcandy/templates/Layout/StaffPage.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;typography&quot;&amp;gt;
		&amp;lt;% include StaffSidear %&amp;gt;
		&amp;lt;div id=&quot;Content&quot;&amp;gt;
				
			&amp;lt;h2&amp;gt;$Title&amp;lt;/h2&amp;gt;
			
			&amp;lt;% if IndividualStaffMember %&amp;gt;
				&amp;lt;% control IndividualStaffMember %&amp;gt;
				&amp;lt;ul&amp;gt;
					&amp;lt;li&amp;gt;$Photo.SetWidth(100)&amp;lt;/li&amp;gt;
					&amp;lt;li&amp;gt;Name: $Name&amp;lt;/li&amp;gt;
					&amp;lt;li&amp;gt;Description: $Description&amp;lt;/li&amp;gt;
				&amp;lt;/ul&amp;gt;
				&amp;lt;% end_control %&amp;gt;
			&amp;lt;% else %&amp;gt;
				&amp;lt;% control StaffMembers %&amp;gt;	
					&amp;lt;a href=&quot;{$Top.Link}{$ID}&quot; &amp;gt;$Photo.SetWidth(100)&amp;lt;/a&amp;gt;
				&amp;lt;% end_control %&amp;gt;		
			&amp;lt;% end_if %&amp;gt;
		
		&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;So that's it! You now have a fully functioning DataObject display(er).&lt;/p&gt;
&lt;h2&gt;More about URLParameters&lt;/h2&gt;
&lt;p&gt;In this tutorial we only really scraped the surface of using URL parameters. There are 3 setup by default in SilverStripe, 'Action', 'ID and 'OtherID'. These can be accessed in the same way as this tutorial using Director:URLParam('ParamName'). They can also be accessed by assigning all of them to an array like so:&lt;/p&gt;
&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;$URLParams = Director::URLParams();
$Action = $URLParams['Action'];
$ID = $URLParams['ID'];
$OtherID = $URLParams['OtherID'];&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;You can also define your own URL parameter structure using &lt;strong&gt;Director::&lt;span class=&quot;me2&quot;&gt;&lt;span class=&quot;search_hit&quot;&gt;addRules(), &lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;span class=&quot;me2&quot;&gt;&lt;span class=&quot;search_hit&quot;&gt;though documentation on the subject &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;me2&quot;&gt;&lt;span class=&quot;search_hit&quot;&gt;is sparse&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;me2&quot;&gt;&lt;span class=&quot;search_hit&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
			<pubDate>Tue, 26 Jan 2010 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/using-silverstripe-url-parameters-to-display-dataobjects-on-a-page/</guid>
		</item>
		
		<item>
			<title>Embedding YouTube or Vimeo inside your Text-Content using a custom 'TextParser'</title>
			<link>http://www.ssbits.com/embedding-youtube-or-vimeo-inside-your-text-content-using-a-custom-textparser/</link>
			<description>&lt;p&gt;Embedded videos from platforms like YouTube or Vimeo are very common nowadays. To embed such a video in the SilverStripe CMS, you would have to enter the HTML-Code directly in the HTML-Source of the TinyMCE Editor.&lt;/p&gt;
&lt;p&gt;This is a cumbersome and error prone process and may mess with your layout. Wouldn't it be nice if you could type: &lt;strong&gt;$YouTube(&lt;em&gt;&amp;lt;videoId&amp;gt;&lt;/em&gt;)&lt;/strong&gt; anywhere in your text and then automatically get the video embedded there?&lt;br /&gt;This snippet makes use of a custom &lt;strong&gt;TextParser&lt;/strong&gt; to achieve that goal.&lt;/p&gt;
&lt;h3&gt;The VideoParser&lt;/h3&gt;
&lt;p&gt;Save the following code in a file named &lt;strong&gt;mysite/code/parsers/VideoParser.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class VideoParser extends TextParser
{
	public static $hooks = array(
		'YouTube'	=&amp;gt; array(__CLASS__, 'embedYoutube'),
		'Vimeo'		=&amp;gt; array(__CLASS__, 'embedVimeo')
	);
	
	public function parse(){
		$regex = '{\$('. implode('|', array_keys(self::$hooks)) . ')\(([^\)]+)\)}i';
		return preg_replace_callback($regex, array(&amp;amp;$this, 'replace'), $this-&amp;gt;content);
	}
	
	protected function replace(&amp;amp;$in){
		if(array_key_exists($in[1], self::$hooks)){
			return call_user_func_array(self::$hooks[$in[1]], explode(',', $in[2]));
		}
	}
	
	protected static function embedYoutube($videoId, $width = 400, $height = 300){
		return '
			&amp;lt;object type=&quot;application/x-shockwave-flash&quot;
			data=&quot;http://www.youtube.com/v/'. $videoId .'&quot; width=&quot;'. $width .'&quot; height=&quot;'. $height .'&quot;&amp;gt;
			&amp;lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/'. $videoId .'&quot; /&amp;gt;
			&amp;lt;param
name=&quot;allowfullscreen&quot; value=&quot;true&quot; /&amp;gt;&amp;lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;/&amp;gt;
			&amp;lt;/object&amp;gt;';
	}
	
	protected static function embedVimeo($videoId, $width = 400, $height = 300){
		return '

			&amp;lt;object type=&quot;application/x-shockwave-flash&quot;
			data=&quot;http://vimeo.com/moogaloop.swf?clip_id='.$videoId.'&amp;amp;server=vimeo.com&amp;amp;fullscreen=1&quot;
			width=&quot;'. $width .'&quot; height=&quot;'. $height .'&quot;&amp;gt;
			&amp;lt;param name=&quot;movie&quot;
value=&quot;http://vimeo.com/moogaloop.swf?clip_id='.$videoId.'&amp;amp;server=vimeo.com&amp;amp;fullscreen=1&quot;
/&amp;gt;
			&amp;lt;param name=&quot;allowfullscreen&quot; value=&quot;true&quot; /&amp;gt;&amp;lt;span  name=&quot;allowscriptaccess&quot;
value=&quot;always&quot;/&amp;gt;
			&amp;lt;/object&amp;gt;';
	}
}&lt;/textarea&gt; &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This class parses your text upon request and adds the capability to embed Vimeo or YouTube videos with the use of a simple variable.&lt;/p&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;p&gt;To make use of the new parser, you have to invoke it on your content. This can be achieved by writing:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;$Content.Parse(VideoParser)&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;in your Template (instead of just &lt;strong&gt;$Content&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;After you did so, you can make use of the new functionality. Log in to the CMS and enter the following in your Content-Area: &lt;strong&gt;$YouTube(oHg5SJYRHA0,500,400)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you visit the page, you should see the YouTube Video with ID &lt;strong&gt;oHg5SJYRHA0 &lt;/strong&gt;and a dimension of 500x400 pixels (you can also skip the dimensions, they will default to 400x300 pixels).&lt;/p&gt;
&lt;p&gt;The same works for Vimeo videos. This code: &lt;strong&gt;$Vimeo(2619976) &lt;/strong&gt;will embed the video at &lt;a href=&quot;http://vimeo.com/2619976&quot;&gt;http://vimeo.com/2619976&lt;/a&gt;. You're free to specify width and height as second and third parameters.&lt;/p&gt;
&lt;h3&gt;Known issues&lt;br /&gt;&lt;/h3&gt;
&lt;p&gt;Somehow, the parsing breaks the Login page, so if you plan to use the parser for all pages (by putting &lt;strong&gt;$Content.Parse(VideoParser)&lt;/strong&gt; in the &lt;strong&gt;Page.ss&lt;/strong&gt; template), then you should create a custom template for the login page (&lt;strong&gt;Security_login.ss&lt;/strong&gt;) and not use the parser there.&lt;/p&gt;
&lt;h3&gt;Custom replacements&lt;br /&gt;&lt;/h3&gt;
&lt;p&gt;You can register custom replacement hooks, by registering new handlers in &lt;strong&gt;mysite/_config.php &lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;VideoParsers::$hooks['MyHook'] = array('Page', 'myReplacer');&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your Page class would need a static method with a signature like &lt;strong&gt;myReplacer(arg1, arg2, arg3,...,argN)&lt;/strong&gt; and could then be invoked by writing: &lt;strong&gt;$MyHook(&lt;/strong&gt;&lt;strong&gt;arg1, arg2, arg3,...,argN&lt;/strong&gt;&lt;strong&gt;)&lt;/strong&gt; in your content area! So this class could in fact be used for much more than just embedding videos.&lt;/p&gt;</description>
			<pubDate>Thu, 10 Dec 2009 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/embedding-youtube-or-vimeo-inside-your-text-content-using-a-custom-textparser/</guid>
		</item>
		
		<item>
			<title>Embed flash content using SWFObject</title>
			<link>http://www.ssbits.com/embed-flash-content-using-swfobject/</link>
			<description>&lt;p&gt;There are numerous ways to embed flash content into your SilverStripe Website. A commonly used and very flexible method to embed flash is provided by the &lt;a title=&quot;SWFObject project page&quot; href=&quot;http://code.google.com/p/swfobject/&quot;&gt;SWFObject&lt;/a&gt; script. In this snippet I'll show you how to create a custom &lt;strong&gt;FlashPage&lt;/strong&gt; that will contain flash content, embedded using SWFObject.&lt;/p&gt;
&lt;h3&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Download and unpack swfobject 2.2 from &lt;a href=&quot;http://code.google.com/p/swfobject/&quot;&gt;http://code.google.com/p/swfobject/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;copy &lt;strong&gt;swfobject.js&lt;/strong&gt; and &lt;strong&gt;expressInstall.swf&lt;/strong&gt; from the swfobject folder to your mysite folder. I copied the files into &lt;strong&gt;mysite/javascript/lib&lt;/strong&gt;, so this is what I'm gonna use in this example&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The FlashPage Class&lt;/h3&gt;
&lt;p&gt;We're going to create a custom class for our Flash-Content-Page. Save the following code as &lt;strong&gt;mysite/code/FlashPage.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class FlashPage extends Page 
{
	public static $db = array(
		'AlternateContent' =&amp;gt; 'HTMLText'
	);
	
	public static $has_one = array(
		'FlashFile'	=&amp;gt; 'File'
	);
	
	public function getCMSFields(){
		$fields = parent::getCMSFields();
		
		// create a file upload field that only allows swf to be uploaded
		$fileUpload = new FileIFrameField('FlashFile', 'Flash file (swf)');
		$fileUpload-&amp;gt;setAllowedExtensions(array('swf'));
		
		$fields-&amp;gt;addFieldsToTab('Root.Content.Flash', array(
			$fileUpload,
			new HtmlEditorField('AlternateContent', 'Flash replacement text', 20)
		));
		
		return $fields;
	}
}
class FlashPage_Controller extends Page_Controller
{
	public function init(){
		parent::init();
		// require SWF Object script
		Requirements::javascript('mysite/javascript/lib/swfobject.js');
	}
}&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After doing so, run &lt;strong&gt;/dev/build&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our FlashPage adds a new Tab named &quot;Flash&quot; to the CMS Content Tab. In this tab you'll be able to upload a swf file and provide an alternative content for all the people who don't have flash installed (iPhone comes to mind).&lt;/p&gt;
&lt;h3&gt;The template&lt;br /&gt;&lt;/h3&gt;
&lt;p&gt;All that's left now is the template part. Create a new template (or layout) named &lt;strong&gt;FlashPage.ss&lt;/strong&gt; and add the following code to it:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;% if FlashFile %&amp;gt;
&amp;lt;div id=&quot;FlashContainer&quot;&amp;gt;
	$AlternateContent
&amp;lt;/div&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
/* &amp;lt;![CDATA[ */
swfobject.embedSWF(
	&quot;$FlashFile.URL&quot;, 
	&quot;FlashContainer&quot;, 
	&quot;400&quot;, &quot;300&quot;, 
	&quot;9.0.0&quot;, 
	&quot;mysite/javascript/lib/expressInstall.swf&quot;
);
/* ]]&amp;gt; */
&amp;lt;/script&amp;gt;
&amp;lt;% end_if %&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;The above part just outputs the flash content, so feel free to add &lt;strong&gt;&amp;lt;h1&amp;gt;$Title&amp;lt;/h1&amp;gt;&lt;/strong&gt;, &lt;strong&gt;$Content&lt;/strong&gt; or any other of the markup you desire.&lt;/p&gt;
&lt;p&gt;Here's a short summary what we do with that template code: &lt;br /&gt;First we check if there's a FlashFile available (&lt;strong&gt;&amp;lt;% if FlashFile %&amp;gt;&lt;/strong&gt;). If not, we can skip the whole flash embed stuff. &lt;br /&gt;The &lt;strong&gt;&amp;lt;div id=&quot;FlashContainer&quot;&amp;gt;&lt;/strong&gt; container holds the alternative content, and will be replaced with the flash content &lt;em&gt;if&lt;/em&gt; the user has the required flash plugin installed. &lt;br /&gt;What follows at the end is the script call to swfobject. SWFObject is highly customizable. You can add parameters to flash, or specify special embed tags (like &lt;strong&gt;wmode&lt;/strong&gt; or &lt;strong&gt;bgcolor&lt;/strong&gt;). There's a good description of all possible parameters on the SWFObject website: &lt;br /&gt;&lt;a href=&quot;http://code.google.com/p/swfobject/wiki/documentation#STEP_3:_Embed_your_SWF_with&quot;&gt;http://code.google.com/p/swfobject/wiki/documentation#STEP_3:_Embed_your_SWF_with&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The size of the flash content (width, height) is now defined in the template code. Of course you could also replace these values with variables that can be customized inside the CMS. Give it a try!&lt;/p&gt;</description>
			<pubDate>Thu, 03 Dec 2009 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/embed-flash-content-using-swfobject/</guid>
		</item>
		
		<item>
			<title>Using jQuery for form validation</title>
			<link>http://www.ssbits.com/using-jquery-for-form-validation/</link>
			<description>&lt;h2&gt;&lt;a href=&quot;http://www.ssbits.com/jquery-validation-demo/&quot; target=&quot;_blank&quot;&gt;Demo&lt;/a&gt;&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note.&lt;/strong&gt; &lt;em&gt;This tutorial continues from the &lt;a href=&quot;http://www.ssbits.com/creating-a-simple-contact-form/&quot;&gt;Creating a Simple Contact Form&lt;/a&gt; tutorial, if you are not sure about form creation is SilverStripe, read that first.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you may have noticed, SilverStripes built in form validation is based on Prototype and is pretty limited. It also means that even though you might be using jQuery for other things on your site, you will still need to load prototype.js and all the associated files that SilverStripe requires for it's built in JavaScript form validation.&lt;/p&gt;
&lt;p&gt;With this in mind using a jQuery validator, such as the excellent &lt;strong&gt;Validate&lt;/strong&gt; plugin has some obvious benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate multiple forms on a single page&lt;/li&gt;
&lt;li&gt;Customize error messages&lt;/li&gt;
&lt;li&gt;More control over inline error styling&lt;/li&gt;
&lt;li&gt;Remove the need for loading extra JS files, such as Prototype.js and Behavior.js etc. (more than 90KB in total)&lt;/li&gt;
&lt;li&gt;Extended validation options as well as the ability to create custom regex validation rules.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So now that you are (most likely) convinced about the benefits of using jQuery validation let's get started.&lt;/p&gt;
&lt;h2&gt;Part 1. Removing SilverStripe's built in JS Validator&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;The first thing we need to do is disable SilverStripe's Prototype validation. We can do that with a single line in our &lt;strong&gt;ContactPage_Controller&lt;/strong&gt;'s &lt;strong&gt;init()&lt;/strong&gt; function:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class ContactPage_Controller extends Page_controller{
   
   function init() {
	parent::init();
		
      	Validator::set_javascript_validation_handler('none'); 		
   }

.
.
.

}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Now if you try the form you will notice that the live Javascript validation is no longer present and more importantly all of the files associated with form validation in SilverStripe (Prototype.js etc) and the messy inline Javascript at the bottom of the page's source is also no where to be seen. Great!&lt;/p&gt;
&lt;h2&gt;Part 2. Including the jQuery Validator files&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;Now let's go about adding in our sexy jQuery validation. First things first we need to include both the jQuery source file and the jQuery.Validate plugin. You may already be including jQuery in your Page.php init() function for other uses, if so just skip the first include.&lt;strong&gt;&lt;br /&gt;Note.&lt;em&gt; &lt;/em&gt;&lt;/strong&gt;&lt;em&gt;I tend to include jQuery from the google API index as it is often already cached by the users browser, but you can include the one inside the SilverStripe JSparty folder if you prefer. However I could not get the SilverStripe included jquery.validate.min.js to work with the lastest jQuery release, so again I used a hosted version.&lt;br /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So now your &lt;strong&gt;init()&lt;/strong&gt; function will look like this:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;function init() {
	parent::init();
		
      	Validator::set_javascript_validation_handler('none'); 		
	Requirements::javascript(&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js&quot;);
	Requirements::javascript(&quot;http://ajax.microsoft.com/ajax/jquery.validate/1.5.5/jquery.validate.min.js&quot;);

}&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Part 3. Applying the Validation to the form&lt;/h2&gt;
&lt;p&gt;Now that we have our jQuery files included we can go about validating our form fields. Due to the great design of the plugin, this is extremely easy. For brevity sake, we are going to include this script by adding a&lt;strong&gt; customScript&lt;/strong&gt; requirement to our &lt;strong&gt;init()&lt;/strong&gt; function, but you could just as easily place it in a seperate&lt;strong&gt; .js&lt;/strong&gt; file and include it in the same was as the files above. So now our innit() function looks like this:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;function init() {
	parent::init();
		
      	Validator::set_javascript_validation_handler('none'); 		
	Requirements::javascript(&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js&quot;);
	Requirements::javascript(&quot;jsparty/jquery/plugins/validate/jquery.validate.min.js&quot;);
		
	Requirements::customScript('
		jQuery(document).ready(function() {
			jQuery(&quot;#Form_ContactForm&quot;).validate({
				rules: {
					Name: &quot;required&quot;,
					Email: {
						required: true,
						email: true
					},
					Comments: {
						required: true,
						minlength: 20
					}
				},
				messages: {
					Name: &quot;I would really love to know your name&quot;,
					Email: &quot;It is quite important that you give me your actual email address so I can send you messages&quot;,
					Comments: &quot;What are you thinking? Tell me&quot;
				}
			});
		});
	');
		
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Although this may look a little scary, it's really very simple. We are simply calling the &lt;strong&gt;jQuery.validate() &lt;/strong&gt;function on &lt;strong&gt;#Form_ContactForm &lt;/strong&gt;which&lt;strong&gt; &lt;/strong&gt;is the&lt;strong&gt; id&lt;/strong&gt; of the &lt;strong&gt;&amp;lt;form&amp;gt; &lt;/strong&gt;tag. We then define the rules by passing in a series of options for each field, following the same field names as defined when creating the fields. For &lt;strong&gt;Name&lt;/strong&gt; we simply tell the validator that it is required, while for &lt;strong&gt;Email&lt;/strong&gt; we tell it that it's required and is of type 'Email'. For the&lt;strong&gt; Comments&lt;/strong&gt; field we also specify a minimum length to make sure people write something of significance.&lt;/p&gt;
&lt;p&gt;We then define some suitably creepy error messages that will be displayed if validation fails.&lt;/p&gt;
&lt;p&gt;Done! You now have a jQuery Validated form. Best of all the server side
SilverStripe validation still works, so you have something to fall back
on if Javascript is switched off.&lt;/p&gt;
&lt;h2&gt;Styling the error messages&lt;/h2&gt;
&lt;p&gt;As with the rest of the plugin, styling errors is super simple. Here are a couple of css rules to get you going:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;label.error{
	border: 1px solid #aaa;
	background: #aa2222;
	color: #fff;
	padding: 5px;
	margin: 0;
	width: 100%;
}
input.error{
	border: 1px solid  #aa2222;
}&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Using multiple forms on a single page&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;This validation will work on as many forms as you like per page, but in order for it to work you need to call the &lt;strong&gt;jQuery.Validate()&lt;/strong&gt; function and pass it's options for&lt;em&gt; &lt;strong&gt;each&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt; &lt;/strong&gt;form. So copy everything inside the&lt;strong&gt; jQuery(document).ready(function(){&lt;/strong&gt; and change the id that is passed into the&lt;strong&gt; validate()&lt;/strong&gt; function.&lt;/p&gt;
&lt;p&gt;If you are using AJAX for form submission, will will also need to give each field a unique name. AJAX form submission....hmm I feel another tutorial coming on :)&lt;/p&gt;</description>
			<pubDate>Fri, 13 Nov 2009 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/using-jquery-for-form-validation/</guid>
		</item>
		
		<item>
			<title>Web 2.0 Mirrored Images</title>
			<link>http://www.ssbits.com/web-2-0-mirrored-images/</link>
			<description>&lt;p&gt;Ever wanted to create the so-called 'web 2.0' mirrored images as you can see on &lt;a href=&quot;http://www.dio5.com/portfolio/&quot;&gt;http://www.dio5.com/portfolio/&lt;/a&gt;, without having to manually rotate and proces the image?             Then this one is for you.&lt;/p&gt;
&lt;p&gt;I attached a fully working zip-package, but let's run through some of the code nonetheless.&lt;/p&gt;
&lt;p&gt;When it comes to images the Image and GD class are the ones we need to tackle.             Create a file in your mysite/code/ folder called ImageDecorator.php and add in the next code:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class ImageDecorator extends DataObjectDecorator
{
    function getMirroredImage($width, $height)
    {
        if ($this-&amp;gt;owner-&amp;gt;ID &amp;amp;&amp;amp; $this-&amp;gt;owner-&amp;gt;Filename &amp;amp;&amp;amp; Director::fileExists($this-&amp;gt;owner-&amp;gt;Filename))
        {
            $cacheFile = $this-&amp;gt;owner-&amp;gt;cacheFilename(&quot;MirroredImage&quot;, $width, $height);

            if (!file_exists(&quot;../&quot;.$cacheFile) || isset ($_GET['flush']))
            {
                $this-&amp;gt;generateMirroredImage($width, $height);
            }

            return new Image_Cached($cacheFile);
        }
    }

    function generateMirroredImage($width, $height)
    {
        $cacheFile = $this-&amp;gt;owner-&amp;gt;cacheFilename(&quot;MirroredImage&quot;, $width, $height);

        $gd = new MyGD(&quot;../&quot;.$this-&amp;gt;owner-&amp;gt;Filename);
        if ($gd-&amp;gt;hasGD())
        {
            $gd = $gd-&amp;gt;mirroredImage($width, $height);
            if ($gd)
            {
                $gd-&amp;gt;writeTo(&quot;../&quot;.$cacheFile);
            }
        }
    }
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Create another file called MyGD.php in the same folder:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class MyGD extends GD
{
    public function mirroredImage($width, $height)
    {
        $newGD = imagecreatetruecolor($this-&amp;gt;width, $this-&amp;gt;height);
        $bg = imagecolorallocatealpha($newGD, 255, 255, 255, 127);
        imagefill($newGD, 0, 0, $bg);
        $dst_x = 0;
        $src_x = 0;

        $coordinate = ($this-&amp;gt;height-1);

        foreach (range($this-&amp;gt;height, 0) as $range)
        {
            $src_y = $range;
            $dst_y = $coordinate-$range;

            imagecopy($newGD, $this-&amp;gt;gd, $dst_x, $dst_y, $src_x, $src_y, $this-&amp;gt;width, 1);
        }

        $output = new GD();
        $output-&amp;gt;setGD($newGD);

        $output = $output-&amp;gt;croppedResize($width, $height);
        return $output;
    }
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;The previous code may look a bit daunting but it's basically just taking the current picture and mirrors it, after which we do a crop on it so you can add in a width &amp;amp; height.&lt;/p&gt;
&lt;p&gt;To get the Image Decorator working, we'll need to add this line to our _config.php file:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;DataObject::add_extension('Image', 'ImageDecorator');&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;The interesting part is how you can use it in your templates: &lt;strong&gt;$ExampleImage.getMirroredImage(300,200)&lt;/strong&gt; will give you a mirrored image with a width of 300px and height of 200px.&lt;/p&gt;
&lt;p&gt;Combine this with some clever use of css and a png gradient and you will be able to achieve what I did on http://www.dio5.com/portfolio/.&lt;/p&gt;
&lt;p&gt;For those of you wanting to test this out, I provided a simple &lt;strong&gt;&lt;a href=&quot;http://www.dio5.com/plugins/ss-mirrors/mirrors.zip&quot;&gt;zip-package&lt;/a&gt;&lt;/strong&gt; which contains a folder called 'mirrors'. Just unzip this in your root SS-folder, do a dev/build/ and you'll have an 'Example Page'-type which allows you to set an image on which this effect is demonstrated.             It basically contains this html-snippet:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;photo&quot; style=&quot;background:url(http://www.ssbits.com/&amp;lt;% control ExampleImage %&amp;gt;
&amp;lt;% control getMirroredImage(300,200) %&amp;gt;$URL&amp;lt;% end_control %&amp;gt;
&amp;lt;% end_control %&amp;gt;) no-repeat left bottom&quot;&amp;gt;
    &amp;lt;div&amp;gt;
	&amp;lt;img src=&quot;&amp;lt;% control ExampleImage %&amp;gt;
	&amp;lt;% control CroppedImage(300,200) %&amp;gt;$URL&amp;lt;% end_control %&amp;gt;
	&amp;lt;% end_control %&amp;gt;&quot; alt=&quot;$Title.ATT&quot; /&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;With this css:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;.photo div{
	position:relative; 
	width:300px; 
	height:401px; 
	background:url(http://www.ssbits.com/../images/overlay.png) repeat-x left 201px; 
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Here we are adding the mirrored image as a background image on the outer container div of the 'normal' image. On the inner container div we add a black to transparent png-gradient and position the whole thing with css.&lt;/p&gt;
&lt;p&gt;You probably want to experiment with changing the png-overlay image size &amp;amp; color in the zip. For a reflection on white you'll need to replace it with a white to transparent one, instead of a black one.&lt;/p&gt;
&lt;p&gt;Hope this little snippet inspired you to do great things! Do not hesitate to ask for help or improve this where possible.&lt;/p&gt;
&lt;p&gt;Note: I'm not adding support for IE6 here.&lt;/p&gt;</description>
			<pubDate>Mon, 02 Nov 2009 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/web-2-0-mirrored-images/</guid>
		</item>
		
		<item>
			<title>Detecting Mobile Browsers to Re-Theme Your Site</title>
			<link>http://www.ssbits.com/detecting-mobile-browsers-to-re-theme-your-site/</link>
			<description>&lt;p&gt;With the proliferation of Smart Phones and other non-traditional-computer ways to browse the internet, wouldn&amp;rsquo;t it be great if you could provide a custom browsing experience for different users?&lt;/p&gt;
&lt;p&gt;You could re-template your site using the sub-sites module, but it might not be stable/functional enough for every one yet. So I&amp;rsquo;ll show you how to use Anthony Hand&amp;rsquo;s great &lt;a href=&quot;http://www.ssbits.com/&amp;rdquo;http://www.hand-interactive.com/resources/detect-mobile-php.htm&amp;rdquo;&quot;&gt;User Agent Detection Class&lt;/a&gt; to re-template your site based on the User Agent of your visitor.&lt;/p&gt;
&lt;p&gt;First things first get yourself a copy of the Class from the link above. He also has a lot of great resources on designing for mobile browsers.&lt;/p&gt;
&lt;p&gt;Before we can actually use the calls we need to make some modification. Because of the way SilverStripe&amp;rsquo;s Manifest builder handles filenames and class names we need to change name of the file, remove the underscores from the name of the class. To sum up:&lt;br /&gt; &lt;strong&gt;mdetect_php.txt&lt;/strong&gt; becomes &lt;strong&gt;uagentinfo.php&lt;/strong&gt;&lt;br /&gt; and in the actual file (line 53):&lt;br /&gt; &lt;strong&gt;class uagent_info&lt;/strong&gt; becomes &lt;strong&gt;class uagentinfo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You now add this file to your mysite/code directory and do a dev/build.&lt;/p&gt;
&lt;p&gt;Now that SilverStripe knows about the Class we need to call it from the init() function in your mysite/code/page.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;$iPhoneTheme='tutorial';
$blackBerryTheme='tutorial';
$wapTheme='tutorial';

//Check if Mobile Phone Is Already Set
if(isset($_SESSION['mobilePhone'])) {
	switch ($_SESSION['mobilePhone']){
		case &quot;iphone&quot;:
			SSViewer::set_theme($iPhoneTheme);
			break;
		case &quot;BlackBerry&quot;:
			SSViewer::set_theme($blackBerryTheme);
			break;
		case &quot;WAP&quot;:
			SSViewer::set_theme($wapTheme);
			break;
		case &quot;None&quot;:
			break;
	}
}
else
{
	//Detect Mobile Phone Tier
	$uagentobj = new uagentinfo();
	
	$mobilePhone = $uagentobj-&amp;gt;DetectTierIphone();
	
	if ($mobilePhone == 1){
		SSViewer::set_theme($iPhoneTheme);
		$_SESSION['mobilePhone']=&quot;iPhone&quot;;
	}
	else
	{
		$mobilePhone = $uagentobj-&amp;gt;DetectTierSmartphones();
		if ($mobilePhone == 1)
		{
			SSViewer::set_theme($blackBerryTheme);
			$_SESSION['mobilePhone']=&quot;BlackBerry&quot;;
		}
		else
		{
			$mobilePhone = $uagentobj-&amp;gt;DetectTierOtherPhones();
			if ($mobilePhone == 1)
			{
				SSViewer::set_theme($wapTheme);
				$_SESSION['mobilePhone']=&quot;WAP&quot;;
			}
			else
			{
				$_SESSION['mobilePhone']=&quot;None&quot;;
			}
		}
	}
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Lets Step through This&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;$iPhoneTheme='tutorial';
$blackBerryTheme='tutorial';
$wapTheme='tutorial';&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;In the first part of the code we set variables for the themes we want to use for the three main tiers of mobile browser: iPhone, BlackBerry and Plain old WAP. Right now we&amp;rsquo;re just using tutorial but you can set these to whatever your want.&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;if(isset($_SESSION['mobilePhone'])) {
	switch ($_SESSION['mobilePhone']){
		case &quot;iphone&quot;:
			SSViewer::set_theme($iPhoneTheme);
			break;
		case &quot;BlackBerry&quot;:
			SSViewer::set_theme($blackBerryTheme);
			break;
		case &quot;WAP&quot;:
			SSViewer::set_theme($wapTheme);
			break;
		case &quot;None&quot;:
			break;
	}
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;n this part we check to see if the mobilePhone session variable is already set, and if it is we use a switch function to use this variable to set the theme with the SSViewer::set_theme() function, dropping in the corresponding theme name variable in each case. If the session variable isn&amp;rsquo;t set we move on to actually detecting the user agent.&lt;/p&gt;
&lt;p&gt;Here we create a new instance of the uagentinfo class and save it to $uagentobj. we then Take that object and run the first class detection on it, iphone. If the browser is an iPhone its saves that value to the $_SESSION[&amp;lsquo;mobilePhone&amp;rsquo;] variable. If not it runs the next tier detection and so on.&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;else
{
	//Detect Mobile Phone Tier
	$uagentobj = new uagentinfo();
	
	$mobilePhone = $uagentobj-&amp;gt;DetectTierIphone();
	
	if ($mobilePhone == 1){
		SSViewer::set_theme($iPhoneTheme);
		$_SESSION['mobilePhone']=&quot;iPhone&quot;;
	}
	else
	{
		$mobilePhone = $uagentobj-&amp;gt;DetectTierSmartphones();
		if ($mobilePhone == 1)
		{
			SSViewer::set_theme($blackBerryTheme);
			$_SESSION['mobilePhone']=&quot;BlackBerry&quot;;
		}
		else
		{
			$mobilePhone = $uagentobj-&amp;gt;DetectTierOtherPhones();
			if ($mobilePhone == 1)
			{
				SSViewer::set_theme($wapTheme);
				$_SESSION['mobilePhone']=&quot;WAP&quot;;
			}
			else
			{
				$_SESSION['mobilePhone']=&quot;None&quot;;
			}
		}
	}
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;This is great, but what if someone is using an iPhone or other similar device whose browser can support a non-mobile site with all the bells and whistles. We need to provide the user with a way to override the detection.&lt;/p&gt;
&lt;p&gt;In you page_controller add the following function:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;//Overide Mobile Theme
function MobileOveride(){
	$_SESSION['mobilePhone']=&quot;None&quot;;
	Director::redirect(Director::baseURL(). $this-&amp;gt;URLSegment);
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;This function provides us a way to manually set the $_SESSION[&amp;lsquo;mobilePhone&amp;rsquo;] variable to &amp;ldquo;None&amp;rdquo; so that the first CHECK function won&amp;rsquo;t move on to detecting the user agent. It then forces the browser to reload the current page. All we have to do is add the following to mobile phone templates:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;a href=&quot;$URLSegment/MobileOveride&quot; &amp;gt;View Full Site&amp;lt;/a&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Thats it, now all you have to do design you mobile templates and you have functioning mobile version of your site. The class is great as it contains methods for detecting many more user agent types including video game platforms and other hand held devices. You could also expand this to use sub-domains to set the $_SESSION[&amp;lsquo;mobilePhone&amp;rsquo;] variable.&lt;/p&gt;
&lt;p&gt;Enjoy&lt;/p&gt;</description>
			<pubDate>Tue, 06 Oct 2009 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/detecting-mobile-browsers-to-re-theme-your-site/</guid>
		</item>
		
		<item>
			<title>Adding a CMS action the (slightly) hacky way</title>
			<link>http://www.ssbits.com/adding-a-cms-action-the-slightly-hacky-way/</link>
			<description>&lt;h2&gt;The bit you probably know&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;If like me you cannot figure out how on earth to use the &lt;strong&gt;updateCMSActions() &lt;/strong&gt;function
in a decorator and you also can't get the &lt;strong&gt;getCMSActions()&lt;/strong&gt; function to
find your custom method inside the controller then here is a way to make it work, which
although certainly not the 'correct' way to do it, still does not require
any editing of the core files and is not particularly messy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note.&lt;/strong&gt; If anyone from SilverStripe is reading this (or anyone else who knows how these functions work), please oh please write some 'proper' documentation for us. I/we will be very grateful :) &lt;/p&gt;
&lt;p&gt;So back to my way of doing this, first thing we need to do is add the form action to our page using the &lt;strong&gt;getCMSActions()&lt;/strong&gt; function which many of you will have already had (probably frustrating) experience of. So inside your page model we add this:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;	function getCMSActions(){
		
		$actions = parent::getCMSActions();
		
		$Action = new FormAction(
			   &quot;doAction&quot;,
			   &quot;Do something different&quot;
			);
		$actions-&amp;gt;push($Action);
		
		return $actions;
	} &lt;/textarea&gt; &lt;/p&gt;
&lt;h2&gt;Decorating CMSMain&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;Now we have our button you might have though adding the &lt;strong&gt;doAction()&lt;/strong&gt; function would be a simple case of sticking it into our controller. Unfortunately not, for some reason this will give you an error, so instead we are going to add our function to the same place as the other CMS actions:&amp;nbsp; inside &lt;strong&gt;CMSMain&lt;/strong&gt;. However to avoid having to edit the core files we will use the &lt;strong&gt;LeftAndMainDecorator&lt;/strong&gt; to do this. This works in exactly the same way as the &lt;strong&gt;DataObjectDecorator&lt;/strong&gt; but, you guessed it, decorates &lt;strong&gt;LeftAndMain&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;So create a new file inside &lt;strong&gt;mysite/code&lt;/strong&gt; called&lt;strong&gt; CMSActionDecorator.php&lt;/strong&gt; and put this code in it:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class CMSActionDecorator extends LeftAndMainDecorator {
	
	function doAction(){
		FormResponse::status_message(sprintf('All good!'),'good');
		return FormResponse::respond();
	}	
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;So all our&lt;strong&gt; doAction()&lt;/strong&gt; function is going to do when run is show a green 'All good!' message at the bottom of the page. You can however put anything you like in here. &lt;/p&gt;
&lt;h2&gt;Something a little more useful&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;At this point you may be wondering how much use this is without having the currently selected pages ID. Well by adding &lt;strong&gt;$id = (int)$_REQUEST['ID'];&amp;nbsp; &lt;/strong&gt;to the top of this function we can then use &lt;strong&gt;DataObject::get_by_id() &lt;/strong&gt;or similar to get the current page and work on it. For example say I wanted to create a button that deleted all the DataObjects attached to the current page I could change my &lt;strong&gt;doAction() &lt;/strong&gt;function to something like this:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;	function doAction(){
		
		$id = (int)$_REQUEST['ID'];	
		
		$DataObjects = DataObject::get('SomeObject', 'PageID=' . $id);
		
		foreach($DataObjects as $DataObject){
			$DataObject-&amp;gt;Delete();
		}
		
		FormResponse::status_message(sprintf('Deleted all objects' ),'good');
		return FormResponse::respond();
	}	&lt;/textarea&gt; &lt;/p&gt;
&lt;h2&gt;Config settings&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;Finally we need to tell SilverStripe that we have extended &lt;strong&gt;CMSMain&lt;/strong&gt; by adding this line to our &lt;strong&gt;mysite/_config.php&lt;/strong&gt;:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;Object::add_extension('CMSMain', 'CMSActionDecorator');&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;And there you have it, the hacky way to add form actions to your CMS pages. Wasn't all that bad was is? :)&lt;/p&gt;</description>
			<pubDate>Tue, 01 Sep 2009 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/adding-a-cms-action-the-slightly-hacky-way/</guid>
		</item>
		
		<item>
			<title>Creating a Simple Contact Form</title>
			<link>http://www.ssbits.com/creating-a-simple-contact-form/</link>
			<description>&lt;h2&gt;Preperation&lt;/h2&gt;
&lt;p&gt;We will be using 3 files in this tutorial, our Contact form page type&lt;strong&gt;, ContactPage.php&lt;/strong&gt; and it's layout template &lt;strong&gt;ContactPage.ss&lt;/strong&gt; as well as the email template &lt;strong&gt;ContactEmail.ss&lt;/strong&gt;. Let's create these files and their initial code:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/ContactForm.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class ContactPage extends Page{
}
class ContactPage_Controller extends Page_Controller{
}
&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/yourtheme/templates/Layout/ContactPage.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;typography&quot;&amp;gt;
	
	&amp;lt;h2&amp;gt;$Title&amp;lt;/h2&amp;gt;
	
&amp;lt;/div&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/yourtheme/templates/ContactEmail.ss&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&amp;gt;
&amp;lt;html&amp;gt;
	&amp;lt;head&amp;gt;
	&amp;lt;/head&amp;gt;
	&amp;lt;body&amp;gt;
	
	&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/textarea&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part I: Creating the Page Model&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first thing we need to do is setup the ContactPage model with database fields (&lt;strong&gt;$db&lt;/strong&gt;) and CMS fields. For this tutorial we will keep it simple and add 2 fields, a &lt;strong&gt;MailTo&lt;/strong&gt; field for the address to mail the contact form to and also a field for the text on submission of the form, which we'll call &lt;strong&gt;SubmitText.&lt;/strong&gt; So add the following to your ContactPage class:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/ContactForm.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class ContactPage extends Page
{
	static $db = array(
		'Mailto' =&amp;gt; 'Varchar(100)',
		'SubmitText' =&amp;gt; 'Text'
	);
	
	function getCMSFields() {
		$fields = parent::getCMSFields();
	
		$fields-&amp;gt;addFieldToTab(&quot;Root.Content.OnSubmission&quot;, new TextField('Mailto', 'Email submissions to'));	
		$fields-&amp;gt;addFieldToTab(&quot;Root.Content.OnSubmission&quot;, new TextareaField('SubmitText', 'Text on Submission'));		
	
		return $fields;	
	}
}
.
.
.&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Once done, flush your database by visiting &lt;strong&gt;yoursite.com/dev/build&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Part II: Defining the Contact Form&lt;/h2&gt;
&lt;p&gt;Now let's actually create the fields for our contact form. Again we will keep it simple and add a &lt;strong&gt;Name&lt;/strong&gt;, &lt;strong&gt;Email&lt;/strong&gt; and &lt;strong&gt;Comments&lt;/strong&gt; field, but you can add as many fields as you like without making the logic more complicated. To get SilverStripe to generate the form we need a function on our &lt;strong&gt;ContactPage_Controller&lt;/strong&gt; class which will return the fields to the page template. We will call this function &lt;strong&gt;ContactForm()&lt;/strong&gt;. A blindingly original name I know.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/ContactForm.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;.
.
.
class ContactPage_Controller extends Page_Controller
{
	function ContactForm() {
      	    // Create fields		  
	    $fields = new FieldSet(
		    new TextField('Name', 'Name*'),
			new EmailField('Email', 'Email*'),
			new TextareaField('Comments','Comments*')
		);
		 	
	    // Create action
	    $actions = new FieldSet(
	    	new FormAction('SendContactForm', 'Send')
	    );
		
	    // Create Validators
	    $validator = new RequiredFields('Name', 'Email', 'Comments');
		
	    return new Form($this, 'ContactForm', $fields, $actions, $validator);
	}
}&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;The first thing we do is define the variable &lt;strong&gt;$fields&lt;/strong&gt; which contains a &lt;strong&gt;FieldSet&lt;/strong&gt; with our 3 fields in them. &lt;/p&gt;
&lt;p&gt;Next we define the &lt;strong&gt;$action&lt;/strong&gt; variable which creates a form action&amp;nbsp; (i.e. the submit button) and assigns it the &lt;strong&gt;SendContactForm&lt;/strong&gt; function which we will be defining in the next step. The second argument that the &lt;strong&gt;FormAction() &lt;/strong&gt;function&lt;strong&gt; &lt;/strong&gt;takes is the label for the submit button, in this case we are labeling it with &lt;strong&gt;Send&lt;/strong&gt; but you can label yours with whatever you like.&lt;/p&gt;
&lt;p&gt;We then set the required fields for the form, in this case all of them and store them in the &lt;strong&gt;$validator &lt;/strong&gt;variable. Finally we return a new form using our predefined variables as arguments. Now when we call &lt;strong&gt;$ContactForm&lt;/strong&gt; from the template, SilverStripe will place our contact form object in it's place.&lt;/p&gt;
&lt;h2&gt;Part III: Processing the Form Submission&lt;/h2&gt;
&lt;p&gt;Our Contact Form can now be drawn and submitted, but once submitted SilverStripe won't know what to do with it because we haven't defined a function to deal with it. The Submit Button we defined in the last step is going to look for a function called &lt;strong&gt;SendContactForm &lt;/strong&gt;when it's pressed so let's define that function now inside our &lt;strong&gt;ContactPage_Controller &lt;/strong&gt;class:&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/ContactForm.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class ContactPage_Controller extends Page_Controller
{
.
.
.
	function SendContactForm($data) {
      
	 	//Set data
		$From = $data['Email'];
		$To = $this-&amp;gt;Mailto;
		$Subject = &quot;Website Contact message&quot;;  	  
		$email = new Email($From, $To, $Subject);
		//set template
		$email-&amp;gt;setTemplate('ContactEmail');
		//populate template
		$email-&amp;gt;populateTemplate($data);
		//send mail
		$email-&amp;gt;send();
	  	//return to submitted message
		Director::redirect(Director::baseURL(). $this-&amp;gt;URLSegment . &quot;/?success=1&quot;);
	}
}&lt;/textarea&gt;&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This function is pretty self explanatory, but let's go through it quickly. We pass in the $data variable which provides the function with the submitted form data so that we can process it. We then assign the relevant bits of data to variables (&lt;strong&gt;$From&lt;/strong&gt;, &lt;strong&gt;$To&lt;/strong&gt; and &lt;strong&gt;$Subject&lt;/strong&gt;) before creating a new Email() object with these variables as arguments.&lt;strong&gt; &lt;/strong&gt;Next we set the template&lt;strong&gt; &lt;/strong&gt;for the email which is just the name of the Template without the&lt;strong&gt; .ss&lt;/strong&gt; on the end. Then we simple pass our $data array into the &lt;strong&gt;populateTemplate() &lt;/strong&gt;function so that we can use the data in our template by simply calling &lt;strong&gt;$Name&lt;/strong&gt; , &lt;strong&gt;$Comments&lt;/strong&gt; etc. You could also pass a custom array into this function if you needed to add other data beyond that submitted in the form.&lt;/p&gt;
&lt;p&gt;Finally we send the email and return the user to the current URL + &lt;strong&gt;/?Success=1&lt;/strong&gt; which allows us to test whether the form has been submitted and if it has then display the SubmitText message instead of the form. To do this we need a function which will return true when Success is equal to 1. We can put in in the &lt;strong&gt;ContactPage_Controller&lt;/strong&gt; under our &lt;strong&gt;SendContactForm() &lt;/strong&gt;function It will look like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/ContactForm.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class ContactPage_Controller extends Page_Controller
{
.
.
.
	public function Success()
	{
		return isset($_REQUEST['success']) &amp;amp;&amp;amp; $_REQUEST['success'] == &quot;1&quot;;
	}
}&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This function checks to see if &lt;strong&gt;success&lt;/strong&gt; is set and whether it is set to 1, returning true if so. We can now use &lt;strong&gt;&amp;lt;% if Success %&amp;gt; &lt;/strong&gt;in&lt;strong&gt; &lt;/strong&gt;our&lt;strong&gt; &lt;/strong&gt;template to add/remove content depending on whether the form has been submitted or not. This completes our php code so lets move onto the frond end templates.&lt;/p&gt;
&lt;h2&gt;Part IV: Email Template&lt;/h2&gt;
&lt;p&gt;Before we send the email in our &lt;strong&gt;SendContactForm() &lt;/strong&gt;function we populate the template &lt;strong&gt;ContactEmail&lt;/strong&gt;. Although the official SilverStripe documentation states that this template should go into mysite/templates/email/ I have found that it in fact only works when it's in your &lt;strong&gt;themes/yourtheme/templates/ &lt;/strong&gt;folder. We need to create this template so that our email arrives with pretty HTML formatting. In the preparation we made the empty file with doc type and a &lt;strong&gt;&amp;lt;head&amp;gt; &lt;/strong&gt;and &lt;strong&gt;&amp;lt;body&amp;gt;&lt;/strong&gt; tags. We now need to add some CSS styling and the&amp;lt;body&amp;gt; content. This is very much down to how you want your email to look so just take this as an example, the only things which need to be the same are the names of the variables, which should match the names of the Form Fields defined in &lt;strong&gt;Part II&lt;/strong&gt;, in this case &lt;strong&gt;$Name&lt;/strong&gt; and &lt;strong&gt;$Comments&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/yourtheme/templates/ContactEmail.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&amp;gt;
&amp;lt;html&amp;gt;
	&amp;lt;head&amp;gt;
		&amp;lt;style&amp;gt;
			p{
				font-size: 1.2em;
				color: #444;
			}
			p.comments{
				font-size: 1.4em;
				color: #222;
				padding: 20px;
			}
		&amp;lt;/style&amp;gt;
	&amp;lt;/head&amp;gt;
	&amp;lt;body&amp;gt;
	
		&amp;lt;p&amp;gt;The following message was submitted to your site by $Name:&amp;lt;/p&amp;gt;
		
		&amp;lt;p class=&quot;comments&quot;&amp;gt;$Comments&amp;lt;/P&amp;gt; 
	
		&amp;lt;p&amp;gt;You may respond by directly replying to this email.&amp;lt;/p&amp;gt;
	
	&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Part V: ContactPage Template&lt;/h2&gt;
&lt;p&gt;Finally we just need to define the template for our contact Page. using our &lt;strong&gt;Success() &lt;/strong&gt;function we can use a conditional to decide whether to include the form of the SubmitText like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/yourtheme/templates/Layout/ContactForm.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;typography&quot;&amp;gt;
	&amp;lt;h2&amp;gt;$Title&amp;lt;/h2&amp;gt;
	
	&amp;lt;% if Success %&amp;gt;
		$SubmitText
	&amp;lt;% else %&amp;gt;
		$Content
		$ContactForm
	&amp;lt;% end_if %&amp;gt;
	
&amp;lt;/div&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;And that's it! You should now have a fully functioning contact form for your visitors to send you wonderful insightful and thought provoking messages to their hearts content!&lt;/p&gt;</description>
			<pubDate>Sun, 03 May 2009 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/creating-a-simple-contact-form/</guid>
		</item>
		
		<item>
			<title>Custom Login Form with Group Based Redirection</title>
			<link>http://www.ssbits.com/custom-login-form-with-group-based-redirection/</link>
			<description>&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;We will be using
2 files in this tutorial: &lt;strong&gt;CustomLoginForm.php&lt;/strong&gt; and &lt;strong&gt;GroupDecorator.php&lt;/strong&gt;. Create
both of these files in your &lt;strong&gt;mysite/code&lt;/strong&gt; directory and fill them will the
following:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/GroupDecorator.php&lt;br /&gt;&lt;/strong&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class GroupDecorator extends DataObjectDecorator {
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mysite/code/CustomLoginForm.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class CustomLoginForm extends MemberLoginForm {
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Part I: Decorating&lt;/h2&gt;
&lt;p&gt;We need to add a couple of database fields to the group
class as well as their corresponding CMS fields. First lets add our database fields:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/GroupDecorator.php&lt;br /&gt;&lt;/strong&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class GroupDecorator extends DataObjectDecorator {
function augmentSQL(SQLQuery &amp;amp;$query) {}
	
	public function extraStatics(){
		
		return array(
			'db' =&amp;gt; array(
				&quot;GoToAdmin&quot; =&amp;gt; &quot;Boolean&quot;
			),
			'has_one' =&amp;gt; array(
				&quot;LinkPage&quot; =&amp;gt; &quot;SiteTree&quot;
			),
		);
	}
}	
&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;The first function &quot;augmentSQL()&quot;
tells silversripe that it's going to need to add some new fields the existing
table in the database.&lt;/p&gt;
&lt;p&gt;The next function 'extraStatics'
is where we define these new fields. Here we define a Boolean to use when
deciding if we want our group to go strait to the admin area. We also define a
new has_one relationship attaching a siteTree object to our group, which will
be the page that we want to redirect to if our GoToAdmin value is false.&lt;/p&gt;
&lt;p&gt;Next we want to add
our CMS fields to the Group object so that we can select our page from a nice
dropdown or check a checkbox to go strait to the admin area. We use a
TreeDropdownField and a CheckboxField respectively.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/GroupDecorator.php&lt;/strong&gt;&lt;br /&gt;
&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class GroupDecorator extends DataObjectDecorator {
.
.
.
	public function updateCMSFields(FieldSet &amp;amp;$fields) {
	   $fields-&amp;gt;addFieldToTab(&quot;Root.Members&quot;, new CheckboxField(&quot;GoToAdmin&quot;, &quot; Go to Admin area&quot;), 'Members');
	   $fields-&amp;gt;addFieldToTab(&quot;Root.Members&quot;, new TreeDropdownField(&quot;LinkPageID&quot;, &quot;Or select a Page to redirect to&quot;, &quot;SiteTree&quot;), 'Members');
	}
}
&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Note. Even though our has_one relationship was called &amp;ldquo;LinkPage&amp;rdquo; we call the TreeDropdown &amp;ldquo;LinkPageID&amp;rdquo;
as this is what the column in the Group table will be called, as it is referencing
a page in another table.&lt;/p&gt;
&lt;p&gt;We now need to tell silverstripe to extend the group object with our
decorator. We do this by adding the following line to our _config.php
file.&lt;/p&gt;
&lt;p&gt;mysite/_config.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;.
.
.
Object::add_extension('Group', 'GroupDecorator');&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Now if you
visit www.yoursite.com/dev/build
the fields will be added to the database. You can now see in the CMS under
security that each group has a checkbox and a tree dropdown. However they don&amp;rsquo;t
do a whole lot just yet.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Part II: Creating the Custom Login action.&lt;/h2&gt;
&lt;p&gt;Our
new Loginform is going to be an extension of the MemberLoginForm which holds
all of the login actions. We only need to overload (read create a new version
of) the dologin() function, as this is the function that normally does the
redirecting.&lt;/p&gt;
&lt;p&gt;This is what
our new function will look like:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mysite/code/CustomLoginForm.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class CustomLoginForm extends MemberLoginForm {
	public function dologin($data) {
		if($this-&amp;gt;performLogin($data)) {
		        if(!$this-&amp;gt;redirectByGroup($data))
					Director::redirect(Director::baseURL());
		} else {
			if($badLoginURL = Session::get(&quot;BadLoginURL&quot;)) {
				Director::redirect($badLoginURL);
			} else {
				Director::redirectBack();
			}
		}      
	}
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;First we try
to perform the login by calling ($this-&amp;gt;performLogin($data)), if we succeed then we move on, otherwise we check for a bad Login
and if that returns false we just return the user back to where they were.&lt;/p&gt;
&lt;p&gt;If our login
was a success then we try to call our redirectByGroup() function (which we will
write in a minute) from an if statement. If this returns false then we run the contents
of the if and just direct to the base URL. Otherwise if it has returned true
then it was run and we don't need to worry. In most cases (hopefully!) this is
what will happen, but it's still essential to make sure there is a fallback
just in case.&lt;/p&gt;
&lt;p&gt;Now let's add
our magic function that will redirect the user based on their group.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mysite/code/CustomLoginForm.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class CustomLoginForm extends MemberLoginForm {
.
.
.
.
	public function redirectByGroup($data) { 	
 
		// gets the current member that is logging in.
		$member = Member::currentUser();
		// gets all the groups.
		$Groups = DataObject::get(&quot;Group&quot;);
		
		//cycle through each group	
		foreach($Groups as $Group){
			//if the member is in the group and that group has GoToAdmin checked
			if($member-&amp;gt;inGroup($Group-&amp;gt;ID) &amp;amp;&amp;amp; $Group-&amp;gt;GoToAdmin == 1) 
			{	
				//redirect to the admin page
	 			Director::redirect(Director::baseURL() . 'admin' );
				return true;
			}
			//otherwise if the member is in the group and that group has a page link defined
			elseif($member-&amp;gt;inGroup($Group-&amp;gt;ID)  &amp;amp;&amp;amp; $Group-&amp;gt;LinkPageID != 0) 
			{	
				//Get the page that is referenced in the group		
				$Link = DataObject::get_by_id(&quot;SiteTree&quot;, &quot;{$Group-&amp;gt;LinkPageID}&quot;)-&amp;gt;URLSegment;
				//direct to that page
				Director::redirect(Director::baseURL() . $Link);
				return true;
			}
			
		}
		//otherwise if none of the above worked return fase
		return false;
				
	}
}
&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;So first thing we do
is get the currently logged in user (i.e. the one that just logged in) followed
by all the groups that exist.&lt;/p&gt;
&lt;p&gt;We then cycle through
each group checking whether the user is in any of them. If they are and that
group has GoToAdmin checked then we go straight to the admin page. Otherwise if
they are in the group and that group has a page relationship defined we get
that page and link to it. If the user is not in any other the groups (very
unlikely if not impossible), or the group they are in does not have a pageLink
defined then we return false and the doLogin() function takes back control.&lt;/p&gt;
&lt;p&gt;Finally we just need to tell SilverStripe to use our custom login class
instead of the usual one. Again this is done in the _config.php file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/_config.php&lt;br /&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;.
.
.
Object::useCustomClass('MemberLoginForm', 'CustomLoginForm');&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's it! You can now
create user groups and choose which page they link to on login.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;If a user is in
multiple groups the login will just redirect them to the first group it comes across
with that member in. This will usually be the one with the lowest ID. You can manipulate this by reordering the $Groups array before cycling through it in the redirectByGroup() function.&lt;/p&gt;</description>
			<pubDate>Tue, 07 Apr 2009 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/custom-login-form-with-group-based-redirection/</guid>
		</item>
		
		<item>
			<title>Building a Theme from a Static Template</title>
			<link>http://www.ssbits.com/building-a-theme-from-a-static-template/</link>
			<description>&lt;h2&gt;Preperation&lt;/h2&gt;
&lt;p&gt;The example template provided with this article was created using the CSS framework in&lt;a title=&quot;An Example CSS framework&quot; href=&quot;http://www.ssbits.com/example-css-framework/&quot;&gt; this article&lt;/a&gt; and so is structured correctly for turning into a silverstripe theme. If this is the first time you are creating a SilverStripe theme we would recommend using this template as it will make following the tutorial a lot easier. However if you are using another template that is not structured in this way see &lt;a href=&quot;http://www.ssbits.com/#appendix&quot;&gt;the appendix&lt;/a&gt; for some things to look out for before starting.&lt;/p&gt;
&lt;p&gt;To use the included template, download and extract the zip file attached above. Copy the &lt;strong&gt;brushedsilver&lt;/strong&gt; folder into the &lt;strong&gt;themes/&lt;/strong&gt; directory of your SilverStripe install. We then need to tell SilverStripe to use this theme, which is done in the &lt;strong&gt;mysite/_config.php&lt;/strong&gt; file. You will see a line setting the theme to &lt;strong&gt;blackcandy &lt;/strong&gt;which we need to replace with the name of our theme. In this example our theme folder is called &lt;strong&gt;brushedsilver&lt;/strong&gt; so we change this line in &lt;strong&gt;_config.php&lt;/strong&gt; to:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;toolbar:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;SSViewer::set_theme('brushedsilver');&lt;/textarea&gt; &lt;/p&gt;
&lt;h2&gt;Part 1: Getting Started&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;The first thing we need to do is create a single theme template which we can then break down into smaller pieces. Create the file &lt;strong&gt;themes/brushedsilver/templates/Page.ss &lt;/strong&gt;and copy the contents of &lt;strong&gt;themes/brushedsilver/index&lt;/strong&gt;.html into it. We now have a static HTML template under a &lt;strong&gt;.ss&lt;/strong&gt; file. It would still work in silverstripe but it would not create any dynamic content. This gives us some indication as to how flexible silverstripes templating system is, we can use as little or as much regular HTML as we like all the way to having a pure HTML page! Obviously we don't want that so lets start making our template dynamic.&lt;/p&gt;
&lt;p&gt;The first thing to do is edit the &amp;lt;head&amp;gt; section. We need silverstripe to dynamically generate the meta-tags, the page title and also include any files we specify in our page controllers. Here is the same &amp;lt;head&amp;gt; section converted into a silverstripe template:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01//EN&quot; &quot;http://www.w3.org/TR/html4/strict.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/html&quot; xml:lang=&quot;en&quot; &amp;gt;
  &amp;lt;head&amp;gt;
	&amp;lt;link rel=&quot;shortcut icon&quot; href=&quot;/favicon.ico&quot; &amp;gt; 
	
        &amp;lt;% base_tag %&amp;gt;
	&amp;lt;title&amp;gt;$Title - Your Site Name&amp;lt;/title&amp;gt;
        $Metatags(false)
	&amp;lt;% require themedCSS(layout) %&amp;gt; 
	&amp;lt;% require themedCSS(typography) %&amp;gt; 
	&amp;lt;% require themedCSS(form) %&amp;gt; 
	&amp;lt;!--[if IE 6]&amp;gt;
			&amp;lt;style type=&quot;text/css&quot;&amp;gt;
			 @import url(themes/brushedsilver/css/IE6.css);
			&amp;lt;/style&amp;gt; 
	&amp;lt;![endif]--&amp;gt;
&amp;lt;/head&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;So lets run through this. The first thing we do is include the &lt;strong&gt;&amp;lt;% base_tag %&amp;gt;&lt;/strong&gt; and set the title to hold the variable &lt;strong&gt;$Title&lt;/strong&gt; which will be replaced by the title of whichever page we are on. This is followed by &lt;strong&gt;$Metatags(false)&lt;/strong&gt; which adds the meta-tags set. We add false to this so that SilverStripe does not include the page title twice. If we wanted our page title to be independently set for each under the meta-data tab in the CMS, we could set this to true and delete the&lt;strong&gt; &amp;lt;title&amp;gt;&lt;/strong&gt; tag. SilverStripe would then include the entire title line for us, but we would not be able to set a persistent part of it like the name of our site.&lt;/p&gt;
&lt;p&gt;We then just add&lt;strong&gt; &amp;lt;% require themedCSS() %&amp;gt;&lt;/strong&gt; tags for each of our CSS files and adjust the IE6 conditional path to be relative to the root of the site. And thats it, our &lt;strong&gt;&amp;lt;head&amp;gt;&lt;/strong&gt; section is now ready for SilverStripe's dynamic magic.&lt;/p&gt;
&lt;h2&gt;Part II: Top Level Navigation&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;The navigation in the satic HTML is in the form of an unordered list. We are going to emulate this structure using a SilverStripe control loop which will loop over the top level pages allowing us to create a new list item and link for each one. It looks like this:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;body&amp;gt;
	
	&amp;lt;div id=&quot;container&quot;&amp;gt;
			
	&amp;lt;div id=&quot;navbar&quot;&amp;gt;
    		&amp;lt;a class=&quot;home&quot; href=&quot;&quot; title=&quot;go to home&quot;&amp;gt;&amp;lt;span&amp;gt;Home&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
    		&amp;lt;ul&amp;gt;
      		&amp;lt;% control Menu(1) %&amp;gt;
                    
                    &amp;lt;li&amp;gt;
                    &amp;lt;a class=&quot;$LinkingMode&quot; href=&quot;$Link&quot; title=&quot;Go to $Title&quot;&amp;gt;$MenuTitle&amp;lt;/a&amp;gt;
                    &amp;lt;/li&amp;gt;
                    
            &amp;lt;% end_control %&amp;gt; 
            &amp;lt;/ul&amp;gt;  
   	&amp;lt;/div&amp;gt;&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;The control loop &lt;strong&gt;&amp;lt;% control Menu(1) %&amp;gt;&lt;/strong&gt; tells SilverSripe to loop through the pages in the top level menu, creating whatever HTML is between this starting control and the &lt;strong&gt;&amp;lt;% end_control %&amp;gt;&lt;/strong&gt; for each one. So in this case we create a &lt;strong&gt;&amp;lt;li&amp;gt;&lt;/strong&gt; followed by a &lt;strong&gt;&amp;lt;a&amp;gt; &lt;/strong&gt;and then a closing &lt;strong&gt;&amp;lt;/li&amp;gt;&lt;/strong&gt;.&amp;nbsp; Within the &lt;strong&gt;&amp;lt;a&amp;gt;&lt;/strong&gt; we use a few SilverStripe variables, namely &lt;strong&gt;$LinkingMode&lt;/strong&gt;, &lt;strong&gt;$Link&lt;/strong&gt;, &lt;strong&gt;$Title&lt;/strong&gt; and &lt;strong&gt;$MenuTitle&lt;/strong&gt;. The key to understanding how this works and how control loops work in general is understanding that within this control loop you are in the &lt;em&gt;context&lt;/em&gt; of whatever item is being looped through, in this case pages in the top level of the site tree. So when we loop through and use the &lt;strong&gt;$Link &lt;/strong&gt;variable in the &lt;strong&gt;href&lt;/strong&gt;, SilverStripe inserts the link to the page whose &lt;em&gt;context&lt;/em&gt; we are currently in. Likewise for &lt;strong&gt;$LinkingMode,&lt;/strong&gt; &lt;strong&gt;$Title&lt;/strong&gt; and &lt;strong&gt;$MenuTitle&lt;/strong&gt;, while outside the control we would be getting the values from the actual page we are on, inside it the values returned are of the items in the loop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$LinkingMode&lt;/strong&gt; inserts a class based on whether the link we are creating is for the current page or whether the current page is a child of this link. This way we can style &lt;strong&gt;a.current&lt;/strong&gt; and &lt;strong&gt;a.section&lt;/strong&gt; to be highlighted to show the user they are on that page or in that section.&lt;/p&gt;
&lt;p&gt;For more information on the built in variables and functions you can use in a template see this page: &lt;a title=&quot;SS Docs: Built in page controls&quot; href=&quot;http://doc.silverstripe.com/doku.php?id=built-in-page-controls&quot; target=&quot;_blank&quot;&gt;http://doc.silverstripe.com/doku.php?id=built-in-page-controls&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At this point, have a look at your page. You will notice the header image not showing as we havn't adjusted the path yet, but also notice that the top level navigation is now generated from your silverstripe site. Remember that you will need to add &lt;strong&gt;?flush=1&lt;/strong&gt; to the end of your URL each time you want to see changes made to a template. This makes sure SilverStripe clears its template cache before rendering the page. It's also worth disabling or clearing your browsers cache to avoid any confusion. If your using firefox you can download the &lt;a title=&quot;Firefox plugin: Developer Toolabr&quot; href=&quot;https://addons.mozilla.org/en-US/firefox/addon/60&quot; target=&quot;_blank&quot;&gt;Developer Toolbar&lt;/a&gt; that has a &quot;disable cache&quot; button among numerous other useful tools.&lt;/p&gt;
&lt;h2&gt;Part III: Layout Section (Content and Sidebar)&lt;/h2&gt;
&lt;p&gt;Now lets get onto the main section of our page, otherwise know as the 'Layout'. The Layout is the section of the page that changes between different types of page, so later on we will be stripping this out of our page and placing it in it's own file so that we can vary the look of our pages without having to have completely new templates. Before we do that though we want a completely working page.&lt;/p&gt;
&lt;p&gt;First part is the banner image and the sidebar. For the banner image we just need to make the path to the image relative to the root. For the sidebar we use a control loop identical to the one we used for the top navigation, only this time we call &lt;strong&gt;Menu(2)&lt;/strong&gt; instead of Menu(1).&lt;br /&gt;&amp;nbsp;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div id=&quot;layout&quot;&amp;gt;
	&amp;lt;img id=&quot;headerImage&quot; src=&quot;themes/brushedsilver/images/header_color.jpg&quot; mce_src=&quot;themes/brushedsilver/images/header_color.jpg&quot; alt=&quot;header Image&quot;&amp;gt;
	&amp;lt;div class=&quot;typography&quot;&amp;gt;
		&amp;lt;div id=&quot;sidebar&quot;&amp;gt;			
			&amp;lt;div class=&quot;sidebarBox&quot;&amp;gt;
				
				&amp;lt;h3&amp;gt;$Level(1).Title&amp;lt;/h3&amp;gt;
		
				&amp;lt;ul id=&quot;menu2&quot;&amp;gt;			
					&amp;lt;% control Menu(2) %&amp;gt;
	                    &amp;lt;li&amp;gt;
	                    	&amp;lt;a class=&quot;$LinkingMode&quot; href=&quot;$Link&quot; mce_href=&quot;$Link&quot; title=&quot;Go to $Title&quot;&amp;gt;$MenuTitle&amp;lt;/a&amp;gt;
	                    &amp;lt;/li&amp;gt;    
	    			&amp;lt;% end_control %&amp;gt; 			
				&amp;lt;/ul&amp;gt;
			
			&amp;lt;/div&amp;gt;			
		&amp;lt;/div&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;The only extra thing to notice here is that we use the variable &lt;strong&gt;$Level(1).Title&lt;/strong&gt; for our sidebar's header. This is a shorter way of writing &lt;strong&gt;&amp;lt;% control Level(1) %&amp;gt;$Title&amp;lt;% end_control %&amp;gt;&lt;/strong&gt; which is just as good because we don't need to loop through multiple items, just jump to the top level page of this section and grab its Title.&lt;/p&gt;
&lt;p&gt;Next up is the actual content area. This is really quite simple as all we need to do is insert our &lt;strong&gt;$Content &lt;/strong&gt;variable along with the&lt;strong&gt; $Form&lt;/strong&gt; (for the login form etc.) and &lt;strong&gt;$PageComments&lt;/strong&gt; (for pages where comments are enabled in the CMS) and then make sure our typography, layout and container divs are closed. We also want to get our breadcrumbs in there which is as simple as using &lt;strong&gt;$Breadcrumbs&lt;/strong&gt;. Gotta love SilverStripe :)&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;		&amp;lt;div id=&quot;content&quot; &amp;gt;
		            
			&amp;lt;div id=&quot;Breadcrumbs&quot;&amp;gt;
				&amp;lt;p&amp;gt;$Breadcrumbs&amp;lt;/p&amp;gt;
			&amp;lt;/div&amp;gt;
		    
			$Content
			$Form
			$Pagecomments
		      
		&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Part IV: The Footer&lt;/h2&gt;
&lt;p&gt;There's not a whole lot to our footer, and even less that needs to be dynamic. We will however add a nifty variable to make the copyright year dynamic. &lt;strong&gt;$Now.Year&lt;/strong&gt; will return the current year, so we can just stick that strait into our template. Alternatively if we want something like 2009 - {year} we can use the dynamic year function in &lt;a title=&quot;Dynamic year&quot; href=&quot;http://www.ssbits.com/dynamic-year-function/&quot;&gt;this post&lt;/a&gt;. For this example well just use the &lt;strong&gt;$Now.Year&lt;/strong&gt; variable:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;	
	&amp;lt;div id=&quot;footer&quot;&amp;gt;
		&amp;lt;div class=&quot;footerContent&quot;&amp;gt;
			&amp;lt;p&amp;gt;&amp;amp;copy; YourCompany $Now.Year | 
			Provided by &amp;lt;a href=&quot;http://ssbits.com&quot;&amp;gt;SSbits.com&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
		&amp;lt;/div&amp;gt;	
	&amp;lt;/div&amp;gt;
	
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Well that wraps up our generic template, but at the moment it's just in a single file which means if we wanted a page without a sidebar for example we would have to make a whole new template. But as usual SilverStripe provides an elegant solution to this situation. &lt;/p&gt;
&lt;h2&gt;Part V:Creating the Layout Template&lt;/h2&gt;
&lt;p&gt;We need to strip out our layout section and place it in its own file so that we can create 'Layouts' for each page type we need. So first cut everything within the &lt;strong&gt;&amp;lt;div id=&quot;layout&quot;&amp;gt; &amp;lt;/div&amp;gt;&lt;/strong&gt; tags pasting it into a new file &lt;strong&gt;themes/brushedsilver/templates/Layout/Page.ss&lt;/strong&gt; and replacing it in the main template with &lt;strong&gt;$Layout&lt;/strong&gt; like so:&lt;/p&gt;
&lt;p&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div id=&quot;layout&quot;&amp;gt; 
	$Layout
&amp;lt;/div&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Now when SilverStripe draws a page it first grabs the main template from &lt;strong&gt;themes/brushedsilver/templates/Page.ss &lt;/strong&gt;and when it encounters the &lt;strong&gt;$Layout&lt;/strong&gt; variable it looks in the &lt;strong&gt;templates/Layout/&lt;/strong&gt; folder for a layout which matches the name of the current page type. If it can't find one or the current page type &lt;em&gt;is&lt;/em&gt; &lt;strong&gt;Page &lt;/strong&gt;then it just uses &lt;strong&gt;themes/brushedsilver/&lt;/strong&gt;&lt;strong&gt;templates/Layout/&lt;/strong&gt;&lt;strong&gt;Page.ss&lt;/strong&gt;. It then resumes drawing from the main template for the footer etc.&lt;/p&gt;
&lt;h2&gt;Part VI: Creating the Includes&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;We can also take this a step further and remove some things from our Layout template so that we can then re-use them in other templates. Let's remove the &lt;strong&gt;sidebar&lt;/strong&gt; and the &lt;strong&gt;breadcrumbs&lt;/strong&gt;. Create two files in the &lt;strong&gt;themes/brushedsilver/templates/Includes/&lt;/strong&gt; folder, &lt;strong&gt;Sidebar.ss&lt;/strong&gt; and &lt;strong&gt;Breadcrumbs.ss&lt;/strong&gt;. Cut everything inside and including the &lt;strong&gt;&amp;lt;div id=&quot;sidebar&quot;&amp;gt; &amp;lt;/div&amp;gt;&lt;/strong&gt; and paste it into &lt;strong&gt;Sidebar.ss&lt;/strong&gt;. Now we can simply replace it with an &lt;strong&gt;&amp;lt;% include %&amp;gt;&lt;/strong&gt; tag, as well as introducing a condition to decide whether we add it to our template or not:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;				&amp;lt;% if Menu(2) %&amp;gt;
					&amp;lt;% include Sidebar %&amp;gt;
					&amp;lt;div id=&quot;content&quot;&amp;gt;
				&amp;lt;% end_if %&amp;gt;
			            
				&lt;/textarea&gt;&amp;nbsp; &lt;/p&gt;
&lt;p&gt;What we are doing here is testing to see whether there is a second level menu to display and if there is we add our sidebar and our &lt;strong&gt;&amp;lt;div id=&quot;content&amp;gt;&lt;/strong&gt; which limits the size of our content area. Otherwise we just leave the sidebar out all together and let the content fill the whole page. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note. &lt;/strong&gt;we are moving the &lt;strong&gt;&amp;lt;div id=&quot;content&amp;gt;&lt;/strong&gt; to be inside this conditional statement, so if you copied and pasted the code above make sure you don't have another content div below it.&lt;/p&gt;
&lt;p&gt;We can do a similar thing with our breadcrumbs so that they are only displayed if we are on the second level. Cut the contents of and including the &lt;strong&gt;&amp;lt;div id=&quot;breadcrumbs&quot;&amp;gt; &amp;lt;/div&amp;gt;&lt;/strong&gt; and paste it into the &lt;strong&gt;Breadcrumbs.ss&lt;/strong&gt; file. In its place put this code into &lt;strong&gt;themes/brushedsilver/templates/Layout/Page.ss&lt;/strong&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;	&amp;lt;% if Level(2) %&amp;gt;
	  	&amp;lt;% include Breadcrumbs %&amp;gt;
	&amp;lt;% end_if %&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;This code has a subtle difference to the one we used for the sidebar. In our &lt;strong&gt;If&lt;/strong&gt; statement we use &lt;strong&gt;Level(2) &lt;/strong&gt;instead of &lt;strong&gt;Menu(2)&lt;/strong&gt;. The difference here is that with the breadcrumbs we only want to display them when the user is&lt;em&gt; actually on&lt;/em&gt; the second level or greater, while with the sidebar we want to show it on the first level only if a second level &lt;em&gt;exists&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This proccess of separating page elements into &lt;strong&gt;Includes&lt;/strong&gt; is good practice that we recommend repeating for the Footer and Header sections as well as any custom blocks you use repeatedly in your templates.&lt;/p&gt;
&lt;p&gt;So that pretty much wraps up the tutorial. You should now have a fully fledged SilverStripe theme that can be extended as you create new page types with different data. The key thing to realise is that SilverStripe's templating system is not only very simple for non-programmers to understand but it is also unobtrusive and allows you to build your templates in a fashion very similar to those of a static HTML page.&lt;/p&gt;
&lt;h2&gt;&lt;a name=&quot;appendix&quot;&gt;&lt;/a&gt;Appendix&lt;/h2&gt;
&lt;p&gt;Sometimes you may want to use a template that was created by somone else who did not have SilverStripe in mind. Here are a few pointers to preparing it for conversion into a SilverStripe theme.
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the blackcandy theme bundled with SilverStripe as a guide to
the file structure of a theme. All your files should be in one folder
with the name of your theme, containing three folders: &lt;strong&gt;css&lt;/strong&gt;, &lt;strong&gt;images &lt;/strong&gt;and &lt;strong&gt;templates&lt;/strong&gt;. The templates folder should then contain two folders: &lt;strong&gt;Layout &lt;/strong&gt;and &lt;strong&gt;Includes&lt;/strong&gt;. Place all your images in the &lt;strong&gt;images&lt;/strong&gt; folder and all your style sheets into the &lt;strong&gt;css&lt;/strong&gt; folder. &lt;/li&gt;
&lt;li&gt;Although not essential it's good practice to separate layout,
typography and form CSS into their own files. This makes editing easier
as well as encouraging re-use in later projects.&lt;/li&gt;
&lt;li&gt;If you are using active states for navigation buttons so that they
are highlighted if you are on that page check to make sure they are
using the correct CSS selectors. Silverstripe uses &lt;strong&gt;.current&lt;/strong&gt; and &lt;strong&gt;.section&lt;/strong&gt; classes, returned when using the &lt;strong&gt;$LinkingMode&lt;/strong&gt; variable.&lt;/li&gt;
&lt;/ul&gt;</description>
			<pubDate>Thu, 26 Mar 2009 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/building-a-theme-from-a-static-template/</guid>
		</item>
		
		<item>
			<title>Create a Static, CMS editable sidebar</title>
			<link>http://www.ssbits.com/create-a-static-sidebar/</link>
			<description>&lt;h2&gt;Preperation&lt;/h2&gt;
&lt;p&gt;We need to create 2 new files for this tutorial, one for the php (Model &amp;amp; Controller) called &lt;strong&gt;StaticSidebar.php&lt;/strong&gt; and another for the template (View) called &lt;strong&gt;StaticSidebar.ss&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can leave StaticSidebar.ss blank for now, but inside StaticSidebar.php add the following:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/StaticSidebar.php&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php 
class StaticSidebar extends Page 
{
} 
class StaticSidebar_Controller extends Page_Controller 
{
}&lt;/textarea&gt;&lt;/p&gt;
&lt;h2&gt;Part 1: Setting up the model&lt;/h2&gt;
&lt;p&gt;Our static sidebar is going to have an external link (TextField), and internal link (SitetreeDropdownField) and an Image as well as using the pages default Content (HTMLEditorField) for its HTML text. So lets add our database model to the class.&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaticSidebar extends Page 
{
	
	static $db = array(
		'ExternalLink' =&amp;gt; 'Text'
	);
	
	static $has_one= array(
		&quot;Image&quot; =&amp;gt; &quot;Image&quot;,
		&quot;InternalLink&quot; =&amp;gt; &quot;SiteTree&quot;
	);
	
	
} &lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Now lets add the CMS fields so that we can edit the sidebar:&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaticSidebar extends Page 
{
	
	public function getCMSFields()
	{
	$fields = parent::getCMSFields();	
	$fields-&amp;gt;addFieldToTab(&quot;Root.Content.Main&quot;, new TextField(&quot;ExternalLink&quot;));
	$fields-&amp;gt;addFieldToTab(&quot;Root.Content.Main&quot;, new TreeDropdownField(&quot;InternalLinkID&quot;, &quot;InternalLink&quot;, &quot;SiteTree&quot;));
	$fields-&amp;gt;addFieldToTab(&quot;Root.Content.Main&quot;, new ImageField(&quot;Image&quot;));
	
	return $fields;
	}	
	
	
} &lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;Note. Even though our has_one relationship was called &amp;ldquo;InternalLink&quot; we call the TreeDropdown &amp;ldquo;InternalLinkID&amp;rdquo;
as this is the column in the StaticSidebar table that it needs to save it's contents into. This is a slight inconsistency with Silverstripe, as we don't need to call our ImageField 'ImageID' even though it is the same principle. &lt;/p&gt;
&lt;p&gt;The last thing to do is to add a line which stops the user creating children under our sidebar and also setting the default for 'ShowInMenus' to be false so that our sidebar page doesnt show up as an actual page on our site.&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;class StaticSidebar extends Page 
{
.
.
.
      static $allowed_children = array(&quot;none&quot;);
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Now run yoursite.com/dev/build (in 2.2X run yoursite.com/db/build?flush=1). You will see the new fields being added and you can now log into the&lt;/p&gt;
&lt;h2&gt;Part II: Creating the Template&lt;/h2&gt;
&lt;p&gt;We are going to intergrate our sidebar into the default blackcandy theme, but it should be an almost indentical process to add it to your theme, just apply your own structure to it. You could also just as easily make it part of the footer, header or any other part of the page you fancy as we havnt actually defined anything that says where the information stored in the model needs to be on the page. Silverstripe gives up total freedom.&lt;/p&gt;
&lt;p&gt;For our tempalte we can just copy and adapt the existing Sidebar template found in &lt;strong&gt;themes/blackcandy/templates/Includes/Sidebar.ss.&lt;/strong&gt; minus the outermost div (id=&quot;Sidebar&quot;). So copy and paste that into our StaticSidebar.ss file we created earlier and make it look like this:&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;themes/blackcandy/templates/Includes/StaticSidebar.ss &lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;% if GetStaticSidebar %&amp;gt;
	&amp;lt;% control GetStaticSidebar %&amp;gt;
	&amp;lt;div id=&quot;Sidebar&quot; class=&quot;typography&quot;&amp;gt;
			&amp;lt;div class=&quot;sidebarBox&quot;&amp;gt;
		 		&amp;lt;h3&amp;gt;Static Sidebar&amp;lt;/h3&amp;gt;
		  		
		  		&amp;lt;ul &amp;gt;
					&amp;lt;li&amp;gt;&amp;lt;a href=&quot;$ExternalLink&quot; title=&quot;Go to ExternalLink&quot;&amp;gt;External Link&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
					&amp;lt;li&amp;gt;&amp;lt;a href=&quot;$InternalLink.Link&quot; title=&quot;Go to InternalLink&quot;&amp;gt;$InternalLink.MenuTitle&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
					
					&amp;lt;li&amp;gt;$Image.SetWidth(180)&amp;lt;/li&amp;gt;
					&amp;lt;li&amp;gt;$Content&amp;lt;/li&amp;gt;
		  		&amp;lt;/ul&amp;gt;
				
				&amp;lt;div class=&quot;clear&quot;&amp;gt;&amp;lt;/div&amp;gt;
			&amp;lt;/div&amp;gt;
			&amp;lt;div class=&quot;sidebarBottom&quot;&amp;gt;&amp;lt;/div&amp;gt;
	&amp;lt;/div&amp;gt;
	&amp;lt;% end_control %&amp;gt;
&amp;lt;% end_if %&amp;gt;&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;So first up you will notice that we are calling a function '&lt;strong&gt;GetStaticSidebar&lt;/strong&gt;'. We havnt created this function yet, we do that in the next part, but all it does is return our StaticSidebar page so that we can use the data held in. So we first check if our StaticSidebar exists and if it does we enter a control context for it, allowing us to use all the variables first hand (i.e. $Image instead of $GetStaticSidebar.Image).&lt;/p&gt;
&lt;p&gt;Then we remove the outermost div so that we dont't get 2 #sidebars and then just use the unordered list (&amp;lt;ul&amp;gt;) and enter each item as a list item (&amp;lt;li&amp;gt;). We use $InternalLink&lt;strong&gt;.Link&lt;/strong&gt; because $InternalLink on it's own returns the actual page that we are linking to where as we want &lt;em&gt;the link&lt;/em&gt; to that page in the same way as we use $Link in our menus. We use the same method to get the pages title, $InternalLink.MenuTitle. We also set the image to be 80px wide by calling $Image.SetWidth(80).&lt;/p&gt;
&lt;h2&gt;part III: Adding it to our Page&lt;br /&gt;&lt;/h2&gt;
&lt;p&gt;Finally we just need add the sidebar to our page template and create the function that will return our StaticSidebar for use in all our pages. Because we want to use it accross the whole site, it makes sense to put this function in the Page_Controller so that all our pages that extend Page will have access to this function. In the case of the blackcandy theme we need to add our sidebar underneath the existing menu(2) sidebar. To do this we will include our template in the Sidebar.ss file which is in turn included in our Layout/Page.ss template. We then need to make some slight alterations to the &amp;lt;% if %&amp;gt; block which surrounds the Sidebar include as we now want to draw the sidebar even if there is no level 2 menu to display.&lt;/p&gt;
&lt;p&gt;So first lets add the function to our Page.php file:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/Page.php&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:php;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;?php
class Page extends SiteTree {	
.
.
.
}
class Page_Controller extends ContentController {
.
.
.
	public function GetStaticSidebar(){
		return DataObject::get_one(&quot;StaticSidebar&quot;);
	}
	
}&lt;/textarea&gt; &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All this function does is grabs the first StaticSidebar it can find and returns it. This is all we need as there will only be one of these sidebars in the site. If you need to have more then one, you will need to create a function which can filter the result based on a parameter that you define. So for example calling &amp;lt;% GetStaticSidebar(right) %&amp;gt; might use a function that has the database call DataObject::get(&quot;StaticSidebar&quot;, &quot;Type = $Side&quot;), where $Side is the variable passed into the function (in this case &quot;right&quot;).&lt;/p&gt;
&lt;p&gt;Finally we need to include our StaticSidebar.ss tempalte in the Sidebar.ss template and then make some slight alterations to the Layout/Page.ss template to ensure the Sidebar.ss is always included. So open up Includes/Sidebar.ss and make the following changes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/blackcandy/templates/Includes/Sidebar.ss&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div id=&quot;Sidebar&quot; class=&quot;typography&quot;&amp;gt;
	&amp;lt;% if menu(2) %&amp;gt; &amp;lt;!-- add an if statment here --&amp;gt;
	&amp;lt;div class=&quot;sidebarBox&quot;&amp;gt;
         .
         .
         .
        &amp;lt;div class=&quot;sidebarBottom&quot;&amp;gt;&amp;lt;/div&amp;gt;
	&amp;lt;% end_if %&amp;gt; &amp;lt;!--end the if statement --&amp;gt;
	
	&amp;lt;% include StaticSidebar %&amp;gt; &amp;lt;!-- include our StaticSidebar --&amp;gt;
&amp;lt;/div&amp;gt;&lt;/textarea&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now we just need to remove the if statment from our Layout/Page.ss:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/blackcandy/templates/Layout/Page.ss&lt;/strong&gt;&lt;br /&gt;&lt;textarea name=&quot;code&quot; class=&quot;brush:xhtml;gutter:false;&quot; cols=&quot;80&quot; rows=&quot;10&quot;&gt;&amp;lt;div class=&quot;typography&quot;&amp;gt;
            &amp;lt;!-- Remove If statement here --&amp;gt;
		&amp;lt;% include SideBar %&amp;gt;         
                &amp;lt;div id=&quot;Content&quot;&amp;gt;	   
            &amp;lt;!-- ------ --&amp;gt;
	   .
           .
           .
         &lt;/textarea&gt; &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;&lt;br /&gt;Remember you will need to add the &amp;lt;% include&amp;nbsp; Sidebar %&amp;gt; to all your new Layout files if you want any sidebar on those pages.&lt;strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<pubDate>Tue, 24 Mar 2009 00:00:00 -0500</pubDate>
			
			
			<guid>http://www.ssbits.com/create-a-static-sidebar/</guid>
		</item>
		

	</channel>
</rss>
