<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
* Services_Google_Calendar
*
* A PHP implementation of the Google Calendar Data API
* (http://code.google.com/apis/gdata/calendar.html)
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt.  If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category   Services
* @package    Services_Google_Calendar
* @author     Keigo AOKI <hoyo@kdn-web.net>
* @copyright  2006 KDN
* @license    http://www.php.net/license/3_0.txt  PHP License 3.0
* @version    CVS: $Id:$
* @link       http://pear.kdn-web.net/package/XML_Serializer
*/

/**
* uses PEAR error management
*/
require_once 'PEAR.php';

/**
* uses HTTP_Client for GData authentication
*/
require_once 'HTTP/Client.php';

/**
* uses XML_Serializer for build and parse atom feed
*/
require_once 'XML/Serializer.php';
require_once
'XML/Unserializer.php';


/**
* end point of gdata api
*/
define('SERVICES_GOOGLE_CALENDAR_URL_AUTH', 'https://www.google.com/accounts/ClientLogin');
define('SERVICES_GOOGLE_CALENDAR_URL_FEED', 'http://www.google.com/calendar/feeds/');
define('SERVICES_GOOGLE_CALENDAR_URL_POST', 'http://www.google.com/calendar/feeds/default/private/full');

/**
* user agent of this class
*/
define('SERVICES_GOOGLE_CALENDAR_SOURCE', 'kdn-gcalendarApi-1.0');


/**
* Services_Google_Calendar
*
* usage sample to get events.
*
* <code>
* require_once 'Services/Google/Calendar.php';
*
* // initialize
* $gc = new Services_Google_Calendar();
*
* // get events data
* $public_data = $gc->getEvents($gmail);
*
* // get events data (private mode)
* $private_data = $gc->getEvents($gmail, $hash);
* <code>
*
*
* usage sample to add an event.
*
* <code>
* require_once 'Services/Google/Calendar.php';
*
* // initialize
* $gc = new Services_Google_Calendar($gmail, $passwd);
*
* // set event data
* $entry['title']        = 'Event title';
* $entry['content']      = 'Event description';
* $entry['where']        = 'Where the event helds';
* $entry['when'][0]      = '2006-10-20';
* $entry['when'][1]      = '2006-10-24';
* $entry['transparency'] = 'transparent';
* $entry['visibility']   = 'private';
*
* // add an event
* $result = $gc->addEvent($entry);
* <code>
*
*
* @category   Services
* @package    Services_Google_Calendar
* @author     Keigo AOKI <hoyo@kdn-web.net>
* @copyright  2006 KDN
* @license    http://www.php.net/license/3_0.txt  PHP License 3.0
* @version    Release: 0.1.0
* @link       http://pear.kdn-web.net/package/XML_Serializer
*/
class Services_Google_Calendar
{
    
/**
     * list of all options
     *
     * @access  private
     * @var     array
     */
    
var $_options = array(
              
'Email'   => '',
              
'Passwd'  => '',
              
'source'  => SERVICES_GOOGLE_CALENDAR_SOURCE,
              
'service' => 'cl',
            );


    
/**
     * instance of HTTP_Client
     *
     * @access  private
     * @var     object
     */
    
var $_client;

    
/**
     * authentication token for header part of post request
     *
     * @access  private
     * @var     string
     */
    
var $_authToken = '';

    
/**
     * constructor
     *
     * @access  public
     * @param   string $email   email address of Google account
     * @param   string $passwd  password of Google account
     */
    
function Services_Google_Calendar($email = null, $passwd = null)
    {
        if (!empty(
$email)) {
            
$this->_options['Email']  = $email;
        }
        if (!empty(
$passwd)) {
            
$this->_options['Passwd'] = $passwd;
        }
        
$this->_client = new HTTP_Client();
    }

    
/**
     * set an option
     *
     * @access  public
     * @param   string $key     key of option (email, passwd, source)
     * @param   string $value   value of option
     */
    
function setOption($key, $value)
    {
        
$this->_options[$key] = $value;
    }

    
/**
     * get a list of the user's calendars
     *
     * @access  public
     * @return  string  XML feed of a list of calendars (meta-feed)
     * @throws  PEAR_Error
     */
    
function getCalendars()
    {
        if (
$this->_authToken === '') {
            if (!
$authResult = $this->_authentication()) {
                return
$authResult;
            }
        }
        if (!
PEAR::isError($this->_client->get(SERVICES_GOOGLE_CALENDAR_URL_FEED . $this->_options['Email']))) {
            
$result = $this->_client->currentResponse();
            return
$this->_parseCalendars($result['body']);
        }
        return
PEAR::raiseError('Connection failed of GET request');
    }

    
/**
     * get a list of events
     *
     * @access  public
     * @param   string $email  Google account
     * @param   string $hash   magicCookie (to get private calendar)
     * @param   array  $range  date-range (0 => min('y', 'm', 'd'), 1 => max('y', 'm', 'd'))
     * @return  array          events information
     * @throws  PEAR_Error
     */
    
function getEvents($email = '', $hash = '', $range = null)
    {
        if (empty(
$email)) {
            
$email = $this->_options['Email'];
        }
        
$mode = empty($hash) ? 'public' : 'private-' . $hash;
        
$url = SERVICES_GOOGLE_CALENDAR_URL_FEED . $email . '/' . $mode . '/full';
        if (
is_array($range)) {
            
$min = date('Y-m-d', mktime(0, 0, 0, $range[0]['m'], $range[0]['d'], $range[0]['y']));;
            
$max = date('Y-m-d', mktime(0, 0, 0, $range[1]['m'], $range[1]['d'], $range[1]['y']));;
            
$data = array('start-min' => $min, 'start-max' => $max);
        }
        if (!
PEAR::isError($this->_client->get($url, isset($data) ? $data : null))) {
            
$result = $this->_client->currentResponse();
            return
$this->_parseEvents($result['body']);
        }
        return
PEAR::raiseError('Connection failed of GET request');
    }

    
/**
     * add an event to user's calendar
     *
     * @access  public
     * @param   mix $entry  array or string (atom feeds) of event information
     * @return  string      event id of Google Calendar
     * @throws  PEAR_Error
     */
    
function addEvent($entry)
    {
        if (
$this->_authToken === '') {
            if (!
$authResult = $this->_authentication()) {
                return
$authResult;
            }
        }
        if (
is_array($entry)) {
            
$entry = $this->_buildAtom($entry);
        }
        
$this->_client->setDefaultHeader('Content-type', 'application/atom+xml');
        
$this->_client->setDefaultHeader('Content-length', strlen($entry));
        
$this->_client->setMaxRedirects(0);
        if (!
PEAR::isError($this->_client->post(SERVICES_GOOGLE_CALENDAR_URL_POST, $entry, true))) {
            
$result = $this->_client->currentResponse();
            if (
$result['code'] == 302) {
                if (!
PEAR::isError($this->_client->post($result['headers']['location'], $entry, true))) {
                    
$result = $this->_client->currentResponse();
                    if (
$result['code'] == 201) {
                        
$unserializer =& new XML_Unserializer();
                        if (!
PEAR::isError($unserializer->unserialize($result['body']))) {
                            
$xml = $unserializer->getUnserializedData();
                            return
$this->_getEventId($xml['id']);
                        }
                        return
PEAR::raiseError('Atom Feed was not unserialized successfully');
                    }
                    return
PEAR::raiseError($result['body']);
                }
            }
        }
        return
PEAR::raiseError('Connection failed of POST request');
    }

    
/**
     * authentication to connect GData API
     *
     * @access  private
     * @param   string $key     key of option (email, passwd, source)
     * @param   string $value   value of option
     * @throws  PEAR_Error
     */
    
function _authentication()
    {
        if (!
PEAR::isError($this->_client->post(SERVICES_GOOGLE_CALENDAR_URL_AUTH, $this->_options))) {
            
$result = $this->_client->currentResponse();
            if (
$result['code'] == 200) {
                
$this->_authToken = trim(substr(strstr($result['body'], 'Auth='), 5));
                        
$this->_client->setDefaultHeader('Authorization', 'GoogleLogin auth=' . $this->_authToken);
                return
true;
            }
        }
        return
PEAR::raiseError('Authentication failed');
    }

    
/**
     * parse events feed to array
     *
     * @access  private
     * @param   string $feed  atom feed of calendars list
     * @return  array         calendars information
     * @throws  PEAR_Error
     */
    
function _parseCalendars($feed)
    {
        
$unserializer =& new XML_Unserializer();
        if (!
PEAR::isError($unserializer->unserialize($feed))) {
            
$xml = $unserializer->getUnserializedData();
            
$entries = isset($xml['entry'][0]) ? $xml['entry'] : array($xml['entry']);
            foreach (
$entries as $i => $entry) {
                foreach (
$entry as $key => $value) {
                    
$calendars[$i]['id'] = $this->getId($entry['id']);
                    
$calendars[$i]['title'] = !empty($entry['title']) ? $entry['title'] : '';
                    
$calendars[$i]['summary'] = !empty($entry['summary']) ? $entry['summary'] : '';
                }
            }
            return
$calendars;
        }
        return
PEAR::raiseError('Parse error of calendar feed');
    }

    
/**
     * parse events feed to array
     *
     * @access  private
     * @param   string $feed  atom feed of events list
     * @return  array         events information
     * @throws  PEAR_Error
     */
    
function _parseEvents($feed)
    {
        
$unserializer =& new XML_Unserializer();
        
$unserializer->setOption(XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, true);
        if (!
PEAR::isError($unserializer->unserialize($feed))) {
            
$xml = $unserializer->getUnserializedData();
            
$entries = isset($xml['entry'][0]) ? $xml['entry'] : array($xml['entry']);
            foreach (
$entries as $i => $entry) {
                foreach (
$entry as $key => $value) {
                    switch (
$key) {
                    case
'id':
                        
$events[$i]['id'] = $this->getId($value);
                        break;
                    case
'title':
                    case
'content':
                        
$events[$i][$key] = !empty($value['_content']) ? $value['_content'] : '';
                        break;
/*
                    case 'author':
                        $events[$i]['name'] = $value['name'];
                        $events[$i]['email'] = $value['email'];
                        break;
*/
                    
case 'gd:where':
                        
$events[$i]['where'] = !empty($value['valueString']) ? $value['valueString'] : '';
                        break;
                    case
'gd:when':
                        
$events[$i]['when'][0] = $value['startTime'];
                        
$events[$i]['when'][1] = $value['endTime'];
                        break;
                    }
                }
            }
            return
$events;
        }
        return
PEAR::raiseError('Parse error of calendar feed');
    }

    
/**
     * convert event information to atom feed
     *
     * @access  private
     * @param   array $entry  array of event information
     * @return  string        atom feed of event information
     * @throws  PEAR_Error
     */
    
function _buildAtom($entry)
    {
        
$rootAttr['xmlns'] = 'http://www.w3.org/2005/Atom';
        
$rootAttr['xmlns:gd'] = 'http://schemas.google.com/g/2005';
        
$data['category']['_attr']['schema'] = 'http://schemas.google.com/g/2005#kind';
        
$data['category']['_attr']['term'] = 'http://schemas.google.com/g/2005#event';
        foreach (
$entry as $key => $value) {
            switch (
$key) {
            case
'title':
            case
'content':
                
$data[$key][] = $value;
                
$data[$key]['_attr']['type'] = 'text';
                break;
/*
            case 'name':
            case 'email':
                $data['author'][$key] = $value;
                break;
*/
            
case 'where':
                
$data['gd:where']['_attr']['valueString'] = $value;
                break;
            case
'when':
                if (
is_array($value) && count($value) > 0) {