Yii 1.1: egcal

A Google Calendar Extension for Yii
17 followers

EGCal - A Google Calendar Extension for Yii

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.

Total 13 comments

#10474 report it
Charles R. Portwood II at 2012/10/30 07:39pm
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.

#10442 report it
jwerner at 2012/10/29 04:56am
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 addint start/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;
}
#10420 report it
Charles R. Portwood II at 2012/10/26 10:23am
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.

#10418 report it
jwerner at 2012/10/26 10:10am
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

#10412 report it
jwerner at 2012/10/26 07:57am
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 -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:

extensions/EGCal$ patch -p1 ../egcal.patch

Regards,

Joachim

#9095 report it
Charles R. Portwood II at 2012/07/19 11:19am
EGCal Update

@Strangler's pull request has been merged. (Better late than never right? Been really busy the past few months).

#8269 report it
Charles R. Portwood II at 2012/05/22 08:30am
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,

#8265 report it
Strangler at 2012/05/22 05:04am
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.

#7877 report it
Charles R. Portwood II at 2012/04/24 10:38am
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.

#7861 report it
warden at 2012/04/23 04:47pm
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();
    }
#7850 report it
Charles R. Portwood II at 2012/04/22 07:03pm
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

#7848 report it
warden at 2012/04/22 06:10pm
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

#7655 report it
mickey85 at 2012/04/05 08:41am
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...

Leave a comment

Please to leave your comment.

Create extension
Downloads
No downloadable files yet