<?php

use LightnCandy\Compiler;

// to do: cron, run once per day to remove old compiled templates 

class MailObject {
	private $to = ['to' => [], 'cc' => [], 'bcc' => []];
	private $body = FALSE;
	private $css = FALSE;
	private $text = FALSE;
	private $subject;
	private $attachments;
	private $images;
	private $data;
	private $template = FALSE;
	private $header = [];
	public  $DKIM;
	
	public function __construct() {
		$this->images = [];
		$this->DKIM = FALSE;
	}
	
	public function body() {			// this is HTML.  Text version created automagically
		if (func_num_args()) {
			$this->body = func_get_arg(0);
			$this->processLocalImages();
			return $this;
		}
		return $this->body;
	}
	
	public function to() {				// to('email@address', 'name')  or   to('"name" <email@address>');
		return $this->_to('to', func_num_args(), func_get_args());
	}
	public function cc() {				// 
		return $this->_to('cc', func_num_args(), func_get_args());
	}
	public function bcc() {				// 
		return $this->_to('bcc', func_num_args(), func_get_args());
	}
	public function replyto() {	    	// 
		return $this->_to('reply', func_num_args(), func_get_args());
	}
	public function from() {	    	// 
		return $this->_to('from', func_num_args(), func_get_args());
	}
	
	public function getImages() {
		return $this->images;
	}
	
	private function _to($index, $argc, $argv) {
		if (!$argc) {
			return $this->to[$index];
		}
		
		if ($argc == 1) {
			if (preg_match('~^(.*)\<(.*)\>~', $argv[0], $match)) {
				$email = $match[2];
				$name = trim($match[1]);
				if (preg_match('~^\"(.*)\"$~', $name, $match)) {
					$name = stripslashes($match[1]);
				}
			} else {
				$email = $argv[0];
				$name = '';
			}
		} else {
			$email = $argv[0];
			$name = $argv[1];
		}
		
		if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
			$this->to[$index][] = ['email' => $email, 'name' => $name];
		}
		return $this;
	}

	public function subject() {			// can be utf8, or contain gmail emojis
		if (func_num_args()) {
			$this->subject = func_get_arg(0);
			return $this;
		}
		return $this->subject;
	}

	// <style> tags are not valid within a html email. So styles are moved inline
	public function css() {				// css styles for the html.  this will be merged in with the final html
		if (func_num_args()) {
			$this->css = func_get_arg(0);
			return $this;
		}
		return $this->css;
	}

	// Use a Handlebars template. Instead of body();
	// parameter is name of template, not template text
	public function template() {
		if (func_num_args()) {
			$this->template = func_get_arg(0);
			// $this->body = '';		// load the template
			return $this;
		}
		return $this->template;
	}

	public function data() {			// data for handlebars template
		if (func_num_args()) {
			$this->data = func_get_arg(0);
			return $this;
		}
		return $this->data;
	}

	public function text() {			// plain text version of html
		if (func_num_args()) {
			$this->text = func_get_arg(0);
			return $this;
		}
		return $this->text;
	}
	
	public function setDKIM($selector, $private_key_path, $passphrase = '') {
		$this->DKIM = [
			'selector'	=> $selector,
			'keyfile'	=> $private_key_path,
			'passphrase'=> $passphrase,
		];
		return $this;
	}
	
	public function addHeader($key, $value) {
		$this->header[] = [
			'key' => $key,
			'value' => $value,
		];
	}
	
	public function Headers() {
		return $this->header;
	}

	// returns an image identifier (md5(content))
	public function addInlineImage($imagedata, $name) {
		$id = md5($imagedata);
		$this->images[$id] = [
			'image'			=> $imagedata,
			'name'			=> $name,
			'disposition'	=> 'inline',
		];
		return 'cid:' . $id;
	}
	
	// addAttachment
	public function addAttachment($filecontent, $filename, $cid = FALSE) {
		$disposition = $cid === FALSE ? 'attachment' : 'inline';
		if ($cid === FALSE) $cid = md5($filecontent);
		$this->images[$cid] = [
			'image'			=> $filecontent,
			'name'			=> $filename,
			'disposition'	=> $disposition,
			'cid'			=> $cid,
		];
		return 'cid:' . $cid;
	}
	
	// this is for inline images
	// addImage -> returns an image identifier (md5(content))
	
	// Send the email using any configured/active transport 
	public function Send() {
		// if handlebars then compile with data
		hook_execute('email.transform', $this);
	
		$result = hook_execute('email.send', NULL, $this);
		return $result;
	}
	
	// load any local images, update <img> html
	private function processLocalImages() {
		$parser = new \phpHTMLimage;
		$parser->parent = $this;		
		$this->body = $parser->process($this->body);
	}
	
	public function __ReplaceImage($uri) {
		// load the image
		$localfile = HOME . '/public' . $uri;
		$content = file_get_contents($localfile);
		$name = pathinfo($uri, PATHINFO_BASENAME);
		// attach the image, getting new name
		$newname = $this->addInlineImage($content, $name);
		return $newname;
	}
	
}

	/*
	   phpHTMLparse v1.0 - PHP HTML parser
	   Copyright (C) 2001 Nathan <nathan@0x00.org> 

	   This program is free software; you can redistribute it and/or
	   modify it under the terms of the GNU General Public License
	   as published by the Free Software Foundation; either version 2
	   of the License, or (at your option) any later version.

	   This program is distributed in the hope that it will be useful,
	   but WITHOUT ANY WARRANTY; without even the implied warranty of
	   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	   GNU General Public License for more details.

	   You should have received a copy of the GNU General Public License
	   along with this program; if not, write to the Free Software
	   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
	*/

		class phpHTMLparse {
			var $tag_handler, $content_handler;
			var $ABORT;

			function __construct($handletag_func=NULL, $handlecontent_func=NULL) {
				/* Setup default handlers if none specified */
				$this->ABORT = 0;
				if ($handletag_func===NULL)
					$handletag_func='handle_tag';
				if ($handlecontent_func==NULL)
					$handlecontent_func='handle_content';
				$this->set_handlers($handletag_func, $handlecontent_func);
			}

			function set_handlers($tag, $content) {
				/* Sets up the handlers */
				$this->set_tag_handler($tag);
				$this->set_content_handler($content);
			}

			function set_content_handler($handlecontent_func) {
				/* Set the content handler */
				
				/* Check if there is a locally defined method for the handler (extended), then check global, abort if none. */
				if (method_exists($this, $handlecontent_func)) {
					$this->content_handler=array('SCOPE'=>'METHOD', 'FUNC'=>$handlecontent_func);
				} else if (function_exists($handlecontent_func)) {
					$this->content_handler=array('SCOPE'=>'FUNCTION', 'FUNC'=>$handlecontent_func);
				} else {
					$this->content_handler=NULL;
					user_error('No content handlers defined!!', E_USER_ERROR);
				}	
			}

			function set_tag_handler($handletag_func) {
				/* Set the tag handler */

				/* Check if there is a locally defined method for the handler (extended), then check global, abort if none. */
				if (method_exists($this, $handletag_func)) {
					$this->tag_handler=array('SCOPE'=>'METHOD', 'FUNC'=>$handletag_func);
				} else if (function_exists($handletag_func)) {
					$this->tag_handler=array('SCOPE'=>'FUNCTION', 'FUNC'=>$handletag_func);;	
				} else {
					$this->tag_handler=NULL;
					user_error('No tag handlers defined!!', E_USER_ERROR);
				}	
			}

			function parse($s) {
				if (!$this->tag_handler || !$this->content_handler) {
					user_error('Not parsing.  Not all handlers defined!!', E_USER_ERROR);
					return FALSE;
				}
				/* Add one opening < to the end just to force parse to force the output, otherwise it would have to do work when main loop ends. */
				$s.='<';
				$len=strlen($s);
				$lastmark=0;
				$curs='';
				$gotopen=FALSE;
				for ($i=0; $i<$len && !$this->ABORT; $i++) {
					switch ($s[$i]) {
						case '<':
							/* If we are already in a < (unclosed) then this < is just an extra one */
							if ($gotopen) {
								$curs.=$s[$i];
								continue 2;
							}
							$gotopen=TRUE;
							if ($curs=='') continue 2;
							
							/* Check if we should be calling a local method or a global function */
							if ($this->content_handler['SCOPE']=='METHOD') $this->{$this->content_handler['FUNC']}($curs);
							else if ($this->content_handler['SCOPE']=='FUNCTION') $this->content_handler['FUNC']($curs);
							$curs='';
						break;
						
						case '>':
							/* If we are not in an opening tag, then this is just a wondering > */
							if (!$gotopen) {
								$curs.=$s[$i];
								continue 2;
							}
							$data=$this->_parse_tag($curs);
							$uppervariables=array();
							/* Setup an arrray of all the variables in upper case (references) */
							foreach ($data['VARIABLES'] as $k=>$v) {
								$uppervariables[strtoupper($k)]=&$data['VARIABLES'][$k];
							}
							$data['UPPERVARIABLES']=$uppervariables;
							$data['RAWTAG']="<$curs>";
							
							/* Check if we should be calling a local method or a global function */
							if ($this->tag_handler['SCOPE']=='METHOD') $this->{$this->tag_handler['FUNC']}($data);
							else if ($this->tag_handler['SCOPE']=='FUNCTION') $this->tag_handler['FUNC']($data);
							$curs='';
							$gotopen=FALSE;
						break;
						
						default:
							if ($s[$i] == "\n" && $gotopen) {
								$curs .= ' '; //PJY
							} else {		
								$curs .= $s[$i];
							}
						break;
					}

				}
			}

			/* Converts all the variables in $tag["VARIABLS"] back to var1="blah1" var2="blah2" */
			function variable_string($variables) {
				$args='';
				foreach ($variables as $k=>$v) {
					/* Make sure we quote the variables with the original quoter, if any.  Just incase they used ' instead of " or maybe they didnt use any! */
					$quotewith=$v['QUOTEWITH'];
					$val=$v['VALUE'];
					if ($v['NOVALUE']) $args.="$k ";
					else $args.="$k={$quotewith}$val{$quotewith} ";		// PJY
				}
				/* Byebye extra space, return */
				return substr($args, 0, -1);
			}

			function _parse_tag($tagline) {
				$spacing=array(' ', "\t", "\r", "\n");
				$quoters=array("'", '"');
				/* Add an extra white space to the end of the tagline, just to make sure the loop reaches all points of the pass data */
				$tagline.= ' ';
				$dat=array();
				$dat['VARIABLES']=array();
				$i=strpos($tagline, ' ');
				if ($i===FALSE) {
					/* If we did not find a space, then this is just a tag like <br>, store it and return below. */
					$dat['TAG']=$tagline;
				} else {
					$dat['TAG']=substr($tagline, 0, $i);
				}
				/* Store upper case version of the tag, easier for user handlers */
				$dat['UPPERTAG']=strtoupper($dat['TAG']);
				if ($i===FALSE) return $dat; /* Return if we did not find a " " in the original string */
				$len=strlen($tagline);
				$varname='';
				$value='';
				$state='VAR';
				$closeon=NULL;
				for ($i++; $i<$len; $i++) {
					$c=$tagline[$i];
					if ($state=='VAR') {
						/* If we reached white space while in var mode, that means its just a single name. (<a href="moo" FOOO>) */
						if (in_array($c, $spacing)) {
							$state='VAR';
							$dat['VARIABLES'][$varname]=array('VALUE'=>NULL, 'NOVALUE'=>TRUE, 'QUOTEWITH'=>NULL);
							$value='';
							$varname='';
							if ($c==$closeon) $i++;
						} else if ($c=='=') {
							/* We got a name=value pair */
							$state='VAL';
							$temp=$i;
							$temp++;
							/* Skip over any extra white space <a foo=  "bar"> */
							while ($len>$temp && in_array($tagline[$temp], $spacing)) {
								$temp++;
							}
							/* If we skipped over space, then set $i to where we skipped to */
							if ($temp!=($i+1)) $i=$temp;
							$closeon=NULL;
							/* Do we have a quoter (', ")? If so, store it so we know what to close on.  */
							if (in_array($tagline[$i+1], $quoters)) { 
								$closeon=$tagline[$i+1];
								$i++;
							}
						} else {
							$varname.=$c;
						}
					} else if ($state=='VAL') {
						/* If we got $close on (", ') or closein is NULL and this is white space */
						if ($c==$closeon || ($closeon==NULL && (in_array($c, $spacing)))) {
							$state='VAR';
							$dat['VARIABLES'][$varname]=array('VALUE'=>$value, 'NOVALUE'=>FALSE, 'QUOTEWITH'=>$closeon);
							$value='';
							$varname='';
							if ($c==$closeon) $i++;
						} else {
							$value.=$c;
						}
					}
				}
				return $dat;
			}

		}

	//////////////// End of  GNU General Public License code

	class phpHTMLimage extends phpHTMLparse {
		protected $newhtml;
		function __construct() {
			parent::__construct();
			$this->newhtml = '';
		}
		function handle_tag($dat) {
			$raw = strtolower($dat['TAG']);
			if ($raw == 'img') {
				$url = $dat['UPPERVARIABLES']['SRC']['VALUE'] ?? '';
				if (substr($url, 0, 1) == '/') {
					$newname = $this->parent->__ReplaceImage($url);
					// replace src in url
					$dat['UPPERVARIABLES']['SRC']['VALUE'] = $newname;
				}
			}
			$this->newhtml .= $this->reassemble_tag($dat);
		}
		function handle_content($dat) {
			$this->newhtml .= $dat;
		}
		
		function reassemble_tag(&$dat) {
			$raw = strtolower($dat['TAG']);
			$output = $raw;
			foreach($dat['UPPERVARIABLES'] as $name => $data) {
				$output .= ' ' . strtolower($name);
				if (!$data['NOVALUE']) {
					$output .= '=' . $data['QUOTEWITH'] . $data['VALUE'] . $data['QUOTEWITH'];
				}
			}
			return "<$output>";
		}
		
		function process($data) {
			$this->parse($data);
			return $this->newhtml;
		}
		
	}

	class phpHTMLplain extends phpHTMLparse {
		var $newhtml, $inStyle;
		function __construct() {
			parent::__construct();
			$this->section = 0;
			$this->inStyle = 0;
			$this->link = '';
		}
		
		function handle_tag($dat) {
			$raw = strtolower($dat['TAG']);
			if ($raw == 'style' || $raw == 'script') {
				$this->inStyle = 1;
			} elseif ($raw == '/style' || $raw == '/script') {
				$this->inStyle = 0;
			} elseif ($this->section == 1) {
				if ($raw == 'li') {
					$this->newhtml .= "\n * ";
				} elseif ($raw == '/td') {
					if (substr($this->newhtml, -1) != ' ') {
						$this->newhtml .= '  ';
					}
				} elseif (($raw == 'p') || ($raw == '/p') || ($raw == 'br') || ($raw == '/tr') || ($raw == '/div')) {
					$this->newhtml .= "\n";		// </p><p> should give a blank line
				} elseif ($raw == 'style' || $raw == 'script') {
					$this->inStyle = 1;
				} elseif ($raw == '/style' || $raw == '/script') {
					$this->inStyle = 0;
				} elseif ($raw == 'img') {
					$this->newhtml .= '[img]'; 
				} elseif ($raw == 'a') {
					$this->link = isset($dat['UPPERVARIABLES']['HREF']) ? $dat['UPPERVARIABLES']['HREF']['VALUE'] : '';
				} elseif ($raw == '/a' && $this->link != '') {
					$this->newhtml .= ", {$this->link} ";
				}
			}
		}
		
		function reassemble_tag(&$dat) {
			$raw = strtolower($dat['TAG']);
			$output = $raw;
			foreach($dat['UPPERVARIABLES'] as $name => $data) {
				$output .= ' ' . strtolower($name);
				if (!$data['NOVALUE']) {
					$output .= '=' . $data['QUOTEWITH'] . $data['VALUE'] . $data['QUOTEWITH'];
				}
			}
			return "<$output>";
		}
		function handle_content($dat) {
			if (!$this->inStyle || $this->section == 2) $this->newhtml .= $dat;
		}
		function process_plaintext($data) {
			$data = str_replace(array("\n","\r"), ' ', $data);
			$data = str_replace(array('    ','   ','  '), ' ', $data);
			$this->newhtml='';
			$this->section = 1;
			$this->parse($data);
			$data = str_replace(array('&nbsp;','&lt;','&gt;', '&#8230;', '&quot;', '&#8220;', '&#8221;', '&raquo;', '&rsquo;', '&acute;', "\n\n\n"),
								   array(' ','<','>', '...', '"', '"', '"', "'", "'", "'", "\n\n"), $this->newhtml);
			$lines = explode("\n", $data);
			$data = '';
			$lastblank = 1;
			foreach($lines as $line) {
				$m = trim($line);
				if ($m == '') {
					if (!$lastblank) {
						$data .= "\r\n";
					}
					$lastblank = 1;
				} else {
					$lastblank = 0;
					$data .= $line . "\r\n";
				}
				
			}
			
			 return wordwrap($data, 90);
		}
			
	}

/*
	TLS error on connection from 150.93.185.35.bc.googleusercontent.com (pkp.the-computer-site.com) [35.185.93.150] (gnutls_handshake): A TLS fatal alert has been received.
	-- this will happen with an expired certificate:
	
	 gnutls-cli -s -p 466 smtp.hostz.org
	will give (these are not problems):  
- The hostname in the certificate matches 'smtp.hostz.org'.
- Peer's certificate issuer is unknown
- Peer's certificate is NOT trusted
	
	
*/
