Tag Archives: gdata

Quick Google Authentication in PHP

Here is a quick way to authenticate against Google and retrieve a protected feed. It does not use the supported ClientLogin method but it does allow you to get to some unsupported feeds (Reader, Bookmarks, etc)

The Zend Gdata library is required

$show_list       = 'show-google-reader-sub-list';  //the hook in a page
$login          = '';
$pass          = '';
$source         = 'wordpress-google-reader-sub-list-';  //the source the api sees when logging into Google
$service         = 'reader';
$login_url        = 'https://www.google.com/accounts/ServiceLoginAuth?service=mail'; //URL to login to google
$subscription_list_url  = 'http://www.google.com/reader/api/0/subscription/list'; //URL that holds a users subscriptions

$client = new Zend_Http_Client($login_url);

//connect, authenticate, and handshake with Google
$client->setCookieJar()
->setMethod(Zend_Http_Client::POST)
->setParameterPost(array(
 'continue'             => $subscription_list_url,
 'service'              => 'reader',
 'niu'                  => 1,
 'hl'                   => 'en',
 'Email'              => $login,
 'Passwd'               => $pass,
 'PersistentCookie'     => 'yes',
 'asts'                 => ''
));

$response = $client->request('POST');
$client->setUri($subscription_list_url)->setMethod(Zend_Http_Client::GET);
$response = $client->request()->getBody();

if ($client->request()->getStatus() == 400) {
?>Unable to login with supplied Google login/password

Google I/O session videos posted with slides

As I said earlier, Quite a few presentations from Google I/O have been posted for your viewing pleasure. I’ll be glued to these this weekend while I’m on call.

Topics include
Ajax
KML
Sketchup
Android
OpenSocial
Appengine
Data APIs
Theory Talks
Youtube
Gears
Mashups
Maps
and more!

October Speedlinking

I haven’t been able to post as often or as in depth as I’d like to have this past month. I chalk it up mostly to work, we all love 12 hour days right? But now that sign-off has passed and our last release of 2007 is calmly approaching (its on Friday), things have settled down a bit. Below are some great links from October, most of which I wanted to mention at some point and haven’t gotten a chance to until now. Enjoy.

Quick Docs Api Example (python)

To use the gdata docs python client you need to upgrade to 1.0.7 or higher. First thing is to import the modules you’ll need.

import gdata.docs.service
import gdata.docs

Then, set up the usual authentication parameters for the client.

gd_client = gdata.docs.service.DocsService()
gd_client.email = 'timothy.broder'
gd_client.password = '*****'
gd_client.source = 'gpowered-docs-list-ex'
gd_client.ProgrammaticLogin()

The most basic query will just return all of your documents

feed = gd_client.GetDocumentListFeed()

However, if we want to display just the spreadsheets, we build the query like this:

q = gdata.docs.service.DocumentQuery(categories=['spreadsheet'])
feed = gd_client.Query(q.ToUri())

Finally, we output the titles

if(len(feed.entry) == 0):
        print 'No entries in feed.\n'
for i, entry in enumerate(feed.entry):
        print '%s %s (%s)' % (i+1, entry.title.text.encode('UTF-8'))

If we wanted to, we could also import the DateTime library and show when the document was last updated

import gdata.docs.service
import gdata.docs

from mx import DateTime

gd_client = gdata.docs.service.DocsService()
gd_client.email = 'timothy.broder'
gd_client.password = '*****'
gd_client.source = 'gpowered-docs-list-ex'
gd_client.ProgrammaticLogin()

q = gdata.docs.service.DocumentQuery(categories=['spreadsheet'])
#feed = gd_client.GetDocumentListFeed()
feed = gd_client.Query(q.ToUri())

if(len(feed.entry) == 0):
        print 'No entries in feed.\n'
for i, entry in enumerate(feed.entry):
        dt = DateTime.ISO.ParseDateTimeUTC(entry.updated.text)
        print '%s %s (%s)' % (i+1, entry.title.text.encode('UTF-8'), dt.strftime('%m/%d/%Y %I:%M %p'))

For me this outputs:

1 TDP2006 Contact Info (11/18/2006 05:41 AM)
2 contact info (07/23/2006 08:15 PM)
3 Tim and Rob (08/09/2007 10:18 PM)
4 nyc happy hour spreadsheet (07/04/2007 08:25 PM)
5 public_spring_2006_roster (10/16/2006 12:40 AM)
6 dax2006 (11/12/2006 11:23 PM)
7 project dream (07/13/2007 03:54 AM)
8 Stuff Tim should get (06/13/2007 01:53 AM)
9 Erg Test Results - 9/26 (10/15/2006 01:02 AM)
10 Head of the Charles Regatta Itineary (10/17/2006 04:54 PM)
11 tvshows (11/02/2006 11:44 PM)
12 HF (10/01/2006 03:36 PM)

HOWTO: Displaying Blogger feeds with PHP

This HOWTO is going to follow the basic structure of the Python one.

To start out you’ll have to grab the Zend Google data Client Library and then set the include_path so you can use it

ini_set("include_path", ".:../:./include:../include:/home/gpowered/webapps/php/includes/ZendGdata-1.0.1/library");

We then import the parts the we’ll need:

require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_Query');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');

One of the first things we’re going to have to do is authenticate with google services.
There are two ways to do this: AuthSub proxy authentication which has a user login using their own credentials, and
ClientLogin username/password authentication where you send a username and password. We will be using ClientLogin. I built a small class called gPoweredBlogger to hold the different parts I will need for this example.

class gPoweredBlogger{
 private $user;// = 'timothy.broder';
 private $pass;// = '**************';
 private $service;// = 'blogger';

 private $blog_id;// = '413573351281770670';
 private $uri;// = "http://www.blogger.com/feeds/" . $blog_id . "/posts/default";
 private $show_num;// = 5;

 private $client;// = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
 private $gdClient;// = new Zend_Gdata($client);
 private $query;// = new Zend_Gdata_Query($uri);

 private $total_posts;

 public $output;

Then we start setting up our call to the service.

 public function __construct($user, $pass, $blog_id){
  $this->user = $user;
  $this->pass = $pass;
  $this->service = 'blogger';

  $this->blog_id = $blog_id;
  $this->uri = "http://www.blogger.com/feeds/" . $this->blog_id . "/posts/default";
  $this->show_num = 5;

  $this->client = Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, $this->service);
  $this->gdClient = new Zend_Gdata($this->client);
  $this->query = new Zend_Gdata_Query($this->uri);
  $this->total_posts = $this->get_total($this->query);
 }

For more info see the blogger developer’s guide with php or the Google Account Authentication documentation
After we have authenticated with Google we need to start building up our query to GData. The first thing you’ll need is your blog’s id.
You can use the function in the dev guide to help you with this if you don’t already know it.

Like the Python version, the below function returns the total number of posts that are in the feed. We can get a small response by sending 0 for the max results. Below is the function and the small response we get from it.

 private function get_total($query){
  //query for no posts
  $this->query->setParam('max-results', '0');
  $this->query->setParam('start-index', '1');

  //get back entryless feed
  $feed = $this->gdClient->getFeed($this->query);
  return $feed->totalResults->text;
 }

 5
 0
 1
 Blogger
 Tim
 tag:blogger.com,1999:blog-413573351281770670
 
 
 
 
 gPowered
 2007-07-18T10:55:06.728-05:00

So we get the total number of posts and then we can start pulling data. Lets make a generic function, PostFrom, that can be used to show multiple posts, or just single ones, depending on what you pass to it. The start number that is passed to
PostFrom has been set to the first post in the blog is considered to have an id of 1 and the latest post is the same as total_posts. This is useful so if viewers want to bookmark the page they are looking at, the post that is being displayed will not change.
The following are the different functions that will make use of it.

 //show latest posts
 public function Posts(){
  return $this->ListPosts($this->total_posts);
 }

 //show posts starting from a certain point
 public function ListPosts($start){
  $start = $this->total_posts - $start + 1;
  return $this->PostFrom($start, $this->show_num);
 }

 //show a single post
 public function Post($start){
  $start = $this->total_posts - $start + 1;
  return PostFrom($start, 1);
 }

 //show count number of posts starting from a certain point
 private function PostFrom($start, $count){
  //query for count number of posts starting at the given post
  $this->query->setParam('max-results', $count);
  $this->query->setParam('start-index', $start);
  $feed = $this->gdClient->getFeed($this->query);

Now we have all the data we need in the feed variable. Its been turned into an object so we don’t have to worry about XML parsing here. Every node has become an objects and lists. Objects for single nodes(title), and lists for where there are multiple nodes of the same name (entry, link)

  //for links
  $curr_id = $this->total_posts - $start + 1;

  //normalize data for output
  foreach($feed->entries as $entry){
   //id for links
   $entry->my_id = $curr_id;
   $curr_id -= 1;

  }

Of course we’re going to need next and previous buttons as well. The way we’ve set up the math with total_posts and the start number, we only have to increment or decrement these by count (the number of posts to display on a page). I also set part of the link, as well as the page title, that I will use below in the HTML.

  $prev = $this->total_posts - ($start - $count) + 1;
  if($prev > $total_posts){
   $prev = null;
  }

  $next = $this->total_posts - ($start + $count) + 1;
  if($next < 1){
   $next = null;
  }

  //showing single post
  if(count == 1){
   $link = 'post';
   $title = $feed->entries[0]->title->text;
  //listing posts
  }
  else{
   $link = 'posts';
   $title = 'home';
  }

The final part is to make a quick object that we can use in the HTML to output everything

  $this->output = new Output($feed->entries, $title, $prev, $next, $link);
 }
} 

class Output{
 public $entries;
 public $title;
 public $prev;
 public $next;
 public $link;
 public function __construct($entries, $title, $prev, $next, $link){
  $this->entries=$entries;
  $this->title=$title;
  $this->prev=$prev;
  $this->next=$next;
  $this->link=$link;
 }
}

To the HTML!

The first part consists of displaying the post itself, along with its relevant information. So lets built up our objects

$blog = new gPoweredBlogger('timothy.broder', '*************', '413573351281770670');
$blog->Posts();

$output = $blog->output;

Below all the php we can run through out output object and display the posts

<? foreach($output->entries as $entry){ ?>
     <h2><a href="/post/<? echo $entry->my_id ?>"><? echo $entry->title->text ?></h2></a>
     <? echo $entry->content->text;
     $datetime = strtotime(substr($entry->published, 0, 10) . ' ' . substr($entry->published, 11, 8 ));
     ?>
     <p>Posted by <? echo $entry->author[0]->name->text ?> on <? echo date("m/d/Y",$datetime) ?> at <? echo date("g:i a",$datetime) ?></p>

      <div id="divider"></div>
      <?}?>

That’s all for now. A working example is here

First Google Gadget

After being inspired about Google Gadgets from the Google Developer Podcast I came up with on that my old crew team can use. We have a shared google calendar that some of us use to keep track of races, meetings, etc. This gadget pulls and formats it nicely for the google IG or desktop.

Add to Google

Javascript code for the gadget:

<style type="text/css">
div.exp{
padding: 0;
margin: 0;
}
div.loc{
margin-left: 19px;
}
</style>
<script type="text/javascript">
<!--
/**
* Season info *
* 0 Fall: Aug 26 - Nov 1
* 1 Winter Training: Nov 2 - March 1
* 2 Spring: March 2 - May 25
* 3 Summer: May 26 - Aug 25
**/

/**
* Callback function for the GData json-in-script call
* Inserts the supplied list of events into a div of a pre-defined name
*
* @param {json} root is the JSON-formatted content from GData
*/
function processRaces(root) {
displayRaces(root.feed, 'races');
}

//meow
function displayRaces(feed, divId){
var now = new Date();
var season = getSeason(now);
var events = document.getElementById(divId);

//clear "Loading..."
if (events.childNodes.length > 0){
events.removeChild(events.childNodes[0]);
}

//display season
var d = document.createElement('div');
d.appendChild(document.createTextNode(getSeasonText(season)));
events.appendChild(d);

//loop races
for (var i=0; i<feed.entry.length; i++){
var entry = feed.entry[i];

var d = getDate(entry['gd$when'][0].startTime);
if(isCurrSeason(now, d)){
var title = entry.title.$t;
var desc = entry.content.$t;

//set up image and clicking to expand
var div = document.createElement('div');
div.className = 'exp';
var toggle = document.createElement('img');
toggle.src= 'http://timothy.broder.googlepages.com/p.jpg';
toggle.align = 'absmiddle';
toggle.id = i;
toggle.onclick = function() {toggleDiv(this); }
div.appendChild(toggle);
div.appendChild(document.createTextNode(' ' + d.getMonth() + "/" + d.getDay() + ' - '));

// get the href to link to the event
for(var j=0; j<entry['link'].length; j++){
if (entry['link'][j]['type'] == 'text/html' && entry['link'][j]['rel'] == 'alternate'){
var href = entry['link'][j]['href'];
}
}

//we can link to the cal
if (typeof href != 'undefined'){
var link = document.createElement('a');
link.href = href;
link.target = '_blank';
link.appendChild(document.createTextNode(title));
div.appendChild(link);
}
else{ //shouldn't get here but just in case
div.appendChild(document.createTextNode(title));
}

div.appendChild(document.createElement('br'));

events.appendChild(div);

var where = entry['gd$where'][0].valueString;
var tDiv = document.createElement('div'); //div that will be hidden initially
tDiv.id ='id' + i;
tDiv.style['display'] = 'none';
tDiv.className = 'loc';
events.appendChild(tDiv);

if(desc != null && desc != ""){ //we have a desc (should be the teams we're competing against)
var dDiv = document.createElement('span');
dDiv.appendChild(document.createTextNode(desc + " "));
tDiv.appendChild(dDiv);
}
//the location field is populated (hidden at start)
if(where != null && where != ""){ //we have a location
var it = document.createElement('i');
var map = document.createElement('a');
map.target = '_blank';
//link to location on google maps
map.href = 'http://maps.google.com/maps?f=q&hl=en&&q=' + spaceLink(entry['gd$where'][0].valueString);
map.appendChild(document.createTextNode(entry['gd$where'][0].valueString));
it.appendChild(document.createTextNode(" ("));
it.appendChild(map);
it.appendChild(document.createTextNode(")"));
tDiv.appendChild(it);
}
}
}
}

//format the date a little
function getDate(when){
var data = when.split("-");
return new Date(data[0], data[1], data[2]);
}

//return int representation of season
function getSeason(d){
var month = d.getMonth();
var day = d.getDate();
var year = d.getFullYear();

var aug = new Date(year, 8, 25);
var nov = new Date(year, 11, 1);
var mar = new Date(year, 3, 1);
var may = new Date(year, 5, 25);

if(aug < d && d <= nov) return 0; //fall
else if(mar < d && d <= may) return 2; //spring
else if(may < d && d <= aug) return 3; //summer
else return 1; //winter
}

//figure out the current season
function isCurrSeason(now, d){
currSeason = getSeason(now);
season = getSeason(d);

if(currSeason == season){ //same season yes
if(now.getFullYear() == d.getFullYear()){ //same year also, match
return true;
}
if(season == 1 && (d.getFullYear() == now.getFullYear()-1)){ //diff year, prob winter
return true;
}
}
return false;
}

//return text for season
function getSeasonText(season){
if(season == 0) return "Fall Season";
if(season == 1) return "Winter Training";
if(season == 2) return "Spring Season";
if(season == 3) return "Summer Season";

return "no season"; //really shouldn't get here
}

//if the div is hidden show it, if not, hide it
function toggleDiv(where_id){
var div = document.getElementById('id' + where_id.id);
var img = document.getElementById(where_id.id);
if(div != null){
if(div.style.display != 'none'){
div.style.display = 'none';
img.src= 'http://timothy.broder.googlepages.com/p.jpg';

}
else{
div.style.display = 'block';
img.src= 'http://timothy.broder.googlepages.com/m.jpg';
}
}
}

//convert the location so it can be used in a link to google maps
function spaceLink(name){
return name.replace(' ', ',+');
}

//-->
</script>

<div id="races"><p>Loading...</p></div>
<script type="text/javascript" src="http://www.google.com/calendar/feeds/rpicrew@gmail.com/public/full?alt=json-in-script&callback=processRaces&orderby=starttime&singleevents=true&sortorder=ascending&start-min=2007-01-01T00:00:00"></script>

And the XML for the gadget

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="RPI Crew Schedule"
title_url="http://gpowered.net"
author="Tim Broder"
height="150"
width="250"
author_email="timothy.broder@gmail.com"
thumbnail="http://timothy.broder.googlepages.com/RPICrewScheduleThumb.jpg"
description="RPI Crew Race Schedule, links to the RPICrew shared google calendar"
author_photo="http://timothy.broder.googlepages.com/timothybrodersimpsons.png"
author_location="NYC"
author_affiliation="gPowered"
author_link="http://www.gpowered.net"
screenshot="http://timothy.broder.googlepages.com/RPICrewScheduleScreen.jpg"
>
<Require feature="dynamic-height"/>
<Require feature="analytics"/>
</ModulePrefs>
<Content type="html"><![CDATA[

<script>
// Track this gadget using Google Analytics.
_IG_Analytics("UA-793489-6", "/RPICrewScheduleG");
</script>
<style type="text/css">
div.exp{
padding: 0;
margin: 0;
}
div.loc{
margin-left: 19px;
}
</style>
<script type="text/javascript">
<!--
/**
* Season info *
* 0 Fall: Aug 26 - Nov 1
* 1 Winter Training: Nov 2 - March 1
* 2 Spring: March 2 - May 25
* 3 Summer: May 26 - Aug 25
**/

/**
* Callback function for the GData json-in-script call
* Inserts the supplied list of events into a div of a pre-defined name
*
* @param {json} root is the JSON-formatted content from GData
*/
function processRaces(root) {
displayRaces(root.feed, 'races');
}

//meow
function displayRaces(feed, divId){
var now = new Date();
var season = getSeason(now);
var events = document.getElementById(divId);

//clear "Loading..."
if (events.childNodes.length > 0){
events.removeChild(events.childNodes[0]);
}

//display season
var d = document.createElement('div');
d.appendChild(document.createTextNode(getSeasonText(season)));
events.appendChild(d);

//loop races
for (var i=0; i<feed.entry.length; i++){
var entry = feed.entry[i];

var d = getDate(entry['gd$when'][0].startTime);
if(isCurrSeason(now, d)){
var title = entry.title.$t;
var desc = entry.content.$t;

//set up image and clicking to expand
var div = document.createElement('div');
div.className = 'exp';
var toggle = document.createElement('img');
toggle.src= 'http://timothy.broder.googlepages.com/p.jpg';
toggle.align = 'absmiddle';
toggle.id = i;
toggle.onclick = function() {toggleDiv(this); }
div.appendChild(toggle);
div.appendChild(document.createTextNode(' ' + d.getMonth() + "/" + d.getDay() + ' - '));

// get the href to link to the event
for(var j=0; j<entry['link'].length; j++){
if (entry['link'][j]['type'] == 'text/html' && entry['link'][j]['rel'] == 'alternate'){
var href = entry['link'][j]['href'];
}
}

//we can link to the cal
if (typeof href != 'undefined'){
var link = document.createElement('a');
link.href = href;
link.target = '_blank';
link.appendChild(document.createTextNode(title));
div.appendChild(link);
}
else{ //shouldn't get here but just in case
div.appendChild(document.createTextNode(title));
}

div.appendChild(document.createElement('br'));

events.appendChild(div);

var where = entry['gd$where'][0].valueString;
var tDiv = document.createElement('div'); //div that will be hidden initially
tDiv.id ='id' + i;
tDiv.style['display'] = 'none';
tDiv.className = 'loc';
events.appendChild(tDiv);

if(desc != null && desc != ""){ //we have a desc (should be the teams we're competing against)
var dDiv = document.createElement('span');
dDiv.appendChild(document.createTextNode(desc + " "));
tDiv.appendChild(dDiv);
}
//the location field is populated (hidden at start)
if(where != null && where != ""){ //we have a location
var it = document.createElement('i');
var map = document.createElement('a');
map.target = '_blank';
//link to location on google maps
map.href = 'http://maps.google.com/maps?f=q&hl=en&&q=' + spaceLink(entry['gd$where'][0].valueString);
map.appendChild(document.createTextNode(entry['gd$where'][0].valueString));
it.appendChild(document.createTextNode(" ("));
it.appendChild(map);
it.appendChild(document.createTextNode(")"));
tDiv.appendChild(it);
}
}
}
}

//format the date a little
function getDate(when){
var data = when.split("-");
return new Date(data[0], data[1], data[2]);
}

//return int representation of season
function getSeason(d){
var month = d.getMonth();
var day = d.getDate();
var year = d.getFullYear();

var aug = new Date(year, 8, 25);
var nov = new Date(year, 11, 1);
var mar = new Date(year, 3, 1);
var may = new Date(year, 5, 25);

if(aug < d && d <= nov) return 0; //fall
else if(mar < d && d <= may) return 2; //spring
else if(may < d && d <= aug) return 3; //summer
else return 1; //winter
}

//figure out the current season
function isCurrSeason(now, d){
currSeason = getSeason(now);
season = getSeason(d);

if(currSeason == season){ //same season yes
if(now.getFullYear() == d.getFullYear()){ //same year also, match
return true;
}
if(season == 1 && (d.getFullYear() == now.getFullYear()-1)){ //diff year, prob winter
return true;
}
}
return false;
}

//return text for season
function getSeasonText(season){
if(season == 0) return "Fall Season";
if(season == 1) return "Winter Training";
if(season == 2) return "Spring Season";
if(season == 3) return "Summer Season";

return "no season"; //really shouldn't get here
}

//if the div is hidden show it, if not, hide it
function toggleDiv(where_id){
var div = document.getElementById('id' + where_id.id);
var img = document.getElementById(where_id.id);
if(div != null){
if(div.style.display != 'none'){
div.style.display = 'none';
img.src= 'http://timothy.broder.googlepages.com/p.jpg';

}
else{
div.style.display = 'block';
img.src= 'http://timothy.broder.googlepages.com/m.jpg';
}
}
}

//convert the location so it can be used in a link to google maps
function spaceLink(name){
return name.replace(' ', ',+');
}

//-->
</script>

<div id="races"><p>Loading...</p></div>
<script type="text/javascript" src="http://www.google.com/calendar/feeds/rpicrew@gmail.com/public/full?alt=json-in-script&callback=processRaces&orderby=starttime&singleevents=true&sortorder=ascending&start-min=2007-01-01T00:00:00"></script>

]]></Content>
</Module>

New Calendar API features

Google Calendar API posts new read and write features. They have added:

- owncalendars: contains the collection of calendars that a user ‘owns’
- allcalendars: contains all the calendars that a user has access to

Usefull for listing all your calendars if you manage a lot of them, or changing their settings all at once (both features have read and write capabilities)

Picasa Web Api updates

Google Data Api’s posted a few updates today, a few of which I’ve really been looking forward to; Community Search, Retrieving a user’s recently uploaded photos, Retrieving recently added comments for a particular user, Searching a user’s photos, Filtering by tag, Uploading non-JPEG photos, Downloading the original photo.

(my fav) Downloading the original photo: You can now download the original photo, including all EXIF data. This is accomplished by retrieving the feed with the imgmax=d query parameter and value This will return a feed where the media:content elements reference the original downloadable image.

It’s nice not having to use a backdoor for this anymore

Performance Optimization WordPress Plugins by W3 EDGE