<?php
/**
 * @category 	Atmail
 * @package 	Models
 * @subpackage	Contacts
 * @copyright 	Copyright (c) 2009-2011 ATMAIL. All rights reserved
 * @license 	See http://atmail.com/license.php for license agreement
 * @author		Allan Wrethman allan@staff.atmail.com
 * @author		Atmail (http://atmail.com)
 */
 

require_once('library/Atmail/Abook/Abook.php');
require_once('application/models/sync.php');

class contacts
{
	private $_account;
	private $_tableNames;
	private $_admin = false; //this will allow an admin to modify contacts without needing a valid Account (owner)
	private $_abook;
		
	public function __construct(Array $args)
	{
		if( array_key_exists('admin', $args) && $args['admin'] == true ) 
		{
			
			$this->_admin = true; //CONSIDER: moving user email1 as metadata for owner rather to a private field as causing problems with referential integrity and ownership   
			if( array_key_exists('Account', $args) )
			{
				$this->_account = $args['Account'];	
			}
			else
			{
				$this->_account = 'admin';	
			}	
		}
		elseif( !isset($args['Account']) )
		{
			throw new Atmail_Exception('Contacts constructor requires Account field in arguments array');
		}
		else
		{
			$this->_account = $args['Account'];               
			
			//dont need this info if admin
			//get users shared Abook property from group
			$this->userDataComplete = users::getAllUserData($this->_account);
			$this->sharedAbookEnabled = $this->userDataComplete['Groups']['SharedAbook'];
			$this->groupwareZone = $this->userDataComplete['Groups']['GroupwareZone'];	
		}
			
		$email = explode('@', $this->_account);
		
		if(!empty($email['1']))
		{
			$this->_domain = $email['1'];
		}
		else
		{			
			$this->_domain = '';	
		}

		$this->_tableNames = new dbTables();
		$this->AbookTableName = $this->_tableNames->Abook;
		$this->AbookGroupTableName = $this->_tableNames->AbookGroup;
		$this->AbookGroupNames = $this->_tableNames->AbookGroupNames;
		$this->AbookPermissions = $this->_tableNames->AbookPermissions;
		
		$this->_currentContactsSort = (isset($args['contactsSort'])?$args['contactsSort']:'UserFirstName');
		$this->_currentContactsOrder = (isset($args['contactsOrder'])?$args['contactsSort']:'ASC');
		$this->_currentGroupsSort = (isset($args['groupsSort'])?$args['groupsSort']:'GroupName');
		$this->_currentGroupsOrder = (isset($args['groupsOrder'])?$args['groupsOrder']:'ASC');
		$this->_currentCurrentGroup = (isset($args['currentGroup'])?$args['currentGroup']:'All');
			
		$this->dbAdapter = Zend_Registry::get('dbAdapter');
		//$this->dbAdapter->getProfiler()->setEnabled(true); //link to enable debug for performance 
		
		$this->log = Zend_Registry::get('log');
		
		$this->allowedFields = array('UserEmail', 'UserEmail2', 'UserEmail3', 'UserEmail4', 'UserEmail5', 'UserFirstName', 'UserMiddleName', 'UserLastName', 'UserTitle', 'UserGender', 'UserDOB', 'UserHomeAddress', 'UserHomeCity', 'UserHomeState', 'UserHomeZip', 'UserHomeCountry', 'UserHomePhone', 'UserHomeMobile', 'UserHomeFax', 'UserURL', 'UserWorkCompany', 'UserWorkTitle', 'UserWorkDept', 'UserWorkOffice', 'UserWorkAddress', 'UserWorkCity', 'UserWorkState', 'UserWorkZip', 'UserWorkCountry', 'UserWorkPhone', 'UserWorkMobile', 'UserWorkFax', 'UserType', 'UserInfo', 'EntryID', 'UserInfo', 'DateModified', 'UserPhoto', 'Global', 'Shared', 'SIP');
		$this->_abook = new Atmail_Abook(array('protocol' => 'sql', 'Account' => $this->_account, 'admin' => $this->_admin));
//		$this->_abook = new Atmail_Abook(array('protocol' => 'carddav', 'Account' => $this->_account, 'username' => 'ben', 'password' => 'benben', 'server' => '192.168.2.26', 'port' => '8800', 'url' => 'addressbooks/users/ben/addressbook'));
		$AbookServersObject = new Atmail_Abook_Servers( array('Account' => $this->_account) );
		$this->abook_servers = $AbookServersObject->GetServers();
	}
	
	// Group methods

	public function createGroup($args) {

		if( !isset($args['newGroupName']) )
			throw new Atmail_Exception (__METHOD__ . ' Group name not in arguments array');

		// Do not allow dupes
		if (0 != $this->dbAdapter->fetchOne("select count(*) from `$this->AbookGroupNames` where Account = ? and GroupName = ?", array($this->_account, $args['newGroupName'])))
		{
			throw new Atmail_Exception ('Group name already exists');
		}
		
		$this->dbAdapter->insert($this->AbookGroupNames, array('Account' => $this->_account, 'GroupName' => $args['newGroupName']) );

		return $this->dbAdapter->lastInsertId();

	}
	
	public function remember($addresses)
	{
		$rfc822 = new Mail_RFC822;
		$addresses = html_entity_decode($addresses, ENT_COMPAT, 'UTF-8');

		$remembered = array();
		if( strlen($addresses) > 3 )
		{
			$addresses = $rfc822->parseAddressList(stripslashes($addresses), null, false);

			foreach($addresses as $address)
			{
				$email = $address->mailbox . '@' . $address->host;
				$personal = $address->personal;
				
				if( !$this->searchContact( array('UserEmail' => $email, 'UserEmail2' => $email, 'UserEmail3' => $email, 'UserEmail4' => $email, 'UserEmail5' => $email, 'fuzzy' => 1)) )
				{
					$newcontact = array('UserEmail' => $email);
					// add to remembered contacts
					$personal = trim($personal, '"');
					if(strlen($personal) > 0)
					{
						list($firstname, $middlename, $lastname) = explode(' ', $personal, 3);
						if(strlen($middlename) > 0 && strlen($lastname) == 0)
						{
							$lastname = $middlename;
							$middlename = '';
						}
						
						if(strlen($firstname) > 0)
						{
							$newcontact = array_merge($newcontact, array('UserFirstName' => $firstname));
						}
						if(strlen($middlename) > 0)
						{
							$newcontact = array_merge($newcontact, array('UserMiddleName' => $middlename));
						}
						if(strlen($lastname) > 0)
						{
							$newcontact = array_merge($newcontact, array('UserLastName' => $lastname));
						}
					}

					$newid = $this->addContact($newcontact);
					$this->addContactToGroup(array('id' => $newid, 'GroupID' => GROUP_AUTO));
					$remembered[] = $email;
				}
			}
		}

		return $remembered;
	}

	/**
	 * get list of group names
	 */
	public function getGroups($sort = null, $order = null) 
	{

		if( isset($sort) )
		{
			
			$this->_currentGroupsSort = $sort;
			
		}
		if( isset($order) )
		{
			
			$this->_currentGroupOrder = $order;
			
		}

		$groups = $this->dbAdapter->select("GroupName, Account, id")
								  ->from($this->AbookGroupNames)
								  ->where('Account = ' . $this->dbAdapter->quote($this->_account) )
								  ->order('GroupName')
								  ->query()
								  ->fetchAll();

		// Optionally enable Shared and Global if allowed for the User-group
		$groupsOther[] = array('GroupName' => 'All', 'id' => 0);
		    
		
		if( $this->groupwareZone != GROUPWARE_DISABLED && $this->sharedAbookEnabled == GROUPWARE_SHARED_ABOOK_ENABLED ) 
		{
			$groupsOther[] = array('GroupName' => 'Global', 'id' => GROUP_GLOBAL);
			$groupsOther[] = array('GroupName' => 'Shared', 'id' => GROUP_SHARED);
		}
		
		$groupsOther[] = array('GroupName' => 'Remembered', 'id' => GROUP_AUTO);
		$groupsOther[] = array('GroupName' => 'Favourites', 'id' => GROUP_FAV);
		
		if(count($this->abook_servers) != 0)
		{
			foreach($this->abook_servers as $abook_server)
			{
				$groupsOther[] = array('GroupName' => $abook_server['username'], 'id' => $abook_server['id'], 'server' => 1, 'protocol' => $abook_server['protocol']);
			}
		}
		
		return array_merge( $groupsOther, $groups);

	}
	
	public function getGroupNameFromId( $id )
	{
		
		$results =  $this->dbAdapter->select()
								->from($this->AbookGroupNames, "GroupName")
								->where('id = ' . $this->dbAdapter->quote($id) )
								->query()
								->fetchAll();
								
		return $results[0]['GroupName'];
		
	}
	
	public function getGroupIdsFromAbookId( $id )
	{
		$results =  $this->dbAdapter->select()
								->from($this->AbookGroupTableName, "GroupID")
								->where('AbookID = ' . $this->dbAdapter->quote($id) )
								->query()
								->fetchAll();
								
		return $results;
		
	}

	public function getTotal($groupName='') 
	{ 
		return $this->_abook->Count();
	}

	/*
	Select the number of entries in a group or addressbook-type
	*/
	public function getGroupCount($groupID='')
	{
		return $this->_abook->Count($groupID);
	}

	public function updateGroupName($args) 
	{
	
		if( !isset($args['GroupID']) )
			throw new Atmail_Exception (__METHOD__ . ' Current GroupID not in arguments array');
		if( !isset($args['newGroupName']) )
			throw new Atmail_Exception (__METHOD__ . ' New group name not in arguments array');
        return $this->dbAdapter->update($this->AbookGroupNames, array('GroupName' => $args['newGroupName']), 'id = ' . $this->dbAdapter->quote($args['GroupID'], 'INTEGER') . ' AND Account = ' . $this->dbAdapter->quote($this->_account, 'STRING') );

	}

	public function updateGroup($args) {
		if( !isset($args['currentGroupName']) )
			throw new Atmail_Exception (__METHOD__ . ' Current group name not in arguments array');
		if( !isset($args['newGroupName']) )
			throw new Atmail_Exception (__METHOD__ . ' New group name not in arguments array');
		if( !isset($args['data']) )
			throw new Atmail_Exception (__METHOD__ . ' New group name not in arguments array');

		$this->deleteGroup( array('GroupName' => $args['currentGroupName']) );
		$this->createGroup( $args );
	}

	public function addContactToGroup($args) {
		$contact = $this->getContact($args['id']);
		$contactEmail = $contact['UserEmail'];

		$existingRows = $this->dbAdapter->select()
								 	   	->from($this->AbookGroupTableName)
									   	->where($this->AbookGroupTableName . '.Account = ' . $this->dbAdapter->quote($this->_account) )
									   	->where($this->AbookGroupTableName . '.GroupID = ' . $this->dbAdapter->quote($args['GroupID']) )
										->where($this->AbookGroupTableName . '.AbookID = ' . $this->dbAdapter->quote($args['id']) )
										->query()
										->fetchAll();

		if( count($existingRows) == 0) {
			$this->dbAdapter->insert($this->AbookGroupTableName, array('Account' => $this->_account, 'GroupID' => $args['GroupID'], 'AbookID' => $args['id']) );
			return 1;
		} else
			return 0;

	}                                                                                                                       

	public function deleteGroup($args) {
		if( !isset($args['GroupID']) )
			throw new Atmail_Exception(__METHOD__ . ' GroupID required in arguments array');

		$this->dbAdapter->delete($this->AbookGroupNames, 'Account = ' . $this->dbAdapter->quote($this->_account, 'STRING') . ' AND id = ' . $this->dbAdapter->quote($args['GroupID']) );

		$this->dbAdapter->delete($this->AbookGroupTableName, 'Account = ' . $this->dbAdapter->quote($this->_account, 'STRING') . ' AND GroupID = ' . $this->dbAdapter->quote($args['GroupID']) );

		return;
	}

	public function getCurrentGroup() 
	{
		return $this->_currentCurrentGroup;
	}
			
	public function getContact($id, $GroupID='') 
	{	
		// Select the Global Addressbook
		
		// TODO: Add a server-level check the user has permissions
		
		// Check if the current user is allow to view shared or global addressbooks
		if( empty($this->sharedAbookEnabled) && ($GroupID == GROUP_GLOBAL || $GroupID == GROUP_SHARED) ) 
		{		
			throw new Atmail_Exception('No permissions to view Shared/Global Addressbook');
			return array();
		}

		$contact = $this->_abook->Get(array('id' => $id, 'GroupID' => $GroupID, 'sharedAbookEnabled' => $this->sharedAbookEnabled, 'groupwareZone' => GROUPWARE_SYSTEM_ZONE));
				
		return $contact;
	}

    public function getContactByCardDAVUrl($cardDAVUrl, $GroupID='') 
    {   
       // Select the Global Addressbook
       
       // TODO: Add a server-level check the user has permissions
       
       // Check if the current user is allow to view shared or global addressbooks
       if( empty($this->sharedAbookEnabled) && ($GroupID == GROUP_GLOBAL || $GroupID == GROUP_SHARED) ) 
       {       
           throw new Atmail_Exception('No permissions to view Shared/Global Addressbook');
           return array();
       }

       $contact = $this->_abook->Get(array('CardDAVUrl' => $cardDAVUrl, 'GroupID' => $GroupID, 'sharedAbookEnabled' => $this->sharedAbookEnabled, 'groupwareZone' => GROUPWARE_SYSTEM_ZONE));
               
       return $contact;
    }
		
	public function getContacts($args = array())
	{
		// Select a group if defined, else the normal Abook table
		if( array_key_exists('GroupName', $args) )
			$this->_currentCurrentGroup = $args['GroupName'];

		if( empty($this->sharedAbookEnabled) && ($args['GroupID'] == GROUP_GLOBAL || $args['GroupID'] == GROUP_SHARED) )
			throw new Atmail_Exception('No permissions to view Shared/Global Addressbook');
		
		if( isset($args['sort']) )
			$this->_currentContactsSort = $args['sort'];
		if( isset($args['order']) )
			$this->_currentContactsOrder = $args['order'];

		$arguments = array(	'sharedAbookEnabled' => $this->sharedAbookEnabled, 
					'sort' => $this->_currentContactsSort,
					'order' => $this->_currentContactsOrder,
					'groupwareZone' => $this->groupwareZone,
					'returnArray' => 1	);
		
		$arguments = array_merge($args, $arguments);
		
		return $this->_abook->Get($arguments);
	}
		
	public function getGroupsContacts($args = array())
	{
		$limit = -1;
		$search = false;
		
		if( array_key_exists(Atmail_Enum::ID_LIMIT, $args) )
			$limit = $args[Atmail_Enum::ID_LIMIT];
		if( array_key_exists('search', $args) )
			$search = $args['search'];
					
		$groupsContactsRaw = $this->dbAdapter->select()
						->from( $this->_tableNames->AbookGroupNames, array( $this->_tableNames->AbookGroupNames . '.GroupName') )
						->joinLeft($this->_tableNames->AbookGroup, $this->_tableNames->AbookGroupNames . '.id = ' . $this->_tableNames->AbookGroup . '.GroupID', array() )
						->joinLeft($this->_tableNames->Abook, $this->_tableNames->AbookGroup . '.AbookID = ' . $this->_tableNames->Abook . '.id', array('UserEmail', 'UserFirstName','UserLastName') )
						->where($this->_tableNames->AbookGroupNames . '.Account = ?', $this->_account);
							
		if($search != false)
		{
			$wildcard = '%' . $search . '%';
			$groupsContactsRawSearch = $groupsContactsRaw->where('UserEmail like ? OR UserEmail2 like ? OR UserEmail3 like ? OR UserEmail4 like ? OR UserEmail5 like ? OR UserFirstName like ? OR UserMiddleName like ? OR UserLastName like ? OR ' . $this->_tableNames->AbookGroupNames . '.GroupName like ?', $wildcard);
			
			$groupsContactsRaw = $this->dbAdapter->select()
							->from( $this->_tableNames->AbookGroupNames, array( $this->_tableNames->AbookGroupNames . '.GroupName') )
							->joinLeft($this->_tableNames->AbookGroup, $this->_tableNames->AbookGroupNames . '.id = ' . $this->_tableNames->AbookGroup . '.GroupID', array() )
							->joinLeft($this->_tableNames->Abook, $this->_tableNames->AbookGroup . '.AbookID = ' . $this->_tableNames->Abook . '.id', array('UserEmail', 'UserFirstName','UserLastName') )
							->where($this->_tableNames->AbookGroupNames . '.Account = ?', $this->_account);
			foreach($groupsContactsRawSearch as $groupContact)
			{
				$groupsContactsRaw = $groupsContactsRaw->orWhere($this->_tableNames->AbookGroup . '.GroupID = ?', $groupContact['GroupID']);
			}
		}
							   
		$groupsContactsRaw = $groupsContactsRaw->order( array( $this->_tableNames->AbookGroupNames . '.GroupName', 'UserFirstName', 'UserLastName', 'UserEmail') );

		$groupsContactsRaw = $groupsContactsRaw->query()->fetchAll();

		$preparedGroupsContacts = array();
		//implode into recipient lists
		foreach( $groupsContactsRaw as $contact )
		{
			
			if( !isset($preparedGroupsContacts[$contact['GroupName']]) || !is_array($preparedGroupsContacts[$contact['GroupName']]) )
			{
				
				$preparedGroupsContacts[$contact['GroupName']] = array();
				
			}
			$preparedGroupsContacts[$contact['GroupName']][] = $contact; 
			
		}
		
		if($limit != -1)
		{
			$preparedGroupsContacts = array_slice($preparedGroupsContacts, 0, $limit);
		}

		return $preparedGroupsContacts;
		
	}
	
	public function addContact($args) 
	{	
		
		// For WebUI, arguments are passed as $args['contact']['field'] - Merge if not already defined ( e.g API call )
		if(!isset($args['contact']))
		{
			$args['contact'] = array_merge($args);
		}
				
		$id = $this->_abook->Put($args);
		
		return $id;
	}
	
//Photo methods

// Upload a users photo into the DB - Input must be base64_encode before function	
	public function uploadUserPhoto($args) 
	{
		
		if( !isset($args['id']) )
			throw new Atmail_Exception (__METHOD__ . ' id not in arguments array');

		return $this->_abook->StorePhoto($args);
	}

	public function viewUserPhoto($args) 
	{
	
		if( !isset($args['id']) )
		{
			
			throw new Atmail_Exception (__METHOD__ . ' id not in arguments array');
			
		}
		if( $this->_admin == true )
		{
			
			$args['admin'] = true;
			
		}
		
		$data = $this->_abook->GetPhoto($args);
		
		// If there is no user photo, return the default style
		if( !empty($data[0]['UserPhoto']) )
		{                                  
			
			return base64_decode($data[0]['UserPhoto']);
			
		}
		else
		{
			
			return false;
			
		}
		
	}

	public static function getCompanyLogoFile($email)
	{
		$emailPart = explode('@', $email);
		if( count($emailPart) < 2 ) 
			return false;

		$emailPart = strtolower($emailPart[1]);

		$logoMap = array(
			'atmail'                  => 'logo-login.png',
			'yahoo.com'               => 'yahoo-logo.png',
			'hotmail.com'             => 'hotmail-logo.png',
			'facebook'                => 'fb-logo.png',
			'ebay.com'                => 'ebay-logo.png',
			'foursquare.com'          => 'foursquare-logo.png',
			'gmail.com'               => 'gmail-logo.png',
			'google.com'              => 'google-logo.png',
			'twitter.com'             => 'twitter-logo.png',
		);

		foreach($logoMap as $key=>$val) {
			if(strpos($emailPart, $key) !== FALSE) {
				return "contacts/viewphoto/logo/" . $val;
			}
		}

		return false;
	}

	// Validate if a user photo exist for message preview
	public function validateUserPhoto($UserEmail, $showCompanyLogo=false) 
	{
	
		if( !isset($UserEmail) )
			throw new Atmail_Exception (__METHOD__ . ' Specify user email address ');

		$select = $this->dbAdapter->select('id')
								  ->distinct()
								  ->from($this->AbookTableName, 'id')
								  ->where('UserEmail = ' . $this->dbAdapter->quote($UserEmail) )
								  ->where('UserPhoto != ""' )
								  ->where('UserPhoto is not null');
	   	if( !$this->_admin )
			$select->where('Account = ' . $this->dbAdapter->quote($this->_account) );

		$data = $select->query()->fetchAll();

		// If there is no photo, lookup in the global contact list
		if(empty($data[0]['id']) && !$this->_admin && !empty($this->sharedAbookEnabled)) {
			
			// Allow the user to view a photo in the global addressbook, if global abook is turned on
			$select = $this->dbAdapter->select('id')
									  ->distinct()
									  ->from($this->AbookTableName, 'id')
									  ->where('UserEmail = ' . $this->dbAdapter->quote($UserEmail) )
									  ->where('UserPhoto != ""' )
									  ->where('UserPhoto is not null');

			$data = $select->query()->fetchAll();	
		}
		

		if(!empty($data[0]['id']))
		{
		
			// Calculate the full URL with the MD5 checksum
			$imageContent = $this->viewUserPhoto(array('id' => $data[0]['id']));
			if( $imageContent === false )
				return false;
			
			$md5 = md5($imageContent);
			return "contacts/viewphoto/id/" . $data[0]['id'] . '/' . $md5;
		
		}
		else
		{
		// No photo, return
			if(!$showCompanyLogo)
				return false;

			return self::getCompanyLogoFile($UserEmail);
		}
	}
	
	public function updateContact($args)
	{	
		if(!isset($args['DateModified']))
		{
			$args['DateModified'] = $this->outlookDate();
		}

		$this->_abook->Modify($args);
	}
	
	public function deleteContact($args)
	{
		return $this->_abook->Delete($args);
	}   
	 
	
	public function deleteContactGroup($args) {
		//TODO: add code to preserve shared contacts when ACL gets added

		return $this->dbAdapter->delete('AbookGroup', 'AbookID = ' . $this->dbAdapter->quote($args['id'], Zend_Db::INT_TYPE) . ' AND Account = ' . $this->dbAdapter->quote($this->_account) . ' AND GroupID = ' . $this->dbAdapter->quote($args['GroupID'], Zend_Db::INT_TYPE));

	}   

	/**
	 * returns table base names as keys field names as subarray keys and default values as values
	 */
	public function describeAllContactFields() {  

		return $this->dbAdapter->describeTable($this->AbookTableName);

	}

//search methods	

   	public function searchContact($args) 
   	{
		return $this->_abook->Search($args); 
	}
	
	/**
	 * return the user's profile abookId
	 */
	public function getProfileAbookId()
	{
		$query = array('UserEmail'=>$this->_account, 'GroupID'=>0);
		$search_results = $this->searchContact($query);

		return $search_results? $search_results : null;		
	}

//Validate if a user photo exist for message preview
   	public function searchFuzzyContact($args, $GroupID)
   	{
		$args['fuzzy'] = 1; 
		$args['GroupID'] = $GroupID; 
		return $this->_abook->Search($args); 
	}  
	
	public function outlookDate()
	{
		$perc = sprintf("%.6f", gmdate("U", time()) / 60 / 1440 + 25569);
		return "$perc";
	}
	
	public function updateUsage($addresses, $amount)
	{
		$rfc822 = new Mail_RFC822;
		
		$addresses = html_entity_decode($addresses, ENT_COMPAT, 'UTF-8');
		if( strlen($addresses) > 3 )
		{
			$addresses = $rfc822->parseAddressList(stripslashes($addresses), null, false);
			
			foreach($addresses as $address)
			{
				$this->dbAdapter->update($this->AbookTableName, array('UsageCount' => new Zend_Db_Expr('UsageCount + 1')), 'UserEmail = ' . $this->dbAdapter->quote($address->mailbox . '@' . $address->host) . ' AND Account = ' . $this->dbAdapter->quote($this->_account, 'STRING') . ' AND Global != 1 AND Shared != 1' );
			}
		}
	}
}
