Files
DomFramework/mail.php
2020-09-07 14:08:26 +00:00

1680 lines
56 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
*/
/** The class to create a complete email. Can read an email from a content
*/
class mail
{
/** The complete of the mail
*/
private $completeEmailEML = "";
/** Private the separator between the headers and the mail. Should be
* \r\n on a line, but special crafted mails may use something else
*/
private $headerBodySeparator = "\r\n";
/** Sections definitions
* Will store the mails sections with the parameters :
* _parent string|null The parent identifier
* _boundary string|null The Boundary identifier
* _contentType string The simplified Content Type
* Content-Type string The complete Content-Type used in previous headers
* _partsIDchild array the parts ID linked to this section
*/
private $sections = array ();
/** Counter for the recursion */
private $recurse;
/** Display the existing sections lighter than print_r, without the Carriage
* returns
*/
private function printSections ()
// {{{
{
foreach ($this->sections as $sectionID=>$vals)
{
echo "[$sectionID] => Array (\n";
foreach ($vals as $key=>$val)
{
if (is_array ($val))
{
echo " [$key] => Array (\n";
foreach ($val as $k2=>$v2)
{
if (is_array ($v2))
{
echo " [$k2] => Array (\n";
foreach ($v2 as $k3=>$v3)
echo " [$k3] => ".rtrim ($v3, "\r\n")."\n";
echo " )\n";
}
else
{
echo " [$k2] => ".rtrim ($v2, "\r\n")."\n";
}
}
echo " )\n";
}
else
{
echo " [$key] => ".rtrim ($val, "\r\n")."\n";
}
}
echo ")\n\n";
}
}
// }}}
/** Add a new section
* @param array $param The parameters to store
* @return the sectionID
*/
private function sectionAdd ($param)
// {{{
{
$sectionID = md5 (microtime(true).rand());
$this->sections[$sectionID] = $param;
return $sectionID;
}
// }}}
/** Add a new section with the default parameters
* @return array The sectionID stored with the default parameters
*/
private function sectionAddDefault ()
// {{{
{
return $this->sectionAdd (
array ("_headerBodySeparator"=>$this->headerBodySeparator,
"_headerCR"=>"\r\n",
"_headersEML"=>"",
"_headersArray"=>array (),
"_contentEML"=>"",
"_contentUTF"=>""));
}
// }}}
/** Del an existing section
* If there is one child, and the section was multiple, remove it and
* associate the child to a new section
* @param string $sectionID The section to delete
*/
private function sectionDel ($sectionID)
{
// TODO !
}
/** Add a newChild to an existing section at the end of the list
* @param string $sectionIDParent The parent modified by adding a child
* @param string $sectionIDchild The sectionID of the child to add
*/
private function sectionAddChild ($sectionIDParent, $sectionIDchild)
// {{{
{
if (! array_key_exists ($sectionIDParent, $this->sections))
throw new \Exception (dgettext ("domframework",
"Section parent not found"), 404);
$this->sections[$sectionIDParent]["_partsIDchild"][] = $sectionIDchild;
$this->sections[$sectionIDchild]["_parentID"] = $sectionIDParent;
}
// }}}
/** Add a newChild to an existing section at the beginning of the list
* @param string $sectionIDParent The parent modified by adding a child
* @param string $sectionIDchild The sectionID of the child to add
*/
private function sectionAddChildFirst ($sectionIDParent, $sectionIDchild)
// {{{
{
if (! array_key_exists ($sectionIDParent, $this->sections))
throw new \Exception (dgettext ("domframework",
"Section parent not found"), 404);
array_unshift ($this->sections[$sectionIDParent]["_partsIDchild"],
$sectionIDchild);
$this->sections[$sectionIDchild]["_parentID"] = $sectionIDParent;
}
// }}}
/** Remove all the defined Childs in the section. Do not remove really the
* childs !
* @param string $sectionID the section to clean
*/
private function sectionDelChilds ($sectionID)
// {{{
{
if (! array_key_exists ($sectionID, $this->sections))
throw new \Exception (dgettext ("domframework",
"Section not found"), 404);
unset ($this->sections[$sectionID]["_partsIDchild"]);
}
// }}}
/** Update the content of an existing section
* @param string $sectionID The section to modify
* @param array $param The parameters to update
*/
private function sectionUpdate ($sectionID, $param)
// {{{
{
if (! array_key_exists ($sectionID, $this->sections))
throw new \Exception (dgettext ("domframework", "Section not found"),
404);
if (! is_array ($param))
throw new \Exception (dgettext ("domframework",
"Param provided to sectionUpdate is not array"), 406);
foreach ($param as $key=>$val)
{
$this->sections[$sectionID][$key] = $val;
}
}
// }}}
/** Get the list of sections ID
* @return array the defined sectionsID
*/
private function sectionList ()
// {{{
{
return array_keys ($this->sections);
}
// }}}
/** Get the section ID List with parents ID
* @return array the defined sectionsID with the parent ID as value
*/
private function sectionListParent ()
// {{{
{
$res = array ();
foreach ($this->sections as $sectionID=>$content)
{
if (! array_key_exists ("_parentID", $content))
$res[$sectionID] = "";
else
$res[$sectionID] = $content["_parentID"];
}
return $res;
}
// }}}
/** Return the content array of the section
* @param string $sectionID The section ID to get
* @return array The content of the section
*/
private function sectionGet ($sectionID)
// {{{
{
if (! array_key_exists ($sectionID, $this->sections))
throw new \Exception (dgettext ("domframework",
"Section not found"), 404);
return $this->sections[$sectionID];
}
// }}}
/** Get the section ID of the main part
* @return bool|string the section ID of the main part or FALSE if not found
*/
public function sectionMainID ()
// {{{
{
foreach ($this->sectionListParent () as $sectionID=>$parentID)
{
if ($parentID === "")
return $sectionID;
}
return false;
}
// }}}
/** Refresh the _headersEML from the _headersArray
* @param string $sectionID the section to refresh
*/
private function sectionRefreshHeadersEML ($sectionID)
// {{{
{
$section = $this->sectionGet ($sectionID);
$headersEML = "";
foreach ($section["_headersArray"] as $val)
{
$head = key ($val);
$value = $val[$head];
$headersEML .= "$head: $value";
}
$this->sections[$sectionID]["_headersEML"] = $headersEML;
}
// }}}
/** Read the complete mail to analyze
* Destroy all the previous definitions of mail
* @param string $content The complete mail to read
*/
public function readMail ($content)
// {{{
{
$this->sections = array ();
$this->completeEmailEML = $content;
$partinfo = $this->parseMessagePart ($content);
if ($partinfo !== null)
$this->readMailContentRecurse ($partinfo);
}
// }}}
/** Read the content of the mail and allow the content to be also multipart.
* Then the method is recursively called to generate the sections
* @param array $partinfo The partinfo from parseMessagePart to analyze
* @param string|null $sectionIDParent The parent sectionID to link with
*/
private function readMailContentRecurse ($partinfo, $sectionIDParent=false)
// {{{
{
if (key_exists ("_contentType", $partinfo) &&
substr ($partinfo["_contentType"], 0, 10) === "multipart/")
{
// multipart/alternative, multipart/related, multipart/mixed
// Remove the content, as it is not valuable (will be stored in childs)
$tmp = $partinfo;
unset ($tmp["_contentEML"]);
unset ($tmp["_contentUTF"]);
if ($sectionIDParent === false)
$sectionIDParent = $this->sectionAdd ($tmp);
$boundaryArray = preg_split ("#([\r\n]+)#", $partinfo["_contentEML"],
null, PREG_SPLIT_DELIM_CAPTURE);
// Remove the first 2 dashes : the boundary is stored like in the headers
$boundary = "";
while (count ($boundaryArray) > 0 && substr ($boundary, 0, 2) !== "--")
{
// Skip the lines until the coundary is found. The boundary start by --
$boundary = array_shift ($boundaryArray);
$boundaryCR = array_shift ($boundaryArray);
}
if ($boundary === false)
throw new \Exception (dgettext ("domframework",
"Can't find boundary in multipart/"), 406);
$boundary = substr ($boundary, 2);
unset ($boundaryArray);
$this->sectionUpdate ($sectionIDParent,
array ("_boundary"=>$boundary,
"_boundaryCR"=>$boundaryCR));
$parts = preg_split ("#(--".preg_quote ($boundary)."(--)?)([\r\n]+)#",
$partinfo["_contentEML"],
null, PREG_SPLIT_DELIM_CAPTURE);
for ($i = 0 ; $i < count ($parts) ; $i = $i + 4)
{
if ($parts[$i] === "")
continue;
$messagePart = $this->parseMessagePart ($parts[$i]);
if ($messagePart === null ||
! array_key_exists ("_contentType", $messagePart))
continue;
$messagePart["_parentID"] = $sectionIDParent;
$sectionIDChild = $this->sectionAdd ($messagePart);
$this->sectionAddChild ($sectionIDParent, $sectionIDChild);
if (substr ($messagePart["_contentType"], 0, 10) === "multipart/")
{
// Recursive part : multipart in multipart
unset ($this->sections[$sectionIDChild]["_contentEML"]);
unset ($this->sections[$sectionIDChild]["_contentUTF"]);
$this->readMailContentRecurse ($messagePart, $sectionIDChild);
}
}
}
else
{
$sectionIDParent = $this->sectionAdd ($partinfo);
}
}
// }}}
/** Return the data for a part of the mail
* @param string $content The content of the mail to parse
* @return array The data content in the mail
*/
private function parseMessagePart ($content)
// {{{
{
// Get the HeaderBodySeparator
$pos = strpos ($content, "\r\n\r\n");
$headerBodySeparator = "\r\n";
if ($pos === false)
{
$pos = strpos ($content, "\n\n");
$headerBodySeparator = "\n";
}
if ($pos === false)
{
$pos = strpos ($content, "\r\r");
$headerBodySeparator = "\r";
}
if ($pos === false)
//throw new \Exception ("Can't find the header/body separator", 500);
return array ("_contentEML"=>$content);
// Get the headers
$headersEML = ltrim (substr ($content, 0, $pos+
strlen ($headerBodySeparator)));
$headersArray = array ();
if ($headersEML !== "" && $headersEML !== "--")
{
$prev = "";
$prevCR = "";
$headersSplit = preg_split ("#([\r\n]+)#", $headersEML, null,
PREG_SPLIT_DELIM_CAPTURE);
for ($i = 0 ; $i < count ($headersSplit); $i = $i + 2)
{
$h = $headersSplit[$i];
if (! isset ($headersSplit[$i+1]))
$headersSplit[$i+1] = $headerBodySeparator;
if ($h === "" || $h === "--")
continue;
if ($h{0} === " " || $h{0} === "\t")
{
$prev .= $prevCR.$h;
$prevCR = $headersSplit[$i+1];
}
elseif ($prev !== "")
{
$exp = explode (": ", $prev, 2);
if (! array_key_exists (1, $exp))
{
//trigger_error ("Malformed mail provided: no section header",
// E_USER_NOTICE);
$prev = "";
continue;
}
list ($head, $value) = $exp;
$value .= $prevCR;
$headersArray[][$head] = $value;
$prev = $h;
$prevCR = $headersSplit[$i+1];
}
else
{
$prev = $h;
$prevCR = $headersSplit[$i+1];
}
}
if ($prev !== "")
{
$exp = explode (": ", $prev, 2);
if (array_key_exists (1, $exp))
{
list ($head, $value) = $exp;
$value .= $prevCR;
$headersArray[][$head] = $value;
}
}
}
$contentType = $this->getHeaderValue ("Content-Type", $headersArray);
if ($contentType === false)
{
// If the mail doesn't provide a Content-Type header, it is then a raw
// text mail. Force the Content-Type to continue;
$contentType = "text/plain; charset=utf-8; format=flowed";
}
$contentTypeArray = $this->contentTypeAnalyze ($contentType);
if (! isset ($contentTypeArray["Content-Type"]))
{
//throw new \Exception ("Can't parse the Content-Type", 500);
return;
}
$contentTransferEncoding =
$this->getHeaderValue ("Content-Transfer-Encoding",
$headersArray);
$charset = (isset ($contentTypeArray["charset"])) ?
$contentTypeArray["charset"] : false;
// Get the body
$pos = strpos ($content, $headerBodySeparator.$headerBodySeparator);
$contentEML = substr ($content, $pos + strlen ($headerBodySeparator) * 2);
if ($contentTransferEncoding === "quoted-printable")
$contentUTF = $this->encodingDecode ($contentEML,
$contentTransferEncoding);
elseif ($contentTransferEncoding === "base64")
$contentUTF = $this->encodingDecode ($contentEML,
$contentTransferEncoding);
else
$contentUTF = $contentEML;
if ($charset !== false)
$contentUTF = iconv ($charset, "utf-8", $contentUTF);
$res = array ("_headerBodySeparator"=>$headerBodySeparator,
"_headerCR"=>$prevCR,
"_headersEML"=>$headersEML,
"_headersArray"=>$headersArray,
"Content-Type"=>$contentType,
"_contentType"=>$contentTypeArray["Content-Type"],
"Content-Transfer-Encoding"=>$contentTransferEncoding,
"_contentEML"=>$contentEML,
"_contentUTF"=>$contentUTF);
preg_match ("#; name=['\"](.+)['\"]#", $contentType, $matches);
if (array_key_exists (1, $matches))
$res["_name"] = $matches[1];
if ($charset !== false)
$res["_charset"] = $charset;
$contentID = $this->getHeaderValue ("Content-ID", $headersArray);
if ($contentID !== false)
$res["Content-ID"] = $contentID;
return $res;
}
// }}}
/** The constuctor verify if the external libraries are available
*/
public function __construct ()
// {{{
{
if (! function_exists ("finfo_buffer"))
throw new \Exception (dgettext ("domframework",
"Missing FileInfo PHP Extension"), 500);
if (! function_exists ("openssl_random_pseudo_bytes"))
throw new \Exception (dgettext ("domframework",
"Missing OpenSSL PHP Extension"), 500);
// Define default headers
$this->addHeader ("Date", date ("r"));
$this->addHeader ("Message-ID", $this->provideMessageID ());
$user = posix_getpwuid (posix_geteuid());
$this->addHeader ("From", $user["name"]."@".php_uname('n'));
$this->addHeader ("MIME-Version", "1.0");
}
// }}}
/** Define a HTML body. If the HTML body already exists, overwrite it
* If there is an text body, manage the boundary in alternative mode
* @param string $htmlContent in UTF-8
* @param string $charset to be stored in the mail
* @param string $encoding the encoding in the mail
*/
public function setBodyHTML ($htmlContent, $charset="utf-8",
$encoding="quoted-printable")
// {{{
{
// Look if there is an existing section with text (main or
// multipart/alternative)
$sectionList = $this->sectionList ();
$sectionIDtoChange = "";
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (array_key_exists ("_contentType", $section) &&
$section["_contentType"] === "text/html")
{
$sectionIDtoChange = $sectionID;
break;
}
}
if ($sectionIDtoChange === "")
{
// No existing HTML section found : need to create one
// Check if the main section is empty to use it
$sectionMainID = $this->sectionMainID ();
if (!array_key_exists ("_contentType",
$this->sectionGet ($sectionMainID)))
{
$sectionIDtoChange = $sectionMainID;
}
}
if ($sectionIDtoChange === "")
{
// No existing HTML section found : need to create one
// Check if there is a multipart/related section without HTML to use
$sectionList = $this->sectionList ();
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (! array_key_exists ("_contentType", $section) ||
array_key_exists ("_contentType", $section) &&
$section["_contentType"] !== "multipart/related")
continue;
// Found ! $sectionID is a multipart/related section without HTML
$sectionIDtoChange = $this->sectionAddDefault ();
$this->sectionAddChildFirst ($sectionID, $sectionIDtoChange);
}
}
if ($sectionIDtoChange === "")
{
// No existing HTML section found : need to create one
// Check if there is an text part alone (to be pushed in
// multipart/alternative)
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (! array_key_exists ("_contentType", $section) ||
$section["_contentType"] !== "text/plain")
continue;
// Found ! $sectionID is text/plain section
// Create a multipart/alternative section
// Move the text section in the multipart/alternative section
$boundary = $this->getBoundary ();
$multiID = $this->sectionAddDefault();
$this->moveChilds ($multiID);
$this->addHeader ("Content-Type", "multipart/alternative;\r\n".
" boundary=$boundary\r\n",
$multiID);
$this->sectionUpdate ($multiID,
array ("_contentType"=>"multipart/alternative",
"_boundary"=>$boundary,
"_boundaryCR"=>"\r\n",
));
// Add a HTML section in the multipart/alternative section after the
// defined text section.
$sectionIDtoChange = $this->sectionAddDefault ();
$this->sectionAddChild ($multiID, $sectionIDtoChange);
}
}
if ($sectionIDtoChange === "")
{
// No existing section found : need to create one
throw new \Exception (dgettext ("domframework",
"Can't find the place to store the HTML"), 500);
}
$htmlContent = iconv ("utf-8", $charset, $htmlContent);
$part["_charset"] = $charset;
$part["_contentType"] = "text/html";
$this->setHeader ("Content-Transfer-Encoding", $encoding,
$sectionIDtoChange);
// TODO : Add $part["_contentUTF"] ?
$part["_contentEML"] = $this->encodingEncode ($htmlContent, $encoding)."\n";
$this->setHeader ("Content-Type", "text/html; charset=$charset",
$sectionIDtoChange);
if (isset ($boundary)) $part["_boundary"] = $boundary;
if (isset ($boundaryCR)) $part["_boundaryCR"] = $boundaryCR;
$this->sectionUpdate ($sectionIDtoChange, $part);
$this->createMailEML ();
}
// }}}
/** Add a Text body. If the text body already exists, overwrite it
* If there is an HTML body, manage the boundary in alternative mode
* @param string $textContent in UTF-8
* @param string $charset to be stored in the mail
* @param string $encoding the encoding in the mail
*/
public function setBodyText ($textContent, $charset="utf-8",
$encoding="quoted-printable")
// {{{
{
// Look if there is an existing section with text (main or
// multipart/alternative)
$sectionList = $this->sectionList ();
$sectionIDtoChange = "";
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (array_key_exists ("_contentType", $section) &&
$section["_contentType"] === "text/plain")
{
$sectionIDtoChange = $sectionID;
break;
}
}
if ($sectionIDtoChange === "")
{
// No existing section found : need to create one
// Check if the main section is empty to use it
$sectionMainID = $this->sectionMainID ();
if (!array_key_exists ("_contentType",
$this->sectionGet ($sectionMainID)))
{
$sectionIDtoChange = $sectionMainID;
}
}
if ($sectionIDtoChange === "")
{
// No existing section found : need to create one
// Check if there is an html part alone (to be pushed in
// multipart/alternative)
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (! array_key_exists ("_contentType", $section) ||
$section["_contentType"] !== "text/html")
continue;
$sectionIDhtml = $sectionID;
// Found ! $sectionID is text/html section
// Create a multipart/alternative section
// Move the text section in the multipart/alternative section
$boundary = $this->getBoundary ();
$multiID = $this->sectionAddDefault();
$this->moveChilds ($multiID);
$this->addHeader ("Content-Type", "multipart/alternative;\r\n".
" boundary=$boundary\r\n",
$multiID);
$this->sectionUpdate ($multiID,
array ("_contentType"=>"multipart/alternative",
"_boundary"=>$boundary,
"_boundaryCR"=>"\r\n",
));
// Add a HTML section in the multipart/alternative section
$sectionIDtoChange = $this->sectionAddDefault ();
$this->sectionAddChildFirst ($multiID, $sectionIDtoChange);
}
}
if ($sectionIDtoChange === "")
{
// No existing section found : need to create one
throw new \Exception (dgettext ("domframework",
"Can't find the place to store the TEXT"), 500);
}
$textContent = iconv ("utf-8", $charset, $textContent);
$part["_charset"] = $charset;
$part["_contentType"] = "text/plain";
$this->setHeader ("Content-Transfer-Encoding", $encoding,
$sectionIDtoChange);
$part["_contentEML"] = $this->encodingEncode ($textContent, $encoding)."\n";
$part["_contentUTF"] = $textContent;
$this->setHeader ("Content-Type", "text/plain; charset=$charset",
$sectionIDtoChange);
if (isset ($boundary)) $part["_boundary"] = $boundary;
if (isset ($boundaryCR)) $part["_boundaryCR"] = $boundaryCR;
$this->sectionUpdate ($sectionIDtoChange, $part);
$this->createMailEML ();
}
// }}}
/** Return the HTML body if exists in UTF-8. If the body is not in UTF-8, it
* is converted
* Return false if it doesn't exists
* @return string|false The HTML body converted in UTF-8 or false if there is
* no HTML part in the mail
*/
public function getBodyHTML ()
// {{{
{
$sectionList = $this->sectionList ();
$sectionIDtoChange = "";
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (array_key_exists ("_contentType", $section) &&
$section["_contentType"] === "text/html")
{
$encoding = $this->getHeaderValue ("Content-Transfer-Encoding",
$section["_headersArray"]);
$body = $this->encodingDecode ($section["_contentEML"],
$encoding);
return iconv ($section["_charset"], "UTF-8", $body);
}
}
return false;
}
// }}}
/** Get the text body if exists in UTF-8. If the body is not in UTF-8, it is
* converted
* Return false if it doesn't exists
* @return string|false The Text body converted in UTF-8 or false if there is
* no Text part in the mail
*/
public function getBodyText ()
// {{{
{
$sectionList = $this->sectionList ();
$sectionIDtoChange = "";
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (array_key_exists ("_contentType", $section) &&
$section["_contentType"] === "text/plain")
{
$encoding = $this->getHeaderValue ("Content-Transfer-Encoding",
$section["_headersArray"]);
$body = $this->encodingDecode ($section["_contentEML"],
$encoding);
return iconv ($section["_charset"], "UTF-8", $body);
}
}
return false;
}
// }}}
/** Move the sections where the parent is defined to $oldParentID to the
* $newParentID.
* Move the headers from the valid sections to the newParent, except the
* Content-XX lines
* @param string $newParentID The Parent which will be modified
* @param string|null $oldParentID The oldParent to look for
*/
private function moveChilds ($newParentID, $oldParentID=false)
// {{{
{
if ($newParentID === $oldParentID)
throw new \Exception ("moveChilds : old and new ParentID are the same",
406);
foreach ($this->sections as $sectionID=>$section)
{
if ($newParentID === $sectionID)
continue;
if ((! isset ($section["_parentID"]) && $oldParentID === false) ||
$oldParentID === $section["_parentID"])
{
$this->sections[$sectionID]["_parentID"] = $newParentID;
$this->sections[$newParentID]["_partsIDchild"][] = $sectionID;
$multipartHeaders = array ();
$headersEML = "";
foreach ($section["_headersArray"] as $key=>$val)
{
$head = key ($val);
$value = $val[$head];
if (substr ($head, 0, 8) !== "Content-")
{
$this->addHeader ($head, $value, $newParentID);
try
{
// Removing the Received: headers entries can only be done one
// time. An exception is raised, but it is not important
$this->delHeader ($head, $sectionID);
}
catch (Exception $e)
{
}
}
}
}
else
{
// Section not attached to the wanted parent : skip it
}
}
// Remove empty sections
foreach ($this->sections as $sectionID=>$section)
{
if (count ($section["_headersArray"]) === 0 &&
rtrim ($section["_headersEML"]) === "" &&
rtrim ($section["_contentEML"]) === "")
{
if (array_key_exists ("_parentID", $section))
{
foreach ($this->sections[$section["_parentID"]]["_partsIDchild"] as
$key=>$val)
if ($val === $sectionID)
unset ($this->sections[$section["_parentID"]]["_partsIDchild"][$key]
);
}
unset ($this->sections[$sectionID]);
}
}
}
// }}}
/** Add an attachment to the mail.
* The allowed encodings are "quoted-printable" or "base64"
* @param string $name The name of the file
* @param string $fileContent The content of the file in binary
* @param string|null $encoding The output encoding. Can be
* base64/quoted-printable
* @param boolean|null $inline Store the file in inline mode
* (multipart/related)
* If false, store the file in attached mode (multipart/mixed)
*/
public function addAttachment ($name, $fileContent, $encoding="base64",
$inline=false)
// {{{
{
if ($this->getBodyHTML() === false && $inline !== false)
$this->setBodyHTML ("No HTML provided by inline file added");
// Look if there is a multipart/mixed where adding the new section. If not,
// create it
$multipart = ($inline === false) ? "multipart/mixed" : "multipart/related";
$sectionList = $this->sectionList ();
$sectionIDMixed = "";
foreach ($sectionList as $sectionID)
{
$section = $this->sectionGet ($sectionID);
if (!array_key_exists ("_contentType", $section) ||
array_key_exists ("_contentType", $section) &&
$section["_contentType"] !== $multipart)
continue;
// Found a multipart/mixed. Will add the new attachment section to it
$sectionIDMixed = $sectionID;
}
if ($sectionIDMixed === "")
{
// No multipart/mixed. Need to create one
$headersEML = "";
$sectionIDMixed = $this->sectionAddDefault ();
// Move all the existing sections in the new multipart/mixed
$this->moveChilds ($sectionIDMixed);
$boundary = $this->getBoundary ();
$this->addHeader ("Content-Type", "$multipart;\r\n".
" boundary=$boundary\r\n",
$sectionIDMixed);
$this->sectionUpdate ($sectionIDMixed,
array ("_contentType"=>$multipart,
"_boundary"=>$boundary,
"_boundaryCR"=>"\r\n",
));
}
if ($sectionIDMixed === "")
{
throw new \Exception (
"Can't find the multipart/mixed section to add an attachment");
}
// Add the new section to the mixed section
$sectionID = $this->sectionAddDefault ();
$this->sectionAddChild ($sectionIDMixed, $sectionID);
$finfo = new \finfo(FILEINFO_MIME);
$mimetype = $finfo->buffer($fileContent);
$this->addHeader ("Content-Type", "$mimetype; name=\"".
str_replace ("\"", "=22",
$this->encodeHeaderStringWithPosition ($name,
"quoted-printable",
strlen ("Content-Type: $mimetype; name=")))."\"\r\n",
$sectionID);
if ($inline === false)
$this->addHeader ("Content-Disposition", "attachment; filename=\"".
str_replace ("\"", "=22",
$this->encodeHeaderStringWithPosition ($name,
"quoted-printable",
strlen ("Content-Disposition: attachment; ".
"filename="))).
"\"\r\n",
$sectionID);
else
{
$this->addHeader ("Content-Disposition", "inline; filename=".
str_replace ("\"", "=22",
$this->encodeHeaderStringWithPosition ($name,
"quoted-printable",
strlen ("Content-Disposition: inline; filename=")
))."\r\n",
$sectionID);
$contentID = $this->provideMessageID ();
$this->addHeader ("Content-ID", "$contentID\r\n", $sectionID);
}
$this->addHeader ("Content-Transfer-Encoding", "$encoding\r\n", $sectionID);
$part["_name"] = $name;
$part["_contentEML"] = $this->encodingEncode ($fileContent, $encoding);
$part["_mimetype"] = $mimetype;
$part["_sizeReal"] = strlen ($fileContent);
$this->sectionUpdate ($sectionID, $part);
$this->createMailEML ();
if ($inline === true)
return substr ($contentID, 1, -1);
}
// }}}
/** Add an inline attachment to the mail.
* The allowed encodings are "quoted-printable" or "base64"
* Return the Content-ID needed to be used in HTML page like :
* <img src='cid:XXXXXXXXXX'/>
* @param string $name The name of the file
* @param string $fileContent The content of the file in binary
* @param string|null $encoding The output encoding. Can be
* base64/quoted-printable
* @return string The content ID created
*/
public function addAttachmentInline ($name, $fileContent, $encoding="base64")
// {{{
{
return $this->addAttachment ($name, $fileContent, $encoding, true);
}
// }}}
/** Get an attachment of the mail
* @param integer $number the number of attach to get starting to 0
* @param boolean|null $inline Return only the attachments Inline if true
* @return the content of the attachment. Can be binary
*/
public function getAttachment ($number, $inline = false)
// {{{
{
$attachmentIDs = $this->getAttachmentID ($inline);
if (! array_key_exists ($number, $attachmentIDs))
throw new \Exception (sprintf (dgettext ("domframework",
"Attachment '%d' not found"), $number), 404);
$part = $this->sectionGet ($attachmentIDs[$number]);
$encoding = $this->getHeaderValue ("Content-Transfer-Encoding",
$part["_headersArray"]);
return $this->encodingDecode ($part["_contentEML"], $encoding);
}
// }}}
/** Get the attachment details
* @param integer $number the number of attach to get starting to 0
* @param boolean|null $inline Return only the attachments Inline if true
* @return array containing the information of the attachment
*/
public function getAttachmentDetails ($number, $inline = false)
// {{{
{
$attachmentIDs = $this->getAttachmentID ($inline);
if (! array_key_exists ($number, $attachmentIDs))
throw new \Exception (sprintf (dgettext ("domframework",
"Attachment '%d' not found"), $number), 404);
$part = $this->sectionGet ($attachmentIDs[$number]);
foreach ($part as $key=>$val)
{
if ($key{0} === "_")
{
if ($key !== "_contentEML" && $key !== "_contentUTF" &&
$key !== "_parentID" && $key !== "_headersArray" &&
$key !== "_headersEML" && $key !== "_headerCR" &&
$key !== "_headerBodySeparator")
$res[substr ($key, 1)] = $val;
}
else
$res[$key] = $val;
}
return $res;
}
// }}}
/** Return the list of the sectionID containing a attachment. Contains the
* inline attachments too.
* @param boolean $inline Return only the sections Inline if true
* @return array The sectionIDs
*/
private function getAttachmentID ($inline = false)
// {{{
{
$res = array ();
foreach ($this->sections as $sectionID=>$section)
{
if (! key_exists ("_headersArray", $section))
continue;
$disposition = $this->getHeaderValue ("Content-Disposition",
$section["_headersArray"]);
if ($disposition !== false)
{
if ($inline === true && substr ($disposition, 0, 6) === "inline")
$res[] = $sectionID;
elseif ($inline === false &&
substr ($disposition, 0, 10) === "attachment")
$res[] = $sectionID;
continue;
}
// The Mailer-Daemons use Content-Type: message/XXXX
$contentType = $this->getHeaderValue ("Content-Type",
$section["_headersArray"]);
if ($contentType !== false && substr ($contentType, 0, 8) === "message/")
{
if ($inline === false)
$res[] = $sectionID;
}
}
return $res;
}
// }}}
/** Add a To: header. If it already exists, add a new recipient
* @param string $toMail The mail to add
* @param string|null $toName The name of the recipient
*/
public function addTo ($toMail, $toName = "")
// {{{
{
if (strspn ($toName, "abcdefghijklmnopqrstuvwxyz".
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".
"0123456789 -_") !== strlen ($toName))
$toName = $this->encodeHeaders ("To", $toName,
"quoted-printable");
if ($toName !== "")
$toName .= " ";
$toField = "$toName<$toMail>";
$oldTo = $this->getTo ();
if ($oldTo !== false)
$this->setHeader ("To", rtrim ($oldTo).",\r\n $toField");
else
$this->setHeader ("To", $toField);
}
// }}}
/** Get the To Header as it is written in the mail
* @return string The To Header defined in the mail
*/
public function getTo ()
// {{{
{
return $this->getHeader ("To");
}
// }}}
/** Add a From: header. If it already exists, overwrite the existing one
* @param string $fromMail The from Mail to define
* @param string|null $fromName The from Name to define
*/
public function setFrom ($fromMail, $fromName= "")
// {{{
{
if (strspn ($fromName, "abcdefghijklmnopqrstuvwxyz".
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".
"0123456789 -_") !== strlen ($fromName))
$fromName = $this->encodeHeaders ("From", $fromName,
"quoted-printable");
if ($fromName !== "")
$fromName .= " ";
$this->setHeader ("From", "$fromName<$fromMail>");
}
// }}}
/** Return the From header as it is written in the mail
* @return string The From Header defined in the mail
*/
public function getFrom ()
// {{{
{
return $this->getHeader ("From");
}
// }}}
/** Return the From header converted to array with mail and name keys
* @return array The From details
*/
public function getFromArray ()
// {{{
{
$from = $this->getHeader ("From");
$res = array ();
$from = $this->decodeHeaders ("From", $from);
$from = $this->convertPeopleToArray ($from);
return reset ($from);
}
// }}}
/** Set the subject
* @param string $subject In UTF8
*/
public function setSubject ($subject)
// {{{
{
$this->setHeader ("Subject",
$this->encodeHeaders ("Subject", $subject,
"quoted-printable"));
}
// }}}
/** Set the Date
* @param string $date In RFC 2822 format
*/
public function setDate ($date)
// {{{
{
// TODO : Check if the date format is valid
$this->setHeader ("Date", $date);
}
// }}}
/** Set the Date
* @param string $timestamp In Timestamp format
*/
public function setDateTimestamp ($timestamp)
// {{{
{
// TODO : Check if the timestamp is valid
$this->setHeader ("Date", date ("r", $timestamp));
}
// }}}
/** Get the Date header if defined.
* Return false if not defined
* @return string|bool The date Header if defined or false if not defined
*/
public function getDate ()
// {{{
{
return $this->getHeader ("Date");
}
// }}}
/** Return the Date header (if defined) in timestamp
* Return false if not defined
* @return integer|bool The date Header if defined or false if not defined
*/
public function getDateTimestamp ()
// {{{
{
$datetimestamp = false;
$date = rtrim ($this->getDate ());
if ($date !== false)
{
$dateTimestamp = \DateTime::createFromFormat (\DateTime::RFC2822,
$date);
if ($dateTimestamp === false)
$dateTimestamp = \DateTime::createFromFormat (\DateTime::RFC822,
$date);
if ($dateTimestamp !== false)
$dateTimestamp = $dateTimestamp->getTimestamp();
}
return $dateTimestamp;
}
// }}}
/** Set a generic header
* @param string $header The name of the Header (without colon)
* @param string $value The value the store. The format must be correct !
* @param string|null $sectionID The section to modify. If null, use the main
*/
public function setHeader ($header, $value, $sectionID=null)
// {{{
{
if (substr ($value, -1) !== "\n" &&
substr ($value, -1) !== "\r" &&
substr ($value, -2) !== "\r\n")
$value .= "\r\n";
if ($sectionID === null)
{
$sectionMainID = $this->sectionMainID ();
if ($sectionMainID === false)
$sectionMainID = $this->sectionAddDefault ();
}
else
{
if (! array_key_exists ($sectionID, $this->sections))
throw new \Exception ("Wanted section not found in setHeader", 404);
$sectionMainID = $sectionID;
}
$found = false;
foreach ($this->sections[$sectionMainID]["_headersArray"] as $key=>$val)
{
$head = key ($val);
if ($head === $header)
{
$this->sections[$sectionMainID]["_headersArray"][$key][$header] =
$value;
$found = true;
}
}
if ($found === false)
$this->sections[$sectionMainID]["_headersArray"][][$header] = $value;
// Re-create the _headersEML for the section
$_headerEML = "";
foreach ($this->sections[$sectionMainID]["_headersArray"] as $val)
{
$head = key ($val);
$value = $val[$head];
$_headerEML .= "$head: $value";
}
$this->sections[$sectionMainID]["_headersEML"] = $_headerEML;
$this->createMailEML ();
}
// }}}
/** Add a generic header
* @param string $header The name of the Header (without colon)
* @param string $value The value the store. The format must be correct !
* @param string|null $sectionID The section to modify. If null, use the main
*/
public function addHeader ($header, $value, $sectionID=null)
// {{{
{
if (substr ($value, -1) !== "\n" &&
substr ($value, -1) !== "\r" &&
substr ($value, -2) !== "\r\n")
$value .= "\r\n";
if ($sectionID === null)
{
$sectionMainID = $this->sectionMainID ();
if ($sectionMainID === false)
$sectionMainID = $this->sectionAddDefault ();
}
else
{
if (! array_key_exists ($sectionID, $this->sections))
throw new \Exception ("Wanted section not found in addHeader", 404);
$sectionMainID = $sectionID;
}
if ($header === "Return-Path")
{
// Must be placed on the first line and must be unique
reset ($this->sections[$sectionMainID]["_headersArray"]);
if (key_exists (0, $this->sections[$sectionMainID]["_headersArray"]) &&
key_exists ("Return-Path",
$this->sections[$sectionMainID]["_headersArray"][0]))
{
// Remove the old one
array_shift ($this->sections[$sectionMainID]["_headersArray"]);
$this->sections[$sectionMainID]["_headersEML"] =
substr ($this->sections[$sectionMainID]["_headersEML"],
strpos ($this->sections[$sectionMainID]["_headersEML"], "\n") +1);
}
array_unshift ($this->sections[$sectionMainID]["_headersArray"],
array ($header => $value));
$this->sections[$sectionMainID]["_headersEML"] = "$header: $value".
$this->sections[$sectionMainID]["_headersEML"];
}
else
{
// The "normal" headers are places sequentially at the end of the headers
$this->sections[$sectionMainID]["_headersArray"][][$header] = $value;
// TODO : Encode ? Strip ?
$this->sections[$sectionMainID]["_headersEML"] .= "$header: $value";
}
$this->createMailEML ();
}
// }}}
/** Delete a specific header
* @param string $header The header to remove
* @param string|null $sectionID The section to modify. If null, use the main
*/
public function delHeader ($header, $sectionID=null)
// {{{
{
if ($sectionID === null)
{
$sectionMainID = $this->sectionMainID ();
if ($sectionMainID === false)
$sectionMainID = $this->sectionAddDefault ();
}
else
{
if (! array_key_exists ($sectionID, $this->sections))
throw new \Exception ("Wanted section not found in delHeader", 404);
$sectionMainID = $sectionID;
}
$found = false;
foreach ($this->sections[$sectionMainID]["_headersArray"] as $key=>$val)
{
$head = key ($val);
if ($head === $header)
{
unset ($this->sections[$sectionMainID]["_headersArray"][$key]);
$found = true;
}
}
if ($found === false)
throw new \Exception (sprintf ("Header to remove '%s' not found",
$header), 404);
// Re-create the _headersEML for the section
$_headerEML = "";
foreach ($this->sections[$sectionMainID]["_headersArray"] as $val)
{
$head = key ($val);
$value = $val[$head];
$_headerEML .= "$head: $value";
}
$this->sections[$sectionMainID]["_headersEML"] = $_headerEML;
$this->createMailEML ();
}
// }}}
/** Get all the headers, in the order in EML. The format is
* [] => array ("header" => "value")
* @param array|null $headers The headers to examine
* @return array
*/
public function getHeaders ($headers=null)
{
$sectionMainID = $this->sectionMainID ();
if ($sectionMainID === false ||
! key_exists ("_headersArray", $this->sections[$sectionMainID]))
$headers = array ();
else
$headers = $this->sections[$sectionMainID]["_headersArray"];
return $headers;
}
/** Get a generic header
* If there is multiple headers with the same name, return the first
* @param string $header The header to get
* @param array|null $headers Optional headers to examine
* @return string|bool the literal value or false if it doesn't exist
*/
public function getHeader ($header, $headers=null)
// {{{
{
$headers = $this->getHeaders ($headers);
foreach ($headers as $key=>$val)
{
$head = key ($val);
$value = $val[$head];
if ($head === $header)
return $value;
}
return false;
}
// }}}
/** Get a generic header with removing the carriage return
* If there is multiple headers with the same name, return the first
* @param string $header The header to get
* @param array $headers The _headersArray array
* @return string|bool the literal value or false if it doesn't exist
*/
public function getHeaderValue ($header, $headers=null)
// {{{
{
$headers = $this->getHeaders ($headers);
foreach ($headers as $key=>$val)
{
$head = key ($val);
$value = $val[$head];
if ($head === $header)
{
$value = preg_replace ("#[\r\n]+[ \t]+#", " ", $value);
if (substr ($value, -2) === "\r\n")
return substr ($value, 0, -2);
return substr ($value, 0, -1);
}
}
return false;
}
// }}}
/** Create the complete mail structure
*/
public function createMailEML ()
// {{{
{
$complete = "";
$this->recurse = 0;
foreach ($this->sectionListParent() as $sectionID=>$sectionParent)
{
if ($sectionParent !== "")
continue;
$part = $this->sectionGet ($sectionID);
$complete .= $part["_headersEML"];
$complete .= $part["_headerBodySeparator"];
if (array_key_exists ("_contentEML", $part))
$complete.= $part["_contentEML"];
$complete .= $this->createMailEMLSub ($part);
}
$this->completeEmailEML = $complete;
}
// }}}
/** Recursive email EML creation for childs
* @param array $parent The parent array
*/
private function createMailEMLSub ($parent)
// {{{
{
$this->recurse++;
if ($this->recurse > 120)
throw new \Exception ("Recurse createMailEMLSub > 120", 500);
if (!array_key_exists ("_partsIDchild", $parent))
return "";
$complete = "";
foreach ($parent["_partsIDchild"] as $childID)
{
$child = $this->sectionGet ($childID);
// The boundary is not defined in the child, but in the parent
if (array_key_exists ("_boundary", $parent))
$complete .= "--".$parent["_boundary"].$parent["_boundaryCR"];
if (array_key_exists ("_headersEML", $child))
$complete .= $child["_headersEML"];
if (array_key_exists ("_headerBodySeparator", $child))
$complete .= $child["_headerBodySeparator"];
$complete .= $this->createMailEMLSub ($child);
if (array_key_exists ("_contentEML", $child))
$complete.= $child["_contentEML"];
}
if (array_key_exists ("_boundary", $parent))
$complete .= "--".$parent["_boundary"]."--".$parent["_boundaryCR"];
return $complete;
}
// }}}
/** Return the complete mail
* @return string The complete mail
*/
public function getMail ()
// {{{
{
if (trim ($this->getBodyHTML()) === "No HTML provided by inline file added")
throw new \Exception ("No HTML provided by inline file added", 500);
return $this->completeEmailEML;
}
// }}}
/** Return an array with the details of the mail :
* the number of attachments, the from, to, subject in UTF-8, if there is
* a text and/or html body
* @return array The details of the mail
*/
public function getDetails ()
// {{{
{
$bodyTextExists = false;
$bodyHTMLExists = false;
$attachmentNb = count ($this->getAttachmentID ());
$attachmentInlineNb = count ($this->getAttachmentID (true));
foreach ($this->sections as $sectionID=>$section)
{
if (array_key_exists ("_contentType", $section) &&
$section["_contentType"] === "text/plain")
$bodyTextExists = true;
if (array_key_exists ("_contentType", $section) &&
$section["_contentType"] === "text/html")
$bodyHTMLExists = true;
}
unset ($sectionID);
unset ($section);
$attachmentDetails = array ();
$attachmentInlineDetails = array ();
for ($i = 0 ; $i < $attachmentNb ; $i ++)
$attachmentDetails[$i] = $this->getAttachmentDetails($i);
for ($i = 0 ; $i < $attachmentInlineNb ; $i ++)
$attachmentInlineDetails[$i] = $this->getAttachmentDetails($i, true);
unset ($i);
$size = strlen ($this->getMail());
$from = trim ($this->getHeader ("From"));
if ($from !== false)
{
$fromArray = $this->convertPeopleToArray (
$this->decodeHeaders ("From", $from));
}
$to = trim ($this->getHeader ("To"));
if ($to !== false)
{
$toArray = $this->convertPeopleToArray (
$this->decodeHeaders ("To", $to));
}
$date = trim ($this->getDate ());
$dateTimestamp = $this->getDateTimestamp ();
$subject = $this->decodeHeaders ("Subject", $this->getHeader ("Subject"));
return get_defined_vars();
}
// }}}
/** Create a boundary
* @return string the textual boundary
*/
private function getBoundary ()
// {{{
{
$data = openssl_random_pseudo_bytes (16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0010
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
return "-----".vsprintf ('%s%s%s%s%s%s%s%s',
str_split (bin2hex ($data), 4));
}
// }}}
/** Create a messageID
* @return string the textual MessageID
*/
public function provideMessageID ()
// {{{
{
$data = openssl_random_pseudo_bytes (16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0010
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
return "<".vsprintf ('%s%s-%s-%s-%s-%s%s%s',
str_split (bin2hex ($data), 4))."@".
php_uname('n').">";
}
// }}}
/** Convert the content to correct encoding.
* The allowed encodings are "quoted-printable" or "base64" or "flowed"
* Cut the long lines to 76 chars with the correct separator
* @param string $content The content to encode
* @param string $encoding The encoding to use
* @return string the content encoded by $encoding
*/
private function encodingEncode ($content, $encoding)
// {{{
{
if ($encoding === "quoted-printable")
{
$tmp = quoted_printable_encode ($content);
return $tmp;
}
elseif ($encoding === "base64")
{
$tmp = base64_encode ($content);
return chunk_split ($tmp);
}
elseif ($encoding === "flowed")
{
return chunk_split ($content);
}
throw new \Exception (sprintf (
dgettext ("domframework",
"Invalid encoding provided to encodingEncode : %s"), $encoding), 500);
}
// }}}
/** Decode the content with correct encoding.
* The allowed encodings are "quoted-printable" or "base64" or "8bit"
* @param string $content The content to decode
* @param string $encoding The encoding to use
* @return the content decoded by $encoding
*/
private function encodingDecode ($content, $encoding)
// {{{
{
if ($encoding === "quoted-printable")
return quoted_printable_decode ($content);
elseif ($encoding === "base64")
return base64_decode ($content);
elseif ($encoding === "8bit" || $encoding === "7bit" || $encoding === false)
return $content;
throw new \Exception (sprintf (
dgettext ("domframework",
"Invalid encoding provided to encodingDecode : '%s'"), $encoding), 500);
}
// }}}
/** Encode a string to be compliant with MIME (used in headers)
* @param string $header The header to be used. Will not be returned, but the
* length of the result will be adapted to it
* @param string $content The content to encode
* @param string $encoding The encoding to use.
* The allowed encodings are "quoted-printable" or "base64"
* @return string the content encoded by $encoding
*/
private function encodeHeaders ($header, $content, $encoding)
// {{{
{
$prefs = array ("input-charset" => "utf-8",
"output-charset" => "utf-8");
if ($encoding === "quoted-printable")
$prefs["scheme"] = "Q";
elseif ($encoding === "base64")
$prefs["scheme"] = "B";
else
throw new \Exception (dgettext ("domframework",
"Invalid encoding provided to encodeHeaders"), 500);
return substr (iconv_mime_encode ($header, $content, $prefs),
strlen ($header)+2);
}
// }}}
/** Convert the header to text
* @param string $header The header to decode
* @param string $content The content of the header to decode
* @return string the header converted
*/
private function decodeHeaders ($header, $content)
// {{{
{
return substr (iconv_mime_decode ("$header: $content", 0, "utf-8"),
strlen ($header)+2);
}
// }}}
/** Encode a header string not starting on first column. The number of chars
* need to be skipped is passed as argument. The function will correctely
* managethe associated carriage returns
* @param string $content The content to encode
* @param string $encoding The The encoding to use.
* The allowed encodings are "quoted-printable" or "base64"
* @param integer $blanks Initial blanks to be added on string
* @return string the content encoded by $encoding
*/
private function encodeHeaderStringWithPosition ($content, $encoding, $blanks)
// {{{
{
$prefs = array ("input-charset" => "utf-8",
"output-charset" => "utf-8");
if ($encoding === "quoted-printable")
$prefs["scheme"] = "Q";
elseif ($encoding === "base64")
$prefs["scheme"] = "B";
else
throw new \Exception (dgettext ("domframework",
"Invalid encoding provided to encodeHeaderStringWithPosition"), 500);
return substr (iconv_mime_encode (str_repeat (" ", $blanks), $content,
$prefs),
$blanks+2);
}
// }}}
/** Convert a From/To string to array. Manage multiple recipients
* Ex. : toto toto <toto@toto.com>, titi <titi@titi.com>
* array (array ("name"=>"toto toto", "mail"=>"toto@toto.com"),
* array ("name"=>"titi", "mail"=>"titi@titi.com"))
* @param string $data The From/To field content
* @return array The array with the converted data
*/
public function convertPeopleToArray ($data)
// {{{
{
$elements = explode (",", $data);
$res = array ();
foreach ($elements as $element)
{
@list ($name, $mail) = explode ("<", $element);
if ($mail === null)
{
$mail = $name;
$name = "";
}
else
{
$name = trim ($name);
$mail = substr (trim ($mail), 0, -1);
}
$array = array ("name"=>$name, "mail"=>$mail);
$res[] = $array;
}
return $res;
}
// }}}
/** Analyze the Content-Type line and return an array with the values
* @param string $contentType The content Type to analyze
* @return array The analyzed Content-Type
*/
public function contentTypeAnalyze ($contentType)
// {{{
{
$contentType = preg_replace ("#[\r\n]+[ \t]+#", " ", $contentType);
$elements = explode (";", $contentType);
$res = array ();
foreach ($elements as $elem)
{
@list ($key, $val) = explode ("=", $elem, 2);
if ($val === null)
$res["Content-Type"] = $key;
else
{
if ($val{0} === "'" || $val{0} === "\"")
$val = substr ($val, 1, -1);
$res[trim ($key)] = $val;
}
}
return $res;
}
// }}}
}