Skip to content

Commit

Permalink
Apply Mentions everywhere (#595)
Browse files Browse the repository at this point in the history
* variables and mentions

* fix lint

* add missing changes

* fix tests

* update quilly, fix bugs

* fix lint

* apply fixes

* apply fixes

* Fix MentionParser

* Apply Mentions everywhere

* Fix MentionParserTest

* Small refactoring

* Fixing quill import issues

* Polished email integration, added customer sender mail

* Add missing changes

* improve migration command

---------

Co-authored-by: Frank <[email protected]>
Co-authored-by: Julien Nahum <[email protected]>
  • Loading branch information
3 people authored Oct 22, 2024
1 parent 2fdf2a4 commit dad5c82
Show file tree
Hide file tree
Showing 50 changed files with 1,901 additions and 872 deletions.
108 changes: 108 additions & 0 deletions api/app/Console/Commands/EmailNotificationMigration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace App\Console\Commands;

use App\Models\Forms\Form;
use App\Models\Integration\FormIntegration;
use Illuminate\Console\Command;

class EmailNotificationMigration extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'forms:email-notification-migration';

/**
* The console command description.
*
* @var string
*/
protected $description = 'One Time Only -- Migrate Email & Submission Notifications to new Email Integration';

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (app()->environment('production')) {
if (!$this->confirm('Are you sure you want to run this migration in production?')) {
$this->info('Migration aborted.');
return 0;
}
}
$query = FormIntegration::whereIn('integration_id', ['email', 'submission_confirmation'])
->whereHas('form');
$totalCount = $query->count();
$progressBar = $this->output->createProgressBar($totalCount);
$progressBar->start();

$query->with('form')->chunk(100, function ($integrations) use ($progressBar) {
foreach ($integrations as $integration) {
try {
$this->updateIntegration($integration);
} catch (\Exception $e) {
$this->error('Error updating integration ' . $integration->id . '. Error: ' . $e->getMessage());
ray($e);
}
$progressBar->advance();
}
});

$progressBar->finish();
$this->newLine();

$this->line('Migration Done');
}

public function updateIntegration(FormIntegration $integration)
{
if (!$integration->form) {
return;
}
$existingData = $integration->data;
if ($integration->integration_id === 'email') {
$integration->data = [
'send_to' => $existingData->notification_emails ?? null,
'sender_name' => 'OpnForm',
'subject' => 'New form submission',
'email_content' => 'Hello there 👋 <br>New form submission received.',
'include_submission_data' => true,
'include_hidden_fields_submission_data' => false,
'reply_to' => $existingData->notification_reply_to ?? null
];
} elseif ($integration->integration_id === 'submission_confirmation') {
$integration->integration_id = 'email';
$integration->data = [
'send_to' => $this->getMentionHtml($integration->form),
'sender_name' => $existingData->notification_sender,
'subject' => $existingData->notification_subject,
'email_content' => $existingData->notification_body,
'include_submission_data' => $existingData->notifications_include_submission,
'include_hidden_fields_submission_data' => false,
'reply_to' => $existingData->confirmation_reply_to ?? null
];
}
return $integration->save();
}

private function getMentionHtml(Form $form)
{
$emailField = $this->getRespondentEmail($form);
return $emailField ? '<span mention-field-id="' . $emailField['id'] . '" mention-field-name="' . $emailField['name'] . '" mention-fallback="" contenteditable="false" mention="true">' . $emailField['name'] . '</span>' : '';
}

private function getRespondentEmail(Form $form)
{
$emailFields = collect($form->properties)->filter(function ($field) {
$hidden = $field['hidden'] ?? false;
return !$hidden && $field['type'] == 'email';
});

return $emailFields->count() > 0 ? $emailFields->first() : null;
}
}
24 changes: 20 additions & 4 deletions api/app/Http/Controllers/Forms/PublicFormController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Jobs\Form\StoreFormSubmissionJob;
use App\Models\Forms\Form;
use App\Models\Forms\FormSubmission;
use App\Open\MentionParser;
use App\Service\Forms\FormCleaner;
use App\Service\WorkspaceHelper;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -105,13 +106,28 @@ public function answer(AnswerFormRequest $request)
return $this->success(array_merge([
'message' => 'Form submission saved.',
'submission_id' => $submissionId,
'is_first_submission' => $isFirstSubmission
], $request->form->is_pro && $request->form->redirect_url ? [
'is_first_submission' => $isFirstSubmission,
], $this->getRedirectData($request->form, $submissionData)));
}

private function getRedirectData($form, $submissionData)
{
$formattedData = collect($submissionData)->map(function ($value, $key) {
return ['id' => $key, 'value' => $value];
})->values()->all();

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

if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
$redirectUrl = null;
}

return $form->is_pro && $redirectUrl ? [
'redirect' => true,
'redirect_url' => $request->form->redirect_url,
'redirect_url' => $redirectUrl,
] : [
'redirect' => false,
]));
];
}

public function fetchSubmission(Request $request, string $slug, string $submissionId)
Expand Down
2 changes: 1 addition & 1 deletion api/app/Http/Requests/UserFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function rules()
're_fillable' => 'boolean',
're_fill_button_text' => 'string|min:1|max:50',
'submitted_text' => 'string|max:2000',
'redirect_url' => 'nullable|active_url|max:255',
'redirect_url' => 'nullable|max:255',
'database_fields_update' => 'nullable|array',
'max_submissions_count' => 'integer|nullable|min:1',
'max_submissions_reached_text' => 'string|nullable',
Expand Down
10 changes: 7 additions & 3 deletions api/app/Integrations/Handlers/DiscordIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Integrations\Handlers;

use App\Open\MentionParser;
use App\Service\Forms\FormSubmissionFormatter;
use Illuminate\Support\Arr;
use Vinkla\Hashids\Facades\Hashids;
Expand Down Expand Up @@ -32,6 +33,9 @@ protected function shouldRun(): bool

protected function getWebhookData(): array
{
$formatter = (new FormSubmissionFormatter($this->form, $this->submissionData))->outputStringsOnly();
$formattedData = $formatter->getFieldsWithValue();

$settings = (array) $this->integrationData ?? [];
$externalLinks = [];
if (Arr::get($settings, 'link_open_form', true)) {
Expand All @@ -50,8 +54,7 @@ protected function getWebhookData(): array
$blocks = [];
if (Arr::get($settings, 'include_submission_data', true)) {
$submissionString = '';
$formatter = (new FormSubmissionFormatter($this->form, $this->submissionData))->outputStringsOnly();
foreach ($formatter->getFieldsWithValue() as $field) {
foreach ($formattedData as $field) {
$tmpVal = is_array($field['value']) ? implode(',', $field['value']) : $field['value'];
$submissionString .= '**' . ucfirst($field['name']) . '**: ' . $tmpVal . "\n";
}
Expand Down Expand Up @@ -80,8 +83,9 @@ protected function getWebhookData(): array
];
}

$message = Arr::get($settings, 'message', 'New form submission');
return [
'content' => 'New submission for your form **' . $this->form->title . '**',
'content' => (new MentionParser($message, $formattedData))->parse(),
'tts' => false,
'username' => config('app.name'),
'avatar_url' => asset('img/logo.png'),
Expand Down
53 changes: 44 additions & 9 deletions api/app/Integrations/Handlers/EmailIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,51 @@

namespace App\Integrations\Handlers;

use App\Rules\OneEmailPerLine;
use App\Notifications\Forms\FormEmailNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use App\Notifications\Forms\FormSubmissionNotification;
use App\Open\MentionParser;
use App\Service\Forms\FormSubmissionFormatter;

class EmailIntegration extends AbstractEmailIntegrationHandler
{
public const RISKY_USERS_LIMIT = 120;

public static function getValidationRules(): array
{
return [
'notification_emails' => ['required', new OneEmailPerLine()],
'notification_reply_to' => 'email|nullable',
'send_to' => 'required',
'sender_name' => 'required',
'sender_email' => 'email|nullable',
'subject' => 'required',
'email_content' => 'required',
'include_submission_data' => 'boolean',
'include_hidden_fields_submission_data' => ['nullable', 'boolean'],
'reply_to' => 'nullable',
];
}

protected function shouldRun(): bool
{
return $this->integrationData->notification_emails && parent::shouldRun();
return $this->integrationData->send_to && parent::shouldRun() && !$this->riskLimitReached();
}

// To avoid phishing abuse we limit this feature for risky users
private function riskLimitReached(): bool
{
// This is a per-workspace limit for risky workspaces
if ($this->form->workspace->is_risky) {
if ($this->form->workspace->submissions_count >= self::RISKY_USERS_LIMIT) {
Log::error('!!!DANGER!!! Dangerous user detected! Attempting many email sending.', [
'form_id' => $this->form->id,
'workspace_id' => $this->form->workspace->id,
]);

return true;
}
}

return false;
}

public function handle(): void
Expand All @@ -28,19 +55,27 @@ public function handle(): void
return;
}

$subscribers = collect(preg_split("/\r\n|\n|\r/", $this->integrationData->notification_emails))
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();
} else {
$sendTo = $this->integrationData->send_to;
}

$recipients = collect(preg_split("/\r\n|\n|\r/", $sendTo))
->filter(function ($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
});
Log::debug('Sending email notification', [
'recipients' => $subscribers->toArray(),
'recipients' => $recipients->toArray(),
'form_id' => $this->form->id,
'form_slug' => $this->form->slug,
'mailer' => $this->mailer
]);
$subscribers->each(function ($subscriber) {
$recipients->each(function ($subscriber) {
Notification::route('mail', $subscriber)->notify(
new FormSubmissionNotification($this->event, $this->integrationData, $this->mailer)
new FormEmailNotification($this->event, $this->integrationData, $this->mailer)
);
});
}
Expand Down
10 changes: 7 additions & 3 deletions api/app/Integrations/Handlers/SlackIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Integrations\Handlers;

use App\Open\MentionParser;
use App\Service\Forms\FormSubmissionFormatter;
use Illuminate\Support\Arr;
use Vinkla\Hashids\Facades\Hashids;
Expand Down Expand Up @@ -32,6 +33,9 @@ protected function shouldRun(): bool

protected function getWebhookData(): array
{
$formatter = (new FormSubmissionFormatter($this->form, $this->submissionData))->outputStringsOnly();
$formattedData = $formatter->getFieldsWithValue();

$settings = (array) $this->integrationData ?? [];
$externalLinks = [];
if (Arr::get($settings, 'link_open_form', true)) {
Expand All @@ -46,20 +50,20 @@ protected function getWebhookData(): array
$externalLinks[] = '*<' . $this->form->share_url . '?submission_id=' . $submissionId . '|✍️ ' . $this->form->editable_submissions_button_text . '>*';
}

$message = Arr::get($settings, 'message', 'New form submission');
$blocks = [
[
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => 'New submission for your form *' . $this->form->title . '*',
'text' => (new MentionParser($message, $formattedData))->parse(),
],
],
];

if (Arr::get($settings, 'include_submission_data', true)) {
$submissionString = '';
$formatter = (new FormSubmissionFormatter($this->form, $this->submissionData))->outputStringsOnly();
foreach ($formatter->getFieldsWithValue() as $field) {
foreach ($formattedData as $field) {
$tmpVal = is_array($field['value']) ? implode(',', $field['value']) : $field['value'];
$submissionString .= '>*' . ucfirst($field['name']) . '*: ' . $tmpVal . " \n";
}
Expand Down
Loading

0 comments on commit dad5c82

Please sign in to comment.