Archive by Author

Bend: Free Minimalist Code & Text Editor

Bend is a minimalistic, aesthetically pleasing code and/or text editor. It was an open source project available on CodePlex, but the author has deleted the project sometime between now and the last time I checked out this cool little app.

Not to mention smooth looks, but it has multiple tabs, line numbers, a javascript beautifier, HTMLTidy, and a fast regular expressions search engine as well!

Bend Free Code & Text Editor

I was able to locate a working set of files from the depths of the internet after a wild goose chase, but I don’t have any of the source files, or the actual installation file (the installer checks home and fails now, anyway) so if any of you have either, please forward them along so we can put them all together!

So, without further ado, download Bend, and cheers!

Lighttpd, PHP, and MySQL on CentOS 5

I’m logging down the installation procedure for a LLMP stack on the CentOS Linux distribution, mostly for my own reference. Because who am I kidding? No one reads this blog anyways. :]

Step 1) Installing & Configuring MySQL

  • yum install mysql mysql-server
  • chkconfig --levels 235 mysqld on && /etc/init.d/mysqld start
    • Sets MySQL to start up when the system boots, and starts the mysql daemon.
  • mysqladmin -u root password your_new_password
    • Set a root password for your MySQL installation.

Step 2) Installing & Configuring Lighttpd

  • If you’re installing on an x86_64 machine:
    wget http://packages.sw.be/rpmforge-release/rpmforge-release-0.3.6-1.el5.rf.x86_64.rpm && rpm -Uvh rpmforge-release-0.3.6-1.el5.rf.x86_64.rpm
  • If you’re installing on an i386 machine:
    wget http://packages.sw.be/rpmforge-release/rpmforge-release-0.3.6-1.el5.rf.i386.rpm && rpm -Uvh rpmforge-release-0.3.6-1.el5.rf.i386.rpm
  • yum install lighttpd
  • chkconfig --levels 235 lighttpd on && /etc/init.d/lighttpd start
    • Sets Lighttpd to start up when the system boots, and starts the Lighttpd daemon.
  • Lighttpd’s default document root is /srv/www/lighttpd, you can use this or change it by editing the configuration file /etc/lighttpd/lighttpd.conf and restarting Lighttpd.

Step 2) Installing & Configuring PHP5 (via FastCGI)

  • yum install lighttpd-fastcgi php-cli
  • echo cgi.fix_pathinfo = 1 >> /etc/php.ini
    • Inserts option to use the real path information for CGI.
  • vi /etc/lighttpd/modules.conf
    • Edit Lighttpd’s module configuration file and uncomment the toggle for mod_fastcgi by removing the hash (#) symbol at the beginning of the line.
  • cp /etc/lighttpd/conf.d/fastcgi.conf /etc/lighttpd/fastcgi.conf-bak
    • Back up the default FastCGI module configuration.
  • vi /etc/lighttpd/conf.d/fastcgi.conf
    • Replace the contents of fastcgi.conf with the following:

      fastcgi.server             = ( ".php" =>
                                     ( "localhost" =>
                                       (
                                         "socket" => "/tmp/php-fastcgi.socket",
                                         "bin-path" => "/usr/bin/php-cgi"
                                       )
                                     )
                                  )

Step 3) Adding MySQL support to the PHP installation

  • yum install php-mysql php-gd php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc

Step 4) Finalizing and testing Lighttpd, PHP, and MySQL stack

  • /etc/init.d/lighttpd restart
    Create a phpinfo.php file in your document root, and browse to /phpinfo.php to verify the installation.

Cheers!

PHP Warning – Failed to Write Session Data

I’ve seen this error pop up a few times, but it usually happens when I choose to install LightTPD instead of Apache.

Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct.

What this means is that the path designated in your php.ini to store session data either does not exist or is not writable by the web server. When you are experiencing this warning from the php backend, the web facing tier of your application most likely has no session persistence at all.

How do we resolve this? Well, you just have to correct your session.save_path variable in your php configuration file, which is usually available at /etc/php.ini, so go ahead and open that up.

Find the line
session.save_path = "/var/lib/php/session"

Replace with (or comment out and add after)
session.save_path = "/tmp"

Once you’ve saved the change to your php configuration, go ahead and reboot your web server. You shouldn’t be experiencing this error anymore.

Mobile Browser Detection With PHP

You’ll find (or maybe you might not) that there are several ways to detect what mobile browser, if any, is currently being used to access a website, but the simplest is just to use regular expressions to parse the HTTP_USER_AGENT variable. I’ll demonstrate simple mobile browser detection using PHP, and to make things even more pleasant, we’ll only be trying to detect if the user is browsing on an iPad.

$accept = $_SERVER['HTTP_ACCEPT'];
$agent = $_SERVER['HTTP_USER_AGENT'];
$target = 'ipad'

if(preg_match('/ipad/i', $agent)) {
echo "All hail Mapple!";
} else {
echo "Wouldn't you like to be on the bandwagon? Come drink the Kool-Aid!";
}

Now that you know how the core of this is going to work, here is a function that will detect what mobile browser is being used, or return false if it’s not a mobile browser.

function detectmobile() {
$accept = $_SERVER['HTTP_ACCEPT'];
$agent = $_SERVER['HTTP_USER_AGENT'];
$result = false;

switch($agent) {
case preg_match('/ipad/i', $agent):
$result = 'iPad';
break;
case preg_match('/(iphone|ipod)/i', $agent):
$result = 'iPhone / iPod Touch';
break;
case preg_match('/android/i', $agent):
$result = 'Android';
break;
case preg_match('/blackberry/i', $agent):
$result = 'Blackberry';
break;
case preg_match('/(pre\/|palm)/i', $agent):
$result = 'Palm';
break;
}

return $result;

}

By the way, if you thought Mapple was a typo, I’m actually referring to the Simpsons episode Mypods and Boomsticks.

Resize Animated GIFs and Preserve Animation

Resizing images is fairly simple, whether you are resizing images from the command line or in the programming language of your choice, but when it comes to animated GIFs (or other animated image type) things can get hairy, especially when most techniques will simply resize the first frame of your image, while destroying the rest of the animation frames.

Circumventing this is easy (if you have ImageMagick installed) and the resizing is performed with simple commands:
convert big.gif -coalesce coalesce.gif
convert -size 200x100 coalesce.gif -resize 200x10 small.gif

If you want to perform this action in PHP, you’d do something like this:
system("convert big.gif -coalesce coalesce.gif");
system("convert -size 200x100 coalesce.gif -resize 200x10 small.gif");

User’s Note: Resizing animated GIFs this way usually creates a larger file size, even though the dimensions are smaller, this is because during the resize process, the images in each frame are de-optimized.

Source: http://stackoverflow.com/questions/718491/resize-animated-gif-file-without-destroying-animation

Microsoft Migrates Live Spaces to WordPress

In retrospect, it’s not as shocking as it first seemed that Microsoft has chosen to ditch their efforts to create a social blogging solution from scratch for an already enterprise choice, but I must say it is still just as funny.

Still, I was definitely surprised earlier when Terry Chay tweeted about the move of some 30 million ‘Spaces’ from Microsoft’s own platform to WordPress.

One of the cool things we’ll all get to see from this relationship is the integration between WordPress and Windows Live Messenger. You’ll be able to ping (notify) your instant messaging friends when a new post occurs, or a comment of theirs is replied to; among other things.

You’ll have about six months longer to play with the Windows Live Spaces platform, during which Microsoft will be transitioning existing Spaces over to a WordPress solution. Users being migrated will be able to download a backup of their existing Space, and/or delete their blog entirely.

PHP: Detect if Script is Running from Console

I hacked together a script to import a large amount of data from a CSV file that was not very fun to work with, and I felt the need to restrict access to the command line in case there was any extra overhead in the script, or anything unforeseen quirks from running such an intensive script in the browser. (I didn’t really have to restrict access as I removed the import script after it was run, but now I needed to know.) Somehow, I needed to capture information about exactly HOW the script was being executed. It’s actually quite simple, PHP captures your console environment into a $_SERVER variable, specifically $_SERVER['CONSOLE'] and/or $_SERVER['TERM'].

if(!isset($_SERVER['SHELL'])) { echo "Please run this script from the console."; exit; }

[Edit: Shortly after posting the above snippet, I found a PHP function (see: php_sapi_name) that returns the type of interface between the web server and PHP. In this case, we are looking for it to return the value 'cli' for command line interface.]

if(php_sapi_name() != 'cli') {  echo "Please run this script from the console."; exit; }

The Hacker’s Code

“A hacker of the Old Code.”

  • Hackers come and go, but a great hack is forever.
  • Public goods belong to the public.*
  • Software hoarding is evil. Software does the greatest good given to the greatest number.
  • Don’t be evil.
  • Sourceless software sucks.
  • People have rights. Organizations live on sufferance.
  • Governments are organizations.
  • If it is wrong when citizens do it, it is wrong when governments do it.
  • Information wants to be free. Information deserves to be free.
  • Being legal doesn’t make it right.
  • Being illegal doesn’t make it wrong.
  • Subverting tyranny is the highest duty.
  • Trust your technolust!

* Definition: A good is public if the marginal production cost is lower than the marginal billing cost.

AdSense Earnings RSS Feed

/*
Hack Name: Adsense to RSS
Version: 1.1
Hack URI: http://planetozh.com/blog/my-projects/track-adsense-earnings-in-rss-feed/
Description: Follow your Adsense earnings with an RSS reader
Author: Ozh
Author URI: http://planetOzh.com
*/

/*
 * Release History
 *
 * 1.1 (04/23/2006 - CGibson)
 * Fixed to work with recent modifications to Google AdSense
 * - Changed "csv" post data field to "outputFormat"
 * - Changed spliting of date from "/" to "-"
 *
 * 1.0 (10/07/2005)
 * Initial Release
*/

/************ SCRIPT CONFIGURATION ***********/
/*********************************************/

$username="you@email.com";
    // your adsense username

$password="MySuPeRpAsSwOrD";
    // your adsense password

$daterange = 20 ;
    // range of days to aggregate in RSS reader

$cookie="./.cookiefile";
        // a temp file name - you mostly don't care about this
        // This will create a hidden file in the current directory. If it seems to fail,
        // replace with a full physical path (i.e. /home/you/temp/cookiefile)

/************ DO NOT MODIFY BELOW ************/
/*********************************************/

$daysbefore = mktime(0, 0, 0, date("m") , date("d") - $daterange, date("Y"));
list ($d_from,$m_from,$y_from) = split(':',date("j:n:Y", $daysbefore));
list ($d_to,$m_to,$y_to) = split(':',date("j:n:Y"));

/* Following lines are based on a script found on WMW forums */
/* http://www.webmasterworld.com/forum89/5349.htm */

$destination="/adsense/report/aggregate?"
    ."sortColumn=0"
    ."&reverseSort=false"
    ."&outputFormat=TSV_EXCEL"
    ."&product=afc"
    ."&dateRange.simpleDate=today"
    ."&dateRange.dateRangeType=custom"
    ."&dateRange.customDate.start.day=$d_from"
    ."&dateRange.customDate.start.month=$m_from"
    ."&dateRange.customDate.start.year=$y_from"
    ."&dateRange.customDate.end.day=$d_to"
    ."&dateRange.customDate.end.month=$m_to"
    ."&dateRange.customDate.end.year=$y_to"
    ."&unitPref=page"
    ."&reportType=property"
    ."&searchField="
    ."&groupByPref=date";

$postdata="destination=".urlencode($destination)."&username=".urlencode($username)."&password=".urlencode($password)."&null=Login";

$ch = curl_init();
curl_setopt ($ch, CURLOPT_URL,"https://www.google.com/adsense/login.do");
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt ($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
curl_setopt ($ch, CURLOPT_TIMEOUT, 20);
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt ($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt ($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt ($ch, CURLOPT_POST, 1);
$result = curl_exec ($ch);
curl_close($ch); 

$result=preg_split("/\n/",$result);
array_pop($result);
array_pop($result);
array_shift($result);
$result = array_reverse($result);

header('Content-type: text/xml');
echo '';
echo "\n";
echo '\r\n";
echo "\r\n";
echo "\t\r\n";
echo "\t
https://www.google.com/adsense/\r\n";
echo "\tAn RSS feed of my Adsense earnings for the last " . $daterange . "days\r\n";
echo "\ten\r\n";

$firstday=1;
foreach ($result as $line) {
	$item = array();
	$line = str_replace("\x00",'',$line);
	$line = str_replace('"','',$line);
	list($day, $pages, $clicks, $ctr, $eCPM, $income) = preg_split("/\s/",$line);
	$item['title']= "";
	$item['guid'] = '' . md5($username.$day) . "";
	$day = split('-',$day);
	$day = mktime(0, 0, 0, $day[1] , $day[0], $day[2]);
	if ($firstday == 1) {
		$day = date("D, d M Y H:i:s +0000");
		$firstday = 0;
	} else {
		$day = date("D, d M Y H:i:s +0000", $day);
	}
	$item['pubDate'] = "
$day";
	$item['category'] = "";
	$item['description'] = "\$$income ($clicks clicks on $pages pages : CTR = $ctr - eCPM = $eCPM)";
	$item['content'] = "\r\n\t

Pages printed
Clicks
CTR
eCPM
Earnings


\r\n\t

$pages
$clicks
$ctr
$eCPM
$income


\r\n\t

\r\n]]>\r\n";

	print "\r\n";
	print $item['title'] ."\r\n";
	print $item['guid'] ."\r\n";
	print $item['pubDate'] ."\r\n";
	print $item['category'] ."\r\n";
	print $item['description'] ."\r\n";
	print $item['content'] ."\r\n";
	print "\r\n";
}

echo "\r\n";
echo "";

LiveJournal Image Search

/*
	lj.php - yet another LiveJournal image getter thingy
		by Ryland Sanders
		Friday, November 24, 2006
		This source code is released into the public domain, copy it and modify it as you see fit. A link and credit would be nice, though.
		This script gets the "latest images" XML stream from LiveJournal, parses it, filters it, and displays the resulting list as clickable links, limited to the number of images specified by the $limit parameter in the query string.
*/

echo "\r\n";
echo "\r\n";
echo "\t\r\n";
echo "\t


\r\n";
echo "\r\n";
echo "\r\n";
echo "\r\n";
echo "

LiveJournal images

\r\n"; echo '
LiveJournal images XML feed :: PHP source code for this page ' . "\r\n"; /* put script file name of this page in a variable (just in case you change the filename but forget to update the script with the new name) */ $thispage = getenv("SCRIPT_NAME"); /* get the 'limit' parameter from the query string. if the server is configured with register_globals turned on, it does this for you automatically, but I like to add zero to numerical values from the query string to make sure it's a number. if there's no 'limit' paramter in the query string, set it to the default, 100 images. */ $limit = $_GET['limit'] + 0; if (!$limit) { $limit = 100; } echo " Number of images: " . $limit . " - Limit to: 25 :: 50 :: 100 :: 250\r\n"; echo "   \r\n"; /* this is a callback function used by array_filter() below to filter our any elements that aren't tagged as 'RECENT-IMAGE' in LJ's xml stream */ function is_img($recent_image) { return $recent_image['tag'] == 'RECENT-IMAGE'; } /* open the stream and assign it to a file handle variable */ $handle = fopen("http://www.livejournal.com/stats/latest-img.bml", "r"); /* dump the stream contents into a string variable */ $ljxml = ""; while (!feof($handle)) { $ljxml .= fgets($handle); } /* close the stream */ fclose($handle); /* create an XML parser object */ $p = xml_parser_create(); /* parse the stream data into an array $vals */ xml_parse_into_struct($p, $ljxml, $vals); /* free the parser object, since we're done with it */ xml_parser_free($p); /* filter the $vals array using the callback function above */ $imgs = array_filter($vals, "is_img"); /* get a subset of the array limited to the number of images in the $limit variable */ if ($limit < 250) { $imgs = array_slice($imgs, 0, $limit); } /* loop through the array and print each array element as a clickable link */ $num_imgs = sizeof($imgs); $ct = 1; foreach ($imgs as $recent_image) { echo " LJ post URL ::\r\n"; echo "Image URL ::\r\n"; if ($ct > 1) { echo "Previous image ::\r\n"; } else { echo "Previous image ::\r\n"; } if ($ct < $num_imgs) { echo "Next image ::\r\n"; } else { echo "Next image ::\n"; } echo "Top of page\r\n"; echo "\r\n \r\n\r\n"; $ct++; } echo "\r\n"; echo "\r\n";