Synchronising Highrise, Basecamp and Google Shared Contacts–Part 2

Background: In Part 1 of this series I wrote about a recent project that I worked on to synchronise contact data between a number of web applications, and how I had to work around the (current) shortcomings of their APIs.

As Scott Hanselman says, “talk is cheap – show me the code”.  So here we go…

Basecamp & Highrise APIs

Implementing an interface to the Basecamp & Highrise APIs is relatively straightforward – I did it using PHP’s curl().  My implementation doesn’t support all of the API’s functions, but you should be able to easily add any additional calls that you need.

The functions centre around a single function which does the hard work on making the request:

private function _getData($uriExtension) {
$base_url = $this->url.$uriExtension;
$session = curl_init();
curl_setopt($session, CURLOPT_URL, $base_url);
curl_setopt($session, CURLOPT_USERPWD, $this->apiKey.':X');
curl_setopt($session, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_HTTPGET, 1);
curl_setopt($session, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
curl_setopt($session, CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($session, CURLOPT_FRESH_CONNECT, true);
$response = curl_exec($session);
$xmlObj = new SimpleXMLElement($response);
return ($xmlObj);

Google Shared Contacts

This was simply done using the Zend GData classes which you can grab here.  Here’s some sample code for updating a shared contact in Google:

$client->setHeaders('If-Match: *');
$gdata=new Zend_Gdata($client);
// get the existing contact.
$query = new Zend_Gdata_Query($id);
$entry = $gdata->getEntry($query);
$xml = simplexml_load_string($entry->getXML());
// update title.
$xnl->title = $title;
// update the fullname.
$xml->name->fullName = $title;
// update entry.
$entryResult = $gdata->updateEntry($xml->saveXML(), $entry->getEditLink()->href);

Get more details over at the Google Apps Shared Contacts API page.

Basecamp & Highrise – Filling in the API gaps

So the big problem with the Basecamp & Highrise APIs is that they have bits missing – rather significant bits it turns out.  In particular the ability to create and update companies.

To get around this I wrote a couple of classes that use a combination of curl() and the awesome phpQuery to walk through the front-end pages and execute URLs just as a real user would.

Whilst this is not impervious to UI changes by 37Signals, it’s a fairly generic implementation and it’s a best endeavours approach given the current lack in the APIs.

Here’s some sample code:

public function createCompany($companyName) {

$success = true;

// go to the company page first, to get the token.
$resp = $this->_fetchUrl($this->_urls['base'].$this->_urls['companies'], 'GET', array(), false, $this->_ch);

$authToken = pq('input[name=authenticity_token]')->val();

$formFields = array(
'authenticity_token' => $authToken,
'company[name]' => $companyName,
'commit' => 'Create Company'

$resp = $this->_fetchUrl($this->_urls['base'].$this->_urls['companies'], 'POST', $formFields, false, $this->_ch);

// check - has this company already been created?
if(pq('div.flash_alert')->text() != '') {
$success = false;
$this->_errMsg = pq('div.flash_alert')->text();

return $success;

Show me the code!

So, in the hope of helping others to get synchronisation working (and work around the lack in the APIs) you can download the code at GitHub for your own use.


One thought on “Synchronising Highrise, Basecamp and Google Shared Contacts–Part 2”

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s