Appearance
Email Template Viewer
Flow ID: AD-53 | Module(s): settings | Complexity: Medium Last Updated: 2026-09-29
Business Context
The email template viewer provides administrators with a browser for previewing all system email templates. Located at /settings/email_views.htm, it renders each hard-coded template with synthetic Greek-market test data so admins can visually verify email layouts, translations, and content before they reach customers.
The viewer also exposes a subject line editor at /settings/email_views/editEmailSubjects.htm that allows per-language customization of email subject lines via the EMAIL_SUBJECTS registry group. This is the primary way admins change email subjects without touching code.
The viewer hard-codes 20 named template views (ecommercen/settings/controllers/AdvEmailViewer.php:166-187). The real runtime dispatcher Adv_mailer can render a slightly different set — the two lists are out of sync, though both resolve against the same template directory (see Two Mail Directories and Known Issues).
API Reference
REST Endpoints
No REST API. Email template viewing is an admin-only tool.
Legacy Admin Routes
| Route | Controller | Method | HTTP | Description |
|---|---|---|---|---|
settings/email_views | AdvEmailViewer | index() | GET | Browse and preview all email templates |
settings/email_views/editEmailSubjects | AdvEmailViewer | editEmailSubjects() | GET/POST | Edit email subject lines per language |
Routes are HMVC auto-routed through the settings module — no entries in application/config/*routes*.php. The HMVC entry point is a thin subclass: application/modules/settings/controllers/Email_views.php (class Email_views extends AdvEmailViewer {}, 5 lines).
RBAC gate: constructor AdvEmailViewer.php:5-15; the check at AdvEmailViewer.php:9 — allowRole([AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN], $this->sessionRoles) → error_401() on mismatch. One gate covers both viewing and editing.
Code Flow
Template Preview (index)
AdvEmailViewer::index() (ecommercen/settings/controllers/AdvEmailViewer.php:160-197)
|
+--> initTestEmailViews() (private, AdvEmailViewer.php:17-24)
| +--> Loads eshop/order_model, eshop/stores_model, encryption library
| +--> loadSampleDataDependencies() (protected no-op hook, AdvEmailViewer.php:39-41 —
| override point for forks to load extra helpers/models; called at the
| end of initTestEmailViews())
|
+--> $data = extendSampleEmailData($this->sampleEmailData()) (AdvEmailViewer.php:164)
| |
| +--> sampleEmailData() (protected, AdvEmailViewer.php:47-149) builds the base
| | sample-data payload (inline, no external fixtures):
| | - Customer scalars (name, email, phone, address)
| | - orderData array with 25+ keys incl. getCurrencyData(sampleCurrencyId())
| | (sampleCurrencyId() default 1, AdvEmailViewer.php:30-33; used at
| | AdvEmailViewer.php:144-145)
| | - store_data, transporter_name, products, loyalty_reward
| | - store (top-level, mirrors orderData['store'] — consumed by
| | order_on_store to preview the customer-selected pickup store's
| | address/phone/opening_hours, #449)
| | - customerData, token, encryption, giftCard,
| | couponRules, giftCardCoupon
| | - $data['emailViews'] = new Template(
| | new Config(APPPATH . '/config/emailViews.json')
| | ) (AdvEmailViewer.php:131-133; only for
| | views' own component resolution — viewer does NOT use
| | layouts/components to choose which templates to render)
| | - externalLang = new ExternalLang($this->language_abbr) (AdvEmailViewer.php:146)
| |
| +--> extendSampleEmailData(array $data): array (protected no-op hook,
| AdvEmailViewer.php:155-158 — override point for forks to add/override
| sample-data keys)
|
+--> Hard-coded $email_views list (20 entries, AdvEmailViewer.php:166-187):
| contact_email, notify_admin, notify_admin_low_stock,
| notify_admin_new_product, order_created, order_on_store, order_update,
| product_review_accept, product_review_reject, reset_password,
| review_for_facebook, review_for_skroutz, review_for_google,
| waiting_list_success, remaining_points, birthday_wishes,
| gift_card, gift_card_inform_customer, blog_comment_accept,
| blog_comment_reject
|
+--> view_content = "{$classView}/email_views"
+--> $this->template_view_admin(...)The admin view (application/views/admin/settings/email_views.php:24) iterates $email_views and renders each via:
php
$this->load->view($this->client_views . '/mail/' . $email_view, $data, true)$this->client_views resolves to "main", not to the deprecated app.php:19 value. Adv_base_controller's constructor overwrites the property unconditionally (Adv_base_controller.php:104) from Template::templateFolder(), which reads mainTemplate.json:3 ("templateFolder": "main") — mainTemplate.json is the file named by $config['clientTemplate'] at app.php:14. AdvEmailViewer inherits this via Admin_c → Adv_admin_controller:29 (parent::__construct()) → Base_c → Adv_base_controller:104, and the view reads the property through MX_Loader::__get() (application/third_party/MX/Loader.php:333-336). So the viewer loads from application/views/main/mail/ — the same directory Adv_mailer uses at runtime. The deprecated app.php:19 value is not consulted on this path. See Two Mail Directories.
Subject Editor (editEmailSubjects)
AdvEmailViewer::editEmailSubjects() (ecommercen/settings/controllers/AdvEmailViewer.php:220-253)
|
+--> Seed empty defaults for contact form keys:
| $contactFormEmptyData = array_map(
| fn ($item) => '',
| array_flip((new FormsConfig())->getContactFormKeys())
| )
|
+--> On POST ($this->input->post('submit')):
| +--> For each admin language ($this->adminLanguages):
| | +--> For each posted field whose name contains $langAbbr:
| | +--> $registryKey = $this->stripLanguageSuffix($postKey, $langAbbr); # AdvEmailViewer.php:228
| | +--> if ($registryKey === null) { continue; } # AdvEmailViewer.php:229-231
| | +--> $this->registry->setValue('EMAIL_SUBJECTS', $registryKey, $postValue, $langAbbr) # AdvEmailViewer.php:233
| +--> Flash success (t('eshop.admin.success.entryedit')), redirect
|
+--> On GET:
| +--> For each admin language:
| | +--> $this->registry->getGroupAsArray('EMAIL_SUBJECTS', $langAbbr)
| | +--> Merge with $contactFormEmptyData as defaults
| +--> Render admin/settings/email_subjects.php with
| inputs named "{key}_{langAbbr}"Domain Layer
No modern domain layer. The email viewer is a pure legacy admin tool.
Templates Directory
Canonical templates live in application/views/main/mail/ (24 files, emailViews.json:3 → templateFolder = "main/mail"). These are what Adv_mailer actually renders at email-send time.
| File | Purpose |
|---|---|
apologize_shipping_delay.php | Delivery-delay apology notice; layout orderInformDelay (emailViews.json:53-55); consumed by sendCustomerInformDelay() (Adv_mailer.php:451) |
birthday_wishes.php | Birthday greetings + optional loyalty points |
blog_comment_accept.php | Blog comment moderation — approved notification |
blog_comment_reject.php | Blog comment moderation — rejected notification |
contact_email.php | Default contact-form email |
contact_email_generated.php | Generic contact-form email via generated form config (fullSampleForm) |
email_products_summary.php | Component included by other templates via componentView('emailProductsSummary') (emailViews.json:75-79) |
gift_card.php | Gift card delivery to recipient |
gift_card_inform_customer.php | Gift card purchase confirmation to buyer |
notify_admin.php | Admin order notification |
notify_admin_low_stock.php | Admin low-stock alert |
notify_admin_new_product.php | Admin new-product notification (inform_new_product() flagged @todo ... BROKEN ATM at Adv_mailer.php:273, method body Adv_mailer.php:274) |
order_created.php | Customer order confirmation |
order_on_store.php | "Your order arrived at the store" |
order_update.php | Order status update (shipping / paid / invoiced) |
product_review_accept.php | Review moderation — approved notification |
product_review_reject.php | Review moderation — rejected notification |
remaining_points.php | Loyalty "you have remaining points" reminder |
reset_password.php | Password reset with encrypted token |
return_form.php | Return-form submission notice; layout returnEmail (emailViews.json:8-10, NEW); consumed by sendReturnFormEmail() (Adv_mailer.php:380); dispatched from Adv_forms.php:136 (shop copy) and :144 (customer copy) |
review_for_facebook.php | Post-purchase review prompt for Facebook |
review_for_google.php | Post-purchase review prompt for Google |
review_for_skroutz.php | Post-purchase review prompt for Skroutz |
waiting_list_success.php | Back-in-stock notification |
Two Mail Directories
There are two mail view directories on disk. Only one has a live reader — both the viewer and the runtime mailer resolve to it, via two independent config files that happen to agree:
| Directory | File Count | Reader |
|---|---|---|
application/views/main/mail/ | 25 | Both Adv_mailer (runtime sends, driven by emailViews.json::templateFolder = "main/mail") and AdvEmailViewer (admin preview, via $this->client_views resolved from mainTemplate.json::templateFolder = "main" — see Code Flow) |
application/views/default/mail/ | 16 | No live code path reads this directory. Adv_mailer assigns the deprecated app.php:19 $config['client_views'] = 'default' value to a property at application/models/Adv_mailer.php:29 (as does the parent Adv_base_model.php:36, for every model on every request) but never reads that property back — it resolves views solely through $this->emailViews (Adv_mailer.php:30). The viewer never touches the config item at all. So the directory itself remains stale/unreferenced regardless. |
This agreement between the two live paths is a coincidence of arithmetic, not a guarantee: the viewer composes {mainTemplate.json::templateFolder} + a hardcoded /mail/ literal (application/views/admin/settings/email_views.php:24, config key clientTemplate at app.php:14), while Adv_mailer uses the whole emailViews.json::templateFolder string ("main/mail", config key emailTemplate at app.php:15). They agree only because "main" + "/mail" == "main/mail". A fork that changes mainTemplate.json:3, repoints clientTemplate, or moves emailViews.json:3 would silently make preview and send resolve to different directories, with no error raised — latent fragility for forks, not a current bug.
Inventory difference between main/mail/ and the stale default/mail/
Present in main/mail/ but not in default/mail/ (8): apologize_shipping_delay, birthday_wishes, blog_comment_accept, blog_comment_reject, contact_email_generated, gift_card_inform_customer, remaining_points, return_form.
This is a stale-directory inventory gap, not a failure mode: since neither the viewer nor Adv_mailer reads default/mail/, none of these files being absent from it has any runtime effect. All 20 templates in the viewer's $email_views list resolve successfully from main/mail/.
Orphan template removed (#97)
The viewer previously listed ask_us_email.php for preview even though it was never dispatched at runtime — no layout entry in application/config/emailViews.json, no caller in Adv_mailer, and no EMAIL_SUBJECTS.ask_us_email registry key. It survived only because AdvEmailViewer::$email_views listed it. Both mail-directory copies and the list entry were removed together in #97 (see Advisable-com/ecommercen#97); its content was a strict subset of contact_email.php, so no replacement was needed.
Templates missing from the viewer's list
The viewer's hard-coded $email_views array (AdvEmailViewer.php:166-187) is missing three layouts that emailViews.json knows about:
contact_email_generated(layoutcontactEmailGenerated)apologize_shipping_delay(layoutorderInformDelay) — file exists inmain/mail/and resolves fine at runtime; simply not offered for previewreturn_form(layoutreturnEmail, NEW) — same: exists inmain/mail/, not offered for preview
The partial email_products_summary is also absent from the list, correctly — it is a component (emailViews.json:75-79), not a standalone preview target.
Subject Registry Keys
Subject strings live in the registry table under the EMAIL_SUBJECTS group, keyed per admin language. Defaults are seeded from application/config/db_default_values.php array email_subject_data (:42, keys L43-151) by database/seeders/InitialSeed.php::emailSubjects() (InitialSeed.php:8569-8580, reggroup='EMAIL_SUBJECTS' at L8576). Edited via AdvEmailViewer::editEmailSubjects() (setValue('EMAIL_SUBJECTS', ...) at AdvEmailViewer.php:209).
| Registry Key | Consumer in application/models/Adv_mailer.php |
|---|---|
contactForm / fullSampleForm | Not consumed by Adv_mailer — read by the contact-forms flow (see CF-29 Contact Forms); call site ecommercen/forms/controllers/Adv_forms.php:62 |
PASSWORD_RESET | recover_password_mail() L75 |
ORDER_COMPLETE | order_complete() L171 |
ORDER_UPDATE | order_has_been_updated() L210 |
WAITING_LIST | sendEmailForWaitingList() L268 |
ORDER_ON_STORE | order_is_on_store() L348 |
USER_REVIEW_ACCEPT | sendUserReviewStatusUpdateEmail() L408 |
USER_REVIEW_REJECT | sendUserReviewStatusUpdateEmail() L408 |
REVIEW_FOR_FACEBOOK | sendCustomerPromptReview() L422 |
REVIEW_FOR_SKROUTZ | sendCustomerPromptReview() L425 |
REVIEW_FOR_GOOGLE | sendCustomerPromptReview() L428 |
CUSTOMER_INFORM_DELAY | sendCustomerInformDelay() L462 |
REMAINING_POINTS | sentRemainingPoints() L510 |
BIRTHDAY_WISHES | sentBirthdayWishes() L543 |
GIFT_CARD | sendGiftCardToEmailRenderer() L589 (view) / L599 (doSend) |
GIFT_CARD_INFORM_CUSTOMER | sendGiftCardToEmailRenderer() L588 (view) / L598 (doSend) |
BLOG_COMMENT_ACCEPT | sendBlogCommentStatusUpdateEmail() L641 |
BLOG_COMMENT_REJECT | sendBlogCommentStatusUpdateEmail() L641 |
The same group is also read in application/views/admin/contact_emails/list.php:89.
Templates with no matching registry key
notify_admin,notify_admin_low_stock,notify_admin_new_product—Adv_mailer::sendAdmin()falls back togetRegistryValue('TITLE', 'SITE', ...)atAdv_mailer.php:743, or uses$data['subject'](set from thesite.emails.inform_new_product.titlelang line atAdv_mailer.php:286).
Email Templates Engine
The viewer instantiates a Template object (AdvEmailViewer.php:131-133) to enable email templates to resolve sub-components via $emailViews->componentView(). For detailed information on template resolution via emailViews.json, the Template / Config system, the complete Adv_mailer method catalog, Adv_mailer internals, SMTP credential management, and the PHPMailer transport pipeline, see SY-24 Email Dispatch System.
Email Transport & SMTP Configuration
For complete information on the Mailer wrapper, SMTP credentials from the registry (EMAIL_CONTACT_FORM, EMAIL_SHOP_MAILER), credential management, and PHPMailer transport internals, see SY-24 Email Dispatch System.
The admin UI for configuring SMTP credentials lives in application/views/admin/settings/email_block.php and ecommercen/settings/controllers/Adv_settings.php:63-183 (SMTP writes :69-76, reads :148-155, validation from :181). There is no MailHog, Mailgun, or SendGrid integration — the platform sends pure SMTP.
Localization
The viewer renders templates using new ExternalLang($this->language_abbr) (the admin's UI language). For detailed information on ExternalLang translation resolution, customer locale precedence, and how the runtime dispatch system handles multi-language email content, see SY-24 Email Dispatch System.
No MUI tables exist for email content — translations live in CI language files via ExternalLang and in registry subject entries.
Dispatch Points
For a complete map of where each template is dispatched from (synchronous callers, asynchronous cron jobs, legacy cronjob controller methods, and the full line numbers), see SY-24 Email Dispatch System.
Architecture
| Component | Path | Purpose |
|---|---|---|
AdvEmailViewer | ecommercen/settings/controllers/AdvEmailViewer.php | Admin controller (253 lines) |
Email_views | application/modules/settings/controllers/Email_views.php | HMVC entry point, thin subclass of AdvEmailViewer |
Adv_mailer | application/models/Adv_mailer.php | Runtime mailer (828 lines, Adv_base_model) |
Mailer | ecommercen/core/Mailer.php | PHPMailer wrapper (SMTP only) |
Advisable\Template\Template | src/Template/Template.php | Resolves layouts / components via emailViews.json |
Advisable\Template\Config | src/Template/Config.php | Loads template JSON config |
| Email views config | application/config/emailViews.json | Layout and component mappings (80 lines, 23 layouts) |
| Default subject seeds | application/config/db_default_values.php:42 (keys L43-151) | Baseline EMAIL_SUBJECTS values |
| Subject seeder | database/seeders/InitialSeed.php:8569-8580 | Inserts defaults into registry |
FormsConfig | application/modules/forms/libraries/FormsConfig.php | Empty subclass of AdvFormsConfig |
AdvFormsConfig | ecommercen/forms/libraries/AdvFormsConfig.php | Loads application/config/contact_forms.php, exposes getContactFormKeys(), getContactFormKeysDropDown(), getEmailTemplateView(), etc. — not a list of email templates |
ExternalLang | ecommercen/libraries/ExternalLang.php | Per-locale string resolver passed to every template |
| Admin menu | application/config/admin_menu.php:955-968 | Two entries (email_views L955-961, editEmailSubjects L962-968), gated on [AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN] |
| Viewer template | application/views/admin/settings/email_views.php | Iterates $email_views and loads each (:24) |
| Subject editor view | application/views/admin/settings/email_subjects.php | Renders per-language subject inputs |
Role of FormsConfig
FormsConfig is purely a contact-form configuration accessor. AdvEmailViewer::editEmailSubjects() uses it only to seed empty defaults:
php
$contactFormEmptyData = array_map(
fn ($item) => '',
array_flip((new FormsConfig())->getContactFormKeys())
);It does not enumerate email templates.
Subject Editor Flow
AdvEmailViewer::editEmailSubjects() (ecommercen/settings/controllers/AdvEmailViewer.php:199-229):
- Seeds empty defaults for contact-form keys via
FormsConfig::getContactFormKeys(). - On POST (
$this->input->post('submit')):- For each admin language in
$this->adminLanguages:- For each posted field whose name contains
$langAbbr:- Strips the language suffix via
rtrim($postKey, "_$langAbbr")atAdvEmailViewer.php:208— see Known Issues #5. - Calls
$this->registry->setValue('EMAIL_SUBJECTS', $postKey, $postValue, $langAbbr)atAdvEmailViewer.php:209.
- Strips the language suffix via
- For each posted field whose name contains
- Flashes
t('eshop.admin.success.entryedit')and redirects.
- For each admin language in
- On GET:
- For each admin language, loads
$this->registry->getGroupAsArray('EMAIL_SUBJECTS', $langAbbr). - Merges with the empty contact-form defaults.
- Renders
admin/settings/email_subjects.phpwith inputs named{key}_{langAbbr}.
- For each admin language, loads
Data Model
No dedicated tables. Email subjects are stored in the registry table under the EMAIL_SUBJECTS group with per-language entries. See Subject Registry Keys for the full list.
| Registry Group | Key Pattern | Description |
|---|---|---|
EMAIL_SUBJECTS | {template_name_or_const} | Subject line for each email template, per language |
EMAIL_CONTACT_FORM | SMTP credentials (8 keys) | Transport for contact-form and return-form emails |
EMAIL_SHOP_MAILER | SMTP credentials (8 keys) | Transport for all other emails |
Configuration
- Admin menu:
application/config/admin_menu.php:955-968— two entries (settings/email_viewsL955-961,settings/email_views/editEmailSubjectsL962-968) gated on[AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN]. - Template config:
application/config/emailViews.json. - Email template file name:
application/config/app.php:15(emailTemplate = 'emailViews.json'). - Client views setting:
application/config/app.php:19($config['client_views'] = 'default') — marked@deprecatedin-source (app.php:18). The value is read into properties:ecommercen/core/models/Adv_base_model.php:36(parent of every model, runs on every request) andapplication/models/Adv_mailer.php:29both assign it, and the separately-@deprecated, zero-callerpreviewOrder_cart()helper (ecommercen/helpers/cart_helper.php:103) reads it too. None of these reads ever resolves a rendered view path in practice —previewOrder_cart()has no callers, and neitherAdv_base_modelnorAdv_mailerreads its own$client_viewsproperty back. The onlyclient_viewsproperty that reaches a view is the controller property, andAdv_base_controller.php:104overwrites that one unconditionally frommainTemplate.jsonbefore any view runs. - Role constants:
application/config/constants.php:99, 101(AUTH_ROLE_ADVISABLE = 1,AUTH_ROLE_ADMIN = 3).
Required roles: AUTH_ROLE_ADVISABLE and AUTH_ROLE_ADMIN. The constructor at AdvEmailViewer.php:5-15 calls allowRole([AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN], $this->sessionRoles) → error_401() on mismatch (gate itself at L9). The admin menu enforces the same set. There is no separation between viewing and editing — both operations share one role gate.
Client Extension Points
- Custom email templates: Client repos can add templates under
application/views/main/mail/and wire them intoemailViews.json+Adv_mailer. Client repos should usecustom/for new modern code, but the legacy mail pipeline still lives underapplication/. - Override controller: Extend
AdvEmailViewerviaapplication/modules/settings/controllers/Email_views.phpto add client-specific preview entries. - Additional subjects: Register client-specific subjects under the
EMAIL_SUBJECTSregistry group per language. - Sample currency override (
#422/ 4.115.0): OverridesampleCurrencyId()in a client subclass to preview emails with a different currency. Base returns1(AdvEmailViewer.php:30-33); used atAdvEmailViewer.php:144-145viagetCurrencyData($this->sampleCurrencyId()). Regression-guarded bytests/Legacy/Settings/AdvEmailViewerHooksTest.php(see Tests). - Sample data override (
#426): OverridesampleEmailData(): array(AdvEmailViewer.php:47-149) to replace the entire base sample-data payload previewed in the email viewer, or overrideextendSampleEmailData(array $data): array(protected no-op hook,AdvEmailViewer.php:155-158) to add or override individual sample-data keys without duplicating the base payload.index()composes them as$data = $this->extendSampleEmailData($this->sampleEmailData())(AdvEmailViewer.php:164). - Extra sample-data dependencies (
#426): OverrideloadSampleDataDependencies(): void(protected no-op hook,AdvEmailViewer.php:39-41, called at the end of the privateinitTestEmailViews()atAdvEmailViewer.php:23) to load extra helpers/models needed by a customizedsampleEmailData()/extendSampleEmailData().
Business Rules
- Preview-only: The viewer renders emails with synthetic test data — it does not send actual emails.
- Per-language subjects: Subjects are stored per language in the registry, allowing full multi-language customization.
- Sample data: Test data is Greek-market (names, addresses); it is returned by the overridable
sampleEmailData()seam (AdvEmailViewer.php:47-149), not inline inindex()orinitTestEmailViews(). - No in-browser template editing: The viewer shows rendered output only. Template HTML is edited in code, not through the admin UI.
- Customer locale wins: At dispatch, the customer's language is preferred over the admin's language for both subject and body.
- SMTP only: Mail is sent via PHPMailer over SMTP using one of two credential sets (
EMAIL_CONTACT_FORMorEMAIL_SHOP_MAILER).
Known Issues & Security Gaps
getDefaultSmtpCredentials()docblock says "3 options" but only 2 are accepted.ecommercen/helpers/registry_helper.php:35— the docblock states "There are 3 options: EMAIL_CONTACT_FORM, EMAIL_SHOP_MAILER" but lists only 2; the guard at:41throws unless the value is exactlyEMAIL_CONTACT_FORMorEMAIL_SHOP_MAILER. This is a stale doc-comment defect only — there is no functional gap, and only two purposes actually exist.sentBirthdayWishes()logs the wrong email type.Adv_mailer.php:542passesemailType='REMAINING_POINTS'(apparent copy/paste fromsentRemainingPoints()) while the subject used isBIRTHDAY_WISHES(L543). Birthday emails are recorded incustomer_message_historyunder typeREMAINING_POINTSinstead ofBIRTHDAY_WISHES.inform_new_product()flagged BROKEN yet still wired.Adv_mailer.php:273carries a/* @todo refactor this is client custom and should not be called BROKEN ATM */comment directly above the method (body at L274), yet it is still invoked fromAdv_products_admin.php:280, 736.- Viewer
$email_viewslist incomplete.AdvEmailViewer.php:166-187omits 3 real standalone templates known toemailViews.json:contact_email_generated,apologize_shipping_delay,return_form. Admins cannot preview any of the three. - Blog-comment emails logged as
USER_REVIEW.sendBlogCommentStatusUpdateEmail()passesemailType='USER_REVIEW'(Adv_mailer.php:640), identical to the review flow (L407), so blog-comment moderation emails are misclassified in customer history (minor). - No CSRF token on subject-editor POST.
AdvEmailViewer::editEmailSubjects()POST atAdvEmailViewer.php:225carries no synchronizer token, and$config['csrf_protection'] = false(application/config/config.php:143), so an attacker-hosted auto-submitting cross-origin POST can rewrite everyEMAIL_SUBJECTSregistry value for a visiting ADVISABLE/ADMIN — those subject lines then ship on real customer email. Mitigated only by the RBAC gate at constructor (line 9).
Tests
tests/Legacy/Settings/AdvEmailViewerHooksTest.php(245 lines, 11#[Test]methods) — exercises three client-override seams:sampleCurrencyId()(#422,L45-54,L57-65,L68-89),sampleEmailData()(#426),extendSampleEmailData()(#426), andloadSampleDataDependencies()(#426). Asserts each method exists, isprotected, and is overridable. UsesnewInstanceWithoutConstructor()on an anonymous subclass to avoid theAdmin_cbootstrap.tests/Legacy/Settings/AdvEmailViewerSubjectSuffixTest.php(#61 regression suite) — exercises thestripLanguageSuffix()protected seam: asserts the method exists, isprotected, returns?string(L103-113), and is fork-overridable (L288-308).tests/Unit/Domains/Customer/Customer/PasswordResetServiceTest.phpandtests/Integration/Domains/Customer/Customer/PasswordResetServiceIntegrationTest.php— cover thePasswordResetServicecaller contract (mocksrecover_password_mail, asserts unknown addresses do not call the mailer). These verify the AD-53 caller contract, not the viewer itself.
Coverage gaps: no dedicated tests exercise AdvEmailViewer::index() rendering, the $email_views list, emailViews.json template resolution, getDefaultSmtpCredentials(), the Adv_mailer send pipeline (send()/sendAdmin()/doSend()), or editEmailSubjects()'s subject-writing behavior.
Related Flows
- AD-13 Settings — general settings admin where SMTP credentials (
EMAIL_CONTACT_FORM,EMAIL_SHOP_MAILER) are configured - AD-52 Review Moderation — dispatches
product_review_accept/product_review_rejectviasendUserReviewStatusUpdateEmail() - SY-11 Review Reminders — sends
review_for_facebook/_skroutz/_googleviasendCustomerPromptReview() - SY-15 Delivery Delay — sends
apologize_shipping_delay(orderInformDelay layout) apology emails viasendCustomerInformDelay() - SY-24 Email Dispatch — the runtime pipeline (
Adv_mailer+Mailer+ PHPMailer) that delivers the templates previewed here - AD-03 Order Management Admin — order emails are the highest-volume dispatchers
- CF-10 Customer Auth —
PasswordResetServiceis the live caller ofrecover_password_mail() - CF-29 Contact Forms — dispatches
contact_email/contact_email_generated/return_formviasendContactFormEmail()/sendReturnFormEmail()