Category: SilverStripe

  • Posting links to Facebook from PHP – not from localhost

    While working on an update for one of my SilverStripe modules, I came upon a problem which held me back for some time, changing configurations and doing all kinds of tests.

    The thing is – Facebook engine verifies the link you’re trying to post, so the post from localhost fails with “Unknown error”. You need to give Facebook a link it can scrape and extract meta data from it, so for testing purposes use an existing link, and when your test is online try how the regular link would work.

  • Silverstripe 3.0 Grid Fields with Thumbnails

    Silverstripe 3 has been out for a few months now, and most the big bugs have been fixed… This tutorial describes the code required to set up a grid field to manage ‘Has Many’ relations. Eg – a staff page that lists many staff members.

    Also, we’ll add in some code that will make the table list in the CMS display a wee thumbnail of each staff member – making it much easier for CMS users to manage the content.

    Create the Staff Page

    Create StaffPage.php

    This is the page on your site that contains all the staff page. Eg, one ‘staff page’ -> ‘has many’ -> ‘staff members’

    class StaffPage extends Page
    {
        public static $has_many = ['StaffMembers' => 'StaffMember'];
    
        public function getCMSFields()
        {
            $fields = parent::getCMSFields();
    
            $gridFieldConfig = GridFieldConfig_RecordEditor::create();
            $gridfield = new GridField("StaffMembers", "StaffMember", $this->StaffMembers, $gridFieldConfig);
            $fields->addFieldToTab('Root.Staff', $gridfield);
            return $fields;
        }
    }
    
    class StaffPage_Controller extends Page_Controller
    {
    }

    Create the Staff Member Object

    Create StaffMember.php

    class StaffMember extends DataObject
    {
        public static $db = ['Name' => 'Varchar', 'Details' => 'HTMLText'];
        public static $has_one = [
            'Image' => 'Image',
            'Page'  => 'Page',
        ];
    }

    This is the object that contains data about each staff member. They belong to a Staff Page

    In the code above, we simple create a DataObject, and define two feilds, a name, and a details.

    We also attach the DataObject to an image object (allowing us to add an image) and to a page – this forms the connection with the Staff page it belongs to

    At this stage, upload the files, run a dev/build and create a ‘staffPage’ in your CMS. You should be able to see a datagrid and start adding staff members and photos.

    Displaying Thumbnails in your Grid Field in the CMS

    // Summary fields
    public static $summary_fields = ['Thumbnail' => 'Thumbnail', 'Name' => 'Name'];
    
    public function getThumbnail()
    {
        return $this->Image()->CMSThumbnail();
    }

    However, the grid field will probably just list your items by their ID – which is a bit meaningless.

    By adding the code above into the StaffMember.php file,  we can define the fields (summary_fields) that are shown on the summary table in the CMS.

    The function getThumbnail takes the attached Image, and creates a thumbnail to use in the summary fields.

  • Silverstripe 3 – Per user page access permissions

    Most of the times group access in SilverStripe is sufficient for controlling user access, but for this project I had a specific situation where every user needs to have a dedicated page. In order to avoid unnecessary editing and creating groups for each individual user, I decided to extend SiteTree and create a page with per user access control.

    So, I’ve created a new page type, since I only needed it for a single page type, but you can extend/decorate SiteTree any way you like.

    Let’s get to the code. This goes to mysite/code/FilePage.php

    <?php
    
    class FilePage extends Page {
    
    	static $db = array(
    		"CanViewTypeExtended" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, OnlyTheseMembers, Inherit', 'Inherit')",
    	);
    
    	static $has_one = array(
    		"ViewerMember" => "Member",
    	);
    
    	public function getSettingsFields(){
    		global $project;
    		$fields = parent::getSettingsFields();
    
    		Requirements::javascript($project.'/javascript/CMSMain.EditFormMember.js'); //Not sure where to put this :)
    
    		$members = Member::map_in_groups();
    		$field = new DropdownField("ViewerMemberID", "Viewer Members", $members);
    		$field->setEmptyString('(Select one)');
    		$fields->addFieldToTab("Root.Settings", $field, 'CanEditType');
    
    		$viewersOptionsField = $fields->removeByName('CanViewType');
    		$viewersOptions = new OptionsetField(
    						"CanViewTypeExtended", 
    						_t('SiteTree.ACCESSHEADER', "Who can view this page?")
    					);
    		$viewersOptionsSource = array();
    		$viewersOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
    		$viewersOptionsSource["Anyone"] = _t('SiteTree.ACCESSANYONE', "Anyone");
    		$viewersOptionsSource["LoggedInUsers"] = _t('SiteTree.ACCESSLOGGEDIN', "Logged-in users");
    		$viewersOptionsSource["OnlyTheseUsers"] = _t('SiteTree.ACCESSONLYTHESE', "Only these people (choose from list)");
    		$viewersOptionsSource["OnlyTheseMembers"] = _t('SiteTree.ACCESSONLYTHESEMEMBERS', "Only these members (choose from list)");
    		$viewersOptions->setSource($viewersOptionsSource);
    		$fields->addFieldToTab("Root.Settings", $viewersOptions, 'ViewerGroups');
    
    		return $fields;
    	}
    
    	//	Override the SiteTree canView function to implement the new variable
    	public function canView($member = null) {
    		if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) {
    			$member = Member::currentUserID();
    		}
    
    		// admin override
    		if($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) return true;
    
    		// Standard mechanism for accepting permission changes from extensions
    		$extended = $this->extendedCan('canView', $member);
    		if($extended !== null) return $extended;
    
    		// check for empty spec
    		if(!$this->CanViewTypeExtended || $this->CanViewTypeExtended == 'Anyone') return true;
    
    		// check for inherit
    		if($this->CanViewTypeExtended == 'Inherit') {
    			if($this->ParentID) return $this->Parent()->canView($member);
    			else return $this->getSiteConfig()->canView($member);
    		}
    
    		// check for any logged-in users
    		if($this->CanViewTypeExtended == 'LoggedInUsers' && $member) {
    			return true;
    		}
    
    		// check for specific groups
    		if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);
    		if(
    			$this->CanViewTypeExtended == 'OnlyTheseUsers' 
    			&& $member 
    			&& $member->inGroups($this->ViewerGroups())
    		) return true;
    
                    //Check for specific Member
    		if(
    			$this->CanViewTypeExtended == 'OnlyTheseMembers' 
    			&& $member 
    			&& $member->ID == $this->ViewerMemberID
    		) return true;
    
    		return false;
    	}
    }

    So, let’s break it down a bit:

    I’ve created a new variable – CanViewTypeExtended to replace SiteTree’s CanViewType, since I couldn’t find the way to add an option (If you know a way, feel free to drop a line). It replicates SiteTree’s CanViewType with OnlyTheseMembers option added, which is our per user access type.

    Then, we have has one ViewerMember, which holds actual user ID for single user (I’ve limited it to one user, since using any more would be a group).

    The rest is pretty basic, mostly copied from SiteTree with a bit of additions, getSettingsFields  is a standard function for updating the Settings tab in CMS. There we have first included the JavaScript file, which will be shown later, and is only for decoration – showing and hiding fields based on selection.

    Then we have created Member selection field, to pick a member to which the access will be granted, and replicated creating CanViewType field from SiteTree.php with addition of our new OnlyTheseMembers option.

    After this is saved, all that is left is to check user permissions in canView() method. Since we don’t use CanViewType any more, but have replaced it with CanViewTypeExtended, the entire function is copied from SiteTree.php, except for the last part which grants the access if current member is our selected member:

    if($this->CanViewTypeExtended == ‘OnlyTheseMembers’ && $member && $member->ID == $this->ViewerMemberID)
    return true;

    So, here’s the remaining js file, which goes to mysite/javascript/CMSMain.EditFormMember.js

    /**
     * File: CMSMain.EditFormMember.js
     */
    (function($) {
    	$.entwine('ss', function($){
    		/**
    		 * Class: .cms-edit-form #CanViewTypeExtended
    		 * 
    		 * Toggle display of Member dropdown in "access" tab,
    		 * based on selection of radiobuttons.
    		 */
    		$('.cms-edit-form #CanViewTypeExtended').entwine({
    			// Constructor: onmatch
    			onmatch: function() {
    				// TODO Decouple
    				var dropdownMembers = $('#ViewerMemberID');
    				var dropdownGroups = $('#ViewerGroups');
    
    				this.find('.optionset :input').bind('change', function(e) {
    					var wrapper = $(this).closest('.middleColumn').parent('div');
    					var wrapper2 = dropdownMembers.closest('.middleColumn').parent('div');
    					if(e.target.value == 'OnlyTheseMembers') {
    						wrapper.addClass('remove-splitter');
    						dropdownMembers['show']();
    						dropdownGroups['hide']();
    					}else if(e.target.value == 'OnlyTheseUsers') {
    						wrapper.addClass('remove-splitter');
    						dropdownMembers['hide']();
    						dropdownGroups['show']();
    					}else{
    						wrapper.removeClass('remove-splitter');
    						dropdownMembers['hide']();
    						dropdownGroups['hide']();
    					}
    				});
    
    				// initial state
    				var currentVal = this.find('input[name=' + this.attr('id') + ']:checked').val();
    				dropdownMembers[currentVal == 'OnlyTheseMembers' ? 'show' : 'hide']();
    				dropdownGroups[currentVal == 'OnlyTheseUsers' ? 'show' : 'hide']();
    
    				this._super();
    			},
    			onunmatch: function() {
    				this._super();
    			}
    		});
    
    	});
    }(jQuery));

     

  • Install Tidy on Ubuntu

    Not something big or important, but I just tried several ways to get tidy library on my Ubuntu box, and none other then this worked. Might save some searching to others.

    sudo apt-get install php5-tidy

    Tidy is recommended for running SilverStripe (not required).

    php5 comes with Tidy library preinstalled but it has to be compiled –with-tidy which is sometimes skipped (well, on the box I’m setting up for example).

  • Silverstripe 3 Grid Field Config Options

    The grid field system in Silverstripe 3 has a number of preset config options – here’s what they do, and what they look like:

    No Config

    If we don’t add any config options, the grid field just displays a list of items…  We can’t view the record details, or edit anything though…

    $gridfield =newGridField("RegisterEvents","RegisterEvent", $this->RegisterEvents());
    $fields->addFieldToTab('Root.Events', $gridfield);

    grid1

    $gridfield = new GridField(“RegisterEvents”, “RegisterEvent”, $this->RegisterEvents());
    $fields->addFieldToTab(‘Root.Events’, $gridfield);

    GridFieldConfig_RecordViewer

    $gridFieldConfig =GridFieldConfig_RecordViewer::create();
    $gridfield =newGridField("RegisterEvents","RegisterEvent", $this->RegisterEvents(), $gridFieldConfig);
    $fields->addFieldToTab('Root.Events', $gridfield);

     

    This option just adds in the ability to click the magnifying glass and view the details of each item – but not edit anything.

    grid2

    GridFieldConfig_RecordEditor

    $gridFieldConfig =GridFieldConfig_RecordEditor::create();
    $gridfield =newGridField("RegisterEvents","RegisterEvent", $this->RegisterEvents(), $gridFieldConfig);
    $fields->addFieldToTab('Root.Events', $gridfield);

     

    Now we’re starting to get something useful – a list that lets us Add, View, and Remove records…

    grid3

    GridFieldConfig_RelationEditor

    The final option is the Relation Edition set up. This adds features to work with ‘has-many’ and ‘many-many’ relationships.

    See our article about Many_Many relations for more details

    Custom Config

    Or, you can just create an empty configuration, and add the bits you need….

    $gridFieldConfig =GridFieldConfig::create()->addComponents(
    newGridFieldToolbarHeader(),
    newGridFieldAddNewButton('toolbar-header-right'),
    newGridFieldSortableHeader(),
    newGridFieldDataColumns(),
    newGridFieldPaginator(15),
    newGridFieldEditButton(),
    newGridFieldDeleteAction(),
    newGridFieldDetailForm(),newGridFieldFilterHeader(),
    );

     

  • Pitch & Tone • Silverstripe caching

    In this post we’re going to cover how silverstripe’s caching works. There are two ways to cache, both have pros and cons.

    Static caching
    Quite simply static caching saves the HTML output via php to a static HTML file. Silverstripe then monitors (via some functions you create/edit) when content is updated so that if an HTML file in the cache becomes out of date it can be regenerated. As the cached file is a complete HTML page it means performance is incredibly fast. Rather than hundreds of milliseconds or even seconds of php execution the page now the site will respond is less than 20 milliseconds…

    Read more on: Pitch & Tone • Silverstripe caching.

  • Map Module

    One of the HotelCMS modules is the map module, enabling hotels to showcase nearby places of interest.

    All the places are displayed on the map, with distance from hotel shown, using custom icon set and customized info window.

    Both front and back end use the latest Google Maps API – V3.

    You can check out an example on:

    88Rooms Hotel

  • Extending SilverStripe 3 Site Settings

    When you first install your SilverStripe site, the ‘Settings’ menu is pretty limited, offering just Title, Tagline, and a choice of templates.

    This is fine, but it’s often useful to add other options, such as logo upload, links to facebook, uploading a banner image that appears on every page.

    MySiteConfig.php

    In your ‘mysite/code/’ folder, create a new file called ‘MySiteConfig.php’, and insert the following text:

    <?php
    
    class MySiteConfig extends DataExtension {     
    
    	 public static $has_one = array(
    		'HeaderImage' => 'Image',
    		'LogoImage' => 'Image',		
    	);
    
    	 public static $db = array(			
    		'FacebookURL' => "Varchar(250)",
    		'VimeoURL' => "Varchar(250)",				
    	  );
    
        public function updateCMSFields(FieldList $fields) {
    	   $fields->addFieldToTab("Root.Main", new UploadField("LogoImage", "Choose an image for your site logo"));
    	   $fields->addFieldToTab("Root.Main", new UploadField("HeaderImage", "Choose an image for the site header"));
    	   $fields->addFieldToTab("Root.Main", new TextField("FacebookURL", "Enter the full URL of your Facebook page"));	 
    	   $fields->addFieldToTab("Root.Main", new TextField("VimeoURL", "Enter the full URL of your Vimeo page"));	
        }
    }

    This file creates a ‘DataExtension’. A DataExtension, as the name suggests, is used to extend any set of data. You declare the additional databse fields and relations just as you would when creating a new page.

    And you use the function ‘public function updateCMSFields {}’ to add the fields to the CMS page, just as you would when creating a new page.

    _config.php

    In your ‘mysite’ folder, open the ‘_config,php’ file, and add the following line:

    Object::add_extension('SiteConfig', 'MySiteConfig');

    This line simple tells SilverStripe to take the ‘SiteConfig’ (the basic settings that already exist) and add ‘MySiteConfig’ to it.

    Dev/Build

    Go to your site, log in, and add ‘/dev/build’ to the URL – eg ‘mysite.com/dev/build’ to refresh the database.

    Now, in the admin area, go to the site settings page and you should see your new options.

    Using these fields in the Template

    You can now add a logo, banner image, and enter the facebook / vimeo links – the data is stored in the database, but we need to show it on our page.

    We do this by adding ‘$SiteConfig.’ in front of the names of the fields. Eg:

    $SiteConfig.LogoImage.SetRatioSize(250,250)
    <a href="$SiteConfig.FacebookURL">Visit my facebook page</a>
  • Silverstripe 3 Many Many – A Comprehensive Example

    I see that the website hosting this resource has disappeared, so I’ll try to save this article, as it might come in handy. The images are gone, so I’m only able to save low quality versions.

    How to create set up a many_many relation using the SS3 gridfield

    In this article we’re going to a create a very simple shop. We’ll create pages for various product categories. And we’ll create products. Some our products will appear in more than one category.

    Eg. Each ‘Category’ -> has many-> Products
    and each ‘Porduct’ -> has many -> ‘Categories’

    This is many_many relationship. In the database, we end with 3 tables – one containing the Categories, one containing the Products, and one table which contains a list of ‘links’ between the products and categories.

    Create the Category Page

    Create ProductCategoryPage.php

    This is the page on your site that will list the the products.

    We define the many_many relation to the product, and then set up the gridfield to allow us to manage the products in the CMS.

    We use the config option ‘GridFieldConfig_RelationEditor’ to make the datagrid show the right options. We also add “GridFieldDeleteAction(‘unlinkrelation’)” to the config. This allows us to ‘delete’ a product altogether (from all categories), or just remove (unlink) it from the category page we’re editing.

    <?php
    class ProductCategoryPage extends Page {
    
    public static $many_many = array(
    'Products' => 'Product'
    );
    
    public function getCMSFields() {
    $fields = parent::getCMSFields();
    
    $gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
    new GridFieldDeleteAction('unlinkrelation')
    );
    
    $gridfield = new GridField("Products", "Product", $this->Products(), $gridFieldConfig);
    $fields->addFieldToTab('Root.Products', $gridfield);
    return $fields;
    
    }
    
    }
    
    class ProductCategoryPage_Controller extends Page_Controller {
    
    public static $allowed_actions = array (
    );
    
    public function init() {
    parent::init();
    }
    
    }

     

    Create the Product Object

    Create Product.php

    This is the object that contains data about each product – at this stage we simply have a title and a photograph.

    We also define the fact that this item belongs to a many_many – this ties the two parts together nicely.

    We’ll also define the summary fields shown in the CMS – or else it will just show the ID, which isn’t much use.

    We’re adding a function here to show a thumbnail image – why not – it looks nice!

    And finally, we’re calling getCMSFields, and setting up a datagrid so that when we’re on a product page, we get a nice table showing us what categories are related to that product.

    <?php
    
    class Product extends DataObject {
    
    public static $db = array(
    'Title' => 'Varchar'
    );
    
    // One-to-one relationship with profile picture and contact list page
    public static $has_one = array(
    'Image' => 'Image'
    );
    
    public static $belongs_many_many = array(
    'ProductCategoryPage' => 'ProductCategoryPage'
    );
    
    // Summary fields
    public static $summary_fields = array(
    'ID' => 'ID',
    'Thumbnail' => 'Thumbnail',
    'Title' => 'Title'
    
    );
    
    public function getThumbnail() {
    return $this->Image()->CMSThumbnail();
    }
    
    public function getCMSFields() {
    $fields = parent::getCMSFields();
    
    $gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
    new GridFieldDeleteAction('unlinkrelation')
    );
    $gridFieldConfig->removeComponentsByType('GridFieldAddNewButton');
    
    $gridfield = new GridField("ProductCategoryPage", "ProductCategoryPage", $this->ProductCategoryPage(), $gridFieldConfig);
    $fields->addFieldToTab('Root.ProductCategoryPage', $gridfield);
    return $fields;
    
    }
    
    }

     

    At this stage, upload the files, run a dev/build and create a few ‘ProductCategoryPages’ in your CMS.

    Adding New Products

    In your CMS, choose one of the new ‘Product Category’ Pages. And move to the ‘Products’ tab.

    resizedimage400231-add-product

    You should see a screen similar to the image above

    Click ‘add product’

    resizedimage400231-add-product-2

    Give the new product a title, in this case we’ll call it ‘Wheel’ – but don’t bother adding an image. You’ll need to click ‘Create’ first, and then add the image, then ‘Save’ it again.

    resizedimage400231-add-product-3

    Once you’ve saved your product, click ‘Back’ at the top of the page to return to the main category page

    Adding an existing product to another category

    Now that we have a product, we can add this product to another category by going to the other page, and again, to the ‘products’ tab

    resizedimage400231-add-product-4

    Here is another list of products – this time though, instead of clicking ‘Add Product’ to create a new products, we’ll search for an existing product – in this case ‘wheel’.

    resizedimage40063-add-product-5

    Enter the first few letters of the product in the box next to ‘add product’ and Silverstripe will come with a list of items that match the search. Click on the item you want to add, and then click ‘Link Existing’. The item will now appear in both category pages.

    Removing an Item

    At the start of this article we added

    new GridFieldDeleteAction('unlinkrelation')

    It’s now that this little line becomes important. It adds an extra icon into right column

    add-product-6

    These icons are ‘Edit’, ‘Delete’, and ‘Unlink’

    ‘Delete’ will remove the product from the database – and remove it from any categories it belong to.

    The ‘Unlink’ button allows you to remove it from just this page while leaving it in the database for any other pages that it might belong to.

    Note. If you click on an item, and then click on the ‘product category’ tab in the top right – you’ll see a list of pages this item belongs to, where you can also unlink a category from the product, or attach a new category. However note that if you click the ‘delete’ button here you will be deleting a page off your site.

    Creating the Templates

    Now come the fun bit – making the template to display the information.

    The details will of course depend how you want your page to look. This is very basic example

    Create ProductCategoryPage.ss with your templates Layout folder

    Something like the following will be enough to show your products…

    <% loop Products %>
    
    <h3>$Title</h3>
    $Image.SetWidth(100)
    
    <% end_loop %>

     

  • SilverStripe 3.0 – Batch Translate

    As a common request around the web is to translate the entire Site Tree. Following the post on: https://www.silverstripe.org/customising-the-cms/show/7318 Which was made for SS 2.3.2, I’ve adopted the code to work with SilverStripe 3.0 (Tested on 3.0.5). It gives you the ability to select entire site tree, or just parts of it, and translate it to draft or to published site. (more…)