<?xml version="1.0"?>
<rss version="2.0">
	<channel>
		<title>SSbits - all things SilverStripe</title>
		<link>http://www.ssbits.com/home/</link>
		

		
		<item>
			<title>Database Configuration Management</title>
			<link>http://www.ssbits.com/database-configuration-management/</link>
			<description>&lt;p&gt;Wouldn't it be easy to have a single environment configuration for all your SilverStripe sites without having to reconfigure each one individually? Wouldn't it be great not to have to change environment settings when you push your site from development to live servers? This is easy to achieve using the SilverStripe &quot;ConfigureFromEnv&quot; script.&lt;/p&gt;
&lt;p&gt;To set this up, remove your $databaseConfig line from your site &lt;strong&gt;_config.php&lt;/strong&gt; and add:&lt;/p&gt;
&lt;p&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;require_once(&quot;conf/ConfigureFromEnv.php&quot;);&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Then create a new &lt;strong&gt;_ss_environment.php&lt;/strong&gt; file. This can live in your web root, in it's parent or it's parent parent folder. Define the following constants:&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;_ss_enviroment.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;/* What kind of environment is this: development, test, or live (ie, production)? */
define('SS_ENVIRONMENT_TYPE', 'dev/test/live');

/* Database connection */
define('SS_DATABASE_SERVER', 'localhost');
define('SS_DATABASE_USERNAME', 'root');
define('SS_DATABASE_PASSWORD', '');

/* Configure a default username and password to access the CMS on all sites in this environment. */
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');&lt;/textarea&gt; &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now your &lt;strong&gt;_config.php&amp;nbsp;&lt;/strong&gt;file only needs one setting, a &lt;strong&gt;$database&lt;/strong&gt; parameter that points SilverStripe to the correct database for this server!&lt;/p&gt;</description>
			<pubDate>Wed, 17 Feb 2010 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/database-configuration-management/</guid>
		</item>
		
		<item>
			<title>Disable Default JavaScript Behaviours</title>
			<link>http://www.ssbits.com/disable-default-javascript-behaviours/</link>
			<description>&lt;p&gt;By default SilverStripe uses bulky and sometimes unwanted JavaScript libraries to handle form validation and AJAX page commenting features. Often you want to disable this behaviour so you can integrate your own JavaScript code. This is easily achieved with the following two lines in you site configuration file:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/_config.php&lt;/strong&gt;&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;Validator::set_javascript_validation_handler('none');
PageCommentInterface::set_use_ajax_commenting(false);&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Now you are free to add the behaviours you want to your view (templates) without the hassle of unpredictable SilverStripe JavaScript includes.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
			<pubDate>Mon, 15 Feb 2010 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/disable-default-javascript-behaviours/</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>Add attachments to Silverstripe Form submissions</title>
			<link>http://www.ssbits.com/add-attachments-to-silverstripe-form-submissions/</link>
			<description>&lt;p&gt;Today I was trying to add a CV file to a silverstripe form for a project and noticed that is not that easy as it sounds. Well with the help of Martijn we figured it out:&lt;/p&gt;
&lt;p&gt;First the form&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 ApplyForm(){                                
	return new Form($this, &quot;ApplyForm&quot;,
		new FieldSet(
			new TextField('FullName','Full Name'),
			new EmailField('Email'),		
			new FileField('CV','upload your CV')
		),
		new FieldSet(
			new FormAction(&quot;ApplyAction&quot;, &quot;submit your cv&quot;)
		), new RequiredFields(
     		&quot;FullName&quot;
		)
	);
}
&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;It's a really simple form but it takes the information we want. Now the function that sends the mail:&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 ApplyAction($data, $form) {
	$email = new Email();
	$email-&amp;gt;setTemplate('Job_Applicant');
	$email-&amp;gt;populateTemplate($data);
	$email-&amp;gt;setTo(&quot;fa@dospuntocero.cl&quot;);  
	$email-&amp;gt;setFrom(&quot;postulaciones@sehop.org&quot;);  		
	$email-&amp;gt;setSubject('New job applicant');
	$email-&amp;gt;populateTemplate($data);
	// thats the tricky part for the files attaced
    if (isset($_FILES[&quot;CV&quot;]) &amp;amp;&amp;amp; is_uploaded_file($_FILES[&quot;CV&quot;][&quot;tmp_name&quot;])) {
       $email-&amp;gt;attachFile($_FILES[&quot;CV&quot;][&quot;tmp_name&quot;], $_FILES[&quot;CV&quot;][&quot;name&quot;]);
    }
	$email-&amp;gt;send();			  
					
	$form-&amp;gt;sessionMessage(&quot;el mail de postulaci&amp;oacute;n ha sido enviado correctamente&quot;, 'important');
	Director::redirectBack();
}	
&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;With the use of this little function you will get the file uploaded and attached to your email.&lt;/p&gt;
&lt;p&gt;amazingly simple isn't it?&lt;/p&gt;</description>
			<pubDate>Tue, 05 Jan 2010 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/add-attachments-to-silverstripe-form-submissions/</guid>
		</item>
		
		<item>
			<title>Select a theme using a URL parameter</title>
			<link>http://www.ssbits.com/select-a-theme-using-a-url-parameter/</link>
			<description>&lt;p&gt;When I develop SilverStripe sites, I usually get functionality working in the default theme &lt;em&gt;blackcandy&lt;/em&gt; before getting it working in the final templates. This way, authors can start adding content and seeing how it looks on the page, and I can get early feedback about the functionality. Also, as a developer I get to see real content in the browser early in the process. So does the designer.&lt;/p&gt;
&lt;p&gt;I'm sure this process is nothing new - and I realise form-first vs function-first is an old debate - but one thing I wanted to share is some code my team and the client found useful in a recent project.&lt;/p&gt;
&lt;p&gt;I added the ability to select a theme based on a parameter in the URL, either for the current page request or for session. This meant we were able to throw around URLs specifying the theme, choosing a theme depending on whether we were discussing the (visual) design or the functionality and content.&lt;/p&gt;
&lt;p&gt;http://www.mywebsite.com/myurlsegment/&lt;strong&gt;?theme=blackcandy&lt;/strong&gt;&lt;br /&gt;sets the theme for a single request.&lt;/p&gt;
&lt;p&gt;http://www.mywebsite.com/myurlsegment/&lt;strong&gt;?setTheme=blackcandy&lt;/strong&gt;&lt;br /&gt;sets the theme for the session.&lt;/p&gt;
&lt;p&gt;Early in the client project, I made sure all the functionality and content worked in both themes, but as more pages worked in the real theme, we left &lt;em&gt;blackcandy&lt;/em&gt; behind.&lt;/p&gt;
&lt;p&gt;Having said that, I kept the most involved pages working in &lt;em&gt;blackcandy&lt;/em&gt; right until the end, so we could always view the new content, such as a large image due to be displayed in a lightbox, or content due to be included in a slider effect was implemented. That way, we kept the feedback coming: by the time the lightbox effect worked, we already knew the image was getting all the way from author to browser, appropriately downsampled and compressed, and everyone was already happy with the size and quality of the large image.&lt;/p&gt;
&lt;p&gt;Here's the essence of code, suitably unrefactored for your viewing pleasure.&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;class Page_Controller extends ContentController {

  function init() {
    parent::init();

    if (isset($_GET['setTheme'])) {
      if (Director::isDev() || Permission::check('ADMIN')) {
        Session::set('theme', $_GET['setTheme']);
      } else {
        Security::permissionFailure(null,
          'Please log in as an administrator to switch theme.');
      }
    }

    if (isset($_GET['theme'])) {
      if (Director::isDev() || Permission::check('ADMIN')) {
        SSViewer::set_theme($_GET['theme']);
      } else {
        Security::permissionFailure(null,
          'Please log in as an administrator to set the theme.');
      }

    } elseif (Session::get('theme')) {
      SSViewer::set_theme(Session::get('theme'));
    }

  }&lt;/textarea&gt; &lt;/p&gt;
&lt;p&gt;See also Martijn van Nieuwenhoven's SSbits snippet:&lt;br /&gt;
&lt;a href=&quot;http://www.ssbits.com/../create-a-front-end-theme-switcher/&quot; target=&quot;_self&quot;&gt;http://www.ssbits.com/create-a-front-end-theme-switcher/&lt;/a&gt;&lt;/p&gt;</description>
			<pubDate>Mon, 21 Dec 2009 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/select-a-theme-using-a-url-parameter/</guid>
		</item>
		
		<item>
			<title>Using a Print Stylesheet</title>
			<link>http://www.ssbits.com/using-a-print-stylesheet/</link>
			<description>&lt;p&gt;Often people want to be able to print webpages without all the menus and graphics and with all the content fitting correctly onto a page. To do this you need to create a separate print stylesheet. It would look something like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;themes/YOURTHEME/css/print.css&lt;/strong&gt;&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;#sidebar, 
#header, 
#navbar, 
#footer {
	display: none;
}
#container, 
#content{
	width: 100%; 
	margin: 0; 
	float: none;
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p&gt;Then all you need to do to include this in your page is to run a seperate &lt;strong&gt;Requirements&lt;/strong&gt; call and specify that it is for &lt;strong&gt;print &lt;/strong&gt;within the&lt;strong&gt; init() &lt;/strong&gt;function of your &lt;strong&gt;Page_controller&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysite/code/Page.php&lt;/strong&gt;&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;Requirements::css(&quot;themes/YOURTHEME/css/print.css&quot;,&quot;print&quot;);	&lt;/textarea&gt; &lt;/p&gt;</description>
			<pubDate>Wed, 16 Dec 2009 00:00:00 -0600</pubDate>
			
			
			<guid>http://www.ssbits.com/using-a-print-stylesheet/</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>
		

	</channel>
</rss>
