';
@@ -128,7 +129,6 @@ final class DataSourcesAdminPage
echo '
';
echo '
';
echo '
';
- echo '
';
echo '
';
+ echo '';
+ echo '
';
+ echo '
';
+ echo '';
echo '
';
echo '
';
@@ -161,10 +165,13 @@ final class DataSourcesAdminPage
echo '
';
echo '
';
echo '
';
+ echo '
';
echo '
Add custom source';
+ $defaultSchema = in_array('fenedgec_members', $schemaList, true) ? 'fenedgec_members' : '';
echo '
';
echo '
';
@@ -180,6 +187,7 @@ final class DataSourcesAdminPage
echo '
Generated DSL';
echo '
';
echo '
';
+ echo '
';
echo '
';
echo '
';
echo ' ';
@@ -250,6 +258,7 @@ final class DataSourcesAdminPage
echo 'window.fecaDataSourcesBuilderConfig = ' . json_encode([
'sourceFields' => $sourceFieldsMap,
'restBase' => '/wp-json/mailshots/v1/data-sources',
+ 'adminPostApi' => $this->wp->adminUrl('admin-post.php?action=feca_mailshots_data_sources_api'),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ';';
echo '(function(){';
echo 'var cfg=window.fecaDataSourcesBuilderConfig||{};';
@@ -263,7 +272,13 @@ final class DataSourcesAdminPage
echo 'var rowsWrap=document.getElementById("ds-builder-rows");';
echo 'var out=document.getElementById("ds-builder-output");';
echo 'var err=document.getElementById("ds-builder-error");';
+ echo 'var tableErr=document.getElementById("ds-builder-table-error");';
echo 'var editorModal=document.getElementById("ds-editor-modal");';
+ echo 'var editorForm=document.getElementById("ds-editor-form");';
+ echo 'var validateBtn=document.getElementById("ds-validate-btn");';
+ echo 'var previewBtn=document.getElementById("ds-preview-btn");';
+ echo 'var inlineResult=document.getElementById("ds-inline-result");';
+ echo 'var inlinePreview=document.getElementById("ds-inline-preview");';
echo 'var openNew=document.getElementById("ds-open-new");';
echo 'var closeEditor=document.getElementById("ds-close-editor");';
echo 'var editorId=document.getElementById("ds-editor-id");';
@@ -275,19 +290,26 @@ final class DataSourcesAdminPage
echo 'var addCustomBtn=document.getElementById("ds-builder-add-custom");';
echo 'var customSources=[];';
echo 'var constraints=[];';
- echo 'var filters=[["selected-renewal","Renewal is selected"],["pending-renewal","Renewal is pending"],["fen1-contact","Contact is FEN1"],["fen2-contact","Contact is FEN2"],["member-or-affiliate-or-parish-council","Account is member/affiliate/parish council"]];';
+ echo 'var isDirty=false;';
+ echo 'function confirmDiscard(){if(!isDirty){return true;}return window.confirm("You have unsaved changes. Close without saving?");}';
+ echo 'if(editorForm){editorForm.querySelectorAll("input,select,textarea").forEach(function(el){el.addEventListener("input",function(){isDirty=true;});el.addEventListener("change",function(){isDirty=true;});});editorForm.addEventListener("submit",function(){isDirty=false;});}';
+ echo 'var filters=[["selected-renewal","Renewal is selected"],["pending-renewal","Renewal is pending"],["primary-contact","Contact is primary"],["fen1-contact","Contact is FEN1"],["member-or-affiliate-or-parish-council","Account is member/affiliate/parish council"]];';
echo 'function selectedSources(){var s=[];builtChecks.forEach(function(c){if(c.checked){s.push(c.value);}});customSources.forEach(function(v){s.push(v);});return s;}';
echo 'function updateCustomList(){customList.innerHTML="";customSources.forEach(function(src,i){var li=document.createElement("li");li.textContent=src+" ";var b=document.createElement("button");b.type="button";b.className="button-link-delete";b.textContent="Remove";b.onclick=function(){customSources.splice(i,1);updateCustomList();renderRows();updateDsl();};li.appendChild(b);customList.appendChild(li);});}';
- echo 'function fetchTables(schema){tableSel.innerHTML="";fetch(cfg.restBase+"/tables?schema="+encodeURIComponent(schema),{credentials:"same-origin"}).then(function(r){return r.json();}).then(function(j){var items=(j&&j.items)||[];tableSel.innerHTML="";items.forEach(function(t){var o=document.createElement("option");o.value=t;o.textContent=t;tableSel.appendChild(o);});}).catch(function(){tableSel.innerHTML="";});}';
- echo 'function sourceFields(source){var map=cfg.sourceFields||{};if(map[source]){return map[source];}return [];}';
+ echo 'function fetchTables(schema){tableSel.innerHTML="";tableSel.style.color="#1d2327";if(tableErr){tableErr.textContent="";}var pickLabel=function(t){if(typeof t==="string"){return t.trim();}if(t===null||t===undefined){return "";}if(typeof t==="number"){return String(t);}if(typeof t==="object"){var direct=[t.table_name,t.name,t.table,t.label];for(var i=0;iSelect table";var invalid=0;(items||[]).forEach(function(t){var label=pickLabel(t);if(!label){invalid++;return;}var o=document.createElement("option");o.value=label;o.textContent=label;tableSel.appendChild(o);});if((items||[]).length===0&&tableErr){tableErr.textContent="No tables returned for this schema. Check DB grants: SELECT and SHOW VIEW on schema tables.";}if(invalid>0&&tableErr){tableErr.textContent="Received "+invalid+" table entries without names; showing only valid table names.";}};var showErr=function(msg){tableSel.innerHTML="";if(tableErr){tableErr.textContent=msg||"Unable to list tables for this schema.";} };fetch(cfg.restBase+"/tables?schema="+encodeURIComponent(schema),{credentials:"same-origin"}).then(function(r){if(!r.ok){throw new Error("REST "+r.status);}return r.json();}).then(function(j){if(!j||j.ok===false){throw new Error((j&&j.error)||"REST error");}fill((j&&j.items)||[]);}).catch(function(){fetch((cfg.adminPostApi||"/wp-admin/admin-post.php?action=feca_mailshots_data_sources_api")+"&op=tables&schema="+encodeURIComponent(schema),{credentials:"same-origin"}).then(function(r){if(!r.ok){throw new Error("API "+r.status);}return r.json();}).then(function(j){if(!j||j.ok===false){throw new Error((j&&j.error)||"API error");}fill((j&&j.items)||[]);}).catch(function(e){showErr((e&&e.message?e.message+". ":"")+"Need MySQL grants on the selected schema.");});});}';
+ echo 'function sourceFields(source){var map=cfg.sourceFields||{};if(map[source]){return map[source];}if(source&&source.indexOf(".")!==-1){fetch((cfg.adminPostApi||"/wp-admin/admin-post.php?action=feca_mailshots_data_sources_api")+"&op=source_fields&source="+encodeURIComponent(source),{credentials:"same-origin"}).then(function(r){return r.json();}).then(function(j){var fields=(j&&j.fields)||[];if(fields&&fields.length){map[source]=fields;cfg.sourceFields=map;renderRows();updateDsl();}}).catch(function(){});}return [];}';
echo 'function mkSelect(options,value){var s=document.createElement("select");options.forEach(function(opt){var o=document.createElement("option");o.value=opt[0];o.textContent=opt[1];if(opt[0]===value){o.selected=true;}s.appendChild(o);});return s;}';
echo 'function addConstraint(){constraints.push({kind:"filter",negate:false,filter:"selected-renewal",lhsSource:"",lhsField:"",op:"=",rhsMode:"literal",rhsLiteral:"",rhsSource:"",rhsField:""});renderRows();updateDsl();}';
echo 'function renderRows(){rowsWrap.innerHTML="";constraints.forEach(function(row,idx){var box=document.createElement("div");box.style.border="1px solid #dcdcde";box.style.padding="8px";box.style.marginBottom="8px";var top=document.createElement("div");var not=document.createElement("input");not.type="checkbox";not.checked=!!row.negate;not.onchange=function(){row.negate=not.checked;updateDsl();};top.appendChild(not);top.appendChild(document.createTextNode(" NOT "));var kind=mkSelect([["filter","Predefined Filter"],["compare","Field Comparison"]],row.kind);kind.onchange=function(){row.kind=kind.value;renderRows();updateDsl();};top.appendChild(kind);var rem=document.createElement("button");rem.type="button";rem.className="button-link-delete";rem.style.marginLeft="8px";rem.textContent="Remove";rem.onclick=function(){constraints.splice(idx,1);renderRows();updateDsl();};top.appendChild(rem);box.appendChild(top);if(row.kind==="filter"){var f=mkSelect(filters,row.filter);f.onchange=function(){row.filter=f.value;updateDsl();};box.appendChild(f);}else{var srcs=selectedSources().map(function(s){return [s,s];});if(srcs.length===0){srcs=[["","Select source"]];}else{srcs.unshift(["","Select source"]);}var lhsS=mkSelect(srcs,row.lhsSource);lhsS.onchange=function(){row.lhsSource=lhsS.value;row.lhsField="";renderRows();updateDsl();};box.appendChild(lhsS);var lhsFields=(row.lhsSource?sourceFields(row.lhsSource):[]).map(function(f){return [f,f]});lhsFields.unshift(["","Field"]);var lhsF=mkSelect(lhsFields,row.lhsField);lhsF.onchange=function(){row.lhsField=lhsF.value;updateDsl();};box.appendChild(lhsF);var op=mkSelect([["=","="],["!=","!="],["contains","contains"],["starts-with","starts-with"],["ends-with","ends-with"],["in","in"]],row.op);op.onchange=function(){row.op=op.value;updateDsl();};box.appendChild(op);var mode=mkSelect([["literal","Literal"],["field","Field ref"]],row.rhsMode);mode.onchange=function(){row.rhsMode=mode.value;renderRows();updateDsl();};box.appendChild(mode);if(row.rhsMode==="literal"){var input=document.createElement("input");input.type="text";input.value=row.rhsLiteral||"";input.placeholder="Value";input.oninput=function(){row.rhsLiteral=input.value;updateDsl();};box.appendChild(input);}else{var rhsS=mkSelect(srcs,row.rhsSource);rhsS.onchange=function(){row.rhsSource=rhsS.value;row.rhsField="";renderRows();updateDsl();};box.appendChild(rhsS);var rhsFields=(row.rhsSource?sourceFields(row.rhsSource):[]).map(function(f){return [f,f]});rhsFields.unshift(["","Field"]);var rhsF=mkSelect(rhsFields,row.rhsField);rhsF.onchange=function(){row.rhsField=rhsF.value;updateDsl();};box.appendChild(rhsF);}}rowsWrap.appendChild(box);});}';
echo 'function quote(v){if(v==="true"||v==="false"||/^\\d+$/.test(v)){return v;}return "\'"+String(v).replace(/\'/g,"")+"\'";}';
echo 'function updateDsl(){err.textContent="";var srcs=selectedSources();if(srcs.length===0){out.value="";err.textContent="Select at least one data source.";return;}var base=srcs.join(" and ");var terms=[];for(var i=0;i0?" where "+terms.join(" and "):"");}';
echo 'function resetBuilder(){builtChecks.forEach(function(c){c.checked=false;});customSources=[];constraints=[];updateCustomList();renderRows();updateDsl();}';
- echo 'function prefillFromDsl(){var text=(dslInput&&dslInput.value?dslInput.value:"").trim();if(!text){return;}var whereIndex=text.indexOf(" where ");var srcPart=whereIndex>=0?text.substring(0,whereIndex):text;var bits=srcPart.split(" and ");bits.map(function(v){return v.trim();}).forEach(function(src){if(!src){return;}var built=false;builtChecks.forEach(function(c){if(c.value===src){c.checked=true;built=true;}});if(!built&&customSources.indexOf(src)===-1){customSources.push(src);}});updateCustomList();renderRows();updateDsl();}';
- echo 'if(schemaSel){schemaSel.onchange=function(){if(schemaSel.value){fetchTables(schemaSel.value);}else{tableSel.innerHTML="";}};}';
+ echo 'function splitAndTerms(text){var out=[];var cur="";var depth=0;var inQuote=false;for(var i=0;i0){depth--;}cur+=ch;continue;}if(depth===0&&text.substring(i,i+5)===" and "){if(cur.trim()){out.push(cur.trim());}cur="";i+=4;continue;}}cur+=ch;}if(cur.trim()){out.push(cur.trim());}return out;}';
+ echo 'function splitFieldRef(text){var s=String(text||"").trim();var idx=s.lastIndexOf(".");if(idx<=0||idx>=s.length-1){return null;}return {source:s.substring(0,idx),field:s.substring(idx+1)};}';
+ echo 'function parseLiteralValue(text){var v=String(text||"").trim();if(!v){return null;}if(v.length>=2&&v.charAt(0)==="\'"&&v.charAt(v.length-1)==="\'"){return v.substring(1,v.length-1);}if(v==="true"||v==="false"||/^\\d+$/.test(v)){return v;}return null;}';
+ echo 'function parseCompareTerm(term){var m=/^(.+?)\\s+(=|!=|contains|starts-with|ends-with|in)\\s+(.+)$/.exec(String(term||"").trim());if(!m){return null;}var lhsRef=splitFieldRef(m[1]);if(!lhsRef){return null;}var op=String(m[2]||"");var rhsRaw=String(m[3]||"").trim();if(!rhsRaw){return null;}var row={kind:"compare",negate:false,filter:"selected-renewal",lhsSource:lhsRef.source,lhsField:lhsRef.field,op:op,rhsMode:"literal",rhsLiteral:"",rhsSource:"",rhsField:""};if(op==="in"){if(rhsRaw.charAt(0)!=="("||rhsRaw.charAt(rhsRaw.length-1)!==")"){return null;}rhsRaw=rhsRaw.substring(1,rhsRaw.length-1).trim();if(!rhsRaw){return null;}}var rhsRef=splitFieldRef(rhsRaw);if(rhsRef){row.rhsMode="field";row.rhsSource=rhsRef.source;row.rhsField=rhsRef.field;return row;}var literal=parseLiteralValue(rhsRaw);if(literal===null){return null;}row.rhsLiteral=literal;return row;}';
+ echo 'function prefillFromDsl(){var text=(dslInput&&dslInput.value?dslInput.value:"").trim();if(!text){return;}var lower=text.toLowerCase();var whereIndex=lower.indexOf(" where ");var srcPart=whereIndex>=0?text.substring(0,whereIndex):text;var wherePart=whereIndex>=0?text.substring(whereIndex+7).trim():"";var bits=srcPart.split(" and ");bits.map(function(v){return v.trim();}).forEach(function(src){if(!src){return;}var built=false;builtChecks.forEach(function(c){if(c.value===src){c.checked=true;built=true;}});if(!built&&customSources.indexOf(src)===-1){customSources.push(src);}});constraints=[];if(wherePart){var known={};filters.forEach(function(f){known[f[0]]=true;});splitAndTerms(wherePart).forEach(function(term){var t=(term||"").trim();if(!t){return;}var negate=false;var lt=t.toLowerCase();if(lt.indexOf("not (")===0&&t.charAt(t.length-1)===")"){negate=true;t=t.substring(5,t.length-1).trim();}else if(lt.indexOf("not ")===0){negate=true;t=t.substring(4).trim();}if(known[t]){constraints.push({kind:"filter",negate:negate,filter:t,lhsSource:"",lhsField:"",op:"=",rhsMode:"literal",rhsLiteral:"",rhsSource:"",rhsField:""});return;}var parsed=parseCompareTerm(t);if(parsed){parsed.negate=negate;constraints.push(parsed);}});}updateCustomList();renderRows();updateDsl();}';
+ echo 'if(schemaSel){schemaSel.onchange=function(){if(schemaSel.value){fetchTables(schemaSel.value);}else{tableSel.innerHTML="";}};if(schemaSel.value){fetchTables(schemaSel.value);}}';
echo 'if(addCustomBtn){addCustomBtn.onclick=function(){if(!schemaSel.value||!tableSel.value){return;}var src=schemaSel.value+"."+tableSel.value;if(customSources.indexOf(src)===-1){customSources.push(src);}updateCustomList();renderRows();updateDsl();};}';
echo 'builtChecks.forEach(function(c){c.onchange=updateDsl;});';
echo 'if(addRowBtn){addRowBtn.onclick=addConstraint;}';
@@ -295,10 +317,16 @@ final class DataSourcesAdminPage
echo 'if(cancelBtn){cancelBtn.onclick=function(){modal.style.display="none";};}';
echo 'if(resetBtn){resetBtn.onclick=function(){resetBuilder();};}';
echo 'if(applyBtn){applyBtn.onclick=function(){if(dslInput){dslInput.value=out.value;}modal.style.display="none";};}';
- echo 'if(openNew){openNew.onclick=function(){if(editorId){editorId.value="";}if(editorName){editorName.value="";}if(dslInput){dslInput.value="";}editorModal.style.display="block";};}';
- echo 'if(closeEditor){closeEditor.onclick=function(){editorModal.style.display="none";};}';
+ echo 'if(openNew){openNew.onclick=function(){if(!confirmDiscard()){return;}if(editorId){editorId.value="";}if(editorName){editorName.value="";}if(dslInput){dslInput.value="";}isDirty=false;editorModal.style.display="block";};}';
+ echo 'if(closeEditor){closeEditor.onclick=function(){if(!confirmDiscard()){return;}editorModal.style.display="none";};}';
+ echo 'function esc(v){return String(v||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""");}';
+ echo 'function showInlineResult(ok,title,lines){if(!inlineResult){return;}inlineResult.style.display="block";inlineResult.style.borderColor=ok?"#8bc34a":"#ef9a9a";inlineResult.style.background=ok?"#f1f8e9":"#ffebee";var h=""+esc(title)+"";if(lines&&lines.length){h+=""+lines.map(function(x){return "- "+esc(x)+"
";}).join("")+"
";}inlineResult.innerHTML=h;}';
+ echo 'if(validateBtn){validateBtn.onclick=function(){var dsl=(dslInput&&dslInput.value?dslInput.value:"");var payload=new URLSearchParams();payload.set("dsl_text",dsl);fetch((cfg.adminPostApi||"/wp-admin/admin-post.php?action=feca_mailshots_data_sources_api")+"&op=validate",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:payload.toString()}).then(function(r){return r.json();}).then(function(j){if(!j||j.ok===false){showInlineResult(false,"Validation failed",[j&&j.error?j.error:"Unknown validation error"]);return;}var errors=(j.errors||[]);var warnings=(j.warnings||[]);if(errors.length===0){showInlineResult(true,"Validation passed",warnings.length?warnings:["No errors"]);}else{showInlineResult(false,"Validation failed",errors.concat(warnings));}}).catch(function(e){showInlineResult(false,"Validation failed",[e&&e.message?e.message:"Request error"]);});};}';
+ echo 'if(previewBtn){previewBtn.onclick=function(){var dsl=(dslInput&&dslInput.value?dslInput.value:"");var payload=new URLSearchParams();payload.set("dsl_text",dsl);payload.set("limit","50");fetch((cfg.adminPostApi||"/wp-admin/admin-post.php?action=feca_mailshots_data_sources_api")+"&op=preview",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:payload.toString()}).then(function(r){return r.json();}).then(function(j){if(!j||j.ok===false){showInlineResult(false,"Preview failed",[j&&j.error?j.error:"Unknown preview error"]);if(inlinePreview){inlinePreview.style.display="none";}return;}var errors=(j.errors||[]);var warnings=(j.warnings||[]);if(errors.length){showInlineResult(false,"Preview failed",errors.concat(warnings));if(inlinePreview){inlinePreview.style.display="none";}return;}showInlineResult(true,"Preview ready",["Count: "+(j.count||0)].concat(warnings));if(!inlinePreview){return;}var rows=(j.rows||[]);if(!rows.length){inlinePreview.style.display="block";inlinePreview.innerHTML="No rows returned.
";return;}var cols=[];var seen={};rows.forEach(function(r){Object.keys(r||{}).forEach(function(k){if(!seen[k]){seen[k]=1;cols.push(k);}});});var html=""+cols.map(function(c){return "| "+esc(c)+" | ";}).join("")+"
";rows.forEach(function(r){html+=""+cols.map(function(c){return "| "+esc((r&&r[c]!==undefined)?r[c]:"")+" | ";}).join("")+"
";});html+="
";inlinePreview.style.display="block";inlinePreview.innerHTML=html;}).catch(function(e){showInlineResult(false,"Preview failed",[e&&e.message?e.message:"Request error"]);if(inlinePreview){inlinePreview.style.display="none";}});};}';
echo 'var hasEdit=' . ($editId > 0 ? 'true' : 'false') . ';';
- echo 'if(hasEdit&&editorModal){editorModal.style.display="block";}';
+ echo 'var hasResult=' . ($result !== null ? 'true' : 'false') . ';';
+ echo 'var resultOk=' . (!empty($result['ok']) ? 'true' : 'false') . ';';
+ echo 'if(hasEdit&&editorModal&&(!hasResult||!resultOk)){editorModal.style.display="block";isDirty=false;}';
echo '})();';
echo '';
echo '
';
@@ -675,6 +703,7 @@ final class DataSourcesAdminPage
private function result(): ?array
{
$raw = $this->wp->getOption(self::RESULT_OPTION_KEY, null);
+ $this->wp->deleteOption(self::RESULT_OPTION_KEY);
return is_array($raw) ? $raw : null;
}
diff --git a/feca_mailshots_plugin/src/Admin/MailshotTestAdminPage.php b/feca_mailshots_plugin/src/Admin/MailshotTestAdminPage.php
index 6a0f91f..969c9e2 100644
--- a/feca_mailshots_plugin/src/Admin/MailshotTestAdminPage.php
+++ b/feca_mailshots_plugin/src/Admin/MailshotTestAdminPage.php
@@ -227,6 +227,7 @@ final class MailshotTestAdminPage
private function result(): ?array
{
$raw = $this->wp->getOption(self::RESULT_OPTION_KEY, null);
+ $this->wp->deleteOption(self::RESULT_OPTION_KEY);
return is_array($raw) ? $raw : null;
}
diff --git a/feca_mailshots_plugin/src/Admin/MailshotsAdminPage.php b/feca_mailshots_plugin/src/Admin/MailshotsAdminPage.php
index 09b2dd0..b824079 100644
--- a/feca_mailshots_plugin/src/Admin/MailshotsAdminPage.php
+++ b/feca_mailshots_plugin/src/Admin/MailshotsAdminPage.php
@@ -88,6 +88,7 @@ final class MailshotsAdminPage
'Message' => (string) ($editItem['Message'] ?? ''),
'PDFAttachment' => (string) ($editItem['PDFAttachment'] ?? ''),
'PDFFilenameDerivedFrom' => (string) ($editItem['PDFFilenameDerivedFrom'] ?? ''),
+ 'RecipientEmailField' => (string) ($editItem['RecipientEmailField'] ?? ''),
'ReplyTo' => (string) ($editItem['ReplyTo'] ?? ''),
];
@@ -110,7 +111,6 @@ final class MailshotsAdminPage
echo '
';
@@ -213,53 +215,531 @@ final class MailshotsAdminPage
echo '';
@@ -332,6 +820,7 @@ final class MailshotsAdminPage
'PDFAttachment' => (string) ($this->wp->requestParam('PDFAttachment', '') ?? ''),
'AttachmentNames' => json_encode($attachmentNames, JSON_UNESCAPED_SLASHES),
'PDFFilenameDerivedFrom' => (string) ($this->wp->requestParam('PDFFilenameDerivedFrom', '') ?? ''),
+ 'RecipientEmailField' => (string) ($this->wp->requestParam('RecipientEmailField', '') ?? ''),
'ReplyTo' => (string) ($this->wp->requestParam('ReplyTo', '') ?? ''),
];
$result = $this->service()->save($id, $payload);
@@ -426,6 +915,7 @@ final class MailshotsAdminPage
'PDFAttachment' => (string) ($this->wp->requestParam('PDFAttachment', '') ?? ''),
'AttachmentNames' => (string) ($this->wp->requestParam('AttachmentNames', '[]') ?? '[]'),
'PDFFilenameDerivedFrom' => (string) ($this->wp->requestParam('PDFFilenameDerivedFrom', '') ?? ''),
+ 'RecipientEmailField' => (string) ($this->wp->requestParam('RecipientEmailField', '') ?? ''),
'ReplyTo' => (string) ($this->wp->requestParam('ReplyTo', '') ?? ''),
];
$this->wp->sendJson($this->service()->save($id, $payload));
@@ -450,10 +940,22 @@ final class MailshotsAdminPage
return ($this->serviceFactory)();
}
+ private function cleanTokenLabel(string $value): string
+ {
+ $label = trim($value);
+ if ($label === '') {
+ return $label;
+ }
+ $label = str_replace(['{{', '}}'], '', $label);
+ $label = preg_replace('/\bfenedgec_members_/i', '', $label) ?? $label;
+ return trim($label);
+ }
+
/** @return array