email body to plain inprovement
This commit is contained in:
@@ -33,13 +33,14 @@ use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Email\Util;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<Email>
|
||||
*/
|
||||
class BeforeSave implements SaveHook
|
||||
{
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
if (
|
||||
@@ -49,5 +50,22 @@ class BeforeSave implements SaveHook
|
||||
) {
|
||||
throw new BadRequest("Cannot set send-at if status is not Draft.");
|
||||
}
|
||||
|
||||
$this->processBodyPlain($entity);
|
||||
}
|
||||
|
||||
private function processBodyPlain(Email $entity): void
|
||||
{
|
||||
if (!$entity->isHtml() || !$entity->isAttributeChanged('body')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $entity->getBody();
|
||||
|
||||
if ($body) {
|
||||
$body = Util::stripHtml($body) ?: null;
|
||||
}
|
||||
|
||||
$entity->setBodyPlain($body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ class Email extends Entity
|
||||
public const SAVE_OPTION_IS_BEING_IMPORTED = 'isBeingImported';
|
||||
public const SAVE_OPTION_IS_JUST_SENT = 'isJustSent';
|
||||
|
||||
private const ATTR_BODY_PLAIN = 'bodyPlain';
|
||||
|
||||
public function get(string $attribute): mixed
|
||||
{
|
||||
if ($attribute === 'subject') {
|
||||
@@ -92,7 +94,7 @@ class Email extends Entity
|
||||
return $this->getReplyToAddressInternal();
|
||||
}
|
||||
|
||||
if ($attribute === 'bodyPlain') {
|
||||
if ($attribute === self::ATTR_BODY_PLAIN) {
|
||||
return $this->getBodyPlain();
|
||||
}
|
||||
|
||||
@@ -172,6 +174,7 @@ class Email extends Entity
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.4. As the system user ID may be not constant in the future.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function isManuallyArchived(): bool
|
||||
{
|
||||
@@ -204,7 +207,7 @@ class Email extends Entity
|
||||
|
||||
public function hasBodyPlain(): bool
|
||||
{
|
||||
return $this->hasInContainer('bodyPlain') && $this->getFromContainer('bodyPlain');
|
||||
return $this->hasInContainer(self::ATTR_BODY_PLAIN) && $this->getFromContainer(self::ATTR_BODY_PLAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,20 +226,13 @@ class Email extends Entity
|
||||
|
||||
public function getBodyPlain(): ?string
|
||||
{
|
||||
if ($this->getFromContainer('bodyPlain')) {
|
||||
return $this->getFromContainer('bodyPlain');
|
||||
if ($this->getFromContainer(self::ATTR_BODY_PLAIN)) {
|
||||
return $this->getFromContainer(self::ATTR_BODY_PLAIN);
|
||||
}
|
||||
|
||||
/** @var string $body */
|
||||
$body = $this->get('body') ?? '';
|
||||
$body = $this->getBody() ?: '';
|
||||
|
||||
$body = EmailUtil::stripHtml($body);
|
||||
|
||||
if (!$body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $body;
|
||||
return EmailUtil::stripHtml($body) ?: null;
|
||||
}
|
||||
|
||||
public function getBodyPlainForSending(): string
|
||||
@@ -246,9 +242,9 @@ class Email extends Entity
|
||||
|
||||
public function getBodyForSending(): string
|
||||
{
|
||||
$body = $this->get('body') ?? '';
|
||||
$body = $this->getBody() ?: '';
|
||||
|
||||
if (!empty($body)) {
|
||||
if ($body && $this->isHtml()) {
|
||||
$attachmentList = $this->getInlineAttachmentList();
|
||||
|
||||
foreach ($attachmentList as $attachment) {
|
||||
@@ -273,9 +269,9 @@ class Email extends Entity
|
||||
{
|
||||
$idList = [];
|
||||
|
||||
$body = $this->get('body');
|
||||
$body = $this->getBody();
|
||||
|
||||
if (empty($body)) {
|
||||
if (!$body) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -302,8 +298,7 @@ class Email extends Entity
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/** @var Attachment|null $attachment */
|
||||
$attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $id);
|
||||
$attachment = $this->entityManager->getRDBRepositoryByClass(Attachment::class)->getById($id);
|
||||
|
||||
if ($attachment) {
|
||||
$attachmentList[] = $attachment;
|
||||
@@ -372,7 +367,7 @@ class Email extends Entity
|
||||
|
||||
public function setBodyPlain(?string $bodyPlain): self
|
||||
{
|
||||
$this->set('bodyPlain', $bodyPlain);
|
||||
$this->set(self::ATTR_BODY_PLAIN, $bodyPlain);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -343,11 +343,11 @@
|
||||
"bodyPlain": {
|
||||
"type": "text",
|
||||
"seeMoreDisabled": true,
|
||||
"clientReadOnly": true,
|
||||
"customizationDisabled": true,
|
||||
"layoutDefaultSidePanelDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"massUpdateDisabled": true
|
||||
"massUpdateDisabled": true,
|
||||
"readOnly": true
|
||||
},
|
||||
"body": {
|
||||
"type": "wysiwyg",
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace Espo\Tools\Email;
|
||||
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
class Util
|
||||
{
|
||||
static public function parseFromName(string $string): string
|
||||
@@ -71,18 +73,19 @@ class Util
|
||||
*/
|
||||
static public function stripHtml(string $string): string
|
||||
{
|
||||
$breaks = [
|
||||
"<br />",
|
||||
"<br>",
|
||||
"<br/>",
|
||||
"<br />",
|
||||
"<br />",
|
||||
"<br/>",
|
||||
"<br>",
|
||||
];
|
||||
if (!$string) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$string = str_ireplace($breaks, "\r\n", $string);
|
||||
$string = strip_tags($string);
|
||||
$converter = new HtmlConverter();
|
||||
$converter->setOptions([
|
||||
'remove_nodes' => 'img',
|
||||
'strip_tags' => true,
|
||||
]);
|
||||
|
||||
$string = $converter->convert($string) ?: '';
|
||||
|
||||
$string = (string) preg_replace('~\R~u', "\r\n", $string);
|
||||
|
||||
$reList = [
|
||||
'&(quot|#34);',
|
||||
@@ -111,10 +114,11 @@ class Util
|
||||
];
|
||||
|
||||
foreach ($reList as $i => $re) {
|
||||
/** @var string $string */
|
||||
$string = mb_ereg_replace($re, $replaceList[$i], $string, 'i');
|
||||
$string = (string) mb_ereg_replace($re, $replaceList[$i], $string, 'i');
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
@@ -860,18 +860,6 @@ class WysiwygFieldView extends TextFieldView {
|
||||
data[this.name] = null;
|
||||
}
|
||||
|
||||
if (this.hasBodyPlainField && this.model.has('isHtml')) {
|
||||
const plainAttribute = this.name + 'Plain';
|
||||
|
||||
if (data[this.name] === null) {
|
||||
data[plainAttribute] = null;
|
||||
} else {
|
||||
data[plainAttribute] = this.isHtml() ?
|
||||
this.htmlToPlain(data[this.name]) :
|
||||
data[this.name];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -53,7 +53,8 @@
|
||||
"lasserafn/php-initial-avatar-generator": "^4.4",
|
||||
"tholu/php-cidr-match": "^0.4",
|
||||
"league/oauth2-client": "^2.8",
|
||||
"symfony/mailer": "^6"
|
||||
"symfony/mailer": "^6",
|
||||
"league/html-to-markdown": "^5.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
|
||||
Generated
+90
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "cca59898a92b6c9f0723465a59dd8c9f",
|
||||
"content-hash": "0c7231d8b867aa042b9360424cf0702a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -3047,6 +3047,95 @@
|
||||
},
|
||||
"time": "2024-08-09T21:24:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/html-to-markdown",
|
||||
"version": "5.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/html-to-markdown.git",
|
||||
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-xml": "*",
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikehaertl/php-shellcommand": "^1.1.0",
|
||||
"phpstan/phpstan": "^1.8.8",
|
||||
"phpunit/phpunit": "^8.5 || ^9.2",
|
||||
"scrutinizer/ocular": "^1.6",
|
||||
"unleashedtech/php-coding-standard": "^2.7 || ^3.0",
|
||||
"vimeo/psalm": "^4.22 || ^5.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/html-to-markdown"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\HTMLToMarkdown\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Nick Cernis",
|
||||
"email": "nick@cern.is",
|
||||
"homepage": "http://modernnerd.net",
|
||||
"role": "Original Author"
|
||||
}
|
||||
],
|
||||
"description": "An HTML-to-markdown conversion helper for PHP",
|
||||
"homepage": "https://github.com/thephpleague/html-to-markdown",
|
||||
"keywords": [
|
||||
"html",
|
||||
"markdown"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/html-to-markdown/issues",
|
||||
"source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.colinodell.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/colinodell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-12T21:21:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/mime-type-detection",
|
||||
"version": "1.16.0",
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
|
||||
namespace tests\unit\Espo\Entities;
|
||||
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Tools\Email\Util;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Repository\RDBRepository;
|
||||
|
||||
class EmailTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
@@ -39,6 +41,16 @@ class EmailTest extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
private $email;
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $entityManager;
|
||||
|
||||
/**
|
||||
* @var RDBRepository
|
||||
*/
|
||||
private $attachmentRepository;
|
||||
|
||||
// TODO defs test helper
|
||||
protected $defs = [
|
||||
'attributes' =>
|
||||
@@ -425,61 +437,74 @@ class EmailTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->entityManager = $this->getMockBuilder('\Espo\Core\ORM\EntityManager')->disableOriginalConstructor()->getMock();
|
||||
$this->entityManager = $this->createMock(EntityManager::class);
|
||||
|
||||
$this->attachmentRepository = $this->createMock(RDBRepository::class);
|
||||
|
||||
$this->entityManager
|
||||
->expects($this->any())
|
||||
->method('getRDBRepositoryByClass')
|
||||
->willReturnMap([
|
||||
[Attachment::class, $this->attachmentRepository],
|
||||
]);
|
||||
|
||||
$this->repository =
|
||||
$this->getMockBuilder('Espo\Core\ORM\Repositories\Database')->disableOriginalConstructor()->getMock();
|
||||
|
||||
$this->entityManager->expects($this->any())
|
||||
$this->entityManager
|
||||
->expects($this->any())
|
||||
->method('getRepository')
|
||||
->will($this->returnValue($this->repository));
|
||||
|
||||
$this->email = new Email('Email', $this->defs, $this->entityManager);
|
||||
$this->email = new Email(
|
||||
entityType: 'Email',
|
||||
defs: $this->defs,
|
||||
entityManager: $this->entityManager,
|
||||
);
|
||||
}
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
$this->entityManager = null;
|
||||
$this->repository = null;
|
||||
$this->email = null;
|
||||
}
|
||||
|
||||
|
||||
function testGetInlineAttachments()
|
||||
public function testGetInlineAttachments(): void
|
||||
{
|
||||
$this->email->set('body', 'test <img src="?entryPoint=attachment&id=Id01">');
|
||||
|
||||
$this->entityManager->expects($this->exactly(1))
|
||||
->method('getEntityById')
|
||||
->with('Attachment', 'Id01');
|
||||
$this->attachmentRepository
|
||||
->expects($this->once())
|
||||
->method('getById')
|
||||
->with('Id01');
|
||||
|
||||
$this->email->getInlineAttachmentList();
|
||||
}
|
||||
|
||||
function testGetBodyForSending()
|
||||
public function testGetBodyForSending(): void
|
||||
{
|
||||
$attachment =
|
||||
$this->getMockBuilder('Espo\Entities\Attachment')->disableOriginalConstructor()->getMock();
|
||||
$attachment = $this->createMock(Attachment::class);
|
||||
|
||||
$attachment
|
||||
->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('Id01');
|
||||
|
||||
$this->email->set('body', 'test <img src="?entryPoint=attachment&id=Id01">');
|
||||
$this->attachmentRepository
|
||||
->expects($this->once())
|
||||
->method('getById')
|
||||
->with('Id01')
|
||||
->willReturn($attachment);
|
||||
|
||||
$this->entityManager->expects($this->any())
|
||||
->method('getEntityById')
|
||||
->with('Attachment', 'Id01')
|
||||
->will($this->returnValue($attachment));
|
||||
$body = 'test <img src="?entryPoint=attachment&id=Id01">';
|
||||
|
||||
$this->email->setBody($body);
|
||||
$this->email->setIsHtml();
|
||||
|
||||
$body = $this->email->getBodyForSending();
|
||||
|
||||
$this->assertEquals('test <img src="cid:Id01@espo">', $body);
|
||||
}
|
||||
|
||||
public function testBodyPlain(): void
|
||||
{
|
||||
$this->email->set('body', '<br /> &');
|
||||
$this->email->setBody('<br /> &');
|
||||
$bodyPlain = $this->email->getBodyPlain();
|
||||
|
||||
$this->assertEquals(" \r\n &", $bodyPlain);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user