Raam Dev’s Weblog

Avatar

"If no one ever reinvented the wheel, we’d be rolling around on stone tires now." - Author Unknown

  • Sun Microsystems has added PHP support to their open-source Netbeans development IDE. I just tried the latest version (6.5) and I’m not impressed at all, at least with their OS X version: It’s slow and the Open File dialog takes a good 45 seconds (!) to load. (0)

Multiple Query Problems with mysql_query()

I was writing some code earlier today that involved writing data to two separate MySQL tables. The second INSERT statement needed to contain the automatically generated ID (auto_increment) of the first INSERT statement, so I wanted all the queries to run one after another.

Thinking it made the most sense to just build one long query and execute it all at once, I wrote code similar to the following:

// Build a query with multiple INSERT statements
$q = "INSERT INTO sessions VALUES(NULL, '$name', '$desc', '$stime');";
$q .= "INSERT INTO events VALUES(LAST_INSERT_ID(), '$event', '$e_desc');";

// Execute query
mysql_query($q, $conn) or die(mysql_error());

Upon running the code I received this error:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '; INSERT INTO events VALUES(LAST_INSERT_ID(), '24', 'my event1', 'button')' at line 1

So, as I normally do when this kind of problem arises, I echoed the query that was being executed and, hoping to get more information on the error, I ran it directly from phpMyAdmin. Here is the SQL I ran:

INSERT INTO sessions
	VALUES(NULL, 'Raam', 'example', '2008-04-24 21:59:08');
INSERT INTO events
	VALUES(LAST_INSERT_ID(), '24', 'my event1', 'button');

phpMyAdmin says:

Your SQL query has been executed successfully

OK, so my SQL is fine.

I then looked up the mysql_query() function on php.net and found this little tidbit of info:

mysql_query() sends an unique query (multiple queries are not supported) to the currently active database on the server that’s associated with the specified link_identifier .

Ah, so multiple queries are not supported with the mysql_query() function. That’s most likely a security feature, but quite annoying none the less. The bottom line is, you cannot run multiple queries with mysql_query().

PHP5 has the mysqli_multi_query() function, which does allow you to run multiple queries (I know, I know, I should be coding for PHP5 by now).

Adding CC Recipients With PEAR Mail

I use the PEAR Mail package quite often in projects that require sending email — either user-generated or system-level notification emails. I recently wrote something at work that required CCing the user a copy of the email. My first thought was that simply adding CC headers with the users’ email address would suffice, but that just isn’t the case.

Since mail headers can be modified to state anything you want, PEAR Mail doesn’t actually use them to to figure out where to send the email (adding the CC header works fine and the users’ email address even shows up in the CC field, but they never receive the email).

A comment by Armin Frey that I found on the PEAR bug page for this problem explains what’s going on and offers a solution:

[2007-07-06 15:22 UTC] arminf (Armin Frey)

It seems that the Recipients decides where to send the e-mail and the
headers decide how to display it.

The simple solution is that you add all the addresses to $recipients.

Here is the code I used:

$to = 'to@example.com';
$cc = 'cc@example.com';
$recipients = $to.", ".$cc;
$headers['From']    = 'from@example.com';
$headers['To']      = $to;
$headers['Subject'] = 'Test message';
$headers['Cc']	    = 'cc@example.com';
$headers['Reply-To'] = 'from@example.com';

$send = $mail->send($recipients, $headers, $body);

The solution works perfectly. Now the email addresses show up in the correct fields and all the recipients receive the email. Unfortunately, this method does not work for BCCing users. I wonder if BCCing is even possible with PEAR Mail or if I’ll need to find something else. To Blind CC (aka, BCC) an address, simply add the address to the $recipients, but not to any of the $headers (thanks Jason!).

Continuous Integration and PHP

Phing: Building with PHP

Amazon S3 HMAC Signatures without PEAR or PHP5

The Amazon S3 proposal for uploading via POST describes how to assemble a policy document that can be used to create a time-sensitive signature. The obvious advantage to this method is that you don’t have to worry about someone stealing your secret AWS key or uploading random files without your permission.

Here is the example policy document from the proposal:

{ "expiration": "2007-12-01T12:00:00.000Z",
  "conditions": [
    {"acl": "public-read" },
    {"bucket": "johnsmith" },
    ["starts-with", "$key", "user/eric/"],
    ["content-length-range", 2048, 20971520]
  ]
}

This Policy document is Base64 encoded and the Signature is the HMAC of the Base64 encoding.

The application I am developing at work requires this signed policy method of uploading files to S3, however I needed to do it with PHP4 and preferably without any extra PEAR packages. This posed somewhat of a challenge, as all the tutorials I found on the web explained how to sign the policy using the PEAR Crypt_HMAC package or some feature of PHP5.

I eventually figured it out, and I’m here to show you how. The two functions used were found on the web (I don’t remember exactly where) and worked perfectly for my situation.

(Note: I had a lot of trouble saving the contents of the following code in WordPress due to some Apache mod_security settings configured on my server.)

/*
 * Calculate HMAC-SHA1 according to RFC2104
 * See http://www.faqs.org/rfcs/rfc2104.html
 */
function hmacsha1($key,$data) {
    $blocksize=64;
    $hashfunc='sha1';
    if (strlen($key)>$blocksize)
        $key=pack('H*', $hashfunc($key));
    $key=str_pad($key,$blocksize,chr(0x00));
    $ipad=str_repeat(chr(0x36),$blocksize);
    $opad=str_repeat(chr(0x5c),$blocksize);
    $hmac = pack(
                'H*',$hashfunc(
                    ($key^$opad).pack(
                        'H*',$hashfunc(
                            ($key^$ipad).$data
                        )
                    )
                )
            );
    return bin2hex($hmac);
}

/*
 * Used to encode a field for Amazon Auth
 * (taken from the Amazon S3 PHP example library)
 */
function hex2b64($str)
{
    $raw = '';
    for ($i=0; $i < strlen($str); $i+=2)
    {
        $raw .= chr(hexdec(substr($str, $i, 2)));
    }
    return base64_encode($raw);
}

/* Create the Amazon S3 Policy that needs to be signed */
$policy = '{ "expiration": "2007-12-01T12:00:00.000Z",
  "conditions": [
    {"acl": "public-read" },
    {"bucket": "johnsmith" },
    ["starts-with", "$key", "user/eric/"],
    ["content-length-range", 2048, 20971520]
  ]';

/*
 * Base64 encode the Policy Document and then
 * create HMAC SHA-1 signature of the base64 encoded policy
 * using the secret key. Finally, encode it for Amazon Authentication.
 */
$base64_policy = base64_encode($policy);
$signature = hex2b64(hmacsha1($secretkey, $base64_policy));

That’s it! This method doesn’t require PHP5 and doesn’t require any additional PEAR packages.

ERROR 406: Not Acceptable

The other day I was writing a script for work and discovered it wasn’t behaving as expected. The web browser didn’t give me any helpful information so I decided to use wget to see what the actual error was:

eris:~ raam$ wget --spider -v mysite.com
Connecting to mysite.com|69.16.69.151|:80... connected.
HTTP request sent, awaiting response... 406 Not Acceptable
16:19:28 ERROR 406: Not Acceptable.

Ah ha! ERROR 406: Not Acceptable. After doing some Googling I discovered the problem is related to an optional (though commonly installed) Apache module called mod_security. This module basically acts as a firewall for Apache to help prevent website attacks, specifically attacks through POST submissions.

To disable mod_security, you can place the following line in an .htaccess file on the root of your site:

SecFilterEngine off

I then confirmed that disabling mod_security actually fixed the problem:

eris:~ raam$ wget --spider -v mysite.com
Connecting to mysite.com|69.16.69.151|:80... connected.
HTTP request sent, awaiting response... 200 OK

So as you can see, the quick solution to fixing the Error 406 problem is to disable mod_security altogether using a .htaccess file. However, this leaves me wondering how much security I’m giving up by disabling mod_security.

I was in a hurry when this happened so I didn’t spend much time investigating what exactly my script was doing that may have caused mod_security to freak out. Sometimes other applications cause the Error 406 problem, such as WordPress or Mambo, and you really don’t have choice except to wait for a fix to be released. Since my own software caused the problem, figuring out why should be easy. I’ll post my results when I determine what was.

Comment History with Get Recent Comments Plugin

My Dad and I have been going back and forth quite a bit in the comments on a recent post I wrote about Consumption. This filled up the Recent Comments list on the sidebar rather quickly and I wasn’t able to see other recent comments. I realized a comment history or archive page, similar to my post archive page, would be very useful.

After looking around a bit, I found a really nice plugin by Krischan Jodies called Get Recent Comments. It has a ton of features and lots of configuration options. It has been updated as recently as last month and even supports the new widgets feature of WordPress 2.3 (it also works with older versions of WordPress as far back as 1.5).

By default, the instructions included with the plugin explain how to add recent comments to your sidebar. They don’t, however, mention anything about creating a comment history page. In the instructions there is a snippet of PHP code which you are supposed to use in the sidebar.php file of your WordPress template. I thought great, I simply need to create a new page in WordPress and add that snippet of code to the page using the runPHP plugin to execute the PHP on that page. This worked, partially. At the top of my comment history was this error:

Parse error: syntax error, unexpected $end in /home/raamdev/public_html/blog/wp-content/plugins/runPHP/runPHP.php(410) : eval()’d code on line 1

I thought perhaps it was because my runPHP plugin was outdated, so I upgraded it to the latest version (currently v3.2.1). I still received the error, so I decided to play around with the snippet of PHP code provided by the Get Recent Comments plugin. I was able to modify it slightly to get rid of the error as well as output some additional text. Here is the snippet of code I use to create my new Comment History page:

<?php
if (function_exists('get_recent_comments')) {
   echo "(Showing 500 most recent comments.)";
   echo "<li><ul>".  get_recent_comments() ."</ul></li>";
}
?>

In the plugin options, I configured the plugin to group recent comments by post. This created a very readable Comment History page. After adding the ID of the new page to the exclude list in my header.php file to prevent the page from showing in the header (wp_list_pages('exclude=704&title_li=' )), I added a ‘View comment history’ link to the bottom of the Recent Comments list on the sidebar.

The Get Recent Comments plugin is really powerful and I’m a bit surprised that the plugin doesn’t include basic instructions about how to create a comment history page. If you receive a decent amount of feedback from your visitors (in the form of comments), this is a great way to see all that feedback on a single page. If you have Trackback’s and Pings enabled, this plugin can even show those.

Using wget to run a PHP script

wget is usually used to download a file or web page via HTTP, so by default running the wget http://www.example.com/myscript.php would simply create a local file called myscript.php and it would contain the contents of the script output. But I don’t want that — I want to execute the script and optionally redirect its output somewhere else (to a log file or into an email for reporting purposes). So here is how it’s done:

$ wget -O - -q http://www.example.com/myscript.php >> log.txt

According to the wget man page, the “-O -” option is used to prevent wget from saving the file locally and instead simply outputs the result of the request. Also, wget normally produces it’s own output (a progress bar showing the status of the download and some other verbose information) but we don’t care about that stuff so we turn it off with the “-q” option. Lastly, the “>> log.txt” redirects the output of the script to a local file called log.txt. This could also be a pipe command to send the output as an email.

There is an incredible amount of power behind wget and there are a lot of cool things you can use it for besides calling PHP scripts from the command line. Check out this LifeHacker article for a bunch of cool uses.

How I started programming

I learned about programming when I was 12, three years after I began building computers. I asked my Dad one day (at the time he was working at Digital as a technical writer) how the games and programs on the computer were created. He didn’t know a whole lot about programming, but he knew of the BASIC programming language and told me I should get a book and learn it. So I bought a book and started using QBASIC on MS-DOS 6.22. From there I moved to Visual Basic (which I later realized was a big mistake). VB was very much like BASIC and making the transition was very easy. I coded “AOL apps” for awhile (for those who remember: Punters, Mail Bombs, ChatRoom Busters, etc.) until AOL started cracking down on such things.

A few years later, when programming for money came into view, I discovered that I should really be familiar with C or C++. So I glanced through a couple of C programming books and wow, what a difference from BASIC! My mental understanding of how programming languages worked had been spoiled by the simplistic syntax of BASIC and VB. It took many, many books to finally get a basic understanding of C and C++. I also flipped through a couple of Java books around this time because I heard the syntax was similar to C. Besides, who wouldn’t want to learn a programming language called Java?

HTML is something I have almost always known how to use (I can’t even remember when I first learned it) and I never really thought of it as a programming language. When I started to realize how important, and powerful, dynamic web applications were becoming, I decided to investigate what it was that made the HTML dynamic (after all, if you view the source of a dynamic web page, it usually just looks like plain HTML!). I discovered, almost accidentally, the open-source programming language PHP (PHP: Hypertext Preprocessor) and quickly started learning it. I later learned about ASP (Microsoft’s Active Server Pages) and JSP (JavaServer Pages) (wow, am I glad I found PHP first!). Since then I have also learned a lot about databases, including database design and the basic principles of good database design. The most popular database used with PHP at the time was MySQL, so that is what I studied. I’ve also had some light exposure to MSSQL.

Currently, I am working at a software startup company called Aerva, Inc. in Cambridge, Massachusetts doing everything from software support and debugging to being the “company muscle” (I’m the only one with a truck). I have built, and currently manage, their Support Center using PHP & MySQL, although I have also had to write some bash and Perl (ugh!) scripts to interface with their software provisioning process. I continue to work on adding new features to the Support Center to help streamline regular processes while at the same time increasing my knowledge of various programming languages and the Linux operating system.

I code with PHP on a daily basis (on a MacBook Pro using MAMP and Eclipse) and I am currently working on several of my own web projects. I just finished working on a little application called ASAP - Automated Staging and Publishing, which allows me to automate the process of checking out a project from CVS and then rsync’ing it to a remote server for staging or publishing purposes.

Everyone says PHP is “easy” to learn, and although they are probably right they fail to realize that its simplicity is also its weakness. To code good and secure PHP you need to have a strong understanding of the language and how best to use its many features. In addition to continuing to perfect my understanding of the PHP programming language and related OOP (Object-Oriented Programming) technologies, I wish to learn more about Java, XML, and AJAX.

phpBB photo-captcha image loading issues

A friend was having trouble with the photo-captcha mod he installed in phpBB. The photo-captcha mod basically presents users with a group of images and asks them to choose all images that fit a certain category (i.e., choose all cars). After installing the mod however (and fixing a couple of silly mistakes), only half of the images would show up. And it wasn’t the same images not showing up, it was entirely random.

As is usually the case with debugging, I looked for a working example with which I could compare the non-working one. When I found a working photo-captcha mod, I noticed the images loaded more slowly and consistently than on the non-working example. It was as if the non-working example was on crack, while the working example was practicing yoga.

I compared HTTP headers, checked cache settings, and everything else I could think of, but nothing looked out of the ordinary. So I decided to try something. Why not force the mod to pause between loading each image?

So I opened /forum/includes/usercp_confirm.php and modified it:

$image = $sub;
@imagegammacorrect($image, 1.0, (0.5 + mt_rand(0,1200)*0.001));

header('Content-Type: image/jpg');
header('Cache-control: no-cache, no-store');
@imagepng($image);
// -------- EDIT BY RAAM --------
sleep(1);
// -------- END EDIT ---------
exit;

And it worked! The code paused for 1 second between loading each image and all the images loaded properly. I have no idea what caused this problem but if you know please leave a comment! And if you’re having this same issue, at least now you have a solution. :)

NoteSake.com beat me to the punchline

For the past two years or so, I’ve been slowly working on a website called SaveNotes.com. However, while reading LifeHacker I came across a site called NoteSake.com. Apparently, they beat me to the punchline.

Their site looks great and has very similar features as what I had planned for SaveNotes.com. There are a couple of things I planned on doing differently, such as the ability to quickly create a note without logging in or authenticating yourself, but there were a lot of things I hadn’t even thought of, such as the ability to export notes as a PDF or share notes with other NoteSake users.

I tried creating an account on NoteSake.com to try out the features, however I’m still waiting for the email that contains the confirmation URL (which is another reason I disliked the idea of requiring an account).

Perhaps I will continue to develop SaveNotes.com simply because it would be a tool I would find very useful. Besides, NoteSake.com doesn’t really have any competition. Yet. :)

ImgListGenerator

Introduction

I’ve been doing a lot of eBay auctions lately and one of the most time consuming parts was creating the HTML for all the images in my auction description. I could reuse a lot the HTML, simply changing the directory and image names, but it was still a lot of repetitive work. This week I had 25 items to list and the repetitive work really got to me, so I stopped and spent 30 minutes putting together a script that would help me.

Usage

To simplify things, I decided not to support the ability to choose the directory with the images for which you want to generate HTML. Instead, you simply upload the index.php file to the directory that contains your images, visit that directory with your web browser, and the HTML is generated. Since your web browser reads HTML, the images will be displayed just as they would in your eBay auction. Simply right click on the page, choose View Source, copy the nicely formatted HTML and paste it into your eBay description.

You can view this script in action by browsing some of my images here.

Details

Here’s a sample output from this script:

<P align="center"><IMG src="http://www.ekarma.net/demo/pics/sample/DSC_0001.jpg"></P>
<P align="center"><IMG src="http://www.ekarma.net/demo/pics/sample/DSC_0003.jpg"></P>
<P align="center"><IMG src="http://www.ekarma.net/demo/pics/sample/DSC_0004.JPG"></P>
<P align="center"><IMG src="http://www.ekarma.net/demo/pics/sample/DSC_0010.JPG"></P>

The code I had to write for this script was rather simple. The real meat of the work is done by a very nice function called preg_find() by Paul Gregg. His code is too much to show here, but I’ll show you the code I wrote for this little script:

// Find all .jpg or .JPG files in the current directory using preg_find()
$files = preg_find('/.jpg|.JPG/', '.', PREG_FIND_SORTBASENAME);

// Store the path to the current directory
// (PHP_SELF includes index.php, so we use substr to remove that)
$link_dir = substr($_SERVER['PHP_SELF'], 0, -9);

// Loop through each of the files and generate the HTML
foreach($files as $file){
	$my_file = substr($file, 2, strlen($file));
	echo "<P align=\"center\"><IMG src=\"http://". $_SERVER['SERVER_NAME'] . $link_dir . $my_file ."\"></P>\n";
}

That’s it! Of course it would be much nicer if you could upload this script to the root directory and either enter or choose a path with images, then click generate. However, this script does exactly what I need, so I don’t plan to make any changes to it.

Download

This script can be downloaded here: index.php.zip (4KB)

Removing unwanted commas in a CSV file with PHP

During the past week, I’ve been working on a small PHP application that a friend paid me to write, called SearchCSV2MySQL. He is exporting data from a specific program and saving the data in Excel as *.csv. Here is what a sample of the data looks like:

130072690978,Jan-31 09:09,4.95,$,1,Vintage McMurdo SILVER Television Pre-Amplifier
220073351918,Jan-25 19:48,"1,031.00",$,2,"PITNEY BOWES, TABLETOP INSERTING MAILING SYSTEM"

As you might have guessed, you’re looking at eBay auction information. The fields in the exported data are item, date, price, currency, bid count, and description. Importing large amounts of CSV data into a MySQL database is one thing (and I’ll write a follow up post detailing how the application works), but I also needed to remove unwanted fields before importing the data into MySQL.

To do this, I break up each line using $fields = explode(",", $lines[$y]);, where $y is the current line we’re processing. This takes the data between the commas and puts it into the $fields array. However if you look at the sample exported data carefully, you may realize that we won’t get the results we’re expecting. The commas found between the double quotes, in the price and description fields, will be processed as field separators! This would cause $fields[2] (the price field) for the second line to contain “1″ instead of “1,031.00″.

So how did I resolve this? After looking over the syntax for strpos(), substr(), and substr_replace() a million times, I finally came up with this solution:

/*
 * ***************** Remove all commas from the price field ******************
 */
/* Isolate the second field (price field) by finding the position
 * of the comma right before the price field (second comma)
 */
$first_comma_pos = strpos($lines[$y], ",");
$second_comma_pos = strpos($lines[$y], ",", $first_comma_pos + 1);

/* Check if the price field contains double quotes,
 * which would mean the price has a comma in it and we need to remove it
 */
if(substr($lines[$y], $second_comma_pos + 1, 1) == "\""){
    /* Find the positions of the opening and closing double quotes around the price */
    $price_quotes_pos_start = strpos($lines[$y], "\"");
    $price_quotes_pos_end = strpos($lines[$y], "\"", $price_quotes_pos_start + 1);

    /* Find all occurences of a comma after the opening double quote, but before the closing quote,
     * around the price field and remove them from this line.
     */
    $price_comma_pos = strpos($lines[$y], ",", $price_quotes_pos_start);
    while($price_comma_pos < $price_quotes_pos_end){
    	$lines[$y] = substr_replace($lines[$y], "", $price_comma_pos, 1);
    	$price_comma_pos = strpos($lines[$y], ",", $price_quotes_pos_start);

    	/* Update the position of $price_quotes_pos_end,
    	 * since it has changed after we removed a comma!
    	 */
    	$price_quotes_pos_end = strpos($lines[$y], "\"", $price_quotes_pos_start + 1);
    }
} 

/*
 * ***************** Remove all commas from the description field ******************
 */
/* Find the position of the comma right before the description field (fourth comma) */
$first_comma_pos = strpos($lines[$y], ",");
$second_comma_pos = strpos($lines[$y], ",", $first_comma_pos + 1);
$third_comma_pos = strpos($lines[$y], ",", $second_comma_pos + 1);
$fourth_comma_pos = strpos($lines[$y], ",", $third_comma_pos + 1);
$fifth_comma_pos = strpos($lines[$y], ",", $fourth_comma_pos + 1);

/* Check if the description field contains double quotes,
 * which would mean the description has a comma in it and we need to remove it
 */
if(substr($lines[$y], $fifth_comma_pos + 1, 1) == "\""){
    /* Find the positions of the opening and closing double quotes around the description */
    $desc_quotes_pos_start = strpos($lines[$y], "\"", $fifth_comma_pos);
    $desc_quotes_pos_end = strpos($lines[$y], "\"", $desc_quotes_pos_start + 1);

    /* Find all occurences of a comma after the opening double quote, but before the closing quote,
     * around the description field and remove them from this line.
     * Since this is the last field, we dont need to worry about finding any
     * commas after the closing quote, and therefore don't need to update $desc_quotes_pos_end.
     */
    while($desc_comma_pos = strpos($lines[$y], ",", $desc_quotes_pos_start)){
    	$lines[$y] = substr_replace($lines[$y], "", $desc_comma_pos, 1);
    }
}

I realize that there are a couple of limitations to this code, such as not removing both commas if the price contains a larger number (i.e., “1,042,240.00″). It also doesn’t look in the description field for commas. UPDATE: I’ve updated the code to remove all commas from both the price and description fields.

This code is simply a proof-of-concept to show how I solved a problem. If you know of a better way to go about this, please let me know! Hopefully posting this snippet will save someone the time I spent figuring it out.

Show me the errors!

It took me forever to figure out why the hell including a specific class file in one of my PHP scripts was causing the script to output nothing — besides a blank white page. I finally figured out that PHP must be hiding errors and as soon as I added the following line to the top of my script, the error was obvious (a single character error, no doubt):


ini_set('display_errors', '1');

I was testing on a webhost I had never used before, so I wrongly assumed it was a problem with my code causing the blank output.

Verizon’s Ultimate Call Forwarding service allows you to remotely enable/disable call forwarding, as well as change the number to which calls will be forwarded. When you call the automated system to activate call forwarding, there are several other options you can select, such as “Forward after no answer” and “Forward when busy”. Those features are extras, for which you need to pay an additional monthly fee. The only feature enabled on account I was configuring is the basic call forwarding, which explicitly forwards all calls. Why then, am I able to select, configure, and enable the other features?

“To enable Forward after no answer, press 1″
“1″
“Please enter the number you wish to forward your calls to”
“617-959-xxxx”
“Please hold while we process your request…”
“Please hold while we process your request…”
“Please hold while we process your request…”
“Forward after no answer has been enabled.”

No it hasn’t! The damn automated system says its been enabled, when in fact configuring and enabling this feature does absolutely nothing! So much for an intelligent system. It should tell me I don’t have that feature enabled, not allow me to configure and enable it.