<?php
/**
 * EGroupware Api: SMTP configuration / mail accounts via SQL
 *
 * @link http://www.stylite.de
 * @package api
 * @subpackage mail
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
 * @copyright (c) 2012-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @version $Id$
 */

namespace EGroupware\Api\Mail\Smtp;

use EGroupware\Api\Mail;
use EGroupware\Api;

/**
 * SMTP configuration / mail accounts via SQL
 */
class Sql extends Mail\Smtp
{
	/**
	 * Label shown in EMailAdmin
	 */
	const DESCRIPTION = 'SQL';

	/**
	 * Capabilities of this class (pipe-separated): default, forward
	 */
	const CAPABILITIES = 'default|forward';

	/**
	 * Reference to global db object
	 *
	 * @var Api\Db
	 */
	protected $db;

	/**
	 * Name of table
	 */
	const TABLE = 'egw_mailaccounts';
	/**
	 * Name of app our table belongs to
	 */
	const APP = 'api';
	/**
	 * Values for mail_type column
	 *
	 * enabled and delivery must have smaller values then alias, forward or mailbox (getUserData depend on it)!
	 */
	const TYPE_ENABLED = 0;
	const TYPE_DELIVERY = 1;
	const TYPE_QUOTA = 2;
	const TYPE_ALIAS = 3;
	const TYPE_FORWARD = 4;
	const TYPE_MAILBOX = 5;

	/**
	 * Constructor
	 *
	 * @param string $defaultDomain =null
	 */
	function __construct($defaultDomain=null)
	{
		parent::__construct($defaultDomain);

		$this->db = $GLOBALS['egw']->db;
	}

	/**
	 * Get all email addresses of an account
	 *
	 * @param string $_accountName
	 * @return array
	 */
	function getAccountEmailAddress($_accountName)
	{
		$emailAddresses	= parent::getAccountEmailAddress($_accountName);

		if (($account_id = $this->accounts->name2id($_accountName, 'account_lid', 'u')))
		{
			foreach($this->db->select(self::TABLE, 'mail_value', array(
				'account_id' => $account_id,
				'mail_type' => self::TYPE_ALIAS,
			), __LINE__, __FILE__, false, 'ORDER BY mail_value', self::APP) as $row)
			{
				$emailAddresses[] = array (
					'name'		=> $emailAddresses[0]['name'],
					'address'	=> $row['mail_value'],
					'type'		=> 'alternate',
				);
			}
		}
		if (!empty($this->debug)) error_log(__METHOD__."('$_accountName') returning ".array2string($emailAddresses));

		return $emailAddresses;
	}

	/**
	 * Get the data of a given user
	 *
	 * Multiple accounts may match, if an email address is specified.
	 * In that case only mail routing fields "uid", "mailbox" and "forward" contain values
	 * from all accounts!
	 *
	 * @param int|string $user numerical account-id, account-name or email address
	 * @param boolean $match_uid_at_domain =true true: uid@domain matches, false only an email or alias address matches
	 * @return array with values for keys 'mailLocalAddress', 'mailAlternateAddress' (array), 'mailForwardingAddress' (array),
	 * 	'accountStatus' ("active"), 'quotaLimit' and 'deliveryMode' ("forwardOnly")
	 */
	function getUserData($user, $match_uid_at_domain=false)
	{
		$userData = array();

		if (is_numeric($user) && $this->accounts->exists($user))
		{
			$account_id = $user;
		}
		elseif (strpos($user, '@') === false)
		{
			$account_id = $this->accounts->name2id($user, 'account_lid', 'u');
		}
		else	// email address
		{
			// check with primary email address
			if (($account_id = $this->accounts->name2id($user, 'account_email')))
			{
				$account_id = array($account_id);
			}
			else
			{
				$account_id = array();
			}
			// always allow username@domain
			list($account_lid) = explode('@', $user);
			if ($match_uid_at_domain && ($id = $this->accounts->name2id($account_lid, 'account_lid')) && !in_array($id, $account_id))
			{
				$account_id[] = $id;
			}
			try {
				foreach($this->db->select(self::TABLE, 'account_id', array(
					'mail_type' => self::TYPE_ALIAS,
					'mail_value' => $user,
				), __LINE__, __FILE__, false, '', self::APP) as $row)
				{
					if (!in_array($row['account_id'], $account_id)) $account_id[] = $row['account_id'];
				}
			}
			catch (Api\Db\Exception $e) {
				_egw_log_exception($e); // happens if username contains a non-ascii char
			}
			//error_log(__METHOD__."('$user') account_id=".array2string($account_id));
		}
		if ($account_id)
		{
			if (!is_array($account_id))
			{
				$userData['mailLocalAddress'] = $this->accounts->id2name($account_id, 'account_email');
			}
			$enabled = $forwardOnly = array();
			foreach($this->db->select(self::TABLE, '*', array(
				'account_id' => $account_id,
			), __LINE__, __FILE__, false, 'ORDER BY mail_type,mail_value', self::APP) as $row)
			{
				switch($row['mail_type'])
				{
					case self::TYPE_ENABLED:
						$userData['accountStatus'] = $row['mail_value'];
						$enabled[$row['account_id']] = $row['mail_value'] == self::MAIL_ENABLED;
						break;

					case self::TYPE_DELIVERY:
						$userData['deliveryMode'] = !strcasecmp($row['mail_value'], self::FORWARD_ONLY) ? Mail\Smtp::FORWARD_ONLY : '';
						$forwardOnly[$row['account_id']] = !strcasecmp($row['mail_value'], self::FORWARD_ONLY);
						break;

					case self::TYPE_QUOTA:
						$userData['quotaLimit'] = $row['mail_value'];
						break;

					case self::TYPE_ALIAS:
						$userData['mailAlternateAddress'][] = $row['mail_value'];
						break;

					case self::TYPE_FORWARD:
						$userData['mailForwardingAddress'][] = $row['mail_value'];
						if ($row['account_id'] < 0 || $enabled[$row['account_id']])
						{
							$userData['forward'][] = $row['mail_value'];
						}
						break;

					case self::TYPE_MAILBOX:
						$userData['mailMessageStore'] = $row['mail_value'];
						//error_log(__METHOD__."('$user') row=".array2string($row).', enabled[$row[account_id]]='.array2string($enabled[$row['account_id']]).', forwardOnly[$row[account_id]]='.array2string($forwardOnly[$row['account_id']]));
						if ($row['account_id'] > 0 && !empty($enabled[$row['account_id']]) && empty($forwardOnly[$row['account_id']]))
						{
							$userData['uid'][] = $this->accounts->id2name($row['account_id'], 'account_lid');
							$userData['mailbox'][] = $row['mail_value'];
						}
						break;
				}
			}
			// if query by email-address (not a regular call from fmail)
			if (is_array($account_id))
			{
				// add group-members for groups as forward (that way we dont need to store&update them)
				foreach($account_id as $id)
				{
					if ($id < 0 && ($members = $this->accounts->members($id, true)))
					{
						foreach($members as $member)
						{
							if (($email = $this->accounts->id2name($member, 'account_email')) && !in_array($email, (array)$userData['forward']))
							{
								$userData['forward'][] = $email;
							}
						}
					}
				}
			}
		}
		if (!empty($this->debug)) error_log(__METHOD__."('$user') returning ".array2string($userData));

		return $userData;
	}

	/**
	 * Set the data of a given user
	 *
	 * @param int $_uidnumber numerical user-id
	 * @param array $_mailAlternateAddress
	 * @param array $_mailForwardingAddress
	 * @param string $_deliveryMode
	 * @param string $_accountStatus
	 * @param string $_mailLocalAddress
	 * @param int $_quota in MB
	 * @param boolean $_forwarding_only =false true: store only forwarding info, used internally by saveSMTPForwarding
	 * @param string $_setMailbox =null used only for account migration
	 * @return boolean true on success, false on error writing to ldap
	 */
	function setUserData($_uidnumber, array $_mailAlternateAddress, array $_mailForwardingAddress, $_deliveryMode,
		$_accountStatus, $_mailLocalAddress, $_quota, $_forwarding_only=false, $_setMailbox=null)
	{
		if (!empty($this->debug)) error_log(__METHOD__."($_uidnumber, ".array2string($_mailAlternateAddress).', '.array2string($_mailForwardingAddress).", '$_deliveryMode', '$_accountStatus', '$_mailLocalAddress', $_quota, forwarding_only=".array2string($_forwarding_only).') '.function_backtrace());

		if (!$_forwarding_only && $this->accounts->id2name($_uidnumber, 'account_email') !== $_mailLocalAddress)
		{
			$account = $this->accounts->read($_uidnumber);
			$account['account_email'] = $_mailLocalAddress;
			$this->accounts->save($account);
		}
		// make sure the primary mail address is not set additional as alias as e.g. AD denies that for proxyAddresses attribute
		if (($key = array_search(strtolower($_mailLocalAddress), array_map('strtolower', $_mailAlternateAddress))) !== false)
		{
			unset($_mailAlternateAddress[$key]);
		}

		$flags = array(
			self::TYPE_DELIVERY => $_deliveryMode,
			self::TYPE_ENABLED => $_accountStatus,
			self::TYPE_QUOTA => $_quota,
		);
		$where = array('account_id' => $_uidnumber);
		if ($_forwarding_only) $where['mail_type'] = array(self::TYPE_FORWARD, self::TYPE_DELIVERY);
		// find all invalid values: either delete or update them
		$delete_ids = array();
		foreach($this->db->select(self::TABLE, '*', $where, __LINE__, __FILE__, false, '', self::APP) as $row)
		{
			switch($row['mail_type'])
			{
				case self::TYPE_ALIAS:
					$new_addresses =& $_mailAlternateAddress;
					// fall-throught
				case self::TYPE_FORWARD:
					if ($row['mail_type'] == self::TYPE_FORWARD) $new_addresses =& $_mailForwardingAddress;
					if (($key = array_search($row['mail_value'], $new_addresses)) === false)
					{
						$delete_ids[] = $row['mail_id'];
					}
					else
					{
						unset($new_addresses[$key]);	// no need to store
					}
					break;

				case self::TYPE_MAILBOX:
					$mailbox = $row['mail_value'];
					break;

				case self::TYPE_QUOTA:
				case self::TYPE_DELIVERY:
				case self::TYPE_ENABLED:
					//error_log(__METHOD__.": ".__LINE__." row=".array2string($row).", flags['$row[mail_type]']=".array2string($flags[$row['mail_type']]));
					if ($row['mail_value'] != $flags[$row['mail_type']])
					{
						if ($flags[$row['mail_type']])
						{
							$this->db->update(self::TABLE, array(
								'mail_value' => $flags[$row['mail_type']],
							), array(
								'mail_id' => $row['mail_id'],
							), __LINE__, __FILE__, self::APP);
						}
						else
						{
							$delete_ids[] = $row['mail_id'];
						}
					}
					unset($flags[$row['mail_type']]);
					break;
			}
		}
		if ($delete_ids)
		{
			$this->db->delete(self::TABLE, array('mail_id' => $delete_ids), __LINE__, __FILE__, self::APP);
		}
		// set mailbox address, if explicitly requested by $_setMailbox parameter
		if ($_setMailbox)
		{
			$flags[self::TYPE_MAILBOX] = $_setMailbox;
		}
		// set mailbox address, if not yet set
		elseif (!$_forwarding_only && empty($mailbox))
		{
			$flags[self::TYPE_MAILBOX] = Api\Translation::to_ascii($this->mailbox_addr(array(
				'account_id' => $_uidnumber,
				'account_lid' => $this->accounts->id2name($_uidnumber, 'account_lid'),
				'account_email' => $_mailLocalAddress,
			)));
		}
		// store all new values
		foreach($flags+array(
			self::TYPE_ALIAS => $_mailAlternateAddress,
			self::TYPE_FORWARD => $_mailForwardingAddress,
		) as $type => $values)
		{
			if ($values && (!$_forwarding_only || in_array($type, array(self::TYPE_FORWARD, self::TYPE_DELIVERY))))
			{
				foreach((array)$values as $value)
				{
					$this->db->insert(self::TABLE, array(
						'account_id' => $_uidnumber,
						'mail_type' => $type,
						'mail_value' => $value,
					), false, __LINE__, __FILE__, self::APP);
				}
			}
		}

		// let interested parties know account was update
		Api\Hooks::process(array(
			'location' => 'mailaccount_userdata_updated',
			'account_id' => $_uidnumber,
		));

		return true;
	}

	/**
	 * Get configured mailboxes of a domain
	 *
	 * @param boolean $return_inactive return mailboxes NOT marked as accountStatus=active too
	 * @param boolean $return_extra =false true: return quota and groups
	 * @return array uid => name-part of mailMessageStore or $return_extra array with keys "mbox", "quota" and "groups"
	 */
	function getMailboxes($return_inactive, $return_extra=false)
	{
		$cols = 'account_lid AS uid,'.self::TABLE.'.mail_value AS mailbox';
		$join = 'JOIN '.Api\Accounts\Sql::TABLE.' ON '.self::TABLE.'.account_id='.Api\Accounts\Sql::TABLE.'.account_id';
		if (!$return_inactive)
		{
			$join .= ' JOIN '.self::TABLE.' active ON active.account_id='.self::TABLE.'.account_id AND active.mail_type='.self::TYPE_ENABLED;
		}
		if ($return_extra)
		{
			$join .= ' LEFT JOIN '.self::TABLE.' quota ON quota.account_id='.Api\Accounts\Sql::TABLE.'.account_id AND quota.mail_type='.self::TYPE_QUOTA;
			$cols .= ','.Api\Accounts\Sql::TABLE.'.account_id AS account_id,ROUND(quota.mail_value,0) AS quota';
		}
		$mailboxes = array();
		foreach($this->db->select(self::TABLE, $cols,
			self::TABLE.'.mail_type='.self::TYPE_MAILBOX,
			__LINE__, __FILE__, false, 'ORDER BY account_lid', self::APP, 0, $join) as $row)
		{
			if ($row['uid'] == 'anonymous') continue;	// anonymous is never a mail-user!
			list($mailbox) = explode('@', $row['mailbox']);
			if (!$return_extra)
			{
				$mailboxes[$row['uid']] = $mailbox;
			}
			else
			{
				$mailboxes[$row['uid']] = [
					'mbox'   => $mailbox,
					'quota'  => !empty($row['quota']) ? $row['quota'] : $GLOBALS['egw_info']['server']['default_quota'],
					'groups' => array_values($GLOBALS['egw']->accounts->memberships($row['account_id'])),
				];
			}
		}
		return $mailboxes;
	}

	/**
	 * Hook called on account deletion
	 *
	 * @param array $_hookValues values for keys 'account_lid', 'account_id'
	 * @return boolean true on success, false on error
	 */
	function deleteAccount($_hookValues)
	{
		$this->db->delete(self::TABLE, array('account_id' => $_hookValues['account_id']), __LINE__, __FILE__);

		return true;
	}
}