EGCal - A Google Calendar Extension for Yii ¶
- Download
- About
- How it Works
- Purpose
- Requirements
- Google Calendar Requirements
- Retrieving Events
- Creating Events
- Updating Events
- Deleting Events
DEPRECATION NOTICE ¶
On November 17, 2014, Google Will Deprecated V2 of the Calendar API. This extension will consequently no longer be supported or functional after that date.
=============
Download ¶
EGCal is available on Github for Download:
Extensions, updates, and other news can be found on my blog
About ¶
EGCal is a simple extension that enables Yii Applications to communicate with Google Calendar.
How it Works ¶
EGCal works by making a autorization request to Google Calendar via ClientLogin. All subsequent requests are then made through a single connection.
Purpose ¶
One of the projects I was working on required me to retrieve events from Google Calendar. I didn't see any good classes that were simple or intuitive to use, so I decided to build my own. The purpose of this class is to provide a simple interface between the programmer and Google Calendar. Once I have completed this class in it's entirety I may consider building an OAuth version of the class to use with Google Calendar API 3.0.
Requirements ¶
PHP 5.3+
Yii Framework 1.1.x
php-curl installed
Usage ¶
Importing the Class ¶
Yii::import('application.extensions.EGCal.EGCal');
Instantiation ¶
You have a couple of options here. All you need to do to get it working is to call:
$cal = new EGCal('username@gmail.com', 'gmail-password');
If you would like EGCal to provide debugging text:
$cal = new EGCal('username@gmail.com', 'gmail-password', TRUE);
By default, EGCal uses your application name (Yii::app()->name) for the source request identifier. This can be easily altered by calling (with debugging disable)
$cal = new EGCal('username@gmail.com', 'gmail-password', FALSE, 'companyName-applicationName-versionID');
Google Calendar Requirements ¶
Calendars (calendar id's) must be either own be owned by the user or granted read/write access to the calendar. Timezones should also be appropriatly set within Google Calendar.
Retrieving Events ¶
Retrieving events can be done by calling find() as such:
$response = $cal->find(
array(
'min'=>date('c', strtotime("8 am")),
'max'=>date('c', strtotime("5 pm")),
'limit'=>50,
'order'=>'a',
'calendar_id'=>'#stardate@group.v.calendar.google.com'
)
);
The fields min, max, and calendar_id are required. The fields limit and order are option, and default to 50, and ascending respectivly.
The min and max times should be in ISO 8601 date format [ date('c') ], and may require a timezone offset.
Adjusting for Timezone ¶
Sometimes events may appear to be several hours off. This is due to the timezone of your Google Calendar differing from that of your PHP System Time. This can be easily corrected by modifying the calendar settings, and/or offseting the min and max request times by a timezone offset.
Response ¶
Responses will take the form of a php array, containing the total number of events , and an array of events. Each event will contain the calendar ID, the start and end times, and the title of the event.
For example:
Array
(
[totalResults] => z
[events] => Array
(
[0] => Array
(
[id] => n9af6k7fpbh4p90snih1vfe1bc
[start] => 2011-12-19T08:20:00.000-06:00
[end] => 2011-12-19T08:40:00.000-06:00
[title] => Meeting with Josh
)
[1] => Array
(
[id] => ux5ohbtgbr0u2tk6cyivsi8tj9
[start] => 2011-12-19T15:30:00.000-06:00
[end] => 2011-12-19T17:00:00.000-06:00
[title] => Meeting with Jane
)
[...]
)
)
Creating Events ¶
Single Events ¶
Single events can be created with the following format
$response = $cal->create(
array(
'start'=>date('c', strtotime("4 pm")),
'end'=>date('c', strtotime("5 pm")),
'title'=>'Appointment with Jane',
'details'=>'Talk about business proposal',
'location'=>'My Office',
'calendar_id'=>'#stardate@group.v.calendar.google.com'
)
);
Adjusting for Timezone ¶
As with retrieving events, you may have to offset your start and end times depending on your timezone.
Response ¶
An unsuccessful response will return an empty array
A successful response will look as follows:
Array
(
[id] => GoogleCalendarID
[title] => Appointment with Jane
[details] => Talk about business proposal
[location] => My Office
[start] => 2011-12-19T16:00:00.000-06:00
[end] => 2011-12-19T17:00:00.000-06:00
)
Updating Events ¶
Updating events is very simmilar to creating them, with the sole exception that with the request, you also pass the event_id you wish to update. Such a request may look as follows
$response = $cal->update(
array(
'id'=>'calendar_id',
'start'=>date('c', strtotime("4 pm")),
'end'=>date('c', strtotime("5 pm")),
'title'=>'Appointment with Jane',
'details'=>'Talk about business proposal',
'location'=>'My Office',
'calendar_id'=>'#stardate@group.v.calendar.google.com'
)
);
A successful response will look as follows:
Array
(
[id] => GoogleCalendarID
[title] => Appointment with Jane
[details] => Talk about business proposal
[location] => My Office
[start] => 2011-12-19T16:00:00.000-06:00
[end] => 2011-12-19T17:00:00.000-06:00
)
Deleting Events ¶
Single events can be deleted by calling the delete method. Deleting an event requires both the specific event_id you with to delete, and the calendar_id that event belongs to.
For example:
$response = $cal->delete(
array(
'id'=>'9u5fj46m0fcd8scb3dohds2kso',
'calendar_id'=>'#stardate@group.v.calendar.google.com'
)
);
The delete method will return true if the event was deleted, and false if the event could not be deleted. If logging is enabled, the response code and message from Google will be provided.
Problem Class 'Curl' not found
Hi, I have problems using this, I have cURL installed and Curl.php in the EGCal folder.
curl = curl_init($url); } /** * Allows for custom headers to be sent before the request is made * * @param array $headers * Headers to be sent with the request * * @param string $url * Modified URL if it needs to be changed from the original request * * @param bool $post * Whether we are performing a GET or POST requst, defaults to GET * * @param bool $follow * Whether we are to follow redirects * * @param int $redirects * Number of redirects to follow before stopping * **/ public function setHeader($headers=array(), $url=NULL, $post=NULL, $follow = TRUE, $redirects = 30) { curl_setopt($this->curl, CURLOPT_URL, $url == NULL ? $this->url : $url); curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers); if ($post != NULL) { curl_setopt($this->curl, CURLOPT_POST, $post); } curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, $follow); // follow redirects curl_setopt($this->curl, CURLOPT_MAXREDIRS, $redirects); // maximum number of redirects } public function run($method, $content = NULL) { if ($method != 'POST' && $method != 'GET') { curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method); } else { curl_setopt($this->curl, CURLOPT_POST, ($method == 'POST') ? true : false); } if ($content != NULL) { curl_setopt($this->curl, CURLOPT_POSTFIELDS, $content); } curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($this->curl); $this->error_code = curl_errno($this->curl); $this->status = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); $this->error_string = curl_error($this->curl); $this->last_url = curl_getinfo($this->curl, CURLINFO_EFFECTIVE_URL); curl_close($this->curl); return $response; } /** * Retrieves the CURL error code retrieved from the last request * @return int $this->error_code **/ public function getErrorCode() { return $this->error_code; } /** * Retrieves the HTTP status code from the last request * @return int $this->status **/ public function getStatus() { return $this->status; } /** * Retrieves the error string from the last request * @return int $this->error_string **/ public function getErrorString() { return $this->error_string; } /** * Retrieves the last URL CURL processed * @return int $this->last_url **/ public function getLastURL() { return $this->last_url; } } ?> Fatal error: Class 'Curl' not found in D:\AppServ\www\icc\public\app\protected\extensions\EGCal\EGCal.php on line 103
UPDATE: problem solved, my PHP does not support files that start with <?, I've changed it the Curl.php file to start with <?php and now it works...
creating events problem
Thank you for a great extension.
I have a problem though, it seems that creating new events is not working properly.
First problem: The events are created twice (so a double event is created with the same data)
Second problem: no matter what date/time I specify and in what format, the event is always created for the current day and time.
Any ideas? can you test if it is working for you fine currently? I went briefly through GApi docs and it seems that there are differences in the data you are sending and the things that google is expecting ?
thanks
Updated
@warden
I'm not experiencing the double creation issue, but I did fix the "create on this day only" issue and have updated the github repository with the modified code. Can you post your code for the first problem? It sounds like the code is running twice.
Make sure the start and end times look like:
date('c', strtotime("TIME STAMP HERE"))
Just make sure strtotime can handle it.
Also, this code uses Calendar API2, not API3 (which is the latest version). The API reference is located here
Great!
Thanks for a quick fix! Indeed, it resolved the problem of time for created issues. And indeed, I was looking at APIv3, that's why I couldn't fix it myself :)
As for the double creation, it still happens, and I have no idea why, the code is nothing special, but any ideas welcome..
It happens for me in two different environments (mac and linux).
public function actionaddToCalendar($id){ /* only ajax post requests supported */ if (Yii::app()->request->isPostRequest && Yii::app()->request->isAjaxRequest ){ Yii::import('application.extensions.EGCal.EGCal'); $cal = new EGCal(Yii::app()->params['google']['username'], Yii::app()->params['google']['password']); $event = Event::model()->findByPk($id); if (!$event){ echo Yii::t("application","Error."); Yii::app()->end(); } $request = array( 'start'=> date('c', strtotime($event->date )), 'end'=> date('c', strtotime($event->date) +86400 ), 'title'=> $event->title, 'details'=> $event->description, 'location'=>'', 'calendar_id'=>Yii::app()->params['google']['calendar'] ); $response = $cal->create($request); if (count($response)>0){ Yii::app()->user->setFlash('success',Yii::t("application","OK")); } else { Yii::app()->user->setFlash('error',Yii::t("application","NOT OK.")); } } else throw new Exception("403"); Yii::app()->end(); }
DP
@warden
I haven't run the code, but just from looking I don't see anything that jumps out. If you run just
$request = array( 'start'=> date('c', strtotime($event->date )), 'end'=> date('c', strtotime($event->date) +86400 ), 'title'=> $event->title, 'details'=> $event->description, 'location'=>'', 'calendar_id'=>Yii::app()->params['google']['calendar'] ); $response = $cal->create($request);
Does it do a double creation? The only thing I can think of that is causing that is a double POST/Form submission from your view or AJAX request.
Double Entry
@charlesportwoodii
I've got the same issue as warden, the entry gets doubled.
I've even used the latest version from Github.
I've made a modification in EGCal.php:
This:
// Make an initial request to get the GSESSIONID $response = $curl->run('POST', json_encode($data)); $last_url = $curl->getLastURL(); // Error code is 200, but is preceeded by a 301 for the gSessionId unset($curl); // Rebuild the Object to create to create the actual create Request $curl = new Curl($url); $curl->setHeader($this->headers, $last_url, TRUE);
becomes this:
// Make an initial request to get the GSESSIONID //$response = $curl->run('POST', json_encode($data)); //$last_url = $curl->getLastURL(); // Error code is 200, but is preceeded by a 301 for the gSessionId unset($curl); // Rebuild the Object to create to create the actual create Request $curl = new Curl($url); $curl->setHeader($this->headers, $url, TRUE);
I've commented out the first request, then proceed to the second request, and everything works fine.
Double Entries, gSessionId
@Strangler
I encourage your to make a GitHub pull request if that truly solves the issue. When I get a chance I'll try to merge it in if everything tests out. My concern with that approach is that it does not take into consideration the following:
Just like in the previous example, Calendar may return an HTTP 302 redirect; if so, then the redirect URL has a new parameter, gsessionid, appended to it. If you received the redirect, then send the same POST request again, with the same Authorization header and the same content, but with the gsessionid parameter appended. The response may also include a S cookie, which you should store and send this cookie with future requests as appropriate. For more information on handling sessions with the Calendar Data API, see the knowledge base. Please note: if a session ID indicated in a cookie header conflicts with the session ID passed as a gsessionid URL parameter, you will get caught in a redirect loop.
During my initial tests I never once generated a request that didn't receive that redirect and assumed that it was the default behavior. If it is behaving differently then I believe another approach would be better suited for handling if a 302/301 redirects occurs with the gSessionId.
Submit a pull request to Github and I'll look at merging that updated code in with a gSessionId check in it.
Thanks,
EGCal Update
@Strangler's pull request has been merged. (Better late than never right? Been really busy the past few months).
Curl Class using Curl Options
Hello,
thanks for this nice extension.
I stumbled upon being behind a company proxy, so I added functionality to pass options to the curl instance.
Usage:
// SET some cUrl options (e.g. using aproxy server): $curlOpts = array( CURLOPT_PROXY => 'www.my-proxy-server.com:port', CURLOPT_PROXYUSERPWD => 'ProxyUsername:ProxyUserPassword', ); // Instantiate calendar: $cal = new EGCal('username@gmail.com', 'gmail-password', TRUE, Yii::app()->name, $curlOpts);
This is the patch:
[diff] diff -Naur EGCal/Curl.php EGCal-with-curl-options/Curl.php --- EGCal/Curl.php Wed Jul 18 07:18:04 2012 +++ EGCal-with-curl-options/Curl.php Fri Oct 26 11:29:42 2012 @@ -18,16 +18,22 @@ private $error_string; // Last effective url - private $last_url; + private $last_url; + + private $curlOpts; /** * PHP5 Constructor * @param string $url * The URL to be requested **/ - public function __construct($url) + public function __construct($url, $curlOpts=null) { - $this->curl = curl_init($url); + $this->curl = curl_init($url); + if(!is_null($curlOpts)) { + $this->curlOpts = $curlOpts; + curl_setopt_array($this->curl, $curlOpts); + } } /** diff -Naur EGCal/EGCal.php EGCal-with-curl-options/EGCal.php --- EGCal/EGCal.php Wed Jul 18 07:18:04 2012 +++ EGCal-with-curl-options/EGCal.php Fri Oct 26 11:34:18 2012 @@ -43,7 +43,9 @@ private $response_code; // CURL Headers - private $headers; + private $headers; + + private $curlOpts=null; /** * Constructor. Called on new GCal(). Sets up initial connection @@ -58,8 +60,10 @@ * @call new GCal('username', 'password', 0, 'companyName-appName-versionId') * @return void **/ - public function __construct($username, $password, $level = 0, $source = NULL) - { + public function __construct($username, $password, $level = 0, $source = NULL, $curlOpts=null) + { + if(!is_null($curlOpts)) + $this->curlOpts = $curlOpts; if ($source == NULL) { $this->source = str_replace(' ', '_',Yii::app()->name); @@ -100,8 +104,8 @@ ); Yii::import('application.extensions.EGCal.Curl'); - $curl = new Curl('https://www.google.com/accounts/ClientLogin'); - $response = $curl->run('POST', $content); + $curl = new Curl('https://www.google.com/accounts/ClientLogin', $this->curlOpts); + $response = $curl->run('POST', $content); $this->response_code = $curl->getStatus(); @@ -219,7 +223,7 @@ // Load the CURL Library Yii::import('application.extensions.GCal.Curl'); - $curl = new Curl($url); + $curl = new Curl($url, $this->curlOpts); // Set the headers $curl->setHeader($this->headers, $url, false); @@ -362,7 +366,7 @@ // Load the CURL Library Yii::import('application.extensions.GCal.Curl'); - //$curl = new Curl($url); + //$curl = new Curl($url, $this->curlOpts); // Create a blank data set $data = array(); @@ -420,7 +424,7 @@ // Rebuild the Object to create to create the actual create Request - $curl = new Curl($url); + $curl = new Curl($url, $this->curlOpts); $curl->setHeader($this->headers, $url, TRUE); // Make an initial request to get the gSessionId @@ -529,7 +533,7 @@ // Load the CURL Library Yii::import('application.extensions.GCal.Curl'); - $curl = new Curl($url); + $curl = new Curl($url, $this->curlOpts); // Set the headers for an If-Match Request $this->setHeaders(TRUE, strlen(json_encode($data))); @@ -595,7 +599,7 @@ // Load the CURL Library Yii::import('application.extensions.GCal.Curl'); - $curl = new Curl($url); + $curl = new Curl($url, $this->curlOpts); // Set the headers for an If-Match Request $this->setHeaders(TRUE); @@ -637,4 +641,4 @@ } } -?> \ No newline at end of file +?>
Save the patch into the file
extensions/egcal.patch
.Apply the patch:
Regards,
Joachim
Updating Calendar Event
Hi again,
I noticed that on updating an existing calendar event, the start date gets set to 1970-01-01 ?!
Regards,
Joachim
Unix Epoch Events
@jwerner,
Google Calendar is very picky about the way timestamps should be sent/received to it. If the timestamp is an invalid variable it will most likely default to 1970. (It's been a while since I've last looked a the API Documentation.)
Make sure your date('c', time()) call is generating the right timestamp. The date you're generating might not be resolving to the date you intended.
date('c', strtotime("5 pm"))
If that doesn't work I recommend you looking at the cURL requests directly and looking at what responses you are sending/receiving.
Re: Unix Epoch Events
@Charles,
I think there's something unclear in the docs.
Creating an Event states to use
date('c', ...
$response = $cal->create( array( 'start'=>date('c', strtotime("4 pm")), 'end'=>date('c', strtotime("5 pm")), 'title'=>'Appointment with Jane', 'details'=>'Talk about business proposal', 'location'=>'My Office', 'calendar_id'=>'#stardate@group.v.calendar.google.com' ) );
Whereas in EGCal.php/create,
date('c', ...
is also used?// Build the data query $data = array( 'data'=>array( // ... 'when'=>array(array( 'start'=>date('c', strtotime($options['start'])), 'end'=>date('c', strtotime($options['end'])) )) ) );
Perhaps you can also add this in function
create
when addintstart/end
to$data
:(see https://developers.google.com/google-apps/calendar/v3/reference/events/insert)
/** * Returns a date/time as per RFC3339 * * @param int $timestamp (Unix Timestamp) * @return string E.g. '2011-06-03T10:25:00.000-07:00' */ public function dateRFC3339($timestamp=0) { if (!$timestamp) { $timestamp = time(); } $date = date('Y-m-d\TH:i:s', $timestamp); $date .= '.000'; $matches = array(); if (preg_match('/^([\-+])(\d{2})(\d{2})$/', date('O', $timestamp), $matches)) { $date .= $matches[1].$matches[2].':'.$matches[3]; } else { $date .= 'Z'; } return $date; }
Unix Epoch Date
The method itself re-wraps everything in the date('c'... call simply so it can handle other issues that may come up. The date function shouldn't have a problem handling something already date-ified. Additionally, the front end is setup to make it easier on the programmer. What is actually passed to Google Calendar is not the same data set that is passed to the method itself.
If you're unable to get the issue resolved, I recommend checking out Google's official Calendar API (v3), as this one will reach EOL (v2 will be deprecated by Google) within the next year or so.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.