10
Feb
8
Display DataObjects in an SEO friendly way
Regarding this 'Need to know' tutorial from Aram, I would like to extend this principle by explaining how to display DataObjects in a SEO friendly way.
There are several ways to do this. I will explain one I prefer the most, by using SiteTree::GenerateURLSegment();.
First step is to add an extra field to the Database called URLSegment. If you use Aram's files in his tutorial on displaying DataObjects, your StaffMember $db array will look like this:
mysite/code/StaffMember.php
To add the URLSegment on save automatically, add the onBeforeWrite() method to the StaffMember class:
mysite/code/StaffMember.php
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 !="This->ID". 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.
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.
And finally, just in case when no Name is entered, we create an unique URLSegment, by using the ClassName and ID.
The Next step is to change the StaffMember links in the StaffSideBar.ss file by replacing {$ID} to {$URLSegment} .
The last step is to change the function getIndividualStaffMember() in StaffPage_Controller:
mysite/code/StaffPage.php
Instead of getting the DataObject by id, we simply use DataObject::get_one() and filter by URLSegment.
What about searchengines and old urls?
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!
Use a 301 redirect
One way to solve this issue, is to just get the StaffMember by id and attach the URLSegment to the StaffMember link : /{$ID}/{$URLSegment} .
Your getIndividualStaffMember function in StaffPage_controller will then look like this :
mysite/code/StaffPage.php
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') . 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.
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') .
Combine ID and URLSegment
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:
mysite/code/Staffmember.php
In StaffSidebar.ss replace {$ID} with {$URL} to display the combined URL. () you could also skip the function and use {$ID}-{$URLSegment} instead.
Use this function in StaffPage_Controller to get the Member:
mysite/code/StaffPage.php
We use strpos() to get the first position of - and use substr() to split the URL to the $ID and $URLSegment.
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!
About the Author
Name: Martijn van Nieuwenhoven
Website: http://www.mediavacature.nl
Martijn van Nieuwenhoven runs the largest Dutch Jobboard for Media, Marcom and Graphic related jobs. He also develops websites and applications for small business companies based in The Netherlands.
Comments (8)
-
In 2.4 you can easily use the below code. The only downside is you will need to create a page of the type MembersHolder and access elements by
yoursite.com/member/show/memberurlsegment
The is a way to get rid of the show action by reading all possible actions in the init function of the MembersHolder and adding them to the allow_actions variable. I didn't go for this option as I suspect that this will negatively influence the sites performance as it will always be read.
<?php
/**
* Action holder to allow Member DataObjects to be shown as Pages on site.
* Cannot be deleted / created by users other than administrators.
*/
class MembersHolder extends Page {
static $singular_name = 'Members Holder';
static $plural_name = 'Members Holders';
static $can_create = false;
static $allowed_children = array();
}
class MembersHolder_Controller extends Page_Controller {
function show($memberURL) {
$URLSegment = Convert::raw2sql(Director::urlParam("ID"));
$member = DataObject::get_one("Member", "URLSegment = '{$URLSegment}'");
if ($member) {
$data = array(
$this,
"Member" => $member
);
return $this->customise($data)->renderWith(array('Member', 'Page'));
} else {
Director::redirect('/page-not-found');
}
}
}
Posted by Marijn Kampf, 15/06/2010 11:15am (1 month ago)
-
@Nick:
I think I would add the URLSegmentField, and create a controller method you call once to update the DataObjects. You basicly need to add the methods and write all the records one time to call the onBeforewrite methods.Posted by Martijn, 05/06/2010 4:02pm (2 months ago)
-
HI, nice solution here!
Is there any way you know of to apply this to objects already in the database??
This is perfect for what I need, but there are already about 1000 dataobjects I want to apply it to....
Posted by nick, 03/06/2010 11:15pm (2 months ago)
-
How would you set up the links if you had a staff holder page, a department page and a staff member dataobject? I currently have the holder page looping through the department pages and displaying the staff member photos by department on the holder page. Now I want to display the staff members on their own page, but there is an extra parameter in the URL, how would I go about doing that?
Posted by mary fran, 10/05/2010 11:34pm (3 months ago)
-
Has anybody had success implementing this on 2.4 beta 2?
Posted by Adam, 29/03/2010 7:48pm (4 months ago)
-
Marcus, there is a static variable you can define in the controller $allowed_actions. Which is an array of function names that can be called via the Action param. see: http://doc.silverstripe.org/doku.php?id=security#limiting_url-access_to_controller_methods
Posted by Dan Hensby, 17/02/2010 5:33pm (5 months ago)
-
Posted by Dan Hensby, 17/02/2010 5:30pm (5 months ago)
-
Great solution to a very common problem.
I ended up needing something like this and did something very similar to this as a solution.
I just wanted to ask about putting data getters (getIndividualStaffMember) in the controller. Any public method that's put into the controller can be accessed through the URL so isn't the model a better fit for these kinds methods?
The lines between the controller and the model tend to blur abit in Silverstripe so I'd like to hear your thoughts on the subject.Posted by Marcus, 11/02/2010 7:04am (6 months ago)
RSS feed for comments on this page RSS feed for all comments