<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Class Imap
 *
 */
class Imap {


	/**
	 * IMAP mailbox
	 *
	 * @var string
	 */
	protected $mailbox;

	/**
	 * IMAP stream
	 *
	 * @var resource
	 */
	protected $stream;

	/**
	 * IMAP current folder
	 *
	 * @var string
	 */
	protected $folder = 'INBOX';


	/**
	 * @param array $config Options: host, encrypto, user, pass
	 *
	 * @return bool|string True if is connected or string with the imap_error
	 */
	public function imap_connect($config = [])
	{
		$enc = '';
		if ($config['encrypto'] != NULL && isset($config['encrypto']) && $config['encrypto'] == 'ssl')
		{
			$enc = '/imap/ssl/novalidate-cert';
		}
		elseif ($config['encrypto'] != NULL && isset($config['encrypto']) && $config['encrypto'] == 'tls')
		{
			$enc = '/imap/tls/novalidate-cert';
		}
		elseif ($config['encrypto'] != NULL && isset($config['encrypto']) && $config['encrypto'] == 'stls')
		{
			$enc = '/imap/tls';
		}
		$this->mailbox = '{' . $config['host'] . $enc . '}';
		ob_start();
		$this->stream = @imap_open($this->mailbox, $config['user'], $config['pass']);
		ob_get_clean();
		if ( $this->stream===false)
		{
			$msg=$this->last_error();
			Mdebug_log::AddGeneralLog("Email to ticket imap config error", Mdebug_log::STATUS_FAILED, Mdebug_log::ENTRY_TYPE_ERROR, $msg);
			return $msg;
		}
		return TRUE;
	}

	public function imap_connect_default(){
		$is_enable=Mapp_setting::GetSettingsValue("is_imap_ticket","N")=="Y";
		if($is_enable){
			$host=Mapp_setting::GetSettingsValue("imap_host","");
			$host=app_trim($host);
			$port=Mapp_setting::GetSettingsValue("imap_port","");
			$port=app_trim($port);
			$encyptoType=Mapp_setting::GetSettingsValue("imap_secure_type","ssl");
			$encrypto=Mapp_setting::GetSettingsValue("imap_is_secure")=="Y"?$encyptoType:NULL;

			$user=Mapp_setting::GetSettingsValue("imap_user");
			$pass=Mapp_setting::GetSettingsValue("imap_pass");
			$config = array(
				'host'     => "{$host}:{$port}",
				'encrypto' => $encrypto,
				'user'     =>app_trim($user),
				'pass'     => $pass
			);
			return @$this->imap_connect($config);
		}
		return FALSE;
	}
	/**
	 * Gets the last IMAP error that occurred during this page request
	 *
	 * @return string
	 */
	protected function last_error()
	{
		return imap_last_error();
	}


	/**
	 * Get all folders names
	 *
	 * @return array
	 */
	public function get_folders()
	{
		$folders = imap_list($this->stream, $this->mailbox, '*');
		sort($folders);

		return str_replace($this->mailbox, '', $folders);
	}


	/**
	 * Select folder
	 *
	 * @param string $folder Folder name
	 *
	 * @return bool
	 */
	public function select_folder($folder)
	{
		if ($result = imap_reopen($this->stream, $this->mailbox . $folder))
		{
			$this->folder = $folder;
		}

		return $result;
	}


	/**
	 * Add folder
	 *
	 * @param string $name Folder name
	 *
	 * @return bool
	 */
	public function add_folder($name)
	{
		return imap_createmailbox($this->stream, $this->mailbox . $name);
	}


	/**
	 * Rename folder
	 *
	 * @param string $name     Current folder name
	 * @param string $new_name New folder name
	 *
	 * @return bool    TRUE on success or FALSE on failure.
	 */
	public function rename_folder($name, $new_name)
	{
		return imap_renamemailbox($this->stream, $this->mailbox . $name, $this->mailbox . $new_name);
	}


	/**
	 * Remove folder
	 *
	 * @param string $name Folder name
	 *
	 * @return bool TRUE on success or FALSE on failure.
	 */
	public function remove_folder($name)
	{
		return imap_deletemailbox($this->stream, $this->mailbox . $name);
	}


	/**
	 * Count all messages
	 *
	 * @return int
	 */
	public function count_messages()
	{
		return imap_num_msg($this->stream);
	}


	/**
	 * Count unread messages
	 *
	 * @param string $folder Folder to count unread messages
	 *
	 * @return int
	 */
	public function count_unread_messages($folder = '')
	{
		$current_folder = $this->folder;

		if (isset($folder))
		{
			$this->select_folder($folder);
		}

		$result = imap_search($this->stream, 'UNSEEN');

		if (isset($folder))
		{
			$this->select_folder($current_folder);
		}

		if ($result === FALSE)
		{
			return 0;
		}

		return count($result);
	}


	/**
	 * Get quota usage and limit from mail account
	 *
	 * @return array
	 */
	public function get_quota()
	{
		return imap_get_quotaroot($this->stream, $this->mailbox);
	}


	/**
	 * Get total size from current mailbox
	 *
	 * @return int
	 */
	public function get_mailbox_size()
	{
		$messages = $this->get_messages();
		$size = 0;
		foreach ($messages as $message)
		{
			$size += $message['size'];
		}

		return $size;
	}


	/**
	 * Returns one email by given id
	 *
	 * @param int  $id           Message id
	 * @param bool $with_body    False if you want without body
	 * @param bool $embed_images If use $with_body TRUE and you want body embedded images, set TRUE
	 *
	 * @return array
	 */
	public function get_message($id = 0, $with_body = FALSE, $embed_images = FALSE)
	{
		return $this->format_message($id, $with_body, $embed_images);
	}


	/**
	 * Get messages
	 *
	 * @param int    $number       Number of messages. 0 to get all
	 * @param int    $start        Starting message number
	 * @param string $order        ASC or DESC
	 * @param bool   $with_body    Get message body
	 * @param bool   $embed_images Get embedded images in message body
	 *
	 * @return array
	 */
	public function get_messages($number = 0, $start = 0, $order = 'DESC', $with_body = FALSE, $embed_images = FALSE)
	{
		if ($number == 0)
		{
			$number = $this->count_messages();
		}
		$emails = [];
		$result = imap_search($this->stream, 'ALL');
		if ($result)
		{
			$ids = [];
			foreach ($result as $k => $i)
			{
				$ids[] = $i;
			}

			if ($order == 'DESC')
			{
				$ids = array_reverse($ids);
			}
			$ids = array_chunk($ids, $number);
			$ids = $ids[$start];

			foreach ($ids as $id)
			{
				$emails[] = $this->format_message($id, $with_body, $embed_images);
			}
		}

		return $emails;
	}


	/**
	 * Get unread messages
	 *
	 * @param int    $number    Number of messages. 0 to get all
	 * @param int    $start     Starting message number
	 * @param string $order     ASC or DESC
	 * @param bool   $with_body Get message body
	 *
	 * @return array
	 */
	public function get_unread_messages($number = 0, $start = 0, $order = 'DESC', $with_body = FALSE)
	{
		return $this->get_search_messages('UNSEEN', $number, $start, $order, $with_body);
	}


	/**
	 * @param string $criteria ALL, UNSEEN, FLAGGED, UNANSWERED, DELETED, UNDELETED, (e.g. FROM
	 *                         "joey smith")
	 * @param int    $number
	 * @param int    $start
	 * @param string $order
	 * @param bool   $with_body
	 * @param bool   $embed_images
	 *
	 * @return array
	 */
	public function get_search_messages($criteria = '',
		$number = 0,
		$start = 0,
		$order = 'DESC',
		$with_body = FALSE,
		$embed_images = FALSE)
	{
		$emails = [];
		$result = imap_search($this->stream, $criteria);
		if ($number == 0)
		{
			$number = count($result);
		}
		if ($result)
		{
			$ids = [];
			foreach ($result as $k => $i)
			{
				$ids[] = $i;
			}
			$ids = array_chunk($ids, $number);
			$ids = array_slice($ids[0], $start, $number);

			$emails = [];
			foreach ($ids as $id)
			{
				$emails[] = $this->format_message($id, $with_body, $embed_images);
			}
		}
		if ($order == 'DESC')
		{
			$emails = array_reverse($emails);
		}

		return $emails;
	}


	/**
	 * Mark message as read or unread
	 *
	 * @param int  $id     Message id
	 * @param bool $unseen True if message is unread, false if message is read
	 *
	 * @return bool True on success
	 */
	public function set_unseen_message($id = 0, $unseen = TRUE)
	{
		$header = $this->get_message_header($id);
		if ($header == FALSE)
		{
			return FALSE;
		}

		$flags = '';
		$flags .= (strlen(app_trim($header->Answered)) > 0 ? '\\Answered ' : '');
		$flags .= (strlen(app_trim($header->Flagged)) > 0 ? '\\Flagged ' : '');
		$flags .= (strlen(app_trim($header->Deleted)) > 0 ? '\\Deleted ' : '');
		$flags .= (strlen(app_trim($header->Draft)) > 0 ? '\\Draft ' : '');
		$flags .= (($unseen == FALSE) ? '\\Seen ' : ' ');

		imap_clearflag_full($this->stream, $id, '\\Seen', ST_UID);

		return imap_setflag_full($this->stream, $id,app_trim($flags), ST_UID);
	}


	public function move_message($id = 0, $target=null)
	{
		return $this->move_messages([$id], $target);
	}


	public function move_messages($ids = [], $target=null)
	{
		if ( ! imap_mail_move($this->stream, implode(',', $ids), $target, CP_UID))
		{
			return FALSE;
		}

		return imap_expunge($this->stream);
	}


	/**
	 * Save message in Sent folder
	 *
	 * @param string $header Message header
	 * @param string $body   Message body
	 *
	 * @return bool
	 */
	public function save_message_in_sent($header = '', $body = '')
	{
		return imap_append($this->stream, $this->mailbox . $this->get_sent(),
			$header . "\r\n" . $body . "\r\n", "\\Seen");
	}


	/**
	 * Move message to trash and expunge all
	 *
	 * @param $id
	 *
	 * @return bool
	 */
	public function delete_message($id)
	{
		return $this->delete_messages([$id]);
	}


	/**
	 * Move messages to trash and expunge all
	 *
	 * @param array $ids
	 *
	 * @return bool
	 */
	public function delete_messages($ids = [])
	{
		if ( ! imap_mail_move($this->stream, implode(',', $ids), $this->get_trash(), CP_UID))
		{
			return FALSE;
		}

		return imap_expunge($this->stream);
	}


	/**
	 * Returns all email addresses
	 *
	 * @return array|bool Array with all email addresses or false on error
	 */
	public function get_all_email_addresses()
	{
		$saveCurrentFolder = $this->folder;
		$emails = [];
		foreach ($this->get_folders() as $folder)
		{
			$this->select_folder($folder);
			foreach ($this->get_messages() as $message)
			{
				$emails[] = $message['from'];

				foreach ($message['to'] as $to)
				{
					$emails[] = $to;
				}

				if (isset($message['cc']))
				{
					$emails = array_merge($emails, $message['cc']);
					foreach ($message['cc'] as $cc)
					{
						$emails[] = $cc;
					}
				}
			}
		}

		$contacts = [];

		foreach ($emails as $k => $i)
		{
			$contacts[$i['email']] = $i['name'];
		}

		foreach ($emails as $k => $i)
		{
			if ( ! empty($i['name']))
			{
				$contacts[$i['email']] = $i['name'];
			}
		}

		$this->select_folder($saveCurrentFolder);

		return $contacts;
	}


	/**
	 * Clean folder content of selected folder
	 *
	 * @return bool True on success
	 */
	public function purge()
	{
		if ($this->folder == $this->get_trash() || strtolower($this->folder) == 'spam')
		{
			if ( ! imap_delete($this->stream, '1:*'))
			{
				return FALSE;
			}

			return imap_expunge($this->stream);
		}
		else
		{
			if ( ! imap_mail_move($this->stream, '1:*', $this->get_trash()))
			{
				return FALSE;
			}

			return imap_expunge($this->stream);
		}
	}


	/**
	 * Return content of messages attachment
	 * Save the attachment in a optional path or get the binary code in the content index
	 *
	 * @param int    $id       Message id
	 * @param int    $index    Index of the attachment - 0 to the first attachment
	 * @param string $tmp_path Optional tmp path, if not set the code will be get in the output
	 *
	 * @return array|bool False if attachment could not be get
	 */
	public function get_attachment($id = 0, $index = 0, $tmp_path = '')
	{
		$messageIndex = imap_msgno($this->stream, imap_uid($this->stream, $id));
		$mailStruct = imap_fetchstructure($this->stream, $messageIndex);
		$attachments = $this->get_attachments($messageIndex, $mailStruct, '');

		if ($attachments == FALSE)
		{
			return FALSE;
		}

		if ($index > count($attachments))
		{
			return FALSE;
		}

		$attachment = $attachments[$index];

		$partStruct = imap_bodystruct($this->stream, $messageIndex, $attachment['partNum']);

		$decodedName = imap_mime_header_decode($partStruct->dparameters[0]->value);
		$filename = $this->convert_to_utf8($decodedName[0]->text);

		$message = imap_fetchbody($this->stream, $id, $attachment['partNum']);
		switch ($attachment['enc'])
		{
			case 0:
			case 1:
				$message = imap_8bit($message);
				break;
			case 2:
				$message = imap_binary($message);
				break;
			case 3:
				$message = imap_base64($message);
				break;
			case 4:
				$message = quoted_printable_decode($message);
				break;
		}

		$file = [
			'name'        => $attachment['name'],
			'size'        => $attachment['size'],
			'disposition' => $attachment['disposition'],
			'reference'   => $attachment['reference'],
			'type'        => $attachment['type'],
			'content'     => $message,
		];

		if ($tmp_path != '')
		{
			$file['content'] = $tmp_path . $filename;
			$fp = fopen($file['content'], 'wb');
			fwrite($fp, $message);
			fclose($fp);
		}

		return $file;
	}


	protected function get_trash()
	{
		foreach ($this->get_folders() as $folder)
		{
			if (strtolower($folder) === 'trash' || strtolower($folder) === 'papierkorb')
			{
				return $folder;
			}
		}

		$this->add_folder('Trash');

		return 'Trash';
	}


	protected function get_sent()
	{
		foreach ($this->get_folders() as $folder)
		{
			if (strtolower($folder) === 'sent' || strtolower($folder) === 'gesendet')
			{
				return $folder;
			}
		}

		$this->add_folder('Sent');

		return 'Sent';
	}


	/**
	 * Create the final message array
	 *
	 * @param int  $id           Message uid
	 * @param bool $with_body    Define if the output will get the message body
	 * @param bool $embed_images Define if message body will show embeded images
	 *
	 * @return array
	 */
	protected function format_message($id = 0, $with_body = TRUE, $embed_images = TRUE)
	{
		$header = imap_headerinfo($this->stream, $id);

		$uid = imap_uid($this->stream, $id);

		preg_match('/X-Priority: ([\d])/mi', imap_fetchheader($this->stream, $id), $matches);
		$priority = isset($matches[1]) ? $matches[1] : 3;

		$subject = '';
		if (isset($header->subject) && strlen($header->subject) > 0)
		{
			foreach (imap_mime_header_decode($header->subject) as $obj)
			{
				$subject .= $obj->text;
			}
		}
		$subject = @$this->convert_to_utf8($subject);

		$email = [
			'to'       => isset($header->to) ? $this->array_to_address($header->to) : '',
			'from'     => $this->to_address($header->from[0]),
			'date'     => $header->udate,
			'subject'  => $subject,
			'priority' => $priority,
			'uid'      => $uid,
			'id'       => $id,
			'reply_to' => !empty($header->reply_to[0]) ? $this->to_address($header->reply_to[0]) : '',
			'unread'   => strlen(app_trim($header->Unseen)) > 0,
			'answered' => strlen(app_trim($header->Answered)) > 0,
			'flagged'  => strlen(app_trim($header->Flagged)) > 0,
			'deleted'  => strlen(app_trim($header->Deleted)) > 0,
			'size'     => $header->Size,
		];
		if (isset($header->cc))
		{
			$email['cc'] = $this->array_to_address($header->cc);
		}

		if ($with_body === TRUE)
		{
			$body = $this->get_body($uid);
			$email['body'] = $body['body'];
			$email['html'] = $body['html'];
		}

		$mailStruct = imap_fetchstructure($this->stream, $id);
		$attachments = $this->attachments_to_name($this->get_attachments($id, $mailStruct, ''));
		if (count($attachments) > 0)
		{
			foreach ($attachments as $val)
			{
				$arr = [];
				foreach ($val as $k => $t)
				{
					if ($k == 'name')
					{
						$decodedName = imap_mime_header_decode($t);
						$t = $this->convert_to_utf8($decodedName[0]->text);
					}
					$arr[$k] = $t;
				}
				$email['attachments'][] = $arr;
			}
		}

		if (isset($email['attachments']) && is_array($email['attachments']) && (count(@$email['attachments']) > 0) && (@$email['html'] == TRUE) && ($embed_images == TRUE))
		{
			$email['body'] = $this->embed_images($email);
		}

		return $email;
	}


	/**
	 * HTML embed inline images
	 *
	 * @param array $email
	 *
	 * @return string
	 */
	protected function embed_images($email)
	{
		$html_embed = $email['body'];

		foreach ($email['attachments'] as $key => $attachment)
		{
			if ($attachment['disposition'] == 'inline' && ! empty($attachment['reference']))
			{
				$file = $this->get_attachment($email['id'], $key);

				$reference = str_replace(['<', '>'], '', $attachment['reference']);
				$img_embed = 'data:image/' . strtolower($file['type']) . ';base64,' . base64_encode($file['content']);

				$html_embed = str_replace('cid:' . $reference, $img_embed, $html_embed);
			}
		}

		return $html_embed;
	}


	/**
	 * Return general mailbox statistics
	 *
	 * @return object|false object
	 */
	public function get_mailbox_statistics()
	{
		return is_resource($this->stream) ? imap_mailboxmsginfo($this->stream) : FALSE;
	}


	protected function convert_to_utf8($str = '')
	{
		if(function_exists("iconv")) {
			if (mb_detect_encoding($str, 'UTF-8, ISO-8859-1, GBK') != 'UTF-8') {
				$str = utf8_encode($str);
			}
			$str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
		}
		return $str;
	}


	protected function array_to_address($addresses = [])
	{
		$addressesAsString = [];
		foreach ($addresses as $address)
		{
			$addressesAsString[] = $this->to_address($address);
		}

		return $addressesAsString;
	}


	protected function to_address($headerinfos = [])
	{
		$from = [
			'email' => '',
			'name'  => '',
		];

		if (isset($headerinfos->mailbox) && isset($headerinfos->host))
		{
			$from['email'] = $headerinfos->mailbox . '@' . $headerinfos->host;
		}

		if ( ! empty($headerinfos->personal))
		{
			$name = imap_mime_header_decode($headerinfos->personal);
			$name = $name[0]->text;
			$from['name'] = $this->convert_to_utf8($name);
		}

		return $from;
	}


	/**
	 * Fetch header by message id
	 *
	 * @param int $id Message id
	 *
	 * @return bool|object Message header on success
	 */
	protected function get_message_header($id = 0)
	{
		$count = $this->count_messages();

		for ($i = 1; $i <= $count; $i++)
		{
			$uid = imap_uid($this->stream, $i);
			if ($uid == $id)
			{
				$header = imap_headerinfo($this->stream, $i);

				return $header;
			}
		}

		return FALSE;
	}


	protected function get_body($uid = 0)
	{
		$body = $this->get_part($this->stream, $uid, 'TEXT/HTML');
		$html = TRUE;
		if ($body == '')
		{
			$body = $this->get_part($this->stream, $uid, 'TEXT/PLAIN');
			$html = FALSE;
		}
		$body = $this->convert_to_utf8($body);

		return [
			'body' => $body,
			'html' => $html,
		];
	}


	protected function get_part($imap, $uid = 0, $mimetype = '', $structure = FALSE, $partNumber = FALSE)
	{
		if ( ! $structure)
		{
			$structure = imap_fetchstructure($imap, $uid, FT_UID);
		}

		if ($structure)
		{
			if ($mimetype == $this->get_mime_type($structure))
			{
				if ( ! $partNumber)
				{
					$partNumber = 1;
				}

				$text = imap_fetchbody($imap, $uid, $partNumber, FT_UID | FT_PEEK);

				switch ($structure->encoding)
				{
					case 3:
						return imap_base64($text);
					case 4:
						return imap_qprint($text);
					default:
						return $text;
				}
			}

			if ($structure->type == 1)
			{
				foreach ($structure->parts as $index => $subStruct)
				{
					$prefix = '';

					if ($partNumber)
					{
						$prefix = $partNumber . '.';
					}

					$data = $this->get_part($imap, $uid, $mimetype, $subStruct, $prefix . ($index + 1));

					if ($data)
					{
						return $data;
					}
				}
			}
		}

		return FALSE;
	}


	protected function get_mime_type($structure)
	{
		$primaryMimetype = [
			'TEXT',
			'MULTIPART',
			'MESSAGE',
			'APPLICATION',
			'AUDIO',
			'IMAGE',
			'VIDEO',
			'OTHER',
		];

		if ($structure->subtype)
		{
			return $primaryMimetype[(int)$structure->type] . '/' . $structure->subtype;
		}

		return 'TEXT/PLAIN';
	}


	protected function attachments_to_name($attachments = [])
	{
		$names = [];
		foreach ($attachments as $attachment)
		{
			if (isset($attachment[0]['name']))
			{
				$names[] = [
					'name'        => $attachment[0]['name'],
					'size'        => $attachment[0]['size'],
					'disposition' => $attachment['disposition'],
					'reference'   => $attachment['reference'],
				];
			}
			else
			{
				$names[] = [
					'name'        => $attachment['name'],
					'size'        => $attachment['size'],
					'disposition' => $attachment['disposition'],
					'reference'   => $attachment['reference'],
				];
			}
		}

		return $names;
	}


	protected function get_attachments($mailNum, $part, $partNum = '')
	{
		$attachments = [];

		if (isset($part->parts))
		{
			foreach ($part->parts as $key => $subpart)
			{
				if ($partNum != '')
				{
					$newPartNum = $partNum . '.' . ($key + 1);
				}
				else
				{
					$newPartNum = ($key + 1);
				}
				$result = $this->get_attachments($mailNum, $subpart, $newPartNum);
				if (count($result) != 0)
				{
					if (isset($result[0]['name']))
					{
						foreach ($result as $inline)
						{
							array_push($attachments, $inline);
						}
					}
					else
					{
						array_push($attachments, $result);
					}
				}
			}
		}
		else
		{
			if (isset($part->disposition))
			{
				if (in_array(strtolower($part->disposition), ['attachment', 'inline']))
				{
					$partStruct = imap_bodystruct($this->stream, $mailNum, $partNum);
					$reference = isset($partStruct->id) ? $partStruct->id : '';
					$attachmentDetails = [];
					if (isset($part->dparameters[0]))
					{
						$attachmentDetails = [
							'name'        => $part->dparameters[0]->value,
							'partNum'     => $partNum,
							'enc'         => @$partStruct->encoding,
							'size'        => $part->bytes,
							'reference'   => $reference,
							'disposition' => $part->disposition,
							'type'        => $part->subtype,
						];
					}

					return $attachmentDetails;
				}
			}
			else
			{
				if (isset($part->subtype) && in_array($part->subtype, ['JPEG', 'GIF', 'PNG']))
				{

					$partStruct = imap_bodystruct($this->stream, $mailNum, $partNum);
					$reference = isset($partStruct->id) ? $partStruct->id : '';
					$disposition = empty($reference) ? 'attachment' : 'inline';
					$part->parameters=(array)$part->parameters;
					if (!empty($part->parameters) && isset($part->dparameters[0]->value))
					{
						$name = $part->dparameters[0]->value;
					}
					elseif (!empty($part->parameters) && $part->parameters[0]->value)
					{
						$name = $part->parameters[0]->value;
					}
					else
					{
						$name = 'unknown';
					}
					$attachmentDetails = [];
					if (isset($part->dparameters[0]))
					{
						$attachmentDetails = [
							'name'        => $name,
							'partNum'     => $partNum,
							'enc'         => $partStruct->encoding,
							'size'        => $part->bytes,
							'reference'   => $reference,
							'disposition' => $disposition,
							'type'        => $part->subtype,
						];
					}

					return $attachmentDetails;
				}
			}
		}

		return $attachments;
	}


	public function __destruct()
	{
		if (is_resource($this->stream))
		{
			imap_close($this->stream);
		}
	}

}
