This is my first post in English, so stay with me during this test as i progress and make some epic fails.
Finding clients the regular way
Normally i wake up, take a shower, have breakfast and check my mail. If i find myself in the position of not having enough work at the moment, i’ll start browsing job boards and search Twitter for relevant information.
The downside to this is the huge amount of time this takes. Not only that, but it involves browsing to different sites with different layouts and different means of filtering and searching for jobs. This can be a tiny pain in the ass!

Finding clients in under 5 minutes
So, provided i browse all these sites and bore myself to death by watching an endless Twitter stream, it could take me hours to come up with a list of potential clients. This just doesn’t cut it. It’s too consuming and too less rewarding.
Hell, i’m a web developer. Why can’t this be more like reading those nice job adverts in the news paper. Honestly? It’s not that hard to accomplish just that. Now, when i wake up i read my mail and browse through job.johmanx.com for some potential victims to pay me some moneyz.
Building a personal job board
Ofcourse i had to go around a really simple way too solve this “problem” of mine, because time is money and all this had to do was display an overview, so here goes.
First i assembled a list of job boards i wanted to follow:
$boards = array(
'odesk' => 'http://www.odesk.com/jobs/rss?c1=Web+Development',
'37 signals - design' => 'http://jobs.37signals.com/categories/1/jobs.rss',
'37 signals - programming' => 'http://jobs.37signals.com/categories/2/jobs.rss',
'freelanceswitch - design' => 'http://feeds.feedburner.com/FSJobsDesign',
'freelanceswitch - devel' => 'http://feeds.feedburner.com/FSJobsProgramming',
'freelanceswitch - misc' => 'http://feeds.feedburner.com/FSJobsMisc',
'authentic jobs' => 'http://www.authenticjobs.com/rss/custom.php?terms=&type=freelance&cats=3,4,2,5,6,'
);
As you may have noticed, i sticked to specific categories with the most value to me.
I also wanted to follow a few search queries on twitter, so i could follow people in need of a developer:
$twitter_searches = array(
'need web designer',
'hire web designer',
'need web developer',
'hire web developer',
'hire php'
);
Then comes the “chooka-chook” and we give those feeds some rails to ride on:
/***
* @function parse_universal(var $feed, int $retnr)
* @description parse a universal XML feed
* @return array array(var $feed, var $name)
*/
function parse_universal($feed,$name="")
{
$feed = simplexml_load_file($feed);
$child = $feed->xpath("channel");
$child = $child[0]->xpath("item");
$feed = array();
foreach($child AS $item)
{
$title = $item->xpath("title");
$date = $item->xpath("pubDate");
$timestamp = strtotime(substr($date[0][0],0));
$guid = $item->xpath("link");
$feed[] = array($timestamp,$name,substr($title[0][0],0),$guid[0][0]);
}
return $feed;
}
/***
* @function socialdate(int $timestamp)
* @description Transform time in human readable difference in time
* @return var $val
*/
function socialdate($timestamp)
{
if($timestamp == 0) { return "?"; }
$diff = time()-$timestamp;
switch($diff)
{
case ($diff<120): $val = round($diff,0)." seconds"; break;
case ($diff<7200): $val = round($diff/60,0)." minutes"; break;
case ($diff<48*3600): $val = round($diff/3600,0)." hours"; break;
case ($diff<14*24*3600): $val = round($diff/(3600*24),0)." days"; break;
case ($diff<60*24*3600): $val = round($diff/(3600*24*7),0)." weeks"; break;
case ($diff<730*24*3600): $val = round($diff/(3600*24*30),0)." months"; break; case ($diff>=365*24*3600): $val = round($diff/(3600*24*365),0)." years"; break;
}
return '-'.$val;
}
/***
* @function web2txt(var $txt)
* @description Transform URLs to Anchor links
* @return var $txt
*/
function web2txt($txt="")
{
$txt = eregi_replace('(((f|ht){1}tp://)[-a-zA-Z0-9@:%_+.~#?&//=]+)', '<a href="\1" target="_blank">\1</a>', $txt);
return $txt;
}
web2text() is a simple function to look for URLs and transform them to clickable links.
socialdate() is a function to determine the distance between now and then in human readable text.
parse_universal() needs some explenating on my part.
Parsing an XML feed
While parsing an XML document is a fairly routine task, i made it a bit more complex to suit my needs. For instance, i wanted to order everything by date, including all the boards and twitter queries. This can be done simply by using the rsort() function of PHP. However, i’m using a multidimensional array which needs to be sorted in reverse order. By default, rsort tries to order everything as logically possible, so in case of an array with multiple dimensions, the first entry of an array within the array is sorted with the first entry of the next array within the array. Confusing? I’ll just give an example:
$array = array(
array(4,6,'amber'),
array(6,4,'mapel'),
array(2,5,'chocolate')
);
rsort($array);
print_r($array);
The output:
Array
(
[0] => Array
(
[0] => 6
[1] => 4
[2] => mapel
)
[1] => Array
(
[0] => 4
[1] => 6
[2] => amber
)
[2] => Array
(
[0] => 2
[1] => 5
[2] => chocolate
)
)
As you can see and as i told before, the first entry of each array is used to compare and pick the order. In the function parse_universal() we make use of this by putting the UNIX timestamp as first entry in the array.
Next up we pass it a name, so we can later on determine what the source of the message actually was. This is especially handy for finding resources that just clutter your stream. After that we pass on the text of the message and the place of the original message.
Parsing the resources
Now we finally got everything ready to be processed into our own board, let’s get some magic happening:
$feed = array();
foreach($boards AS $board=>$url)
{
$board = @parse_universal($url,$board);
$feed = @array_merge($feed,$board);
}
sleep(2);
foreach($twitter_searches AS $query)
{
$search = @parse_universal('http://search.twitter.com/search.rss?q='.$query,"twitter: ".$query);
$feed = @array_merge($feed,$search);
}
rsort($feed);
foreach($feed AS $row)
{
print '
<div class="rounded">
<a href="'.$row[3].'" target="blank">'.web2txt($row[2]).'</a>
<div class="bottom"><small>'.socialdate($row[0]).'</small> - <small><em>'.$row[1].'</em></small></div>
</div>
';
}
Unfortunately we need the array_merge() function, because we want to generate multiple arrays at first and there’s no other way to merge an array. Sounds fair enough. It’s just not an optmized way of handling arrays. The reason i put in a different foreach for the twitter functionality is just because i’m lazy and don’t want to copy and paste that feed url everytime i change my search queries. As you can see i pass in both the feed url and a name, which will be used to identify the messages later on.
When the final feed is processed, we use an array with processed data:
$row[(int)]
- 0: The timestamp which will be transformed using socialdate()
- 1: The name of the feed from which it originated
- 2: The text of the current message, checked on possible links using web2txt()
- 3: The URL of the original location of the message
Now we’re almost there. We already have the following garbage:

Giving it some style
As it is, this list is garbage. You can’t read it easily and it’s really hard to filter anything usefull. Luckily we can give it some spice, using CSS:
.rounded{
-webkit-border-radius: 16px;
-moz-border-radius: 16px;
border-radius: 16px;
background:#eee;
margin:3px;
padding:8px;
float:left;
width:478px;
height:90px;
position:relative;
font-family:Tahoma;
}
.bottom{
position:absolute;
bottom:8px;
}
Looky here:

Now, that’s more like it.
Next up: Automating the shizzle
Ofcourse reading N amount of feeds and search queries takes some time. Time you don’t wanna spend waiting, because this removes the whole purpose of saving time using a personal board. Normally i would go with something like this:
*/15 * * * * php /path/to/parse.php > /home/job.johmanx.com/public_html/cache.html
However, this isn’t going to work, because sometimes a feed has an enormous hold up or inconsistent data. I have tried everything to extend my parsing time, but i always end up with a 500 – Internal server error. On this account you have to minimize your resources and build in a failsafe, like i did:
*/15 * * * * php /path/to/cacher.php
Cacher.php:
$content = file_get_contents('/path/to/parse.php');
if(strlen($content)>1200){file_put_contents('/home/job.johmanx.com/public_html/cache.html',$content);}
This way you don’t get an empty page when it failed, which can be really frustrating.
Index.php:
<!doctype html>
<html>
<head>
<title>Job listings | Joh Man X - Clear webdevelopment</title>
<link rel="stylesheet" href="stylesheet/style.css" type="text/css" />
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div id="pagewrap">
<section>
<div class="wrapper">
<?php print file_get_contents('cache.html'); ?>
<br class="clear" />
</div>
</section>
</div>
</body>
</html>
The final product
And there you have it. After some minor alterations, i had my job board.
