<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id: webmaillib.php 68475 2018-12-02 14:35:47Z jonnybradley $

//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
	header("location: index.php");
	exit;
}

class WebMailLib extends TikiLib
{

	// current account row from database (tiki_user_mail_accounts)
	// [accountId, user, account, pop, current, port, username, pass, msgs, smtp, useAuth, smtpPort, flagsPublic, autoRefresh, imap, mbox, maildir, useSSL, fromEmail]
	var $current_account = [];

	// slightly complicated sub-query to check for public messages
	var $SQL_CLAUSE_FOR_PUBLIC_MAILBOX = '(select count(*) from `tiki_user_mail_accounts` where `tiki_user_mail_accounts`.`accountId`=`tiki_webmail_messages`.`accountId` and `flagsPublic` = \'y\')';

	function remove_webmail_message($current, $user, $msgid)
	{
		$query = "delete from `tiki_webmail_messages` where `mailId`=? and (`user`=? or $this->SQL_CLAUSE_FOR_PUBLIC_MAILBOX)";	// mailId is message-id
		$result = $this->query($query, [$msgid, $user]);
	}

	function replace_webmail_message($current, $user, $msgid)
	{

		$query = "select count(*) from `tiki_webmail_messages` where `accountId`=? and `mailId`=? and (`user`=? or $this->SQL_CLAUSE_FOR_PUBLIC_MAILBOX)";

		if ($this->getOne($query, [(int)$current, $msgid, $user]) == 0) {
			$query = "insert into `tiki_webmail_messages`(`accountId`,`mailId`,`user`,`isRead`,`isFlagged`,`isReplied`) values(?,?,?,'n','n','n')";
			$result = $this->query($query, [(int)$current, $msgid, $user]);
		}
	}

	function set_mail_flag($current, $user, $msgid, $flag, $value)
	{
		// flag can be: isRead,isFlagged,isReplied, value: y/n

		if ($flag == 'isFlagged') {
			if ($value == 'y') {
				$fMsg = "$user," . mktime() . ", ";
			} else {
				$fMsg = '';
			}
		}

		$query = "SELECT * FROM `tiki_webmail_messages` WHERE `accountId` = ? AND `mailId` = ? AND (`user`=? or $this->SQL_CLAUSE_FOR_PUBLIC_MAILBOX)";
		$result = $this->query($query, [(int)$current, $msgid, $user]);
		$row = $result->fetchRow();

		if ($row != null) {
			// Update is select found a match
			if ($flag == 'isFlagged') {
				$query = "UPDATE `tiki_webmail_messages` SET `$flag` = '$value', `flaggedMsg` = '$fMsg'" .
									" WHERE `accountId` = ? AND `mailId` = ? AND (`user`=? or $this->SQL_CLAUSE_FOR_PUBLIC_MAILBOX)";
			} else {
				$query = "UPDATE `tiki_webmail_messages` SET `$flag` = '$value'" .
								" WHERE `accountId` = ? AND `mailId` = ? AND (`user`=? or $this->SQL_CLAUSE_FOR_PUBLIC_MAILBOX)";
			}
			$result = $this->query($query, [(int)$current, $msgid, $user]);
		} else {
			// Otherwise insert, we have no flags for this massesge yet
			$query = "insert into `tiki_webmail_messages`(`$flag`,`accountId`,`mailId`,`user`,`flaggedMsg`) values (?,?,?,?,?)";
			$result = $this->query($query, [$value, (int)$current, $msgid, $user, $fMsg]);
		}
		return true;
	}

	function get_mail_flags($current, $user, $msgid)
	{

		$query = "select `isRead`,`isFlagged`,`isReplied` from `tiki_webmail_messages`" .
							" where `accountId`=? and `mailId`=? and (`user`=? or $this->SQL_CLAUSE_FOR_PUBLIC_MAILBOX)";
		$result = $this->query($query, [(int)$current,$msgid,$user]);

		if (! $result->numRows()) {
			return ['n', 'n', 'n'];	// seems odd to return a valid array on error?
		}

		$res = $result->fetchRow();
		return [
				$res["isRead"],
				$res["isFlagged"],
				$res["isReplied"]
		];
	}

	function current_webmail_account($user, $accountId)
	{
		global $tikilib;

		$query = "update `tiki_user_mail_accounts` set `current`='n' where `user`=?";
		$result = $this->query($query, [$user]);

		$acc = $this->get_webmail_account($user, $accountId);
		if ($acc && $acc['flagsPublic'] == 'y' && $acc['user'] != $user) {
			$tikilib->set_user_preference($user, 'mailCurrentAccount', $accountId);
		} else {
			$query = "update `tiki_user_mail_accounts` set `current`='y' where `user`=? and `accountId`=?";
			$this->query($query, [$user,(int)$accountId ]);
			$tikilib->set_user_preference($user, 'mailCurrentAccount', 0);
		}
		$this->get_current_webmail_account($user);
	}

	function list_webmail_accounts($user, $offset, $maxRecords, $sort_mode, $find)
	{
		if ($find) {
			$findesc = '%' . $find . '%';

			$mid = " where `flagsPublic` = 'n' and `user`=? and (`account` like ?)";
			$bindvars = [$user, $findesc];
		} else {
			$mid = " where `flagsPublic` = 'n' and `user`=?";
			$bindvars = [$user];
		}

		$query = "select * from `tiki_user_mail_accounts` $mid order by " . $this->convertSortMode($sort_mode);
		$query_cant = "select count(*) from `tiki_user_mail_accounts` $mid";
		$result = $this->query($query, $bindvars, $maxRecords, $offset);
		$cant = $this->getOne($query_cant, $bindvars);
		$ret = [];

		while ($res = $result->fetchRow()) {
			$ret[] = $res;
		}

		$retval = [];
		$retval["data"] = $ret;
		$retval["cant"] = $cant;
		return $retval;
	}

	function list_webmail_group_accounts($user, $offset, $maxRecords, $sort_mode, $find)
	{
		if ($find) {
			$findesc = '%' . $find . '%';

			$mid = " where `flagsPublic` <> 'n' and (`account` like ?)";
			$bindvars = [$findesc];
		} else {
			$mid = " where `flagsPublic` <> 'n'";
			$bindvars = [];
		}

		$query = "select * from `tiki_user_mail_accounts` $mid order by " . $this->convertSortMode($sort_mode);
		$query_cant = "select count(*) from `tiki_user_mail_accounts` $mid";
		$result = $this->query($query, $bindvars, $maxRecords, $offset);
		$cant = $this->getOne($query_cant, $bindvars);
		$ret = [];

		while ($res = $result->fetchRow()) {
			$ret[] = $res;
		}

		$retval = [];
		$retval["data"] = $ret;
		$retval["cant"] = $cant;
		return $retval;
	}

	function count_webmail_accounts($user, $includePublic = true)
	{
		$query_cant = 'SELECT COUNT(*) FROM `tiki_user_mail_accounts` WHERE `user`=?';
		if ($includePublic) {
			$query_cant .= ' OR `flagsPublic` = \'y\'';
		}
		$bindvars = [$user];
		$cant = $this->getOne($query_cant, $bindvars);
		return $cant;
	}

	function replace_webmail_account(
		$accountId,
		$user,
		$account,
		$pop,
		$port,
		$username,
		$pass,
		$msgs,
		$smtp,
		$useAuth,
		$smtpPort,
		$flagsPublic,
		$autoRefresh,
		$imap,
		$mbox,
		$maildir,
		$useSSL,
		$fromEmail
	) {


		// Check the name
		if ($accountId) {
			$query = "update `tiki_user_mail_accounts` set `user`=?, `account`=?, `pop`=?, `port`=?," .
							" `smtpPort`=?, `username`=?, `pass`=?, `smtp`=?, `useAuth`=?, `msgs`=?, `flagsPublic`=?," .
							" `autoRefresh`=? , `imap`=?, `mbox`=?, `maildir`=?, `useSSL`=?, `fromEmail`=?" .
							" where `accountId`=? and `user`=?";

			$bindvars = [
								$user,
								$account,
								$pop,
								(int)$port,
								(int)$smtpPort,
								$username,
								$pass,
								$smtp,
								$useAuth,
								$msgs,
								$flagsPublic,
								(int)$autoRefresh,
								$imap,
								$mbox,
								$maildir,
								$useSSL,
								$fromEmail,
								(int)$accountId,
								$user
			];
			$result = $this->query($query, $bindvars);
		} else {
			$query = "insert into `tiki_user_mail_accounts`" .
				" (`user`,`account`,`pop`,`port`,`smtpPort`,`username`,`pass`," .
				" `smtp`,`useAuth`,`msgs`,`flagsPublic`,`autoRefresh`, `imap`," .
				" `mbox`, `maildir`, `useSSL`, `fromEmail`)" .
				" values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

			$bindvars = [
							$user,
							$account,
							$pop,
							$port,
							$smtpPort,
							$username,
							$pass,
							$smtp,
							$useAuth,
							$msgs,
							$flagsPublic,
							$autoRefresh,
							$imap,
							$mbox,
							$maildir,
							$useSSL,
							$fromEmail
			];
			$result = $this->query($query, $bindvars);
		}
		if ($accountId == $this->get_current_webmail_accountId($user)) {
			$this->current_account = [];	// reload
			$this->get_webmail_account($user, $accountId);
		}
		return true;
	}

	function new_webmail_account(
		$user,
		$account,
		$pop,
		$port,
		$username,
		$pass,
		$msgs,
		$smtp,
		$useAuth,
		$smtpPort,
		$flagsPublic,
		$autoRefresh,
		$imap,
		$mbox,
		$maildir,
		$useSSL,
		$fromEmail
	) {


		$query = "insert into `tiki_user_mail_accounts`(`user`,`account`,`pop`,`port`," .
						"`smtpPort`,`username`,`pass`,`smtp`,`useAuth`,`msgs`,`flagsPublic`," .
						"`autoRefresh`, `imap`, `mbox`, `maildir`, `useSSL`, `fromEmail`)" .
						" values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

		$bindvars = [
						$user,
						$account,
						$pop,
						$port,
						$smtpPort,
						$username,
						$pass,
						$smtp,
						$useAuth,
						$msgs,
						$flagsPublic,
						$autoRefresh,
						$imap,
						$mbox,
						$maildir,
						$useSSL,
						$fromEmail
		];
		$result = $this->query($query, $bindvars);

		$query = 'SELECT `accountID` FROM `tiki_user_mail_accounts` WHERE' .
						' `user`=? AND `account`=? AND `pop`=? AND `port`=? AND `smtpPort`=? AND' .
						' `username`=? AND `pass`=? AND `smtp`=? AND `useAuth`=? AND `msgs`=? AND' .
						' `flagsPublic`=? AND `autoRefresh`=? AND `imap`=? AND `mbox`=? AND' .
						' `maildir`=? AND `useSSL`=? AND `fromEmail`=?';
		$accountID = $this->getOne($query, $bindvars);

		return $accountID;
	}



	function get_current_webmail_accountId($user)
	{
		global $tikilib;

		$pubAc = $tikilib->get_user_preference($user, 'mailCurrentAccount');
		if (empty($pubAc)) {
			$pubAc = 0;
		}
		// check database even if pref is set in case it's changed
		$query = "select accountId from `tiki_user_mail_accounts` where (`current`='y' and `user`=?) or (`flagsPublic`='y' and `accountId`=?)";
		$result = $this->getOne($query, [$user, $pubAc]);

		return $result;
	}

	function get_current_webmail_account($user)
	{
		return $this->get_webmail_account($user);
	}

	function remove_webmail_account($user, $accountId)
	{
		$query = "delete from `tiki_user_mail_accounts` where `accountId`=? and `user`=?";
		$result = $this->query($query, [(int)$accountId,$user]);
		return true;
	}

	function get_webmail_account($user, $accountId = 0)
	{
		if ($accountId == 0) {
			$accountId = $this->get_current_webmail_accountId($user);
		}

		$query = "select * from `tiki_user_mail_accounts` where `accountId`=? and (`user`=? or `flagsPublic`='y')";
		$result = $this->query($query, [(int)$accountId,$user]);

		if (! $result->numRows()) {
			$res = [];
		} else {
			$res = $result->fetchRow();
		}

		if ($accountId == $this->get_current_webmail_accountId($user)) {
			$this->current_account = $res;
		}
		return $res;
	}

	/**
	 * @param string $user
	 * @param string $accountName
	 * @return int accountId or 0 if no single account with that name
	 */
	function get_webmail_account_by_name($user, $accountName)
	{

		$query = "select accountId from `tiki_user_mail_accounts` where `account`=? and (`user`=? or `flagsPublic`='y')";
		$result = $this->fetchAll($query, [ $accountName, $user]);

		if (count($result) == 1) {
			return (int) $result[0]['accountId'];
		} else {
			return 0;
		}
	}

	/**
	 * @param string $user current user
	 * @param int $accountid can be 0 (uses current account)
	 * @param bool $reload force reload from mail server?
	 * @param string $folder
	 * @return array            of partial message headers
	 * @throws Exception
	 */
	function refresh_mailbox($user, $accountid, $reload, $folder = '')
	{

		if (! empty($accountid)) {
			$this->current_account = $this->get_webmail_account($user, $accountid);
		} else {
			$this->current_account = $this->get_current_webmail_account($user);
		}

		if (empty($this->current_account)) {
			return [];
		}

		// the current folder to list, not stored in the database
		$this->current_account['folder'] = $folder ? $folder : 'INBOX';

		// start getting mail

		$timeout = -1;
		if ($this->current_account['autoRefresh'] > 0) {
			$timeout = time() - $this->current_account['autoRefresh'];
		}

		$serialized_params = $this->current_account['accountId'] . ':' .
			$this->current_account['user'] . ':' .
			$this->current_account['account'] . ':' .
			$this->current_account['folder'];

		if (isset($_SESSION['webmailinbox'][$serialized_params]) &&
			(
				(! isset($reload) || ! $reload) &&
				(isset($_SESSION['webmailinbox'][$serialized_params]['timestamp']) && $_SESSION['webmailinbox'][$serialized_params]['timestamp'] > $timeout)
			)
		) {
			$webmail_list = $_SESSION['webmailinbox'][$serialized_params]['webmail_list'];
		} else {	// no cached list or timed out
			// get mail the zend way

			try {
				$mail = $this->get_mail_storage();
			} catch (Exception $e) {
				// do something better with the error
				unset($_SESSION['webmailinbox'][$serialized_params]['webmail_list']);
				unset($_SESSION['webmailinbox'][$serialized_params]['timestamp']);
				throw($e);
			}

			if (! isset($mail)) {
				return tra('Is the mailbox not initialised??');
			}

			$webmail_list = [];
			$numMessages = $mail->countMessages();

			for ($messageNum = 1; $messageNum < $numMessages; $messageNum++) {

				try {
					$message = $mail->getMessage($messageNum);

					$headers = $message->getHeaders()->toArray();		// quicker than the Zend accessors?

					$wmail = [];	// Tiki Webmail row

					$wmail['from'] = $headers['From'];
					$wmail['to'] = $headers['To'];
					$wmail['subject'] = $headers['Subject'];
					$wmail['date'] = $headers['Date'];
					$wmail["timestamp"] = strtotime($headers['Date']);

					$from = preg_split('/[<>]/', $wmail['from'], -1, PREG_SPLIT_NO_EMPTY);
					$wmail['sender']['name'] = $from[0];
					$wmail['sender']['email'] = $from[1];
					if (empty($wmail['sender']['email'])) {
						$wmail['sender']['email'] = $wmail['sender']['name'];
					} elseif (! strstr($wmail['sender']['email'], '@')) {
						$e = $wmail['sender']['name'];
						$wmail['sender']['name'] = $wmail['sender']['email'];
						$wmail['sender']['email'] = $e;
					}
					$wmail['sender']['name'] = htmlspecialchars($wmail['sender']['name']);

					if (! empty($headers['Message-ID'])) {
						$wmail['realmsgid'] = preg_replace('/[<>]/', '', $headers['Message-ID']);
					} else {
						$wmail['realmsgid'] = $wmail['timestamp'] . '.' . $wmail['sender']['email'];	// TODO better?
					}

					if (empty($wmail['subject'])) {
						$wmail['subject'] = '[' . tra('No subject') . ']';
					}
					$wmail['subject'] = htmlspecialchars($wmail['subject']);

					$wmail['msgid'] = $messageNum;

					// TODO
					$wmail['has_attachment'] = false;
					//				$l = $pop3->_cmdList($i);
					$wmail['size'] = 0;
				} catch (Exception $e) {
					$excpetionMessage = $e->getMessage();

					$wmail = [
						'subject'   => tr('Error was: "%0"', $excpetionMessage),
						'sender'      => [
							'name'  => tr('Message could not be retrieved'),
							'email' => ''
						],
						'to'        => tr('Message could not be retrieved'),
						'realmsgid' => '',
						'msgid'     => $messageNum,

					];

					trigger_error(
						tr('Message id %0 corrupted. Header contains: "%1"',
								$messageNum, $mail->getRawHeader($messageNum)
						)
					);
				}


				// Add to output
				$webmail_list[] = $wmail;
			}

			$_SESSION['webmailinbox'][$serialized_params]['webmail_list'] = $webmail_list;
			$_SESSION['webmailinbox'][$serialized_params]['timestamp'] = time();

			$mail->close();
		}		// endif no cached list or timed out

		return $webmail_list;
	}	// end refresh_mailbox()

	function get_mail_storage()
	{
		$this->current_account['folder'] = ! empty($_REQUEST['folder']) ? $_REQUEST['folder'] : 'INBOX';

		if (! empty($this->current_account['imap'])) {
			// connecting with Imap
			return new Zend\Mail\Storage\Imap(
				[
					'host'     => $this->current_account["imap"],
					'user'     => $this->current_account["username"],
					'password' => $this->current_account["pass"],
					'port'	   => $this->current_account["port"],
					'ssl'      => $this->current_account["useSSL"] == 'y' ? 'SSL' : false,
					'folder'   => $this->current_account['folder'],
				]
			);
		} elseif (! empty($this->current_account['mbox'])) {
			// connecting with Mbox locally
			return new Zend\Mail\Storage\Mbox(
				['filename' => $this->current_account["mbox"]]
			);
		} elseif (! empty($this->current_account['maildir'])) {
			// connecting with Maildir locally
			return new Zend\Mail\Storage\Maildir(
				['dirname' => $this->current_account["mbox"]]
			);
		} elseif (! empty($this->current_account['pop'])) {
			// connecting with Pop3
			return new Zend\Mail\Storage\Pop3(
				[
					'host'     => $this->current_account["pop"],
					'user'     => $this->current_account["username"],
					'password' => $this->current_account["pass"],
					'port'     => $this->current_account["port"],
					'ssl'      => $this->current_account["useSSL"] == 'y' ? 'SSL' : false,
					'folder'   => $this->current_account['folder'],
				]
			);
		}
		// not returned yet?
		throw new Zend\Mail\Storage\Exception\RuntimeException('No server to check');
	}	// end get_mail_storage()

	/**
	 * @param $part - a part a message or the message itself
	 * @return array with the decoded body of the part
	 */
	private function decode_mail_part($part)
	{
		$result = null;
		$c = $part->getContent();
		// deal with transfer encoding
		try {	// no headerExists() func a part (why?)
			$enc = $part->contentTransferEncoding;
		} catch (Zend\Mail\Exception\ExceptionInterface $e) {
			$enc = '';
		}
		try {	// no headerExists() func a part (why?)
			$ct = $part->contentType;
		} catch (Zend\Mail\Exception\ExceptionInterface $e) {
			$ct = '';
		}
		try {
			switch ($enc) {
				case 'quoted-printable':
					$c = quoted_printable_decode($c);
					break;
				case 'base64':
					$c = base64_decode($c);
					break;
				case '7bit':
				case '8bit':
				default:
					$c = $c;
			}
			// deal with charset
			if (preg_match('/charset\s*=\s*[\'"](.*)[\'"]/i', $ct, $m) && count($m) > 1) {
				$charset = $m[1];
			}
			if (! empty($charset) && strtolower($charset) != 'utf-8') {
				$c = utf8_encode($c);
			}
			$result = ['body' => trim($c), 'contentType' => strtok($ct, ';')];

			if (strtok($ct, ';') == 'text/plain' && ! $getAllParts) {
				return $result;
			}
		} catch (Zend\Mail\Exception\ExceptionInterface $e) {
			// ignore?
		}
		return $result;
	}
	/**
	 * @param $user			current user
	 * @param $accountid	can be 0
	 * @param $msgId		message to get
	 * @param $getAllParts	if false returns the plain text body as a string - if true return an array of all parts
	 * @return string/array	the message body/bodies
	 */
	function get_mail_content($user, $accountid, $msgId, $getAllParts = false)
	{
		global $webmail_account;	// TODO remove global and refactor

		if (! empty($accountid)) {
			$this->current_account = $this->get_webmail_account($user, $accountid);
		} else {
			$this->current_account = $this->get_current_webmail_account($user);
		}

		if (empty($this->current_account)) {
			return '';
		}

		// connecting with Zend
		try {
			$mail = $this->get_mail_storage();
		} catch (Exception $e) {
			// do something better with the error
			return '';
		}

		if (empty($mail)) {
			return '';
		}

		$foundPart = null;
		$message = $mail->getMessage($msgId);
		$cont = [];

		// parse parts - initially from http://framework.zend.com/manual/en/zend.mail.read.html
		foreach (new RecursiveIteratorIterator($message) as $part) {
			$cont[] = $this->decode_mail_part($part);
		}
		if (empty($cont)) {
			$cont[] = $this->decode_mail_part($message);	// no parts, so try the whole message
		}
		if (empty($cont)) {
			$cont[] = ['body' => tra('No mail body found'), 'contentType' => 'text/plain'];
		}

		return $getAllParts ? $cont : $cont[0];
	}	// end get_mail_content()

	/**
	 * Saves the message to the "Sent" folder
	 *
	 * @param \Zend\Mail\Message|string $message
	 */
	function saveSentMessage($message) {
		$storage = $this->get_mail_storage();

		if (! is_string($message)) {
			$message = $message->toString();
		}

		$folders = new RecursiveIteratorIterator($storage->getFolders(), RecursiveIteratorIterator::SELF_FIRST);
		foreach ($folders as $localName => $folder) {

			if ($folder->isSelectable()) {
				$globalName = $folder->getGlobalName();

				if (strpos($globalName, 'Sent') !== false || strpos($globalName, tr('Sent')) !== false) {
					try {
						$storage->appendMessage($message, $globalName);
						return;

					} catch (Exception $e) {
						Feedback::error($e->getMessage());
					}
				}
			}
		}


	}

} # class WebMailLib
$webmaillib = new WebMailLib;
