. * * Consult LICENSE file for details ************************************************/ include_once('lib/default/diffbackend/diffbackend.php'); class BackendVCardDir extends BackendDiff { /**---------------------------------------------------------------------------------------------------------- * default backend methods */ /** * Authenticates the user - NOT EFFECTIVELY IMPLEMENTED * Normally some kind of password check would be done here. * Alternatively, the password could be ignored and an Apache * authentication via mod_auth_* could be done * * @param string $username * @param string $domain * @param string $password * * @access public * @return boolean */ public function Logon($username, $domain, $password) { return true; } /** * Logs off * * @access public * @return boolean */ public function Logoff() { return true; } /** * Sends an e-mail * Not implemented here * * @param SyncSendMail $sm SyncSendMail object * * @access public * @return boolean * @throws StatusException */ public function SendMail($sm) { return false; } /** * Returns the waste basket * * @access public * @return string */ public function GetWasteBasket() { return false; } /** * Returns the content of the named attachment as stream * not implemented * * @param string $attname * * @access public * @return SyncItemOperationsAttachment * @throws StatusException */ public function GetAttachmentData($attname) { return false; } /**---------------------------------------------------------------------------------------------------------- * implemented DiffBackend methods */ /** * Returns a list (array) of folders. * In simple implementations like this one, probably just one folder is returned. * * @access public * @return array */ public function GetFolderList() { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetFolderList()'); $contacts = array(); $folder = $this->StatFolder("root"); $contacts[] = $folder; return $contacts; } /** * Returns an actual SyncFolder object * * @param string $id id of the folder * * @access public * @return object SyncFolder with information */ public function GetFolder($id) { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetFolder('.$id.')'); if($id == "root") { $folder = new SyncFolder(); $folder->serverid = $id; $folder->parentid = "0"; $folder->displayname = "Contacts"; $folder->type = SYNC_FOLDER_TYPE_CONTACT; return $folder; } else return false; } /** * Returns folder stats. An associative array with properties is expected. * * @param string $id id of the folder * * @access public * @return array */ public function StatFolder($id) { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::StatFolder('.$id.')'); $folder = $this->GetFolder($id); $stat = array(); $stat["id"] = $id; $stat["parent"] = $folder->parentid; $stat["mod"] = $folder->displayname; return $stat; } /** * Creates or modifies a folder * not implemented * * @param string $folderid id of the parent folder * @param string $oldid if empty -> new folder created, else folder is to be renamed * @param string $displayname new folder name (to be created, or to be renamed to) * @param int $type folder type * * @access public * @return boolean status * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions * */ public function ChangeFolder($folderid, $oldid, $displayname, $type){ return false; } /** * Deletes a folder * * @param string $id * @param string $parent is normally false * * @access public * @return boolean status - false if e.g. does not exist * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions * */ public function DeleteFolder($id, $parentid){ return false; } /** * Returns a list (array) of messages * * @param string $folderid id of the parent folder * @param long $cutoffdate timestamp in the past from which on messages should be returned * * @access public * @return array/false array with messages or false if folder is not available */ public function GetMessageList($folderid, $cutoffdate) { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetMessageList('.$folderid.')'); $messages = array(); $dir = opendir($this->getPath()); if(!$dir) return false; while($entry = readdir($dir)) { if(is_dir($this->getPath() .'/'.$entry)) continue; $message = array(); $message["id"] = $entry; $stat = stat($this->getPath() .'/'.$entry); $message["mod"] = $stat["mtime"]; $message["flags"] = 1; // always 'read' $messages[] = $message; } return $messages; } /** * Returns the actual SyncXXX object type. * * @param string $folderid id of the parent folder * @param string $id id of the message * @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc) * * @access public * @return object/false false if the message could not be retrieved */ public function GetMessage($folderid, $id, $contentparameters) { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetMessage('.$folderid.', '.$id.', ..)'); if($folderid != "root") return; $types = array ('dom' => 'type', 'intl' => 'type', 'postal' => 'type', 'parcel' => 'type', 'home' => 'type', 'work' => 'type', 'pref' => 'type', 'voice' => 'type', 'fax' => 'type', 'msg' => 'type', 'cell' => 'type', 'pager' => 'type', 'bbs' => 'type', 'modem' => 'type', 'car' => 'type', 'isdn' => 'type', 'video' => 'type', 'aol' => 'type', 'applelink' => 'type', 'attmail' => 'type', 'cis' => 'type', 'eworld' => 'type', 'internet' => 'type', 'ibmmail' => 'type', 'mcimail' => 'type', 'powershare' => 'type', 'prodigy' => 'type', 'tlx' => 'type', 'x400' => 'type', 'gif' => 'type', 'cgm' => 'type', 'wmf' => 'type', 'bmp' => 'type', 'met' => 'type', 'pmb' => 'type', 'dib' => 'type', 'pict' => 'type', 'tiff' => 'type', 'pdf' => 'type', 'ps' => 'type', 'jpeg' => 'type', 'qtime' => 'type', 'mpeg' => 'type', 'mpeg2' => 'type', 'avi' => 'type', 'wave' => 'type', 'aiff' => 'type', 'pcm' => 'type', 'x509' => 'type', 'pgp' => 'type', 'text' => 'value', 'inline' => 'value', 'url' => 'value', 'cid' => 'value', 'content-id' => 'value', '7bit' => 'encoding', '8bit' => 'encoding', 'quoted-printable' => 'encoding', 'base64' => 'encoding', ); // Parse the vcard $message = new SyncContact(); $data = file_get_contents($this->getPath() . "/" . $id); $data = str_replace("\x00", '', $data); $data = str_replace("\r\n", "\n", $data); $data = str_replace("\r", "\n", $data); $data = preg_replace('/(\n)([ \t])/i', '', $data); $lines = explode("\n", $data); $vcard = array(); foreach($lines as $line) { if (trim($line) == '') continue; $pos = strpos($line, ':'); if ($pos === false) continue; $field = trim(substr($line, 0, $pos)); $value = trim(substr($line, $pos+1)); $fieldparts = preg_split('/(? $v){ $val[$i] = quoted_printable_decode($v); } break; case 'b': case 'base64': foreach($val as $i => $v){ $val[$i] = base64_decode($v); } break; } }else{ foreach($val as $i => $v){ $val[$i] = $this->unescape($v); } } $fieldvalue['val'] = $val; $vcard[$type][] = $fieldvalue; } if(isset($vcard['email'][0]['val'][0])) $message->email1address = $vcard['email'][0]['val'][0]; if(isset($vcard['email'][1]['val'][0])) $message->email2address = $vcard['email'][1]['val'][0]; if(isset($vcard['email'][2]['val'][0])) $message->email3address = $vcard['email'][2]['val'][0]; if(isset($vcard['tel'])){ foreach($vcard['tel'] as $tel) { if(!isset($tel['type'])){ $tel['type'] = array(); } if(in_array('car', $tel['type'])){ $message->carphonenumber = $tel['val'][0]; }elseif(in_array('pager', $tel['type'])){ $message->pagernumber = $tel['val'][0]; }elseif(in_array('cell', $tel['type'])){ $message->mobilephonenumber = $tel['val'][0]; }elseif(in_array('home', $tel['type'])){ if(in_array('fax', $tel['type'])){ $message->homefaxnumber = $tel['val'][0]; }elseif(empty($message->homephonenumber)){ $message->homephonenumber = $tel['val'][0]; }else{ $message->home2phonenumber = $tel['val'][0]; } }elseif(in_array('work', $tel['type'])){ if(in_array('fax', $tel['type'])){ $message->businessfaxnumber = $tel['val'][0]; }elseif(empty($message->businessphonenumber)){ $message->businessphonenumber = $tel['val'][0]; }else{ $message->business2phonenumber = $tel['val'][0]; } }elseif(empty($message->homephonenumber)){ $message->homephonenumber = $tel['val'][0]; }elseif(empty($message->home2phonenumber)){ $message->home2phonenumber = $tel['val'][0]; }else{ $message->radiophonenumber = $tel['val'][0]; } } } //;;street;city;state;postalcode;country if(isset($vcard['adr'])){ foreach($vcard['adr'] as $adr) { if(empty($adr['type'])){ $a = 'other'; }elseif(in_array('home', $adr['type'])){ $a = 'home'; }elseif(in_array('work', $adr['type'])){ $a = 'business'; }else{ $a = 'other'; } if(!empty($adr['val'][2])){ $b=$a.'street'; $message->$b = w2ui($adr['val'][2]); } if(!empty($adr['val'][3])){ $b=$a.'city'; $message->$b = w2ui($adr['val'][3]); } if(!empty($adr['val'][4])){ $b=$a.'state'; $message->$b = w2ui($adr['val'][4]); } if(!empty($adr['val'][5])){ $b=$a.'postalcode'; $message->$b = w2ui($adr['val'][5]); } if(!empty($adr['val'][6])){ $b=$a.'country'; $message->$b = w2ui($adr['val'][6]); } } } if(!empty($vcard['fn'][0]['val'][0])) $message->fileas = w2ui($vcard['fn'][0]['val'][0]); if(!empty($vcard['n'][0]['val'][0])) $message->lastname = w2ui($vcard['n'][0]['val'][0]); if(!empty($vcard['n'][0]['val'][1])) $message->firstname = w2ui($vcard['n'][0]['val'][1]); if(!empty($vcard['n'][0]['val'][2])) $message->middlename = w2ui($vcard['n'][0]['val'][2]); if(!empty($vcard['n'][0]['val'][3])) $message->title = w2ui($vcard['n'][0]['val'][3]); if(!empty($vcard['n'][0]['val'][4])) $message->suffix = w2ui($vcard['n'][0]['val'][4]); if(!empty($vcard['bday'][0]['val'][0])){ $tz = date_default_timezone_get(); date_default_timezone_set('UTC'); $message->birthday = strtotime($vcard['bday'][0]['val'][0]); date_default_timezone_set($tz); } if(!empty($vcard['org'][0]['val'][0])) $message->companyname = w2ui($vcard['org'][0]['val'][0]); if(!empty($vcard['note'][0]['val'][0])){ $message->body = w2ui($vcard['note'][0]['val'][0]); $message->bodysize = strlen($vcard['note'][0]['val'][0]); $message->bodytruncated = 0; } if(!empty($vcard['role'][0]['val'][0])) $message->jobtitle = w2ui($vcard['role'][0]['val'][0]);//$vcard['title'][0]['val'][0] if(!empty($vcard['url'][0]['val'][0])) $message->webpage = w2ui($vcard['url'][0]['val'][0]); if(!empty($vcard['categories'][0]['val'])) $message->categories = $vcard['categories'][0]['val']; if(!empty($vcard['photo'][0]['val'][0])) $message->picture = base64_encode($vcard['photo'][0]['val'][0]); return $message; } /** * Returns message stats, analogous to the folder stats from StatFolder(). * * @param string $folderid id of the folder * @param string $id id of the message * * @access public * @return array */ public function StatMessage($folderid, $id) { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::StatMessage('.$folderid.', '.$id.')'); if($folderid != "root") return false; $stat = stat($this->getPath() . "/" . $id); $message = array(); $message["mod"] = $stat["mtime"]; $message["id"] = $id; $message["flags"] = 1; return $message; } /** * Called when a message has been changed on the mobile. * This functionality is not available for emails. * * @param string $folderid id of the folder * @param string $id id of the message * @param SyncXXX $message the SyncObject containing a message * * @access public * @return array same return value as StatMessage() * @throws StatusException could throw specific SYNC_STATUS_* exceptions */ public function ChangeMessage($folderid, $id, $message) { ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::ChangeMessage('.$folderid.', '.$id.', ..)'); $mapping = array( 'fileas' => 'FN', 'lastname;firstname;middlename;title;suffix' => 'N', 'email1address' => 'EMAIL;INTERNET', 'email2address' => 'EMAIL;INTERNET', 'email3address' => 'EMAIL;INTERNET', 'businessphonenumber' => 'TEL;WORK', 'business2phonenumber' => 'TEL;WORK', 'businessfaxnumber' => 'TEL;WORK;FAX', 'homephonenumber' => 'TEL;HOME', 'home2phonenumber' => 'TEL;HOME', 'homefaxnumber' => 'TEL;HOME;FAX', 'mobilephonenumber' => 'TEL;CELL', 'carphonenumber' => 'TEL;CAR', 'pagernumber' => 'TEL;PAGER', ';;businessstreet;businesscity;businessstate;businesspostalcode;businesscountry' => 'ADR;WORK', ';;homestreet;homecity;homestate;homepostalcode;homecountry' => 'ADR;HOME', ';;otherstreet;othercity;otherstate;otherpostalcode;othercountry' => 'ADR', 'companyname' => 'ORG', 'body' => 'NOTE', 'jobtitle' => 'ROLE', 'webpage' => 'URL', ); $data = "BEGIN:VCARD\nVERSION:2.1\nPRODID:Z-Push\n"; foreach($mapping as $k => $v){ $val = ''; $ks = explode(';', $k); foreach($ks as $i){ if(!empty($message->$i)) $val .= $this->escape($message->$i); $val.=';'; } if(empty($val)) continue; $val = substr($val,0,-1); if(strlen($val)>50){ $data .= $v.":\n\t".substr(chunk_split($val, 50, "\n\t"), 0, -1); }else{ $data .= $v.':'.$val."\n"; } } if(!empty($message->categories)) $data .= 'CATEGORIES:'.implode(',', $this->escape($message->categories))."\n"; if(!empty($message->picture)) $data .= 'PHOTO;ENCODING=BASE64;TYPE=JPEG:'."\n\t".substr(chunk_split($message->picture, 50, "\n\t"), 0, -1); if(isset($message->birthday)) $data .= 'BDAY:'.date('Y-m-d', $message->birthday)."\n"; $data .= "END:VCARD"; // not supported: anniversary, assistantname, assistnamephonenumber, children, department, officelocation, radiophonenumber, spouse, rtf if(!$id){ if(!empty($message->fileas)){ $name = u2wi($message->fileas); }elseif(!empty($message->lastname)){ $name = $name = u2wi($message->lastname); }elseif(!empty($message->firstname)){ $name = $name = u2wi($message->firstname); }elseif(!empty($message->companyname)){ $name = $name = u2wi($message->companyname); }else{ $name = 'unknown'; } $name = preg_replace('/[^a-z0-9 _-]/i', '', $name); $id = $name.'.vcf'; $i = 0; while(file_exists($this->getPath().'/'.$id)){ $i++; $id = $name.$i.'.vcf'; } } file_put_contents($this->getPath().'/'.$id, $data); return $this->StatMessage($folderid, $id); } /** * Changes the 'read' flag of a message on disk * * @param string $folderid id of the folder * @param string $id id of the message * @param int $flags read flag of the message * * @access public * @return boolean status of the operation * @throws StatusException could throw specific SYNC_STATUS_* exceptions */ public function SetReadFlag($folderid, $id, $flags) { return false; } /** * Called when the user has requested to delete (really delete) a message * * @param string $folderid id of the folder * @param string $id id of the message * * @access public * @return boolean status of the operation * @throws StatusException could throw specific SYNC_STATUS_* exceptions */ public function DeleteMessage($folderid, $id) { return unlink($this->getPath() . '/' . $id); } /** * Called when the user moves an item on the PDA from one folder to another * not implemented * * @param string $folderid id of the source folder * @param string $id id of the message * @param string $newfolderid id of the destination folder * * @access public * @return boolean status of the operation * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions */ public function MoveMessage($folderid, $id, $newfolderid) { return false; } /**---------------------------------------------------------------------------------------------------------- * private vcard-specific internals */ /** * The path we're working on * * @access private * @return string */ private function getPath() { return str_replace('%u', $this->store, VCARDDIR_DIR); } /** * Escapes a string * * @param string $data string to be escaped * * @access private * @return string */ function escape($data){ if (is_array($data)) { foreach ($data as $key => $val) { $data[$key] = $this->escape($val); } return $data; } $data = str_replace("\r\n", "\n", $data); $data = str_replace("\r", "\n", $data); $data = str_replace(array('\\', ';', ',', "\n"), array('\\\\', '\\;', '\\,', '\\n'), $data); return u2wi($data); } /** * Un-escapes a string * * @param string $data string to be un-escaped * * @access private * @return string */ function unescape($data){ $data = str_replace(array('\\\\', '\\;', '\\,', '\\n','\\N'),array('\\', ';', ',', "\n", "\n"),$data); return $data; } }; ?>