PHP

Behind the Scenes at OSNews

I just started putting together a series of articles I will be publishing on OSNews.  I’ve only roughly sketched it out, but in short, it’s going to discuss how OSNews works, how the PHP is structured, why we made certain architectural decisions, why we don’t use tried-and-true CMSes like WordPress, Slash, or Joomla!, and how, during peak traffic times, we have survived 30,000 unique visitors per hour on a single server.  OSNews didn’t happen by mistake: over a series of months, arguably years, we took a constant read of performance, hits, server load, and usability with the mission to continually improve load time, performance, and UX.  We’ve just recently begun testing some new data presentation methods that I intend to include in my little exposé.

If you’re interested in some revealing behind-the-scenes info, feel free to ask questions now.

Flip 3.0.1

I never expected to release another version of my old weblog project Flip, but while searching my own name in a new search engine, I came upon several vulnerability reports for Flip 3.0. I’ve known about them for awhile now, but having dropped Flip in favor of another project (which I’ve since abandoned, for the most part, in favor of WordPress), it seemed pointless to bother. However, since there is an active exploit, I thought I’d release an update and a patch.

I don’t believe anyone out there is still using Flip, but if there is, this is how to defeat the script: simple add this line at line 102 of account.php:

 if(strstr($_POST['em'],"][")) { die('Fail'); }

and this at line 162:

 if(strstr($_POST['nem'],"][")) { die('Fail'); }

Alternatively, you can download the modified file here or download Flip 3.0.1 here.

It may sound odd, but I would highly recommend that you do *not* use this code. It’s now 7 years old and the web is a much different place. The code here is really not suited for running a website today. That said, it was odd to unzip and install it and see that it actually works. The rendering of most of the “themes” is weird (Fudge works great), but otherwise, everything worked.

If you are still a Flip user, I recommend you update your account.php page immediately, and if you have the time and inclination, upgrade to 3.0.1. The following files have some minor changes:

  • account.php
  • index.php
  • inc/config.php
  • README.html

Once again, this code is aged not particularly well suited for today’s web.  If you want a simple weblog, I recommend WordPress.

Posting Your Latest Tweet in Wordpress

Although I posted yesterday how to add your latest tweet to Wordpress without a plugin, I made several changes to the script before I posted it to make it more “generic” and re-usable. Since I’ve changed it quite a bit, I decided to repost it. This new script also autolinks @usernames and #hash tags.

Directions are this easy: set the path of $tw_File with a static, writable file.  Set $tw_userid to your Twitter user id.  Done. 

Download firsttube.com “get latest tweet” php snippet.

Sorting a Multi-Dimensional Array with PHP

Every so often I find myself with a multidimensional array that I want to sort by a value in a sub-array. I have an array that might look like this:

//an array of some songs I like
$songs =  array(
		'1' => array('artist'=>'The Smashing Pumpkins', 'songname'=>'Soma'),
		'2' => array('artist'=>'The Decemberists', 'songname'=>'The Island'),
		'3' => array('artist'=>'Fleetwood Mac', 'songname' =>'Second-hand News')
	);

The problem is thus: I’d like to echo out the songs I like in the format “Songname (Artist),” and I’d like to do it alphabetically by artist. PHP provides many functions for sorting arrays, but none will work here. ksort() will allow me to sort by key, but the keys in the $songs array are irrelevant. asort() allows me to sort and preserves keys, but it will sort $songs by the value of each element, which is also useless, since the value of each is “array()”. usort() is another possible candidate and can do multi-dimensional sorting, but it involves building a callback function and is often pretty long-winded. Even the examples in the PHP docs references specific keys.

So I developed a quick function to sort by the value of a key in a sub-array. Please note this version does a case-insensitive sort. See subval_sort() below.

function subval_sort($a,$subkey) {
	foreach($a as $k=>$v) {
		$b[$k] = strtolower($v[$subkey]);
	}
	asort($b);
	foreach($b as $key=>$val) {
		$c[] = $a[$key];
	}
	return $c;
}

To use it on the above, I would simply type:

$songs = subval_sort($songs,'artist'); 
print_r($songs);

This is what you should expect see:

Array
(
    [0] => Array
        (
            [artist] => Fleetwood Mac
            [song] => Second-hand News
        )
 
    [1] => Array
        (
            [artist] => The Decemberists
            [song] => The Island
        )
 
    [2] => Array
        (
            [artist] => The Smashing Pumpkins
            [song] => Cherub Rock
        )
 
)

The songs, sorted by artist.

PHP Weirdness

Beware: this post is definitely not for the feint of heart. It includes a lot of code. You have been warned.

I wrote an application some time ago for my company that looks up the longitude and latitude of an address for use in our geocoding initiative. It relied on yahoo_geo(), a function written by PHP creator Rasmus Lerdorf and the Yahoo Maps API. It was largely dependent on this function:

function yahoo_geo($location) {
	$q = 'http://api.local.yahoo.com/MapsService/V1/geocode?appid=rlerdorf&location='
		.rawurlencode($location);
	$tmp = '/tmp/yws_geo_'.md5($q);
	request_cache($q, $tmp, 43200);
	libxml_use_internal_errors(true);
	$xml = simplexml_load_file($tmp);
	$ret['precision'] = (string)$xml->Result['precision'];
	foreach($xml->Result->children() as $key=>$val) {
		if(strlen($val)) $ret[(string)$key] = (string)$val;
	}
	return $ret;
}

This function worked for over two years for us with no problems at all. Then suddenly, in the last month, it started getting spotty. I fixed things by commenting out the caching parts of the function and forcing each execution to run again. Then I got errors about the libxml_use_internal_errors() function, so I commented that out. But today, the function just flat out failed, every single time returning the same error:

Warning: file_get_contents(http://XXXXXXXXXX/XXX) [function.file-get-contents]: failed to open stream: HTTP request failed! in /home/intranet/html/fetch.php on line X

What the heck? This code is all over the web. I’ve tried a million permutations of this function, including using fopen() and ob_get_contents(), and none have worked. And most frustratingly, I could load the URL successfully in Lynx and eLinks, so the machine could quickly and easily fetch the URL.

So I ventured into a sandbox I’ve never really played before: cURL. cURL is an interesting animal. But the interesting thing is, once I got it working, it worked faster than ever! So, without further ado, here is the new and improved yahoo_geo() function:

function yahoo_geo($location) {
	$q = 'http://api.local.yahoo.com/MapsService/V1/geocode?appid=rlerdorf&location='.urlencode(trim($location));
	$ch = curl_init($q);
	curl_setopt($ch, CURLOPT_HEADER, 0);
	ob_start();
	curl_exec($ch);
	$stream = ob_get_contents();
	ob_end_clean();
	if($stream) {
		$xml = simplexml_load_string($stream);
		$ret['precision'] = (string)$xml->Result['precision'];
		if($xml) {
			foreach($xml->Result->children() as $key=>$val) {
				if(strlen($val)) $ret[(string)$key] =  (string)$val;
			}
		}
		curl_close($ch);
		return $ret;
	} else {
		return FALSE;
	}
}

Note: If you’re reproducing these functions elsewhere, be careful – WordPress may have converted the quotes into smart quotes that will need to be fixed before this script will run properly.

OSNews vs. WordPress

I’ve spent quite a bit of time, over the last 5 or 6 days, diving into WordPress and learning what makes it tick.  Parts of WordPress are really impressive – just flat out cool. The way some of it works is fairly complex and deciphering it sometimes means reading page after page after page to understand an entire routine.  But sometimes, when you finally see, end to end, how something in WordPress works –  I mean really see individual bits of the engine – you have to admit it teaches you a little about PHP.  WordPress, underneath it all, is a pretty big beast and its strength and ubiquitous presence comes largely, I think, from the fact that it can do virtually anything.  The really sweet plugin system, the ways hooks work, “The Loop,” the dynamic options panel – it’s all very educational.  

The interesting thing here is that I’ve browsed the source of Slash, Scoop, phpNuke, and now WordPress, and all of them are definitively more complex and much heavier than the entire OSNews codebase. Now, before you jump all over me – firstly, Slash and Scoop are Perl, and I don’t really read Perl, so I can’t speak as an expert there.  Secondly, WordPress and Nuke both are very portable and dynamic, whereas OSNews has a narrow focus and, location-wise, is very static.  But that aside, OSNews has withstood simultaneous link bombs from Slashdot and Digg.  As amazing as WordPress is, it’s mostly amazing that it functions at all and loads in less than 2 minutes per page with as much going on as I can see behind the scenes.   That’s not a cut on WordPress, by the way.

In fact, if anything , what is really impressed upon me is how smooth and simple OSNews code is, if I may be so bold.  OSNews runs superfast due, in part, to lots of creative caching, some on-demand, some via cron.  But it also does so because of highly efficient queries that are measured for speed on their JOINs, meaning in some cases, it’s faster to do 20 simple queries than one complex one, or build a long and scary chain of “OR x=a OR x=b OR x=c OR x=d…”  Watching WordPress code in action is really fun for me, but watching OSNews work knowing what I now know about how much work PHP can cram into its threads is even more fun.

How To REALLY Survive Digg on a Shared Host

After reading a ridiculous post on “surviving the Digg effect on a shared host,” (and then laughing ridiculously at it), I decided to write a real tutorial on real-live ways not only to survive the Digg effect, but also a simple but powerful way to improve your site’s performance. Read more within.

Integers on the Intertubes

Some time ago, I wrote an application for my company. Like most weblets I’ve written, this used PHP and either MySQL or MSSQL for the backend. This particular application logged all phone calls. As part of the record, it would record the caller’s account number, which is a 5 or 6 digit integer.

So, I got a phone call from the director of our customer contact department this week. He was concerned about the reports. He made a decision last week that when a call came in that was a lead – in other words, a non-customer, that his people would fill the phone number from the caller ID into the account number field. But when he ran his export reports, he found that hisn techs had entered this phone number for ALL of the calls: 429-496-7295. That’s weird, he said. So he called me and asked why that was. I checked all the calls and most were from one woman, so my first instinct was “Check if her browser has autocomplete turned on”. But he swore that he tried it too and gotten the same result.

I checked the database and sure enough, it was right there: 429-496-7295, in all of the fields. So I went back to the code. In short, I took the input from the form, and declared it like this:

$accountnum = (int) $_POST['accountnum'];

Pretty straightforward: explicitly declare the type. So, I started my debugging by attempting to manually enter the data into the database. Sure enough: the account key field showed this: 4294967295.

So, I went back to the PHP and started by dumping out the raw SQL query:

INSERT INTO calls ('','x','x','x','4294967295','x','x');

What? So the database automatically converts it to this weird phone number and PHP does too? Suddenly it occured to me. One of the benefits of 64-bit computing is the ability to address more memory. There are limits to what can be done in 32-bit computing, and one is that integers have a limit! In this case, a database field called “integer” is limited to numbers between -2,147,483,648 and +2,147,483,647. It just so happens that the number is the same length as a US phone number – 10 digits. Changing the db field to “BIGINT” allowed me to manually run the SQL query and it worked. But the app still didn’t.

PHP’s int() and (int) $var syntaxes both conform to the integer limit. So I devised a work around:

$ac = $_POST['accountnum'];
if(!is_numeric($ac) { $ac = (int) $ac; }

It’s not gorgeous, but it will more than suffice for an internal app. We web programmers don’t usually have to deal with big integers, so it’s totally possible that web developers would never have had occasion to handle a situation like this. Here’s looking forward to native 64-bit for our next server, though.

PHP vs. ASP.NET

We have a new web-based client portal application we are going to use for my company extranet. However, because it was originally designed to be a hosted application, there are several variables involved in all areas that don’t apply to us, since we host it ourselves.

When using said portal, every URL looks something like:

domain.com/login.aspx?QS=jasbndfiaubnfoaeuifwoeifbwfe

The only difference is that the “QS” GET variable is even longer. I made the request of our developers to get rid of this query string for the login page, and the login page only. This is what that code looks like in PHP, inserted at line 1.

if(!$_GET['QS']) { 
     $_GET['QS'] = 'jasbndfiaubnfoaeuifwoeifbwfe'; 
}

That’s it. One line of code. In ASP.net, this cost me 3 hours of developer time. THREE hours.

Then I asked our old developers to make a change to their code. It was doing a check in login if they are customers from the new app or the old one. If they are old, it processes the login. If it’they are new, it gives them an error message. So I said, instead of giving them the error, let’s redirect them to /new-directory/login.aspx?email=[base64_encoded email]&password=[base64_encoded password].

This is that code in PHP:

if($is_new) { 
     header("Location: /newdirectory/login.aspx?email="
.base64_encode(stripslashes($_POST['email'])) . "&password="
.base64_encode(stripslashes($_POST['password'])));
} else {
     //process login
}

This cost me 2 hours at $165. Am I getting taken for a ride? I keep telling them – this would take 30 seconds in PHP. And they tell me, yes but ASP.net doesn’t work that way, and we need to change the web.config, and we need to recompile the entire site, etc, etc. If it were just one vendor, I’d be more suspicious, but two separate, unrelated developers are giving me crazy quotes like this.

I hear people bitch about PHP online ad nauseum. Every time I see real code, it appears PHP is FAR faster and far more friendly when it comes to customization.

PHP Lesson 1: Pretty URLs!

I am going to start a new practice here. Every now and again I’m going to post some PHP code with some explanation. Today, I’m going to write about what I’ve been calling “pretty URLs” and how to create and manage them in PHP.

PHP includes a variable in the superglobal scope $_SERVER called “PATH_INFO.” PATH_INFO includes information entered after the name of the requested script.

Let’s use firsttube.com as an example. The URL of a story is constructed as such:

http://firsttube.com/read.php/[id]/[url_friendly_title].html

The story is also accessible as /read.php?id=[id]

So how do we construct this so-called “pretty URL?” Using PATH_INFO. Read on for details.
More >