Allow Message Templates to be tagged, via the message_admin extension - #36489
Open
mattwire wants to merge 4 commits into
Open
Allow Message Templates to be tagged, via the message_admin extension#36489mattwire wants to merge 4 commits into
mattwire wants to merge 4 commits into
Conversation
|
🤖 Thank you for contributing to CiviCRM! ❤️ We will need to test and review this PR. 👷 Introduction for new contributors...
PR commands & links...
|
mattwire
marked this pull request as draft
August 14, 2026 09:49
3 tasks
mattwire
force-pushed
the
tag-message-templates
branch
from
August 14, 2026 10:49
16d7443 to
d3967e8
Compare
3 tasks
mattwire
force-pushed
the
tag-message-templates
branch
2 times, most recently
from
August 14, 2026 14:59
bbc9c40 to
011988c
Compare
…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
force-pushed
the
tag-message-templates
branch
from
August 14, 2026 15:29
011988c to
376b935
Compare
mattwire
marked this pull request as ready for review
August 14, 2026 15:44
mattwire
force-pushed
the
tag-message-templates
branch
from
August 14, 2026 20:18
376b935 to
51e0abb
Compare
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
force-pushed
the
tag-message-templates
branch
from
August 14, 2026 20:46
51e0abb to
6d60bde
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Depends on both #36488 (Angular editor default for User-Driven templates) and #36492 (the shared
crmEntityTagscomponent 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_idis a dynamic FK), and the only thing missing forMessageTemplatewas an entry in thetag_used_foroption 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_MessageTemplatesform:<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.Edit.html/Edit.js), for User-Driven templates only. Tags are saved via the genericEntityTag.replaceaction, sequenced after the record itself is created/updated since a brand-new record's id doesn't exist until then.Test plan
MessageTemplateshows a generic "Add/Remove Tags" bulk task in a SearchKit display built on it.