# PRD: Email Template Module — Bug Fixes

**Date:** 2026-04-29  
**Module:** Email Template (`manages/email-templates`)  
**Controller:** `app/Http/Controllers/Manages/EmailTemplateController.php`  
**Model:** `app/Models/Masters/SiteTemplate.php`  
**View:** `resources/views/admin/manages/email-templates/`  
**JS:** `public/assets/js/email-template.js`

---

## Failing Test Cases

| ID | Priority | Scenario | Status |
|----|----------|----------|--------|
| ETC-007 | Medium | Back to Administration button navigates correctly | Fail |
| ETC-016 | High | Attachments button opens attachment popup | Fail |
| ETC-017 | Medium | Copy button opens copy flow with template data | Fail |
| ETC-029 | Medium | Template Title max length (256+ chars) shows validation | Fail |
| ETC-030 | Medium | Email Subject max length (256+ chars) shows validation | Fail |
| ETC-031 | Medium | Special characters in Template Title save correctly | Fail |
| ETC-032 | Medium | Special characters in Email Subject save/display correctly | Fail |
| ETC-033 | High | Duplicate Template Title is rejected | Fail |
| ETC-034 | High | Add button creates template with valid data | Fail |
| ETC-035 | High | Saved template appears in correct category tab | Fail |
| ETC-036 | Medium | Track Opens checkbox toggles on/off and saves | Fail |
| ETC-037 | Medium | Alert on Open checkbox toggles on/off and saves | Fail |
| ETC-038 | Medium | Auto Edit Mode checkbox toggles on/off and saves | Fail |
| ETC-039 | Medium | Tooltips missing for all 4 checkbox options | Fail |
| ETC-039 | Medium | Track Visitors checkbox toggles on/off and saves | Fail |
| ETC-050 | High | Full create-and-preview end-to-end workflow | Fail |

---

## Fix 1 — ETC-007: Back to Administration Button Missing

### Root Cause
The `index.blade.php` for Email Templates has no back navigation button. The test expects a "Back to Administration" link that navigates to the administration/manages landing page.

### Affected File
- `resources/views/admin/manages/email-templates/index.blade.php`

### Fix
Add a back button in the `index.blade.php` above the project-box, pointing to `window.history.back()` (consistent with how other module back buttons work, e.g. User Activity History).

```blade
{{-- Add immediately inside @section('content'), before <div class="project-box"> --}}
<div class="col-12">
    <div class="back-section mb-2">
        <button type="button" class="btn back-btn waves-effect" onclick="window.history.back()">
            <i class="{{ \Config::get('settings.icon_back') }}"></i> Back to Administration
        </button>
    </div>
    ...
</div>
```

---

## Fix 2 — ETC-016: Attachments Button Does Not Open Popup

### Root Cause
The Attachments button in `index.blade.php` is conditionally rendered only when `$permission['FileForm']` is true. This permission is loaded via `Permission::checkModulePermissions(['permissionForm', 'FileForm'], 'EmailTemplateController')`. If `FileForm` is not registered in the permission system for the current user's role, the button is hidden entirely — the user sees no attachment button and cannot click it.

The backend (route `manages.email-templates.file-form`, method `FileForm()`, modal form `__formUiGenerationFiles()`) is fully implemented.

### Affected Files
- `app/Http/Controllers/Manages/EmailTemplateController.php` (`index()`)
- `resources/views/admin/manages/email-templates/index.blade.php`

### Fix
In `index()`, ensure the `FileForm` key is always present in `$this->_data['permission']` with a safe default, and fall back to the general `edit` permission if `FileForm` is not explicitly configured:

```php
// In index(), after merging permissions:
$this->_data['permission']['FileForm'] = $this->_data['permission']['FileForm']
    ?? $this->_data['permission']['edit']
    ?? false;
```

Additionally, the Attachments `<a>` tag uses `show-modal-xl` — confirm this CSS class is handled in `common.js` the same way as `show-modal` and `show-modal-lg`.

---

## Fix 3 — ETC-017: Copy Button Does Not Pass Selected Template ID

### Root Cause
When the user clicks "Copy" on a template row, a modal opens showing a dropdown (step 1, `type=copy` without `copy_from`). The modal renders a "Copy & Create" anchor button generated in `__formUiGeneration()`:

```php
'<a href="' . route($this->_routePrefix . '.create', ['type' => 'copy', 'copy_from']) . '"
    class="... copy-email-template-btn">Copy & Create</a>',
```

The PHP syntax `['type' => 'copy', 'copy_from']` produces `copy_from=` with no value — it's a numeric-keyed array entry, not a named param. More critically, there is **no JavaScript** that reads the selected dropdown value (`#copy_from_template_select`) and injects it into the `copy-email-template-btn` href before navigation. Clicking "Copy & Create" always sends an empty `copy_from`, causing the copy flow to restart instead of pre-filling the form.

### Affected Files
- `app/Http/Controllers/Manages/EmailTemplateController.php` (`__formUiGeneration()`)
- `public/assets/js/email-template.js`

### Fix A — Controller: Fix the button href to include a placeholder parameter
In `__formUiGeneration()`, change the "Copy & Create" button's href so the JS can update it:

```php
'<a href="' . route($this->_routePrefix . '.create', ['type' => 'copy', 'copy_from' => '']) . '"
    class="' . \Config::get('view.buttons.primary') . ' show-modal-lg copy-email-template-btn">Copy & Create</a>',
```

### Fix B — JS: Intercept click and inject selected ID
In `email-template.js`, add a delegated handler that updates the href before navigation:

```javascript
$(document).on('click', '.copy-email-template-btn', function (e) {
    e.preventDefault();
    var selectedId = $('#copy_from_template_select').val();
    if (!selectedId) {
        alert('Please select a template to copy from.');
        return;
    }
    var url = new URL($(this).attr('href'), window.location.origin);
    url.searchParams.set('copy_from', selectedId);
    window.location.href = url.toString();
});
```

---

## Fix 4 — ETC-029 & ETC-030: No Client-Side Length Enforcement on Title/Subject

### Root Cause
Both `template_title` and `subject` fields define help text with character limits, but neither has a `maxlength` HTML attribute. Additionally, `template_title` is absent from server-side validation rules in `__formPost()`:

```php
// Current rules — template_title is missing!
$validationRules = [
    'subject'          => 'required|max:255',
    'template_content' => 'required',
];
```

A user can submit a 1000-character title and it will save silently. There is also no client-side browser enforcement to prevent typing beyond the limit.

### Affected Files
- `app/Http/Controllers/Manages/EmailTemplateController.php` (`__formPost()`, `__formUiGeneration()`)

### Fix A — Controller: Add `template_title` to validation

```php
protected function __formPost(Request $request, $id = 0)
{
    $this->validate($request, [
        'template_title'   => 'required|max:255',
        'subject'          => 'required|max:255',
        'template_content' => 'required',
    ]);

    // ... rest of method unchanged
}
```

### Fix B — Controller: Add `maxlength` attributes and correct help text

In `__formUiGeneration()`, update both field definitions:

```php
'template_title' => [
    'type'   => 'text',
    'label'  => 'Template Title',
    'help'   => 'Maximum 255 characters',          // was: 'Maximum 50 characters'
    'attributes' => [
        'required'  => true,
        'maxlength' => 255,                         // add this
    ],
    'value' => isset($data->template_title) ? $data->template_title : '',
    'width' => $fieldWidth,
],

'subject' => [
    'type'   => 'text',
    'label'  => 'Email Subject',
    'help'   => 'Maximum 255 characters',
    'attributes' => [
        'required'  => true,
        'id'        => 'subject_id',
        'maxlength' => 255,                         // add this
    ],
    'value' => isset($data->subject) ? $data->subject : '',
    'width' => $fieldWidth,
],
```

---

## Fix 5 — ETC-031 & ETC-032: Special Characters in Title/Subject

### Root Cause
With `template_title` absent from validation (see Fix 4), any character passes through unchecked. For `subject`, the rule `max:255` allows all characters including special ones. The failing concern is about display and save correctness.

ETC-031 test data `Welcome_Email #1 - Merchant!` and ETC-032 test data `Your Application → Approved! #123` should be accepted.

The `subject` field is rendered in the listing via `{{ $dd->subject }}` (Blade auto-escapes), and `template_title` via `{{ $dd->template_title }}`. Blade escaping is correct.

The only risk is if a developer accidentally adds an overly restrictive regex in Fix 4. Ensure the `template_title` validation rule contains **no regex** — only `required|max:255`.

### Fix
- No special character restriction should be added to either field's validation rule.
- Confirm the database connection and table charset is `utf8mb4` to handle characters like `→` (U+2192) or other multi-byte characters.

The database migration should ensure:
```sql
-- Confirm via: SHOW CREATE TABLE site_templates;
-- Expected: DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
```

---

## Fix 6 — ETC-033: Duplicate Template Title Not Rejected

### Root Cause
`__formPost()` has no uniqueness check on `template_title`. The `store()` method generates a unique `template_name` slug (URL key) via `Helper::getUniqueSlug()`, but allows multiple templates to share the same `template_title`. The test expects creating "Quick Email Sample" a second time to be rejected.

### Affected File
- `app/Http/Controllers/Manages/EmailTemplateController.php` (`__formPost()`)

### Fix
Add a per-company, case-insensitive duplicate check after validation, scoped to the same `template_type` (Email = 1):

```php
protected function __formPost(Request $request, $id = 0)
{
    $this->validate($request, [
        'template_title'   => 'required|max:255',
        'subject'          => 'required|max:255',
        'template_content' => 'required',
    ]);

    $duplicate = \App\Models\Masters\SiteTemplate::whereRaw('LOWER(template_title) = LOWER(?)', [$request->template_title])
        ->where('company_id', \Auth::user()->company_id)
        ->where('template_type', 1)
        ->whereNull('deleted_at')
        ->when($id, fn($q) => $q->where('id', '!=', $id))
        ->exists();

    if ($duplicate) {
        return redirect()->back()
            ->withInput()
            ->withErrors(['template_title' => 'A template with this title already exists.']);
    }

    try {
        $input    = $request->all();
        $response = $this->_model->store($input, $id, $request);

        if (in_array($response['status'], [200, 201])) {
            return redirect()
                ->route($this->_routePrefix . '.index', ['gr' => $response['data']['template_category']])
                ->with('success', $response['message']);
        }
        return redirect()->back()->with('error', $response['message']);
    } catch (\Exception $e) {
        \App\Models\ErrorLog::Log($e);
        return redirect()->back()->with('error', $e->getMessage());
    }
}
```

> Note: This also wraps the store call in try/catch and consolidates the method (replacing the existing one at line 633).

---

## Fix 7 — ETC-034 & ETC-035: Template Creation Fails / Wrong Tab After Save

### Root Cause (ETC-034)
`template_title` is not in validation rules (fixed in Fix 4), so submitting without a title would proceed to `store()` where `Helper::getUniqueSlug('')` generates an empty or collision-prone slug. The model `create()` can produce an invalid record.

### Root Cause (ETC-035)
After a successful save, the redirect is:
```php
return redirect()->route($this->_routePrefix . '.index', ['gr' => $response['data']['template_category']]);
```
`$response['data']` is the created model object. This should work correctly as `template_category` is saved in `store()`. This fix is automatically resolved once ETC-034 is fixed (template saves correctly, `template_category` is populated).

### Fix
Fix 4 (adding `template_title` to validation) and Fix 6 (rewriting `__formPost()`) together resolve both ETC-034 and ETC-035. No additional changes needed.

---

## Fix 8 — ETC-036, ETC-037, ETC-038, ETC-039 (Checkbox): Toggle Off Does Not Persist

### Root Cause — Checkbox Unchecking on Update
In `SiteTemplate::store()`, the checkbox block only runs when the `email_template_checkbox` array is present in input:

```php
if (isset($input['email_template_checkbox']) && is_array($input['email_template_checkbox'])) {
    $input['track_opens']    = 0;
    $input['alert_on_open']  = 0;
    $input['auto_edit_mode'] = 0;
    $input['track_visitors'] = 0;
    // set checked ones to 1 ...
}
// No ELSE branch
```

When **all** checkboxes are unchecked, HTML forms do not submit the checkbox field at all — `email_template_checkbox` is absent from `$input`. The `if` block is skipped, so `track_opens`, etc. are not included in `$input`, and `$data->update($input)` leaves them at their previous values. The user's "uncheck" action has no effect on update.

### Root Cause — Null Safety in `getSecuritesAttribute`
When creating a new template (`$id` is empty), `$data` may be null after `extract($this->_data)`. The call `$this->_model->getSecuritesAttribute($data)` then crashes on `$data->track_opens`:

```php
public function getSecuritesAttribute($data)
{
    if ($data->track_opens == 1) { ... }  // Fatal if $data is null
```

### Root Cause — Missing Tooltips (ETC-039)
The `email_template_checkbox` field's options array has no tooltip/title text:
```php
public $email_template_checkbox = [
    1 => ['id' => 1, 'name' => 'Track Opens'],
    2 => ['id' => 2, 'name' => 'Alert on Open'],
    3 => ['id' => 3, 'name' => 'Auto Edit Mode'],
    4 => ['id' => 4, 'name' => 'Track Visitors'],
];
```
No tooltip icons or `title` attributes are rendered next to the checkbox labels.

### Affected Files
- `app/Models/Masters/SiteTemplate.php` (`store()`, `getSecuritesAttribute()`, `$email_template_checkbox`)
- `app/Http/Controllers/Manages/EmailTemplateController.php` (`__formUiGeneration()`)

### Fix A — `SiteTemplate::store()`: Add ELSE clause to reset checkboxes

```php
// In store(), replace the checkbox block:
if (isset($input['email_template_checkbox']) && is_array($input['email_template_checkbox'])) {
    $input['track_opens']    = 0;
    $input['alert_on_open']  = 0;
    $input['auto_edit_mode'] = 0;
    $input['track_visitors'] = 0;

    foreach ($input['email_template_checkbox'] as $etc) {
        switch ($etc) {
            case '1': $input['track_opens']    = 1; break;
            case '2': $input['alert_on_open']  = 1; break;
            case '3': $input['auto_edit_mode'] = 1; break;
            case '4': $input['track_visitors'] = 1; break;
        }
    }
    unset($input['email_template_checkbox']);
} else {
    // All checkboxes unchecked — explicitly reset all to 0
    $input['track_opens']    = 0;
    $input['alert_on_open']  = 0;
    $input['auto_edit_mode'] = 0;
    $input['track_visitors'] = 0;
}
```

### Fix B — `SiteTemplate::getSecuritesAttribute()`: Null guard

```php
public function getSecuritesAttribute($data)
{
    $email_template_checkbox = [];
    if (!$data) {
        return $email_template_checkbox;
    }
    if ($data->track_opens == 1)    { $email_template_checkbox[] = 1; }
    if ($data->alert_on_open)       { $email_template_checkbox[] = 2; }
    if ($data->auto_edit_mode)      { $email_template_checkbox[] = 3; }
    if ($data->track_visitors)      { $email_template_checkbox[] = 4; }
    return $email_template_checkbox;
}
```

### Fix C — Tooltips: Add `tooltip` to each checkbox option in `$email_template_checkbox`

In `SiteTemplate.php`, update `$email_template_checkbox` to include tooltip descriptions:

```php
public $email_template_checkbox = [
    1 => ['id' => 1, 'name' => 'Track Opens',     'tooltip' => 'Track when the recipient opens this email.'],
    2 => ['id' => 2, 'name' => 'Alert on Open',   'tooltip' => 'Send an alert notification when the email is opened.'],
    3 => ['id' => 3, 'name' => 'Auto Edit Mode',  'tooltip' => 'Automatically open the template in edit mode when selected.'],
    4 => ['id' => 4, 'name' => 'Track Visitors',  'tooltip' => 'Track link clicks and visitor activity within the email.'],
];
```

In `__formUiGeneration()`, the `email_template_checkbox` field should render tooltip icons next to each label. The Blade component for checkbox type must be updated to render a `<i class="..." data-toggle="tooltip" title="...">` icon when a `tooltip` key is present in the option. This is a Blade component change in the form-controls partial.

---

## Fix 9 — ETC-050: End-to-End Create and Preview Workflow

### Root Cause
This is a composite test that exercises the full flow: Add New Template → fill all fields → Insert field → Save → Open created template → Click Preview.

The Preview action is already implemented: clicking the preview icon on a template row calls `route('.edit', ['email_template' => $id, 'type' => 'show'])`, which renders a read-only HTML preview modal.

**ETC-050 fails because ETC-034 fails** — if the template does not save successfully (due to missing `template_title` validation), the template never appears in the listing, and the preview step cannot be reached.

### Fix
Resolves automatically once Fix 4 (validation), Fix 6 (duplicate check + `__formPost` rewrite), and Fix 7 (category tab redirect) are applied. No additional code changes required.

---

## Implementation Checklist

| Fix | File(s) | Status |
|-----|---------|--------|
| 1. Back button (ETC-007) | `index.blade.php` | TODO |
| 2. Attachments permission fallback (ETC-016) | `EmailTemplateController::index()` | TODO |
| 3. Copy JS handler (ETC-017) | `email-template.js`, `__formUiGeneration()` | TODO |
| 4. maxlength + title validation (ETC-029, 030) | `__formPost()`, `__formUiGeneration()` | TODO |
| 5. Special chars (ETC-031, 032) | Validate UTF-8 charset; no code change | TODO |
| 6. Duplicate title check (ETC-033) | `__formPost()` rewrite | TODO |
| 7. Template creation/tab (ETC-034, 035) | Resolved by Fix 4 + Fix 6 | TODO |
| 8a. Checkbox uncheck persist (ETC-036–039) | `SiteTemplate::store()` | TODO |
| 8b. Null guard (ETC-036–039) | `SiteTemplate::getSecuritesAttribute()` | TODO |
| 8c. Tooltips (ETC-039) | `$email_template_checkbox`, form-controls Blade | TODO |
| 9. End-to-end (ETC-050) | Resolved by Fix 4 + Fix 6 | TODO |
