Skip to content

Allow Message Templates to be tagged, via the message_admin extension - #36489

Open
mattwire wants to merge 4 commits into
civicrm:masterfrom
mattwire:tag-message-templates
Open

Allow Message Templates to be tagged, via the message_admin extension#36489
mattwire wants to merge 4 commits into
civicrm:masterfrom
mattwire:tag-message-templates

Conversation

@mattwire

@mattwire mattwire commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Depends on both #36488 (Angular editor default for User-Driven templates) and #36492 (the shared crmEntityTags component this uses for the list widget) - #36492 is independent of #36487/#36488, so this branch just contains both of those plus this commit. Only the last commit here is new; please review that one.

Message templates have never supported tagging. CiviCRM's tagging system is entity-agnostic (civicrm_entity_tag.entity_table/entity_id is a dynamic FK), and the only thing missing for MessageTemplate was an entry in the tag_used_for option group, so this adds one permanently, the same way Contact/Activity/Case/File are registered.

The tagging UI itself is added only to the message_admin extension (Administer > Communications > Message Templates), not to the classic CRM_Admin_Form_MessageTemplates form:

  • An inline "Tags" dropdown is added to every row of the User-Driven list table, using the shared <crm-entity-tags> component from Extract crmSearchAdminTags into a shared crmEntityTags component #36492, so any user-driven template - including document (.docx/.odt) uploads, which have no other tagging path - can be tagged directly from the list. System Workflow templates don't get this for now.
  • A Tags field is added to the extension's own Angular template editor (Edit.html/Edit.js), for User-Driven templates only. Tags are saved via the generic EntityTag.replace action, sequenced after the record itself is created/updated since a brand-new record's id doesn't exist until then.

Test plan

  • Administer > Communications > Tags: confirm "Message Templates" appears as a "Used For" option regardless of whether message_admin is enabled.
  • User-Driven Messages tab: tag/untag a template inline from the list widget; confirm it persists on reload.
  • "Add template" - set tags on a brand-new template, save, confirm they persist.
  • Edit an existing user-driven template's Tags field, save, reload, confirm persistence.
  • A document-upload template can still only be tagged via the list's inline widget (no Tags field in the classic form) - confirm that still works.
  • If SearchKit is enabled, confirm MessageTemplate shows a generic "Add/Remove Tags" bulk task in a SearchKit display built on it.

@civibot

civibot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🤖 Thank you for contributing to CiviCRM! ❤️ We will need to test and review this PR. 👷

Introduction for new contributors...
  • If this is your first PR, an admin will greenlight automated testing with the command ok to test or add to whitelist.
  • A series of tests will automatically run. You can see the results at the bottom of this page (if there are any problems, it will include a link to see what went wrong).
  • A demo site will be built where anyone can try out a version of CiviCRM that includes your changes.
  • If this process needs to be repeated, an admin will issue the command test this please to rerun tests and build a new demo site.
  • Before this PR can be merged, it needs to be reviewed. Please keep in mind that reviewers are volunteers, and their response time can vary from a few hours to a few weeks depending on their availability and their knowledge of this particular part of CiviCRM.
  • A great way to speed up this process is to "trade reviews" with someone - find an open PR that you feel able to review, and leave a comment like "I'm reviewing this now, could you please review mine?" (include a link to yours). You don't have to wait for a response to get started (and you don't have to stop at one!) the more you review, the faster this process goes for everyone 😄
  • To ensure that you are credited properly in the final release notes, please add yourself to contributor-key.yml
  • For more information about contributing, see CONTRIBUTING.md.
PR commands & links...
  • /rebase <branch-name> will rebase your branch and change the base of the PR.
  • /squash will combine all commits (keeping only the first commit messsage).
  • /port <branch-name> will create a copy of this PR against a different branch.
  • /lintroll will automatically fix linting errors, amending commits as needed.
  • retest this please will rerun the tests and rebuild the demo site.
  • 📖 Review standards
  • 🗒️ Review template (brief or verbose)

➡️ Online demo of this PR 🔗

…rkflow records

The message_admin extension's Angular editor (ang/crmMsgadm/Edit.js and friends) has so far only ever been reachable for System Workflow templates, which always have a reserved-default "Original" revision and curated example data tied to their workflow_name. That assumption is baked into a few places that will need to hold up once the editor is opened for User-Driven templates too (a follow-up change), where neither of those things exist:

- "Show diff" always diffed against $ctrl.original, so opening it for a record with no such thing threw trying to read a field off undefined. The button is now guarded on a new $ctrl.hasDiffBase(), so it hides instead of crashing.
- The "Original" tab had no visibility condition at all (unlike "Draft", which correctly checks $ctrl.hasDraft()) - it was always rendered even though $ctrl.hasRevType('original') already exists precisely to guard this.
- "Open preview" unconditionally required curated example data tied to a workflow name. It now falls back to the generic contact-only example CiviCRM ships (Civi\WorkflowMessage\GenericWorkflowMessage) when there's no workflow_name, which is enough to render plain contact tokens - and, as a side effect, also surfaces the existing (previously unreachable) "adhoc example" JSON editor in the preview dialog, letting a value be injected for the token renderer to use.

Also fixed while testing this end-to-end - both apply regardless of workflow_name, so aren't specific to the non-workflow scenario above:

- The "Open large editor" modal offered its Token picker even when opened in "Show diff" mode, which doesn't make sense there - it's a straight comparison of two already-rendered versions of the content, with nothing to insert a token into. It's now hidden for that dialog only, via a new isDiff flag on the dialog's model, leaving it in place for the regular (non-diff) "Open large editor" case.
- WorkflowTranslated.html's table body columns didn't match its header row order (Locale's data cell was rendered after the actions cell, not before it, so it displayed misaligned).
…itor

The message_admin extension's Angular editor (Administer > Communications > Message Templates > User-Driven Messages tab) has so far only ever been reachable for System Workflow templates - User-Driven templates were always edited via the classic CRM_Admin_Form_MessageTemplates form. This routes User-Driven templates through the Angular editor by default too, except for templates with an uploaded document (.docx/.odt), which keep using the classic form since the Angular editor has no upload-document equivalent yet - ListCtrl.js now flags each record with _hasDocument (via a chained EntityFile.get) and editUrl() picks the form accordingly. The "Add Message Template" button is split into two accordingly: one for on-screen templates (new Angular create flow) and one for document-upload templates (unchanged, classic form, now reachable via a docOnly=1 URL param that hides the classic form's now-redundant Source radio).

Since the Angular editor previously only handled existing records, it's extended to support create mode too: a brand-new template's content fields are seeded with empty strings rather than left undefined (Monaco's setValue() throws on an undefined model value), and doSave() branches between MessageTemplate.create and .update depending on whether a record id exists yet.

Making the Angular editor the default for User-Driven templates also exposes the gaps fixed in the previous commit for real, plus a few more gaps only visible once actually used day-to-day, fixed here:

- The HTML Content field only offered a raw Monaco code editor, whereas User-Driven templates are typically composed by non-technical users who expect WYSIWYG editing (which the classic form did provide, via CKEditor). System Workflow templates keep the Monaco editor and have no toggle; User-Driven templates default to the same CKEditor-backed civi-rich-text-input component used elsewhere in core, with an explicit toggle button to switch to raw HTML and back. The toggle, and the same rich-text/Monaco choice, are also available in the "Open large editor" modal. Switching editor mode, opening the large editor or diff view, opening the preview, or saving all flush any in-progress rich-text edit into the model first, as a safety net against reading stale content. The HTML Content token selector is disabled unless the rich-text editor is actively open (its collapsed preview state has no live cursor to insert at); it's always enabled in Monaco mode, which has no such distinction.
- The previous commit's "Show diff" guard just hid the button when there was nothing to diff against - User-Driven templates now get an actual diff base: a snapshot of the record taken when it was loaded (or last saved), so "Show diff" shows what's changed since then. It stays hidden for a brand-new, never-saved template, which has no such snapshot yet.
- Defaulting User-Driven templates to the rich-text editor exposed a real, pre-existing data-loss trap: a couple of core's own bundled sample templates (and presumably some real-world ones) predate CiviMail's Header/Footer feature and still store msg_html as a full HTML document (its own doctype/html/head/body), which a content-editable-based rich-text editor - CKEditor here, but this isn't CKEditor-specific, no contenteditable editor can do otherwise - silently and permanently strips down to just the inner content the moment it's edited and saved, in the classic form today as much as here. The new editor now detects that up front and defaults such templates to raw HTML mode instead (the toggle to rich text is still available, with a warning explaining what it'll do), rather than walking users into the trap by default.

Two small polish items while testing this end-to-end:

- The Text Content field gave no indication of what happens if it's left blank. Confirmed in CRM_Utils_Mail::setEmailHeaders() that a blank msg_text is auto-generated from msg_html via CRM_Utils_String::htmlToText() at send time, so a help note now says so.
- Message Title was a plain, unsized input, cramped compared to the rest of the form. It's widened with the existing "huge" utility class.
@mattwire
mattwire force-pushed the tag-message-templates branch from 011988c to 376b935 Compare August 14, 2026 15:29
@mattwire
mattwire marked this pull request as ready for review August 14, 2026 15:44
@mattwire
mattwire force-pushed the tag-message-templates branch from 376b935 to 51e0abb Compare August 14, 2026 20:18
search_kit's crmSearchAdminTags (ang/crmSearchAdmin/crmSearchAdminTags.component.js) - an inline "Tags" dropdown showing colored badges for the tags currently applied to an entity, with inline quick-create - was hardcoded to civicrm_saved_search in exactly three spots: the entity_table used by EntityTag.create/delete, the used_for value passed to Tag.create, and the CRM.crmSearchAdmin.tags global it read its tag list from. A follow-up change needs the same widget for Message Templates, and copying it a second time (with just those three spots swapped) isn't worth repeating a third time down the line, so this extracts it into a shared core component first.

The new module, ang/crmEntityTags.ang.php/.js/.css and ang/crmEntityTags/crmEntityTags.html, is parameterized on entityTable (also used as the used_for value, since for a real DB-table entity those are the same string) and an optional entityId - omitted for a not-yet-saved record, in which case tagIds is mutated locally with no EntityTag API calls, the same "local until save" mode crmSearchAdminTags already supported for the search-settings panel. Rather than requiring each caller to keep publishing its own settingsFactory-populated tag list global, the component now fetches its own tags via Tag.get(where: used_for CONTAINS entityTable), cached per entity_table so a list with many rows - each embedding its own <crm-entity-tags> - triggers one API call per distinct table, not one per row; a newly quick-created tag is pushed into that same cached array, so it's immediately visible to every other row's widget too.

search_kit now uses the shared component: crmSearchAdminTags.component.js/.html are deleted, both existing usages (the per-row list widget in searchListing/tags.html, and the search-settings panel in crmSearch-settings.html) switched to <crm-entity-tags>, and the CSS rules the widget's dropdown markup relies on (tag-color swatch, hiding the button's text label in compact/xs contexts, and the dropdown's form-inline layout) moved to crmEntityTags.css, since they're only ever exercised by that markup, not anything else in crmSearchAdmin.css.

Verified live: search_kit's Saved Search list (Tags column) and its search-settings panel still show/toggle/create tags correctly through the shared component, with no console errors.
Message templates have never supported tagging. CiviCRM's tagging system is entity-agnostic (civicrm_entity_tag.entity_table/entity_id is a dynamic FK), and the only thing missing for MessageTemplate was an entry in the tag_used_for option group, so this adds one permanently, the same way Contact/Activity/Case/File are registered.

The tagging UI itself is added only to the message_admin extension (Administer > Communications > Message Templates), not to the classic CRM_Admin_Form_MessageTemplates form:

- An inline "Tags" dropdown is added to every row of the User-Driven list table, using the shared <crm-entity-tags> component (extracted from search_kit's own tag widget in the previous commit), so any user-driven template - including document (.docx/.odt) uploads, which have no other tagging path - can be tagged directly from the list. System Workflow templates don't get this for now.
- A Tags field is added to the extension's own Angular template editor (Edit.html/Edit.js), for User-Driven templates only - System Workflow templates don't get this for now either. This builds on an earlier commit making that editor the default for User-Driven templates and supporting create mode ("Add template", split from the classic form's document-upload flow in that same commit); tags are saved via the generic EntityTag.replace action, sequenced after the record itself is created/updated since a brand-new record's id doesn't exist until then.
@mattwire
mattwire force-pushed the tag-message-templates branch from 51e0abb to 6d60bde Compare August 14, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant