Let me start by saying that I love Gravatar.com–most of the time. They do one thing very well: Serving up an avatar based on an email address.
(skip below if you are just interested in why they FAIL)
How Gravatar Works
Anyone can sign up on Gravatar, upload images, crop them and associate them to 1 or more email addresses, which can then be accessed by 3rd party sites using a simple to generate URL:
<img src="
http://gravatar.com/avatar.php?
gravatar_id=f83d1b99858682d4d567e8bf400a55b8
&rating=PG
&size=40
&default=identicon
"/> |
<img src="
http://gravatar.com/avatar.php?
gravatar_id=f83d1b99858682d4d567e8bf400a55b8
&rating=PG
&size=40
&default=identicon
"/>
Where f83d1b99858682d4d567e8bf400a55b8 is an md5() hash of my email address.
This outputs
, and if it couldn’t find my email address, it would have output
(since I specified ‘identicon’ as the default image if one could not be found). I could also have supplied a full URL to a default image of my own.
Basic Implementation in PHP
<?php
/**
* Gravatar
* Fetches a gravatar from the Gravatar website using the specified params
* @access public
* @param string $email The email address of the avatar to fetch
* @param string $rating Defines a maximum rating for the image’s content. If the owner has flagged the image with a rating higher than that specified, the default image will instead be used. Acceptable values are G, PG, R, or X.
* @param integer $size Defines the desired height and width of the image in pixels, with acceptable values being between 1 and 80. Specifying a size smaller than 80 will result in the original gravatar image to be downsampled using bicubic resampling before output.
* @param string $default The default parameter allows you to specify the URL to an image that will be used as the default if the query fails to locate a gravatar for the given email address. Additionally, if a user’s gravatar exceeds the rating limit defined by the “rating†parameter, this is the image that will instead be shown.
$default can also be 'identicon', 'monsterid', or 'wavatar'
* @return string
*/
function gravatar( $email, $rating = 'X', $size = '80', $default = 'identicon' ) {
return "http://gravatar.com/avatar.php?gravatar_id="
.md5( strtolower($email) )."&rating="
.$rating."&size="
.$size."&default="
.urlencode($default);
}
?> |
<?php
/**
* Gravatar
* Fetches a gravatar from the Gravatar website using the specified params
* @access public
* @param string $email The email address of the avatar to fetch
* @param string $rating Defines a maximum rating for the image’s content. If the owner has flagged the image with a rating higher than that specified, the default image will instead be used. Acceptable values are G, PG, R, or X.
* @param integer $size Defines the desired height and width of the image in pixels, with acceptable values being between 1 and 80. Specifying a size smaller than 80 will result in the original gravatar image to be downsampled using bicubic resampling before output.
* @param string $default The default parameter allows you to specify the URL to an image that will be used as the default if the query fails to locate a gravatar for the given email address. Additionally, if a user’s gravatar exceeds the rating limit defined by the “rating†parameter, this is the image that will instead be shown.
$default can also be 'identicon', 'monsterid', or 'wavatar'
* @return string
*/
function gravatar( $email, $rating = 'X', $size = '80', $default = 'identicon' ) {
return "http://gravatar.com/avatar.php?gravatar_id="
.md5( strtolower($email) )."&rating="
.$rating."&size="
.$size."&default="
.urlencode($default);
}
?>
Easily called as so:
<img src="<?=gravatar('email@domain.com')?>" alt="avatar" /> |
<img src="<?=gravatar('email@domain.com')?>" alt="avatar" />
How Does it FAIL?
I’m a Web Developer. I make websites–mostly social websites. It is imperative for these sites that users have a personal avatar. If they don’t, we provide a default dummy image and give them an opportunity to override it by uploading one of their own. This is VERY common on the web. Look at Twitter (they don’t use Gravatar). Every site with a profile allows users to upload an avatar.
Here’s the crux of the problem: None of these sites are going to tell their users to go create an account at Gravatar.com to upload an avatar. That’s just absurd. Why tell your users to leave the site if you don’t have to? If it’s easy enough to build your own solution without having to have your users go elsewhere to get the job done, companies will always roll their own avatar upload/crop/management system.
This is… stupid.
This is not what the dream calls for.
The Solution
Gravatar could (fairly) easily develop a developer system that allows 3rd party sites to use an embedded form of Gravatars upload/crop system to add images for email addresses that don’t have a Gravatar account. Hey, they are already doing this on WordPress.com! There are gobs of ways to do this. If they want to hire me, I’ll gladly build it for them. I’m sick of having to implement avatar upload/crop and management systems on my own–and it sucks to be able to use Gravatar only until they don’t have an email on file, at which point we have to have the user store an avatar on our servers–then the user goes to another site and they have to do it again–all because we will never tell them to create a gravatar account and gravatar will not allow us to upload an avatar for the user.
follow on Twitter
I just created my life plan (or the start of one anyway–I’ll be editing this thing for a while) and I figured I’d share with you the code used to make it.
My live updated version is on my Portfolio: The Future as I See It.
Demo
$maxYear){
$diffYears = $year – $maxYear;
$year = $maxYear; // this is what we will use in mktime() then we can add seconds to that output
$addtime = $diffYears * 31536000; // years of seconds
}
// count leap years
$numYears = $year – date(‘Y’);
$leaps = floor($numYears/4);
$addtime -= $leaps * 86400; // subtract these excess days (to get accurate year count)
$date = mktime(0,0,0,$month,$day,$year) + $addtime;
$now = time();
$days = ceil(($date – $now)/86400);
if($days < 365){
return 'in '.$days .' day'.($days==1?'':'s');
}else{
$years = floor($days/365);
$days = $days%365;
return 'in '.$years.' year'.($years==1?'':'s').', '.$days.' day'.($days==1?'':'s');
}
}
$events = array(
array(
'yyyy'=>‘2010’,’mm’=>’01’,’dd’=>’20’,
‘name’=>’Move to Magdeburg, Deutschland’,
‘info’=>’My family will be moving to Magdeburg to experience living in Germany and to strenghten our deutsche Sprachfähigkeiten.’,
‘type’=>’travel’
),
array(
‘yyyy’=>’2010′,’mm’=>’06’,’dd’=>’15’,
‘name’=>’Produce an iPhone App’,
‘info’=>’This might get done before I move to Magdeburg but this is my personal deadline for a v1.0 release.’,
‘type’=>’work’
),
array(
‘yyyy’=>’2011′,’mm’=>’01’,’dd’=>’20’,
‘name’=>’Move Back to Seattle, WA USA’,
‘info’=>’Unless we decide to stay in Germany or move elsewhere’,
‘type’=>’travel’
),
array(
‘yyyy’=>’2013′,’mm’=>’07’,’dd’=>’15’,
‘name’=>’Accomplish a Free-Standing Backflip’,
‘info’=>’I\’ve done it on a trampoline and with spotters but never alone without aid. This is a life-long goal.’,
‘type’=>’fitness’
),
array(
‘yyyy’=>’2017′,’mm’=>’09’,’dd’=>’01’,
‘name’=>’Publish First Novel’,
‘info’=>’Contact me if you want to be one of my pre-publication reviewers’,
‘type’=>’creative’
),
array(
‘yyyy’=>’2021′,’mm’=>’07’,’dd’=>’22’,
‘name’=>’Give First TED Talk’,
‘info’=>’On what topic? Web, security, social media, nano-technology, engineering, the future, productivity in an age of overabundance? I\’m not sure yet–but it will be awesome.’,
‘url’=>’http://ted.com’,
‘type’=>’social’
),
array(
‘yyyy’=>’2026′,’mm’=>’07’,’dd’=>’22’,
‘name’=>’Engineering Degree from MIT’,
‘info’=>’Oh, yes, I will be going back to school. Probably many times in my life.’,
‘type’=>’education’
),
array(
‘yyyy’=>’2044′,’mm’=>’11’,’dd’=>’17’,
‘name’=>’Undergo First Major Rejuvination Treatments’,
‘info’=>’Certainly there will be many improvements to personal health management before this date, but this is the anticipated date of my first major operation (or likely, full system nanobot integration)’,
‘type’=>’fitness’
),
array(
‘yyyy’=>’2079′,’mm’=>’11’,’dd’=>’05’,
‘name’=>’Engage Dyson Sphere’,
‘url’=>’http://en.wikipedia.org/wiki/Dyson_sphere’,
‘info’=>’If not on the team that deploys it, I will at least be celebrating the event :)’,
‘type’=>’social’
)
);
$types = array();
foreach($events as $event){
if(empty($types[$event[‘type’]])) $types[$event[‘type’]] = 1;
else $types[$event[‘type’]]++;
}
ksort($types);
$typeLinks = ”;
foreach($types as $key=>$val){
$typeLinks .= ‘‘.$key.’ (‘.$val.’)‘;
}
?>
- =$event['yyyy'].'.'.$event['mm'].'.'.$event['dd']?> =$event['name']?>
if(!empty($event['url'])) echo '[more info]’;
?> =getUntil($event['yyyy'],$event['mm'],$event['dd'])?>
=$event['info']?>
=$event['type']?>
}
?>
The PHP/HTML
Of course, you will need to make sure you include jQuery. I recommend using the Google Hosted Ajax Library rather than hosting it yourself. There are many good reasons to do this (blog post soon to come).
Just place this either in your HTML head or just before your closing body tag:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> |
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
I’ve used PHPDoc blocks to supply inline documentation throughout the code. Post a comment with any questions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
| <?php
$pageName = 'plan';
$pageTitle = 'The Future as I See It';
include('header.php');
/**
* get the years and days until a date
* this function is a bit of a hack until my server supports PHP 5.3.0
* which has date_diff, alias of DateTime::diff (http://www.php.net/manual/en/datetime.diff.php)
*/
function getUntil($year,$month,$day){
$year = intval($year);
$maxYear = 2035;
$addtime = 0;
if($year > $maxYear){
$diffYears = $year - $maxYear;
$year = $maxYear;
$addtime = $diffYears * 31536000; // years of seconds
}
// count leap years
$numYears = $year - date('Y');
$leaps = floor($numYears/4);
$addtime -= $leaps * 86400; // subtract these excess days (to get accurate year count)
$date = mktime(0,0,0,$month,$day,$year) + $addtime;
$now = time();
$days = ceil(($date - $now)/86400);
if($days < 365){
return 'in '.$days .' day'.($days==1?'':'s');
}else{
$years = floor($days/365);
$days = $days%365;
return 'in '.$years.' year'.($years==1?'':'s').', '.$days.' day'.($days==1?'':'s');
}
}
$events = array(
array(
'yyyy'=>'2010','mm'=>'01','dd'=>'20',
'name'=>'Move to Magdeburg, Deutschland',
'info'=>'My family will be moving to Magdeburg to experience living in Germany and to strenghten our deutsche Sprachfähigkeiten.',
'type'=>'travel'
),
array(
'yyyy'=>'2010','mm'=>'06','dd'=>'15',
'name'=>'Produce an iPhone App',
'info'=>'This might get done before I move to Magdeburg but this is my personal deadline for a v1.0 release.',
'type'=>'work'
),
array(
'yyyy'=>'2013','mm'=>'07','dd'=>'15',
'name'=>'Accomplish a Free-Standing Backflip',
'info'=>'I\'ve done it on a trampoline and with spotters but never alone without aid. This is a life-long goal.',
'type'=>'fitness'
),
array(
'yyyy'=>'2017','mm'=>'09','dd'=>'01',
'name'=>'Publish First Novel',
'info'=>'Contact me if you want to be one of my pre-publication reviewers',
'type'=>'creative'
),
array(
'yyyy'=>'2021','mm'=>'07','dd'=>'22',
'name'=>'Give First TED Talk',
'info'=>'On what topic? Web, security, social media, nano-technology, engineering, the future, productivity in an age of overabundance? I\'m not sure yet--but it will be awesome.',
'url'=>'http://ted.com',
'type'=>'social'
),
array(
'yyyy'=>'2026','mm'=>'07','dd'=>'22',
'name'=>'Engineering Degree from MIT',
'info'=>'Oh, yes, I will be going back to school. Probably many times in my life.',
'type'=>'education'
),
array(
'yyyy'=>'2044','mm'=>'11','dd'=>'17',
'name'=>'Undergo First Major Rejuvination Treatments',
'info'=>'Certainly there will be many improvements to personal health management before this date, but this is the anticipated date of my first major operation (or likely, full system nanobot integration)',
'type'=>'fitness'
),
array(
'yyyy'=>'2079','mm'=>'11','dd'=>'05',
'name'=>'Engage Dyson Sphere',
'url'=>'http://en.wikipedia.org/wiki/Dyson_sphere',
'info'=>'If not on the team that deploys it, I will at least be celebrating the event :)',
'type'=>'social'
)
);
?>
<h1><?=$pageTitle?></h1>
<div id="pageFilter">
<a href="javascript:pageFilter('all')" class="active all" title="Show All">show all</a><?php
$types = array();
foreach($events as $event){
if(empty($types[$event['type']])) $types[$event['type']] = 1;
else $types[$event['type']]++;
}
ksort($types);
foreach($types as $key=>$val){
echo '<a href="javascript:pageFilter(\''.$key.'\')" class="'.$key.'" title="Show '.ucfirst($key).'">'.$key.' ('.$val.')</a>';
}
?>
</div>
<ul class="timeline">
<?php
foreach($events as $event){
?>
<li class="<?=$event['type']?>">
<div class="head">
<span class="date"><?=$event['yyyy'].'.'.$event['mm'].'.'.$event['dd']?></span>
<span class="name"><?=$event['name']?></span>
<?
if(!empty($event['url'])) echo '[<span class="url"><a href="'.$event['url'].'">more info</a></span>]';
?>
<span class="until"><?=getUntil($event['yyyy'],$event['mm'],$event['dd'])?></span>
</div>
<div class="body">
<div class="info"><?=$event['info']?></div>
<div class="type"><?=$event['type']?></div>
</div>
</li>
<?
}
?>
</ul>
<script type="text/javascript">
function pageFilter(type){
$('#pageFilter a').removeClass('active');
$('#pageFilter .'+type).addClass('active');
if(type=='all'){
$('.timeline li').slideDown();
}else{
$('.timeline li').slideUp();
$('li.'+type).slideDown();
}
}
$('.type').bind('click',function(){
pageFilter($(this).html());
});
</script>
<?php include('footer.php'); ?> |
<?php
$pageName = 'plan';
$pageTitle = 'The Future as I See It';
include('header.php');
/**
* get the years and days until a date
* this function is a bit of a hack until my server supports PHP 5.3.0
* which has date_diff, alias of DateTime::diff (http://www.php.net/manual/en/datetime.diff.php)
*/
function getUntil($year,$month,$day){
$year = intval($year);
$maxYear = 2035;
$addtime = 0;
if($year > $maxYear){
$diffYears = $year - $maxYear;
$year = $maxYear;
$addtime = $diffYears * 31536000; // years of seconds
}
// count leap years
$numYears = $year - date('Y');
$leaps = floor($numYears/4);
$addtime -= $leaps * 86400; // subtract these excess days (to get accurate year count)
$date = mktime(0,0,0,$month,$day,$year) + $addtime;
$now = time();
$days = ceil(($date - $now)/86400);
if($days < 365){
return 'in '.$days .' day'.($days==1?'':'s');
}else{
$years = floor($days/365);
$days = $days%365;
return 'in '.$years.' year'.($years==1?'':'s').', '.$days.' day'.($days==1?'':'s');
}
}
$events = array(
array(
'yyyy'=>'2010','mm'=>'01','dd'=>'20',
'name'=>'Move to Magdeburg, Deutschland',
'info'=>'My family will be moving to Magdeburg to experience living in Germany and to strenghten our deutsche Sprachfähigkeiten.',
'type'=>'travel'
),
array(
'yyyy'=>'2010','mm'=>'06','dd'=>'15',
'name'=>'Produce an iPhone App',
'info'=>'This might get done before I move to Magdeburg but this is my personal deadline for a v1.0 release.',
'type'=>'work'
),
array(
'yyyy'=>'2013','mm'=>'07','dd'=>'15',
'name'=>'Accomplish a Free-Standing Backflip',
'info'=>'I\'ve done it on a trampoline and with spotters but never alone without aid. This is a life-long goal.',
'type'=>'fitness'
),
array(
'yyyy'=>'2017','mm'=>'09','dd'=>'01',
'name'=>'Publish First Novel',
'info'=>'Contact me if you want to be one of my pre-publication reviewers',
'type'=>'creative'
),
array(
'yyyy'=>'2021','mm'=>'07','dd'=>'22',
'name'=>'Give First TED Talk',
'info'=>'On what topic? Web, security, social media, nano-technology, engineering, the future, productivity in an age of overabundance? I\'m not sure yet--but it will be awesome.',
'url'=>'http://ted.com',
'type'=>'social'
),
array(
'yyyy'=>'2026','mm'=>'07','dd'=>'22',
'name'=>'Engineering Degree from MIT',
'info'=>'Oh, yes, I will be going back to school. Probably many times in my life.',
'type'=>'education'
),
array(
'yyyy'=>'2044','mm'=>'11','dd'=>'17',
'name'=>'Undergo First Major Rejuvination Treatments',
'info'=>'Certainly there will be many improvements to personal health management before this date, but this is the anticipated date of my first major operation (or likely, full system nanobot integration)',
'type'=>'fitness'
),
array(
'yyyy'=>'2079','mm'=>'11','dd'=>'05',
'name'=>'Engage Dyson Sphere',
'url'=>'http://en.wikipedia.org/wiki/Dyson_sphere',
'info'=>'If not on the team that deploys it, I will at least be celebrating the event :)',
'type'=>'social'
)
);
?>
<h1><?=$pageTitle?></h1>
<div id="pageFilter">
<a href="javascript:pageFilter('all')" class="active all" title="Show All">show all</a><?php
$types = array();
foreach($events as $event){
if(empty($types[$event['type']])) $types[$event['type']] = 1;
else $types[$event['type']]++;
}
ksort($types);
foreach($types as $key=>$val){
echo '<a href="javascript:pageFilter(\''.$key.'\')" class="'.$key.'" title="Show '.ucfirst($key).'">'.$key.' ('.$val.')</a>';
}
?>
</div>
<ul class="timeline">
<?php
foreach($events as $event){
?>
<li class="<?=$event['type']?>">
<div class="head">
<span class="date"><?=$event['yyyy'].'.'.$event['mm'].'.'.$event['dd']?></span>
<span class="name"><?=$event['name']?></span>
<?
if(!empty($event['url'])) echo '[<span class="url"><a href="'.$event['url'].'">more info</a></span>]';
?>
<span class="until"><?=getUntil($event['yyyy'],$event['mm'],$event['dd'])?></span>
</div>
<div class="body">
<div class="info"><?=$event['info']?></div>
<div class="type"><?=$event['type']?></div>
</div>
</li>
<?
}
?>
</ul>
<script type="text/javascript">
function pageFilter(type){
$('#pageFilter a').removeClass('active');
$('#pageFilter .'+type).addClass('active');
if(type=='all'){
$('.timeline li').slideDown();
}else{
$('.timeline li').slideUp();
$('li.'+type).slideDown();
}
}
$('.type').bind('click',function(){
pageFilter($(this).html());
});
</script>
<?php include('footer.php'); ?>
The CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
| #pageFilter {
font-size:.8em;
text-align:right;
margin-top:10px;
}
#pageFilter a{
border:1px solid #dde;
padding:3px 5px;
margin-left:5px;
}
.timeline {
list-style:none;
font-size:.8em;
}
.timeline li{
margin-top:10px;
border:1px solid #9cf;
}
.timeline .head {
background-color:#def;
padding:3px 5px;
}
.timeline .body {
padding:10px;
position:relative;
}
.timeline .date{
color:#036;
font-weight:bold;
margin-right:10px;
}
.timeline .name{
color:#666;
}
.timeline .until{
float:right;
}
.timeline .info{
padding-bottom:15px;
}
.timeline .type{
position:absolute;
bottom:0;
right:0;
border:1px solid #dde;
background-color:#ffe;
padding:3px 3px 3px 5px;
cursor:pointer;
}
a.all, .timeline .all .type{
background-color:#FFFFFF;
}
a.creative, .timeline .creative .type{
background-color:#F0FFFD;
}
a.education, .timeline .education .type{
background-color:#EEF4FF;
}
a.fitness, .timeline .fitness .type{
background-color:#F2EEFE;
}
a.social, .timeline .social .type{
background-color:#FDF5F5;
}
a.travel, .timeline .travel .type{
background-color:#FFFDED;
}
a.work, .timeline .work .type{
background-color:#F3FFEB;
} |
#pageFilter {
font-size:.8em;
text-align:right;
margin-top:10px;
}
#pageFilter a{
border:1px solid #dde;
padding:3px 5px;
margin-left:5px;
}
.timeline {
list-style:none;
font-size:.8em;
}
.timeline li{
margin-top:10px;
border:1px solid #9cf;
}
.timeline .head {
background-color:#def;
padding:3px 5px;
}
.timeline .body {
padding:10px;
position:relative;
}
.timeline .date{
color:#036;
font-weight:bold;
margin-right:10px;
}
.timeline .name{
color:#666;
}
.timeline .until{
float:right;
}
.timeline .info{
padding-bottom:15px;
}
.timeline .type{
position:absolute;
bottom:0;
right:0;
border:1px solid #dde;
background-color:#ffe;
padding:3px 3px 3px 5px;
cursor:pointer;
}
a.all, .timeline .all .type{
background-color:#FFFFFF;
}
a.creative, .timeline .creative .type{
background-color:#F0FFFD;
}
a.education, .timeline .education .type{
background-color:#EEF4FF;
}
a.fitness, .timeline .fitness .type{
background-color:#F2EEFE;
}
a.social, .timeline .social .type{
background-color:#FDF5F5;
}
a.travel, .timeline .travel .type{
background-color:#FFFDED;
}
a.work, .timeline .work .type{
background-color:#F3FFEB;
}
follow on Twitter