<?php
/* Mozilla LDIF to vCard Converstion Tool
 * Copyright (c) 2006 by Jonathan Reams
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * You may contact the Author at jreams@drew.edu.
 *
 * The primary function in this library is convert_ldif_to_vcard($filein, $fileout).
 * Just call it and specify the input file and the output file. It assumes that the
 * filename you pass it is a valid LDIF file exported from a Mozilla address
 * book. It won't do syntax checking on the oiginal LDIF. 
 *
 * Optionally, there is also string_convert_ldif_to_vcard($stringin, & $stringout).
 * This function is exactly the same as the other function, except that it works on
 * strings instead of files. Note that the second variable is a reference to a string.
 *
 * Both functions return a count of the number of LDIF cards that were converted 
 * upon success.
 *
 * This script can also be used as a command-line converter which specifies the
 * input and output files as arguments on the commandline. Support for pre-defined
 * argv, argc variables must be turned on in php.ini.
 * 
 * This library was originally written for PHP 5, but has subsequently been fixed
 * to run on PHP 4.2. All of it's functions should be predefined by PHP and don't
 * require any loadable modules.
 */

function encode($string) {
	return escape(quoted_printable_encode($string));
}

function escape($string) {
	return str_replace(";","\\;",$string);
}

function fool_proof_address($val)
{
	$linenames = array('address1', 'address2', 'city', 'state', 'zip', 'country');
	foreach($linenames as $name)
	{
		if(!isset($val[$name]))
			$val[$name] = '';
	}
	return $val;
}

function parse_person_ldif($entry)
{
	foreach($entry as $attrib_pair)
	{
		//echo "AP ";
		if($attrib_pair[0] == 'dn')
		{
			$dns = explode(",", $attrib_pair[1]);
			foreach($dns as $dn_attrib)
			{
				$dn_pair = explode("=", $dn_attrib, 2);
				if($dn_pair[0] == 'cn')
				{
					$cn = $dn_pair[1];
					$properties['formattedName'] = $cn;
					unset($cn);
				}
				if($dn_pair[0] == 'mail')
				{
					$properties['primaryEmail'] = $dn_pair[1];
				}
			}
		}
		if($attrib_pair[0] == 'givenName')
		$properties['fullName'][0] = $attrib_pair[1];
		if($attrib_pair[0] == 'sn')
		$properties['fullName'][1] = $attrib_pair[1];
			
		if($attrib_pair[0] == 'xmozillanickname')
		$properties['formattedName'] = $attrib_pair[1];

		if($attrib_pair[0] == 'mail')
		$properties['primaryEmail'] = $attrib_pair[1];
		if($attrib_pair[0] == 'mozillaSecondEmail')
		$properties['secondEmail'] = $attrib_pair[1];
		
		if(!isset($properties['fullName']) && isset($properties['primaryEmail']))
			$properties['fullName'] = array($properties['primaryEmail'], '');

		switch($attrib_pair[0])
		{
			case "postalAddress":
				$properties['workAddress']['address1'] = $attrib_pair[1];
				break;
			case 'mozillaPostalAddress2':
				$properties['workAddress']['address2'] = $attrib_pair[1];
				break;
			case 'l':
				$properties['workAddress']['city'] = $attrib_pair[1];
				break;
			case 'st':
				$properties['workAddress']['state'] = $attrib_pair[1];
				break;
			case 'postalCode':
				$properties['workAddress']['zip'] = $attrib_pair[1];
				break;
			case 'c':
				$properties['workAddress']['country'] = $attrib_pair[1];
				break;

			case 'homePostalAddress':
				$properties['homeAddress']['address1'] = $attrib_pair[1];
				break;
			case 'mozillaHomePostalAddress2':
				$properties['homeAddress']['address2'] = $attrib_pair[1];
				break;
			case 'mozillaHomeLocalityName':
				$properties['homeAddress']['city'] = $attrib_pair[1];
				break;
			case 'mozillaHomeState':
				$properties['homeAddress']['state'] = $attrib_pair[1];
				break;
			case 'mozillaHomePostalCode':
				$properties['homeAddress']['zip'] = $attrib_pair[1];
				break;
			case 'mozillaHomeCountryName':
				$properties['homeAddress']['country'] = $attrib_pair[1];
				break;

			case 'title':
				$properties['title'] = $attrib_pair[1];
				break;
			case 'ou':
				$properties['department'] = $attrib_pair[1];
				break;
			case 'o':
				$properties['organization'] = $attrib_pair[1];
				break;

			case 'telephoneNumber':
				$properties['phoneNumbers'][] = array($attrib_pair[1], 'WORK');
				break;
			case 'homePhone':
				$properties['phoneNumbers'][] = array($attrib_pair[1], 'HOME');
				break;
			case 'facsimileTelephoneNumber':
				$properties['phoneNumbers'][] = array($attrib_pair[1], 'FAX');
				break;
			case 'pager':
				$properties['phoneNumbers'][] = array($attrib_pair[1], 'PAGER');
				break;
			case 'mobile':
				$properties['phoneNumbers'][] = array($attrib_pair[1], 'CELL');
				break;

			case 'mozilla_AimScreenName':
				$properties['aimScreenName'] = $attrib_pair[1];
				break;

			case 'workurl':
				$properties['urls'][] = array($attrib_pair[1], 'WORK');
				break;
			case 'homeurl':
				$properties['urls'][] = array($attrib_pair[1], 'HOME');
				break;
		}
	}
	if(!isset($properties['fullName']) && !isset($properties['primaryEmail']))
		return '';
	
	$vcardout = "BEGIN:VCARD\r\nVERSION:2.1\r\nX-GWTYPE:USER\r\n";
	
	if(isset($properties['primaryEmail']))
	{
		$vcardout .= "EMAIL;WORK;PREF:{$properties['primaryEmail']}\r\n";
		$emailarray = explode("@", $properties['primaryEmail'], 2);
		if(count($emailarray) > 1)
		{
			$vcardout .= "X-GWUSERID:{$emailarray[0]}\r\nX-GWADDRFMT:0\r\nX-GWIDOMAIN:{$emailarray[1]}\r\n";
		}
	}
	
	if(isset($properties['secondEmail']))
		$vcardout .= "EMAIL:{$properties['secondEmail']}\r\n";
		
	if(isset($properties['phoneNumbers']))
	{
		foreach($properties['phoneNumbers'] as $phonenumber)
		{
			$vcardout .= "TEL;{$phonenumber[1]}:{$phonenumber[0]}\r\n";
		}
	}
	if(isset($properties['fullName']))
	{
		for($i = 0; $i < 2; $i++)
		{
			if(!isset($properties['fullName'][$i]))
				$properties['fullName'][$i] = '';
		}
		$vcardout .= "N:{$properties['fullName'][1]};{$properties['fullName'][0]};;;\r\n";
	}
	
	if(isset($properties['formattedName']))
		$vcardout .= "FN:{$properties['formattedName']}\r\n";
	else
	{
		$fullname = $properties['fullName'][0] . " " . $properties['fullName'][1];
		$vcardout .= "FN:$fullname\r\n";
	}
	
	if(isset($properties['workAddress']))
	{
		$address = $properties['workAddress'];
		$address = fool_proof_address($address);
		$vcardout .= "ADR;INTL;WORK;PARCEL;POSTAL:;;";
		$vcardout .= encode($address['address1']) . "\\n" . encode($address['address2']).";".
			encode($address['city']).";". encode($address['state']) . ";" .encode($address['zip']).";".
			encode($address['country']);
		$vcardout .= "\r\n";
	
		$label = "LABEL;INTL;WORK;PARCEL;POSTAL;ENCODING=QUOTED-PRINTABLE:";
		$label_content = '';
		foreach($address as $lad)
		{
			$label_content .= $lad . "\r\n";
		}
		$vcardout .= $label . quoted_printable_encode($label_content) . "\r\n";
		unset($label);
	}
	
	if(isset($properties['homeAddress']))
	{
		$address = $properties['homeAddress'];
		$address = fool_proof_address($address);
		$vcardout .= "ADR;INTL;HOME;PARCEL;POSTAL:;;";
		$vcardout .= encode($address['address1']) . "\\n" . encode($address['address2']).";".
			encode($address['city']).";". encode($address['state']) . ";" .encode($address['zip']).";".
			encode($address['country']);
		$vcardout .= "\r\n";
	
		$label = "LABEL;INTL;HOME;PARCEL;POSTAL;ENCODING=QUOTED-PRINTABLE:";
		$label_content = '';
		foreach($address as $lad)
		{
			$label_content .= $lad . "\r\n";
		}
		$vcardout .= $label . quoted_printable_encode($label_content) . "\r\n";
		unset($label);		
	}
	
	if(isset($properties['aimScreenName']))
		$vcardout .= "AOL:aim:{$properties['aimScreenName']}\r\n";
	
	if(isset($properties['title']))
		$vcardout .= "TITLE:{$properties['title']}\r\n";
		
	if(isset($properties['organization']))
	{
		$vcardout .= "ORG:{$properties['organization']};";
		if(isset($properties['department']))
			$vcardout .= $properties['department'];
		$vcardout .= "\r\n";
	}

	$vcardout .= "END:VCARD\r\n\r\n";
	return $vcardout;
}

function build_dist_list($entry)
{
	foreach($entry as $attrib_pair)
	{
		if($attrib_pair[0] == 'member' && $attrib_pair[1] != '')
		{
			if(substr($attrib_pair[1], 0, 3) == "cn=" || substr($attrib_pair[1], 0, 5) == "mail=")
			{
				$dns = explode(",", $attrib_pair[1]);
				foreach($dns as $dn_attrib)
				{
					$dn_pair = explode("=", $dn_attrib, 2);
					if($dn_pair[0] == 'cn')
					{
						$member[0] = $dn_pair[1];
					}
					if($dn_pair[0] == 'mail')
					{
						$member[1] = $dn_pair[1];
					}
				}
				if(!isset($member[0]) && isset($member[1]))
				$member[0] = $member[1];
				if(isset($member[1]))
				$members[] = $member;
				else
				{
					$member = array($attrib_pair[1], $attrib_pair[1]);
					$members[] = $member;
				}
				unset($member);
			}
			else 
			{
				$member[] = array($attrib_pair[1], $attrib_pair[1]);
			}
		}
		else 
		{
			if($attrib_pair[0] == 'dn')
			{
				$dns = explode(",", $attrib_pair[1]);
				foreach($dns as $dn_attrib)
				{
					$dn_pair = explode("=", $dn_attrib, 2);
					if($dn_pair[0] == 'cn')
					{
						$listname = $dn_pair[1];
					}
				}
			}
			if($attrib_pair[0] == 'cn')
				$listname = $attrib_pair[1];
		}
	}

	if(isset($members) && isset($listname))
	{
		$membercount = count($members);
		$vcardstream = "BEGIN:VCARD\r\nVERSION:2.1\r\nX-GWTYPE:GROUP\r\nFN:{$listname}\r\nN:{$listname}\r\n" .
			"X-DL:{$listname}({$membercount} items)\r\nEND:VCARD\r\n\r\n";
			
		foreach($members as $member)
		{
			$exploded = explode("@", $member[1], 2);
			if(count($exploded) > 1)
			{
				$vcardstream .= "BEGIN:VCARD\r\nVERSION:2.1\r\nX-GWTYPE:USER\r\nFN:{$member[0]}\r\n" .
					"EMAIL;WORK;PREF:{$member[1]}\r\nX-GWUSERID:{$exploded[0]}\r\nX-GWADDRFMT:0\r\n" .
					"X-GWIDOMAIN:{$exploded[1]}\r\nN:{$member[0]}\r\nX-GWTARGET:TO\r\nEND:VCARD\r\n\r\n";
			}
			else
			{
				$vcardstream .= "BEGIN:VCARD\r\nVERSION:2.1\r\nX-GWTYPE:USER\r\nFN:{$member[0]}\r\n" .
					"EMAIL;WORK;PREF:{$member[1]}\r\nX-GWADDRFMT:0\r\nN:{$member[0]}\r\n" .
					"X-GWTARGET:TO\r\nEND:VCARD\r\n\r\n";
			}
		}
		
		$vcardstream .= "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:{$listname}\r\nX-GWTYPE:GROUPEND\r\nEND:VCARD\r\n\r\n";
		return $vcardstream;
	}
	else 
		return '';
}

function convert_ldif_to_vcard($filein, $fileout)
{
	$oHandle = fopen($fileout, "w");
	$cardsprocessed = 0;
	$ldifs_raw = file($filein);
	$ldifs = implode('', $ldifs_raw);
	
	$ldif_array = explode("\n\n", $ldifs);
	foreach($ldif_array as $ldif_entry)
	{
		$lines = explode("\n", $ldif_entry);
		$pairs = array();
		foreach($lines as $line)
		{
			$attrib_pair = explode(": ", $line, 2);
			if(isset($attrib_pair[1]))
			{
				$attrib_pair[1] = ereg_replace("[\x01-\x1F'\"`]", "", $attrib_pair[1]);
				$pairs[] = $attrib_pair;
			}
		}
		$ldif_entries[] = $pairs;
	}
	
	foreach($ldif_entries as $entry)
	{
		$vcardstream = '';
		$isperson = false;
		foreach($entry as $entry_pair)
		{	
			if($entry_pair[0] == 'objectclass' && $entry_pair[1] == "person")
				$isperson = true;
		}	
		if($isperson == true) // Handle the person ldif entries.
		{
			$vcardstream .= parse_person_ldif($entry);
			$cardsprocessed++;
		}
		else // Handle the dist. list ldif entries.
		{
			$vcardstream .= build_dist_list($entry);
			$cardsprocessed++;
		}
		fwrite($oHandle, $vcardstream);
	}
	fclose($oHandle);
	return $cardsprocessed;
}

function string_convert_ldif_to_vcard($stringin, & $stringout)
{
	$cardsprocessed = 0;	
	$ldif_array = explode("\r\n\r\n", $stringin);
	foreach($ldif_array as $ldif_entry)
	{
		$lines = explode("\n", $ldif_entry);
		$pairs = array();
		foreach($lines as $line)
		{
			$attrib_pair = explode(": ", $line, 2);
			if(isset($attrib_pair[1]))
			{
				$attrib_pair[1] = ereg_replace("[\x01-\x1F'\"`]", "", $attrib_pair[1]);
				$pairs[] = $attrib_pair;
			}
		}
		$ldif_entries[] = $pairs;
	}
	
	foreach($ldif_entries as $entry)
	{
		$vcardstream = '';
		$isperson = false;
		foreach($entry as $entry_pair)
		{	
			if($entry_pair[0] == 'objectclass' && $entry_pair[1] == "person")
				$isperson = true;
		}	
		if($isperson == true) // Handle the person ldif entries.
		{
			$vcardstream .= parse_person_ldif($entry);
			$cardsprocessed++;
		}
		else // Handle the dist. list ldif entries.
		{
			$vcardstream .= build_dist_list($entry);
			$cardsprocessed++;
		}
		$stringout .= $vcardstream;
	}
	return $cardsprocessed;
}

if(isset($argv[1]) && isset($argv[2]))
{
	echo "LDIF to vCard Conversion Script\r\n";
	echo "Copyright (c) 2006 by Jonathan Reams\r\n";
	$cardsprocessed = convert_ldif_to_vcard($argv[1], $argv[2]);
	echo "Converted {$argv[1]} to {$argv[2]} ($cardsprocessed) cards converted.\r\n";
}
else 
{
	echo "LDIF to vCard Conversion Script\r\n";
	echo "Copyright (c) 2006 by Jonathan Reams\r\n";
	echo "\r\nphp {$argv[0]} inputfile outputfile\r\n";
	echo "inputfile - LDIF file you wish to convert.\r\n";
	echo "outputfile - vCard file the script should output to.\r\n";
}

?>