Skip to content

Commit

Permalink
Merge branch 'main' into ESC-365-no-error-message-for-a-deleted-record
Browse files Browse the repository at this point in the history
  • Loading branch information
JhumanJ authored Nov 13, 2024
2 parents 8ccf789 + ef12c82 commit 3b9cbac
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 79 deletions.
2 changes: 1 addition & 1 deletion api/app/Http/Controllers/Forms/PublicFormController.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private function getRedirectData($form, $submissionData)
return ['id' => $key, 'value' => $value];
})->values()->all();

$redirectUrl = ($form->redirect_url) ? (new MentionParser($form->redirect_url, $formattedData))->parse() : null;
$redirectUrl = ($form->redirect_url) ? (new MentionParser($form->redirect_url, $formattedData))->parseAsText() : null;

if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
$redirectUrl = null;
Expand Down
3 changes: 2 additions & 1 deletion api/app/Integrations/Handlers/EmailIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function handle(): void
if ($this->form->is_pro) { // For Send to field Mentions are Pro feature
$formatter = (new FormSubmissionFormatter($this->form, $this->submissionData))->outputStringsOnly();
$parser = new MentionParser($this->integrationData?->send_to, $formatter->getFieldsWithValue());
$sendTo = $parser->parse();
$sendTo = $parser->parseAsText();
} else {
$sendTo = $this->integrationData?->send_to;
}
Expand All @@ -73,6 +73,7 @@ public function handle(): void
'form_slug' => $this->form->slug,
'mailer' => $this->mailer
]);

$recipients->each(function ($subscriber) {
Notification::route('mail', $subscriber)->notify(
new FormEmailNotification($this->event, $this->integrationData, $this->mailer)
Expand Down
4 changes: 2 additions & 2 deletions api/app/Notifications/Forms/FormEmailNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ private function getReplyToEmail($default): string
private function parseReplyTo(string $replyTo): ?string
{
$parser = new MentionParser($replyTo, $this->formatSubmissionData(false));
return $parser->parse();
return $parser->parseAsText();
}

private function getSubject(): string
{
$defaultSubject = 'New form submission';
$parser = new MentionParser($this->integrationData->subject ?? $defaultSubject, $this->formatSubmissionData(false));
return $parser->parse();
return $parser->parseAsText();
}

private function addCustomHeaders(Email $message): void
Expand Down
72 changes: 55 additions & 17 deletions api/app/Open/MentionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,64 @@ public function parse()
return $result;
}

private function replaceMentions()
public function parseAsText()
{
$pattern = '/<span[^>]*mention-field-id="([^"]*)"[^>]*mention-fallback="([^"]*)"[^>]*>.*?<\/span>/';
return preg_replace_callback($pattern, function ($matches) {
$fieldId = $matches[1];
$fallback = $matches[2];
$value = $this->getData($fieldId);
// First use the existing parse method to handle mentions
$html = $this->parse();

if ($value !== null) {
if (is_array($value)) {
return implode(' ', array_map(function ($v) {
return $v;
}, $value));
}
return $value;
} elseif ($fallback) {
return $fallback;
$doc = new DOMDocument();
$internalErrors = libxml_use_internal_errors(true);

// Wrap in root element
$wrappedContent = '<root>' . $html . '</root>';

$doc->loadHTML(mb_convert_encoding($wrappedContent, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_use_internal_errors($internalErrors);

// Convert HTML to plain text with proper line breaks
$text = '';
$this->domToText($doc->getElementsByTagName('root')->item(0), $text);

// Clean up the text:
// 1. Remove escaped newlines
// 2. Replace multiple newlines with single newline
// 3. Trim whitespace
$text = str_replace(['\\n', '\n'], "\n", $text);
$text = preg_replace('/\n+/', "\n", trim($text));

// Ensure each line has exactly one email
$lines = explode("\n", $text);
$lines = array_map('trim', $lines);
$lines = array_filter($lines); // Remove empty lines

return implode("\n", $lines);
}

private function domToText($node, &$text)
{
if ($node->nodeType === XML_TEXT_NODE) {
$text .= $node->nodeValue;
return;
}

$block_elements = ['div', 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li'];
$nodeName = strtolower($node->nodeName);

// Add newline before block elements
if (in_array($nodeName, $block_elements)) {
$text .= "\n";
}

if ($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
$this->domToText($child, $text);
}
return '';
}, $this->content);
}

// Add newline after block elements
if (in_array($nodeName, $block_elements)) {
$text .= "\n";
}
}

private function getData($fieldId)
Expand Down
206 changes: 148 additions & 58 deletions api/tests/Unit/Service/Forms/MentionParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,85 +2,175 @@

use App\Open\MentionParser;

test('it replaces mention elements with their corresponding values', function () {
$content = '<p>Hello <span mention mention-field-id="123">Placeholder</span></p>';
$data = [['id' => '123', 'value' => 'World']];
describe('MentionParser', function () {
it('replaces mentions with their values in HTML', function () {
$content = '<div>Hello <span mention mention-field-id="123" mention-fallback="">Name</span></div>';
$data = [
['id' => '123', 'value' => 'John Doe']
];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<div>Hello John Doe</div>');
});

it('uses fallback when value is not found', function () {
$content = '<span mention mention-field-id="456" mention-fallback="Guest">Name</span>';
$data = [];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('Guest');
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
it('removes the element when no value and no fallback is provided', function () {
$content = '<div>Hello <span mention mention-field-id="789" mention-fallback="">Name</span>!</div>';
$data = [];

expect($result)->toBe('<p>Hello World</p>');
});
$parser = new MentionParser($content, $data);
$result = $parser->parse();

test('it handles multiple mentions', function () {
$content = '<p><span mention mention-field-id="123">Name</span> is <span mention mention-field-id="456">Age</span> years old</p>';
$data = [
['id' => '123', 'value' => 'John'],
['id' => '456', 'value' => 30],
];
expect($result)->toBe('<div>Hello !</div>');
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
describe('parseAsText', function () {
it('converts HTML to plain text with proper line breaks', function () {
$content = '<div>First line</div><div>Second line</div>';

expect($result)->toBe('<p>John is 30 years old</p>');
});
$parser = new MentionParser($content, []);
$result = $parser->parseAsText();

test('it uses fallback when value is not found', function () {
$content = '<p>Hello <span mention mention-field-id="123" mention-fallback="Friend">Placeholder</span></p>';
$data = [];
expect($result)->toBe("First line\nSecond line");
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
it('handles email addresses with proper line breaks', function () {
$content = '<span mention mention-field-id="123" mention-fallback="">Email</span><div>john@example.com</div>';
$data = [
['id' => '123', 'value' => 'jane@example.com']
];

expect($result)->toBe('<p>Hello Friend</p>');
});
$parser = new MentionParser($content, $data);
$result = $parser->parseAsText();

test('it removes mention element when no value and no fallback', function () {
$content = '<p>Hello <span mention mention-field-id="123">Placeholder</span></p>';
$data = [];
expect($result)->toBe("jane@example.com\njohn@example.com");
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
it('handles multiple mentions and complex HTML structure', function () {
$content = '
<div>Contact: <span mention mention-field-id="123" mention-fallback="">Email1</span></div>
<div>CC: <span mention mention-field-id="456" mention-fallback="">Email2</span></div>
<div>Additional: test@example.com</div>
';
$data = [
['id' => '123', 'value' => 'primary@example.com'],
['id' => '456', 'value' => 'secondary@example.com'],
];

expect($result)->toBe('<p>Hello </p>');
});
$parser = new MentionParser($content, $data);
$result = $parser->parseAsText();

test('it handles array values', function () {
$content = '<p>Tags: <span mention mention-field-id="123">Placeholder</span></p>';
$data = [['id' => '123', 'value' => ['PHP', 'Laravel', 'Testing']]];
expect($result)->toBe(
"Contact: primary@example.com\n" .
"CC: secondary@example.com\n" .
"Additional: test@example.com"
);
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
it('handles array values in mentions', function () {
$content = '<span mention mention-field-id="123" mention-fallback="">Emails</span>';
$data = [
['id' => '123', 'value' => ['first@test.com', 'second@test.com']]
];

expect($result)->toBe('<p>Tags: PHP, Laravel, Testing</p>');
});
$parser = new MentionParser($content, $data);
$result = $parser->parseAsText();

test('it preserves HTML structure', function () {
$content = '<div><p>Hello <span mention mention-field-id="123">Placeholder</span></p><p>How are you?</p></div>';
$data = [['id' => '123', 'value' => 'World']];
expect($result)->toBe('first@test.com, second@test.com');
});
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
test('it replaces mention elements with their corresponding values', function () {
$content = '<p>Hello <span mention mention-field-id="123">Placeholder</span></p>';
$data = [['id' => '123', 'value' => 'World']];

expect($result)->toBe('<div><p>Hello World</p><p>How are you?</p></div>');
});
$parser = new MentionParser($content, $data);
$result = $parser->parse();

test('it handles UTF-8 characters', function () {
$content = '<p>こんにちは <span mention mention-field-id="123">Placeholder</span></p>';
$data = [['id' => '123', 'value' => '世界']];
expect($result)->toBe('<p>Hello World</p>');
});

$parser = new MentionParser($content, $data);
$result = $parser->parse();
test('it handles multiple mentions', function () {
$content = '<p><span mention mention-field-id="123">Name</span> is <span mention mention-field-id="456">Age</span> years old</p>';
$data = [
['id' => '123', 'value' => 'John'],
['id' => '456', 'value' => 30],
];

expect($result)->toBe('<p>こんにちは 世界</p>');
});
$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<p>John is 30 years old</p>');
});

test('it uses fallback when value is not found', function () {
$content = '<p>Hello <span mention mention-field-id="123" mention-fallback="Friend">Placeholder</span></p>';
$data = [];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<p>Hello Friend</p>');
});

test('it removes mention element when no value and no fallback', function () {
$content = '<p>Hello <span mention mention-field-id="123">Placeholder</span></p>';
$data = [];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<p>Hello </p>');
});

test('it handles array values', function () {
$content = '<p>Tags: <span mention mention-field-id="123">Placeholder</span></p>';
$data = [['id' => '123', 'value' => ['PHP', 'Laravel', 'Testing']]];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<p>Tags: PHP, Laravel, Testing</p>');
});

test('it preserves HTML structure', function () {
$content = '<div><p>Hello <span mention mention-field-id="123">Placeholder</span></p><p>How are you?</p></div>';
$data = [['id' => '123', 'value' => 'World']];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<div><p>Hello World</p><p>How are you?</p></div>');
});

test('it handles UTF-8 characters', function () {
$content = '<p>こんにちは <span mention mention-field-id="123">Placeholder</span></p>';
$data = [['id' => '123', 'value' => '世界']];

$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('<p>こんにちは 世界</p>');
});

test('it handles content without surrounding paragraph tags', function () {
$content = 'some text <span contenteditable="false" mention="" mention-field-id="123" mention-field-name="Post excerpt" mention-fallback="">Post excerpt</span> dewde';
$data = [['id' => '123', 'value' => 'replaced text']];
test('it handles content without surrounding paragraph tags', function () {
$content = 'some text <span contenteditable="false" mention="" mention-field-id="123" mention-field-name="Post excerpt" mention-fallback="">Post excerpt</span> dewde';
$data = [['id' => '123', 'value' => 'replaced text']];

$parser = new MentionParser($content, $data);
$result = $parser->parse();
$parser = new MentionParser($content, $data);
$result = $parser->parse();

expect($result)->toBe('some text replaced text dewde');
expect($result)->toBe('some text replaced text dewde');
});
});

0 comments on commit 3b9cbac

Please sign in to comment.