Creating an Event Registration form for Event Calendar
26 Aug
Overview
One question that I find myself answering frequently for users of the EventCalendar module is how to customize the module to handle registrations. A lot of people are inclined to look to the UserForms module, or SilverStripe’s homegrown “Events” module, or even thirdparty solutions to service this need. It’s understandable that we get so many different ideas about how to go about solving this problem. After all, event registration is something that can take on many forms, and doesn’t always follow a pattern suitable for cookie-cutter, turnkey solutions.
But EventCalendar is, itself, guilty of a lot of whiz-bang wizardry that often gets in your way as a developer, so in this tutorial, it’s fitting that we demonstrate something basic that will serve the needs of a website managing a light amount of registrations. The goals of this exercise will include:
- Creating a set of subclasses for the EventCalendar module
- Adding a “register” link to each event
- Handling a registration through a public-facing form, and notifying an administrator
- Saving the registration to the database
- Tracking and enforcing available seats
Step 1: Break the Chains
When customising the EventCalendar module, it is almost always necessary to subclass its components. The advantage of this approach is that we don’t have to concede working with its existing structure. Further, subclasses allow us to insulate our changes, leaving the Event Calendar module in a position where upgrades are possible without breaking anything or reverting our customisations.
In this example, we have a website who is managing registrations for conferences. Depending on your use case, you’ll probably want to substitute the word “Conference” with something else when naming all of these classes.
In your code directory, create the following classes:
ConferenceHolder.php
class ConferenceHolder extends Calendar {
static $has_many = array (
'Conferences' => 'Conference'
);
static $allowed_children = array('Conference');
static $hide_ancestor = 'Calendar';
}
class ConferenceHolder_Controller extends Calendar_Controller {
}
Nothing too special here. We’ve simply subclassed the largest component of EventCalendar (Calendar) and created our own version. We use $allowed_children to ensure nothing else sneaks into the hierarchy, and another useful SiteTree property $hide_ancestor, which is useful when subclassing modules. This will hide the parent class “Calendar” from the create dropdown in the CMS. It exists only as a parent class to our customised version.
The $has_many relationship to Conferences may seem a bit awkward, and that’s because, well, it is. Conferences is the child of this class, so no $has_many should really be necessary, but this is done to accommodate some poorly-informed coding that went into building EventCalendar, so let’s just consider it legacy support for now.
Conference.php
class Conference extends CalendarEvent {
static $db = array (
'Cost' => 'Currency'
);
static $has_many = array (
'DateTimes' => 'ConferenceDateTime'
);
static $has_one = array (
'ConferenceHolder' => 'ConferenceHolder'
);
static $can_be_root = false;
static $hide_ancestor = 'CalendarEvent';
public function getCMSFields() {
$f = parent::getCMSFields();
$f->addFieldToTab("Root.Content.Main", new CurrencyField('Cost',_t('Conference.COST','Conference Cost')),'Content');
return $f;
}
}
class Conference_Controller extends CalendarEvent_Controller {
}
We’ve done a little bit more customisation in this subclass, adding the cost of the conference. We set up the custom DateTime class in the $has_many relation, and we tie back the seemingly unnecessary $has_many from ConferenceHolder with a reciprocating $has_one. The ancestor “CalendarEvent” is hidden, and for good measure, we ensure this page type cannot be created without a parent.
ConferenceDateTime.php
class ConferenceDateTime extends CalendarDateTime {
static $db = array (
'TicketsAvailable' => 'Int'
);
static $has_one = array (
'Conference' => 'Conference'
);
public function extendTable() {
$this->addTableTitles(array(
'TicketsAvailable' => _t('Conference.TICKETSAVAILABLE','Tickets available')
));
$this->addPopupFields(array(
new NumericField('TicketsAvailable', _t('Conference.TICKETSAVAILABLE','Tickets available'))
));
}
public function CanRegister() {
return $this->TicketsAvailable > 0;
}
public function RegisterLink() {
return $this->Conference()->Parent()->Link("register")."?DateID=$this->ID";
}
}
Every event has multiple dates, so all of the date and time information is stored on a separate table in EventCalendar. In the above example, we’ve customised the CalendarDateTime class to fit our Conference model. Because tickets will be available for a particular date and time, the “TicketsAvailable” field goes in this class. An simple example of this model is a movie theater, showing a single movie at multiple dates and times. The movie is the CalendarEvent, the individual showings are the CalendarDateTimes, and the theater itself is the Calendar.
The extendTable() method is a bit curious, and is another relic of the days when I didn’t quite get SilverStripe. My thinking here was that the user shouldn’t have to concern himself with messing with the DataObjectManager to manage dates, so I created a simple API for updating it. It is what it is. Fortunately, it’s pretty self-explanatory.
In the RegisterLink() function, we pass link a controller action “register” and an ID. We’ll deal with that later. I’ve also added a CanRegister() function just in case the TicketsAvailable field gets messed up and goes below zero or something strange.
Now that the datamodel is in place, run a /dev/build.
If everything looks good, go into the CMS and create a ConferenceHolder with a few child Conference pages, and assign a few dates to each Conference.
The data model is complete!
Step Two: Setting Up the Templates
Looking back on the Conference class, we created a RegisterLink() function that will serve as a template accessor for the registration link for the given event. That means it’s time to update the templates.
Create:
- /themes/your_theme/templates/Layout/ConferenceHolder.ss
- /themes/your_theme/templates/Layout/Conference.ss
In those files, paste in the contents from:
- /event_calendar/templates/Layout/Calendar.ss
- /event_calendar/template/Layout/CalendarEvent.ss
Now run ?flush=1. SilverStripe will now use these custom templates to override those in the core. If you are like me, and don’t use themes, substitute /themes/your_theme/ with /mysite/, or whatever your project directory is.
Customise the templates structurally as you see fit. For these next changes, I’ll just post the relevant snippets:
ConferenceHolder.ss
Conference.ss
$_Dates
($Cost.Nice)
- $_Times
Now we have registration links for all of our dates. If you’ve clicked on any, you’ll probably get an error. That’s because we need to update our controller to accept the register action. We’ll do that in the next step.
Step Three: Customising the Controller
With the new register action for ConferenceHolder, it is necessary to add the function to the $allowed_actions array. This array tells SilverStripe which member functions are allowed to be executed from the URL. For security purposes, most methods are not.
ConferenceHolder.php
class ConferenceHolder_Controller extends Calendar_Controller {
static $allowed_actions = array (
'register',
'RegistrationForm'
);
}
We’ll need the RegistrationForm() function later. Next, let’s create the register() function itself in that same controller.
public function register(SS_HTTPRequest $request) {
if(!$request->requestVar('DateID')) {
return Director::redirectBack();
}
return array();
}
This controller action, referenced in ConferenceDateTime::RegisterLink(), does a sanity check to make sure we were passed a DateID in the request, and then returns the template. In this case, the template that the request handler is going to look for is ConferenceHolder_register.ss. Whenever a controller action is used, SilverStripe will first look for _[function name] first, and fall back on the main template.
All controller actions must return an array of template variables and their values to the template. In this case, since we’re going to define RegistrationForm() as a public function in the controller anyway, it’s not necessary to return anything, so an empty array is all we need.
Now let’s build the form, in that same controller.
public function RegistrationForm() {
$date_id = (int) $this->getRequest()->requestVar('DateID');
if(!$date = DataObject::get_by_id("ConferenceDateTime", $date_id)) {
return $this->httpError(404);
}
$date_map = array();
if($conference = $date->Conference()) {
if($all_dates = $conference->DateTimes()) {
$date_map = $all_dates->toDropdownMap('ID','DateLabel');
}
}
return new Form (
$this,
"RegistrationForm",
new Fieldset (
new TextField('Name', _t('Conference.Name','Name')),
new EmailField('Email', _t('Conference.EMAIl','Email')),
new DropdownField('DateID', _t('Conference.CHOOSEDATE','Choose a date'), $date_map, $date_id)
),
new FieldSet (
new FormAction('doRegister', _t('Conference.REGISTER','Register'))
),
new RequiredFields('Name','Email','DateID')
);
}
The first thing we have to do is check if a DateID was sent. Keep in mind, this can come one of two ways: 1) through the query string in the URL, if the user is coming from a registration link, and 2) as posted form data after the user has filled out the form. RegistrationForm() is actually run again when the form is posted. Therefore, it’s important that the DateID field in the form matches the DateID param that we’re using the in URL. It makes verification a lot easier when the request param only has one name.
If the DateID is passed, but doesn’t exist in the database, a 404 error is thrown. Realistically, that should never happen.
As a usability enhancement, I’ve made the DateID a dropdown in the form, containing all of the other dates related to the same conference. If your events only have one date, then it might make more sense to use a HiddenField for DateID. Notice in the toDropdownMap() function, we use a custom getter, DateLabel. We use this custom getter to render dynamic text for each option in the dates dropdown. Let’s look at the getDateLabel() function.
ConferenceDateTime.php
public function getDateLabel() {
return $this->obj('StartDate')->Format('d-m-Y').", " . $this->obj('StartTime')->Nice24() . " : (" . sprintf(_t('Conference.TICKETSREMAINING','%d tickets remaining'), $this->TicketsAvailable).")";
}
The label is a concatenation of the start date, the time, and the number of tickets remaining, in parenthesis.
Now let’s set up a handler for the form. It would be nice if the registrations saved to the database, in a table on the ConferenceHolder page in the CMS. Let’s define a ConferenceRegistration dataobject.
ConferenceRegistration.php
class ConferenceRegistration extends DataObject {
static $db = array (
'Name' => 'Varchar',
'Email' => 'Varchar'
);
static $has_one = array (
'Date' => 'ConferenceDateTime',
'ConferenceHolder' => 'ConferenceHolder'
);
static $summary_fields = array (
'Name' => 'Name',
'Email' => 'Email',
'ConferenceLabel' => 'Conference',
'DateLabel' => 'Date'
);
public function getConferenceLabel() {
if($this->Date()) {
return $this->Date()->Conference()->Title;
}
}
public function getDateLabel() {
if($this->Date()) {
return $this->Date()->_Dates();
}
}
}
The $db array contains information about the user who has signed up, and of course, we have a relation to ConferenceDateTime to tell the administrator for which date the user registered. There are also a couple of custom getters here that are used in the $summary_fields array to create some nice labels for what will be a DataObjectManager on the ConferenceHolder page.
ConferenceHolder_Controller
public function doRegister($data, $form) {
// Sanity check
if(!isset($data['DateID'])) {
return Director::redirectBack();
}
if(!$date = DataObject::get_by_id("ConferenceDateTime", (int) $data['DateID'])) {
return $this->httpError(404);
}
$conference = $date->Conference();
// Save the registration
$form->saveInto($reg = new ConferenceRegistration());
$reg->ConferenceHolderID = $date->Conference()->ParentID;
$reg->write();
// Decrease the tickets available
$date->TicketsAvailable--;
$date->write();
// Email the admin
$email = new Email($data['Email'], "administrator@yoursite.com", "Event Registration: {$conference->Title}");
$email->ss_template = "ConferenceRegistration";
$email->populateTemplate(array(
'Registration' => $reg
));
$email->send();
$form->sessionMessage(_t('Conference.THANKYOU','Thank you for signing up!'),'good');
return Director::redirectBack();
}
After validating the date ID, ensuring it is present and in the database, we see the benefit of creating a ConferenceRegistration object. The form saves into it easily. That’s because all of the form fields (Name, Email, and DateID) all correspond with fields on the ConferenceRegistration object. If we had named the field “FirstName” instead of “Name” on the form, it wouldn’t work, and we would have to set the field manually.
Next, we decrement the number of tickets available, having confirmed the registration.
Finally, let’s send an email to the administrator. In this case, all we need to pass to the template is the ConferenceRegistration object, since it contains data about both the user and the date registered.
Let’s build a template for that email.
/themes/your_theme/templates/email/ConferenceRegistration.ss
Dear Administrator, $Name has registered for the event $Conference.Title on $Date._Dates
Make sure you run a ?flush=1 to load this new template into the manifest.
Now that all the code is in place for the form, we can add it to its new template:
/themes/your_theme/templates/Layout/ConferenceHolder_register.ss
$RegistrationForm
The last thing we need to do is add the new ConferenceRegistration table to the ConferenceHolder.
ConferenceHolder.php
class ConferenceHolder extends Calendar {
static $has_many = array (
'Conferences' => 'Conference',
'Registrations' => 'ConferenceRegistration'
);
static $allowed_children = array('Conference');
static $hide_ancestor = 'Calendar';
public function getCMSFields() {
$f = parent::getCMSFields();
$f->addFieldToTab("Root.Content.Registrations", new DataObjectManager(
$this,
'Registrations',
'ConferenceRegistration'
));
return $f;
}
}
At this point, you should be ready to test your registration form! That concludes this tutorial. If you have any questions, or if you find an error in the example code, please post a comment using the form below.




Very excited to try this! First attempt I changed Conference to Training, ran dev/build, got Website Error. Then I just copied your code directly, no changes, and dev/build seemed to run fine. Tried to go back to open my site and got the dreaded white screen. When I remove the three files from my mysite/code folder and run dev/build again, all is well. I’m using SS version 2.3.8. Would that make a difference?
Hi, Joni,
All of my tutorials are for 2.4. I can’t really support 2.3 anymore. To ensure you get error reporting, make sure you have your environment set to dev, and your PHP error reporting on. White screens and “website error” are not helpful messages!
So in your _config.php, add:
Director::set_environment_type(‘dev’);
and in your .htaccess, add:
php_value display_errors On
Followed your instructions, got this error when I ran dev/build:
Parse error: syntax error, unexpected ‘=’, expecting ‘)’ in /srv/www/htdocs/staff/mysite/code/ConferenceDateTime.php on line 4
which is ‘TicketsAvailable’ => ‘Int’
I’d love to upgrade to 2.4 but the three times I tried it, my site broke. All the issues I see people entering about it don’t make me feel real confident. When I have more time, maybe I’ll give it another try.
Thanks for all your work.
Joni
I noticed that the code blocks on that page were rendering “>” in place of “>”, so if you did a cut-and-paste job, that’s probably where your error is coming from.
You are the man! Or the Cheese! Whatever… that worked – changing the text to the greater than symbol. Thanks so much. Hopefully tomorrow I’ll have a little time to get on to Steps 2 and 3.
Would this work for a weekly repeating event? Would anything have to change?
Great new Left&Main site. I was searching for Postale, and Register events was on the back of my mind as well.
Graphic Agenda began using Silverstripe at the turn of 2.4 and hasn’t looked at using anything else since.
Hi, Greg,
To answer your questions, no it won’t work, and yes, a lot would have to change. Repeating events create “phantom” dates that do not actually have a record in the database, so everything we’re doing involving retrieving a date and its event by the DateID does not apply. You would have to get around that by padding a composite EventID and Date (string) to the form.
Maybe I could write a revision that is repeating-event safe.
Hi i tried to do this in the same manner as you. but i am unable to assign the dates from admin to the events. and in the front end the registration link gives 404 error http://74.200.74.138/~cumberla/events/chamber/register?DateID=18. Please help me
ConferenceRegistration.ss what code should have here?
How to get comfrace cost in email .ss template i am getting all registration values but don’t know how to get cost…
Please help…..
Thanks
Sunil Kasana
http://74.200.74.138/~cumberla/events/chamber/view/2010-10-21 gives page not found error please help
Hi there, thanks for this tutorial and you modules. I have downloaded the code and dev built and all is good. I do get a 404 when i try to click the register link, like Sunil above. I’m on the r521 dataobject manager, SS 2.4.3 and the event calendar module from this site. I don’t seem to be getting any other errors, any ideas?
Cheers
Mick
Hi Uncle!
I’m working on PostgreSQL quite successfully using SilverStripe 2.4 branch. I have helped port the ecommerce and page-elements modules. Since SS 2.4 there’s been a lot of work on getting modules to be more database agnostic. I checked out trunk of event_calendar and dataobject_manager today and have got a fair way through making the code PostgreSQL friendly (ie. DB agnostic) while still keeping it running with MySQL. I’m now having a few problems with the GROUP BY clauses, and it looks like a combination of the augmentedSQL in SortableDataObject and the buildSQL in DataObject. I can supply some of my debugging code and SQL samples if you like, but probably would be better off forum? Would it be possible to contact you directly via email to discuss in more detail?
Cheers,
Tony
Hi Uncle Cheese,
I’m in the process of re-creating my 2.3.8 site for the specific purpose of using this module. It’s happily humming along at version 2.4.4, and I’ve just started with your Step 1, and ran the dev/build with success. When I try to create a Conference Holder, I get the following error:
ERROR [User Error]: Bad class to singleton() – ConferenceRegistration
IN POST /staff242/admin/getitem?ID=57&ajax=1
Line 346 in /srv/www/htdocs/staff242/sapphire/core/Core.php
Source
======
337: *
338: * @param string $className
339: * @return Object
340: */
341: function singleton($className) {
342: global $_SINGLETONS;
343: if(!isset($className)) user_error(“singleton() Called without a class”, E_USER_ERROR);
344: if(!is_string($className)) user_error(“singleton() passed bad class_name: ” .
var_export($className,true), E_USER_ERROR);
345: if(!isset($_SINGLETONS[$className])) {
* 346: if(!class_exists($className)) user_error(“Bad class to singleton() – $className”, E_USER_ERROR);
347: $_SINGLETONS[$className] = Object::strong_create($className,null, true);
348: if(!$_SINGLETONS[$className]) user_error(“singleton() Unknown class ‘$className’”, E_USER_ERROR);
349: }
350: return $_SINGLETONS[$className];
351: }
352:
Trace
=====
user_error(Bad class to singleton() – ConferenceRegistration,256)
line 346 of Core.php
singleton(ConferenceRegistration)
line 96 of DataObjectManager.php
DataObjectManager->__construct(ConferenceHolder,Registrations,ConferenceRegistration)
line 18 of ConferenceHolder.php
ConferenceHolder->getCMSFields(CMSMain)
line 437 of CMSMain.php
CMSMain->getEditForm(57)
line 1039 of LeftAndMain.php
LeftAndMain->EditForm()
line 389 of LeftAndMain.php
LeftAndMain->getitem(SS_HTTPRequest)
line 193 of Controller.php
Controller->handleAction(SS_HTTPRequest)
line 143 of RequestHandler.php
RequestHandler->handleRequest(SS_HTTPRequest)
line 147 of Controller.php
Controller->handleRequest(SS_HTTPRequest)
line 282 of Director.php
Director::handleRequest(SS_HTTPRequest,Session)
line 125 of Director.php
Director::direct(/admin/getitem)
line 127 of main.php
Does this mean something to you?
thanks,
Joni
Hi, Joni,
There seems to be something wrong with my syntax highlighter. All of the example code is hidden in my post. I’ll work on fixing that, but in the meantime, I would start with the example code that is available for download from the post. That should give you everything you need.
Hi. I did download the code from the Get this Code link. Just tried it again, replaced the three files from Step 1 and did dev/build again. Same error when I tried to create a ConferenceHolder. To make sure I was up to date, I also installed the most current data object manager, and Event Calendar works fine on its own. Thanks.
Wow.. I have no idea. ConferenceRegistration.php is in your code folder? And it contains “class ConferenceRegistration extends DataObject” ??
That doesn’t make any sense. ???
In your tutorial above, Step 1 says “In your code directory, create the following classes:” then refers to ConferenceHolder.php, Conference.php, ConferenceDateTime.php, then instructs to “Now that the datamodel is in place, run a /dev/build.
If everything looks good, go into the CMS and create a ConferenceHolder with a few child Conference pages, and assign a few dates to each Conference.
The data model is complete!”
That is what I have done so far, and am getting the error I posted, when I go into the CMS and try to create a ConferenceHolder. So I’m not up to ConferenceRegistration.php yet. I’m still in Step 1!
I found a post with your WorkshopRegistration.zip and thought I’d try that. The Holder works fine, but I get the singleton error when I try to create a workshop underneath. Actually, THIS is exactly what I need (the Workshop one). Are both errors related? Thanks again.
Workshops? I’m lost. Did you download the code here?http://www.leftandmain.com/example_code/event_calendar_registration.zip
And if so,what happened?
As I said, the code examples in the blog entry above are missing because the syntax highlighter is broken, so when it says to create all those classes, the code that goes in each one of those files should follow, but it’s not there. So just use the zip file above.
I guess I’ve confused the issue. I did download the http://www.leftandmain.com/example_code/event_calendar_registration.zip file, followed the instructions in Step 1, and got to the end of Step 1 where I was to “go into the CMS and create a ConferenceHolder with a few child Conference pages, and assign a few dates to each Conference.” This is where I got the error I posted above when I clicked on Create, then ConferenceHolder in my CMS.
Then as I was looking around for some clues, I came across the WorkshopRegistration referenced in this SilverStripe forum post:http://silverstripe.org/all-other-modules/show/9247?start=0. The WorkshopRegistration.zip is from http://silverstripe.org/assets/Uploads/WorkshopRegistration.zip. This is a totally different item, however it looks like something that I need – a registration feature! It was from back in Oct 2009, so maybe you don’t remember it.
Oh geez. What a doofus. I looked again at my error message and your post, smacked myself in the head with the ConferenceRegistration.php file, then stuck it in my mysite/code folder, did a dev/build, and DUH – it is working now. Moving on to the next steps. Thanks, sorry for wasting your time.
“Let’s build a template for that email.
/themes/your_theme/templates/email/ConferenceRegistration.ss”
There’s no ConferenceRegistration.ss in the downloadable code.
Uncle Cheese,
Would you be able to email the text that is supposed to appear in the “text highlighter” which is missing from your tutorial? I’m close to finishing but don’t know what to put for the Customizing the Controller part.
Joni
Hi, Joni,
The syntax highlighter is working again. See above!
Thanks! You must think I’m quite a knucklehead – it is all working quite nicely now. I’m trying to customize it a little, as our training classes do not have any cost associated with them – is there an easy way to remove references to the currency? I hate the thought of me mucking around and breaking the whole thing. Thanks again for your patience and help.
Joni
I managed to load the ConferenceRegistration into ModelAdmin
By adding in ConferenceRegistration.php
static $searchable_fields = array(
‘ConferenceRegistration.DateID’ => ‘PartialMatchFilter’
);
This helps me export the registrations for easy to make rosters.
But when searching by DateID, I get blank member values, but the correct ID for each date. So I’m guessing when I select an empty date. Is there a way to fill in the Dropdown members’ names?
Thank you again for the great tutorial.
Please disregard my question posted on Jan 19 – I was able to read the ss files and find where I needed to edit them to remove the references to a dollar amount. Thank you so much for your help and a terrific module.
Very nice work, exactly what we needed, thanks
We have a question, is there a easy way to show the events of TODAY on the homepage? (only the ones who are bookable, not the ones the user put in announcements on the holder)
Best regards,
W3Spor
… and one more question:
Only the events should only open for booking the same day (or a time range of for example 3 days, week etc.) Do you have a suggestion?
Dear Uncle Cheese,
we found a solution. Dont use time to answer our two questions above.
But you extention of the event calender was very useful for our needs, thanks again
I tried Greg K’s code
(By adding in ConferenceRegistration.php
static $searchable_fields = array(
‘ConferenceRegistration.DateID’ => ‘PartialMatchFilter’
);
This helps me export the registrations for easy to make rosters.)
for exporting the registrations, but it broke my event registration app, so I had to delete it to make it work again. Uncle Cheese, do you know of a way that the registrations can be exported from the Conference holder Registrations tab? Thanks.
First of all, great tutorial on the event calendar module. I’ve used both the tutorial and code as a base and constant back reference for a conference site I been working on. Now, after all the work implementing a solid system, the client has come back and wanted to add “tracks” to their conference model. By tracks, I mean a specialized grouping of events running simultaneously and some events can be in multiple tracks.
I’ve combed through the event calendar code and can see that there can be nested event calendars and am wondering if that is the way to proceed in creating the tracks functionality? Or is there a more simple method using something like tags or some form of categorization?
Thanks in advance for your input and guidance and many cudos for the many contributions to the SS platform!
Freat work! I have added a FileField in the registration form, added a db filefield with has_one relationship, but the couldn’t recieve a file with the submission.. any idea?
thanks
sorry for the typo! Great work
Hi, thanks for the informative tutorial. I would now like to integrate a payment module to the registration. I have tried to understand the ecommerce and payment modules for SS although the whole ecommerce/orders it making it too complex to learn.
Thanks in advanced for any advice.