Skip to content

Commit d8dab89

Browse files
authored
Merge pull request #2 from tattersoftware/parents
Implement parent templates
2 parents c83a92f + be2169e commit d8dab89

File tree

10 files changed

+160
-10
lines changed

10 files changed

+160
-10
lines changed

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Email toolkit for CodeIgniter 4
66

77
## Quick Start
88

9-
1. Install with Composer: `> composer require --dev tatter/outbox`
9+
1. Install with Composer: `> composer require tatter/outbox`
1010
2. Migrate the database: `> php spark migrate -all`
1111

1212
## Description
@@ -108,3 +108,14 @@ version of the `Email` class with rendered and inlined content from the library:
108108
$email = Outbox::fromTemplate($template);
109109
$email->setTo('[email protected]')->send();
110110
```
111+
112+
### Cascading Templates
113+
114+
Each `Template` may also be entered with a "Parent Template". Parent templates need to have
115+
a `{body}` token which will receive the parsed content from its child. Additional tokens
116+
in the parent template can be entered by defining them in the child.
117+
118+
Cascading templates makes it easy to have a few "layouts" with many different variable
119+
messages for each layout. For example, your app may send both newsletters and receipts
120+
with their own layout (Parent Template) and then a myriad of different custom content
121+
for different types of users.

src/Controllers/Templates.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ public function new($templateId = null): string
5353
$template = is_null($templateId) ? new Template() : $this->model->find($templateId);
5454

5555
return view('Tatter\Outbox\Views\Templates\form', [
56-
'method' => $template->id ? 'Clone' : 'New',
57-
'template' => $template,
56+
'method' => $template->id ? 'Clone' : 'New',
57+
'template' => $template,
58+
'templates' => $this->model->orderBy('name')->findAll(),
5859
]);
5960
}
6061

@@ -111,8 +112,9 @@ public function show($templateId = null): string
111112
public function edit($templateId = null): string
112113
{
113114
return view('Tatter\Outbox\Views\Templates\form', [
114-
'method' => 'Edit',
115-
'template' => $this->getTemplate($templateId),
115+
'method' => 'Edit',
116+
'template' => $this->getTemplate($templateId),
117+
'templates' => $this->model->orderBy('name')->findAll(),
116118
]);
117119
}
118120

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php namespace Tatter\Outbox\Database\Migrations;
2+
3+
use CodeIgniter\Database\Migration;
4+
5+
class AddTemplatesParent extends Migration
6+
{
7+
public function up()
8+
{
9+
$this->forge->addColumn('outbox_templates', [
10+
'parent_id' => ['type' => 'int', 'null' => true, 'after' => 'tokens'],
11+
]);
12+
}
13+
14+
public function down()
15+
{
16+
$this->forge->dropColumn('outbox_templates', 'parent_id');
17+
}
18+
}

src/Database/Seeds/TemplateSeeder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function run()
1313
}
1414

1515
// Prep the parser tokens
16-
$tokens = ['subject', 'title', 'preview', 'main', 'contact', 'unsubscribe'];
16+
$tokens = ['subject', 'title', 'preview', 'body', 'contact', 'unsubscribe'];
1717
$data = [];
1818
foreach ($tokens as $token)
1919
{

src/Entities/Template.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
<?php namespace Tatter\Outbox\Entities;
22

33
use CodeIgniter\Entity;
4+
use Tatter\Outbox\Models\TemplateModel;
45
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
56

67
class Template extends Entity
78
{
89
protected $table = 'outbox_templates';
910
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
1011
protected $casts = [
11-
'tokens' => 'csv',
12+
'tokens' => 'csv',
13+
'parent_id' => '?int',
1214
];
1315

1416
/**
@@ -27,7 +29,15 @@ public function render($data = [], string $styles = null): string
2729
}
2830

2931
// Replace tokens with $data values
30-
$body = service('parser')->setData($data)->renderString($this->attributes['body']);
32+
$body = service('parser')->setData($data, 'raw')->renderString($this->attributes['body']);
33+
34+
// If this has a parent Template then render it with this body
35+
if ($parent = $this->getParent())
36+
{
37+
$data['body'] = $body;
38+
39+
return $parent->render($data, $styles);
40+
}
3141

3242
// Determine styling
3343
$styles = $styles ?? view(config('Outbox')->styles, [], ['debug' => false]);
@@ -38,5 +48,17 @@ public function render($data = [], string $styles = null): string
3848

3949
return (new CssToInlineStyles)->convert($body, $styles);
4050
}
51+
52+
/**
53+
* Returns the parent Template, if set.
54+
*
55+
* @return self|null
56+
*/
57+
public function getParent(): ?self
58+
{
59+
return $this->parent_id
60+
? model(TemplateModel::class)->find($this->parent_id)
61+
: null;
62+
}
4163
}
4264

src/Models/TemplateModel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ class TemplateModel extends Model
1414
protected $skipValidation = true;
1515

1616
protected $allowedFields = [
17-
'name', 'subject', 'body', 'tokens',
17+
'name', 'subject', 'body', 'tokens', 'parent_id',
1818
];
1919
}

src/Views/Templates/form.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ class="form-control"
2424
value="<?= implode(',', $template->tokens) ?>"
2525
/>
2626
</div>
27+
<div class="form-group">
28+
<label for="parent_id">Parent Template</label>
29+
<select name="parent_id">
30+
<option value="">NONE</option>
31+
32+
<?php foreach ($templates as $temp): ?>
33+
<?php if ($temp->id === $template->id): continue; endif; ?>
34+
<option value="<?= $temp->id ?>" <?= $temp->id === $template->parent_id ? 'selected' : '' ?>><?= $temp->name ?></option>
35+
<?php endforeach; ?>
36+
37+
</select>
38+
</div>
2739
<div class="form-group">
2840
<label for="name">Name</label>
2941
<input name="name" type="text" class="form-control" id="name" value="<?= old('name', $template->name) ?>">
@@ -34,7 +46,7 @@ class="form-control"
3446
</div>
3547
<div class="form-group">
3648
<label for="body">Body</label>
37-
<textarea name="body" class="form-control" id="body" rows="30" style="font-family: monospace;"><?= old('body', $template->body) ?></textarea>
49+
<textarea name="body" class="form-control" id="body" rows="25" style="font-family: monospace;"><?= old('body', $template->body) ?></textarea>
3850
</div>
3951
<div class="form-group">
4052
<input name="template_id" type="hidden" value="<?= $template->id ?>">

src/Views/Templates/index.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<tr>
1414
<th scope="col">Name</th>
1515
<th scope="col">Subject</th>
16+
<th scope="col">Parent</th>
1617
<th scope="col">Added</th>
1718
<th scope="col"></th>
1819
</tr>
@@ -23,6 +24,7 @@
2324
<tr>
2425
<td><?= $template->name ?></td>
2526
<td><?= $template->subject ?></td>
27+
<td><?= ($parent = $template->getParent()) ? $parent->name : 'NONE' ?></td>
2628
<td><?= $template->created_at->format('n/j/Y') ?></td>
2729
<td>
2830
<?= anchor('emails/templates/show/' . $template->id, 'View') ?>

tests/templates/ParentTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
use CodeIgniter\Test\CIUnitTestCase;
4+
use CodeIgniter\Test\DOMParser;
5+
use Tatter\Outbox\Entities\Template;
6+
use Tatter\Outbox\Models\TemplateModel;
7+
use Tests\Support\DatabaseTestCase;
8+
9+
class ParentTest extends DatabaseTestCase
10+
{
11+
/**
12+
* @var DOMParser
13+
*/
14+
protected $parser;
15+
16+
/**
17+
* @var Template
18+
*/
19+
protected $parent;
20+
21+
/**
22+
* @var Template
23+
*/
24+
protected $template;
25+
26+
public function setUp(): void
27+
{
28+
$this->resetServices();
29+
parent::setUp();
30+
31+
$this->parser = new DOMParser();
32+
33+
$this->parent = new Template([
34+
'name' => 'Parent Template',
35+
'subject' => 'Parent {subject}',
36+
'body' => '<div>{body}</div><aside>{foobar}</aside>',
37+
'tokens' => ['subject', 'body', 'foobar'],
38+
]);
39+
$this->parent->id = model(TemplateModel::class)->insert($this->parent);
40+
41+
$this->template = new Template([
42+
'name' => 'Test Template',
43+
'subject' => 'Some {subject}',
44+
'body' => '<p>{number}</p>',
45+
'tokens' => ['subject', 'number', 'foobar'],
46+
'parent_id' => $this->parent->id,
47+
]);
48+
$this->template->id = model(TemplateModel::class)->insert($this->template);
49+
}
50+
51+
public function testRenderReturnsParentBody()
52+
{
53+
// Expect the child variable in the parent context with inlined CSS
54+
$expected = '<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">{number}</p>';
55+
$result = $this->template->render();
56+
57+
$this->parser->withString($result);
58+
$this->assertTrue($this->parser->see('{number}', 'p'));
59+
60+
$this->assertStringContainsString($expected, $result);
61+
}
62+
63+
public function testRenderAppliesTokens()
64+
{
65+
$this->parser->withString($this->template->render([
66+
'number' => 'Banana',
67+
'foobar' => 'Orange',
68+
]));
69+
70+
$this->assertTrue($this->parser->see('Banana', 'div'));
71+
$this->assertTrue($this->parser->see('Orange', 'aside'));
72+
}
73+
74+
public function testRenderInlinesStyles()
75+
{
76+
$result = $this->template->render([], 'aside { color: magenta; }');
77+
78+
$this->parser->withString($result);
79+
80+
$this->assertTrue($this->parser->see('magenta'));
81+
}
82+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class TemplateEntityTest extends CIUnitTestCase
1818

1919
public function setUp(): void
2020
{
21+
$this->resetServices();
2122
parent::setUp();
2223

2324
$this->parser = new DOMParser();

0 commit comments

Comments
 (0)