Tuesday 12 April 2011

Accessing Google's Content API for Shopping using OAuth in PHP

***Update 31st July 2011 - edited the post to replace my Google API account number with "XXXXXXX" in a couple of URIs - you'll need to replace with your own to get this working. Thanks to Robert for suggesting this in the comments section.****

Trying to get OAuth working against Google's APIs using the standard PHP libraries like oauth-php and the Zend library supplied by Google was turning out to be a nightmare. Nothing would work and I kept running into problems. In the end I built scripts based on oauth-php using the following useful pages to guide me:

After installing the oauth-php library and running the MySQL store install script in the library/store/mysql directory of the library files in order to set up the right MySQL database structure, I was heading in the right direction. Step-by-step, through a lot of trial and error, and by watching the apache and MySQL logs, I got to three scripts which are shown below.

1. Google OAuth Consumer Generation Script
name: test-oauth-google-consumer.php
Call this script in your browser and it will take you to Google's OAuth interface where you grant your application permission to access Google services which use OAuth (in this case the Content API for Shopping). It will then redirect you back to your own site. Meanwhile, in the background the php-oauth library is taking care of getting all the right token details etc into the database.

<?php

// Script name = test-oauth-google-consumer.php
//
// Script editor Sam Critchley - sam@l3bv.com
//
// This is a script to manage OAuth against the Google OAuth service.
// It uses php-oauth library and a set of tips gained from a web page at
// http://www.elance.com/p/api/examples/oauth/php
// In doing so, it pretty much follows the Google OAuth flow which is at
// http://code.google.com/apis/accounts/docs/OAuth_ref.html

// First include the relevant files from the oauth-php library which is in
// <YOUR_PATH>/oauth-php/library
// Also make sure you include OAuthRequestLogger to log requests
require_once '/oauth-php/library/OAuthDiscovery.php';
require_once '/oauth-php/library/OAuthRequester.php';
require_once '/oauth-php/library/OAuthRequestVerifier.php';
require_once '/oauth-php/library/OAuthServer.php';
require_once '/oauth-php/library/OAuthRequestLogger.php';

// Now add some code to initialise the OAuth store, with oauth-php connecting
// to the MySQL database using the set of parameters supplied
// N.B. Make sure you've run the install script in the oauth-php library/store/mysql
// directory to install the right tables in the database
$store_options = array('server' => 'localhost', 'username' => '<YOUR_MYSQL_USERNAME>',
                 'password' => '<YOUR_MYSQL_PASSWORD>',  'database' => '<YOUR_MYSQL_DATABASE');

$store = OAuthStore::instance("MySQL", $store_options);

// Now register the Google OAuth API with the local OAuth store. It first calls
// $store->getServer() to test if the server is already registered, and if it isn't
// then it registers it with the OAuth store.
// It's necessary to fill in the values below such as consumer_key
// For Google you get these from your domain management page at https://www.google.com/accounts/ManageDomains
// First the consumer key e.g. $consumer_key = 'www.example.com';
$consumer_key = '<YOUR_CONSUMER_KEY>';
// Then the consumer secret e.g. $consumer_secret = 'GRp9MrOW4+HHWKoRI34Le609'
$consumer_secret = '<YOUR_CONSUMER_SECRET>';
// Set the user_id for use in the database. This can, of course, be dynamic if you've got many many users
$user_id = 1;
// The $params values get added to the request
// Add the API scope which you get from the Content API for Shopping page at:
// http://code.google.com/apis/shopping/content/getting-started/usingapi-products.html
// NOTE - the scope will vary if you are accessing other Google APIs than the
// Content API for Shopping, so you'll need to change it
//
$scope = "https://www.googleapis.com/auth/structuredcontent"; // fill with the scope

// If you set $xoauth_displayname it sets the description on the Google verification
// page and gives you an error about not being able to verify the application as it's
// "on your computer and not on a registered web service"
// $xoauth_displayname = ('<YOUR_SERVICE_DISPLAY_NAME');
//
// The $params array if you're going to include the xoauth_displayname in it - if you don't
// it just uses the default from your domain management page
//$params = array('scope' => $scope, 'oauth_callback' => $callback_url, 'xoauth_displayname' => $xoauth_displayname);
//
// Don't forget to include the oauth_callback as Google requires it at this stage rather than later
$callback_url = "http://<YOUR_WEB_SERVER>/test-oauth-google-consumer-callback.php?user_id=1";
$params = array('scope' => $scope, 'oauth_callback' => $callback_url);

$store = OAuthStore::instance("MySQL", $store_options);

$server = null;
try {
        $server = $store->getServer($consumer_key, $user_id);
} catch (OAuthException2 $e) {
    $server = array(
        'consumer_key' => $consumer_key,
        'consumer_secret' => $consumer_secret,
        // Note that server_uri needs to be the URI of the API you'll be accessing or else it will throw
        // an error which you'll have to use MySQL to debug. In this case the URI is obtained from the
        // Google Content API for Shopping documentation at
        // http://code.google.com/apis/shopping/content/getting-started/usingapi-products.html
        // It will be different for different Google APIs
        'server_uri' => 'https://content.googleapis.com/content/v1/XXXXXXX/items/products/generic',
        'signature_methods' => array('HMAC-SHA1'),
        'request_token_uri' => 'https://www.google.com/accounts/OAuthGetRequestToken',
        'authorize_uri' => 'https://www.google.com/accounts/OAuthAuthorizeToken',
        'access_token_uri' => 'https://www.google.com/accounts/OAuthGetAccessToken'
    );

    $store->updateServer($server, $user_id);
}

// Now we've got everything lined up locally including all the variables necessary to
// go out and hit the Google OAuth URI, all you have to do is call OAuthRequester and
// it goes out and does it for you. Don't forget to include $params in the request
// - it wasn't included by default in the Elance documentation
// Also don't forget that if you're missing one of the parameters defined in the Google
// OAuth documentation then you will get a 400 exception error saying something like:
//
// Unexpected result from the server "https://www.google.com/accounts/OAuthGetRequestToken" (400) while requesting a request token
//
// Use the new error handling method in PHP with tryand catch
try {
$token = OAuthRequester::requestRequestToken($consumer_key, $user_id, $params);
}
catch(OAuthException2 $e)
{
        echo "Exception:<br>" . $e->getMessage();
}

// If this works successfully the oauth-php library will get back info from Google
// containing the token, token_secret, and authorize_uri values. You can test this by
// printing out the array $token, but note that it doesn't contain the token_secret
// although oauth-php should have grabbed this. You can check by looking in the
// oauth_consumer_registry and oauth_consumer_token tables in MySQL as this happens
// and watch the entries in the tables changing during the process

// TEST print out the $token array, comment this out if you don't want that at the moment
/*
echo '<br><br>OAuthRequester response:<br>';
echo '<PRE>';
print_r($token);
echo '</PRE>';
echo '<br><br>';
*/

// Now that the token and token_secret have been obtained from Google, redirect the
// user to the authorisation URI to run the OAuthAuthorizeToken process
//
// Then construct the right authorisation uri from the values at the beginning
$authorization_uri = $token['authorize_uri'] . '?oauth_token=' . $token['token'];
// Then redirect the user to it
header("Location: " . $authorization_uri);
// Then exit the script
exit();

?>

2. Google OAuth Callback Script
name: test-oauth-google-consumer-callback.php
After you've successfully authenticated over at Google, you'll be redirected back to the callback script running on your server. As well as displaying a success message the script will extract the OAuth access token Google sends back in the URL (as a GET parameter) and will store it in the database.

<?php

// Script name = test-oauth-google-consumer-callback.php
//
// Script editor Sam Critchley  - sam@l3bv.com

// This is a callback script for managing OAuth against the Google OAuth service.
// You will be redirected to this page by Google if authenticating your signup
// request is successful
//
// The script uses php-oauth library and a set of tips gained from a web page at
// http://www.elance.com/p/api/examples/oauth/php
// In doing so, it pretty much follows the Google OAuth flow which is at
// http://code.google.com/apis/accounts/docs/OAuth_ref.html

// First include the relevant files from the oauth-php library which is in
// <YOUR_PATH>/oauth-php/library
// Also make sure you include OAuthRequestLogger to log requests
require_once '/oauth-php/library/OAuthDiscovery.php';
require_once '/oauth-php/library/OAuthRequester.php';
require_once '/oauth-php/library/OAuthRequestVerifier.php';
require_once '/oauth-php/library/OAuthServer.php';
require_once '/oauth-php/library/OAuthRequestLogger.php';

// Now add some code to initialise the OAuth store, with oauth-php connecting
// to the MySQL database using the set of parameters supplied
// N.B. Make sure you've run the install script in the oauth-php library/store/mysql
// directory to install the right tables in the database
$store_options = array('server' => 'localhost', 'username' => '<YOUR_MYSQL_USERNAME>',
                 'password' => '<YOUR_MYSQL_PASSWORD>',  'database' => '<YOUR_MYSQL_DATABASE');

$store = OAuthStore::instance("MySQL", $store_options);

// Now call OAuthRequester::requestAccessToken() in oauth-php to exchange your Request Token for an Access Token
// It's necessary to fill in the values below such as consumer_key
// First the consumer key e.g. $consumer_key = 'www.example.com';
$consumer_key = '<YOUR_CONSUMER_KEY>';
// Grab the user_id from the incoming GET value in the URL
$user_id = $_GET['user_id'];

// Now process the options received as the Google stuff returns
$options = array();
$options['oauth_verifier'] = $_GET['oauth_verifier'];

try
{
    OAuthRequester::requestAccessToken($consumer_key, $_GET['oauth_token'], $user_id, 'POST', $options);

}
catch (OAuthException $e)
{
        echo "Exception:<br>" . $e->getMessage();       
}

// If OAuthRequester::requestAccessToken() does not throw an exception, then the request was successful.
// Google grants your application an Access Token and an Access Token Secret, which oauth-php inserts into
// the OAuth store. These values will be included with and used to sign all requests you make against any
// authenticated methods.

echo 'Google account request successful';

?>

3. Content API for Shopping query script
name: test-oauth-google-shopping-api.php
This is a simple script which goes and queries the Google Content API for Shopping for all the items you have listed. The content is returned in an ATOM feed so you may need to "view source" in your browser (or modify the script) to be able to view the items.

<?php

// Script name = test-oauth-google-shopping-api.php
//
// Script editor Sam Critchley - sam@l3bv.com
//
// This is a script to manage OAuth against the Google OAuth service.
// It uses php-oauth library and a set of tips gained from a web page at
// http://www.elance.com/p/api/examples/oauth/php
// In doing so, it pretty much follows the Google OAuth flow which is at
// http://code.google.com/apis/accounts/docs/OAuth_ref.html

// First include the relevant files from the oauth-php library which is in
// <YOUR_PATH>/oauth-php/library
// Also make sure you include OAuthRequestLogger to log requests
require_once '/oauth-php/library/OAuthDiscovery.php';
require_once '/oauth-php/library/OAuthRequester.php';
require_once '/oauth-php/library/OAuthRequestVerifier.php';
require_once '/oauth-php/library/OAuthServer.php';
require_once '/oauth-php/library/OAuthRequestLogger.php';

// Now add some code to initialise the OAuth store, with oauth-php connecting
// to the MySQL database using the set of parameters supplied
// N.B. Make sure you've run the install script in the oauth-php library/store/mysql
// directory to install the right tables in the database
$store_options = array('server' => 'localhost', 'username' => '<YOUR_MYSQL_USERNAME>',
                 'password' => '<YOUR_MYSQL_PASSWORD>',  'database' => '<YOUR_MYSQL_DATABASE');

// Set the user_id (used in the OAuth MySQL store) as an example for this script
$user_id = 1;

$store = OAuthStore::instance("MySQL", $store_options);

// Need to set a couple of options including (optionally) GData-Version=1.0
// see http://code.google.com/apis/gdata/docs/developers-guide.html for details
$options = array('max-results' => '10', 'v' => '1.0');

// Run the API call using the php-oauth library
$manage_request = new OAuthRequester("https://content.googleapis.com/content/v1/XXXXXXX/items/products/generic", 'GET', $options);

try {
    $result = $manage_request->doRequest($user_id);
} catch (OAuthException $e){
    // Handle error if it occurs (will appear in your apache logs).
    echo "Exception:<br>" . $e->getMessage();
}

/*
The output from <span style="font-family:monospace;">doRequest()</span> is the full array of header and body values
returned by the cUrl HTTP library. We access the JSON response using the body array key.
*/

// Print out the results of the API poll
// These are in an ATOM feed so in a normal browser you'll need to
// look at the page source if the content on screen is blank
$text = $result['body'];
echo $text;


?>

I hope these scripts are useful. Leave a comment if you have any questions.


Friday 4 March 2011

Installing a UDF in MySQL in Mac OS X Snow Leopard

I was having a bit of trouble getting a MySQL user-defined function (UDF) running in MySQL in Snow Leopard on my Mac. In the end I came across this page and its comments, which helped a lot. So here's the procedure:

1. Move the C++ UDF file into /usr/local/mysql/bin on your Mac. You should know how to do this in terminal, if not, use the "mv" or "cp" command.

2. Compile the C++ UDF file using g++ - here's the command I used:

> sudo g++ -Wall -bundle -bundle_loader /usr/local/mysql/bin/mysqld -o udf_name.so `/usr/local/mysql/bin/mysql_config --cflags` udf_name.cc

This should give you a file called udf.so in the /usr/local/mysql/bin directory. Note that when you compile on Mac OS X the compiler automatically creates an associated directory called ufo.so.dSYM - you can find out why here.

3. Move the udf.so file and the udf.so.dSYM directory into the MySQL plugin directory in /usr/local/mysql/lib/plugin:


> sudo mv udf.so ../lib/plugin/
> sudo mv udf.so.dSYM/ ../lib/plugin/


4. Install the function in MySQL, making sure you're running a MySQL user with full privileges:


mysql> CREATE FUNCTION udf_name RETURNS REAL SONAME "udf.so";
Query OK, 0 rows affected (0.02 sec)


Once you've done this you should be able to use the UDF in your MySQL queries.

Sunday 17 October 2010

Exporting call logs, text messages and other info from a Blackberry

I need a way to extract the record of calls I've made and received on my Blackberry. It would also be handy to be able to archive old text (SMS) messages and other info. In addition to providing a good record of the time and duration of your calls, it's also nice to have an archive of your text messages going back over time.

After searching for a while, I came across a couple of ways to do this. The first way is an app called SBSH Historia which you can buy on Blackberry App World for a couple of €€/$$. This looks like a neat app, and very professional, but I didn't need to pay for it in the end as I found a way to do it for free using the Desktop Manager software on my Mac and a Java app called ipddump I found on Google Code.

Here are the instructions for how to do it on a Mac. The Windows procedure is broadly similar.

Tuesday 23 March 2010

Carriage return in Excel on the Mac

If you are entering text in a cell in Excel in Mac Office 2008 (or other versions), hitting the return key will move the focus to the cell below. To enter a carriage return inside the cell when typing, hit ctrl-option-return instead.

Wednesday 10 February 2010

Photos appear blank in iPhoto on the Mac

At home I've got an iMac (2009 version). We run iPhoto on it to manage our family photos - the photo library lives in a shared directory - both of us have separate accounts on the computer but we want to share the same photo library. Occasionally we run into a problem where you can see photo thumbnails in iPhoto, but when you double-click to open the photo, it goes blank and you just see a black screen where the photo should be.

After some searching I managed to find a few other people who'd run into the same problem. The solution is to rebuild your photo library in iPhoto. Exit the application, whilst holding down the command and option keys on the keyboard, restart iPhoto. You will see a prompt asking which parts of the library you'd like to rebuild. For me, examining and repairing iPhoto library permissions is what's worked.

If you want to know more you can find an Apple support article here.

Tuesday 1 December 2009

Using MySQL to retrieve substring from the middle of a string

In MySQL it's hard to grab part of a string out of the middle of a VARCHAR or other text-based field, especially if you don't know exactly how far into the string the text you want begins, and it's of variable length, so you don't know where it ends either. Here's a way to do it.

Tuesday 17 November 2009

Capturing screenshots on your Blackberry

****UPDATE September 16th 2011 - download link updated****

Ever since I first got a Blackberry I've been looking for a good way to capture screenshots of apps running on the device. Including, of course, our very own GyPSii. Now, thanks to Amit Agarwal's blog entry, I've found it.

Friday 25 September 2009

Vanishing formula bar in Mac Office Excel 2008

A couple of weeks ago my formula bar in Excel 2008 on my Mac just vanished into thin air - gone. I tried various remedies to get it to reappear, but none worked, including the obvious, turning on "formula bar" in the view menu. Eventually I managed to track down a Macrumors forum thread where someone had fixed the problem. Here's what you do:

- Close Excel and any other running Office apps
- In Finder, go to your home directory and then to /Library/Preferences/Microsoft/Office 2008/
- Delete the file called "Excel Toolbars"
- Restart Excel

This should restore the formula bar for you.

Thursday 6 August 2009

Getting MySQL running with PHP on a Mac

I've just been trying to get PHP and MySQL running with Apache on a Mac (PHP 5.2.8 and MySQL 5.1.33 running OS X 10.5.7). Somehow I kept running into the following error when trying to get a PHP script to connect to MySQL:

mysql_connect(): Can't connect to local MySQL server through socket '/var/mysql/mysql.sock'

Here's how I fixed it.

Monday 3 August 2009

How to configure an Airport on/off shortcut key on a Mac


****Update - 10th February 2010****
This worked well in Leopard, but I haven't got it to work since upgrading to Snow Leopard. Does anyone have a fix?

The Problem
One of the things which has always bugged me about my Mac is the lack of a shortcut key to turn the Airport WiFi on and off. At home I connect using WiFi and in the office I plug into a wired ethernet connection, so I wanted to be able to turn the Airport on or off with a shortcut key instead of having to use my fat fingers and the mouse to click on the fiddly little Airport symbol in the menu bar. Unfortunately, Apple didn't build an Airport shortcut key into OS X. I'd been looking for a solution to this problem for ages, and now I've finally got it worked out.