Following on from my previous post about doing things the Elgg way, I thought I’d illustrate some of what I was talking about by building out a quick plugin while the kettle boiled for my tea.

This plugin uses the hooks present in Elgg – specifically the getIcon() api – to provide Gravatar icons for users which have not provided their own.

On many platforms this might have been an onerous task, but on Elgg it literally took me 15 minutes (in fact, it took me longer to write this post than the plugin).

If a user defines their own icon it will use that, but if they haven’t provided one it will use their email address to find their gravatar icon.

This is accomplished by setting the priority of the hook somewhere after the icon handler provided by the profile plugin, and before the default icon handler.

Anyway, here is the code:

<?php
/**
* Simple gravatar integration for Elgg.
* Scratching an itch! (+ a good example of icon overloading)
*
* TODO:
* 1) Fallback to elgg default icons instead of gravatar one for missing images
* 2) Have sizes better handle changes in defaults for theming
*
* @package ElggGravatar
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2
* @author Curverider Ltd
* @copyright Curverider Ltd 2008
* @link http://elgg.com/
*/

/**
* Init.
*
*/
function gravatar_init()
{
// Now override icons. Note priority: This sits somewhere between the profile user icons and default icons -
// so if you specify an icon for a user it will use that, else it will try a gravatar icon.
register_plugin_hook('entity:icon:url', 'user', 'gravatar_usericon_hook', 900);
}

/**
* This hooks into the getIcon API and returns a gravatar icon where possible
*
* @param unknown_type $hook
* @param unknown_type $entity_type
* @param unknown_type $returnvalue
* @param unknown_type $params
* @return unknown
*/
function gravatar_usericon_hook($hook, $entity_type, $returnvalue, $params)
{
global $CONFIG;

// Size lookup. TODO: Do this better to allow for changed themes.
$size_lookup = array(
'master' => '200',
'large' => '200',
'topbar' => '16',
'tiny' => '25',
'small' => '40',
'medium' => '100'
);

if ((!$returnvalue) && ($hook == 'entity:icon:url') && ($params['entity'] instanceof ElggUser))
{
$size = 40;
if (isset($size_lookup[$params['size']]))
$size = $size_lookup[$params['size']];

return "http://www.gravatar.com/avatar/".md5($params['entity']->email) . ".jpg?s=$size";
}
}

// Initialise plugin
register_elgg_event_handler('init','system','gravatar_init');
?>

Pretty simple.

Now that Elgg 1.0 is finally out of the door, I think it is time to talk a little bit about about some of the more advanced features – namely APIs and data formats!

Elgg 1.0 provides a number of ways at getting at your data, including a number of natively supported data formats; including OpenDD (of course), JSON, PHP, and of course RSS.

If you’re putting together a mashup, you can use these views very simply. You may have noticed when you’re looking at a page with a list of items on it, that there are buttons in the top left which link to either a RSS or an OpenDD view.

Now when you click on one of those you’ll see your data presented in an entirely different way. Now one of the fun things you can do is change that by altering the “view” parameter, for example “view=json” to get a JSON view.

You can export individual items of data (that you have access to) in different ways by visiting the “export” url. For example, to export GUID 1 (which will almost certainly be the first site you set up) visit:

http://mysite.com/export/opendd/1/

Of course, if you replace “opendd” with “json” or “php” you can see this data presented in an entirely different way. Additionally, you can also add support for other formats, as I discussed in a previous article.

On top of that, there is an API system where plugin writers can export a function as public. These functions can then be called via a rest-like api (discussed in more detail here).

You can then select how you want to then see that data – whether its PHP, XML or JSON etc.

So, in a nutshell, Elgg has some quite powerful tools for quickly and easily creating some pretty funky mashups. Have fun!

The other week I posted a PHP example for connecting to the elggVoices API. For those of you interested in developing desktop applications, here is some example code for doing the same thing in C/C++.

This code is designed to be built into a library (either a static or dynamic) but could probably be directly included without too much trouble. It is tested and works fine on Linux, but should also build as a Windows DLL without too much trouble. Windows doesn’t support gettimeofday, so this has been rewritten.

You will also need to install libcurl and cryptopp, on Debian/Ubuntu these are apt-gettable.

searunner.h

/**
* @file searunner.h Searunner C++ Client library.
* This is the main include file for the Linux/Win32 client api for the
* searunner social networking platform.
* @author Marcus Povey <marcus@dushka.co.uk>
*/
#ifndef SEARUNNER_H_
#define SEARUNNER_H_

/** Some definitions */
#define VERSION "0.1.0"

#ifdef WIN32
// Windows Only

#ifdef LIBSEARUNNERW32_EXPORTS
#define LIBSEARUNNERW32_API __declspec(dllexport)
#else
#define LIBSEARUNNERW32_API __declspec(dllimport)
#endif

#include <winsock2.h>

// Map snprintf to _snprintf
#define snprintf _snprintf

#endif

#ifdef LINUX
// Linux

#define LIBSEARUNNERW32_API

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>

#endif

/* Include crypto++ lib from www.cryptopp.com */
#include <crypto++/cryptlib.h>

/**
* Create the configuration structure.
*/
typedef struct {
char apikey[33];
char secret[33];
char endpoint[255];
char hmac_algo[8];
char hash_algo[8];
} SearunnerConfig;


extern LIBSEARUNNERW32_API SearunnerConfig hSearunnerConfig;

/** Get call */
#define SEARUNNER_CALLMODE_GET 0
/** Post call */
#define SEARUNNER_CALLMODE_POST 1

/** CURL Write function callback type */
typedef size_t (*CURLOPT_WRITEFUNCTION_CALLBACK)( void *ptr, size_t size, size_t nmemb, void *stream);

/**
* Initialisation the API client.
* This function initialises the api client configuration structure ready for use.
* @param api_key The API key you have been provided with.
* @param secret The secret key you have been provided with.
* @param endpoint_url The API Endpoint.
*/
LIBSEARUNNERW32_API int searunner_initialise_api(const char * api_key, const char * secret, const char * endpoint_url);

/**
* Raw POST request.
* @param parameters List of parameters.
* @param postData Optional post data in post call or null if this is a GET call.
* @param postdata_length The length of the data being sent.
* @param content_type Optional content type or null if this is a GET call.
* @param buffer The buffer to write the result into [OUT]
* @param buffer_size The size of the buffer
* @return The amount actually written.
*/
LIBSEARUNNERW32_API int __searunner_post_call(const char * parameters, unsigned char * postData, int postdata_length, char * content_type, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func);

/**
* Raw GET request.
* @param parameters List of parameters.
* @param buffer The buffer to write the result into [OUT]
* @param buffer_size The size of the buffer
* @return The amount actually written.
*/
LIBSEARUNNERW32_API int __searunner_get_call(const char * parameters, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func);

/**
* Make a searunner call.
* @param method Method call.
* @param parameters List of parameters.
* @param postData Optional post data in post call or null if this is a GET call.
* @param postdata_length The length of the data being sent.
* @param content_type Optional content type or null if this is a GET call.
* @param buffer The buffer to write the result into [OUT]
* @param buffer_size The size of the buffer
* @return The amount actually written.
*/
LIBSEARUNNERW32_API int __searunner_call(int method, const char * parameters, unsigned char * postData, int postdata_length, char * content_type, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func);

#endif /*SEARUNNER_H_*/

searunner.cpp

/**
* @file searunner.cpp
* Main functions and Win32 DLL entrypoint.
* @author Marcus Povey <marcus@dushka.co.uk>
*/

#include "searunner.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <crypto++/sha.h>
#include <crypto++/hmac.h>
#include <crypto++/hex.h>
#include <crypto++/filters.h>
#include <curl/curl.h>
#include <time.h>

/* Configuration structure */
LIBSEARUNNERW32_API SearunnerConfig hSearunnerConfig;

/* Utility functions used internally */
#ifdef WIN32

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64
#else
#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
#endif

struct timezone
{
int tz_minuteswest; /* minutes W of Greenwich */
int tz_dsttime; /* type of dst correction */
};

/**
* Replacement gettimeofday function for windows.
* Obtained from http://www.openasthra.com/c-tidbits/gettimeofday-function-for-windows/
*/
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
FILETIME ft;
unsigned __int64 tmpres = 0;
static int tzflag;

if (NULL != tv)
{
GetSystemTimeAsFileTime(&ft);

tmpres |= ft.dwHighDateTime;
tmpres <<= 32;
tmpres |= ft.dwLowDateTime;

/*converting file time to unix epoch*/
tmpres /= 10; /*convert into microseconds*/
tmpres -= DELTA_EPOCH_IN_MICROSECS;
tv->tv_sec = (long)(tmpres / 1000000UL);
tv->tv_usec = (long)(tmpres % 1000000UL);
}

if (NULL != tz)
{
if (!tzflag)
{
_tzset();
tzflag++;
}
tz->tz_minuteswest = _timezone / 60;
tz->tz_dsttime = _daylight;
}

return 0;
}

#endif

#define MICRO_IN_SEC 1000000.00

/**
* Micro time function.
*
* Example usage:
*
* char oo[100];
* microtime(oo);
* printf(oo);
*
* @param buffer Where to output variables.
*/
int microtime(char * buffer)
{
struct timeval tp;
long sec = 0L;
double msec = 0.0;
char ret[100];

if (gettimeofday((struct timeval *) &tp, (NULL)) == 0)
{
msec = (double) (tp.tv_usec / MICRO_IN_SEC);
sec = tp.tv_sec;

if (msec >= 1.0) msec -= (long) msec;

int msec2 = (int)(msec*100);

snprintf(ret, 100, "%ld.%d", sec, msec2);
strcpy(buffer, ret);
return 0;
}

return -1;
}

/**
* Calculate a hmac for the given data.
*
* Example usage:
*
* char oo [2*CryptoPP::SHA1::DIGESTSIZE+1];
* calculate_hmac(&hSearunnerConfig, "1", "2", "3", oo, 2*CryptoPP::SHA1::DIGESTSIZE+1);
* printf(oo);
*
* @param time String representation of time
* @param get_variables String of parameters
* @param post_hash The hash
* @param buffer The buffer
* @param bugger_size Size of the buffer
*/
int calculate_hmac(
const char * time,
const char * get_variables,
const char * post_hash,

char * buffer, int buffer_size)
{
memset(buffer,0,buffer_size);

if (strcmp(hSearunnerConfig.hmac_algo, "sha1")==0)
{
// Sha1

// Check the buffer size before we do anything
if (buffer_size<2*CryptoPP::SHA1::DIGESTSIZE + 1) return -1;

// Initialise memory
unsigned char tmp[ 2*CryptoPP::SHA1::DIGESTSIZE + 1 ];
memset(tmp,0,2*CryptoPP::SHA1::DIGESTSIZE + 1);

// Compute HMAC
CryptoPP::HMAC<CryptoPP::SHA1> hmac((const byte*)hSearunnerConfig.secret, strlen(hSearunnerConfig.secret));
hmac.Update((const byte*)time, strlen(time));
hmac.Update((const byte*)hSearunnerConfig.apikey, strlen(hSearunnerConfig.apikey));
hmac.Update((const byte*)get_variables, strlen(get_variables));
if (post_hash) hmac.Update((const byte*)post_hash, strlen(post_hash));

hmac.Final((byte*)tmp);

// hex encode
CryptoPP::HexEncoder encoder;
encoder.Attach(new CryptoPP::ArraySink((byte*)buffer, 2*CryptoPP::SHA1::DIGESTSIZE));
encoder.Put(tmp, sizeof(tmp));
encoder.MessageEnd();

// Convert to lower case
for (int n = 0; n<buffer_size; n++)
if (buffer[n]!=0) buffer[n] = tolower(buffer[n]);

return 0;
}

return -1;
}

/*
* Calculate the hash of some POST data.
* TODO: Tidy this up
* TODO: Support more algorithms
*
* Example usage:
*
* char oo [2*CryptoPP::MD5::DIGESTSIZE+1];
* calculate_POST_hash(&hSearunnerConfig, (unsigned char*)"test", 4, oo, 2*CryptoPP::MD5::DIGESTSIZE+1);
* printf(oo);
*
* @param postdata The data
* @param postlength The length
* @param outputbuffer The buffer
* @param outputbuffer_size The size of the output buffer
*/
int calculate_POST_hash(const unsigned char * postdata, int postlength, char * outputbuffer, int outputbuffer_size)
{
memset(outputbuffer,0,outputbuffer_size);

if (strcmp(hSearunnerConfig.hash_algo, "sha1")==0)
{
// Check the buffer size before we do anything
if (outputbuffer_size<2*CryptoPP::SHA1::DIGESTSIZE + 1) return -1;


// Initialise memory
unsigned char tmp[ 2*CryptoPP::SHA1::DIGESTSIZE + 1 ];
memset(tmp,0,2*CryptoPP::SHA1::DIGESTSIZE + 1);

// Calculate the digest
CryptoPP::SHA1 hash;
hash.CalculateDigest((byte*)tmp, (const byte*)postdata, postlength);

// hex encode
CryptoPP::HexEncoder encoder;
encoder.Attach(new CryptoPP::ArraySink((byte*)outputbuffer, 2*CryptoPP::SHA1::DIGESTSIZE));
encoder.Put(tmp, sizeof(tmp));
encoder.MessageEnd();

// Convert to lower case
for (int n = 0; n<outputbuffer_size; n++)
if (outputbuffer[n]!=0) outputbuffer[n] = tolower(outputbuffer[n]);

return 0;
}

return -1;
}

/*************************************/

LIBSEARUNNERW32_API int searunner_initialise_api(const char * api_key, const char * secret, const char * endpoint_url)
{
strcpy(hSearunnerConfig.apikey, api_key);
strcpy(hSearunnerConfig.secret, secret);
strcpy(hSearunnerConfig.endpoint, endpoint_url);
strcpy(hSearunnerConfig.hmac_algo, "sha1");
strcpy(hSearunnerConfig.hash_algo, "sha1");
return 0;
}

LIBSEARUNNERW32_API int __searunner_call(int method,
const char * parameters,
unsigned char * postData,
int postdata_length,
char * content_type,
CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func)
{
char url[2048] = {""};
char posthash[256] = {""};
char hmac[256] = {""};
char tmp[100] = {""};

struct curl_slist *headerlist = NULL;

// Get the microtime for now
char time[100];
microtime(time);

// Construct URL
strcpy(url, hSearunnerConfig.endpoint);
strcat(url, "?");
strcat(url, parameters);

// If post data, hash it
if (postdata_length > 0)
calculate_POST_hash(postData, postdata_length, posthash, 256);

// Constract the hmac
calculate_hmac(time, parameters, posthash, hmac, 256);

// Initialise CURL
CURL * curl = curl_easy_init();
if(curl) {

// Set headers
sprintf(tmp, "X-Searunner-apikey: %s", hSearunnerConfig.apikey);
headerlist = curl_slist_append(headerlist, tmp);

sprintf(tmp, "X-Searunner-time: %s", time);
headerlist = curl_slist_append(headerlist, tmp);

sprintf(tmp, "X-Searunner-hmac-algo: %s", hSearunnerConfig.hmac_algo);
headerlist = curl_slist_append(headerlist, tmp);

sprintf(tmp, "X-Searunner-hmac: %s", hmac);
headerlist = curl_slist_append(headerlist, tmp);

// Set options
if (method == SEARUNNER_CALLMODE_POST)
{
curl_easy_setopt(curl, CURLOPT_POST, 1); // Use POST

sprintf(tmp, "X-Searunner-posthash: %s", posthash);
headerlist = curl_slist_append(headerlist, tmp);

sprintf(tmp, "X-Searunner-posthash-algo: %s", hSearunnerConfig.hash_algo);
headerlist = curl_slist_append(headerlist, tmp);

sprintf(tmp, "Content-type: %s", content_type);
headerlist = curl_slist_append(headerlist, tmp);

sprintf(tmp, "Content-Length: %d", postdata_length);
headerlist = curl_slist_append(headerlist, tmp);

curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData); // Set the post data
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postdata_length);
}

curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); // Set the headers
curl_easy_setopt(curl,CURLOPT_URL, url); // Set the url
curl_easy_setopt(curl, CURLOPT_READFUNCTION, curl_read_func); // Use our own read function

// Make call
CURLcode res = curl_easy_perform(curl);

// Clean up
curl_easy_cleanup(curl);

return res;
}

return 0;
}

LIBSEARUNNERW32_API int __searunner_post_call(const char * parameters, unsigned char * postData, int postdata_length, char * content_type, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func)
{
return __searunner_call(SEARUNNER_CALLMODE_POST, parameters, postData, postdata_length, content_type, curl_read_func);
}

LIBSEARUNNERW32_API int __searunner_get_call(const char * parameters, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func)
{
return __searunner_call(SEARUNNER_CALLMODE_GET, parameters, NULL, 0, NULL, curl_read_func);
}

#ifdef WIN32

/*
Windows Only DLL entrypoint.
*/

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


#endif