<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2007 - 2013 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flEmail Class
 *
 * Class to send email direct through an smtp server or via the mail
 * command. The class also supports sending HTML emails and file attachments.
 *
 * SMTP interface supports HELO, EHLO, AUTH LOGIN, AUTH PLAIN, MAIL, RCPT
 * and DATA commands.
 *
 * @package fusionLib
 */
class flEmail {
	/**#@+
	 * Email priority.
	 */
	const HIGH = 1;
	const NORMAL = 3;
	const LOW = 5;
	/**#@-*/

	/**
	 * The connection to the server.
	 * @var resource
	 */
	private $connection = false;

	/**
	 * The value to give when in the HELO or EHLO commands.
	 * @var string
	 */
	private $localhost = 'localhost';

	/**
	 * The SMTP server to connect to
	 * @var string
	 */
	private $host = 'localhost';

	/**
	 * The port on the server to connect to.
	 * @var int
	 */
	private $port = 25;

	/**
	 * The ESMTP data returned from the server.
	 * @var array
	 */
	private $esmtp = array();

	/**
	 * The user name to use for authentication, or '' if not authenticating
	 * @var string
	 */
	private $userName = '';

	/**
	 * The password to use for authentication.
	 * @var string
	 */
	private $password = '';

	/**
	 * The mailer to use either mail() (false) or direct smtp (true)
	 * @var bool
	 */
	private $useSMTP = false;

	/**
	 * The user email address to send the mail from.
	 * @var array
	 */
	private $from = array('', '');

	/**
	 * The email address to reply to or NULL.
	 * @var array
	 */
	private $replyTo = null;

	/**
	 * List of recipient addresses, each entry is an array in the form (email, name).
	 * @var array
	 */
	private $recipients = array();

	/**
	 * List of Cc: addresses, each entry is an array in the form (email, name).
	 * @var array
	 */
	private $cc = array();

	/**
	 * List of Bcc: addresses, each entry is an array in the form (email, name).
	 * @var array
	 */
	private $bcc = array();

	/**
	 * The subject of the email.
	 * @var string
	 */
	private $subject = '';

	/**
	 * The text version of the body.
	 * @var string
	 */
	private $textBody = null;

	/**
	 * The HTML version of the body.
	 * @var string
	 */
	private $htmlBody = null;

	/**
	 * Array of attachments in the form array(filename, type, base64 encoded data).
	 * @var array
	 */
	private $attachments = array();

	/**
	 * Array of embedded files in the form array(cid, filename, type, base64 encoded data).
	 *
	 * @var array
	 */
	private $embedded = array();

	/**
	 * The cid to use for the next embedded image.
	 *
	 * @var int
	 */
	private $cid = 1;

	/**
	 * The character set to use.
	 *
	 * @var string
	 */
	private $charset;

	/**
	 * The priority of the message.
	 *
	 * @var int
	 */
	private $priority = flEmail::NORMAL;

	/**
	 * The string for X-Mailer
	 *
	 * @var string
	 */
	static private $mailer = 'fusionLib';

	/**
	 * Create a new mailer object and optionally set the character set being used.
	 *
	 * @param string $charset The character set being used either 'utf-8' the default or 'iso-8859-1'.
	 */
	function __construct($charset = 'utf-8') {
		$this->charset = $charset;
	}

	/**
	 * Set the string to use in the X-Mailer header system wide.
	 *
	 * @param string $mailer The mailer string to set.
	 * @static
	 */
	static function setMailerString($mailer) {
		self::$mailer = $mailer;
	}

	/**
	 * Set the priority to send the mail at.
	 *
	 * @param int $priority The priority to use.
	 * @return flEmail This object.
	 */
	function setPriority($priority) {
		$this->priority = $priority;
		return $this;
	}

	/**
	 * Set the host name used in HELO/EHLO negotiation.
	 *
	 * @param string $localhost The name of the local host used in HELO/EHLO.
	 * @return flEmail This object.
	 */
	function setLocalhost($localhost) {
		$this->localhost = $localhost;
		return $this;
	}

	/**
	 * Set the SMTP server to use for sending emails and the authentication information.
	 *
	 * @param string $host The SMTP host.
	 * @param int $port The port to connect to on the SMTP server, default is 25.
	 * @param string $user The user name to connect with, or '' if authentication not required.
	 * @param string $pass The password for the user.
	 * @return flEmail This object.
	 */
	function setServer($host, $port, $user = '', $pass = '') {
		$this->host = $host;
		$this->port = $port;
		$this->userName = $user;
		$this->password = $pass;
		$this->useSMTP = true;

		return $this;
	}

	/**
	 * Internal routine to read reply from the SMTP server until the connection is closed
	 * or a newline is found, which ever occurs first.
	 *
	 * @return array Each element of the array is array(code, arguments)
	 */
	private function _readReply() {
		$line = '';
		$loops = 100;	// Number of attempts
		$result = array();

		while(!feof($this->connection) && $loops--) {
			$line .= @fgets($this->connection, 2048);

			if(substr($line, -1) == "\n") {
				$line = rtrim($line, "\r\n");
				$result[] = array((int)substr($line, 0, 3), trim(substr($line, 4)));

				// If not multi-line
				if(substr($line, 3, 1) != '-')
					return($result);
				else
					$line = '';
			}
		}
		return $result;
	}

	/**
	 * Internal function to convert an email address to the form
	 * name <email>
	 *
	 * @param array $email The array holding the email address [0] is the email
	 * and [1] is the name.
	 * @return string The combined email.
	 */
	private function _makeAddr($email) {
		if(strlen($email[1]))
			return "\"$email[1]\" <$email[0]>";
		else
			return "<$email[0]>";
	}

	/**
	 * Internal routine to encode the given string to quoted-printable format.
	 *
	 * @param string $string The string to encode.
	 * @return string The string encoded into quotable-printable format.
	 */
    private function _quotedPrintableEncode($string) {
		// Quote everything outside the range 33-126, 9 and 32 also quote 61
	    $string = preg_replace_callback('/([\x00-\x08\x0b-\x1F\x3D\x7F-\xFF])/',
	        function($m) { return sprintf('=%02X', ord($m[1])); },
		    trim(str_replace("\r", '', $string))
	    );

		// Quote space and tab at the end of a line
		$string = preg_replace_callback("/([\x09\x20])\n/",
			function($m) { return sprintf("=%02X\n", ord($m[1])); },
			$string
		);

		// Break lines to 76 characters
		$lines = preg_split("/\n/", $string);
		$numLines = count($lines);

		for($i=0;$i<$numLines;$i++) {
			if(strlen($lines[$i]) > 76)
				$lines[$i] = preg_replace('/(.{73}((=[0-9a-f]{2})|([^=]{0,3})))/i', "\\1=\n", $lines[$i]);
		}

		return implode("\n", $lines);
	}

	/**
	 * Internal routine to perform authentication.
	 *
	 * @return bool true if authenticated, else false.
	 */
	private function _auth() {
		// If auth not required
		if(!strlen($this->userName))
			return true ;

		// If not auth options
		if(!isset($this->esmtp['AUTH']))
			return false;

		// Get available auth methods
		$method = explode(' ', strtoupper($this->esmtp['AUTH']));

		// Use LOGIN method
		if(in_array('LOGIN', $method)) {
			// Start login
			if(!fwrite($this->connection, "AUTH LOGIN\r\n"))
				return false;
			$r = $this->_readReply();
			if($r[0][0] != 334)
				return false;

			// Send user name
			if(!fwrite($this->connection, base64_encode($this->userName) . "\r\n"))
				return(false);
			$r = $this->_readReply();
			if($r[0][0] != 334)
				return false;

			// Send the password
			if(!fwrite($this->connection, base64_encode($this->password) . "\r\n"))
				return false;
			$r = $this->_readReply();
			if($r[0][0] != 235)
				return false;
		}
		// Use PLAIN method
		else if(in_array('PLAIN', $method)) {
			// Start login
			if(!fwrite($this->connection, "AUTH PLAIN\r\n"))
				return false;
			$r = $this->_readReply();
			if($r[0][0] != 334)
				return false;

			// Send user name & password
			if(!fwrite($this->connection, base64_encode(chr(0) . $this->userName . chr(0) . $this->password) . "\r\n"))
				return false;
			$r = $this->_readReply();
			if($r[0][0] != 235)
				return false;
		}
		// Else nothing we support
		else
			return false;

		return true;
	}

	/**
	 * Form a connection to the SMTP server.
	 *
	 * @return bool True if connected else false.
	 */
	function connect() {

		// If using SMTP
		if($this->useSMTP) {
			// Connect to the host
			$errno = 0;
			$errstr = '';
			$this->connection = fsockopen($this->host, $this->port, $errno, $errstr, 5);

			if($this->connection == FALSE)
				return false;

			// Get reply from server
			$r = $this->_readReply();
			if(@$r[0][0] != 220) {
				fclose($this->connection);
				$this->connection = false;
				return false;
			}

			// Try EHLO command
			if(!fwrite($this->connection, "EHLO $this->localhost\r\n")) {
				fclose($this->connection);
				$this->connection = false;
				return false;
			}

			$r = $this->_readReply();
			if($r[0][0] != 250) {
				// EHLO Failed so try HELO
				if(!fwrite($this->connection, "HELO $this->localhost\r\n")) {
					fclose($this->connection);
					$this->connection = false;
					return false;
				}

				$r = $this->_readReply();
				if($r[0][0] != 250) {
					fclose($this->connection);
					$this->connection = false;
					return false;
				}
			}

			// Extract the ESMTP verbs
			array_shift($r);
			$this->esmtp = array();
			foreach($r as $a) {
				$verb = strtok($a[1], ' ');
				$arg = substr($a[1], strlen($verb) + 1);
				$this->esmtp[$verb] = $arg;
			}

			// Authenticate
			if(!$this->_auth()) {
				$this->close();
				return false;
			}
		}

		// Return connected
		return true;
	}

	/**
	 * Internal function to create the message part of the email, this may include embedded files.
	 *
	 * @return array The headers and the body (0 = headers, 1 = body).
	 */
	private function _createMsgPart() {

		// If no embedded or attached files
		if(!count($this->embedded) && !count($this->attachments)) {
			// HTML only
			if(is_null($this->textBody)) {
				$headers = "Content-Type: text/html; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n";
				$body = "$this->htmlBody\n";
			}
			// Text only
			else if(is_null($this->htmlBody)) {
				$headers = "Content-Type: text/plain; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n";
				$body = "$this->textBody\n";
			}
			else {
				$boundaryA = uniqid('----=_NextPart_a_'); // alternative

				$headers = "Content-Type: multipart/alternative; boundary=\"$boundaryA\"\n";

				$body = "--$boundaryA\nContent-Type: text/plain; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n\n";
				$body .= "$this->textBody\n\n";
				$body .= "--$boundaryA\nContent-Type: text/html; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n\n";
				$body .= "$this->htmlBody\n\n--$boundaryA--\n\n";
			}
		}
		else {
			$boundary = uniqid('----=_NextPart_e_'); // embedded

			$headers = "Content-Type: multipart/related;\n\tboundary=\"$boundary\"\n";

			// HTML only
			if(is_null($this->textBody)) {
				$headers .= "--$boundary\nContent-Type: text/html; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n";
				$body = "$this->htmlBody\n\n--$boundary\n\n";
			}
			// Text only
			else if(is_null($this->htmlBody)) {
				$headers .= "--$boundary\nContent-Type: text/plain; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n";
				$body = "$this->textBody\n\n--$boundary\n\n";
			}
			// Else has plain text as well
			else {
				$boundaryA = uniqid('----=_NextPart_a_'); // alternative

				$headers .= "--$boundary\nContent-Type: multipart/alternative; boundary=\"$boundaryA\"\n";

				$body = "--$boundaryA\nContent-Type: text/plain; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n\n";
				$body .= "$this->textBody\n\n";
				$body .= "--$boundaryA\nContent-Type: text/html; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n\n";
				$body .= "$this->htmlBody\n\n--$boundaryA--\n\n";
			}

			// Add embedded files
			foreach($this->embedded as $a)
				$body .= "--$boundary\nContent-Type: $a[2]; name=\"$a[1]\"\nContent-Transfer-Encoding: base64\nContent-ID: $a[0]\nContent-Disposition: inline;\n\tfilename=\"$a[1]\"\n\n$a[3]\n\n";

			// Add attachments
			foreach($this->attachments as $a)
				$body .= "--$boundary\nContent-Type: $a[1]; name=\"$a[0]\"\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment;\n\tfilename=\"$a[0]\"\n\n$a[2]\n\n";

			$body .= "--$boundary--\n";
		}

		return array($headers, $body);
	}

	/**
	 * Send the email
	 *
	 * @return bool true if the email was sent or false if the email could not be sent.
	 */
	function send() {

		// Build the common headers
		$headers = 'From: ' . $this->_makeAddr($this->from) . "\n";

		// Add date
		$headers .= 'Date: ' . date('D, j M Y H:i:s O') . "\n";

		// Add message ID
		$headers .= 'Message-ID: <' . md5(uniqid(time())) . "@$this->localhost>\n";

		// The user agent
		$headers .= 'X-Mailer: ' . self::$mailer . "\n";

		// Add priority (1 = High, 3 = Normal, 5 = Low)
		$headers .= "X-Priority: $this->priority\n";

		// If has reply to
		if(!is_null($this->replyTo))
			$headers .= 'Reply-To: ' . $this->_makeAddr($this->replyTo) . "\n";

		// To: list
		if($this->useSMTP) {
			if(!count($this->recipients)) {
				$headers .= "To: undisclosed-recipients:;\n";
			}
			else {
				$to = array();
				foreach($this->recipients as $addr)
					$to[] = $this->_makeAddr($addr);
				$headers .= 'To: ' . implode(', ', $to) . "\n";
			}
		}

		// Cc: list
		if(count($this->cc)) {
			$to = array();
			foreach($this->cc as $addr)
				$to[] = $this->_makeAddr($addr);
			$headers .= 'cc: ' . implode(', ', $to) . "\n";
		}

		$body = '';

		// If has HTML or attachments
		if(!is_null($this->htmlBody) || count($this->attachments)) {

			// Build the headers
			$headers .= "MIME-Version: 1.0\n";

			// Build the headers and body
			list($h, $b) = $this->_createMsgPart();
			$headers .= $h;
			$body .= $b;
		}
		// Simple text email
		else {
			$headers .= "Content-Type: text/plain; charset=\"$this->charset\"\nContent-Transfer-Encoding: quoted-printable\n";
			$body = $this->textBody;
		}

		// If using SMTP
		if($this->useSMTP) {
			if(!is_resource($this->connection))
				return false;

			$headers = "Subject: $this->subject\n" . $headers;

			// merge the header & body also change \n & \r to \n\r
			$data = preg_replace("/([^\r]{1})\n/", "\\1\r\n", $headers . "\r\n" . $body);
			$data = preg_replace("/\n\n/", "\n\r\n", $data);

			// Replace lines starting with . with ..
			$data = preg_replace("/\n\./", "\n..", $data);

			// Check the message size limits
			if(isset($this->esmtp['SIZE']) && $this->esmtp['SIZE'] > 0 && $this->esmtp['SIZE'] < strlen($data))
				return false;

			// Send who the mail is from
			if(!fwrite($this->connection, 'MAIL FROM:<' . $this->from[0] . ">\r\n"))
				return false;
			$r = $this->_readReply();
			if(!isset($r[0][0]) || $r[0][0] != 250)
				return false;

			// Write all the recipients
			foreach($this->recipients as $adr) {
				if(!fwrite($this->connection, "RCPT TO:<$adr[0]>\r\n"))
					return false;
				$r = $this->_readReply();
				if(!isset($r[0][0]) || ($r[0][0] != 250 && $r[0][0] != 251))
					return false;
			}

			// Write all the ccs
			foreach($this->cc as $adr) {
				if(!fwrite($this->connection, "RCPT TO:<$adr[0]>\r\n"))
					return false;
				$r = $this->_readReply();
				if(!isset($r[0][0]) || ($r[0][0] != 250 && $r[0][0] != 251))
					return false;
			}

			// Write all the bccs
			foreach($this->bcc as $adr) {
				if(!fwrite($this->connection, "RCPT TO:<$adr[0]>\r\n"))
					return false;
				$r = $this->_readReply();
				if(!isset($r[0][0]) || ($r[0][0] != 250 && $r[0][0] != 251))
					return false;
			}

			// Write the start of data
			if(!fwrite($this->connection, "DATA\r\n"))
				return false;
			$r = $this->_readReply();
			if(!isset($r[0][0]) || $r[0][0] != 354)
				return false;

			// Write headers & body
			if(!fwrite($this->connection, "$data\r\n.\r\n"))
				return false;
			$r = $this->_readReply();
			if(!isset($r[0][0]) || $r[0][0] != 250)
				return false;

			// Worked
			return true;
		}
		// Send with mail command
		else {
			// bcc: list
			if(count($this->bcc)) {
				$to = array();
				foreach($this->bcc as $addr)
					$to[] = $this->_makeAddr($addr);
				$headers .= 'Bcc: ' . implode(', ', $to) . "\n";
			}

			// Reciptients
			$to = array();
			foreach($this->recipients as $addr)
				$to[] = $this->_makeAddr($addr);

			// Send
			return mail(implode(', ', $to),
						$this->subject,
						$body,
						$headers,
						'-f ' . $this->from[0]);
		}
	}

	/**
	 * Close the connection to the SMTP server.
	 */
	function close() {
		if($this->useSMTP && is_resource($this->connection)) {
			fwrite($this->connection, "QUIT\r\n");
			$this->_readReply(); // discard results
			fclose($this->connection);
			$this->connection = false;
		}
	}

	/**
	 * Set the email address of the sender.
	 * @param string $email Email address of the sender.
	 * @param string $name Name of the sender.
	 * @return flEmail This object.
	 */
	function setFrom($email, $name = '') {
		$this->from[0] = $email;
		$this->from[1] = $name;
		return $this;
	}

	/**
	 * Set the email address to send replies to.
	 * @param string $email Email address to reply to.
	 * @param string $name Name of the address to reply to.
	 * @return flEmail This object.
	 */
	function replyTo($email, $name = '') {
		$this->replyTo = array($email, $name);
		return $this;
	}

	/**
	 * Clear the reply to address
	 * @return flEmail This object.
	 */
	function clearReplyTo() {
		$this->replyTo = NULL;
		return $this;
	}

	/**
	 * Add an email address to the recipient list (To:)
	 * @param string $email Email address of the recipient.
	 * @param string $name Name of the recipient.
	 * @return flEmail This object.
	 */
	function addRecipient($email, $name = '') {
		$this->recipients[] = array($email, $name);
		return $this;
	}

	/**
	 * Clear the recipient list.
	 * @return flEmail This object.
	 */
	function clearRecipients() {
		$this->recipients = array();
		return $this;
	}

	/**
	 * Add an email address to the Cc list
	 * @param string $email Email address of the recipient.
	 * @param string $name Name of the recipient.
	 * @return flEmail This object.
	 */
	function addCc($email, $name = '') {
		$this->cc[] = array($email, $name);
		return $this;
	}

	/**
	 * Clear the Cc list.
	 * @return flEmail This object.
	 */
	function clearCc() {
		$this->cc = array();
		return $this;
	}

	/**
	 * Add an email address to the Bcc list
	 * @param string $email Email address of the recipient.
	 * @param string $name Name of the recipient.
	 * @return flEmail This object.
	 */
	function addBcc($email, $name = '') {
		$this->bcc[] = array($email, $name);
		return $this;
	}

	/**
	 * Clear the Bcc list.
	 * @return flEmail This object.
	 */
	function clearBcc() {
		$this->bcc = array();
		return $this;
	}

	/**
	 * Set the subject.
	 * @param string $subject The subject string.
	 * @return flEmail This object.
	 */
	function setSubject($subject) {
		// if using UTF-8 then encode the subject
		if($this->charset == 'utf-8')
			$this->subject = '=?utf-8?B?' . base64_encode($subject) . '?=';
		else
			$this->subject = $subject;
		return $this;
	}

	/**
	 * Set the text body.
	 * @param string $text The text body
	 * @return flEmail This object.
	 */
	function setTextBody($text) {
		$this->textBody = $this->_quotedPrintableEncode($text);
		return $this;
	}

	/**
	 * Set the HTML body encoding it to quoted-printable, and references to embedded files are updated.
	 *
	 * @param string $html The html body.
	 * @return flEmail This object.
	 */
	function setHTMLBody($html) {

		// Update embedded file referenced
		foreach($this->embedded as $e) {
			$html = preg_replace('/(<[^<>]+src=")' . preg_quote($e[1], '/') . '("[^<>]*>)/i', "$1cid:$e[0]$2", $html); // HTML
			$html = preg_replace('/(:\s*url\s*\("?)' . preg_quote($e[1], '/') . '("?\))/i', "$1cid:$e[0]$2", $html); // CSS
		}

		$this->htmlBody = $this->_quotedPrintableEncode($html);
		return $this;
	}

	/**
	 * Clear the email body.
	 *
	 * @return flEmail This object.
	 */
	function clearBody() {
		$this->textBody = $this->htmlBody = null;
		return $this;
	}

	/**
	 * Attach a file to the email.
	 *
	 * @param string $file The file name of the file to attach.
	 * @param string $type The mime type, e.g. image/jpeg
	 * @param string $name Optional file name to attach the file as.
	 * @return bool true is returned if the file was attached or false on error.
	 */
	function attachFile($file, $type = null, $name = null) {
		if(is_readable($file)) {
			$this->attachFileData(file_get_contents($file), is_null($type) ? flMimeTypes::getType($file) : $type, $name === null ? basename($file) : $name);
			return true;
		}
		else
			return false;
	}

	/**
	 * Attach file data to the email.
	 *
	 * @param string $data The file data to attach.
	 * @param string $type The mime type, e.g. image/jpeg
	 * @param string $name File name to attach the file as.
	 */
	function attachFileData($data, $type, $name) {
		$this->attachments[] = array($name, $type, rtrim(chunk_split(base64_encode($data))));
	}

	/**
	 * Clear the list of attached files.
	 *
	 * @return flEmail This object.
	 */
	function clearAttachedFiles() {
		$this->attachments = array();
		return $this;
	}

	/**
	 * Embed an image file within the email, these files are only sent if an HTML email is also present.
	 *
	 * All embedded files must be added before calling setHTMLBody or they will not be referenced.
	 *
	 * @param string $file The name of the file within the file system to embed.
	 * @param string $src The file name used in the HTMl to reference the file, must be an exact match.
	 * @param string $type The mime type of the file e.g. image/jpg, image/gif or image/png or null to guess
	 * the type from the file name.
	 * @return bool true is returned if the file was embedded or false on error.
	 */
	function addEmbeddedFile($file, $src, $type = null) {
		if(is_readable($file)) {
			$this->addEmbeddedFileData(file_get_contents($file), $src, is_null($type) ? flMimeTypes::getType($file) : $type);
			return true;
		}
		else
			return false;
	}

	/**
	 * Embed an image file within the email, these files are only sent if an HTML email is also present.
	 *
	 * All embedded files must be added before calling setHTMLBody or they will not be referenced.
	 *
	 * @param string $data The data to embed.
	 * @param string $src The file name used in the HTMl to reference the file, must be an exact match.
	 * @param string $type The mime type of the file e.g. image/jpg, image/gif or image/png or null to guess
	 * the type from the file name.
	 */
	function addEmbeddedFileData($data, $src, $type = null) {
		$this->embedded[] = array($this->cid++, $src, is_null($type) ? flMimeTypes::getType($src) : $type, rtrim(chunk_split(base64_encode($data))));
	}

	/**
	 * Clear the list of embedded files.
	 * @return flEmail This object.
	 */
	function clearEmbedded() {
		$this->embedded = array();
		return $this;
	}
}
