Skip to content

Commit 27a034f

Browse files
authored
Merge pull request #16 from mailcarrierapp/feat/multiple-cc-bcc
Allow multiple CCs and BCCs
2 parents 00033e4 + cae7d44 commit 27a034f

File tree

14 files changed

+411
-83
lines changed

14 files changed

+411
-83
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Support\Facades\DB;
5+
use MailCarrier\Models\Log;
6+
7+
return new class extends Migration {
8+
/**
9+
* Run the migrations.
10+
*/
11+
public function up(): void
12+
{
13+
$tableName = (new Log())->getTable();
14+
15+
DB::table($tableName)
16+
->whereNotNull('cc')
17+
->orWhereNotNull('bcc')
18+
->orderBy('id')
19+
->each(function (object $log) use ($tableName) {
20+
$changes = [];
21+
22+
if (!is_null($log->cc)) {
23+
$changes['cc'] = [json_decode($log->cc, true)];
24+
}
25+
26+
if (!is_null($log->bcc)) {
27+
$changes['bcc'] = [json_decode($log->bcc, true)];
28+
}
29+
30+
if (!empty($changes)) {
31+
DB::table($tableName)
32+
->where('id', $log->id)
33+
->update($changes);
34+
}
35+
});
36+
}
37+
38+
/**
39+
* Reverse the migrations.
40+
*/
41+
public function down(): void
42+
{
43+
//
44+
}
45+
};

phpunit.xml.dist

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" executionOrder="random" failOnWarning="true" failOnRisky="true" failOnEmptyTestSuite="true" beStrictAboutOutputDuringTests="true" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" executionOrder="random" failOnWarning="true" failOnRisky="true" failOnEmptyTestSuite="true" beStrictAboutOutputDuringTests="true" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
33
<testsuites>
44
<testsuite name="MailCarrier Test Suite">
55
<directory>tests</directory>
66
</testsuite>
77
</testsuites>
88
<coverage>
9-
<include>
10-
<directory suffix=".php">./src</directory>
11-
</include>
129
<report>
1310
<html outputDirectory="build/coverage"/>
1411
<text outputFile="build/coverage.txt"/>
@@ -22,4 +19,9 @@
2219
<env name="APP_ENV" value="testing"/>
2320
<env name="APP_KEY" value="base64:yk+bUVuZa1p86Dqjk9OjVK2R1pm6XHxC6xEKFq8utH0="/>
2421
</php>
22+
<source>
23+
<include>
24+
<directory suffix=".php">./src</directory>
25+
</include>
26+
</source>
2527
</phpunit>

resources/views/modals/details.blade.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,26 @@
1010
@if ($log->cc)
1111
<div>
1212
<p class="font-bold mb-1">Cc</p>
13-
@if ($log->cc->name)
14-
<p>{{ $log->cc->name }} &lt;{{ $log->cc->email }}&gt;</p>
15-
@else
16-
<p>{{ $log->cc->email }}</p>
17-
@endif
13+
@foreach ($log->cc as $cc)
14+
@if ($cc->name)
15+
<p>{{ $cc->name }} &lt;{{ $cc->email }}&gt;</p>
16+
@else
17+
<p>{{ $cc->email }}</p>
18+
@endif
19+
@endforeach
1820
</div>
1921
@endif
2022

2123
@if ($log->bcc)
2224
<div>
2325
<p class="font-bold mb-1">Bcc</p>
24-
@if ($log->bcc->name)
25-
<p>{{ $log->bcc->name }} &lt;{{ $log->bcc->email }}&gt;</p>
26-
@else
27-
<p>{{ $log->bcc->email }}</p>
28-
@endif
26+
@foreach ($log->bcc as $bcc)
27+
@if ($bcc->name)
28+
<p>{{ $bcc->name }} &lt;{{ $bcc->email }}&gt;</p>
29+
@else
30+
<p>{{ $bcc->email }}</p>
31+
@endif
32+
@endforeach
2933
</div>
3034
@endif
3135

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace MailCarrier\Dto\Casters;
4+
5+
use Illuminate\Support\Collection;
6+
use MailCarrier\Dto\ContactDto;
7+
use Spatie\DataTransferObject\Caster;
8+
9+
class ContactArrayCaster implements Caster
10+
{
11+
public function cast(mixed $value): ?array
12+
{
13+
if (is_null($value)) {
14+
return null;
15+
}
16+
17+
if ($value instanceof ContactDto) {
18+
return [$value];
19+
}
20+
21+
if (!is_array($value)) {
22+
$value = [
23+
'email' => $value,
24+
];
25+
}
26+
27+
if (Collection::make($value)->every(fn (mixed $value) => $value instanceof ContactDto)) {
28+
return $value;
29+
}
30+
31+
if (is_array($value) && array_is_list($value) && is_string($value[0])) {
32+
$value = array_map(
33+
fn (string $item) => [
34+
'email' => $item,
35+
],
36+
$value
37+
);
38+
}
39+
40+
if (!array_is_list($value)) {
41+
$value = [$value];
42+
}
43+
44+
return array_map(
45+
fn (array $item) => new ContactDto($item),
46+
$value
47+
);
48+
}
49+
}

src/Dto/GenericMailDto.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ class GenericMailDto extends DataTransferObject
2121

2222
public ?ContactDto $sender;
2323

24-
public ?ContactDto $cc;
24+
/** @var \MailCarrier\Dto\ContactDto[]|null */
25+
public ?array $cc;
2526

26-
public ?ContactDto $bcc;
27+
/** @var \MailCarrier\Dto\ContactDto[]|null */
28+
public ?array $bcc;
2729

2830
public Template $template;
2931

src/Dto/RecipientDto.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace MailCarrier\Dto;
44

5-
use MailCarrier\Dto\Casters\ContactStringCaster;
5+
use MailCarrier\Dto\Casters\ContactArrayCaster;
66
use MailCarrier\Dto\Validators\Email;
77
use Spatie\DataTransferObject\Attributes\CastWith;
88
use Spatie\DataTransferObject\Casters\ArrayCaster;
@@ -16,11 +16,13 @@ class RecipientDto extends DataTransferObject
1616
/** @var array<string, mixed> */
1717
public array $variables = [];
1818

19-
#[CastWith(ContactStringCaster::class)]
20-
public ?ContactDto $cc;
19+
/** @var \MailCarrier\Dto\ContactDto[]|null */
20+
#[CastWith(ContactArrayCaster::class)]
21+
public ?array $cc;
2122

22-
#[CastWith(ContactStringCaster::class)]
23-
public ?ContactDto $bcc;
23+
/** @var \MailCarrier\Dto\ContactDto[]|null */
24+
#[CastWith(ContactArrayCaster::class)]
25+
public ?array $bcc;
2426

2527
/** @var \Illuminate\Http\UploadedFile[] */
2628
public array $attachments = [];

src/Dto/SendMailDto.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace MailCarrier\Dto;
44

5+
use MailCarrier\Dto\Casters\ContactArrayCaster;
56
use MailCarrier\Dto\Casters\ContactStringCaster;
67
use Spatie\DataTransferObject\Attributes\CastWith;
78
use Spatie\DataTransferObject\Casters\ArrayCaster;
@@ -20,11 +21,13 @@ class SendMailDto extends DataTransferObject
2021

2122
public ?string $recipient;
2223

23-
#[CastWith(ContactStringCaster::class)]
24-
public ?ContactDto $cc;
24+
/** @var \MailCarrier\Dto\ContactDto[]|null */
25+
#[CastWith(ContactArrayCaster::class)]
26+
public ?array $cc;
2527

26-
#[CastWith(ContactStringCaster::class)]
27-
public ?ContactDto $bcc;
28+
/** @var \MailCarrier\Dto\ContactDto[]|null */
29+
#[CastWith(ContactArrayCaster::class)]
30+
public ?array $bcc;
2831

2932
/** @var array<string, mixed> */
3033
public array $variables = [];

src/Http/Requests/SendMailRequest.php

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ public function rules(): array
2626
'trigger' => 'sometimes|string|max:255',
2727
'subject' => 'required|string|max:255',
2828
'sender' => ['sometimes', new ContactRule()],
29-
'cc' => ['sometimes', new ContactRule()],
30-
'bcc' => ['sometimes', new ContactRule()],
29+
'cc' => 'sometimes|array',
30+
'bcc' => 'sometimes|array',
31+
'cc.*' => [new ContactRule()],
32+
'bcc.*' => [new ContactRule()],
3133
'variables' => 'sometimes|array',
3234
'tags' => 'sometimes|array',
3335
'metadata' => 'sometimes|array',
@@ -65,8 +67,10 @@ public function rules(): array
6567
Rule::when($this->has('recipients') && is_array($this->json('recipients.0')), 'required|email'),
6668
],
6769
'recipients.*.variables' => 'sometimes|array',
68-
'recipients.*.cc' => ['sometimes', new ContactRule()],
69-
'recipients.*.bcc' => ['sometimes', new ContactRule()],
70+
'recipients.*.cc' => 'sometimes|array',
71+
'recipients.*.cc.*' => [new ContactRule()],
72+
'recipients.*.bcc' => 'sometimes|array',
73+
'recipients.*.bcc.*' => [new ContactRule()],
7074

7175
// Recipients attachments
7276
'recipients.*.attachments' => 'sometimes|array',
@@ -106,6 +110,26 @@ protected function prepareForValidation(): void
106110
]);
107111
}
108112

113+
// Wrap cc array list
114+
if (
115+
!is_null($this->input('cc'))
116+
&& (!is_array($this->input('cc')) || !array_is_list($this->input('cc')))
117+
) {
118+
$this->merge([
119+
'cc' => [$this->input('cc')],
120+
]);
121+
}
122+
123+
// Wrap bcc array list
124+
if (
125+
!is_null($this->input('bcc'))
126+
&& (!is_array($this->input('bcc')) || !array_is_list($this->input('bcc')))
127+
) {
128+
$this->merge([
129+
'bcc' => [$this->input('bcc')],
130+
]);
131+
}
132+
109133
// Wrap recipient array list into a structured data
110134
if (is_array($this->input('recipients')) && !is_array($this->json('recipients.0'))) {
111135
$this->merge([
@@ -118,6 +142,31 @@ protected function prepareForValidation(): void
118142
]);
119143
}
120144

145+
// Wrap recipient cc and bcc
146+
$recipients = $this->input('recipients');
147+
148+
if (is_array($recipients)) {
149+
foreach ($recipients as $i => $recipient) {
150+
if (
151+
array_key_exists('cc', $recipient)
152+
&& (!is_array($this->input('cc')) || !array_is_list($this->input('cc')))
153+
) {
154+
$recipients[$i]['cc'] = [$recipient['cc']];
155+
}
156+
157+
if (
158+
array_key_exists('bcc', $recipient)
159+
&& (!is_array($this->input('bcc')) || !array_is_list($this->input('bcc')))
160+
) {
161+
$recipients[$i]['bcc'] = [$recipient['bcc']];
162+
}
163+
}
164+
165+
$this->merge([
166+
'recipients' => $recipients,
167+
]);
168+
}
169+
121170
// Wrap remote attachments array list into a structured data
122171
if (is_array($this->input('remoteAttachments')) && !is_array($this->json('remoteAttachments.0'))) {
123172
$this->merge([

src/Mail/GenericMail.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ public function build(): self
6565
)
6666
->when(
6767
$this->params->cc,
68-
fn (GenericMail $mail) => $mail->cc($this->params->cc->email, $this->params->cc->name)
68+
fn (GenericMail $mail) => $mail->cc($this->params->cc)
6969
)
7070
->when(
7171
$this->params->bcc,
72-
fn (GenericMail $mail) => $mail->bcc($this->params->bcc->email, $this->params->bcc->name)
72+
fn (GenericMail $mail) => $mail->bcc($this->params->bcc)
7373
);
7474
}
7575

src/MailCarrierServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function packageConfigured(Package $package): void
7676
'3_create_templates_table',
7777
'4_create_logs_table',
7878
'5_create_attachments_table',
79+
'6_transform_logs_cc_bcc_array',
7980
])
8081
->runsMigrations();
8182

0 commit comments

Comments
 (0)