Archive for March, 2009

Turning an Array or Object into XML using PHP

March 25th, 2009

I have read a fair few tutorials written by others using classes and DOM functions to create XML from arrays. But what happens if you have an older PHP installation or prefer to keep things really simple as I do.

The following function declarations generate an XML string from an associative array. In my scripts that use it, it’s usually coupled with a download function so that XML is generated on the fly and then downloaded to a file. The main advantage of this is that you can write a basic import script using the SimpleXML library to restore the array at any time which makes it perfect for use as part of a backup/restore system.

Wordpress stores alot of it’s sitewide data in a table called wp_options which stores a named key and a value string for each piece of information. Plugins can also use this table by using the add_option, update_option, delete_option and get_option functions passing values, arrays or objects along with a key name. This isn’t an ideal data structure but does mean that plugin writers can easily store information without bloating the database with unnecessary and often badly designed tables.

The Code..

function generate_xml_from_array($array, $node_name) {
	$xml = '';

	if (is_array($array) || is_object($array)) {
		foreach ($array as $key=>$value) {
			if (is_numeric($key)) {
				$key = $node_name;
			}

			$xml .= '<' . $key . '>' . "\n" . generate_xml_from_array($value, $node_name) . '</' . $key . '>' . "\n";
		}
	} else {
		$xml = htmlspecialchars($array, ENT_QUOTES) . "\n";
	}

	return $xml;
}

function generate_valid_xml_from_array($array, $node_block='nodes', $node_name='node') {
	$xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";

	$xml .= '<' . $node_block . '>' . "\n";
	$xml .= generate_xml_from_array($array, $node_name);
	$xml .= '</' . $node_block . '>' . "\n";

	return $xml;
}

Usage

You can use the above functions by generating an array or object as normal and then using the following code. The function returns the XML and you can then do with it as you wish.

$xml = generate_valid_xml_from_array($array);

Things to consider

XML doesn’t allow for numeric tags so any numbers are replaced by the content of the $node_name variable. XML also doesn’t allow for certain special characters within it’s tags and ,for this reason, the htmlspecialchars function is passed over the raw data before it is placed into the string.

You can, however make use of cdata tags which tells XML readers to essentially ‘ignore whats coming next’. See the w3schools explanation of cdata here: http://www.w3schools.com/XML/xml_cdata.asp. There is no right or wrong way to escape data but if you have problems with my script then factor in the cdata tags replacing the htmlspecialchars call and see how you get on.

Alternatively feel free to contact me by commenting on this post or using the contact form and I will work with you to get your script working.

A simple ‘Where Am I?’ using MaxMind Geolocation

March 9th, 2009

MaxMind seem to be the only company that offer some level of free geolocation. You do, however, have to delve into their site (or google it) to find the Open Source section where you can download the free binary database and look at the documentation. To give MaxMind some credit though their documentation is excellent and examples are offered in many programming languages.

So why this blog post?
Well firstly to promote MaxMinds service and willingness to provide a free database to all (even if it is a cunning ploy to sign people up to the paid version) and secondly to extend the PHP example somewhat and help others to understand what’s going on.

The database is essentially a huge lookup table and the associated PHP file will parse the binary file into something a little more searchable. They release a new version of this database monthly so in theory if you have a CRON job setup to fetch the file (or a reminder if you want something a little simpler) then staying up to date couldn’t be easier.

So… A code example and integration information

With any system like this, Integration is the hardest part. Getting their code sample shoehorned into place within your code and making sure that the database is updated regularly are things that might put some people off a bit.

The result of having the PHP include and the database (free version is 1 megabyte) is that you can then pass an IP address into a function and it will return the country in which the user resides. The following block of code is a class based version of the sample.php file that maxmind have supplied in their PHP examples section.

define ('GEOIP_BIN_PATH', '/path/to/GeoIP.dat');
$geoip = new geoip_class();

class geoip_class {
	private $gi = false;

	function __construct($bin_path = GEOIP_BIN_PATH) {
		require_once('geoip.include.php');
		$this->gi = geoip_open($bin_path, GEOIP_STANDARD);
	}

	function __destruct() {
		$this->gi = geoip_close($this->gi);
	}

	function country_code($ip) {
		return geoip_country_code_by_addr($this->gi, $ip);
	}

	function country_name($ip) {
		return geoip_country_name_by_addr($this->gi, $ip);
	}

	function where_am_i_name() {
		return $this->country_name($_SERVER['REMOTE_ADDR']);
	}

	function where_am_i_code() {
		return $this->country_code($_SERVER['REMOTE_ADDR']);
	}

	function where_am_i() {
		$return = array();
		$return['name'] = $this->country_name($_SERVER['REMOTE_ADDR']);
		$return['code'] = $this->country_code($_SERVER['REMOTE_ADDR']);

		return $return;
	}
}

The class has a few more helper methods inside it but it is still essentially what maxmind had intended from their example. Using the above code will do nothing at present except for instantiate a new version of the geoip_class class. However the advantage of using a class like this is that it will always work when used in a situation where you can’t control the other code that might be running at the same time.

For example, if this were to be used within a Wordpress plugin then technically the blog owner could be using another plugin that declares and uses the same functions which would cause a fatal error thus killing the site.

Usage

Adding this code into yours now couldn’t be simpler because all you need to do is update the define (at the top of my example), download the latest DAT file, and use the following ‘Where am I’ example as a guide.

$geoip = new geoip_class();
echo $geoip->where_am_i_name();

Updating the database file

There are a couple of ways to do this but here is a simple bash script (again an updated version of MaxMinds WGET example) to update the file (only when necessary) every time it’s called.

#!/bin/bash

UNGZIPPED_FILE=GeoIP.dat
GZIP_FILE=GeoIP.dat.gz
REMOTE_PATH=http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/
LOCAL_PATH=/tmp/
LOCAL_FINAL_PATH=/www/vhtdocs/geo_ip/ #update this to your directory of choice

cd $LOCAL_PATH
wget -N -q $REMOTE_PATH$GZIP_FILE
gunzip $GZIP_FILE
cp -f $UNGZIPPED_FILE $LOCAL_FINAL_PATH$UNGZIPPED_FILE

exit

The above example will get the file from MaxMind and copy it to a path of your choice updating any existing files that are there or adding it if it isn’t. It could do with having some error checking adding for example if the file failed to download or the permissions are incorrect on the copy path. However, use it as an example and extend as appropriate but as long as you run it as a root user and all destination directories exist then you will be fine.

It could be simply added to your CRONTAB on a daily or monthly schedule and you can forget about it forever (or at least until either the location of your local DAT file changes or they relocate the remote version). Make sure that if you do use CRONTAB to add it onto that of a priviledged user who has access to all the locations specified in the file.

Resources

Here are a few key pages on MaxMinds site that I used to compile this tutorial

http://www.maxmind.com/app/php
http://geolite.maxmind.com/download/geoip/api/php/
http://www.maxmind.com/app/geolitecountry
http://www.maxmind.com/app/installation

Subversion server sertup and configuration for dummies

March 7th, 2009

For those of you that don’t know what it is, Subversion (or SVN for short) is a popular open source version control system used all over the world. Previously people have used other systems like CVS however SVN seems to now be the standard. Firstly here is a little information about clients, skip past it for the server section.

Clients

An SVN client is (obviously) a piece of software that allows you to interact with a central ‘repository’ (or repositories if you work on version controlled files from multiple sources). They facilitate communication and interaction with the SVN server, actions like ‘checking out’ repositories, ‘committing’ your changes and ‘merging’ in other people changes to your own local copy.

This might be a little basic for this tutorial so I shall move swiftly onto client software. The Subversion project is hosted by tigris.org who release the source code for Subversion itself. Volunteers port the code onto different platforms. I can only recommend the software I have used to they are as follows:

  • TortoiseSVN – A free and very popular windows client with Explorer integration
  • SCPlugin – A free, yet buggy, Mac client whose advantage is integration with Finder
  • Zigversion – A Mac client which is free for personal use. No Finder integration (to my knowledge)

Server

This section will detail how to install and configure a Subversion repository on a Unix based system. It assumes that the ‘yum’ package is installed however for those of you who don’t have it or are unaware of what it does then please read Valerie Aurora’s Linux Basics guide where there is a helpful section called ‘Finding and Installing Software’.

First things first, install subversion and its dependancies using yum. This is a nice one liner to get the job done for you.

yum -y install apr apr-utils http-devel mod_dav_svn subversion

Yes it was that easy! One command just installed Subversion and it’s dependancies (let me know if i have missed any). The next thing to do is configure a new repository and setup access using htaccess.

The first thing to do is to create a new repository. For this tutorial I will be using ‘/usr/local/svn’ as a place to store repositories however you can put it anywhere you like on the file system.

Firstly, create the directory ’svn’ using the mkdir command (within /usr/local/ :) ) and then chown it recursively to apache:apache. Move into it and use the following command to set up your first repository (which in this case is called test but obviously it can be anything you choose):

svnadmin create test

The above install process also adds a new config file into the apache conf.d directory (/etc/httpd/conf.d/). The config file is called subversion.conf, within which is an example block to uncomment to set up http access to the respository and set up htaccess permissions.

The most basic example of what to put in this file is below. It will set up a virtual subdirectory of your website so try to either make the name unique or not use the name on any of your sites on the same server. This example will set up a subdirectory called ’svn’ so if your site is http://127.0.0.1 then your Subversion path will be http://127.0.0.1/svn

<Location /svn>i
	DAV svn
	SVNParentPath /usr/local/svn
</Location>

After making any changes to this file (and any file served by apache for that matter) then you must restart apache for them to take effect. You can then view your repository in your browser and assuming you see a blank page with something that resembles ‘Revision 0:’ or the name of your repository as a link then you are ready to start using it.

For those that run multiple sites using VirtualHost directives then the above section of code can be included within one as per the example below which should create access to Subversion on a site named ’svn.localhost’.

<VirtualHost *:80>
	DocumentRoot /usr/local/svn
	ServerName svn.localhost
	<Location /svn>
		DAV svn
		SVNParentPath /usr/local/svn
	</Location>
</VirtualHost>

Access Control

You may want to configure access to your repository allowing only certain people and groups to have different levels of access. Subversion allows a couple of ways to do this but the most common seems to be htaccess. Configuring this is more or less the same as configuring htaccess. See the example below:

AuthType Basic
AuthName "Subversion"
AuthUserFile "/etc/httpd/conf.d/svn_passwd"
Require valid-user

The above 4 lines can be slotted in beween the ‘<Location>…</Location>’ tags. It tells Apache not to allow access to anyone except those that have their crudentials within a file (in passwd format) at the location specified. The ‘AuthName’ line is optional, all it does it put a name onto the box that pops up asking for login information.

The file that I have called svn_passwd can be situated anywhere on the system however it would be sensible NOT to put it anywhere that can be served by any of your websites. Generate the ’svn_passwd’ file using the following command if it doesn’t already exist.

htpasswd -cm /etc/httpd/conf.d/svn_passwd admin

You will then be prompted for a password for the user and once entered (twice?), after Apache has been restarted you won’t be able to get access to your repositories without those details. The name of the user you choose for this doesn’t have to be a system user and if it is (like admin for example) then the password you use doesn’t have to match its counterpart.

After restarting Apache again you will be only be able to access your repository with a correct set of credentials defined in the htpasswd file.

That concludes the tutorial for now. I hope that I have given you a good round knowledge of Subversion server setup which in turn will enable you to easily run your own version control system. There will likely be snags along the way, there always are. If in doubt then Google it! or ask me (who will likely Google it anyway :) )

Resources

http://svnbook.red-bean.com

SQL Import from a file using PHP

March 4th, 2009

On seeing the title of this post your first uncontrollable outburst may be ‘Why on earth would I want to do that?‘. On further reading, you may decide that PHP is the way to get tables created from within an automated install process (Wordpress plugin activation or PHP based software ‘installation’). It may trigger another outburst when you see that the code I am about to paste in is more than a single line long (which would be all it would take using the PHP exec() command).

Why not use the Exec command?

Alot of developers cringe at the thought of the exec command in PHP, others have disabled it due to ’security issues’. My reason for not using it is because my script doesnt just import SQL. It can do two very important things for me:

  • Prioritise the import
  • Prefix the table names

Prioritising the import isn’t essential but it means that your SQL file can be a mess of random statements and no matter where the INSERTs are, they will be grouped and run last to allow the CREATE statements a chance to work.

Prefixing the table names is almost self explanatory. Wordress, which I wrote this script for, allows Database prefixes to be used when setting up a blog. It only makes sense that to avoid conflict when more than one blog is in the same database to prefix my tables with the same string.

How does it all work?

For those interested the code above simply does the following:

  • Imports the file from an absolute path passed to the function into an array split by line
  • Loops through the lines looking for starting words defined as SQL statement keywords in an array and generates a list of complete statements
  • Removes all comments as it goes (comment defined as any line starting with ‘–’
  • Looks for the first backtick character (`) which MUST be present (at least around the table name) and inserts the Wordpress Database Prefix
  • Inserts the completed query into an array grouped by those that are INSERT queries and those that aren’t
  • Sorts the new array by a priority figure (new numbers can be added to mark muti-level priority if you like)
  • Runs the queries

Integration

To integrate it into your Wordpress plugin then simply copy/paste the code into yours and pass it an absolute path on plugin activation. I also use it to drop tables on a ‘hard’ deactivation by passing it an SQL file full of DROP commands. To integrate it into your NON Wordpress site then simply remove all references to $wpdb and substitute ‘$wpdb->prefix’ with your own string or variable name.

The Code

Here is the code although I have also provided it in download form: MySQL Import Script (1.14 KB)

function mysql_import($filename) {
	global $wpdb;

	$return = false;
	$sql_start = array('INSERT', 'UPDATE', 'DELETE', 'DROP', 'GRANT', 'REVOKE', 'CREATE', 'ALTER');
	$sql_run_last = array('INSERT');

	if (file_exists($filename)) {
		$lines = file($filename);
		$queries = array();
		$query = '';

		if (is_array($lines)) {
			foreach ($lines as $line) {
				$line = trim($line);

				if(!preg_match("'^--'", $line)) {
					if (!trim($line)) {
						if ($query != '') {
							$first_word = trim(strtoupper(substr($query, 0, strpos($query, ' '))));
							if (in_array($first_word, $sql_start)) {
								$pos = strpos($query, '`')+1;
								$query = substr($query, 0, $pos) . $wpdb->prefix . substr($query, $pos);
							}

							$priority = 1;
							if (in_array($first_word, $sql_run_last)) {
								$priority = 10;
							} 

							$queries[$priority][] = $query;
							$query = '';
						}
					} else {
						$query .= $line;
					}
				}
			}

			ksort($queries);

			foreach ($queries as $priority=>$to_run) {
				foreach ($to_run as $i=>$sql) {
					$wpdb->query($sql);
				}
			}
		}
	}
}