Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend

This commit is contained in:
Taras Machyshyn
2016-11-03 18:00:15 +02:00
31 changed files with 704 additions and 412 deletions
+47 -299
View File
@@ -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&amp;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,14 +217,17 @@ 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;
$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') {
@@ -354,243 +341,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') . "<br>";
}
$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;
}
}
+338
View File
@@ -0,0 +1,338 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Mail\Parsers;
class Zend
{
private $entityManager;
public function __construct($entityManager)
{
$this->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&amp;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') . "<br>";
}
$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;
}
}
@@ -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']);
}
}
@@ -90,7 +90,7 @@ class CampaignTrackOpened extends \Espo\Core\EntryPoints\Base
imagefill($img, 0, 0, $color);
imagepng($img);
imagecolordeallocate($background);
imagecolordeallocate($color);
imagedestroy($img);
}
}
@@ -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
@@ -59,6 +59,7 @@
"Insurance": "Insurance",
"Legal": "Legal",
"Manufacturing": "Manufacturing",
"Marketing": "Marketing",
"Publishing": "Publishing",
"Real Estate": "Real Estate",
"Service": "Service",
@@ -45,6 +45,7 @@
"Insurance",
"Legal",
"Manufacturing",
"Marketing",
"Publishing",
"Real Estate",
"Service",
@@ -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
);
@@ -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);
+4 -1
View File
@@ -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;
}
+5 -5
View File
@@ -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];
}
}
+8 -7
View File
@@ -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;
}
+3
View File
@@ -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();
@@ -76,7 +76,7 @@
"fieldLevel": {
},
"assignmentPermission": "all",
"userPermission": "no",
"userPermission": "all",
"portalPermission": "no"
},
"scopeLevelTypesDefaults": {
+18 -1
View File
@@ -119,10 +119,24 @@ class Email extends \Espo\Core\SelectManagers\Base
}
$d = array(
'usersMiddle.inTrash=' => false,
'usersMiddle.folderId' => null
'usersMiddle.folderId' => null,
array(
'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;
@@ -151,6 +165,9 @@ class Email extends \Espo\Core\SelectManagers\Base
'createdById' => $this->getUser()->id
)
),
array(
'status!=' => 'Draft'
),
'usersMiddle.inTrash=' => false
);
}
+1 -1
View File
@@ -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]);
+1 -1
View File
@@ -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);
}
@@ -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) {
+3
View File
@@ -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') {
+2 -1
View File
@@ -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(
@@ -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 () {
+27 -35
View File
@@ -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({
@@ -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, '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);
@@ -1,3 +0,0 @@
<input type="text" class="main-element form-control input-sm" name="{{name}}" value="{{searchParams.valueText}}" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}}>
@@ -1,3 +0,0 @@
<input type="text" class="main-element form-control input-sm" name="{{name}}" value="{{searchParams.valueText}}" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}}>
+1 -1
View File
@@ -1,6 +1,6 @@
<select class="form-control search-type input-sm" name="{{name}}-type">
{{options searchParams.typeOptions searchParams.typeFront field='varcharSearchRanges'}}
{{options searchTypeList searchType field='varcharSearchRanges'}}
</select>
<input type="text" class="main-element form-control input-sm" name="{{name}}" value="{{searchParams.value}}" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}}{{#if params.size}} size="{{params.size}}"{{/if}} autocomplete="off" placeholder="{{translate 'Value'}}">
@@ -1,6 +1,6 @@
<select class="form-control search-type input-sm" name="{{name}}-type">
{{options searchParams.typeOptions searchParams.typeFront field='varcharSearchRanges'}}
{{options searchTypeList searchType field='varcharSearchRanges'}}
</select>
<input type="text" class="main-element form-control input-sm" name="{{name}}" value="{{searchParams.value}}" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}}{{#if params.size}} size="{{params.size}}"{{/if}} autocomplete="off" placeholder="{{translate 'Value'}}">
+2 -20
View File
@@ -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;
}
});
});
+2 -16
View File
@@ -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;
},
});
});
+6 -1
View File
@@ -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;
}
});
+6 -1
View File
@@ -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;
}
});
@@ -0,0 +1,59 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2016 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Email;
class SearchByEmailAddressTest extends \tests\integration\Core\BaseTestCase
{
public function testSearchByEmailAddress()
{
$entityManager = $this->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']));
}
}