Booosta Tutorial
Facebook Example
lib/framework.incl.php:
<?php
$DEBUG_MODE = false;
$BACKUPMODE = false;
include_once 'db_mysql.incl.php';
include_once 'dbconnect.incl.php';
include_once 'templateparser.incl.php';
include_once 'ajax.incl.php';
include_once 'schedulefinder.incl.php';
include_once 'gui.incl.php';
############ do not change anything beyond this ####################
require 'tableclass.incl.php';
eval($ds_);
require 'staticclass.incl.php';
?>
lib/schedulefinder.incl.php:
<?php
include_once 'facebookapp.incl.php';
class ScheduleFinder extends FacebookApp
{
protected $USERID;
public function __construct()
{
parent::__construct();
$this->USERID = query_value("select id from useraccount where username='$this->FB_USERID'");
if($this->USERID == ''):
$obj = new C_Useraccount();
$obj->set('username', $this->FB_USERID);
$obj->set('privileges', array());
$user = $this->fb->api("/$this->FB_USERID");
$obj->set('settings', array('firstname' => $user['first_name'], 'surname' => $user['last_name']));
$this->USERID = $obj->insert();
endif;
}
}
?>
- Line 2: Include the facebook.incl.php . framework.incl.php is loaded by other files
- Line 4: Define class as extension of FacebookApp. This class is extended again in the following files
- Line 6: Create a new class variable to hold the actual user id from the database
- Line 12: Try to load this user id from the database
- Line 13: If this fails, the user is not yet added to the useraccount table. So we have to create a new one
- Line 17: Load an array with the user data from the Facebook API
- Line 20: insert() returns the database id of the new record. It is the actual user id now
index.php:
<?php
include_once 'lib/framework.incl.php';
class App extends ScheduleFinder
{
protected $name = 'event';
public function __construct()
{
parent::__construct();
$this->hide_fields('id,owner,user_invites,user_add_dates');
$this->set_header(array('name'=>'Name', 'description'=>'Description', 'location'=>'Location'));
}
protected function before_add_($data, &$obj)
{
$obj->set('owner', query_value("select id from useraccount where username='$this->FB_USERID'"));
if($data['user_add_dates']) $obj->set('user_add_dates', 1); else $obj->set('user_add_dates', 0);
if($data['user_invites']) $obj->set('user_invites', 1); else $obj->set('user_invites', 0);
$addvars = 'name,description,location,user_add_dates,user_invites';
$this->set_addvars($addvars);
}
protected function before_edit_($id, $data, &$obj)
{
if($obj->get('owner') == $this->USERID):
$editvars = 'name,description,location,user_add_dates,user_invites,newdate1,newdate2,newdate3,newdate4,newdate5,' .
'newtime1,newtime2,newtime3,newtime4,newtime5';
if($data['user_add_dates']) $obj->set('user_add_dates', 1); else $obj->set('user_add_dates', 0);
if($data['user_invites']) $obj->set('user_invites', 1); else $obj->set('user_invites', 0);
elseif($obj->get('user_add_dates')):
$editvars = 'newdate1,newdate2,newdate3,newdate4,newdate5,newtime1,newtime2,newtime3,newtime4,newtime5';
else:
$editvars = '';
endif;
$this->set_editvars($editvars);
}
protected function after_edit_($id, $data)
{
for($i = 1; $i <=5; $i++):
if($data["newdate$i"]):
$obj = new C_Dates();
$obj->set('event', $id);
$obj->set('dtime', $data["newdate$i"] . ' ' . $data["newhour$i"] . ':' . $data["newminute$i"]);
$obj->insert();
endif;
endfor;
}
protected function after_action_edit()
{
$result = '';
$this->TPL['is_owner'] = (query_value("select owner from event where id='$this->id'") == $this->USERID);
$objs = C_Dates::get_objects("event='$this->id'");
foreach($objs as $obj):
if($this->TPL['is_owner']) $link = '{LINK <img~src="tpl/delete.png"> index.php?action=deletedate&id=' . $obj->get('id') . '}';
else $link = '';
$result .= '<tr><td>' . $obj->get('dtime') . "</td><td>$link</td></tr>";
endforeach;
$this->TPL['current_dates'] = $result;
}
protected function action_deletedate()
{
$obj = C_Dates::get_object($this->id);
$obj->delete();
$this->backpage = $this->phpself;
}
protected function default_clause()
{
$invitations = query_value_set("select event from invitation where invitee='$this->USERID'");
$ownevents = query_value_set("select id from event where owner='$this->USERID'");
$events = array_merge($invitations, $ownevents);
if(sizeof($events)) return 'id in (' . implode(',', $events) . ')';
return '0'; // do not display any events
}
protected function in_default_makelist(&$list)
{
$list->set_links(array('vote' => "$this->phpself?action=vote&eventid={"."$this->idfield}",
'invite' => "askfriends.php?event={"."$this->idfield}",
'edit' => "$this->phpself?action=edit&id={"."$this->idfield}",
'delete' => "$this->phpself?action=delete&id={"."$this->idfield}"));
$list->add_condition('delete', "{owner} == '$this->USERID'");
$list->add_condition('edit', "{owner} == '$this->USERID' || {user_add_dates} == '1'");
$list->add_condition('invite', "{owner} == '$this->USERID' || {user_invites} == '1'");
$extrafields = array('vote' => new GuiTooltip('vote#', 'Vote for a schedule', 'tpl', 'vote.png'),
'invite' => new GuiTooltip('invite#', 'Invite friends to vote', 'tpl', 'invite.png'),
'edit' => new GuiTooltip('edit#', 'Edit this event', 'tpl', 'edit.png'),
'delete' => new GuiTooltip('delete#', 'Delete this event', 'tpl', 'delete.png'));
$list->set_extrafields($extrafields);
$this->TPL['js'] .= $this->get_invite_js();
$this->TPL['js'] .= Gui::get_html_includes('lib');
}
protected function action_vote()
{
$dates = query_value_set("select id from dates where event='".$this->VAR['eventid']."'");
$voters = query_value_set("select distinct u.id from useraccount u, votelist v where u.id=v.useraccount and v.event='".$this->VAR['eventid']."'");
$result = '<tr><th colspan="2">Voter<th>';
foreach($dates as $date):
$ddate = query_value("select dtime from dates where id='$date'");
$ddate = str_replace(' ', '<br>', $ddate);
$result .= "<th>$ddate<th>";
endforeach;
$result .= "<tr><tr><td><img src='https://graph.facebook.com/$this->FB_USERID/picture'/><td><td>My Vote<td>";
foreach($dates as $date):
$has_voted = query_value("select count(*) from vote where useraccount='$this->USERID' and ddate='$date'");
$img = ($has_voted ? 'yes.png' : 'no.png');
$result .= "<td>{IMG tpl/$img onClick::request_vote($date); id::voteimg$date}<td>";
endforeach;
$result .= '<tr>';
foreach($voters as $voter):
if($voter == $this->USERID) continue; // do not display myself again
$obj = C_Useraccount::get_object($voter);
$username = $obj->get('username');
$settings = $obj->get('settings');
$result .= "<tr class='vote'><td><img src='https://graph.facebook.com/$username/picture' /></td><td>$settings[firstname] $settings[surname]</td>";
foreach($dates as $date):
$has_voted = query_value("select count(*) from vote where useraccount='$voter' and ddate='$date'");
$img = ($has_voted ? 'yes.png' : 'no.png');
$result .= "<td>{IMG tpl/$img}</td>";
endforeach;
$result .= '</tr>';
endforeach;
$result .= '<tr class="gesamt"><td colspan="2">Summary:</td>';
foreach($dates as $date)
$result .= "<td id='sum$date'>" . query_value("select count(*) from vote where ddate='$date'") . '</td>';
$result .= '</tr>';
$ajax = new Ajax('vote', 'result');
$this->TPL['js'] = $ajax->get_javascript();
$this->TPL['votelist'] = $result;
$this->maintpl = 'tpl/votes.tpl';
}
protected function action_votedo()
{
$was_voted = query_value("select count(*) from vote where useraccount='$this->USERID' and ddate='{$this->VAR['dateid']}'");
if($was_voted)
query("delete from vote where useraccount='$this->USERID' and ddate='{$this->VAR['dateid']}'");
else
query("insert into vote (useraccount, ddate) values ('$this->USERID', '{$this->VAR['dateid']}')");
$image = ($was_voted ? 'no.png' : 'yes.png');
Ajax::print_response('result', $image);
$this->maintpl = null;
}
protected function action_imprint() { $this->maintpl = 'tpl/imprint.tpl'; }
};
$app = new App();
$app->run();
?>
- Line 2: Include the booosta framework in every PHP file
- Line 5: Extend the class defined in the previous file
- Line 7: Set $name to the name of the database table
- Line 13: Define database table fields to be hidden in the data table
- Line 14: Define table headers for the data table
- Line 17: Hook function that is executed before a new row is inserted in the table. The data object is passed by
reference and can be manipulated, what is done in Lines 19 to 21.
- Line 24: Define, which POST variables are used in the saved data object. This is to prevent users to pass additional
POST variables to manipulate data in the table they should not.
- Line 27: Hook function that is executed befor a row is edited. The data object is passed by
reference and can be manipulated.
- Line 40: Define, which POST variables are used in the saved data object. This is to prevent users to pass additional
POST variables to manipulate data in the table they should not.
- Line 43: Hook function that is executed after a row has been edited in the table. $data holds all the passed POST
variables.
- Line 55: Hook function that is executed after the user clicked on "edit" in the data table. $this->id holds the id
of the selected row.
- Line 66: Make a list of all available dates for that event with a delete link that is only displayed for the owner
- Line 69: Save the list in the template variable {%current_dates}
- Line 72: Function that is called when POST variable "action" is set to "deletedate"
- Line 77: Tell the app to return to the same script when done all tasks
- Line 80: Function that returns a SQL where clause, that selects the rows to display from the table
- Line 86: Return a clause that is a valid code after the "where" keyword in SQL
- Line 91: This is a little more complicated ;-) The data list that is displayed by the app is the content of a
CTableLister object. Before the data is displayed the object which is passed by reference to $list can be manipulated to
alter the output of the list.
- Line 93 - 96: set_links() adds Links to the data that is displayed. The keys are the field names and the data the
link target. The fields can be data fields from the table or extra fields that are added with set_extrafields() like here in
line 106.
- Line 98 - 100: add_condition() adds conditions that determine wheter to display a field or not. The first
parameter is the field name and the second a string with the condition. Strings in {} are parsed as field names.
- Line 102 - 106: Additional fields to the table fields are added with set_extrafields(). The parameter is an array
whose index are the field names and the values are the text to display. Here we use a Tooltip object which is from the
Gui class that is actually under development and not released yet. You should use simple text there.
- Line 108: get_invite_js() returns some javascript code to initialize the Facebook invitation function. This line
would logically belong to action_default(), but since we do not override this function here we put it quick and dirty
into this function.
- Line 113: Function that is called when POST variable "action" is set to "vote"
- Line 125: Show the Facebook profile image of the actual user
- Line 141: Show Facebook profile image and name of every voter
- Line 157: Create an Ajax object for handling voting clicks via ajax. The first parameter is the name of the
ajax object used in the javascript code in the template. The second parameter is the name of the result tag returned by
the ajax call.
- Line 158: The javascript code of the Ajax object is written to the template variable {%js}
- Line 161: "tpl/votes.tpl" is selected as the template to use
- Line 165: Function that is called when POST/GET variable "action" is set to "votedo". This is done
in the ajax call in tpl/votes.tpl in line 8
- Line 174: Depending on the fact if the actual user had voted before for this date or not the string
"no.png" or "yes.png" is returned
- Line 175: Ajax::print_response forms a correct XML response whith the tag name as first parameter and
the string to return as second parameter.
- Line 177: As only the XML response should be printed out we set the main template to null
askfriends.php:
<?php
include_once 'lib/framework.incl.php';
class App extends ScheduleFinder
{
protected $name = 'askfriends';
protected function action_default()
{
$friendlist = $this->get_friendids();
$userlist = query_value_set('select username from useraccount');
$frienduser = array_intersect($friendlist, $userlist);
$usernames = $this->get_friendnames();
$invitees = query_value_set("select invitee from invitation where event='{$this->VAR['event']}'");
foreach($frienduser as $user):
$userid = query_value("select id from useraccount where username='$user'");
if(in_array($userid, $invitees)) $check = 'checked'; else $check = '';
$list .= "$usernames[$user]{CHECKBOX invite_$userid $check}";
endforeach;
$this->TPL['liste'] = $list;
$this->TPL['js'] = $this->get_invite_js();
$this->maintpl = 'tpl/askfriends_default.tpl';
}
protected function action_invitedo()
{
$invitee_list = array();
foreach($this->VAR as $var=>$val)
if(substr($var, 0, 7) == 'invite_' && $val):
list($dummy, $invitee) = explode('_', $var);
$invitee_list[] = $invitee;
$invited_before = query_value("select count(*) from invitation where event='{$this->VAR['event']}' and invitee='$invitee'");
if(!$invited_before): // do all the stuff to invite user to vote for this event
$obj = new C_Invitation();
$obj->set('event', $this->VAR['event']);
$obj->set('invitor', $this->USERID);
$obj->set('invitee', $invitee);
$obj->insert();
// send invitee a facebook message to tell him he is invited to vote
$invitee_fbid = query_value("select username from useraccount where id='$invitee'");
$eventname = query_value("select name from event where id='{$this->VAR['event']}'");
$message = 'I have invited you to vote on a schedule on Schedule Finder!';
$data = array('name' => 'Vote on a schedule', 'message' => $message,
'link' => 'https://apps.facebook.com/at_epb_datefinder/?action=vote&eventid=3',
'description' => "You are invited to vote for a schedule on the event '$eventname'.");
if($invitee_fbid) $this->post_to_wall($data, $invitee_fbid);
endif;
endif;
// if we have invited the invitee
// we can remove him from the list if he has not voted yet
$invitations = query_arrays("select id, invitor, invitee from invitation where event='{$this->VAR['event']}'");
foreach($invitations as $invitation)
if($invitation['invitor'] == $this->USERID && !in_array($invitation['invitee'], $invitee_list)):
if(query_value("select count(*) from votelist where event='$this->VAR[event]' and useraccount='$invitation[invitee]'") == 0):
$obj = C_Invitation::get_object($invitation['id']);
$obj->delete();
endif;
endif;
$this->backpage = 'index.php';
}
}
$app = new App();
$app->run();
?>
- Line 2: Include the booosta framework in every PHP file
- Line 5: Extend the class defined in the include file
- Line 7: Set $name to the name of the database table
- Line 9: Function action_default() is called when no variable "action" is submitted
- Line 11: get_friendids() returns an array of Facebook IDs of all friends of the actual user
- Line 12: Get Facebook IDs of all users in the database
- Line 13: Find all of your friends that are in the user table
- Line 14: get_friendnames() returns an array of all names of Facebook friends of the actual user
- Line 17 - 21: Make a list of all friends in the database and check the checkbox if they are already invited
to this particular event
- Line 23: write genterated list to template variable {%liste}
- Line 24: write javascript code necessary for invitation of new users to template variable {%js}
- Line 25: select a different file as main template
- Line 29: Function is called when variable "action" is submitted with value "invitedo"
- Line 34 - 36: get the IDs of all users that are invited by this action. This information is in the
POST variables with name invite_id e. g. invite_124.
- Line 37: determine, if the invited user already has been invited before
- Line 54: post to the wall of the invited user with the data array constructed before
- Line 60: get a list of all users that have been invited by the acutal app user
- Line 63 - 65: delete all users, that were invited before but are not any more - according to
the POST variables invite_id
- Line 69: when done, return to index.php
tpl/askfriends_default.tpl:
{%js}
<h1>Ask Friends to vote</h1>
{FORMSTART askfriends.php}
{HIDDEN event {%event}}
{HIDDEN action invitedo}
<table>
<tr><th>Name</th><th>Invite</th></tr>
{%liste}
</table>
{FORMSUBMIT}
{FORMEND}
<br><br>
Any friends missing? Probably they don't have this app installed!
{LINK Invite~them! javascript:invite_friends()}
<br>
{LINK Home index.php}
- Line 5: call askfriends.php as form action
- Line 6 - 7: submit hidden variables used by askfriends.php
- Line 11: print out content of $this->TPL['liste']
tpl/event_default.tpl:
{%js}
<h1>Events</h1>
{LINK New~Event ?action=new}
{%liste}
<br><br>
{LINK Invite~Friends javascript:invite_friends()}
tpl/event_edit.tpl:
<h1>Edit Event</h1>
{FORMSTART #}
{HIDDEN action editdo}
<table>
%if({%is_owner}):
<tr><td>Name:</td><td>{TEXT name {%~name}}</td></tr>
<tr><td>Description:</td><td>{TEXTAREA description 30 5
{%description}}</td></tr>
<tr><td>Locaction:</td><td>{TEXT location {%~location}}</td></tr>
<tr><td>Others can add dates:</td><td>{CHECKBOX user_add_dates {%user_add_dates}}</td></tr>
<tr><td>Others can invite users:</td><td>{CHECKBOX user_invites {%user_invites}}</td></tr>
%else:
<tr><td>Name:</td><td>{%name}</td></tr>
<tr><td>Description:</td><td>{%description}</td></tr>
<tr><td>Locaction:</td><td>{%location}</td></tr>
<tr><td>Others can add dates:</td><td>{%user_add_dates}</td></tr>
<tr><td>Others can invite users:</td><td>{%user_invites}</td></tr>
%endif;
<tr><td><b>Dates:</b></td><td> </td></tr>
{%current_dates}
{DATEINIT}
<tr><td>New Date:</td><td>{DATE newdate1}{NUMBERSEL newhour1 23 0 0}:{NUMBERSEL newminute1 59 0 0}</td></tr>
<tr><td>New Date:</td><td>{DATE newdate2}{NUMBERSEL newhour2 23 0 0}:{NUMBERSEL newminute2 59 0 0}</td></tr>
<tr><td>New Date:</td><td>{DATE newdate3}{NUMBERSEL newhour3 23 0 0}:{NUMBERSEL newminute3 59 0 0}</td></tr>
<tr><td>New Date:</td><td>{DATE newdate4}{NUMBERSEL newhour4 23 0 0}:{NUMBERSEL newminute4 59 0 0}</td></tr>
<tr><td>New Date:</td><td>{DATE newdate5}{NUMBERSEL newhour5 23 0 0}:{NUMBERSEL newminute5 59 0 0}</td></tr>
</table>
{FORMSUBMIT}
{FORMEND}
<br><br>
{LINK Home index.php}
- Line 3: start form with $php_self as form action
- Line 7: evaluate template variable is_owner and make output depending on the value with %if. %if has to start at
the first column of the line!
- Line 8: {%~name} outpute the content of the template variable $this->TPL['name'] with spaces replaced by ~ for use
in the TEXT template field.
- Line 26 - 30: {DATE ...} shows a date select field, {NUMBERSELECT ...} a select field with numbers
tpl/event_new.tpl:
<h1>Create New Event</h1>
{FORMSTART #}
{HIDDEN action newdo}
<table>
<tr><td>Name:</td><td>{TEXT name}</td></tr>
<tr><td>Description:</td><td>{TEXTAREA description 30 5}</td></tr>
<tr><td>Locaction:</td><td>{TEXT location}</td></tr>
<tr><td>Others can add dates:</td><td>{CHECKBOX user_add_dates}</td></tr>
<tr><td>Others can invite users:</td><td>{CHECKBOX user_invites}</td></tr>
</table>
{FORMSUBMIT}
{FORMEND}
tpl/votes.tpl:
{%js}
<script type='text/javascript'>
var lastdateid;
function request_vote_(dateid)
{
url_vote = 'index.php?action=votedo&eventid={%eventid}&dateid=' + dateid;
lastdateid = dateid;
}
function response_vote_()
{
var image = document.getElementById('voteimg' + lastdateid);
image.src = 'tpl/' + xml_result_vote;
var sum = document.getElementById('sum' + lastdateid);
var count = parseInt(sum.innerHTML);
if(xml_result_vote == 'yes.png') count = count + 1;
else count = count - 1;
sum.innerHTML = count;
}
</script>
<h1>All Votes</h1>
<table>
{%votelist}
</table>
<br><br>
{LINK Home index.php}
- Line 6: this function is called by request_vote() which is called in line 129 in index.php
- Line 8: url_vote is a global variable that holds the URL for the Ajax call
- Line 12: response_vote_() is automatically called when the Ajax reply is available
- Line 15: xml_result_vote holds the result string from the ajax call
Database definition:
--
-- Tabellenstruktur für Tabelle 'dates'
--
CREATE TABLE dates (
id int(11) NOT NULL auto_increment,
event int(11) NOT NULL,
dtime datetime NOT NULL,
`comment` text collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (id),
KEY event (event)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle 'event'
--
CREATE TABLE event (
id int(11) NOT NULL auto_increment,
owner int(11) NOT NULL,
`name` varchar(255) NOT NULL,
description text NOT NULL,
location varchar(255) NOT NULL,
user_add_dates tinyint(1) NOT NULL,
user_invites tinyint(1) NOT NULL,
PRIMARY KEY (id),
KEY owner (owner)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle 'invitation'
--
CREATE TABLE invitation (
id int(11) NOT NULL auto_increment,
event int(11) NOT NULL,
invitor int(11) NOT NULL,
invitee int(11) NOT NULL,
PRIMARY KEY (id),
KEY invitor (invitor),
KEY invitee (invitee),
KEY event (event)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle 'useraccount'
--
CREATE TABLE useraccount (
id int(11) NOT NULL auto_increment,
username varchar(255) collate utf8_unicode_ci NOT NULL,
`password` varchar(255) collate utf8_unicode_ci NOT NULL,
`privileges` text collate utf8_unicode_ci NOT NULL,
facebook tinyint(1) NOT NULL default '1',
settings text collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle 'vote'
--
CREATE TABLE vote (
id int(11) NOT NULL auto_increment,
ddate int(11) NOT NULL,
useraccount int(11) NOT NULL,
`comment` text collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (id),
KEY vote (ddate),
KEY useraccount (useraccount)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE ALGORITHM=UNDEFINED DEFINER=root@localhost SQL SECURITY DEFINER
VIEW votelist AS
select v.id AS id,v.ddate AS ddate,v.useraccount AS useraccount,v.`comment` AS `comment`, d.event AS event
from (vote v join dates d) where (v.ddate = d.id);
--
-- Constraints der exportierten Tabellen
--
--
-- Constraints der Tabelle `dates`
--
ALTER TABLE `dates`
ADD CONSTRAINT dates_ibfk_1 FOREIGN KEY (event) REFERENCES event (id) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `event`
--
ALTER TABLE `event`
ADD CONSTRAINT event_ibfk_1 FOREIGN KEY (owner) REFERENCES useraccount (id) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints der Tabelle `invitation`
--
ALTER TABLE `invitation`
ADD CONSTRAINT invitation_ibfk_3 FOREIGN KEY (invitee) REFERENCES useraccount (id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT invitation_ibfk_1 FOREIGN KEY (event) REFERENCES event (id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT invitation_ibfk_2 FOREIGN KEY (invitor) REFERENCES useraccount (id) ON UPDATE CASCADE;
--
-- Constraints der Tabelle `vote`
--
ALTER TABLE `vote`
ADD CONSTRAINT vote_ibfk_2 FOREIGN KEY (useraccount) REFERENCES useraccount (id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT vote_ibfk_3 FOREIGN KEY (ddate) REFERENCES dates (id) ON DELETE CASCADE ON UPDATE CASCADE;
There are also some static template files like HTML and images which are not of interest for
this tutorial and are omitted here.