Archive for the 'howto' Category

Feb 7, 2009

HOWTO: PHP image resize, centered and cropped

Author: gaweee | Filed under: development, howto

Sorry for not posting in such a long time folks, i just came back from the Pegasus galaxy. While working on a Joomla! 1.5 project, we had to develop a function to crop and resize images for obvious content management and aesthetic reasons. So i gathered around some good sources on PHP.net and wrote the following class:

class ImageHelper {
 
	static function treatFilename($filename) {
		$newfilename = strtolower($filename);
		$newfilename = str_replace(" ","_",$newfilename);
		return $newfilename;
	}
 
	static function isPotrait($srcimage) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		return !ImageHelper::isLandscape($srcimage);
	}
 
	static function isLandscape($srcimage) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		$size	= getimagesize( $srcimage );
		return ($size[0] > $size[1]);
	}
 
	static function resizeImage($srcimage, $destimage, $width, $height) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		$srcpathinfo 	= pathinfo($srcimage);
		$srcext 		= strtolower($srcpathinfo['extension']);
		$destpathinfo 	= pathinfo($destimage);
		$destext 		= strtolower($destpathinfo['extension']);
		$size 			= getimagesize( $srcimage );				// Get the size of the original image into an array [0]=> width, [1]=> height
		$image			= null;
		$canvas 		= imagecreatetruecolor( $width, $height );	// Prepare canvas
 
		// Create a new image in the memory from the file 
		switch ($srcext) { 
			case 'wbmp':
			case 'bmp':
				$image = imagecreatefromwbmp($srcimage);
				break;
			case 'jpg':
			case 'jpeg':
				$image = imagecreatefromjpeg($srcimage);
				break;
			case 'png':
				$image = imagecreatefrompng($srcimage);
				break;
			case 'gif':
				$image = imagecreatefromgif($srcimage);
				break;
			case 'xpm':
				$image = imagecreatefromxpm($srcimage);
				break;
			default:
				return;
		}
 
		// Calculate dimensions
		$widthratio		= $size[0]/$width;
		$heightratio	= $size[1]/$height;
		$dimensions 	= array(
						'ratio' 			=> $widthratio,
						'source_cropwidth' 	=> 0,
						'source_cropheight' => 0,
						'source_offsetx' 	=> 0,
						'source_offsety' 	=> 0
						);
 
		if ($heightratio < $widthratio)
			$dimensions['ratio'] 			= $heightratio;
 
		// let say image is 1200*800, then:
		// widthratio = 1200/400 = 3
		// heightratio = 800/300 = 2.66
		// since there is less height than width, the max scale we can do is 2.66, then
		// the targetwidth to crop = 400 * 2.66
		// the targetheight to crop = 300 * 2.66
		// the offset width = (1200 - (2.66 * 400))/2 = 68
		// the offset height = (800 - (2.66 * 300))/2 = 1
 
		$dimensions['source_width'] 		= $size[0];
		$dimensions['source_height'] 		= $size[1];
		$dimensions['source_cropwidth'] 	= $width * $dimensions['ratio'];
		$dimensions['source_cropheight'] 	= $height * $dimensions['ratio'];
		$dimensions['source_offsetx'] 		= ($size[0] - $dimensions['source_cropwidth']) / 2;
		$dimensions['source_offsety'] 		= ($size[1] - $dimensions['source_cropheight']) / 2;
 
		imagecopyresampled($canvas, $image, 0, 0, $dimensions['source_offsetx'], $dimensions['source_offsety'], $width, $height, $dimensions['source_cropwidth'], $dimensions['source_cropheight']);
 
		switch ($destext) {
			case 'jpg':
			case 'jpeg':
				imagejpeg( $canvas, $destimage );
				break;
			case 'png':
				imagepng( $canvas, $destimage );
				break;
			case 'gif':
				imagegif( $canvas, $destimage );
				break;
			case 'wbmp':
			case 'bmp':
				imagewbmp( $canvas, $destimage );
				break;
		}
 
		imagedestroy( $canvas );
		imagedestroy( $image );
 
		return true;
	}
}
use treatFilename to remove those pesky spaces, then use isPotrait or isLandscape to determine the orientation of the picture, then perform your resize accordingly. If your uploaded file input was called ulimage, then the code to resize your image would be:

	$width 		= 150;
	$height 		= 150;
	if (is_uploaded_file(@$_FILES['ulimage']['tmp_name'])){
		$targetfilename	= ImageHelper::treatFilename(uniqid() . "_" . $_FILES['ulimage']['name']);
		move_uploaded_file($_FILES['ulimage']['tmp_name'], dirname(__FILE__) . "/tmp/" . $_FILES['ulimage']['name']);
		ImageHelper::resizeImage(dirname(__FILE__) . "/tmp/" . @$_FILES['ulimage']['name'], dirname(__FILE__) . "/tmp/" . $targetfilename, $width, $height);
	}

The reason you need to save the file first is for the imageResize function to infer the image type off its extension. I hadnt bothered to create an image type override. :D

Get the source here: ImageHelper.php
or view the demo here
Jun 25, 2008

HOWTO: PHP and jQuery upload progress bar

Author: gaweee | Filed under: development, howto

With the controllable jQuery Progress Bar, writing a form upload progress bar seems like a piece of cake now. Hypothetically, all we need is to create the bar, poll for the progress of the file upload, derive the new progress bar value (in percentage) and set it.

To do that you need to prepare the php script to do it. By default PHP cant report the progress of upload progress. However people smarter than me have already solved that problem. In 5 mins i’ve found 2 solutions: the Alternative PHP Cache (APC) method as well as the UploadProgress method. Both of them are PECL packages. Because i couldnt get APC to work on my server properly, i’ll document the UploadProgress more in detail here…

Step 1: Install the uploadprogress package. Really simple just run the following command  

pecl install uploadprogress

Once that is done, register the extension to your PHP with the following line in your php.ini

extension=uploadprogress.so

then restart your apache/httpd

Step 2: Create the form and your progress bar  

<form id="uploadform" enctype="multipart/form-data" method="post">
<input id="progress_key" name="UPLOAD_IDENTIFIER" type="hidden" value="&lt;?= $uuid ?&gt;" />
<input id="ulfile" name="ulfile" type="file" />
<input type="submit" value="Upload" />
	<span id="uploadprogressbar" class="progressbar">0%</span>
</form>

this creates the form with a file field as well as a unique UPLOAD_IDENTIFIER hidden field that allows our script to check the progress of the form submission.

Step 3: Next the script itself to check the respond with the progress of the form submission. Lets call this file uploadprogress.php  

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
if (@$_GET['id']) {
	echo json_encode(uploadprogress_get_info($_GET['id']));
	exit();
}

The header no-cache declarations circumvents IE’s cache of the response. Basically this form does nothing but respond with a json encoded string of the uploadprogress_get_info function. The id argument is the same one we used in the form. Think of it as a form-upload-process-id. A typical response looks like this:

{"time_start":"1214384364","time_last":"1214384366","speed_average":"25889","speed_last":"40952","bytes_uploaded" :"51778","bytes_total":"8125518","files_uploaded":"0","est_sec":"311"}

the response encodes a good deal of data about the form submission. most importantly for us: bytes_uploaded and bytes_total

Step 3: use jQuery and a timer to keep polling the page and update the progress bar value  

var progress_key = '';
 
// this sets up the progress bar
$(document).ready(function() {
	$("#uploadprogressbar").progressBar();
});
 
// fades in the progress bar and starts polling the upload progress after 1.5seconds
function beginUpload() {
	$("#uploadprogressbar").fadeIn();
	setTimeout("showUpload()", 1500);
}
 
// uses ajax to poll the uploadprogress.php page with the id
// deserializes the json string, and computes the percentage (integer)
// update the jQuery progress bar
// sets a timer for the next poll in 750ms
function showUpload() {
	$.get("uploadprogress.php?id=" + progress_key, function(data) {
		if (!data)
			return;
 
		var response;
		eval ("response = " + data);
 
		if (!response)
			return;
 
		var percentage = Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
		$("#uploadprogressbar").progressBar(percentage);
 
	});
	setTimeout("showUpload()", 750);
}

viola! read the comments if you dont understand the code. it is _THAT_ straightforward. Of course there can be many improvements such as stopping the script when the upload reaches 100% but thats probably not really needed since the whole page is refreshed. But this approach allows the flexibility of ajax submissions and what nots.

Again, download the jQuery progressbar here: jQuery progressbar
or view the demo here

Note: for the APC solution seekers out there, assuming you can get APC to work, the solution is not so different. A few changes will get your there:  

  • Change the HTML hidden form field name from UPLOAD_IDENTIFIER to APC_UPLOAD_PROGRESS
  • Change the PHP uploadprogress_get_info($_GET['id']) to apc_fetch(‘upload_’.$_GET['id']));
  • Change the Javascript percentage calculation from:
    Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
    to:
    Math.floor(100 * parseInt(response['current']) / parseInt(response['total']));
Jun 23, 2008

HOWTO: struts 2 i18n

Author: gaweee | Filed under: development, howto

i18n in struts is really flexible if set up correctly. Unfortunately searching for a document to learn the ‘hidden’ tricks of it is really frustrating… So in revenge, i’m writing my own! I hope someone out there finds this useful…

Step 1: Edit your struts.properties file – Add/Edit the following line:

struts.custom.i18n.resources=languages_actions,languages_views

This definition instructs struts to use the files languages_actions.properties and languages_view.properties for your language definitions. When there is a new request locale, such as zh_CN (chinese), struts will find for the file languages_actions_zh_CN.properties, or if defaults to en_US (english), struts will use the file language_actions_en_US.properties.
On top of that, there can be many places where language definitions can be found. They will be searched in the following order:

  1. ActionClass.properties
  2. BaseClass.properties (all the way to Object.properties)
  3. Interface.properties (every interface and sub-interface)
  4. ModelDriven’s model (if implements ModelDriven), for the model object repeat from 1
  5. package.properties (of the directory where class is located and every parent directory all the way to the root directory)
  6. search up the i18n message key hierarchy itself
  7. global resource properties

I put my files in the main WEB-INF\classes folder where struts.properties can be found
Read here for more information on how struts 2 searches for i18n definitions.

Step 2: Writing the language file itself

user.submit.image=/images/en_US/submit.gif
user.welcome=Welcome, {0}
user.link=<a href="somelink.action?id={1}&amp;token={2}">{0}</a>' document
user.validation.token.invalid={0} does not exists in the system

the above is an example of a en_US language definition file. You can see how the image and welcome message would be different in a zh_CN language definition file. but just for the heck of it, here it is anyway

user.submit.image=/images/zh_CN/submit.gif
user.welcome={0}, 你好
user.link=<a href="somelink.action?id={1}&amp;token={2}">{0}</a> 的文件
user.validation.token.invalid={0} 不纯在于系统

The key here in flexibility is the use of # parameters for the definitions (very printf-ish). This allows the dynamic data to be reformatted to any language structure (left to right, right to left, orientation of grammer, verbs, subject, topics, etc). Really smart way to do it!

Step 3: Using the definitions:
in JSP,

<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<h1><s:text name="user.welcome">
	<s:param value="user.name" />
</s:text></h1>
<s:text name="user.link">
	<s:param value="document.name" />
	<s:param value="document.id" />
	<s:param value="document.token" />
</s:text>
<form input="">
	<s:submit type="image" src="%{getText('user.submit.image')}" cssClass="submit" />
</form>

Or in Java,

public class SomeActions extends ActionSupport {
	public String someMethod() throws Exception {
		if (!token.equalsIgnoreCase("abcdefghi")) {
			String [] messageargs = { token };
			addActionError(getText("user.validation.token.invlaid", messageargs));
		}
	}
 
	private String token;
	public String getToken() 	{ return  token; }
	public void setToken(String s) 	{ token = s; }
}

The above examples goes to show how you can combine dynamic data with the i18n strings in a JSP or a Java file

Tips

  • When using a utf8 language definition file (such as for zh_CN), you have to write out your files then run it through the native2ascii program. On windows it would look like this:
    "c:\Program Files\Java\jdk1.6.0_04\bin\native2ascii.exe" -encoding UTF8 \path\to\zh_CN.locale \new\path\to\languages_actions_zh_CN.properties

    Change your jdk\bin directory to where appropriate

  • Flood HTML with s:text language definitions – If you’ve already decided to make your app internationalized, its a big step, so make sure everything is pulled off a dynamic language pack.
  • Always use the s:date taglib to represent date, save yourself all the time (and frustration) in the world
  • Set up your default language definition file first. Struts makes it such that if your definition doesn’t exists then it searches the default file for it. That is to say if your definition for user.link doesn’t exists in languages_actions_zh_CN.properties, struts will continue to find for it in languages_actions.properties so at least there will be a link there, even if its untranslated.
Jun 18, 2008

HOWTO: Scheduled Remote MySQL Backups

Author: gaweee | Filed under: development, howto

If you have more access to more than 1 server, a good idea would be to schedule remote backups using a small script and crontab. This way, if 1 server goes down at least SOME data still resides on another. (a friend’s hosting service got prematurely terminted recently because someone else sharing a hosting plan was serving illegal content and the whole system was confiscated by the police in that country!)

of course backing up mysql is the only thing you can do, unless you’ve way too much disk space and way too much bandwidth… in that case please consult a professional on the actual 1001 ways to throw money at redundancy.

The script will first run remote commands on the target server over ssh to dump and gzip the database into a file. The file is then downloaded to the backup server. A final ssh command removes the dump file from the target server. Viola! And here is the shell script…

ssh root@wits.sg 'mysqldump -uroot twits> /tmp/twits.backup.sql && bzip2 -9 /tmp/twits.backup.sql'
scp root@wits.sg:/tmp/twits.backup.sql.bz2 /var/backups/twits/`date "+%Y%m%d-%H%M"`.sql.bz2
ssh root@wits.sg 'rm /tmp/twits.backup.sql.bz2'

Save this few lines to a file on the backup server and give it execute permissions! (I saved it at /root/twits_backup.sh)
Note: Passwordless logins between the 2 servers must have been setup prior to running this script. Otherwise scp and ssh commands will both be prompted for a password.

Next set up the cron job on the backup server to run the script

30 05 * * * /root/twits_backup.sh

This sets the script to run at 5:30am everyday.
Here is a good place to read up more on cron jobs.

Jun 17, 2008

HOWTO: Did you mean…

Author: gaweee | Filed under: development, howto

I always thought it was really cool to have those “Did you mean <some other spelling corrected word>”? Took me long enough to finally chance upon how it works! Basically it uses sound functions: soundex, metaphone or levenshtein distance to match words.

Different database supports different possible implementations of the functions. A sample mySQL query would look like:

SELECT `name` FROM `organizations` WHERE SOUNDEX(`name`) = SOUNDEX('dog');

I’ve also seen some mysql stored procs that hack in a levenshtein distance calculation algorithm. This allows a even more dynamic and accurate match. Unfortunately, i’ve never gotten it to work. Do let me know if anyone has succeeded.

Jun 17, 2008

HOWTO: Fix EXTJS Textarea for IE6

Author: gaweee | Filed under: development, howto

I encountered a interesting display bug when working with EXTJS recently. The following code renders well in FF 2 and above but not in IE6.

var form = new Ext.form.FormPanel({
	baseCls: 'x-plain',
	labelWidth: 140,
	url: 'abc/def.php',
	defaultType: 'textfield',
	items: [{
		fieldLabel: 'Name',
		name: 'name',
		anchor:'100%'  // anchor width by percentage
	}, {
		xtype: 'textarea',
		fieldLabel: 'Description',
		name: 'description',
		anchor: '100% -30'  // bug in IE6
	}]
});
 
var window = new Ext.Window({
	title: 'Some Window',
	width: 500, height: 300, 
	layout: 'fit',
	plain: true,
	buttonAlign: 'center',
	items: form
});
 
window.show();

brian.moeskau from the EXTJS forums was kind enough to answer this problem. The fix is a piece of code that primes the width of the controls to prevent it from overflowing out of the window/panels. The code is as follows

Ext.override(Ext.form.Field, {
    adjustWidth : function(tag, w){
        tag = tag.toLowerCase();
        if(typeof w == 'number' && !Ext.isSafari){
            if(Ext.isIE && (tag == 'input' || tag == 'textarea')){
                if(!Ext.isStrict){
                    return this.inEditor ? w : w - 3;
                }
                if(tag == 'input' && Ext.isStrict){
                    return w - (Ext.isIE6 ? 4 : 1);
                }
                if(tag == 'textarea' && Ext.isStrict){
                    return w-4;
                }
            }else if(Ext.isOpera && Ext.isStrict){
                if(tag == 'input'){
                    return w + 2;
                }
                if(tag == 'textarea'){
                    return w-2;
                }
            }
        }
        return w;
    }
});

I hope this will solve other people’s problems too!

Jun 17, 2008

HOWTO: Codeigniter, IIS and ISAPI_Rewrite

Author: gaweee | Filed under: development, howto

Pretty URLs on IIS is possible. Not so much to make it pretty but to make it convenient when testing your system. Its really simple too!

Step 1 – Install ISAPI_Rewrite from Helicon
Step 2 – Configure your ISAPI_Rewrite httpd.ini
below is my configuration for a codeigniter virtual directory called firingrange

RewriteRule ^/firingrange/(images|css|files|js)(.*)$ /firingrange/webroot/$1$2 [L]
RewriteRule ^/firingrange/$ /firingrange/index.php [L]
RewriteRule ^/firingrange/index.php$ /firingrange/index.php [L]
RewriteRule ^/firingrange/index.php?(.*)$ /firingrange/index.php?$1 [L]
RewriteRule ^/firingrange/(\w+).php$ /firingrange/webroot/($1).php [L]
RewriteRule ^/firingrange/(.+)\?(.*)$ /firingrange/index.php?$1&amp;$2 [L]
RewriteRule ^/firingrange/(.*)$ /firingrange/index.php?$1 [L]

Just like the mod_rewrite HOWTO, I threw all my static files into webroot folder so that you can access the site via http://t.wits.g/images/myimage.jpg easily. Same goes for css and js.

Jun 15, 2008

HOWTO: Passwordless logins between servers

Author: gaweee | Filed under: development, howto
Before i get into this HOWTO, its worth noting that the use of passwordless logins can be really dangerous when it applies on an organizational level. Some organizations do not have any operating procedures in place to remove inactive employee accounts much less bother to check for ssh authorized keys. That being said, i’ve found this technique tremendously useful in creating backup server relations between one or more servers.
So if you’ve properly weighted your pros and cons, here we go…
Step 1 – generate your RSA/DSA keypair

gaweee@wits:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/gaweee/.ssh/id_rsa): [enter]
Enter passphrase (empty for no passphrase): [enter]
Enter same passphrase again: [enter]
Your identification has been saved in /home/gaweee/.ssh/id_rsa.
Your public key has been saved in /home/gaweee/.ssh/id_rsa.pub.
The key fingerprint is:
2c:1f:fb:f0:ae:2b:88:99:60:ee:eb:a5:83:3c:3c:c4 gaweee@wits

Why? Distribution of these keys is crucial to logging into the remote server without having to key in your password.
The 2 files created: /home/gaweee/.ssh/id_rsa and /home/gaweee/.ssh/id_rsa.pub are your private and public keys respectively. Keep your private key to yourself ONLY.

Step 2 – Distribute the public key to target servers

gaweee@wits:~$ scp .ssh/id_rsa.pub somenewserver:.ssh/authorized_keys
gaweee@somenewserver password: [password]

this uses scp to put the public key onto the new server’s authorized_keys.
Viola! and you’re done!

Jun 15, 2008

HOWTO: Migrate your svn repository between servers

Author: gaweee | Filed under: development, howto
Step 1 – Backup your svn data:

svnadmin dump /path/to/somerepository &lt; somerespositry.svn.backup

The resulting file can be pretty large. Its basically a file of all your snapshots. Needless to say, if you’re at revision 300, be prepared to wait.

Step 2 – Create the new respository:
login to the new server to create the repository

svnadmin create /path/to/newrespository

Double check the created directory structure where possible, many a times my svn folders arent created with the correct group write permissions. So remote commits encounters transaction errors

Step 3 – Restore the repository on the new server

svnadmin load /path/to/newrepository < somerepository.svn.backup

And you’re done! really simple wasnt it?

Jun 13, 2008

HOWTO: Codeigniter and mod_rewrite

Author: gaweee | Filed under: development, howto

I cant remember the last time i got mod_rewrite to just work. I think i know regex, i think i know how http request works, i think i know some mod_rewrite. I try. For sure it breaks. This is undeniable proof that good things ONLY come to those who sweat and strain their necks…

The following is my mod_rewrite ruleset for codeigniter

Options +FollowSymlinks
RewriteEngine on
 
RewriteCond %{REQUEST_URI} (index\.php|webroot/|images/|css/|js/|robots\.txt|favicon\.ico)
RewriteRule ^(images|css|files|js)/(.*)$ webroot/$1/$2 [L]
RewriteRule ^(favicon\.ico)$ webroot/$1 [L]
 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

I threw all my static files into webroot folder so that you can access the site via http://t.wits.g/images/myimage.jpg easily. Same goes for css and js.
So now you can access your controllers through the preferred pretty url: http://t.wits.sg/welcome
I’ll go hack the router.php file someday when i’m free enough so that it can accept GET requests too.