Dec 082013
 

I have updated my Hesk hack based on a request from a reader. You can view the new instructions or download the new files here: http://wiki.cornempire.net/hesk/hesk252

For those unaware, my previous post on this matter was here: http://cornempire.net/2010/07/31/hesk-enhancements-for-version-2-2/

2.5 introduced a lot of changes. One of the big ones that affected my hack was the ability to directly link to a category type. This is now built into the application, so I modified my hack to take advantage of this.

If you have any questions, let me know.

Feb 262012
 

One of my long time goals has been to be able to easily display tournament brackets on the web. It seems to be a difficult problem to solve with lots of questions out there, and few answers. There are a few solutions, but they have various issues and downfalls.

I decided to give it another try myself. And this is what I have come up with: A bracket builder, a saved file with raw data, and a bracket viewer that can read the files. If all that sounds like too much, the bracket builder can just spit out the HTML code to display the tree.

I’ve outlined how it works, uploaded an example and have provided a download so you can integrate it with your own applications.

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>
Jan 082012
 

This is the second 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 <- You Are Here
  3. Part 3: A Sample Web Page For Bookings

Background

This was by far the hardest part of the whole exercise. I had worked with version 1 of the Google API for PHP a few years back. This allowed you to code your username and password into your script, and it would handle authentication for your application. Now that we are on to version 3 of the API, that method is no longer available. Instead OAuth2 is used for authentication and token management.

I downloaded code samples, and went about building my application, however, I quickly realized that the OAuth2 code samples are designed to allow you to interact with a visitors calendar. In the case of taking online bookings, I need to work with a single calendar, namely my calendar, not theirs.

After a lot of trial and error, and then reading, I realized that it could be done, and relied on what is called a ‘Refresh Token’ in OAuth2. This token allows you to get a new valid authentication token when the initial grant from your end user expires. Since the refresh token doesn’t expire, you can always use it to get a new authentication token, and therefore people can continue to use your application after you have initially configured it. I spent a while trying to implement it myself with no success, but then I came across this page: http://www.ericnagel.com/how-to-tips/google-affiliate-network-api.html This explains in some detail how to configure the application and token. It is written for the Google Affiliate Network API, but I made a few tweaks to make it work for Calendar. I will now take you through the steps of setting up your application with Google, and generating your Refresh Token.

Create Your Application

Log into your Google Account, and then visit https://code.google.com/apis/console/. This will take you to a page that invites you to create a project with the Google API. Click on Create project….

You are now asked to activate the services you wish to use. Click the button next to Calendar API to enable the calendar. You will be redirected to a page with a Terms of Service. Read and accept this.

Now click on API Access. Here we will configure the IDs needed for your application to authenticate with Google. Click on Create an OAuth2 client ID…. You will be offered to create Branding Information. You should add your project/product name. The rest won’t be necessary as you will not be asking users directly for access to their resources, but you can complete it if you like.

Then click Next. Here you will want to select Installed application. Click Create client ID.

You will be taken back to the API Access screen, with your new Client ID and Client secret. You will need this information to generate your Refresh Token, and to configure your application.

This page will also have your API key for ‘Simple API Access’. You will also need this API Key for your final calendar application.

Get Your Application Information

Now that you have your application information, it is time to generate your refresh token. I’ve modified the script available from http://www.ericnagel.com/how-to-tips/google-affiliate-network-api.html to just get us our refresh token for our calendar application. Here is the code for the script. Download this and save it as oauth-setup.php:

<?php
$cScope         =   'https://www.googleapis.com/auth/calendar';
$cClientID      =   '';
$cClientSecret  =   '';
$cRedirectURI   =   'urn:ietf:wg:oauth:2.0:oob';
 
$cAuthCode      =   '';

if (empty($cAuthCode)) {
    $rsParams = array(
                        'response_type' => 'code',
                        'client_id' => $cClientID,
                        'redirect_uri' => $cRedirectURI,
                        'access_type' => 'offline',
                        'scope' => $cScope,
                        'approval_prompt' => 'force'
                     );

    $cOauthURL = 'https://accounts.google.com/o/oauth2/auth?' . http_build_query($rsParams);
    echo("Go to\n$cOauthURL\nand enter the given value into this script under \$cAuthCode\n");
    exit();
} // ends if (empty($cAuthCode))
elseif (empty($cRefreshToken)) {
    $cTokenURL = 'https://accounts.google.com/o/oauth2/token';
    $rsPostData = array(
                        'code'          =>   $cAuthCode,
                        'client_id'     =>   $cClientID,
                        'client_secret' =>   $cClientSecret,
                        'redirect_uri'  =>   $cRedirectURI,
                        'grant_type'    =>   'authorization_code',
                        );
    $ch = curl_init();
 
    curl_setopt($ch, CURLOPT_URL, $cTokenURL);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $rsPostData);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 
    $cTokenReturn = curl_exec($ch);
    $oToken = json_decode($cTokenReturn);
    echo("Here is your Refresh Token for your application.  Do not loose this!\n\n");
    echo("Refresh Token = '" . $oToken->refresh_token . "';\n");
} // ends
?>

Before running this script, you will need to enter your Client ID ($cClientID) and Client Secret ($cClientSecret) as we found on the API page with Google. Once these values are added, run this script from the command line like this: php oauth-setup.php. You should see output like this:

thomas@thomas-desktop:~/code$ php oauth-setup.php 
Go to
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=##########################&redirect_uri=###############&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar
and enter the given value into this script under $cAuthCode

Visit the website, grant permission to access your resources, and then copy the code on this page. This is your auth code, and is normally good for 3600 seconds or so.

Enter this code into the oauth-setup.php script in the $cAuthCode variable. Then run the script again: php oauth-setup.php. You should see output like this:

thomas@thomas-desktop:~/code$ php oauth-setup.php 
Here is your Refresh Token for your application.  Do not loose this!

Refresh Token = '#####################################';

Now, copy down the Refresh Token and save it for later. You will need it to make subsequent requests to Google to get a valid Auth Code for a transaction.

Stay tuned for Part 3 of the tutorial, which will use the above information to make calendar requests to Google. And allow us to create a web application that uses Google Calendar as a backend for a scheduling application.

Dec 232010
 

Hello everyone,
I’ve been saying that I was going to post this for a long time, ever since my presentation at Fusion 2010: http://www.desire2learn.com/fusion/ and now I finally have.

I have written up two pages on code I developed for use with PHP and D2L’s web services. I have been using it at my work for the last year or so, and I finally polished it up a little, and created some installation and usage instructions. I’m not finished yet, but it is a start.

If you use D2L and want to extend it through Web Services (you need to purchase the web services package from D2L, and have it installed first), I encourage you to read over these pages:

If you have any comments, please post them below. Feel free to email me with any questions or problems you run into while working with the API or the testing framework. I’m eager to improve the code for everyone.

Jul 312010
 

I’ve ported my popular (that means more than one person is using it. ;)) hacks from version 2.1 of Hesk to the new 2.2. You can read the whole tutorial on my wiki: http://wiki.cornempire.net/hesk/hesk22/

For those upgrading, you will need to reapply the hack to the new files. The differences were pretty minor between 2.1 and 2.2. I’ll outline the differences here:

  • The line numbers are different for the files
  • The function print_add_ticket_start that you need to add needs a few additional lines to bring in the global stylesheets. This was not required in 2.1
  • I also made a small tweak to this function so that there is no category selected by default, allowing you to select the default category via the dropdown box. Previously, this would have been unselectable.

I’m working on a screencast of applying this hack for those who are more visual learners. So stay tuned for that.

To take a look back at my previous post, and learn more about Hesk, click here.

May 072010
 

Just a quick announcement that I’m working on a new programming project called The Director!. It is essentially a url redirection/shortening service written in PHP and utilizing the apache rewrite module to do the really cool stuff.

Those in the know realize that creating such a service is just a few lines of code in the vhost entry for apache, and then a few lines of code in a php script to do the redirection, but the goal here is to make it easy to use management interface with a bit of simple stat tracking.

Hoping to have version 0.1 out this weekend (depending on how my fence construction goes this weekend).

You can view more information here: http://wiki.cornempire.net/thedirector/start

Oct 252008
 

If you are doing any PHP command line scripting, you may have wondered how you can add a bit of simple colouring to your scripts so it is easier for you, or your clients to view the output.

Using some Bash codes, you can colour the output to the terminal (assuming you are running the scripts in Bash or some other compatible terminal). Depending on the font colours and effects you want, you can use any of this code in your script. Just place it at the top, and include the variables in your output:


/*
 * Font Colours
 */
$BLACK="33[0;30m"; //Can not be used on backgrounds
$DARKGRAY="33[1;30m"; //Can be used on backgrounds
$RED="33[0;31m"; //Can not be used on backgrounds
$LIGHTRED="33[1;31m"; //Can be used on backgrounds
$GREEN="33[0;32m"; //Can not be used on backgrounds
$LIGHTGREEN="33[1;32m"; //Can be used on backgrounds
$BROWN="33[0;33m"; //Can not be used on backgrounds
$YELLOW="33[1;33m"; //Can be used on backgrounds
$BLUE="33[0;34m"; //Can not be used on backgrounds
$LIGHTBLUE="33[1;34m"; //Can be used on backgrounds
$PURPLE="33[0;35m"; //Can not be used on backgrounds
$LIGHTPURPLE="33[1;35m"; //Can be used on backgrounds
$CYAN="33[0;36m"; //Can not be used on backgrounds
$LIGHTCYAN="33[1;36m"; //Can be used on backgrounds
$LIGHTGRAY="33[0;37m"; //Can not be used on backgrounds
$WHITE="33[1;37m"; //Can be used on backgrounds
/*
 * Bolded colours that can be used on backgrounds
 * Duplicates of the colours above with BOLD in the name
 */
$BOLDBLACK="33[1;30m";
$BOLDRED="33[1;31m";
$BOLDGREEN="33[1;32m";
$BOLDBROWN="33[1;33m";
$BOLDBLUE="33[1;34m";
$BOLDPURPLE="33[1;35m";
$BOLDCYAN="33[1;36m";
$BOLDGRAY="33[1;37m";
/*
 * Background Colours
 */
$BLACKBG="33[0;40m";
$REDBG="33[0;41m";
$GREENBG="33[0;42m";
$BROWNBG="33[0;43m";
$BLUEBG="33[0;44m";
$PURPLEBG="33[0;45m";
$CYANBG="33[0;46m";
$LIGHTGRAYBG="33[0;47m";
/*
 * Font Effects
 */
$UNDERLINE="33[4;30m";
$BLINK="33[5;30m"; //Doesn't seem to work.
$INVERSE="33[7;30m";
$INVISIBLE="33[8;30m"; //Pretty pointless
/*
 * Turn it back to the default
 */
$DEFAULT="33[0m";

To test this out, you can place this in a php script and execute it:


echo "<-- Font Colours -->
";
echo "$BLACK This is BLACK
";
echo "$DARKGRAY This is DARKGRAY
";
echo "$RED This is RED
";
echo "$LIGHTRED This is LIGHTRED
";
echo "$GREEN This is GREEN
";
echo "$LIGHTGREEN This is LIGHTGREEN
";
echo "$BROWN This is BROWN
";
echo "$YELLOW This is YELLOW
";
echo "$BLUE This is BLUE
";
echo "$LIGHTBLUE This is LIGHTBLUE
";
echo "$PURPLE This is PURPLE
";
echo "$LIGHTPURPLE This is LIGHTPURPLE
";
echo "$CYAN This is CYAN
";
echo "$LIGHTCYAN This is LIGHTCYAN
";
echo "$LIGHTGRAY This is LIGHTGRAY
";
echo "$WHITE This is WHITE
";

echo "<-- Backgrounds -->
";
echo "$BLACKBG This is BLACKBG
";
echo "$REDBG This is REDBG
";
echo "$GREENBG This is GREENBG
";
echo "$BROWNBG This is BROWNBG
";
echo "$BLUEBG This is BLUEBG
";
echo "$PURPLEBG This is PURPLEBG
";
echo "$CYANBG This is CYANBG
";
echo "$LIGHTGRAYBG This is LIGHTGRAYBG
";

echo "<-- Font Effects -->
";
echo "$UNDERLINE This is UNDERLINE{$DEFAULT}
";
echo "$BLINK This is BLINK //Doesn't seem to work.
";
echo "$INVERSE This is INVERSE{$DEFAULT}
";
echo "$INVISIBLE This is INVISIBLE {$DEFAULT}//Invisible - Pretty pointless
";
echo "$DEFAULT Back to the default.
";

echo "<-- A Few Examples -->
";
echo "{$REDBG}{$YELLOW}This is a red background, with yellow font that {$UNDERLINE}{$YELLOW}is underlined{$DEFAULT}{$REDBG}{$YELLOW} for a portion. {$DEFAULT}
";
echo "{$CYANBG}{$LIGHTRED}This is red text on a cyan background. {$DEFAULT}
";

And practical example would be:


//Check to see if courses.txt exists
if(!file_exists($courses)){
	echo "$RED The courses.txt file doesn't exist. $DEFAULT
";
	exit();
}