From: <abe...@us...> - 2013-10-28 19:38:09
|
Revision: 6250 http://sourceforge.net/p/astlinux/code/6250 Author: abelbeck Date: 2013-10-28 19:38:06 +0000 (Mon, 28 Oct 2013) Log Message: ----------- web interface, add LDAP-AB tab for managing the LDAP Address Book Modified Paths: -------------- branches/1.0/package/webinterface/altweb/admin/prefs.php branches/1.0/package/webinterface/altweb/admin/slapd.php branches/1.0/package/webinterface/altweb/admin/system.php branches/1.0/package/webinterface/altweb/common/header.php Added Paths: ----------- branches/1.0/package/webinterface/altweb/admin/ldapab.php branches/1.0/package/webinterface/altweb/common/vcard-parse-convert.php Added: branches/1.0/package/webinterface/altweb/admin/ldapab.php =================================================================== --- branches/1.0/package/webinterface/altweb/admin/ldapab.php (rev 0) +++ branches/1.0/package/webinterface/altweb/admin/ldapab.php 2013-10-28 19:38:06 UTC (rev 6250) @@ -0,0 +1,416 @@ +<?php + +// Copyright (C) 2008-2013 Lonnie Abelbeck +// This is free software, licensed under the GNU General Public License +// version 3 as published by the Free Software Foundation; you can +// redistribute it and/or modify it under the terms of the GNU +// General Public License; and comes with ABSOLUTELY NO WARRANTY. + +// ldapad.php for AstLinux +// 10-26-2013 +// +// System location of rc.conf file +$CONFFILE = '/etc/rc.conf'; + +$myself = $_SERVER['PHP_SELF']; + +require_once '../common/functions.php'; + +require_once '../common/vcard-parse-convert.php'; + +$action_menu = array ( + '' => '– select action –', + 'export' => 'Export as LDIF', + 'revert' => 'Revert to Previous' +); + +// Function: exportLDIF +// +function exportLDIF($rootpw, $ou) { + global $global_prefs; + + if ($rootpw === '') { + return(10); + } + + if (($backup_name = get_HOSTNAME_DOMAIN()) === '') { + $backup_name = $_SERVER['SERVER_NAME']; + } + if (getPREFdef($global_prefs, 'system_backup_hostname_domain') !== 'yes') { + if (($pos = strpos($backup_name, '.')) !== FALSE) { + $backup_name = substr($backup_name, 0, $pos); + } + } + $prefix = '/mnt/kd/.'; + $tmpfile = $backup_name.'-'.$ou.'-'.date('Y-m-d').'.ldif.txt'; + $bdn = trim(shell_exec('. /etc/rc.conf; echo "${LDAP_SERVER_BASEDN:-dc=ldap}"')); + + $admin = tempnam("/var/tmp", "PHP_"); + $auth = '-x -D "cn=admin,'.$bdn.'" -H ldap://127.0.0.1 -y '.$admin; + $cmd = '/usr/bin/ldapsearch '.$auth.' -b "ou='.$ou.','.$bdn.'" -LLL'; + @file_put_contents($admin, $rootpw); + shell($cmd.' >'.$prefix.$tmpfile.' 2>/dev/null', $status); + @unlink($admin); + + if ($status != 0) { + @unlink($prefix.$tmpfile); + return(($status == 49) ? 28 : 29); + } else { + header('Content-Type: text/plain'); + header('Content-Disposition: attachment; filename="'.$tmpfile.'"'); + header('Content-Length: '.filesize($prefix.$tmpfile)); + ob_clean(); + flush(); + @readfile($prefix.$tmpfile); + @unlink($prefix.$tmpfile); + exit; + } +} + +// Function: revertLDIF +// +function revertLDIF($rootpw, $ou) { + + $bdn = trim(shell_exec('. /etc/rc.conf; echo "${LDAP_SERVER_BASEDN:-dc=ldap}"')); + $ou_old = $ou.'-old'; + $ou_tmp = $ou.'-tmp'; + + if ($rootpw === '') { + return(10); + } + + $admin = tempnam("/var/tmp", "PHP_"); + $auth = '-x -D "cn=admin,'.$bdn.'" -H ldap://127.0.0.1 -y '.$admin; + $cmd = '/usr/bin/ldapsearch '.$auth.' -b "ou='.$ou_old.','.$bdn.'" -LLL "(ou='.$ou_old.')"'; + @file_put_contents($admin, $rootpw); + shell($cmd.' >/dev/null 2>/dev/null', $status); + if ($status != 0) { + @unlink($admin); + if ($status == 49) { + return(28); + } + return(31); + } else { + $cmd = '/usr/bin/ldapdelete '.$auth.' -r "ou='.$ou_tmp.','.$bdn.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $cmd .= '/usr/bin/ldapmodrdn '.$auth.' -r "ou='.$ou.','.$bdn.'" "ou='.$ou_tmp.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $cmd .= '/usr/bin/ldapmodrdn '.$auth.' -r "ou='.$ou_old.','.$bdn.'" "ou='.$ou.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $cmd .= '/usr/bin/ldapmodrdn '.$auth.' -r "ou='.$ou_tmp.','.$bdn.'" "ou='.$ou_old.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $rtn = 41; + } + shell($cmd.' >/dev/null 2>/dev/null', $status); + @unlink($admin); + if ($status != 0) { + $rtn = 99; + } + return($rtn); +} + +// Function: importLDIF +// +function importLDIF($rootpw, $ou, $name, &$count) { + + $count = 0; + $bdn = trim(shell_exec('. /etc/rc.conf; echo "${LDAP_SERVER_BASEDN:-dc=ldap}"')); + $ou_old = $ou.'-old'; + + $cmd = 'grep -qi "^dn:[^,]*ou='.$ou.','.$bdn.'$" '.$name; + shell($cmd.' >/dev/null 2>/dev/null', $status); + if ($status != 0) { + return(23); + } + if ($rootpw === '') { + return(10); + } + $count = (int)trim(shell_exec('grep -ci "^dn:" '.$name)); + + $admin = tempnam("/var/tmp", "PHP_"); + $auth = '-x -D "cn=admin,'.$bdn.'" -H ldap://127.0.0.1 -y '.$admin; + $cmd = '/usr/bin/ldapdelete '.$auth.' -r "ou='.$ou_old.','.$bdn.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $cmd .= '/usr/bin/ldapmodrdn '.$auth.' -r "ou='.$ou.','.$bdn.'" "ou='.$ou_old.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $cmd .= '/usr/bin/ldapadd '.$auth.' -f '.$name; + @file_put_contents($admin, $rootpw); + shell($cmd.' >/dev/null 2>/dev/null', $status); + if ($status != 0) { + if ($status == 49) { + @unlink($admin); + return(28); + } + $cmd = '/usr/bin/ldapdelete '.$auth.' -r "ou='.$ou.','.$bdn.'"'; + $cmd .= ' >/dev/null 2>/dev/null ; '; + $cmd .= '/usr/bin/ldapmodrdn '.$auth.' -r "ou='.$ou_old.','.$bdn.'" "ou='.$ou.'"'; + $rtn = 30; + } else { + @unlink($admin); + return(40); + } + shell($cmd.' >/dev/null 2>/dev/null', $status); + @unlink($admin); + if ($status != 0) { + $rtn = 99; + } + return($rtn); +} + +// Function: importVCARD +// +function importVCARD($rootpw, $ou, $name, &$count) { + + $count = 0; + $bdn = trim(shell_exec('. /etc/rc.conf; echo "${LDAP_SERVER_BASEDN:-dc=ldap}"')); + $out_file = tempnam("/mnt/kd", ".PHP_"); + + if (vcard_export($ou, $bdn, $name, $out_file) === FALSE) { + @unlink($out_file); + return(23); + } + $rtn = importLDIF($rootpw, $ou, $out_file, $count); + @unlink($out_file); + return($rtn); +} + +function vcard_export($ou, $bdn, $in_file, $out_file) { + + $options = array( + 'mailonly' => isset($_POST['opt_m']), + 'phoneonly' => isset($_POST['opt_p']), + 'sanitize' => isset($_POST['opt_s']), + 'sanitize_dash' => isset($_POST['opt_S']) + ); + + // parse a vCard file + $conv = new vcard_convert($options); + if (! $conv->fromFile($in_file)) { + return(FALSE); + } + $out = $conv->toLdif("ou=$ou,$bdn"); + $ou_dn = "dn: ou=$ou,$bdn\nobjectClass: organizationalUnit\nou: $ou\n\n"; + if (($fp = @fopen($out_file,"wb")) === FALSE) { + return(FALSE); + } + fwrite($fp, $ou_dn); + fwrite($fp, $out); + fclose($fp); + + return(TRUE); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $result = 1; + $rootpw = isset($_POST['rootpw']) ? tuqd($_POST['rootpw']) : ''; + if (! $global_staff) { + $result = 999; + } elseif (isset($_POST['submit_action'])) { + $action = $_POST['addressbook_action']; + if ($action === 'export') { + $result = exportLDIF($rootpw, 'addressbook'); + } elseif ($action === 'revert') { + $result = revertLDIF($rootpw, 'addressbook'); + } else { + $result = 11; + } + } elseif (isset($_POST['submit_ldif'], $_FILES['import_ldif'])) { + $result = 1; + $error = $_FILES['import_ldif']['error']; + $tmp_name = $_FILES['import_ldif']['tmp_name']; + $name = basename($_FILES['import_ldif']['name']); + if ($error == 0) { + $size = filesize($tmp_name); + if ($size === FALSE || $size > 5000000 || $size == 0) { + $result = 20; + } else { + $suffix = '.ldif.txt'; + if (($len = strlen($name) - strlen($suffix)) < 0) { + $len = 0; + } + if (stripos($name, $suffix, $len) === FALSE) { + $result = 22; + } + } + } elseif ($error == 1 || $error == 2) { + $result = 20; + } else { + $result = 21; + } + if ($result == 1) { + $result = 99; + $name = '/mnt/kd/.import_ldif'.$suffix; + if (move_uploaded_file($tmp_name, $name)) { + $result = importLDIF($rootpw, 'addressbook', $name, $count); + } + if (is_file($name)) { + @unlink($name); + } + header('Location: '.$myself.'?result='.$result.'&count='.$count); + exit; + } + } elseif (isset($_POST['submit_vcard'], $_FILES['import_vcard'])) { + $result = 1; + $error = $_FILES['import_vcard']['error']; + $tmp_name = $_FILES['import_vcard']['tmp_name']; + $name = basename($_FILES['import_vcard']['name']); + if ($error == 0) { + $size = filesize($tmp_name); + if ($size === FALSE || $size > 5000000 || $size == 0) { + $result = 20; + } else { + $suffix = '.vcf'; + if (($len = strlen($name) - strlen($suffix)) < 0) { + $len = 0; + } + if (stripos($name, $suffix, $len) === FALSE) { + $result = 26; + } + } + } elseif ($error == 1 || $error == 2) { + $result = 20; + } else { + $result = 27; + } + if ($result == 1) { + $result = 99; + $name = '/mnt/kd/.import_vcard'.$suffix; + if (move_uploaded_file($tmp_name, $name)) { + $result = importVCARD($rootpw, 'addressbook', $name, $count); + } + if (is_file($name)) { + @unlink($name); + } + header('Location: '.$myself.'?result='.$result.'&count='.$count); + exit; + } + } + header('Location: '.$myself.'?result='.$result); + exit; +} else { // Start of HTTP GET +$ACCESS_RIGHTS = 'staff'; +require_once '../common/header.php'; + + putHtml("<center>"); + if (isset($_GET['result'])) { + $result = $_GET['result']; + if ($result == 10) { + putHtml('<p style="color: red;">"cn=admin" Password not specified.</p>'); + } elseif ($result == 11) { + putHtml('<p style="color: red;">No Action, select an action command.</p>'); + } elseif ($result == 20) { + putHtml('<p style="color: red;">File size must be less then 5 MBytes.</p>'); + } elseif ($result == 21) { + putHtml('<p style="color: red;">An input .ldif.txt file must be defined.</p>'); + } elseif ($result == 22) { + putHtml('<p style="color: red;">Invalid suffix, only LDIF files ending with .ldif.txt are allowed.</p>'); + } elseif ($result == 23) { + putHtml('<p style="color: red;">Invalid file format, no data changed.</p>'); + } elseif ($result == 24) { + putHtml('<p style="color: red;">Error importing LDAP data, no data changed.</p>'); + } elseif ($result == 26) { + putHtml('<p style="color: red;">Invalid suffix, only vCard files ending with .vcf are allowed.</p>'); + } elseif ($result == 27) { + putHtml('<p style="color: red;">An input .vcf file must be defined.</p>'); + } elseif ($result == 28) { + putHtml('<p style="color: red;">Invalid "cn=admin" credentials.</p>'); + } elseif ($result == 29) { + putHtml('<p style="color: red;">Address Book export failed.</p>'); + } elseif ($result == 30) { + putHtml('<p style="color: red;">Address Book import failed.</p>'); + } elseif ($result == 31) { + putHtml('<p style="color: red;">No previous Address Book to revert to.</p>'); + } elseif ($result == 40) { + $count = (isset($_GET['count'])) ? $_GET['count'] : '0'; + putHtml('<p style="color: green;">Successful LDAP Address Book import. Count: '.$count.' entries.</p>'); + } elseif ($result == 41) { + putHtml('<p style="color: green;">Reverted to previous LDAP Address Book.</p>'); + } elseif ($result == 99) { + putHtml('<p style="color: red;">Action Failed.</p>'); + } elseif ($result == 999) { + putHtml('<p style="color: red;">Permission denied for user "'.$global_user.'".</p>'); + } else { + putHtml('<p style="color: orange;">No Action.</p>'); + } + } else { + putHtml('<p> </p>'); + } + putHtml("</center>"); +?> + <center> + <table class="layout"><tr><td><center> + <form method="post" action="<?php echo $myself;?>" enctype="multipart/form-data"> + <table width="100%" class="stdtable"> + <tr><td style="text-align: center;" colspan="2"> + <h2>LDAP Address Book Management:</h2> + </td></tr> +<?php + +if (is_file('/var/run/slapd/slapd.pid')) { + $db = parseRCconf($CONFFILE); + + $rootpw = getVARdef($db, 'LDAP_SERVER_PASS'); + putHtml('<tr><td style="text-align: center;" colspan="2">'); + putHtml('"cn=admin" Password:'); + if ($rootpw !== '') { + putHtml('**********'); + putHtml('<input type="hidden" name="rootpw" value="'.$rootpw.'" />'); + } else { + putHtml('<input type="password" size="18" maxlength="128" name="rootpw" value="" />'); + } + putHtml('</td></tr>'); + + putHtml('<tr><td style="text-align: center;" colspan="2">'); + putHtml('<select name="addressbook_action">'); + foreach ($action_menu as $key => $value) { + putHtml('<option value="'.$key.'"'.$sel.'>'.$value.'</option>'); + } + putHtml('</select>'); + putHtml('–'); + putHtml('<input type="submit" value="LDAP Address Book" name="submit_action" />'); + putHtml('</td></tr>'); + + putHtml('<tr><td style="text-align: center;" colspan="2">'); + putHtml('<h2>Import LDIF File to Address Book:</h2>'); + putHtml('<input type="hidden" name="MAX_FILE_SIZE" value="5000000" />'); + putHtml('</td></tr><tr><td style="text-align: center;" colspan="2">'); + putHtml('<input type="file" name="import_ldif" />'); + putHtml('–'); + putHtml('<input type="submit" name="submit_ldif" value="Import LDIF" />'); + putHtml('</td></tr>'); + + putHtml('<tr><td style="text-align: center;" colspan="2">'); + putHtml('<h2>Import vCard File to Address Book:</h2>'); + putHtml('<input type="hidden" name="MAX_FILE_SIZE" value="5000000" />'); + putHtml('</td></tr>'); + putHtml('<tr><td class="dialogText" style="text-align: right;" width="60">'); + putHtml('<strong>Filter:</strong>'); + putHtml('</td><td class="dialogText">Options</td></tr>'); + putHtml('<tr><td class="dialogText" style="text-align: right;">'); + putHtml('<input type="checkbox" value="opt_s" name="opt_s" /></td><td>Sanitize phone numbers to only<br />include "+0123456789" characters</td></tr>'); + putHtml('<tr><td class="dialogText" style="text-align: right;">'); + putHtml('<input type="checkbox" value="opt_S" name="opt_S" checked="checked" /></td><td>Sanitize as above but replace<br />sequential non-numbers with a dash "-"</td></tr>'); + putHtml('<tr><td class="dialogText" style="text-align: right;">'); + putHtml('<input type="checkbox" value="opt_p" name="opt_p" /></td><td>Skip vCards without phone numbers</td></tr>'); + putHtml('<tr><td class="dialogText" style="text-align: right;">'); + putHtml('<input type="checkbox" value="opt_m" name="opt_m" /></td><td>Skip vCards without e-mail addresses</td></tr>'); + putHtml('<tr><td style="text-align: center;" colspan="2">'); + putHtml('<input type="file" name="import_vcard" />'); + putHtml('–'); + putHtml('<input type="submit" name="submit_vcard" value="Import vCard" />'); + putHtml('</td></tr>'); +} else { + putHtml('<tr><td style="text-align: center;" colspan="2">'); + putHtml('<p style="color: red;">LDAP Server is not enabled.</p>'); + putHtml('</td></tr>'); +} + + putHtml('</table>'); + putHtml('</form>'); + + putHtml("</center></td></tr></table>"); + putHtml("</center>"); +} // End of HTTP GET +require_once '../common/footer.php'; + +?> Property changes on: branches/1.0/package/webinterface/altweb/admin/ldapab.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Modified: branches/1.0/package/webinterface/altweb/admin/prefs.php =================================================================== --- branches/1.0/package/webinterface/altweb/admin/prefs.php 2013-10-25 21:27:10 UTC (rev 6249) +++ branches/1.0/package/webinterface/altweb/admin/prefs.php 2013-10-28 19:38:06 UTC (rev 6250) @@ -419,6 +419,10 @@ $value = 'tab_sqldata_disable_staff = no'; fwrite($fp, $value."\n"); } + if (isset($_POST['tab_ldapab'])) { + $value = 'tab_ldapab_show = yes'; + fwrite($fp, $value."\n"); + } if (isset($_POST['tab_users'])) { $value = 'tab_users_show = yes'; fwrite($fp, $value."\n"); @@ -1106,6 +1110,10 @@ putHtml('<input type="checkbox" value="sqldata_disable_staff" name="sqldata_disable_staff"'.$sel.' /> Disable SQL-Data Tab for "staff" user</td></tr>'); putHtml('<tr class="dtrow1"><td style="text-align: right;">'); + $sel = (getPREFdef($global_prefs, 'tab_ldapab_show') === 'yes') ? ' checked="checked"' : ''; + putHtml('<input type="checkbox" value="tab_ldapab" name="tab_ldapab"'.$sel.' /></td><td colspan="5">Show LDAP-AB Tab</td></tr>'); + + putHtml('<tr class="dtrow1"><td style="text-align: right;">'); $sel = (getPREFdef($global_prefs, 'tab_users_show') === 'yes') ? ' checked="checked"' : ''; putHtml('<input type="checkbox" value="tab_users" name="tab_users"'.$sel.' /></td><td colspan="5">Show Users Tab</td></tr>'); Modified: branches/1.0/package/webinterface/altweb/admin/slapd.php =================================================================== --- branches/1.0/package/webinterface/altweb/admin/slapd.php 2013-10-25 21:27:10 UTC (rev 6249) +++ branches/1.0/package/webinterface/altweb/admin/slapd.php 2013-10-28 19:38:06 UTC (rev 6250) @@ -109,7 +109,7 @@ $pass2 = tuqd($_POST['pass2']); if (isset($_POST['submit_password']) || ($pass1 !== '' && $pass2 !== '')) { if (($user = $_POST['username']) !== '') { - $result = set_LDAP_user_passwd($rootpw, $pass1, $pass2, $user, 4); + $result = set_LDAP_user_passwd($rootpw, $pass1, $pass2, $user, 3); } } } @@ -250,7 +250,12 @@ putHtml('<tr><td style="text-align: right;" colspan="2">'); putHtml('"cn=admin" Password:'); putHtml('</td><td style="text-align: left;" colspan="4">'); - putHtml('<input type="password" size="56" maxlength="128" name="rootpw" value="'.$rootpw.'" />'); + if ($rootpw !== '') { + putHtml('**********'); + putHtml('<input type="hidden" name="rootpw" value="'.$rootpw.'" />'); + } else { + putHtml('<input type="password" size="18" maxlength="128" name="rootpw" value="'.$rootpw.'" />'); + } putHtml('</td></tr>'); putHtml('<tr><td style="text-align: right;" colspan="2">'); putHtml('New Password:'); Modified: branches/1.0/package/webinterface/altweb/admin/system.php =================================================================== --- branches/1.0/package/webinterface/altweb/admin/system.php 2013-10-25 21:27:10 UTC (rev 6249) +++ branches/1.0/package/webinterface/altweb/admin/system.php 2013-10-28 19:38:06 UTC (rev 6250) @@ -851,6 +851,7 @@ $var === 'DDPASS' || $var === 'GUI_FIREWALL_RULES' || $var === 'STATICHOSTS' || + $var === 'LDAP_SERVER_PASS' || $var === 'PPTP_USER_PASS' || $var === 'OVPN_USER_PASS' || $var === 'OVPNC_USER_PASS' || Modified: branches/1.0/package/webinterface/altweb/common/header.php =================================================================== --- branches/1.0/package/webinterface/altweb/common/header.php 2013-10-25 21:27:10 UTC (rev 6249) +++ branches/1.0/package/webinterface/altweb/common/header.php 2013-10-28 19:38:06 UTC (rev 6250) @@ -238,6 +238,9 @@ if (($global_admin || $global_staff_enable_sqldata) && (getPREFdef($global_prefs, 'tab_sqldata_show') === 'yes')) { putHtml('<li><a href="/admin/sqldata.php"><span>SQL-Data</span></a></li>'); } + if ($global_staff && (getPREFdef($global_prefs, 'tab_ldapab_show') === 'yes')) { + putHtml('<li><a href="/admin/ldapab.php"><span>LDAP-AB</span></a></li>'); + } if ($global_staff && (getPREFdef($global_prefs, 'tab_users_show') === 'yes')) { putHtml('<li><a href="/admin/users.php"><span>Users</span></a></li>'); } Added: branches/1.0/package/webinterface/altweb/common/vcard-parse-convert.php =================================================================== --- branches/1.0/package/webinterface/altweb/common/vcard-parse-convert.php (rev 0) +++ branches/1.0/package/webinterface/altweb/common/vcard-parse-convert.php 2013-10-28 19:38:06 UTC (rev 6250) @@ -0,0 +1,1974 @@ +<?php + +//AstLinux// Adapted for AstLinux - 10-11-2013 by Lonnie Abelbeck +//AstLinux// All code in one file, use name "vcard-export" +//AstLinux// Default for 'ldap' format +//AstLinux// Change -n option to -b for Base_DN +//AstLinux// Add sanitize phone numbers option, -s and -S + +/* + +-----------------------------------------------------------------------+ + | Commandline vCard converter | + | Version 0.8.7 | + | | + | Copyright (C) 2006-2012, Thomas Bruederli - Switzerland | + | Licensed under the GNU GPL | + | | + | Type './vcard-export help' for usage information | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <th...@br...> | + +-----------------------------------------------------------------------+ + +*/ + +@ini_set('error_reporting', E_ALL&~E_NOTICE); + +// version 1.31 required + +// +// begin of Contact_Vcard_Parse.php +// + +// +----------------------------------------------------------------------+ +// | PHP version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2002 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 2.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/2_02.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | li...@ph... so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: Paul M. Jones <pm...@ph...> | +// +----------------------------------------------------------------------+ +// +// $Id: Contact_Vcard_Parse.php,v 1.4 2005/05/28 15:40:17 pmjones Exp $ + + +/** +* +* Parser for vCards. +* +* This class parses vCard 2.1 and 3.0 sources from file or text into a +* structured array. +* +* Usage: +* +* <code> +* // include this class file +* require_once 'Contact_Vcard_Parse.php'; +* +* // instantiate a parser object +* $parse = new Contact_Vcard_Parse(); +* +* // parse a vCard file and store the data +* // in $cardinfo +* $cardinfo = $parse->fromFile('sample.vcf'); +* +* // view the card info array +* echo '<pre>'; +* print_r($cardinfo); +* echo '</pre>'; +* </code> +* +* +* @author Paul M. Jones <pm...@ph...> +* +* @package Contact_Vcard_Parse +* +* @version 1.31 +* +*/ + +class Contact_Vcard_Parse { + + + /** + * + * Reads a file for parsing, then sends it to $this->fromText() + * and returns the results. + * + * @access public + * + * @param array $filename The filename to read for vCard information. + * + * @return array An array of of vCard information extracted from the + * file. + * + * @see Contact_Vcard_Parse::fromText() + * + * @see Contact_Vcard_Parse::_fromArray() + * + */ + + function fromFile($filename, $decode_qp = true) + { + $text = $this->fileGetContents($filename); + + if ($text === false) { + return false; + } else { + // dump to, and get return from, the fromText() method. + return $this->fromText($text, $decode_qp); + } + } + + + /** + * + * Reads the contents of a file. Included for users whose PHP < 4.3.0. + * + * @access public + * + * @param array $filename The filename to read for vCard information. + * + * @return string|bool The contents of the file if it exists and is + * readable, or boolean false if not. + * + * @see Contact_Vcard_Parse::fromFile() + * + */ + + function fileGetContents($filename) + { + if (file_exists($filename) && + is_readable($filename)) { + + $text = ''; + $len = filesize($filename); + + $fp = fopen($filename, 'r'); + while ($line = fread($fp, filesize($filename))) { + $text .= $line; + } + fclose($fp); + + return $text; + + } else { + + return false; + + } + } + + + /** + * + * Prepares a block of text for parsing, then sends it through and + * returns the results from $this->fromArray(). + * + * @access public + * + * @param array $text A block of text to read for vCard information. + * + * @return array An array of vCard information extracted from the + * source text. + * + * @see Contact_Vcard_Parse::_fromArray() + * + */ + + function fromText($text, $decode_qp = true) + { + // convert all kinds of line endings to Unix-standard and get + // rid of double blank lines. + $this->convertLineEndings($text); + + // unfold lines. concat two lines where line 1 ends in \n and + // line 2 starts with a whitespace character. only removes + // the first whitespace character, leaves others in place. + $fold_regex = '(\n)([ |\t])'; + $text = preg_replace("/$fold_regex/i", "", $text); + + // massage for Macintosh OS X Address Book (remove nulls that + // Address Book puts in for unicode chars) + $text = str_replace("\x00", '', $text); + + // convert the resulting text to an array of lines + $lines = explode("\n", $text); + + // parse the array of lines and return vCard info + return $this->_fromArray($lines, $decode_qp); + } + + + /** + * + * Converts line endings in text. + * + * Takes any text block and converts all line endings to UNIX + * standard. DOS line endings are \r\n, Mac are \r, and UNIX is \n. + * + * NOTE: Acts on the text block in-place; does not return a value. + * + * @access public + * + * @param string $text The string on which to convert line endings. + * + * @return void + * + */ + + function convertLineEndings(&$text) + { + // DOS + $text = str_replace("\r\n", "\n", $text); + + // Mac + $text = str_replace("\r", "\n", $text); + } + + + /** + * + * Splits a string into an array at semicolons. Honors backslash- + * escaped semicolons (i.e., splits at ';' not '\;'). + * + * @access public + * + * @param string $text The string to split into an array. + * + * @param bool $convertSingle If splitting the string results in a + * single array element, return a string instead of a one-element + * array. + * + * @return mixed An array of values, or a single string. + * + */ + + function splitBySemi($text, $convertSingle = false) + { + // we use these double-backs (\\) because they get get converted + // to single-backs (\) by preg_split. the quad-backs (\\\\) end + // up as as double-backs (\\), which is what preg_split requires + // to indicate a single backslash (\). what a mess. + $regex = '(?<!\\\\)(\;)'; + $tmp = preg_split("/$regex/i", $text); + + // if there is only one array-element and $convertSingle is + // true, then return only the value of that one array element + // (instead of returning the array). + if ($convertSingle && count($tmp) == 1) { + return $tmp[0]; + } else { + return $tmp; + } + } + + + /** + * + * Splits a string into an array at commas. Honors backslash- + * escaped commas (i.e., splits at ',' not '\,'). + * + * @access public + * + * @param string $text The string to split into an array. + * + * @param bool $convertSingle If splitting the string results in a + * single array element, return a string instead of a one-element + * array. + * + * @return mixed An array of values, or a single string. + * + */ + + function splitByComma($text, $convertSingle = false) + { + // we use these double-backs (\\) because they get get converted + // to single-backs (\) by preg_split. the quad-backs (\\\\) end + // up as as double-backs (\\), which is what preg_split requires + // to indicate a single backslash (\). ye gods, how ugly. + $regex = '(?<!\\\\)(\,)'; + $tmp = preg_split("/$regex/i", $text); + + // if there is only one array-element and $convertSingle is + // true, then return only the value of that one array element + // (instead of returning the array). + if ($convertSingle && count($tmp) == 1) { + return $tmp[0]; + } else { + return $tmp; + } + } + + + /** + * + * Used to make string human-readable after being a vCard value. + * + * Converts... + * \: => : + * \; => ; + * \, => , + * literal \n => newline + * + * @access public + * + * @param mixed $text The text to unescape. + * + * @return void + * + */ + + function unescape(&$text) + { + if (is_array($text)) { + foreach ($text as $key => $val) { + $this->unescape($val); + $text[$key] = $val; + } + } else { + $text = str_replace('\:', ':', $text); + $text = str_replace('\;', ';', $text); + $text = str_replace('\,', ',', $text); + $text = str_replace('\n', "\n", $text); + } + } + + + /** + * + * Emulated destructor. + * + * @access private + * @return boolean true + * + */ + + function _Contact_Vcard_Parse() + { + return true; + } + + + /** + * + * Parses an array of source lines and returns an array of vCards. + * Each element of the array is itself an array expressing the types, + * parameters, and values of each part of the vCard. Processes both + * 2.1 and 3.0 vCard sources. + * + * @access private + * + * @param array $source An array of lines to be read for vCard + * information. + * + * @return array An array of of vCard information extracted from the + * source array. + * + */ + + function _fromArray($source, $decode_qp = true) + { + // the info array will hold all resulting vCard information. + $info = array(); + + // tells us whether the source text indicates the beginning of a + // new vCard with a BEGIN:VCARD tag. + $begin = false; + + // holds information about the current vCard being read from the + // source text. + $card = array(); + + // loop through each line in the source array + foreach ($source as $line) { + + // if the line is blank, skip it. + if (trim($line) == '') { + continue; + } + + // find the first instance of ':' on the line. The part + // to the left of the colon is the type and parameters; + // the part to the right of the colon is the value data. + $pos = strpos($line, ':'); + + // if there is no colon, skip the line. + if ($pos === false) { + continue; + } + + // get the left and right portions + $left = trim(substr($line, 0, $pos)); + $right = trim(substr($line, $pos+1, strlen($line))); + + // have we started yet? + if (! $begin) { + + // nope. does this line indicate the beginning of + // a new vCard? + if (strtoupper($left) == 'BEGIN' && + strtoupper($right) == 'VCARD') { + + // tell the loop that we've begun a new card + $begin = true; + } + + // regardless, loop to the next line of source. if begin + // is still false, the next loop will check the line. if + // begin has now been set to true, the loop will start + // collecting card info. + continue; + + } else { + + // yep, we've started, but we don't know how far along + // we are in the card. is this the ending line of the + // current vCard? + if (strtoupper($left) == 'END' && + strtoupper($right) == 'VCARD') { + + // yep, we're done. keep the info from the current + // card... + $info[] = $card; + + // ...and reset to grab a new card if one exists in + // the source array. + $begin = false; + $card = array(); + + } else { + + // we're not on an ending line, so collect info from + // this line into the current card. split the + // left-portion of the line into a type-definition + // (the kind of information) and parameters for the + // type. + $typedef = $this->_getTypeDef($left); + $params = $this->_getParams($left); + + // if we are decoding quoted-printable, do so now. + // QUOTED-PRINTABLE is not allowed in version 3.0, + // but we don't check for versioning, so we do it + // regardless. ;-) + $this->_decode_qp($params, $right); + + // now get the value-data from the line, based on + // the typedef + switch ($typedef) { + + case 'N': + // structured name of the person + $value = $this->_parseN($right); + break; + + case 'ADR': + // structured address of the person + $value = $this->_parseADR($right); + break; + + case 'NICKNAME': + // nicknames + $value = $this->_parseNICKNAME($right); + break; + + case 'ORG': + // organizations the person belongs to + $value = $this->_parseORG($right); + break; + + case 'CATEGORIES': + // categories to which this card is assigned + $value = $this->_parseCATEGORIES($right); + break; + + case 'GEO': + // geographic coordinates + $value = $this->_parseGEO($right); + break; + + default: + // by default, just grab the plain value. keep + // as an array to make sure *all* values are + // arrays. for consistency. ;-) + $value = array(array($right)); + break; + } + + // add the type, parameters, and value to the + // current card array. note that we allow multiple + // instances of the same type, which might be dumb + // in some cases (e.g., N). + $card[$typedef][] = array( + 'param' => $params, + 'value' => $value + ); + } + } + } + + $this->unescape($info); + return $info; + } + + + /** + * + * Takes a vCard line and extracts the Type-Definition for the line. + * + * @access private + * + * @param string $text A left-part (before-the-colon part) from a + * vCard line. + * + * @return string The type definition for the line. + * + */ + + function _getTypeDef($text) + { + // split the text by semicolons + $split = $this->splitBySemi($text); + + // only return first element (the typedef) + return strtoupper($split[0]); + } + + + /** + * + * Finds the Type-Definition parameters for a vCard line. + * + * @access private + * + * @param string $text A left-part (before-the-colon part) from a + * vCard line. + * + * @return mixed An array of parameters. + * + */ + + function _getParams($text) + { + // split the text by semicolons into an array + $split = $this->splitBySemi($text); + + // drop the first element of the array (the type-definition) + array_shift($split); + + // set up an array to retain the parameters, if any + $params = array(); + + // loop through each parameter. the params may be in the format... + // "TYPE=type1,type2,type3" + // ...or... + // "TYPE=type1;TYPE=type2;TYPE=type3" + foreach ($split as $full) { + + // split the full parameter at the equal sign so we can tell + // the parameter name from the parameter value + $tmp = explode("=", $full); + + // the key is the left portion of the parameter (before + // '='). if in 2.1 format, the key may in fact be the + // parameter value, not the parameter name. + $key = strtoupper(trim($tmp[0])); + + // get the parameter name by checking to see if it's in + // vCard 2.1 or 3.0 format. + $name = $this->_getParamName($key); + + // list of all parameter values + $listall = trim($tmp[1]); + + // if there is a value-list for this parameter, they are + // separated by commas, so split them out too. + $list = $this->splitByComma($listall); + + // now loop through each value in the parameter and retain + // it. if the value is blank, that means it's a 2.1-style + // param, and the key itself is the value. + foreach ($list as $val) { + if (trim($val) != '') { + // 3.0 formatted parameter + $params[$name][] = trim($val); + } else { + // 2.1 formatted parameter + $params[$name][] = $key; + } + } + + // if, after all this, there are no parameter values for the + // parameter name, retain no info about the parameter (saves + // ram and checking-time later). + if (count($params[$name]) == 0) { + unset($params[$name]); + } + } + + // return the parameters array. + return $params; + } + + + /** + * + * Looks at the parameters of a vCard line; if one of them is + * ENCODING[] => QUOTED-PRINTABLE then decode the text in-place. + * + * @access private + * + * @param array $params A parameter array from a vCard line. + * + * @param string $text A right-part (after-the-colon part) from a + * vCard line. + * + * @return void + * + */ + + function _decode_qp(&$params, &$text) + { + // loop through each parameter + foreach ($params as $param_key => $param_val) { + + // check to see if it's an encoding param + if (trim(strtoupper($param_key)) == 'ENCODING') { + + // loop through each encoding param value + foreach ($param_val as $enc_key => $enc_val) { + + // if any of the values are QP, decode the text + // in-place and return + if (trim(strtoupper($enc_val)) == 'QUOTED-PRINTABLE') { + $text = quoted_printable_decode($text); + return; + } + } + } + } + } + + + /** + * + * Returns parameter names from 2.1-formatted vCards. + * + * The vCard 2.1 specification allows parameter values without a + * name. The parameter name is then determined from the unique + * parameter value. + * + * Shamelessly lifted from Frank Hellwig <fr...@he...> and his + * vCard PHP project <http://vcardphp.sourceforge.net>. + * + * @access private + * + * @param string $value The first element in a parameter name-value + * pair. + * + * @return string The proper parameter name (TYPE, ENCODING, or + * VALUE). + * + */ + + function _getParamName($value) + { + static $types = array ( + 'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK', + 'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER', + 'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO', + 'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD', + 'INTERNET', 'IBMMAIL', 'MCIMAIL', + 'POWERSHARE', 'PRODIGY', 'TLX', 'X400', + 'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB', + 'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME', + 'MPEG', 'MPEG2', 'AVI', + 'WAVE', 'AIFF', 'PCM', + 'X509', 'PGP' + ); + + // CONTENT-ID added by pmj + static $values = array ( + 'INLINE', 'URL', 'CID', 'CONTENT-ID' + ); + + // 8BIT added by pmj + static $encodings = array ( + '7BIT', '8BIT', 'QUOTED-PRINTABLE', 'BASE64' + ); + + // changed by pmj to the following so that the name defaults to + // whatever the original value was. Frank Hellwig's original + // code was "$name = 'UNKNOWN'". + $name = $value; + + if (in_array($value, $types)) { + $name = 'TYPE'; + } elseif (in_array($value, $values)) { + $name = 'VALUE'; + } elseif (in_array($value, $encodings)) { + $name = 'ENCODING'; + } + + return $name; + } + + + /** + * + * Parses a vCard line value identified as being of the "N" + * (structured name) type-defintion. + * + * @access private + * + * @param string $text The right-part (after-the-colon part) of a + * vCard line. + * + * @return array An array of key-value pairs where the key is the + * portion-name and the value is the portion-value. The value itself + * may be an array as well if multiple comma-separated values were + * indicated in the vCard source. + * + */ + + function _parseN($text) + { + // make sure there are always at least 5 elements + $tmp = array_pad($this->splitBySemi($text), 5, ''); + return array( + $this->splitByComma($tmp[0]), // family (last) + $this->splitByComma($tmp[1]), // given (first) + $this->splitByComma($tmp[2]), // addl (middle) + $this->splitByComma($tmp[3]), // prefix + $this->splitByComma($tmp[4]) // suffix + ); + } + + + /** + * + * Parses a vCard line value identified as being of the "ADR" + * (structured address) type-defintion. + * + * @access private + * + * @param string $text The right-part (after-the-colon part) of a + * vCard line. + * + * @return array An array of key-value pairs where the key is the + * portion-name and the value is the portion-value. The value itself + * may be an array as well if multiple comma-separated values were + * indicated in the vCard source. + * + */ + + function _parseADR($text) + { + // make sure there are always at least 7 elements + $tmp = array_pad($this->splitBySemi($text), 7, ''); + return array( + $this->splitByComma($tmp[0]), // pob + $this->splitByComma($tmp[1]), // extend + $this->splitByComma($tmp[2]), // street + $this->splitByComma($tmp[3]), // locality (city) + $this->splitByComma($tmp[4]), // region (state) + $this->splitByComma($tmp[5]), // postcode (ZIP) + $this->splitByComma($tmp[6]) // country + ); + } + + + /** + * + * Parses a vCard line value identified as being of the "NICKNAME" + * (informal or descriptive name) type-defintion. + * + * @access private + * + * @param string $text The right-part (after-the-colon part) of a + * vCard line. + * + * @return array An array of nicknames. + * + */ + + function _parseNICKNAME($text) + { + return array($this->splitByComma($text)); + } + + + /** + * + * Parses a vCard line value identified as being of the "ORG" + * (organizational info) type-defintion. + * + * @access private + * + * @param string $text The right-part (after-the-colon part) of a + * vCard line. + * + * @return array An array of organizations; each element of the array + * is itself an array, which indicates primary organization and + * sub-organizations. + * + */ + + function _parseORG($text) + { + $tmp = $this->splitbySemi($text); + $list = array(); + foreach ($tmp as $val) { + $list[] = array($val); + } + + return $list; + } + + + /** + * + * Parses a vCard line value identified as being of the "CATEGORIES" + * (card-category) type-defintion. + * + * @access private + * + * @param string $text The right-part (after-the-colon part) of a + * vCard line. + * + * @return mixed An array of categories. + * + */ + + function _parseCATEGORIES($text) + { + return array($this->splitByComma($text)); + } + + + /** + * + * Parses a vCard line value identified as being of the "GEO" + * (geographic coordinate) type-defintion. + * + * @access private + * + * @param string $text The right-part (after-the-colon part) of a + * vCard line. + * + * @return mixed An array of lat-lon geocoords. + * + */ + + function _parseGEO($text) + { + // make sure there are always at least 2 elements + $tmp = array_pad($this->splitBySemi($text), 2, ''); + return array( + array($tmp[0]), // lat + array($tmp[1]) // lon + ); + } +} + +// +// end of Contact_Vcard_Parse.php +// + +// +// begin of vcard_convert.php +// + +/** + * Typedef of a vCard object + */ +class vCard +{ + var $version; + var $displayname; + var $surname; + var $firstname; + var $middlename; + var $nickname; + var $title; + var $birthday; + var $organization; + var $department; + var $jobtitle; + var $home = array(); + var $work = array(); + var $countrycode; + var $relatedname; + var $email; + var $email2; + var $email3; + var $pager; + var $mobile; + var $im = array(); + var $notes; + var $categories; + var $uid; + var $photo; +} + + +/** + * vCard to LDIF/CSV Converter Class + */ +class vcard_convert extends Contact_Vcard_Parse +{ + var $parsed = array(); + var $vcards = array(); + var $file_charset = 'ISO-8859-1'; + var $charset = 'ISO-8859-1'; + var $export_count = 0; + var $mailonly = false; + var $phoneonly = false; + var $accesscode = null; + var $sanitize = false; //AstLinux// + var $sanitize_dash = false; //AstLinux// + + + /** + * Constructor taking a list of converter properties + */ + function vcard_convert($p = array()) + { + foreach ($p as $prop => $value) + $this->$prop = $value; + } + + + /** + * Read a file and parse it + * + * @override + */ + function fromFile($filename, $decode_qp = true) + { + if (!filesize($filename) || ($text = $this->fileGetContents($filename)) === false) + return false; + + // dump to, and get return from, the fromText() method. + return $this->fromText($text, $decode_qp); + } + + /** + * Parse a given string for vCards + * + * @override + */ + function fromText($text, $decode_qp = true) + { + // check if charsets are specified (usually vcard version < 3.0 but this is not reliable) + if (preg_match('/charset=/i', substr($text, 0, 2048))) + $this->charset = null; + // try to detect charset of the whole file + else if ($encoding = vcard_convert::get_charset($text)) + $this->charset = $this->file_charset = $encoding; + + // convert document to UTF-8 + if (isset($this->charset) && $this->charset != 'UTF-8' && $this->charset != 'ISO-8859-1') + { + $text = $this->utf8_convert($text); + $this->charset = 'UTF-8'; + } + + $this->parsed = parent::fromText($text, $decode_qp); + if (!empty($this->parsed)) + { + $this->normalize(); + + // after normalize() all values should be UTF-8 + if (!isset($this->charset)) + $this->charset = 'UTF-8'; + + return count($this->cards); + } + else + return false; + } + + + /** + * Convert the abstract vCard structure into address objects + * + * @access private + */ + function normalize() + { + $this->cards = array(); + foreach($this->parsed as $i => $card) + { + $vcard = new vCard; + $vcard->version = (float)$card['VERSION'][0]['value'][0][0]; + + // convert all values to UTF-8 according to their charset param + if (!isset($this->charset)) + $card = $this->card2utf8($card); + + // extract names + $names = $card['N'][0]['value']; + $vcard->surname = trim($names[0][0]); + $vcard->firstname = trim($names[1][0]); + $vcard->middlename = trim($names[2][0]); + $vcard->title = trim($names[3][0]); + + if (empty($vcard->title) && isset($card['TITLE'])) + $vcard->title = trim($card['TITLE'][0]['value'][0][0]); + + $vcard->displayname = isset($card['FN']) ? trim($card['FN'][0]['value'][0][0]) : ''; + $vcard->nickname = isset($card['NICKNAME']) ? trim($card['NICKNAME'][0]['value'][0][0]) : ''; + + // extract notes + $vcard->notes = isset($card['NOTE']) ? ltrim($card['NOTE'][0]['value'][0][0]) : ''; + + // extract birthday and anniversary + foreach (array('BDAY' => 'birthday', 'ANNIVERSARY' => 'anniversary', 'X-ANNIVERSARY' => 'anniversary') as $vcf => $propname) + { + if (is_array($card[$vcf])) + { + $temp = preg_replace('/[\-\.\/]/', '', $card[$vcf][0]['value'][0][0]); + $vcard->$propname = array( + 'y' => substr($temp,0,4), + 'm' => substr($temp,4,2), + 'd' => substr($temp,6,2)); + } + } + + if (is_array($card['GENDER'])) + $vcard->gender = $card['GENDER'][0]['value'][0][0]; + else if (is_array($card['X-GENDER'])) + $vcard->gender = $card['X-GENDER'][0]['value'][0][0]; + + if (!empty($vcard->gender)) + $vcard->gender = strtoupper($vcard->gender[0]); + + // extract job_title + if (is_array($card['TITLE'])) + $vcard->jobtitle = $card['TITLE'][0]['value'][0][0]; + + // extract UID + if (is_array($card['UID'])) + $vcard->uid = $card['UID'][0]['value'][0][0]; + + // extract org and dep + if (is_array($card['ORG']) && ($temp = $card['ORG'][0]['value'])) + { + $vcard->organization = trim($temp[0][0]); + $vcard->department = trim($temp[1][0]); + } + + // extract urls + if (is_array($card['URL'])) + $this->parse_url($card['URL'], $vcard); + + // extract addresses + if (is_array($card['ADR'])) + $this->parse_adr($card['ADR'], $vcard); + + // extract phones + if (is_array($card['TEL'])) + $this->parse_tel($card['TEL'], $vcard); + + // read Apple Address Book proprietary fields + for ($n = 1; $n <= 9; $n++) + { + $prefix = 'ITEM'.$n; + if (is_array($card["$prefix.TEL"])) { + $this->parse_tel($card["$prefix.TEL"], $vcard); + } + if (is_array($card["$prefix.URL"])) { + $this->parse_url($card["$prefix.URL"], $vcard); + } + if (is_array($card["$prefix.ADR"])) { + $this->parse_adr($card["$prefix.ADR"], $vcard); + } + if (is_array($card["$prefix.X-ABADR"])) { + $this->parse_cc($card["$prefix.X-ABADR"], $vcard); + } + if (is_array($card["$prefix.X-ABRELATEDNAMES"])) { + $this->parse_rn($card["$prefix.X-ABRELATEDNAMES"], $vcard); + } + } + + // extract e-mail addresses + $a_email = array(); + $n = 0; + if (is_array($card['EMAIL'])) { + while (isset($card['EMAIL'][$n])) { + $a_email[] = $card['EMAIL'][$n]['value'][0][0]; + $n++; + } + } + if ($n < 2) { //as only 3 e-mail address will be exported we don't need to search for more + for ($n = 1; $n <= 9; $n++) { + if (is_array($card["ITEM$n.EMAIL"])) + { + $a_email[] = $card["ITEM$n.EMAIL"][0]['value'][0][0]; + if (isset($card["ITEM$n.EMAIL"][1])) + $a_email[] = $card["ITEM$n.EMAIL"][1]['value'][0][0]; + } + } + } + + if (count($a_email)) + $vcard->email = $a_email[0]; + if (!empty($a_email[1])) + $vcard->email2 = $a_email[1]; + if (!empty($a_email[2])) + $vcard->email3 = $a_email[2]; + + // find IM entries + if (is_array($card['X-AIM'])) + $vcard->im['aim'] = $card['X-AIM'][0]['value'][0][0]; + if (is_array($card['X-IQC'])) + $vcard->im['icq'] = $card['X-ICQ'][0]['value'][0][0]; + if (is_array($card['X-MSN'])) + $vcard->im['msn'] = $card['X-MSN'][0]['value'][0][0]; + if (is_array($card['X-JABBER'])) + $vcard->im['jabber'] = $card['X-JABBER'][0]['value'][0][0]; + + if (is_array($card['PHOTO'][0])) + $vcard->photo = array('data' => $card['PHOTO'][0]['value'][0][0], 'encoding' => $card['PHOTO'][0]['param']['ENCODING'][0], 'type' => $card['PHOTO'][0]['param']['TYPE'][0]); + + $vcard->categories = join(',', (array)$card['CATEGORIES'][0]['value'][0]); + + $this->cards[] = $vcard; + } + } + + /** + * Helper method to parse an URL node + * + * @access private + */ + function parse_url(&$node, &$vcard) + { + foreach($node as $url) + { + if (empty($url['param']['TYPE'][0]) || in_array_nc("WORK", $url['param']['TYPE']) || in_array_nc("PREF", $url['param']['TYPE'])) + $vcard->work['url'] = $url['value'][0][0]; + if (in_array_nc("HOME", $url['param']['TYPE'])) + $vcard->home['url'] = $url['value'][0][0]; + } + } + + /** + * Helper method to parse first or preferred related name node (when available) + * + * @access private + */ + function parse_rn(&$node, &$vcard) + { + foreach($node as $rn) + { + if (empty($vcard->relatedname) || in_array_nc("PREF", $rn['param']['TYPE'])) + $vcard->relatedname = $rn['value'][0][0]; + } + } + + /** + * Helper method to parse first or preferred country code (when available) + * + * @access private + */ + function parse_cc(&$node, &$vcard) + { + foreach($node as $cc) + { + if (empty($vcard->countrycode) || in_array_nc("PREF", $cc['param']['TYPE'])) + $vcard->countrycode = $cc['value'][0][0]; + } + } + + /** + * Helper method to parse an address node + * + * @access private + */ + function parse_adr(&$node, &$vcard) + { + foreach($node as $adr) + { + if (empty($adr['param']['TYPE'][0]) || in_array_nc("HOME", $adr['param']['TYPE'])) + $home = $adr['value']; + if (in_array_nc("WORK", $adr['param']['TYPE'])) + $work = $adr['value']; + } + + // values not splitted by Contact_Vcard_Parse if key is like item1.ADR + if (strstr($home[0][0], ';')) + { + $temp = explode(';', $home[0][0]); + $vcard->home += array( + 'addr1' => $temp[2], + 'city' => $temp[3], + 'state' => $temp[4], + 'zipcode' => $temp[5], + 'country' => $temp[6]); + } + else if (sizeof($home)>6) + { + $vcard->home += array( + 'addr1' => $home[2][0], + 'city' => $home[3][0], + 'state' => $home[4][0], + 'zipcode' => $home[5][0], + 'country' => $home[6][0]); + } + + // values not splitted by Contact_Vcard_Parse if key is like item1.ADR + if (strstr($work[0][0], ';')) + { + $temp = explode(';', $work[0][0]); + $vcard->work += array( + 'office' => $temp[1], + 'addr1' => $temp[2], + 'city' => $temp[3], + 'state' => $temp[4], + 'zipcode' => $temp[5], + 'country' => $temp[6]); + } + else if (sizeof($work)>6) + { + $vcard->work += array( + 'addr1' => $work[2][0], + 'city' => $work[3][0], + 'state' => $work[4][0], + 'zipcode' => $work[5][0], + 'country' => $work[6][0]); + } + } + + /** + * Helper method to parse an phone number node + * + * @access private + */ + function parse_tel(&$node, &$vcard) + { + foreach($node as $tel) + { + if (in_array_nc("PAGER", $tel['param']['TYPE'])) + $vcard->pager = $tel['value'][0][0]; + else if (in_array_nc("CELL", $tel['param']['TYPE'])) + $vcard->mobile = $tel['value'][0][0]; + else if (in_array_nc("HOME", $tel['param']['TYPE']) || + (in_array_nc("PREF", $tel['param']['TYPE']) && !in_array_nc("WORK", $tel['param']['TYPE']) && empty($vcard->home['phone']))) + { + if (in_array_nc("FAX", $tel['param']['TYPE'])) + $vcard->home['fax'] = $tel['value'][0][0]; + else + $vcard->home['phone'] = $tel['value'][0][0]; + } + else if (in_array_nc("WORK", $tel['param']['TYPE'])) + { + if(in_array_nc("FAX", $tel['param']['TYPE'])) + $vcard->work['fax'] = $tel['value'][0][0]; + else + $vcard->work['phone'] = $tel['value'][0][0]; + } + } + } + + + /** + * Convert the parsed vCard data into CSV format + */ + function toCSV($delm="\t", $add_title=true, $encoding=null) + { + $out = ''; + $this->export_count = 0; + + if ($add_title) + { + $out .= 'First Name'.$delm.'Last Name'.$delm.'Display Name'.$delm.'Nickname'.$delm.'E-mail Address'.$delm.'E-mail 2 Address'.$delm.'E-mail 3 Address'.$delm; + $out .= 'Home Phone'.$delm.'Business Phone'.$delm.'Home Fax'.$delm.'Business Fax'.$delm.'Pager'.$delm.'Mobile Phone'.$delm; + $out .= 'Home Street'.$delm.'Home Address 2'.$delm.'Home City'.$delm.'Home State'.$delm.'Home Postal Code'.$delm.'Home Country'.$delm; + $out .= 'Business Address'.$delm.'Business Address 2'.$delm.'Business City'.$delm.'Business State'.$delm.'Business Postal Code'.$delm; + $out .= 'Business Country'.$delm.'Country Code'.$delm.'Related name'.$delm.'Job Title'.$delm.'Department'.$delm.'Organization'.$delm.'Notes'.$delm. + $out .= 'Birthday'.$delm.'Anniversary'.$delm.'Gender'.$delm; + $out .= 'Web Page'.$delm.'Web Page 2'.$delm... [truncated message content] |