private function setStructure(array $subparts = null, string $parentPartId = null, bool $skipPart = false, bool $lastWasSigned = false)
{
// First call - an object returned by the imap_fetchstructure function is returned
if (null === $subparts) {
$this->structure['obj'] = imap_fetchstructure($this->connection, $this->uid, FT_UID);
if (!$this->structure['obj']) {
throw new Parser\EmailNotExistException('Email does not exist');
}
}
// Sometimes (especially in spams) the type is missing
if (empty($this->structure['obj']->type)) {
$this->structure['obj']->type = TYPETEXT;
}
// For situations when the body is missing but we have attachments
if ($this->structure['obj']->type != TYPETEXT && $this->structure['obj']->type != TYPEMULTIPART) {
$temp = $this->structure['obj'];
// Don't add a body just create the multipart container because the body wouldn't have an Id
$this->structure['obj'] = new \stdClass();
$this->structure['obj']->type = TYPEMULTIPART;
$this->structure['obj']->ifsubtype = 1;
$this->structure['obj']->subtype = 'MIXED';
$this->structure['obj']->ifdescription = 0;
$this->structure['obj']->ifid = '0';
$this->structure['obj']->bytes = isset($temp->bytes) ? $temp->bytes : 0;
$this->structure['obj']->ifdisposition = 1;
$this->structure['obj']->disposition = 'inline';
$this->structure['obj']->ifdparameters = 0;
$this->structure['obj']->dparameters = [];
$this->structure['obj']->ifparameters = 0;
$this->structure['obj']->parameters = [];
$this->structure['obj']->parts = [$temp];
}
// Deals a multipart/alternative or multipart/report problem when they are as the first part
if (null === $subparts && null === $parentPartId) {
$ftype = empty($this->structure['obj']->type) ? $this->getMajorMimeType(0) . '/' . strtolower($this->structure['obj']->subtype) : $this->getMajorMimeType($this->structure['obj']->type) . '/' . strtolower($this->structure['obj']->subtype);
// As first they do not have any actual Id, assign a fake one 0
$this->structure['pid'][0] = '0';
$this->structure['ftype'][0] = $ftype;
$this->structure['encoding'][0] = !empty($this->structure['obj']->encoding) ? self::$encodingTypes[$this->structure['obj']->encoding] : self::$encodingTypes[0];
$this->structure['fsize'][0] = !empty($this->structure['obj']->bytes) ? $this->structure['obj']->bytes : 0;
$this->structure['disposition'][0] = 'inline';
}
// Subparts
if (isset($this->structure['obj']->parts) || is_array($subparts)) {
if (is_array($subparts)) {
$parts = $subparts;
} else {
$parts = $this->structure['obj']->parts;
}
$count = 1;
foreach ($parts as $part) {
// Skips multipart/mixed, following multipart/alternative or multipart/report (if this part is message/rfc822), multipart/related
// There are more problematic parts but we haven't tested them yet
$ftype = empty($part->type) ? $this->getMajorMimeType(0) . '/' . strtolower($part->subtype) : $this->getMajorMimeType($part->type) . '/' . strtolower($part->subtype);
$thisIsSigned = 'multipart/signed' == $ftype;
$skipNext = 'message/rfc822' == $ftype;
$no = isset($this->structure['pid']) ? count($this->structure['pid']) : 0;
// Skip parts fulfilling certain conditions
if ('multipart/mixed' == $ftype && ($lastWasSigned || $skipPart) || 'multipart/signed' == $ftype || $skipPart && 'multipart/alternative' == $ftype || $skipPart && 'multipart/report' == $ftype || 'multipart/related' == $ftype && 1 === count($parts)) {
$skipped = true;
// Although this part is skipped, save is for later use (as Id we use the parent Id)
$this->structure['pid'][$no] = $parentPartId;
$this->structure['ftype'][$no] = $ftype;
$this->structure['encoding'][$no] = !empty($this->structure['obj']->encoding) ? self::$encodingTypes[$this->structure['obj']->encoding] : self::$encodingTypes[0];
$this->structure['fsize'][$no] = !empty($this->structure['obj']->bytes) ? $this->structure['obj']->bytes : 0;
$this->structure['disposition'][$no] = 'inline';
} else {
$skipped = false;
$this->structure['pid'][$no] = !is_array($subparts) ? strval($count) : $parentPartId . '.' . $count;
$this->structure['ftype'][$no] = $ftype;
$this->structure['encoding'][$no] = !empty($part->encoding) ? self::$encodingTypes[$part->encoding] : self::$encodingTypes[0];
$this->structure['fsize'][$no] = !empty($part->bytes) ? $part->bytes : 0;
// Loads parameters
if ($part->ifdparameters) {
foreach ($part->dparameters as $param) {
$this->structure[strtolower($param->attribute)][$no] = strtolower($param->value);
}
}
if ($part->ifparameters) {
foreach ($part->parameters as $param) {
$this->structure[strtolower($param->attribute)][$no] = strtolower($param->value);
}
}
// Builds a part name (can be split into multiple lines)
if ($part->ifparameters) {
foreach ($part->parameters as $param) {
if (0 === stripos($param->attribute, 'name')) {
if (!isset($this->structure['fname'][$no])) {
$this->structure['fname'][$no] = $param->value;
} else {
$this->structure['fname'][$no] .= $param->value;
}
}
}
}
if ($part->ifdparameters && (!isset($this->structure['fname'][$no]) || empty($this->structure['fname'][$no]))) {
foreach ($part->dparameters as $param) {
if (0 === stripos($param->attribute, 'filename')) {
if (!isset($this->structure['fname'][$no])) {
$this->structure['fname'][$no] = $param->value;
} else {
$this->structure['fname'][$no] .= $param->value;
}
}
}
}
// If a name exists, decode it
if (isset($this->structure['fname'][$no])) {
$this->structure['fname'][$no] = $this->decodeFilename($this->structure['fname'][$no]);
}
// If the given part is message/rfc822, load its headers and use the subject as its name
if ('message/rfc822' == $ftype) {
$rfcHeader = $this->getHeaders($this->structure['pid'][$no]);
$this->structure['fname'][$no] = !empty($rfcHeader['subject']) ? $rfcHeader['subject'] . '.eml' : '';
}
// Part Id
if ($part->ifid) {
$this->structure['cid'][$no] = substr($part->id, 1, -1);
}
// Attachment or inline part (sometimes we do not get the required information from the message or it's nonsense)
list($type, $subtype) = explode('/', $ftype);
if ($part->ifdisposition && 'attachment' == strtolower($part->disposition)) {
$this->structure['disposition'][$no] = 'attachment';
} elseif (isset($this->structure['cid'][$no]) && 'image' == $type) {
$this->structure['disposition'][$no] = 'inline';
} elseif ('message' == $type || 'application' == $type || 'image' == $type || 'audio' == $type || 'video' == $type || 'model' == $type || 'other' == $type) {
$this->structure['disposition'][$no] = 'attachment';
} elseif ('text' == $type && ('html' != $subtype && 'plain' != $subtype)) {
$this->structure['disposition'][$no] = 'attachment';
} elseif ('text' == $type && isset($this->structure['fname'][$no])) {
$this->structure['disposition'][$no] = 'attachment';
} else {
$this->structure['disposition'][$no] = 'inline';
}
}
if (isset($part->parts) && is_array($part->parts)) {
if (!$skipped) {
$this->structure['hasAttach'][$no] = true;
}
$this->setStructure($part->parts, end($this->structure['pid']), $skipNext, $thisIsSigned);
} elseif (!$skipped) {
$this->structure['hasAttach'][$no] = false;
}
$count++;
}
} else {
// No subparts
$this->structure['pid'][0] = '1';
$this->structure['ftype'][0] = $this->getMajorMimeType($this->structure['obj']->type) . '/' . strtolower($this->structure['obj']->subtype);
// If the message has only one part it should be text/plain or text/html
if ($this->structure['ftype'][0] != 'text/plain' && $this->structure['ftype'][0] != 'text/html') {
$this->structure['ftype'][0] = 'text/plain';
}
if (empty($this->structure['obj']->encoding)) {
$this->structure['obj']->encoding = 0;
}
$this->structure['encoding'][0] = self::$encodingTypes[$this->structure['obj']->encoding];
if (isset($this->structure['obj']->bytes)) {
$this->structure['fsize'][0] = $this->structure['obj']->bytes;
}
$this->structure['disposition'][0] = 'inline';
$this->structure['hasAttach'][0] = false;
// Walks through next parameters
if (isset($this->structure['obj']->ifparameters) && $this->structure['obj']->ifparameters) {
foreach ($this->structure['obj']->parameters as $param) {
$this->structure[strtolower($param->attribute)][0] = $param->value;
}
}
}
}