Monday, August 17, 2009

Captcha

Here's a custom image captcha I just made. What's cool about it is that it's transparent (PNG), so it 'blends' into the background. To suit different backgrounds, it has two settings - dark and light - which you set according to the background color of the page you want to put it on. Here's a picture that demonstrates this; you can see that the captcha is completely unreadable when put on the wrong background, but completely readable when it's right: Captcha Demo Here's the code: <?php $W = 160; // width $H = 60; // height $L = 6; // length of the key $BG = 'light'; // can be 'light' or 'dark', accorting to the background color of // the page it will be on $F = './DejaVuSans.ttf'; // path to true-type font file function makeKey($length) { // generate a random sequence of characters $a = 'abcdefghijklmnopqrstuvwxyz'; $s = ''; for ($i=0; $i < $length; $i++) { $s .= $a[mt_rand(0, strlen($a) - 1)]; } return $s; } $img = imagecreatetruecolor($W, $H); // make the image alpha-aware imagesavealpha($img, true); // make colors 'blend', not overwrite imagealphablending($img, true); // make the image transparent imagefill($img, 1, 1, imagecolorallocatealpha($img, 0, 0, 0, 127)); // generate two random colors and decide which one goes where $dark = Array (mt_rand(0, 126), mt_rand(0, 126), mt_rand(0, 126)); $light = Array (mt_rand(127, 255), mt_rand(127, 255), mt_rand(127, 255)); if ($BG == 'dark') { $bg_color = imagecolorallocatealpha($img, $dark[0], $dark[1], $dark[2], mt_rand(64, 96)); $fg_color = imagecolorallocatealpha($img, $light[0], $light[1], $light[2], mt_rand(32, 64)); } else { $bg_color = imagecolorallocatealpha($img, $light[0], $light[1], $light[2], mt_rand(64, 96)); $fg_color = imagecolorallocatealpha($img, $dark[0], $dark[1], $dark[2], mt_rand(32, 64)); } // write background static $angle = mt_rand(20, 35); for ($i=0; $i < 15; $i++) { imagettftext($img, 12, $angle, 0, $i*15, $bg_color, $F, makeKey(30)); } $key = makeKey($L); // you should store this in the user session to check it later // write the actual key, in two parts imagettftext($img, mt_rand(16, 22), mt_rand(10, 30), mt_rand(5, 30), mt_rand($H-16, $H-22), $fg_color, $F, substr($key, 0, 3)); imagettftext($img, mt_rand(16, 22), mt_rand(-30, -10), mt_rand($W/2+5, $W/2+30), mt_rand(16, 22), $fg_color, $F, substr($key, 3, 3)); // output the image header("Content-Type: image/png"); imagepng($img); ?> On my machine (Pentium DualCore @ 2.80Ghz) it generates images in 70-75ms. I think that's pretty fair. Also, it works with non-bundled GD versions, too, so you don't have to worry about that. Enjoy.

Saturday, August 15, 2009

Ubuntu One

Ubuntu One is a file hosting service provided by Canonical (the company that develops Ubuntu). You are given 2GB for free and 10GB for $10/month. The way it works is that you create an account, install a client and associate it with your account. After that, the client will monitor your $HOME/Ubuntu One folder and synchronize its contents with the Ubuntu One servers. The main idea of it is that of syncing between computers -- you associate all your PCs/laptops with the same account and your files get automatically synced between them. However, I use it for another purpose -- backups. I back up my work and other stuff on Ubuntu One, 2GB is really enough (for me, at least). What's great is that they also provide a web interface to download / upload files from / to your account, so I can access my files wherever, whenever, from whatever. Don't jump head first, though. You might want to have a look at Dropbox, too. The main advantages are:
  • It has clients for more platforms (Windows, Mac, other Linux distros);
  • It is more badwidth efficient, as it only uploads diffs when overwriting a file (I'm not sure whether Ubuntu One does that too, or not);
  • It's a bit more mature
The only reason why I chose Ubuntu One over Dropbox is because I was sure it integrated well with Ubuntu and because I don't use any other OSes. Also, they are still in Beta, so they are constantly improving and adding features. The choice... is yours. [manic laugh]

Pagination with Smarty

I've spent the last hour or two on this. It's a Smarty template that takes an associative array with these keys:
  • curPage: The number of the current page
  • url: The url to which it will make the links. It replaces "%x" with the page number in the URL, so this can be something like /news/page-%x/ or /news.php?page=%x
  • totalPages: The number of total pages.
It also has a few variables that tweak its output (these are set in the template itself, using {assign}):
  • putDots: If the distance between the current page and the first/last page is greater than this, it will put dots towards the end
  • border: when it puts dots around the current page, this is the number of pages that appear around it until the dots start.
I'm too tired and bored to explain anything else, but if anyone has any questions, I'll be glad to answer them. Here's the template: {assign var="putDots" value=3} {assign var="border" value=2} {assign var="curPage" value=$pagination.curPage} {assign var="url" value=$pagination.url} {assign var="totalPages" value=$pagination.totalPages} {if $totalPages > 1} <div class="pages"> <span> {if $curPage > 1} <a title="Previous Page" href="{$url|replace:'%x':$curPage-1}">&laquo;&laquo;</a> {/if} {* Handle the first part of the pages -- up to the current one *} {if $curPage > $putDots} <a title="Page 1" href="{$url|replace:'%x':'1'}">1</a> ... {section name=i start=$curPage-$border loop=$curPage} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} {else} {section name=i start=1 loop=$curPage} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} {/if} {* Current page *} <a title="Page {$curPage}" class="current" href="{$url|replace:'%x':$curPage}">{$curPage}</a> {* Handle the last part of the pages -- from the current one to the end *} {if $totalPages - $curPage + 1 > $putDots} {section name=i start=$curPage+1 loop=$curPage+$border+1} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} ... <a title="Page {$totalPages}" href="{$url|replace:'%x':$totalPages}">{$totalPages}</a> {else} {section name=i start=$curPage+1 loop=$totalPages+1} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} {/if} {if $curPage < $totalPages} <a title="Next Page" href="{$url|replace:'%x':$curPage+1}">&raquo;&raquo;</a> {/if} </span> </div> {/if}

Friday, August 14, 2009

PHP Server Uptime

This is a pretty simple way of getting the server uptime using PHP. Note that this only works on Linux (and probably other Unix-like OSes that store the machine uptime in /proc/uptime). Straight to the code: function get_uptime() { $file = @fopen('/proc/uptime', 'r'); if (!$file) return 'Opening of /proc/uptime failed!'; $data = @fread($file, 128); if ($data === false) return 'fread() failed on /proc/uptime!'; $upsecs = (int)substr($data, 0, strpos($data, ' ')); $uptime = Array ( 'days' => floor($data/60/60/24), 'hours' => $data/60/60%24, 'minutes' => $data/60%60, 'seconds' => $data%60 ); return $uptime; }

Thursday, August 13, 2009

GeoIP MySQL

A while ago I found this great article on how to import the free GeoIP database into MySQL. It provides a really simple way to look up IPs and see what country they are from using a MySQL database. It is also fairly optimized for size (the GeoIP .csv is 7.9MB and the MySQL tables are 1.9MB). Vincent (the author of the article) also provides some PHP snippets that look up IPs, just to get the feel of it: <?php function getALLfromIP($addr,$db) { // this sprintf() wrapper is needed, because the PHP long is signed by default $ipnum = sprintf("%u", ip2long($addr)); $query = "SELECT cc, cn FROM ip NATURAL JOIN cc WHERE ${ipnum} BETWEEN start AND end"; $result = mysql_query($query, $db); if((! $result) or mysql_numrows($result) < 1) { //exit("mysql_query returned nothing: ".(mysql_error()?mysql_error():$query)); return false; } return mysql_fetch_array($result); } function getCCfromIP($addr,$db) { $data = getALLfromIP($addr,$db); if($data) return $data['cc']; return false; } function getCOUNTRYfromIP($addr,$db) { $data = getALLfromIP($addr,$db); if($data) return $data['cn']; return false; } function getCCfromNAME($name,$db) { $addr = gethostbyname($name); return getCCfromIP($addr,$db); } function getCOUNTRYfromNAME($name,$db) { $addr = gethostbyname($name); return getCOUNTRYfromIP($addr,$db); } ?> If anyone needs this, I have exported the cc and ip tables from the 01-May-09 version of the GeoIP database (it's the latest one at this point in time). Download: geoip.01-May-2009.sql.gz [773.5 KB] Also, here's a little demo application that looks up IPs and/or hostnames: http://znupi.no-ip.org/felix/work/2/ip-lookup/ (which might be offline at times)

Wednesday, August 12, 2009

Ubuntu: Home Sweet Home

Back to Ubuntu from my temporary Windows usage. Man, it's good to be back. Have to go restore all my backups now, buh bye.

Thursday, August 6, 2009

Notepad++ and Python

I know, I know, I'm using Windows. But it's just temporary, I just felt like playing some video games. Anyway, Notepad++ is awesome, I must say. And I wanted to use it for Python developing (I hate that IDLE Python comes with). To set up Notepad++ for Python developing I had to:
  • Create a batch script that would run a python script and then wait for a key (so that the terminal (or, command line) doesn't disappear);
  • Configure a shortcut in Notepad++ to run that script with the current file as parameter.
First things first: the batch script. Pretty basic, I just looked at a couple of Wikipedia articles and another one about using arguments in batch scripts: @ECHO OFF C:\Python26\python.exe "%1" echo. PAUSE @ECHO ON Then, in Notepad++, I went to Run → Run..., type in C:\Python26\python.bat "$(FULL_CURRENT_PATH)", then Save... and assign a shortcut. Press that shortcut now with a python file opened and boom goes the dynamite. Enjoy :)

Rant on Eclipse

If you at least once thought about programming in Java, you must have heard about Eclipse. Well, I'm playing around with the Android SDK, learning Java at the same time. While using Eclipse to code for Android is very easy and straightforward, Eclipse itself is a monstrous bloat of an application. I barely have one project and five files open and Eclipse is using up 400MB of memory. Don't believe me? Check for yourself: Quite annoying. And this is nothing, one time it happened that Eclipse was using 700MB with only a couple of files open. I'm not sure whether this is Eclipse's fault or Java's fault, but I think it's a bit of both. Eclipse's fault is that it's a huge application with tons of features you will never ever use. Java's fault is that it was never designed to run desktop applications (in my opinion). Because Java runs in a virtual machine, it can not give memory back to the host OS, so it just keeps using more and more, until it explodes, creating a black hole in your motherboard where the memory chips used to live. Bah, end of rant.

Wednesday, August 5, 2009

Small bandwidth optimization trick

I've been helping my brother out with his project (http://tastekid.com) and learning some new tricks in the meantime. One thing that bugged me is that we have a lot of JavaScript files, because we like to keep things separated (one file for the tooltip, one for the autocomplete feature etc.). While this makes developing easier, it's pretty bad for production because it drastically increases the number of requests made by a client. The solution I came up with is a small PHP script that concatenates all the scripts in to one and minimizes everything using JSMin (ported to PHP). This reduces the number of requests to one and lowers bandwidth usage. The code is pretty straight forward: <?php /** * JS on-the-fly compressor with caching * * Uses JSMin by Douglas Crockford * * Author: Felix Oghina * */ //-- Configuration --// $JSMin = 'jsmin-1.1.1.php'; // path to the JSMin class file $path = '.'; // this can be made dynamic by assigning a value from $_GET, although it may be unsafe $cache = 'js.cache'; // this file will be used as cache. If it's in the same directory as $path, // it should not end in .js //-- End of configuration --// // include the JSMin script require_once $JSMin; // first decide if we use the cache or not $usecache = true; $files = glob($path . '/*.js'); $maxtime = filemtime($files[0]); foreach ($files as $file) { $curtime = filemtime($file); if ($maxtime < $curtime) $maxtime = $curtime; } if (!file_exists($cache)) { $usecache = false; } elseif (filemtime($cache) < $maxtime) { $usecache = false; } // send appropiate headers header("Content-Type: text/javascript"); // we use the cache if ($usecache) { readfile($cache); } // we rebuild the cache else { $js = ''; foreach ($files as $file) { $js .= file_get_contents($file); } $js = JSMin::minify($js); // rewrite the cache file_put_contents($cache, $js); // output the js echo $js; } // done ?> This solution uses caching, so it only minifies after you change something in one of the JavaScript files. In our case, this reduces the number of requests from 5 to 1 and the total size by 6kb (that's six thousand characters). The only flaw (that I see) is that if you delete one of your JavaScript files, it won't update the cache. Although I see the problem, I don't see an immediate solution, so I won't bother with it. It's not like we're going to delete JavaScript files all the time.