Jan 152012
 

This is the third part of a three part series on how to embed a Google Calendar into a web page and use it to accept online bookings/appointments from other online users.

The Series:

  1. Part 1: Setting up Google Calendar
  2. Part 2: OAuth2 and Configuring Your ‘Application’ With Google
  3. Part 3: A Sample Web Page For Bookings <- You Are Here

Recap

So, I’ve gone through adding a calendar to your webpage to display to your users, and then creating your application with Google so that you can do queries against your calendar. Now we get to the fun part, writing the application! I should mention that this is written in PHP, because I don’t know if I mentioned that specifically yet.

The Plan

The plan is to create a PHP script that will display a list of calendars people can book items on, and then allow them to book off time on one of these calendars. We will also include some restrictions, like not booking over other events, not booking in the past, only allowing bookings so far into the future, and not allowing bookings during ‘closed’ times. You can add more restrictions as you see fit.

In my example, the script is hosted behind some password protection and user accounts. Depending on the nature of your application, that may or may not be required. For this example, I’m going to assume it is an open system.

The API

Google provides a series of classes that you can use to interface with their API. However, I could not figure out how to use them with refresh tokens. If you intend to use a lot of the functions of the API, it might be worth while for you to get it working. However, for me, it was easier to make raw calls to the API since I’m only using 2 of the API functions. If you want to download all of the Google code for PHP, you can find the directions here.

Getting Started

The Google API is based on REST principles, and uses JSON to encode data. To know exactly what to send to the API, I played around with the Google API explorer. To make a long story short, I’ll include the code I used for my calls.

The Functions Needed

As we are not using the Google code, we will need to create some functions to do our bidding. We need a function to manage our authentication tokens, to submit post requests (to add events), to submit get requests (to check for previous bookings), and to craft our JSON request for the POST.

Auth Function

function getAccessToken(){
	$tokenURL = 'https://accounts.google.com/o/oauth2/token';
	$postData = array(
		'client_secret'=>'############',
		'grant_type'=>'refresh_token',
		'refresh_token'=>'###############',
		'client_id'=>'#############.apps.googleusercontent.com'
	);
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $tokenURL);
	curl_setopt($ch, CURLOPT_POST, 1);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

	$tokenReturn = curl_exec($ch);
	$token = json_decode($tokenReturn);
	//var_dump($tokenReturn);
	$accessToken = $token->access_token;
	return $accessToken;
}

You will need to add your values for client_id, refresh_token and client_secret that you established in Part 2 of this tutorial. This function is pretty stand alone. The only thing you might want to add to your finished product is better token management. I generate a new token every time. It would be best to check the token before regenerating, as tokens tend to be good for 3600 seconds.

Format JSON For Post

function createPostArgsJSON($date,$starttime,$endtime,$title){
	$arg_list = func_get_args();
	foreach($arg_list as $key => $arg){
		$arg_list[$key] = urlencode($arg);
	}
	$postargs = <<<JSON
{
 "start": {
  "dateTime": "{$date}T{$starttime}:00.000-03:30"
 },
 "end": {
  "dateTime": "{$date}T{$endtime}:00.000-03:30"
 },
 "summary": "$title",
 "description": "$title"
}
JSON;
	return $postargs;
}

This function creates the body of the our post request. What you include depends on what you are storing in the event. I only need the start and end time, and a title for the event. Check out the API explorer to see what you can do.

The date is YYYY-MM-DD format, and times are passed to the function in HH:mm format. I then pad the string with the seconds and timezone.

Odds and Ends

The rest of my functions depend on these odds and ends I set up before I start doing stuff, so I’ll cover them here.

$thecal = 'court1';
if(isset($_GET['cal'])){
	$thecal = addslashes($_GET['cal']);
}

/*
 * Advance is the amount of time in the future someone can book something.
 * days
 * weeks
 * months
 * If it is 0, it will allow unlimited future booking
 */
$courts = array(
	'court1' => array('cid' => 'court1', 'name' => 'Court 1', 'id' => '###########@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
	'court2' => array('cid' => 'court2', 'name' => 'Court 2', 'id' => '###########@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
);

$APIKEY = '###################';

This script expects a URL parameter to tell it which calendar to load. I first set it if nothing is specified. Then I set up the calendars. I’m using this for tennis court bookings, hence my naming conventions. The keys here are:

  • The array index/cid – This is the url parameter you will pass to the page to load the correct calendar.
  • name – The nice name you will display to users.
  • id – This is your calendar id from Google that you set up in Part 1.
  • starttime/endtime – The limits in booking times specified in HH:mm:ss
  • advance – This is the amount of time in the future that you allow bookings. PHPs strtotime syntax is used for defining the future time.

The API key is the key from Part 2.

Function For Get Requests

function sendGetRequest($token,$request){
	global $APIKEY;
	
	$session = curl_init($request);
	
	// Tell curl to use HTTP POST
	curl_setopt ($session, CURLOPT_HTTPGET, true); 
	// Tell curl not to return headers, but do return the response
	curl_setopt($session, CURLOPT_HEADER, false); 
	curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($session, CURLINFO_HEADER_OUT, false);
	curl_setopt($session, CURLOPT_HTTPHEADER, array('Authorization:  Bearer ' . $token,'X-JavaScript-User-Agent:  Mount Pearl Tennis Club Bookings'));
    
	$response = curl_exec($session);
	
	curl_close($session); 
	return $response;
}

This function preforms a get request. You need to pass into it the request you want to make, as well as a valid auth token to complete the request. The function returns the response from the server.

Function For Post Requests

function sendPostRequest($postargs,$token, $cal){
	global $APIKEY;
	$request = 'https://www.googleapis.com/calendar/v3/calendars/' . $cal . '/events?pp=1&key=' . $APIKEY;
	
	$session = curl_init($request);
	
	// Tell curl to use HTTP POST
	curl_setopt ($session, CURLOPT_POST, true); 
	// Tell curl that this is the body of the POST
	curl_setopt ($session, CURLOPT_POSTFIELDS, $postargs); 
	// Tell curl not to return headers, but do return the response
	curl_setopt($session, CURLOPT_HEADER, false); 
	curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
	//curl_setopt($session, CURLOPT_VERBOSE, true);
	curl_setopt($session, CURLINFO_HEADER_OUT, true);
	curl_setopt($session, CURLOPT_HTTPHEADER, array('Content-Type:  application/json','Authorization:  Bearer ' . $token,'X-JavaScript-User-Agent:  Mount Pearl Tennis Club Bookings'));
    
	$response = curl_exec($session);
	
	curl_close($session); 
	return $response;
}

This function accepts your post arguments that were created in another function, your valid auth token, as well as the calendar you want to post too.

It returns the result from the API.

Function To Test If Booked Already

function isTimeBooked($date,$starttime,$endtime,$cal){
	global $APIKEY;
	$start = $date . 'T' . $starttime . ':00-03%3A30';
	$end = $date . 'T' . $endtime . ':00-03%3A30';
	$token = getAccessToken();
	$result = sendGetRequest($token, 'https://www.googleapis.com/calendar/v3/calendars/' . $cal . '/events?timeMax=' . $end . '&timeMin=' . $start . '&fields=items(end%2Cstart%2Csummary)&pp=1&key=' . $APIKEY);
	if(strlen($result) > 5){
		return true;
	}
	else{
		return false;
	}
}

This function accepts the date, starttime, endtime and calendar id. It does a check to see if any event exists for this time on the calendar already. It uses the get request function to get it’s result. If the result is more than 5 characters return true, telling us that the time is used, but if it is less then 5, then there are no events, so return false.

HTML Code For Page

<html>
<head>

</head>
<body>

<div class="courtlist">
<?php 
	foreach($courts as $court){
		echo '<a href="calbook.php?cal=' . $court['cid'] . '">' . $court['name'] . '</a> | ';
	}
?>
</div>

<?php
	if(strlen($message) > 1){
		echo '<div class="message">';
		echo $message;
		echo '</div>';
	}
?>

<iframe src="https://www.google.com/calendar/embed?mode=WEEK&amp;showTitle=1&amp;showCalendars=0&amp;height=1000&amp;wkst=2&amp;bgcolor=%23FFFFFF&amp;src=<?php echo $courts[$thecal]['id']; ?>&amp;color=%232952A3&amp;ctz=America%2FSt_Johns" style=" border-width:0 " width="800" height="600" frameborder="0" scrolling="no" id="califrame" onload="document.getElementById('califrame').contentWindow.scrollTo(0,document.getElementById('califrame').contentWindow.document.body.scrollHeight)"></iframe>
<h2>Book a Court</h2>
<form action="calbook.php?cal=<?php echo $thecal; ?>" method="post" name="booking">
	<input type="hidden" readonly="true" value="<?php echo $thecal; ?>" name="calendar"></input>
	Court: <input type="text" readonly="true" value="<?php echo $courts[$thecal]['name']; ?>" name="calendarname"></input><br />
	Title of Booking: <input type="text" value="Booking for ...." name="title"></input><br />
	Date: <input type="text" value="<?php echo date('Y-m-d'); ?>" id="startdate" name="startdate"></input><br />
	Start Time: <input type="text" value="" id="starttime" name="starttime"></input><br />
	End Time: <input type="text" value="" id="endtime" name="endtime"></input><br />
	<input type="submit" name="submit" value="Book Court"></input>
</form>
</body>
</html>

This code prints out a list of calendars from our array, and links to them.

Then it prints out a message if one is set.

Then we use the Google iframe code from Part 1 to display a calendar.

Then we create a form to accept input from our users. Using a nice date/time picker would make things easier on the user. The forms posts back to itself, but you could separate much of this code into different files.

PHP Code To Handle Booking

So now that we have all of the pieces in place, we can create the code that will accept the input from the users.

$message = "";

if(isset($_POST['submit']) && $_POST['submit'] == 'Book Court'){
	/*
	 * Check to see if everything was filled out properly.
	 */
	if(date('Ymd') > date('Ymd',strtotime($_POST['startdate']))){
		$message = 'You cannot make a booking in the past.  Please check your date.';
	}
	elseif($_POST['starttime'] == ''){
		$message = 'You must enter a start time.';
	}
	elseif($_POST['endtime'] == ''){
		$message = 'You must enter an end time.';
	}
	/*
	 * Check to see if booking is available for this time.
	 */
	elseif(date('Hms', strtotime($_POST['starttime'] . ':00')) < date('Hms', strtotime($courts[$_POST['calendar']]['starttime'])) || date('Hms', strtotime($_POST['endtime'] . ':00')) > date('Hms', strtotime($courts[$_POST['calendar']]['endtime']))){
		$message = 'Booking not available during this time.  Please select another time.';
	}
	/*
	 * Check to see if we are alowed to book this far in advance.
	 */
	elseif(date('Ymd',strtotime($_POST['startdate'])) > date('Ymd',strtotime('+' . $courts[$_POST['calendar']]['advance'],strtotime($_POST['startdate'])))){
		$message = 'You cannot book that far into the future.  You can only book ' . $courts[$_POST['calendar']]['advance'] . ' in the future.  Please try again.';
		//$message .= date('Ymd',strtotime($_POST['startdate'])) . ' > ' . date('Ymd',strtotime('+' . $courts[$_POST['calendar']]['advance'],strtotime($_POST['startdate'])));
	}
	/*
	 * Check and see if a booking already exists.
	 */
	elseif(isTimeBooked($_POST['startdate'],$_POST['starttime'],$_POST['endtime'],$courts[$_POST['calendar']]['id'])){
		$message = 'Time already booked.  Please select another time.';
	}
	/*
	 * Everything is good, submit the event.
	 */
	else{
		$postargs = createPostArgsJSON($_POST['startdate'],$_POST['starttime'],$_POST['endtime'],$_POST['title']);
		$token = getAccessToken();
		$result = sendPostRequest($postargs,$token,$courts[$_POST['calendar']]['id']);
		//echo '<pre>' . $result . '</pre>';
	}
}

This code checks to see if a form was submitted, and then completes a series of checks to make sure that it is a valid request. If they all pass, then the event is added to the calendar. You may want to print a message to let the user know that the event was added.

At this point, you have a fully working calendar booking system. You can experiment with the API to add additional features.

Hopefully this tutorial series was of some use to you. If you have any questions, feel free to post them below.

UPDATE (Feb. 1, 2013):

By popular demand, I’ve included a complete example of putting all of these pieces together. I’ve stripped out all of the authentication and calendar codes and keys. The first two steps of this tutorial will teach you how to get all of those.

<?php
/*
 * Example of a working config for booking to a calendar.
 */
session_start();

require_once('../backend/libs/config.php');
require_once('../backend/libs/cal/corecal.php');

/* 
 * Core Calendar Functions (contents of the corecal.php file above)
 */
function sendPostRequest($postargs,$token, $cal){
        global $APIKEY;
        $request = 'https://www.googleapis.com/calendar/v3/calendars/' . $cal . '/events?pp=1&key=' . $APIKEY;

        //$auth = json_decode($_SESSION['oauth_access_token'],true);

        //var_dump($auth);

        $session = curl_init($request);

        // Tell curl to use HTTP POST
        curl_setopt ($session, CURLOPT_POST, true);
        // Tell curl that this is the body of the POST
        curl_setopt ($session, CURLOPT_POSTFIELDS, $postargs);
        // Tell curl not to return headers, but do return the response
        curl_setopt($session, CURLOPT_HEADER, true);
        curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($session, CURLOPT_VERBOSE, true);
        curl_setopt($session, CURLINFO_HEADER_OUT, true);
        curl_setopt($session, CURLOPT_HTTPHEADER, array('Content-Type:  application/json','Authorization:  Bearer ' . $token,'X-JavaScript-User-Agent:  Mount Pearl Tennis Club Bookings'));
    
        $response = curl_exec($session);
   
        //echo '<pre>';
        //var_dump(curl_getinfo($session, CURLINFO_HEADER_OUT)); 
        //echo '</pre>';
        
        curl_close($session);
        return $response;
}

function sendGetRequest($token,$request){
        global $APIKEY;
        //$request = 'https://www.googleapis.com/calendar/v3/calendars/' . $CAL . '/events?pp=1&key=' . $APIKEY;

        //$auth = json_decode($_SESSION['oauth_access_token'],true);

        //var_dump($auth);

        $session = curl_init($request);

        // Tell curl to use HTTP POST
        curl_setopt ($session, CURLOPT_HTTPGET, true);
        // Tell curl not to return headers, but do return the response
        curl_setopt($session, CURLOPT_HEADER, false);
        curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($session, CURLINFO_HEADER_OUT, false);
        curl_setopt($session, CURLOPT_HTTPHEADER, array('Authorization:  Bearer ' . $token,'X-JavaScript-User-Agent:  Mount Pearl Tennis Club Bookings'));

        $response = curl_exec($session);

        //echo '<pre>';
        //var_dump(curl_getinfo($session, CURLINFO_HEADER_OUT)); 
        //echo '</pre>';

        curl_close($session);
        return $response;
}

function createPostArgsJSON($date,$starttime,$endtime,$title){
        $arg_list = func_get_args();
        foreach($arg_list as $key => $arg){
                $arg_list[$key] = urlencode($arg);
        }
        $postargs = <<<JSON
{
 "start": {
  "dateTime": "{$date}T{$starttime}:00.000-03:30"
 },
 "end": {
  "dateTime": "{$date}T{$endtime}:00.000-03:30"
 },
 "summary": "$title",
 "description": "$title"
}
JSON;
        return $postargs;
}

function getAccessToken(){
        $tokenURL = 'https://accounts.google.com/o/oauth2/token';
        $postData = array(
                'client_secret'=>'......', #You need to fill these in to match your site.
                'grant_type'=>'refresh_token',
                'refresh_token'=>'...........', #You need to fill these in to match your site.
                'client_id'=>'.................' #You need to fill these in to match your site.
        );
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $tokenURL);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $tokenReturn = curl_exec($ch);
        $token = json_decode($tokenReturn);
        //var_dump($tokenReturn);
        $accessToken = $token->access_token;
        return $accessToken;
}

function isTimeBooked($date,$starttime,$endtime,$cal){
        global $APIKEY;
        $start = $date . 'T' . $starttime . ':00-03%3A30';
        $end = $date . 'T' . $endtime . ':00-03%3A30';
        $token = getAccessToken();
        $result = sendGetRequest($token, 'https://www.googleapis.com/calendar/v3/calendars/' . $cal . '/events?timeMax=' . $end . '&timeMin=' . $start . '&fields=items(end%2Cstart%2Csummary)&pp=1&key=' . $APIKEY);
        if(strlen($result) > 5){
                return true; 
        }
        else{
                return false;
        }
}
  
function checkCourtRegistrations($startdate,$starttime,$enddate,$endtime,$cal){
        global $APIKEY;
        $start = $startdate . 'T' . $starttime . ':00-03%3A30';
        $end = $enddate . 'T' . $endtime . ':00-03%3A30';
        $token = getAccessToken();
        $result = sendGetRequest($token, 'https://www.googleapis.com/calendar/v3/calendars/' . $cal . '/events?timeMax=' . $end . '&timeMin=' . $start . '&fields=items(end%2Cstart%2Csummary)%2Csummary&pp=1&key=' . $APIKEY);
        if(strlen($result) > 5){
            $result = json_decode($result,true);
            //return array($result['summary'] => $result['items']);
            return array('items' => $result['items'], 'court' => $result['summary']);
        }
        else{
            return '';
        }
}
/* 
 * End Core Calendar Functions (contents of the corecal.php file above)
 */

$thecal = 'court1';
if(isset($_GET['cal'])){
        $thecal = addslashes($_GET['cal']);
}

/*
 * Advance is the amount of time in the future someone can book something.
 * days
 * weeks
 * months
 * If it is 0, it will allow unlimited future booking
 */

$courts = array(
        'court1' => array('cid' => 'court1', 'name' => 'Court 1', 'id' => '{YOUR CAL}@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
        'court2' => array('cid' => 'court2', 'name' => 'Court 2', 'id' => '{YOUR CAL}@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
        'court3' => array('cid' => 'court3', 'name' => 'Court 3', 'id' => '{YOUR CAL}@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
        'court4' => array('cid' => 'court4', 'name' => 'Court 4', 'id' => '{YOUR CAL}@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
        'court5' => array('cid' => 'court5', 'name' => 'Court 5', 'id' => '{YOUR CAL}@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
        'court6' => array('cid' => 'court6', 'name' => 'Court 6', 'id' => '{YOUR CAL}@group.calendar.google.com', 'starttime' => '08:00:00', 'endtime' => '23:00:00', 'advance' => '1 week'),
);

$APIKEY = '{YOUR API KEY}';

$message = "";

if(isset($_POST['submit']) && $_POST['submit'] == 'Book Court'){
        /*
         * Check to see if everything was filled out properly.
         */
        //echo 'start submit' . date('Hms', strtotime($_POST['starttime'] . ':00'));
        //echo 'start default' . date('Hms', strtotime($courts[$_POST['calendar']]['starttime']));
        //echo 'end submit' . date('Hms', strtotime($_POST['endtime'] . ':00'));
        //echo 'end default' . date('Hms', strtotime($courts[$_POST['calendar']]['endtime']));
        if(date('Ymd') > date('Ymd',strtotime($_POST['startdate']))){
                $message = 'You cannot make a booking in the past.  Please check your date.';
        }
        elseif($_POST['starttime'] == ''){
                $message = 'You must enter a start time.';
        }
        elseif($_POST['endtime'] == ''){
                $message = 'You must enter an end time.';
        }
        /*
         * Check to see if booking is available for this time.
         */
        elseif(date('Hms', strtotime($_POST['starttime'] . ':00')) < date('Hms', strtotime($courts[$_POST['calendar']]['starttime'])) || date('Hms', strtotime($_POST['endtime'] . ':00')) > date('Hms', strtotime($courts[$_POST['calendar']]['endtime']))){
                $message = 'Booking not available during this time.  Please select another time.';
        }
        /*
         * Check to see if we are alowed to book this far in advance.
         */

        elseif(date('Ymd',strtotime($_POST['startdate'])) > date('Ymd',strtotime('+' . $courts[$_POST['calendar']]['advance'],strtotime($_POST['startdate'])))){
                $message = 'You cannot book that far into the future.  You can only book ' . $courts[$_POST['calendar']]['advance'] . ' in the future.  Please try again.';
                //$message .= date('Ymd',strtotime($_POST['startdate'])) . ' > ' . date('Ymd',strtotime('+' . $courts[$_POST['calendar']]['advance'],strtotime($_POST['startdate'])));
        }
        /*
         * Check and see if a booking already exists.
         */
        elseif(isTimeBooked($_POST['startdate'],$_POST['starttime'],$_POST['endtime'],$courts[$_POST['calendar']]['id'])){
                $message = 'Time already booked.  Please select another time.';
        }
        /*
         * Everything is good, submit the event.
         */
        else{
                $postargs = createPostArgsJSON($_POST['startdate'],$_POST['starttime'],$_POST['endtime'],$_POST['title']);
                $token = getAccessToken();
                $result = sendPostRequest($postargs,$token,$courts[$_POST['calendar']]['id']);
                //echo '<pre>' . $result . '</pre>';
        }
}
?>
<html>
<head>
	<!-- The below JS library is from home.jongsma.org/software/js/datepicker -->
        <script language="javascript" src="../backend/libs/picker/js/prototype-1.6.0.2.js"></script>
        <script language="javascript" src="../backend/libs/picker/js/prototype-base-extensions.js"></script>
        <script language="javascript" src="../backend/libs/picker/js/prototype-date-extensions.js"></script>
        <script language="javascript" src="../backend/libs/picker/js/datepicker.js"></script>
        <link rel="stylesheet" href="../backend/libs/picker/css/datepicker.css" />
        <link rel="stylesheet" type="text/css" href="../backend/styles/default.css" />
        <script type="text/javascript">
                function showHideCourt(){
                        if(document.getElementById('courtref').style.display == 'block'){
                                document.getElementById('courtref').style.display = 'none';
                                document.getElementById('showHideToggle').innerHTML = "Show Court Reference";
                        }
                        else{
                                document.getElementById('courtref').style.display = 'block';
                                document.getElementById('showHideToggle').innerHTML = "Hide Court Reference";
                        }
                }
        </script>
</head>
<body>
<div class="notes">
        <h2>Instructions</h2>
        <ul>
                <li>Use the links below to view existing bookings for each of the courts.</li>
                <li>Once you have selected a time you would like to book, scroll to the bottom of the page and fill out the booking form.</li>

        </ul>
</div>
<div class="courtlist">
<?php
        foreach($courts as $court){
                echo '<a href="bookings.php?cal=' . $court['cid'] . '">' . $court['name'] . '</a> | ';
        }
?>
        <a href="#" id="showHideToggle" onclick="showHideCourt()">Show Court Reference</a>
</div>
<div id="courtref" class="courtref">
        <img src="<?php echo SITE_ROOT . 'images/courts.png'; ?>" />
</div>

<?php
        if(strlen($message) > 1){
                echo '<div class="message">';
                echo $message;
                echo '</div>';
        }
?>

<iframe src="https://www.google.com/calendar/embed?mode=WEEK&amp;showTitle=1&amp;showCalendars=0&amp;height=1000&amp;wkst=2&amp;bgcolor=%23FFFFFF&amp;src=<?php echo $courts[$thecal]['id']; ?>&amp;color=%232952A3&amp;ctz=America%2FSt_Johns" style=" border-width:0 " width="800" height="600" frameborder="0" scrolling="no" id="califrame" onload="document.getElementById('califrame').contentWindow.scrollTo(0,document.getElementById('califrame').contentWindow.document.body.scrollHeight)"></iframe>
<h2>Book a Court</h2>
<form action="bookings.php?cal=<?php echo $thecal; ?>" method="post" name="booking">
        <input type="hidden" readonly="true" value="<?php echo $thecal; ?>" name="calendar"></input>
        Court: <input type="text" readonly="true" value="<?php echo $courts[$thecal]['name']; ?>" name="calendarname"></input><br />
        Title of Booking: <input type="text" value="Booking for: <?php echo substr($_SESSION['fname'],0,1) . '. ' . $_SESSION['lname'];  ?>" name="title"></input><br />
        Date: <input type="text" readonly="true" value="<?php echo date('Y-m-d'); ?>" id="startdate" name="startdate"></input><br />
        Start Time: <input type="text" readonly="true" value="" id="starttime" name="starttime"></input><br />
        End Time: <input type="text" readonly="true" value="" id="endtime" name="endtime"></input><br />
        <input type="submit" name="submit" value="Book Court"></input>
</form>
<div class="notes">
        <h2>Notes</h2>
        <ul>
                <li>If you need to change your booking, please call the club house.</li>
                <li>You can only book one session per day, and up to one week in advance.</li>
        </ul>
</div>
<script language="javascript">
        new Control.DatePicker('startdate', { icon: '../backend/libs/picker/images/calendar.png', datePicker: true, timePicker: false, dateFormat: 'yyyy-MM-dd' });
        new Control.DatePicker('starttime', { icon: '../backend/libs/picker/images/clock.png', datePicker: false, timePicker: true });
        new Control.DatePicker('endtime', { icon: '../backend/libs/picker/images/clock.png', datePicker: false, timePicker: true });
</script>
</body>
</html>

  30 Responses to “Part 3: A Sample Web Page For Bookings”

  1. [...] Part 3: A Sample Web Page For Bookings [...]

  2. any demostration please… thanks !

  3. I don’t have a public example to show. And showing screenshots won’t really mean anything. ;) You do have all of the pieces to make a fully functional application, are you curious about something specific?

    You could also join my tennis club this summer and see the code in action. :)

  4. Managed to get it working with the Google php library. The access token for the $client->setAccessToken() function has to be formatted in the following way:

    $at= ‘{“access_token”:”‘.$accessToken.’”,”token_type”:”Bearer”,”expires_in”:3600,”refresh_token”:”‘.$refresh_token.’”,”created”:’.time().’}';

    Where $accessToken is the access token returned by your getAccessToken() function and $refresh_token is the refresh token. Tested with the useless simple.php google example.

    Authentication is then just:
    $client->setAccessToken($at);

  5. Hello,

    Thank you for the tutorial it’s great. I get an error when I create a new event: “401 Invalid Credentials”.
    Yet I have followed all the steps carefully … Any idea? :)

  6. I too am getting the same problem, any ideas?

  7. I think I might have to take some time and go through my own tutorial again and see if I can replicate your problems. I’ll take a look tonight, and post anything I find tomorrow.

  8. I managed to isolate the problem for myself at least. I had somehow truncated Bearer from the request header. It’s working fine now, thanks.

  9. I’m a little confused about the format of the $request that needs to be passed to the sendGetRequest function. Could anyone help me out?!

    All I really want to do is grab the start/end times and event names from my google calendar and then use them in my webpage.

    Great tutorial. Really helpful.

  10. Hello Will,
    In my sendGetRequest function you need to pass two parameters, the token from the getAccessToken call, and the request, which is simply a URL with the parameters you want returned defined in the call. In the example above I have: https://www.googleapis.com/calendar/v3/calendars/‘ . $cal . ‘/events?timeMax=’ . $end . ‘&timeMin=’ . $start . ‘&fields=items(end%2Cstart%2Csummary)&pp=1&key=’ . $APIKEY

    The bold items are what you need to add. They are the calendar ID, and the start and end dates of the events you want to search for. These are essentially filters that limit the results. The last piece is the your API key.

    The part in italics is where you tell google what you want to get back. In my case I’m looking to get the end date, start date, and the event summary. I got this information from the Google API explorer: https://developers.google.com/apis-explorer/#p/

    While using the explorer, you can edit the ‘fields’ option at the very bottom, and using the fields explorer, select the items you want google to return. If you just want start/end times and event names your fields= should look like this: fields=items(end%2Cstart%2Csummary) which is the same as mine in the example. summary is the event title as far as I can tell.

    In the API explorer, above the results for the call, it will have the complete URL that is needed to get those results. So you can just harvest what you need from that for your code.

    Let me know if that clears things up.

  11. Hi Cornmaster,

    Thanks for your detailed reply. I appreciate it! I understand the format of the $request now, but unfortunately I’m getting the ’401 Invalid Credentials’ error. I guess there must be a problem elsewhere… Thanks anyway!

  12. Here the same, I was hoping finally to get it to work, invalid credentials. It drives me nuts. Help appreciated.

  13. Found a solution. This is what I use:

    $OAuth = array(
    ‘oauth_token_uri’ => ‘https://accounts.google.com/o/oauth2/token’,
    ‘client_id’ => ‘XXXXXXXXX.apps.googleusercontent.com’,
    ‘client_secret’ => ‘XXXXXXXXXXXXXXXXXXXX’,
    ‘redirect_uri’ => ‘XXXXXXXXXXXXXXXXXXXXXX’,
    ‘refresh_token’ => ‘XXXXXXXXXXXXXXXXXX’
    );

    function _get_auth_token_by_refresh($params)
    {
    $url = $params['oauth_token_uri'];

    $fields = array(
    ‘refresh_token’ => $params['refresh_token'],
    ‘client_id’ => $params['client_id'],
    ‘client_secret’ => $params['client_secret'],
    ‘grant_type’ => ‘refresh_token’
    );
    $response = _do_post($url, $fields);
    return $response;
    }

    function _do_post($url, $fields)
    {
    $fields_string = ”;
    foreach ($fields as $key => $value)
    {
    $fields_string .= $key . ‘=’ . $value . ‘&’;
    }
    $fields_string = rtrim($fields_string, ‘&’);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, count($fields));
    curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
    }

  14. And for the insert of a calendar event:

    function createPostArgsJSON($date,$starttime,$endtime,$title){
    $arg_list = func_get_args();
    foreach($arg_list as $key => $arg){
    $arg_list[$key] = urlencode($arg);
    }
    $postargs = <<<JSON
    {
    "start":
    {
    "dateTime": "2012-09-23T15:00:00+02:00"
    },
    "end":
    {
    "dateTime": "2012-09-23T16:00:00+02:00"
    },
    "description": "Dit is de omschrijving",
    "summary": "Dogwash: Dhr Peter Geens"
    }
    JSON;
    return $postargs;
    }

    function sendPostRequest($postargs,$token, $cal){
    global $APIKEY;
    $request = 'https://www.googleapis.com/calendar/v3/calendars/&#039; . $cal . '/events?pp=1&alt=json&key=' . $APIKEY;

    $session = curl_init($request);

    // Tell curl to use HTTP POST
    curl_setopt ($session, CURLOPT_POST, true);
    // Tell curl that this is the body of the POST
    curl_setopt ($session, CURLOPT_POSTFIELDS, $postargs);
    // Tell curl not to return headers, but do return the response
    curl_setopt($session, CURLOPT_HEADER, false);
    curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
    //curl_setopt($session, CURLOPT_VERBOSE, true);
    curl_setopt($session, CURLINFO_HEADER_OUT, true);
    curl_setopt($session, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Authorization: Bearer ' . $token,'X-JavaScript-User-Agent: PGI Google Calendar Connector'));

    $response = curl_exec($session);

    curl_close($session);
    return $response;
    }

  15. Can someone please share a full working example file of part 3 please?

  16. Great tutorial.. this was a huge help for me. The part that really doesn’t seem to be documented anywhere is how to pass the token via cURL and parse for the needed property.

    Just wanted to mention for those getting the 401 invalid credentials error, you probably didn’t complete step 2 properly. I was breezing through this the first time and didn’t paste the first variable that Google returns into the code post authorization and then execute the page again.

  17. Hi,

    would be great if we could get a working example or at least guidelines to compile all the information in the right php/html pages
    jl

  18. Hi,

    is this code supposed to store the token and avoid to ask permission everytime we one to book an event ?
    I get part 1 and2 ok but can’t get 3 to work, an example even basic would help,
    jl

  19. The code does not manage the token. It simply gets a new one for each request using the refresh token that you generate in step 2 of this tutorial.

    I’ve included a complete example of the page from the application I developed this for. Hopefully that will help get people started.

  20. Thanks for putting this together. A really great tutorial! It’s helped me a lot.

  21. Ellow! Great tut.. Though I get this error when I try to post an event: Undefined property: stdClass::$access_token in C:\…\..\..\calbook.php on line 21.. Ive changed the calendar to my own and its displaying ect. Any tips :) ? Ty in advance

  22. Hey! Saaweet tutorial, works perfectly… Though my problem is ive developt a website using jsp/servlets, and now I want to do the exact thing uve done here using Java. Been searching the webz forever but i have not been able to find a good example of this in java, only examples using Oauth where users have to log in ect! Anyone here know how to convert/do this in java? Would be much appreciated..

  23. Hi Cornmarster,

    Do you think this script could easily be adapted to give a range of users a calendar each?

  24. Hi Cornmaster! I just found your script and I hope it can help me out in my project. I have everything working up to and including part 2. On your Feb 1 update, you put everything together, and you have commented the start/end of corecal.php. Where is config.php? Is it the code on lines 146-218? Thanks!

  25. Steve: You could do that. You would just need to identify the users some how, with a cookie, session, or something, and then just serve the correct calendar from there.

    William: corecal.php is lines 10 – 144 while the config.php isn’t mentioned in that file. My config file was for the whole site, and isn’t actually used for this script. But, for completeness….the config.php file is:

    /**
    * Your database settings go here.
    */
    define('DB_HOST','localhost');
    define('DB_DB','{DBNAME}');
    define('DB_PREFIX','');
    define('DB_USER','{DBUSER}');
    define('DB_PASSWORD','{DBPASS}');

    /**
    * Site settings go here
    */
    define('SITE_ROOT','http://mptc.tennisnewfoundland.com/');
    define('SAVED_TREES','/PATH/TO/SAVED/TREES/');
    /*
    * Uncomment for debugging.
    */
    ini_set('display_errors',1);
    error_reporting(E_ALL);

  26. All I wanted to do was grab a public calendar feed, server-side, from the Google Calendar API (v3) and I struggled because of the lack of documentation on the google-api-php-client and the confusion over how to gain the access token and refresh key.

    Comments in this tutorial hinted at using the Google OAuth playground to get these tokens, which is what I ended up doing.

    Armed with a refresh token I was able to produce the following code that might help others:


    $client = new Google_Client();
    $client->setApplicationName("Google Calendar PHP Starter Application");
    $client->setClientId($clientId);
    $client->setClientSecret($clientSecret);

    if (isset($_SESSION['oa2_token'])) {
    $client->setAccessToken($_SESSION['oa2_token']);
    }

    if ($client->isAccessTokenExpired()) {
    $client->refreshToken($refreshToken);
    $_SESSION['oa2_token'] = $client->getAccessToken();
    }

    // .. calendar feed code

    Don’t use $_SESSION in production for storing the token as this is per client, store the token in a file cache or DB.

  27. maybe it is stupid question, but what does “pp=1″ mean? (&fields=items(end%2Cstart%2Csummary)&pp=1&key=’ . $APIKEY)

  28. I have to say, I was very overwhelmed by the Calendar API. All I wanted to do was be able to publish events to my calendar and every example on the Google developer site was so much more involved than I needed it to be. This guide was absolutely perfect and without it, I probably would have given up. I do want to suggest one code edit though. The OP uses this to organize usage of tennis courts which is a summer thing, so daylight savings time isn’t an issue. I use it for year round scheduling so in the winter in the Eastern timezone, our UTC offset is -05:00. In the summer, once we “Spring” ahead, our offset becomes -04:00. The result would be that my script was sending events to my calendar with a -05:00 offset (central time in the summer) and since my Google calendar is set to Eastern time, my events would be an hour later on my calendar. So I developed a PHP script to determine the offset and wanted to share the complete code for anyone who may or may not run into this issue. This code is my code that replaces function createPostArgsJSON which starts on line 72 of OPs original full working code (comments are mine for explanation).

    function createPostArgsJSON($date,$starttime,$endtime,$title,$description){
    $arg_list = func_get_args();
    foreach($arg_list as $key => $arg){
    $arg_list[$key] = urlencode($arg);
    }

    $dateTimeZoneUTC = new DateTimeZone(“UTC”); //This establishes the baseline for your offset… UTC is always Z or 00:00
    $dateTimeZoneNY = new DateTimeZone(“America/New_York”); //set to your current time zone (New_York, Indiana, Chicago, Denver, Etc).
    $dateTimeUTC = new DateTime(“now”, $dateTimeZoneUTC); //Creates a current timestamp in seconds
    $dateTimeNY = new DateTime(“now”, $dateTimeZoneNY); //The only difference between the two is the offset.
    $timeOffset = $dateTimeZoneNY->getOffset($dateTimeUTC); //Get the offset between the two timezoens
    $timeOffset = $timeOffset / 3600; //converts seconds to hours… will return single digit hours.
    $posneg = substr($timeOffset, 0, 1); //remove + or – from number so we can convert single to double digit number.
    $timeOffset = str_replace($posneg, “”, $timeOffset); //remove sign from offset.
    $timeOffset = sprintf(“%02s”, $timeOffset); //convert single digit to double (ie: turn 4 into 04).
    $timeOffset = $posneg.$timeOffset; // reunite double digit number with sign.
    // during EST, $timeOffset will be “-05″… during EDT, $timeOffset will be “-04″.

    $postargs = <<<JSON
    {
    "start": {
    "dateTime": "{$date}T{$starttime}:00.000{$timeOffset}:00" //incorporate the offset into your event start and end time.
    },
    "end": {
    "dateTime": "{$date}T{$endtime}:00.000{$timeOffset}:00"
    },
    "summary": "$title",
    "description": "$description"
    }
    JSON;
    return $postargs;
    }

    If you wanted to be even more thorough/universal you would monitor the half hours as well as some time zones are half hours (ie: Venezuela is -04:30) but for my purposes, I only needed US timezones which are all even hours. It would be a simple checking to see if there was a decimal point after dividing by 3600 which would mean there is a .5 and then adding 30 (so you could do $timeOffset = $posneg.$timeOffset.":30"; assuming you exploded the original offset and then only worked with the hour part for the manipulation of the double digit). I went through all the trouble of splitting out the sign and making the digit two digits because I wasn't sure if google would accept a single digit hour offset (+0:00 format instead of +00:00). To be safe, I assumed a double digit would be preferred.

    Once again, I appreciate the OP in putting this together, just offering a solution to a problem I found that the OP would probably never run into due to only using his during the summer.

  29. For the OP in moderating… an edit to my comment. My code also assumes you are negative or west of UTC. A worldwide/universal code would need to check for half hours (as already noted) and would also need to check to see if the $timeOffset variable is positive or negative (if it is positive, you wouldn’t want to substr and you’d want to set $posneg = “+”. I don’t know if you can add this comment into my original post while moderating.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>