From 3b7f99daa1d73277cf45cd3453d51eee420e18d6 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 12:14:21 +0300 Subject: [PATCH 01/22] fix import --- application/Espo/Services/Import.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/Espo/Services/Import.php b/application/Espo/Services/Import.php index ead09c2280..5024def9da 100644 --- a/application/Espo/Services/Import.php +++ b/application/Espo/Services/Import.php @@ -417,6 +417,9 @@ class Import extends \Espo\Services\Record foreach ($importFieldList as $i => $field) { if (!empty($field)) { + if (!array_key_exists($i, $row)) { + continue; + } $value = $row[$i]; if ($field == 'id') { if ($params['action'] == 'create') { From 20569e425253e05c58e6de4719c0137705c8b233 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 12:47:54 +0300 Subject: [PATCH 02/22] fix notices --- application/Espo/ORM/EntityFactory.php | 5 ++++- application/Espo/ORM/Metadata.php | 10 +++++----- application/Espo/ORM/Repositories/RDB.php | 15 ++++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/application/Espo/ORM/EntityFactory.php b/application/Espo/ORM/EntityFactory.php index 25d7c2decc..8dc1612111 100644 --- a/application/Espo/ORM/EntityFactory.php +++ b/application/Espo/ORM/EntityFactory.php @@ -43,10 +43,13 @@ class EntityFactory public function create($name) { $className = $this->entityManager->normalizeEntityName($name); - $defs = $this->metadata->get($name); if (!class_exists($className)) { return null; } + $defs = $this->metadata->get($name); + if (is_null($defs)) { + return null; + } $entity = new $className($defs, $this->entityManager); return $entity; } diff --git a/application/Espo/ORM/Metadata.php b/application/Espo/ORM/Metadata.php index b8aa4d1257..7cd7e8e51c 100644 --- a/application/Espo/ORM/Metadata.php +++ b/application/Espo/ORM/Metadata.php @@ -38,11 +38,11 @@ class Metadata $this->data = $data; } - public function get($entityName) + public function get($entityType) { - return $this->data[$entityName]; + if (!array_key_exists($entityType, $this->data)) { + return null; + } + return $this->data[$entityType]; } - } - - diff --git a/application/Espo/ORM/Repositories/RDB.php b/application/Espo/ORM/Repositories/RDB.php index 818f24a758..ebca5bcaaf 100644 --- a/application/Espo/ORM/Repositories/RDB.php +++ b/application/Espo/ORM/Repositories/RDB.php @@ -104,16 +104,17 @@ class RDB extends \Espo\ORM\Repository protected function getEntityById($id) { + $entity = $this->entityFactory->create($this->entityType); + + if (!$entity) return null; + $params = array(); $this->handleSelectParams($params); - - $entity = $this->entityFactory->create($this->entityType); - if ($entity) { - if ($this->getMapper()->selectById($entity, $id, $params)) { - $entity->setAsFetched(); - return $entity; - } + if ($this->getMapper()->selectById($entity, $id, $params)) { + $entity->setAsFetched(); + return $entity; } + return null; } From 276941197ffbbda6be0c108e2d1b475f4d8636a7 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 15:35:35 +0300 Subject: [PATCH 03/22] email importer refactoring --- application/Espo/Core/Mail/Importer.php | 343 +++----------------- application/Espo/Core/Mail/Parsers/Zend.php | 338 +++++++++++++++++++ 2 files changed, 382 insertions(+), 299 deletions(-) create mode 100644 application/Espo/Core/Mail/Parsers/Zend.php diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index d1da61023a..b83dae6f4c 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -64,16 +64,19 @@ class Importer return $this->filtersMatcher; } - public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false, $folderData = null) + public function importMessage($message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false, $folderData = null, $parserType = 'Zend') { try { + $parserClassName = '\\Espo\\Core\\Mail\\Parsers\\' . $parserType; + $parser = new $parserClassName($this->getEntityManager()); + $email = $this->getEntityManager()->getEntity('Email'); $email->set('isBeingImported', true); $subject = ''; - if (isset($message->subject)) { - $subject = $message->subject; + if ($parser->checkMessageAttribute($message, 'subject')) { + $subject = $parser->getMessageAttribute($message, 'subject'); } if (!empty($subject) && is_string($subject)) { $subject = trim($subject); @@ -98,17 +101,18 @@ class Importer } } - $fromArr = $this->getAddressListFromMessage($message, 'from'); - if (isset($message->from)) { - $email->set('fromString', $message->from); - } - if (isset($message->replyTo)) { - $email->set('replyToString', $message->replyTo); + if ($parser->checkMessageAttribute($message, 'from')) { + $email->set('fromString', $parser->getMessageAttribute($message, 'from')); } - $toArr = $this->getAddressListFromMessage($message, 'to'); - $ccArr = $this->getAddressListFromMessage($message, 'cc'); - $replyToArr = $this->getAddressListFromMessage($message, 'replyTo'); + if ($parser->checkMessageAttribute($message, 'replyTo')) { + $email->set('replyToString', $parser->getMessageAttribute($message, 'replyTo')); + } + + $fromArr = $parser->getAddressListFromMessage($message, 'from'); + $toArr = $parser->getAddressListFromMessage($message, 'to'); + $ccArr = $parser->getAddressListFromMessage($message, 'cc'); + $replyToArr = $parser->getAddressListFromMessage($message, 'replyTo'); $email->set('from', $fromArr[0]); $email->set('to', implode(';', $toArr)); @@ -125,15 +129,14 @@ class Importer return false; } - if (isset($message->messageId) && !empty($message->messageId)) { - $messageId = $message->messageId; - $messageId = str_replace('<<', '<', $messageId); - $messageId = str_replace('>>', '>', $messageId); + if ($parser->checkMessageAttribute($message, 'messageId') && $parser->getMessageAttribute($message, 'messageId')) { + $messageId = $parser->getMessageMessageId($message); + $email->set('messageId', $messageId); - if (isset($message->deliveredTo)) { - $email->set('messageIdInternal', $message->messageId . '-' . $message->deliveredTo); + if ($parser->checkMessageAttribute($message, 'deliveredTo')) { + $email->set('messageIdInternal', $messageId . '-' . $parser->getMessageAttribute($message, 'deliveredTo')); } - if (stripos($message->messageId, '@espo-system') !== false) { + if (stripos($messageId, '@espo-system') !== false) { return; } } @@ -167,49 +170,29 @@ class Importer return $duplicate; } - if (isset($message->date)) { - $dt = new \DateTime($message->date); - if ($dt) { - $dateSent = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s'); - $email->set('dateSent', $dateSent); - } + if ($parser->checkMessageAttribute($message, 'date')) { + try { + $dt = new \DateTime($parser->getMessageAttribute($message, 'date')); + if ($dt) { + $dateSent = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s'); + $email->set('dateSent', $dateSent); + } + } catch (\Exception $e) {} } else { $email->set('dateSent', date('Y-m-d H:i:s')); } - if (isset($message->deliveryDate)) { - $dt = new \DateTime($message->deliveryDate); - if ($dt) { - $deliveryDate = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s'); - $email->set('deliveryDate', $deliveryDate); - } + if ($parser->checkMessageAttribute($message, 'deliveryDate')) { + try { + $dt = new \DateTime($parser->getMessageAttribute($message, 'deliveryDate')); + if ($dt) { + $deliveryDate = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s'); + $email->set('deliveryDate', $deliveryDate); + } + } catch (\Exception $e) {} } - $inlineIds = array(); - if (!$fetchOnlyHeader) { - if ($message->isMultipart()) { - foreach (new \RecursiveIteratorIterator($message) as $part) { - $this->importPartDataToEmail($email, $part, $inlineIds); - } - } else { - $this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain'); - } - - if (!$email->get('body') && $email->get('bodyPlain')) { - $email->set('body', $email->get('bodyPlain')); - } - - $body = $email->get('body'); - if (!empty($body)) { - foreach ($inlineIds as $cid => $attachmentId) { - if (strpos($body, 'cid:' . $cid) !== false) { - $body = str_replace('cid:' . $cid, '?entryPoint=attachment&id=' . $attachmentId, $body); - } else { - $email->addLinkMultipleId('attachments', $attachmentId); - } - } - $email->set('body', $body); - } + $parser->fetchContentParts($email, $message); if ($this->getFiltersMatcher()->match($email, $filterList)) { return false; @@ -222,8 +205,9 @@ class Importer $parentFound = false; $replied = null; - if (isset($message->inReplyTo) && !empty($message->inReplyTo)) { - $arr = explode(' ', $message->inReplyTo); + + if ($parser->checkMessageAttribute($message, 'inReplyTo') && $parser->getMessageAttribute($message, 'inReplyTo')) { + $arr = explode(' ', $parser->getMessageAttribute($message, 'inReplyTo')); $inReplyTo = $arr[0]; $replied = $this->getEntityManager()->getRepository('Email')->where(array( 'messageId' => $inReplyTo @@ -233,8 +217,8 @@ class Importer } } - if (isset($message->references) && !empty($message->references)) { - $arr = explode(' ', $message->references); + if ($parser->checkMessageAttribute($message, 'references') && $parser->getMessageAttribute($message, 'references')) { + $arr = explode(' ', $parser->getMessageAttribute($message, 'references')); $reference = $arr[0]; $reference = str_replace(array('/', '@'), " ", trim($reference, '<>')); $parentType = $parentId = null; @@ -354,243 +338,4 @@ class Importer } } } - - protected function normilizeHeader($header) - { - if (is_a($header, 'ArrayIterator')) { - return $header->current(); - } else { - return $header; - } - } - - protected function getAddressListFromMessage($message, $type) - { - $addressList = array(); - if (isset($message->$type)) { - $list = $this->normilizeHeader($message->getHeader($type))->getAddressList(); - foreach ($list as $address) { - $addressList[] = $address->getEmail(); - } - } - return $addressList; - } - - protected function importPartDataToEmail(\Espo\Entities\Email $email, $part, &$inlineIds = array(), $defaultContentType = null) - { - try { - $type = null; - - if ($part->getHeaders() && isset($part->contentType)) { - $type = strtok($part->contentType, ';'); - } - - $contentDisposition = false; - if (isset($part->ContentDisposition)) { - if (strpos(strtolower($part->ContentDisposition), 'attachment') === 0) { - $contentDisposition = 'attachment'; - } else if (strpos(strtolower($part->ContentDisposition), 'inline') === 0) { - $contentDisposition = 'inline'; - } - } else if (isset($part->contentID)) { - $contentDisposition = 'inline'; - } - - if (empty($type)) { - if (!empty($defaultContentType)) { - $type = $defaultContentType; - } else { - return; - } - } - - $encoding = null; - $isAttachment = true; - if ($type == 'text/plain' || $type == 'text/html') { - if ($contentDisposition !== 'attachment') { - $isAttachment = false; - $content = $this->getContentFromPart($part); - if ($type == 'text/plain') { - $bodyPlain = ''; - if ($email->get('bodyPlain')) { - $bodyPlain .= $email->get('bodyPlain') . "\n"; - } - $bodyPlain .= $content; - $email->set('bodyPlain', $bodyPlain); - } else if ($type == 'text/html') { - $body = ''; - if ($email->get('body')) { - $body .= $email->get('body') . "
"; - } - $body .= $content; - $email->set('isHtml', true); - $email->set('body', $body); - } - } - } - - if ($isAttachment) { - $content = $part->getContent(); - $disposition = null; - - $fileName = null; - $contentId = null; - - if ($contentDisposition) { - if ($contentDisposition === 'attachment') { - $fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition); - if ($fileName) { - $disposition = 'attachment'; - } - } else if ($contentDisposition === 'inline') { - if (isset($part->contentID)) { - $contentId = trim($part->contentID, '<>'); - $fileName = $contentId; - $disposition = 'inline'; - } else { - // for iOS attachments - if (empty($fileName)) { - $fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition); - if ($fileName) { - $disposition = 'attachment'; - } - } - } - } - } - - if (isset($part->contentTransferEncoding)) { - $encoding = strtolower($this->normilizeHeader($part->getHeader('Content-Transfer-Encoding'))->getTransferEncoding()); - } - - $attachment = $this->getEntityManager()->getEntity('Attachment'); - $attachment->set('name', $fileName); - $attachment->set('type', $type); - - if ($disposition == 'inline') { - $attachment->set('role', 'Inline Attachment'); - } else { - $attachment->set('role', 'Attachment'); - } - - if ($encoding == 'base64') { - $content = base64_decode($content); - } - - $attachment->set('contents', $content); - - $this->getEntityManager()->saveEntity($attachment); - - if ($disposition == 'attachment') { - $attachmentsIds = $email->get('attachmentsIds'); - $attachmentsIds[] = $attachment->id; - $email->set('attachmentsIds', $attachmentsIds); - } else if ($disposition == 'inline') { - $inlineIds[$contentId] = $attachment->id; - } - } - } catch (\Exception $e) {} - } - - protected function decodeAttachmentFileName($fileName) - { - if ($fileName && stripos($fileName, "''") !== false) { - list($encoding, $fileName) = explode("''", $fileName); - $fileName = rawurldecode($fileName); - if (strtoupper($encoding) !== 'UTF-8') { - if ($encoding) { - $fileName = mb_convert_encoding($fileName, 'UTF-8', $encoding); - } - } - } - return $fileName; - } - - protected function fetchFileNameFromContentDisposition($contentDisposition) - { - $contentDisposition = preg_replace('/\\\\"/', "{{_!Q!U!O!T!E!_}}", $contentDisposition); - - $fileName = false; - $m = array(); - - if (preg_match('/filename="([^"]+)";?/i', $contentDisposition, $m)) { - $fileName = $m[1]; - } else if (preg_match('/filename=([^";]+);?/i', $contentDisposition, $m)) { - $fileName = $m[1]; - } else if (preg_match('/filename\*="([^"]+)";?/i', $contentDisposition, $m)) { - $fileName = $m[1]; - $fileName = $this->decodeAttachmentFileName($fileName); - } else if (preg_match('/filename\*=([^";]+);?/i', $contentDisposition, $m)) { - $fileName = $m[1]; - $fileName = $this->decodeAttachmentFileName($fileName); - } else { - $fileName = ''; - foreach (['0', '1'] as $i) { - if (preg_match('/filename\*'.$i.'[\*]?="([^"]+)";?/i', $contentDisposition, $m)) { - $part = $m[1]; - $fileName .= $part; - } else if (preg_match('/filename\*'.$i.'[\*]?=([^";]+);?/i', $contentDisposition, $m)) { - $part = $m[1]; - $fileName .= $part; - } - } - - if ($fileName === '') { - $fileName = null; - } else { - $fileName = $this->decodeAttachmentFileName($fileName); - } - } - - if ($fileName) { - $fileName = str_replace('{{_!Q!U!O!T!E!_}}', '"', $fileName); - } - - return $fileName; - } - - protected function getContentFromPart($part) - { - if ($part instanceof \Zend\Mime\Part) { - $content = $part->getRawContent(); - if (strtolower($part->charset) != 'utf-8') { - $content = mb_convert_encoding($content, 'UTF-8', $part->charset); - } - } else { - $content = $part->getContent(); - - $encoding = null; - - if (isset($part->contentTransferEncoding)) { - $cteHeader = $this->normilizeHeader($part->getHeader('Content-Transfer-Encoding')); - $encoding = strtolower($cteHeader->getTransferEncoding()); - } - - if ($encoding == 'base64') { - $content = base64_decode($content); - } - - $charset = 'UTF-8'; - - if (isset($part->contentType)) { - $ctHeader = $this->normilizeHeader($part->getHeader('Content-Type')); - $charsetParamValue = $ctHeader->getParameter('charset'); - if (!empty($charsetParamValue)) { - $charset = strtoupper($charsetParamValue); - } - } - - if (isset($part->contentTransferEncoding)) { - $cteHeader = $this->normilizeHeader($part->getHeader('Content-Transfer-Encoding')); - if ($cteHeader->getTransferEncoding() == 'quoted-printable') { - $content = quoted_printable_decode($content); - } - } - - if ($charset !== 'UTF-8') { - $content = mb_convert_encoding($content, 'UTF-8', $charset); - } - } - return $content; - } } diff --git a/application/Espo/Core/Mail/Parsers/Zend.php b/application/Espo/Core/Mail/Parsers/Zend.php new file mode 100644 index 0000000000..8bdffa36f9 --- /dev/null +++ b/application/Espo/Core/Mail/Parsers/Zend.php @@ -0,0 +1,338 @@ +entityManager = $entityManager; + } + + public function getEntityManager() + { + return $this->entityManager; + } + + public function checkMessageAttribute($message, $attribute) + { + return isset($message->$attribute); + } + + public function getMessageAttribute($message, $attribute) + { + if (!isset($message->$attribute)) return null; + + return $message->$attribute; + } + + public function getMessageMessageId($message) + { + if (!isset($message->messageId)) return null; + + $messageId = $message->messageId; + $messageId = str_replace('<<', '<', $messageId); + $messageId = str_replace('>>', '>', $messageId); + + return $messageId; + } + + public function getAddressListFromMessage($message, $type) + { + $addressList = array(); + if (isset($message->$type)) { + $list = $this->normilizeHeader($message->getHeader($type))->getAddressList(); + foreach ($list as $address) { + $addressList[] = $address->getEmail(); + } + } + return $addressList; + } + + public function fetchContentParts(\Espo\Entities\Email $email, $message) + { + $inlineIds = array(); + + if ($message->isMultipart()) { + foreach (new \RecursiveIteratorIterator($message) as $part) { + $this->importPartDataToEmail($email, $part, $inlineIds); + } + } else { + $this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain'); + } + + if (!$email->get('body') && $email->get('bodyPlain')) { + $email->set('body', $email->get('bodyPlain')); + } + + $body = $email->get('body'); + if (!empty($body)) { + foreach ($inlineIds as $cid => $attachmentId) { + if (strpos($body, 'cid:' . $cid) !== false) { + $body = str_replace('cid:' . $cid, '?entryPoint=attachment&id=' . $attachmentId, $body); + } else { + $email->addLinkMultipleId('attachments', $attachmentId); + } + } + $email->set('body', $body); + } + } + + protected function importPartDataToEmail(\Espo\Entities\Email $email, $part, &$inlineIds = array(), $defaultContentType = null) + { + try { + $type = null; + + if ($part->getHeaders() && isset($part->contentType)) { + $type = strtok($part->contentType, ';'); + } + + $contentDisposition = false; + if (isset($part->ContentDisposition)) { + if (strpos(strtolower($part->ContentDisposition), 'attachment') === 0) { + $contentDisposition = 'attachment'; + } else if (strpos(strtolower($part->ContentDisposition), 'inline') === 0) { + $contentDisposition = 'inline'; + } + } else if (isset($part->contentID)) { + $contentDisposition = 'inline'; + } + + if (empty($type)) { + if (!empty($defaultContentType)) { + $type = $defaultContentType; + } else { + return; + } + } + + $encoding = null; + $isAttachment = true; + if ($type == 'text/plain' || $type == 'text/html') { + if ($contentDisposition !== 'attachment') { + $isAttachment = false; + $content = $this->getContentFromPart($part); + if ($type == 'text/plain') { + $bodyPlain = ''; + if ($email->get('bodyPlain')) { + $bodyPlain .= $email->get('bodyPlain') . "\n"; + } + $bodyPlain .= $content; + $email->set('bodyPlain', $bodyPlain); + } else if ($type == 'text/html') { + $body = ''; + if ($email->get('body')) { + $body .= $email->get('body') . "
"; + } + $body .= $content; + $email->set('isHtml', true); + $email->set('body', $body); + } + } + } + + if ($isAttachment) { + $content = $part->getContent(); + $disposition = null; + + $fileName = null; + $contentId = null; + + if ($contentDisposition) { + if ($contentDisposition === 'attachment') { + $fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition); + if ($fileName) { + $disposition = 'attachment'; + } + } else if ($contentDisposition === 'inline') { + if (isset($part->contentID)) { + $contentId = trim($part->contentID, '<>'); + $fileName = $contentId; + $disposition = 'inline'; + } else { + // for iOS attachments + if (empty($fileName)) { + $fileName = $this->fetchFileNameFromContentDisposition($part->ContentDisposition); + if ($fileName) { + $disposition = 'attachment'; + } + } + } + } + } + + if (isset($part->contentTransferEncoding)) { + $encoding = strtolower($this->normilizeHeader($part->getHeader('Content-Transfer-Encoding'))->getTransferEncoding()); + } + + $attachment = $this->getEntityManager()->getEntity('Attachment'); + $attachment->set('name', $fileName); + $attachment->set('type', $type); + + if ($disposition == 'inline') { + $attachment->set('role', 'Inline Attachment'); + } else { + $attachment->set('role', 'Attachment'); + } + + if ($encoding == 'base64') { + $content = base64_decode($content); + } + + $attachment->set('contents', $content); + + $this->getEntityManager()->saveEntity($attachment); + + if ($disposition == 'attachment') { + $attachmentsIds = $email->get('attachmentsIds'); + $attachmentsIds[] = $attachment->id; + $email->set('attachmentsIds', $attachmentsIds); + } else if ($disposition == 'inline') { + $inlineIds[$contentId] = $attachment->id; + } + } + } catch (\Exception $e) {} + } + + protected function getContentFromPart($part) + { + if ($part instanceof \Zend\Mime\Part) { + $content = $part->getRawContent(); + if (strtolower($part->charset) != 'utf-8') { + $content = mb_convert_encoding($content, 'UTF-8', $part->charset); + } + } else { + $content = $part->getContent(); + + $encoding = null; + + if (isset($part->contentTransferEncoding)) { + $cteHeader = $this->normilizeHeader($part->getHeader('Content-Transfer-Encoding')); + $encoding = strtolower($cteHeader->getTransferEncoding()); + } + + if ($encoding == 'base64') { + $content = base64_decode($content); + } + + $charset = 'UTF-8'; + + if (isset($part->contentType)) { + $ctHeader = $this->normilizeHeader($part->getHeader('Content-Type')); + $charsetParamValue = $ctHeader->getParameter('charset'); + if (!empty($charsetParamValue)) { + $charset = strtoupper($charsetParamValue); + } + } + + if (isset($part->contentTransferEncoding)) { + $cteHeader = $this->normilizeHeader($part->getHeader('Content-Transfer-Encoding')); + if ($cteHeader->getTransferEncoding() == 'quoted-printable') { + $content = quoted_printable_decode($content); + } + } + + if ($charset !== 'UTF-8') { + $content = mb_convert_encoding($content, 'UTF-8', $charset); + } + } + return $content; + } + + protected function normilizeHeader($header) + { + if (is_a($header, 'ArrayIterator')) { + return $header->current(); + } else { + return $header; + } + } + + protected function fetchFileNameFromContentDisposition($contentDisposition) + { + $contentDisposition = preg_replace('/\\\\"/', "{{_!Q!U!O!T!E!_}}", $contentDisposition); + + $fileName = false; + $m = array(); + + if (preg_match('/filename="([^"]+)";?/i', $contentDisposition, $m)) { + $fileName = $m[1]; + } else if (preg_match('/filename=([^";]+);?/i', $contentDisposition, $m)) { + $fileName = $m[1]; + } else if (preg_match('/filename\*="([^"]+)";?/i', $contentDisposition, $m)) { + $fileName = $m[1]; + $fileName = $this->decodeAttachmentFileName($fileName); + } else if (preg_match('/filename\*=([^";]+);?/i', $contentDisposition, $m)) { + $fileName = $m[1]; + $fileName = $this->decodeAttachmentFileName($fileName); + } else { + $fileName = ''; + foreach (['0', '1'] as $i) { + if (preg_match('/filename\*'.$i.'[\*]?="([^"]+)";?/i', $contentDisposition, $m)) { + $part = $m[1]; + $fileName .= $part; + } else if (preg_match('/filename\*'.$i.'[\*]?=([^";]+);?/i', $contentDisposition, $m)) { + $part = $m[1]; + $fileName .= $part; + } + } + + if ($fileName === '') { + $fileName = null; + } else { + $fileName = $this->decodeAttachmentFileName($fileName); + } + } + + if ($fileName) { + $fileName = str_replace('{{_!Q!U!O!T!E!_}}', '"', $fileName); + } + + return $fileName; + } + + protected function decodeAttachmentFileName($fileName) + { + if ($fileName && stripos($fileName, "''") !== false) { + list($encoding, $fileName) = explode("''", $fileName); + $fileName = rawurldecode($fileName); + if (strtoupper($encoding) !== 'UTF-8') { + if ($encoding) { + $fileName = mb_convert_encoding($fileName, 'UTF-8', $encoding); + } + } + } + return $fileName; + } + +} + From dfa2d6be894ca43c38ad0ff217423f65bd357e6b Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 16:04:43 +0300 Subject: [PATCH 04/22] email notifications related to parent --- application/Espo/Core/Mail/Importer.php | 3 +++ .../Espo/Services/EmailNotification.php | 22 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index b83dae6f4c..b22f99c0f7 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -225,6 +225,9 @@ class Importer $emailSent = PHP_INT_MAX; $number = null; $n = sscanf($reference, '%s %s %d %d espo', $parentType, $parentId, $emailSent, $number); + if ($n != 4) { + $n = sscanf($reference, '%s %s %d %d espo-system', $parentType, $parentId, $emailSent, $number); + } if ($n == 4 && $emailSent < time()) { if (!empty($parentType) && !empty($parentId)) { if ($parentType == 'Lead') { diff --git a/application/Espo/Services/EmailNotification.php b/application/Espo/Services/EmailNotification.php index 7c055c938c..f04a40e22a 100644 --- a/application/Espo/Services/EmailNotification.php +++ b/application/Espo/Services/EmailNotification.php @@ -133,7 +133,9 @@ class EmailNotification extends \Espo\Core\Services\Base 'body' => $body, 'isHtml' => true, 'to' => $emailAddress, - 'isSystem' => true + 'isSystem' => true, + 'parentId' => $entity->id, + 'parentType' => $entity->getEntityType() )); try { $this->getMailSender()->send($email); @@ -429,6 +431,13 @@ class EmailNotification extends \Espo\Core\Services\Base 'to' => $emailAddress, 'isSystem' => true )); + if ($parentId && $parentType) { + $email->set(array( + 'parentId' => $parentId, + 'parentType' => $parentType + )); + } + try { $this->getMailSender()->send($email); } catch (\Exception $e) { @@ -517,6 +526,12 @@ class EmailNotification extends \Espo\Core\Services\Base 'to' => $emailAddress, 'isSystem' => true )); + if ($parentId && $parentType) { + $email->set(array( + 'parentId' => $parentId, + 'parentType' => $parentType + )); + } try { $this->getMailSender()->send($email); } catch (\Exception $e) { @@ -576,8 +591,11 @@ class EmailNotification extends \Espo\Core\Services\Base 'body' => $body, 'isHtml' => true, 'to' => $emailAddress, - 'isSystem' => true + 'isSystem' => true, + 'parentId' => $parentId, + 'parentType' => $parentType )); + try { $this->getMailSender()->send($email); } catch (\Exception $e) { From d0b5e2188bead6bd6218902fac098e0bf551a262 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 16:13:01 +0300 Subject: [PATCH 05/22] add marketing industry --- application/Espo/Modules/Crm/Resources/i18n/en_US/Account.json | 1 + .../Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json | 1 + 2 files changed, 2 insertions(+) diff --git a/application/Espo/Modules/Crm/Resources/i18n/en_US/Account.json b/application/Espo/Modules/Crm/Resources/i18n/en_US/Account.json index c6be76bb79..a4fed37fc4 100644 --- a/application/Espo/Modules/Crm/Resources/i18n/en_US/Account.json +++ b/application/Espo/Modules/Crm/Resources/i18n/en_US/Account.json @@ -59,6 +59,7 @@ "Insurance": "Insurance", "Legal": "Legal", "Manufacturing": "Manufacturing", + "Marketing": "Marketing", "Publishing": "Publishing", "Real Estate": "Real Estate", "Service": "Service", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json index e04c511362..01d6f5ff2d 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json @@ -45,6 +45,7 @@ "Insurance", "Legal", "Manufacturing", + "Marketing", "Publishing", "Real Estate", "Service", From 7fbc9fbac8599853897691262bd4f0d57e425871 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 16:38:00 +0300 Subject: [PATCH 06/22] email fix --- application/Espo/SelectManagers/Email.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/Espo/SelectManagers/Email.php b/application/Espo/SelectManagers/Email.php index 21c99b32cf..280066c9c5 100644 --- a/application/Espo/SelectManagers/Email.php +++ b/application/Espo/SelectManagers/Email.php @@ -119,7 +119,10 @@ class Email extends \Espo\Core\SelectManagers\Base } $d = array( 'usersMiddle.inTrash=' => false, - 'usersMiddle.folderId' => null + 'usersMiddle.folderId' => null, + array( + 'status!=' => 'Draft' + ) ); if (!empty($idList)) { $d['fromEmailAddressId!='] = $idList; @@ -151,6 +154,9 @@ class Email extends \Espo\Core\SelectManagers\Base 'createdById' => $this->getUser()->id ) ), + array( + 'status!=' => 'Draft' + ), 'usersMiddle.inTrash=' => false ); } From a3836b0091deb17d02932d08a88a8b7839ae33ce Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 19 Oct 2016 16:38:00 +0300 Subject: [PATCH 07/22] email fix --- application/Espo/SelectManagers/Email.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/Espo/SelectManagers/Email.php b/application/Espo/SelectManagers/Email.php index 21c99b32cf..280066c9c5 100644 --- a/application/Espo/SelectManagers/Email.php +++ b/application/Espo/SelectManagers/Email.php @@ -119,7 +119,10 @@ class Email extends \Espo\Core\SelectManagers\Base } $d = array( 'usersMiddle.inTrash=' => false, - 'usersMiddle.folderId' => null + 'usersMiddle.folderId' => null, + array( + 'status!=' => 'Draft' + ) ); if (!empty($idList)) { $d['fromEmailAddressId!='] = $idList; @@ -151,6 +154,9 @@ class Email extends \Espo\Core\SelectManagers\Base 'createdById' => $this->getUser()->id ) ), + array( + 'status!=' => 'Draft' + ), 'usersMiddle.inTrash=' => false ); } From 1eb258cab7f03fe878dec2d7767411ae41bf4afc Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 11:26:41 +0300 Subject: [PATCH 08/22] email filtering fix --- application/Espo/SelectManagers/Email.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/application/Espo/SelectManagers/Email.php b/application/Espo/SelectManagers/Email.php index 280066c9c5..f4a3f4bb10 100644 --- a/application/Espo/SelectManagers/Email.php +++ b/application/Espo/SelectManagers/Email.php @@ -121,11 +121,22 @@ class Email extends \Espo\Core\SelectManagers\Base 'usersMiddle.inTrash=' => false, 'usersMiddle.folderId' => null, array( - 'status!=' => 'Draft' + 'status' => ['Archived', 'Sent'] ) ); if (!empty($idList)) { $d['fromEmailAddressId!='] = $idList; + $d[] = array( + 'OR' => array( + 'status' => 'Archived', + 'createdById!=' => $this->getUser()->id + ) + ); + } else { + $d[] = array( + 'status' => 'Archived', + 'createdById!=' => $this->getUser()->id + ); } $result['whereClause'][] = $d; From 46190df6ff79478a54ebc2caf2351fe82d113849 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 11:30:19 +0300 Subject: [PATCH 09/22] fix notice --- .../Espo/Modules/Crm/EntryPoints/CampaignTrackOpened.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/Modules/Crm/EntryPoints/CampaignTrackOpened.php b/application/Espo/Modules/Crm/EntryPoints/CampaignTrackOpened.php index 3a0feed98e..e510f955a8 100644 --- a/application/Espo/Modules/Crm/EntryPoints/CampaignTrackOpened.php +++ b/application/Espo/Modules/Crm/EntryPoints/CampaignTrackOpened.php @@ -90,7 +90,7 @@ class CampaignTrackOpened extends \Espo\Core\EntryPoints\Base imagefill($img, 0, 0, $color); imagepng($img); - imagecolordeallocate($background); + imagecolordeallocate($color); imagedestroy($img); } } From a9e783b5f19155c936f2c862d83ee9ac5dfec1c1 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 11:36:52 +0300 Subject: [PATCH 10/22] email autocomplete: users first --- application/Espo/Services/EmailAddress.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/Services/EmailAddress.php b/application/Espo/Services/EmailAddress.php index e88aa4ddfe..65abc84a27 100644 --- a/application/Espo/Services/EmailAddress.php +++ b/application/Espo/Services/EmailAddress.php @@ -177,13 +177,13 @@ class EmailAddress extends Record { $result = []; + $this->findInAddressBookUsers($query, $limit, $result); if ($this->getAcl()->checkScope('Contact')) { $this->findInAddressBookByEntityType($query, $limit, 'Contact', $result); } if ($this->getAcl()->checkScope('Lead')) { $this->findInAddressBookByEntityType($query, $limit, 'Lead', $result); } - $this->findInAddressBookUsers($query, $limit, $result); if ($this->getAcl()->checkScope('Account')) { $this->findInAddressBookByEntityType($query, $limit, 'Account', $result); } From ccb9022e161127631f274634b7599aedfe3a917c Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 12:47:44 +0300 Subject: [PATCH 11/22] fix email address search --- application/Espo/Services/Email.php | 2 +- .../Espo/Email/SearchByEmailAddressTest.php | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/integration/Espo/Email/SearchByEmailAddressTest.php diff --git a/application/Espo/Services/Email.php b/application/Espo/Services/Email.php index 3347bf66ae..982e35ebb7 100644 --- a/application/Espo/Services/Email.php +++ b/application/Espo/Services/Email.php @@ -586,7 +586,7 @@ class Email extends Record $searchByEmailAddress = false; if (!empty($params['where']) && is_array($params['where'])) { foreach ($params['where'] as $i => $p) { - if (!empty($p['field']) && $p['field'] == 'emailAddress') { + if (!empty($p['attribute']) && $p['attribute'] == 'emailAddress') { $searchByEmailAddress = true; $emailAddress = $p['value']; unset($params['where'][$i]); diff --git a/tests/integration/Espo/Email/SearchByEmailAddressTest.php b/tests/integration/Espo/Email/SearchByEmailAddressTest.php new file mode 100644 index 0000000000..0c7615e8ab --- /dev/null +++ b/tests/integration/Espo/Email/SearchByEmailAddressTest.php @@ -0,0 +1,59 @@ +getContainer()->get('entityManager'); + + $email = $entityManager->getEntity('Email'); + $email->set('from', 'test@test.com'); + $email->set('status', 'Archived'); + $entityManager->saveEntity($email); + + $emailService = $this->getApplication()->getContainer()->get('serviceFactory')->create('Email'); + + $result = $emailService->findEntities(array( + 'where' => array( + array( + 'type' => 'equals', + 'attribute' => 'emailAddress', + 'value' => 'test@test.com' + ) + ) + )); + + $this->assertArrayHasKey('collection', $result); + $this->assertEquals(1, count($result['collection'])); + } +} From aeeb84b626013be802e8215bf4b9d0e34e67a1d9 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 14:47:18 +0300 Subject: [PATCH 12/22] fix text and varchar --- client/res/templates/fields/text/search.tpl | 2 +- client/res/templates/fields/varchar/search.tpl | 2 +- client/src/views/fields/text.js | 7 ++++++- client/src/views/fields/varchar.js | 7 ++++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/res/templates/fields/text/search.tpl b/client/res/templates/fields/text/search.tpl index 20f56dea71..7141323b65 100644 --- a/client/res/templates/fields/text/search.tpl +++ b/client/res/templates/fields/text/search.tpl @@ -1,6 +1,6 @@ diff --git a/client/res/templates/fields/varchar/search.tpl b/client/res/templates/fields/varchar/search.tpl index 20f56dea71..7141323b65 100644 --- a/client/res/templates/fields/varchar/search.tpl +++ b/client/res/templates/fields/varchar/search.tpl @@ -1,6 +1,6 @@ diff --git a/client/src/views/fields/text.js b/client/src/views/fields/text.js index d6aeaa8106..079323494c 100644 --- a/client/src/views/fields/text.js +++ b/client/src/views/fields/text.js @@ -48,6 +48,8 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) { rowsDefault: 4, + searchTypeList: ['startsWith', 'contains', 'equals', 'isEmpty', 'isNotEmpty'], + events: { 'click a[data-action="seeMoreText"]': function (e) { this.seeMoreText = true; @@ -62,7 +64,6 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) { }, setupSearch: function () { - this.searchParams.typeOptions = ['startsWith', 'contains', 'equals', 'isEmpty', 'isNotEmpty']; this.events = _.extend({ 'change select.search-type': function (e) { var type = $(e.currentTarget).val(); @@ -192,6 +193,10 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) { } } return false; + }, + + getSearchType: function () { + return this.searchParams.typeFront || this.searchParams.type; } }); diff --git a/client/src/views/fields/varchar.js b/client/src/views/fields/varchar.js index 6ef017de9f..712032a0b3 100644 --- a/client/src/views/fields/varchar.js +++ b/client/src/views/fields/varchar.js @@ -34,8 +34,9 @@ Espo.define('views/fields/varchar', 'views/fields/base', function (Dep) { searchTemplate: 'fields/varchar/search', + searchTypeList: ['startsWith', 'endsWith', 'contains', 'equals', 'like', 'isEmpty', 'isNotEmpty'], + setupSearch: function () { - this.searchParams.typeOptions = ['startsWith', 'endsWith', 'contains', 'equals', 'like', 'isEmpty', 'isNotEmpty']; this.events = _.extend({ 'change select.search-type': function (e) { var type = $(e.currentTarget).val(); @@ -131,6 +132,10 @@ Espo.define('views/fields/varchar', 'views/fields/base', function (Dep) { } } return false; + }, + + getSearchType: function () { + return this.searchParams.typeFront || this.searchParams.type; } }); From dde044d8c5e773179d8c068c6cdf1a51c7c7ec3a Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 14:53:47 +0300 Subject: [PATCH 13/22] imptove email/phone filters --- client/res/templates/fields/email/search.tpl | 3 --- client/res/templates/fields/phone/search.tpl | 3 --- client/src/views/fields/email.js | 22 ++------------------ client/src/views/fields/phone.js | 18 ++-------------- 4 files changed, 4 insertions(+), 42 deletions(-) delete mode 100644 client/res/templates/fields/email/search.tpl delete mode 100644 client/res/templates/fields/phone/search.tpl diff --git a/client/res/templates/fields/email/search.tpl b/client/res/templates/fields/email/search.tpl deleted file mode 100644 index 8e41d53c13..0000000000 --- a/client/res/templates/fields/email/search.tpl +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/res/templates/fields/phone/search.tpl b/client/res/templates/fields/phone/search.tpl deleted file mode 100644 index 8e41d53c13..0000000000 --- a/client/res/templates/fields/phone/search.tpl +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/src/views/fields/email.js b/client/src/views/fields/email.js index cbbd616a2b..3f5ce8f711 100644 --- a/client/src/views/fields/email.js +++ b/client/src/views/fields/email.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('views/fields/email', 'views/fields/base', function (Dep) { +Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) { return Dep.extend({ @@ -38,11 +38,8 @@ Espo.define('views/fields/email', 'views/fields/base', function (Dep) { listTemplate: 'fields/email/list', - searchTemplate: 'fields/email/search', - validations: ['required', 'emailData'], - validateEmailData: function () { var data = this.model.get(this.dataFieldName); if (data && data.length) { @@ -370,23 +367,8 @@ Espo.define('views/fields/email', 'views/fields/base', function (Dep) { } return data; - }, - - fetchSearch: function () { - var value = this.$element.val() || null; - if (value) { - if (typeof value.trim === 'function') { - value = value.trim(); - } - var data = { - type: 'like', - value: value + '%', - valueText: value - }; - return data; - } - return false; } + }); }); diff --git a/client/src/views/fields/phone.js b/client/src/views/fields/phone.js index 63a1c5547d..6bb75320f7 100644 --- a/client/src/views/fields/phone.js +++ b/client/src/views/fields/phone.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('views/fields/phone', 'views/fields/base', function (Dep) { +Espo.define('views/fields/phone', 'views/fields/varchar', function (Dep) { return Dep.extend({ @@ -38,8 +38,6 @@ Espo.define('views/fields/phone', 'views/fields/base', function (Dep) { listTemplate: 'fields/phone/list', - searchTemplate: 'fields/phone/search', - validations: ['required'], validateRequired: function () { @@ -263,20 +261,8 @@ Espo.define('views/fields/phone', 'views/fields/base', function (Dep) { } return data; - }, + } - fetchSearch: function () { - var value = this.$element.val().trim() || null; - if (value) { - var data = { - type: 'like', - value: value + '%', - valueText: value - }; - return data; - } - return false; - }, }); }); From 5d79d8520fd606ce63271d6def48765be6d94b77 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 15:04:26 +0300 Subject: [PATCH 14/22] email: show group email account if email address matches --- application/Espo/Repositories/Email.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/Espo/Repositories/Email.php b/application/Espo/Repositories/Email.php index 4f6cf87521..34d80259fd 100644 --- a/application/Espo/Repositories/Email.php +++ b/application/Espo/Repositories/Email.php @@ -149,6 +149,9 @@ class Email extends \Espo\Core\ORM\Repositories\RDB $idHash = (object) []; foreach ($addressList as $address) { $p = $this->getEntityManager()->getRepository('EmailAddress')->getEntityByAddress($address); + if (!$p) { + $p = $this->getEntityManager()->getRepository('InboundEmail')->where(array('emailAddress' => $address))->findOne(); + } if ($p) { $nameHash->$address = $p->get('name'); $typeHash->$address = $p->getEntityName(); From 1f09015c864fdb2bfd90df256632ca857ca7d937 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 20 Oct 2016 15:48:33 +0300 Subject: [PATCH 15/22] fix ids --- application/Espo/Modules/Crm/Repositories/Meeting.php | 3 ++- application/Espo/Services/User.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/application/Espo/Modules/Crm/Repositories/Meeting.php b/application/Espo/Modules/Crm/Repositories/Meeting.php index 385417f0a1..bcac81af50 100644 --- a/application/Espo/Modules/Crm/Repositories/Meeting.php +++ b/application/Espo/Modules/Crm/Repositories/Meeting.php @@ -30,6 +30,7 @@ namespace Espo\Modules\Crm\Repositories; use Espo\ORM\Entity; +use Espo\Core\Utils\Util; class Meeting extends \Espo\Core\ORM\Repositories\RDB { @@ -210,7 +211,7 @@ class Meeting extends \Espo\Core\ORM\Repositories\RDB $remindAt->sub(new \DateInterval('PT' . $seconds . 'S')); foreach ($userIdList as $userId) { - $id = uniqid(true); + $id = Util::generateId(); $sql = " INSERT diff --git a/application/Espo/Services/User.php b/application/Espo/Services/User.php index 416becd5bd..d6ec806870 100644 --- a/application/Espo/Services/User.php +++ b/application/Espo/Services/User.php @@ -32,6 +32,7 @@ namespace Espo\Services; use \Espo\Core\Exceptions\Forbidden; use \Espo\Core\Exceptions\Error; use \Espo\Core\Exceptions\NotFound; +use Espo\Core\Utils\Util; use \Espo\ORM\Entity; @@ -153,7 +154,7 @@ class User extends Record throw new Forbidden(); } - $requestId = uniqid(); + $requestId = Util::generateId(); $passwordChangeRequest = $this->getEntityManager()->getEntity('PasswordChangeRequest'); $passwordChangeRequest->set(array( From 2333b7a1eff56d085861c279ddd81b621579e5e8 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 21 Oct 2016 11:21:03 +0300 Subject: [PATCH 16/22] user permission = all by default --- application/Espo/Resources/metadata/app/acl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/Resources/metadata/app/acl.json b/application/Espo/Resources/metadata/app/acl.json index 448dd5543d..f0f6a1b55e 100644 --- a/application/Espo/Resources/metadata/app/acl.json +++ b/application/Espo/Resources/metadata/app/acl.json @@ -76,7 +76,7 @@ "fieldLevel": { }, "assignmentPermission": "all", - "userPermission": "no", + "userPermission": "all", "portalPermission": "no" }, "scopeLevelTypesDefaults": { From d9ef87fb907e1dee0a0eff99f803de5d6aff20c3 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 21 Oct 2016 11:30:21 +0300 Subject: [PATCH 17/22] fix naming --- client/modules/crm/src/views/lead/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/modules/crm/src/views/lead/convert.js b/client/modules/crm/src/views/lead/convert.js index 2683afc780..4b1480bc73 100644 --- a/client/modules/crm/src/views/lead/convert.js +++ b/client/modules/crm/src/views/lead/convert.js @@ -111,7 +111,7 @@ Espo.define('crm:views/lead/convert', 'View', function (Dep) { }.bind(this)); } - this.createView(scope, 'Record.Edit', { + this.createView(scope, 'views/record/edit', { model: model, el: '#main .edit-container-' + Espo.Utils.toDom(scope), buttonsPosition: false, From 7b439ace0443cf48289c93ce69222287034aa805 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 21 Oct 2016 11:32:10 +0300 Subject: [PATCH 18/22] cleanup --- client/modules/crm/src/views/lead/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/modules/crm/src/views/lead/convert.js b/client/modules/crm/src/views/lead/convert.js index 4b1480bc73..0ec4663d54 100644 --- a/client/modules/crm/src/views/lead/convert.js +++ b/client/modules/crm/src/views/lead/convert.js @@ -96,7 +96,7 @@ Espo.define('crm:views/lead/convert', 'View', function (Dep) { this.getFieldManager().getEntityAttributes(model.name).forEach(function (attr) { if (~attributeList.indexOf(attr) && !~ignoreAttributeList.indexOf(attr)) { - model.set(attr, this.model.get(attr), {silent: true}); + model.set(attr, this.model.get(attr), {silent: true}); } }, this); From 304bbb62ad688fa94f5f0ee001dbb1906116345d Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 21 Oct 2016 12:05:36 +0300 Subject: [PATCH 19/22] fix naming --- client/modules/crm/src/views/lead/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/modules/crm/src/views/lead/convert.js b/client/modules/crm/src/views/lead/convert.js index 0ec4663d54..581b026fce 100644 --- a/client/modules/crm/src/views/lead/convert.js +++ b/client/modules/crm/src/views/lead/convert.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('crm:views/lead/convert', 'View', function (Dep) { +Espo.define('crm:views/lead/convert', 'view', function (Dep) { return Dep.extend({ From 7773e41ad4099e7177227ddc55470e35a7865991 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 21 Oct 2016 14:48:58 +0300 Subject: [PATCH 20/22] convert lead changes --- .../Espo/Modules/Crm/Controllers/Lead.php | 14 ++- .../Espo/Modules/Crm/Services/Lead.php | 111 ++++++++++++++++++ client/modules/crm/src/views/lead/convert.js | 60 ++++------ 3 files changed, 147 insertions(+), 38 deletions(-) diff --git a/application/Espo/Modules/Crm/Controllers/Lead.php b/application/Espo/Modules/Crm/Controllers/Lead.php index 02324e424f..22b40c1627 100644 --- a/application/Espo/Modules/Crm/Controllers/Lead.php +++ b/application/Espo/Modules/Crm/Controllers/Lead.php @@ -34,14 +34,11 @@ use \Espo\Core\Exceptions\BadRequest; class Lead extends \Espo\Core\Controllers\Record { - public function actionConvert($params, $data, $request) + public function postActionConvert($params, $data, $request) { if (empty($data['id'])) { throw new BadRequest(); } - if (!$request->isPost()) { - throw new BadRequest(); - } $entity = $this->getRecordService()->convert($data['id'], $data['records']); if (!empty($entity)) { @@ -49,4 +46,13 @@ class Lead extends \Espo\Core\Controllers\Record } throw new Error(); } + + public function postActionGetConvertAttributes($params, $data, $request) + { + if (empty($data['id'])) { + throw new BadRequest(); + } + + return $this->getRecordService()->getConvertAttributes($data['id']); + } } diff --git a/application/Espo/Modules/Crm/Services/Lead.php b/application/Espo/Modules/Crm/Services/Lead.php index d6ebf83ba5..c66985f7cb 100644 --- a/application/Espo/Modules/Crm/Services/Lead.php +++ b/application/Espo/Modules/Crm/Services/Lead.php @@ -36,6 +36,18 @@ use \Espo\ORM\Entity; class Lead extends \Espo\Services\Record { + + protected function init() + { + parent::init(); + $this->addDependency('container'); + } + + protected function getFieldManager() + { + return $this->getInjection('container')->get('fieldManager'); + } + protected function getDuplicateWhereClause(Entity $entity, $data = array()) { $data = array( @@ -100,6 +112,105 @@ class Lead extends \Espo\Services\Record } } + public function getConvertAttributes($id) + { + $lead = $this->getEntity($id); + + if (!$this->getAcl()->check($lead, 'read')) { + throw new Forbidden(); + } + + $data = array(); + + $entityList = $this->getMetadata()->get('entityDefs.Lead.convertEntityList', []); + + $ignoreAttributeList = ['createdAt', 'modifiedAt', 'modifiedById', 'modifiedByName', 'createdById', 'createdByName']; + + $convertFieldsDefs = $this->getMetadata()->get('entityDefs.Lead.convertFields', array()); + + foreach ($entityList as $entityType) { + if (!$this->getAcl()->checkScope($entityType, 'edit')) continue; + + $attributes = array(); + + $target = $this->getEntityManager()->getEntity($entityType); + + $fieldMap = array(); + + $fieldList = array_keys($this->getMetadata()->get('entityDefs.Lead.fields', array())); + foreach ($fieldList as $field) { + if (!$this->getMetadata()->get('entityDefs.'.$entityType.'.fields.' . $field)) continue; + if ( + $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, 'type']) + !== + $this->getMetadata()->get(['entityDefs', 'Lead', 'fields', $field, 'type']) + ) continue; + + $fieldMap[$field] = $field; + } + if (array_key_exists($entityType, $convertFieldsDefs)) { + foreach ($convertFieldsDefs[$entityType] as $field => $leadField) { + $fieldMap[$field] = $leadField; + } + } + + foreach ($fieldMap as $field => $leadField) { + $type = $this->getMetadata()->get(['entityDefs', 'Lead', 'fields', $field, 'type']); + + + if (in_array($type, ['file', 'image'])) { + $attachment = $lead->get($field); + if ($attachment) { + $attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment); + $idAttribute = $field . 'Id'; + $nameAttribute = $field . 'Name'; + if ($attachment) { + $attributes[$idAttribute] = $attachment->id; + $attributes[$nameAttribute] = $attachment->get('name'); + } + } + continue; + } else if (in_array($type, ['attachmentMultiple'])) { + $attachmentList = $lead->get($field); + if (count($attachmentList)) { + $idList = []; + $nameHash = (object) []; + $typeHash = (object) []; + foreach ($attachmentList as $attachment) { + $attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment); + if ($attachment) { + $idList[] = $attachment->id; + $nameHash->{$attachment->id} = $attachment->get('name'); + $typeHash->{$attachment->id} = $attachment->get('type'); + } + } + $attributes[$field . 'Ids'] = $idList; + $attributes[$field . 'Names'] = $nameHash; + $attributes[$field . 'Types'] = $typeHash; + } + continue; + } + + $leadAttributeList = $this->getFieldManager()->getAttributeList('Lead', $leadField); + $attributeList = $this->getFieldManager()->getAttributeList($entityType, $field); + + foreach ($attributeList as $i => $attribute) { + if (in_array($attribute, $ignoreAttributeList)) continue; + + $leadAttribute = $leadAttributeList[$i]; + if (!$lead->has($leadAttribute)) continue; + + $attributes[$attribute] = $lead->get($leadAttribute); + } + } + + $data[$entityType] = $attributes; + + } + + return $data; + } + public function convert($id, $recordsData) { $lead = $this->getEntity($id); diff --git a/client/modules/crm/src/views/lead/convert.js b/client/modules/crm/src/views/lead/convert.js index 581b026fce..0697b24cc3 100644 --- a/client/modules/crm/src/views/lead/convert.js +++ b/client/modules/crm/src/views/lead/convert.js @@ -90,42 +90,34 @@ Espo.define('crm:views/lead/convert', 'view', function (Dep) { var attributeList = this.getFieldManager().getEntityAttributes(this.model.name); var ignoreAttributeList = ['createdAt', 'modifiedAt', 'modifiedById', 'modifiedByName', 'createdById', 'createdByName']; - scopeList.forEach(function (scope) { - this.getModelFactory().create(scope, function (model) { - model.populateDefaults(); + if (scopeList.length) { + this.ajaxPostRequest('Lead/action/getConvertAttributes', { + id: this.model.id + }).done(function (data) { + scopeList.forEach(function (scope) { + this.getModelFactory().create(scope, function (model) { + model.populateDefaults(); - this.getFieldManager().getEntityAttributes(model.name).forEach(function (attr) { - if (~attributeList.indexOf(attr) && !~ignoreAttributeList.indexOf(attr)) { - model.set(attr, this.model.get(attr), {silent: true}); - } + model.set(data[scope] || {}, {silent: true}); + + this.createView(scope, 'views/record/edit', { + model: model, + el: '#main .edit-container-' + Espo.Utils.toDom(scope), + buttonsPosition: false, + layoutName: 'detailConvert', + exit: function () {}, + }, function (view) { + i++; + if (i == scopeList.length) { + this.wait(false); + this.notify(false); + } + }, this); + + }, this); }, this); - - for (var field in this.model.defs.convertFields[scope]) { - var leadField = this.model.defs.convertFields[scope][field]; - var leadAttrs = this.getFieldManager().getAttributes(this.model.getFieldParam(leadField, 'type'), leadField); - var attrs = this.getFieldManager().getAttributes(model.getFieldParam(field, 'type'), field); - - attrs.forEach(function (attr, i) { - var leadAttr = leadAttrs[i]; - model.set(attr, this.model.get(leadAttr)); - }.bind(this)); - } - - this.createView(scope, 'views/record/edit', { - model: model, - el: '#main .edit-container-' + Espo.Utils.toDom(scope), - buttonsPosition: false, - layoutName: 'detailConvert', - exit: function () {}, - }, function (view) { - i++; - if (i == scopeList.length) { - this.wait(false); - this.notify(false); - } - }.bind(this)); - }, this); - }, this); + }.bind(this)); + } if (scopeList.length == 0) { this.wait(false); From c1a115e2b0f12e8462acea0d6efd8a2add17310a Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 21 Oct 2016 15:55:03 +0300 Subject: [PATCH 21/22] reminders: skip if declined --- .../Espo/Modules/Crm/Services/Activities.php | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/application/Espo/Modules/Crm/Services/Activities.php b/application/Espo/Modules/Crm/Services/Activities.php index 15f35e9b1b..47c05afd6e 100644 --- a/application/Espo/Modules/Crm/Services/Activities.php +++ b/application/Espo/Modules/Crm/Services/Activities.php @@ -1101,22 +1101,39 @@ class Activities extends \Espo\Core\Services\Base $sth = $pdo->prepare($sql); $sth->execute(); - $rows = $sth->fetchAll(PDO::FETCH_ASSOC); + $rowList = $sth->fetchAll(PDO::FETCH_ASSOC); $result = array(); - foreach ($rows as $row) { - $entity = $this->getEntityManager()->getEntity($row['entityType'], $row['entityId']); + foreach ($rowList as $row) { + $reminderId = $row['id']; + $entityType = $row['entityType']; + $entityId = $row['entityId']; + + + $entity = $this->getEntityManager()->getEntity($entityType, $entityId); $data = null; + if ($entity) { + if ($entityType === 'Meeting' || $entityType === 'Call') { + $entity->loadLinkMultipleField('users', array('status' => 'acceptanceStatus')); + $status = $entity->getLinkMultipleColumn('users', 'status', $userId); + if ($status === 'Declined') { + $this->removeReminder($reminderId); + continue; + } + } + $data = array( 'id' => $entity->id, - 'entityType' => $row['entityType'], + 'entityType' => $entityType, 'dateStart' => $entity->get('dateStart'), 'name' => $entity->get('name') ); + } else { + continue; } $result[] = array( - 'id' => $row['id'], + 'id' => $reminderId, 'data' => $data ); From 730b5fb8a95232c38c40f6b38f8a57931470e914 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 28 Oct 2016 14:46:37 +0300 Subject: [PATCH 22/22] fix calendar dashlet --- client/modules/crm/src/views/dashlets/calendar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/modules/crm/src/views/dashlets/calendar.js b/client/modules/crm/src/views/dashlets/calendar.js index 219fe59548..486f8d5680 100644 --- a/client/modules/crm/src/views/dashlets/calendar.js +++ b/client/modules/crm/src/views/dashlets/calendar.js @@ -38,7 +38,7 @@ Espo.define('crm:views/dashlets/calendar', 'views/dashlets/abstract/base', funct init: function () { Dep.prototype.init.call(this); - this.optionsFields['enabledScopeList'].options = this.getMetadata().get('clientDefs.Calendar.scopeList') || this.optionsFields['enabledScopeList'].options; + this.optionsFields['enabledScopeList'].options = this.getConfig().get('calendarEntityList') || []; }, afterRender: function () {