Changeset 3497572
- Timestamp:
- 04/02/2026 02:27:04 PM (14 hours ago)
- Location:
- groundhogg
- Files:
-
- 4 added
- 16 edited
- 1 copied
-
tags/4.4 (copied) (copied from groundhogg/trunk)
-
tags/4.4/README.txt (modified) (3 diffs)
-
tags/4.4/admin/settings/settings-page.php (modified) (1 diff)
-
tags/4.4/assets/js/admin/features/email-detector.js (added)
-
tags/4.4/assets/js/admin/features/email-detector.min.js (added)
-
tags/4.4/assets/js/admin/make-el.js (modified) (5 diffs)
-
tags/4.4/assets/js/admin/make-el.min.js (modified) (1 diff)
-
tags/4.4/groundhogg.php (modified) (2 diffs)
-
tags/4.4/includes/form/form-v2.php (modified) (2 diffs)
-
tags/4.4/includes/functions.php (modified) (2 diffs)
-
tags/4.4/includes/scripts.php (modified) (2 diffs)
-
trunk/README.txt (modified) (3 diffs)
-
trunk/admin/settings/settings-page.php (modified) (1 diff)
-
trunk/assets/js/admin/features/email-detector.js (added)
-
trunk/assets/js/admin/features/email-detector.min.js (added)
-
trunk/assets/js/admin/make-el.js (modified) (5 diffs)
-
trunk/assets/js/admin/make-el.min.js (modified) (1 diff)
-
trunk/groundhogg.php (modified) (2 diffs)
-
trunk/includes/form/form-v2.php (modified) (2 diffs)
-
trunk/includes/functions.php (modified) (2 diffs)
-
trunk/includes/scripts.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
groundhogg/tags/4.4/README.txt
r3490332 r3497572 7 7 Tested up to: 6.9 8 8 Requires PHP: 7.1 9 Stable tag: 4. 3.39 Stable tag: 4.4 10 10 License: GPLv3 11 11 License URI: https://www.gnu.org/licenses/gpl.md … … 379 379 == Changelog == 380 380 381 = 4.4 (2026-04-01) = 382 * ADDED "Contact Peek" feature 383 * Allows you to instantly see basic contact details in any non-Groundhogg screen (like WooCommerce or MailHawk) in the WordPress admin whenever an email address is detected. 384 * If a contact does not exist, you'll be given the option to create one and edit it from where you are. 385 * ADDED Developer filter to register additional free inbox providers. 386 381 387 = 4.3.3 (2026-03-11) = 382 388 * ADDED Automatic detection of free inbox providers for contacts. … … 384 390 * ADDED New base components and classes for use in addons. 385 391 * FIXED Settings page pickers were not working due to a missing script if the toolbar widget is disabled. 386 * FIXED Broadcast send time estimate not showingcorrect time estimate.392 * FIXED Broadcast send time estimate is not showing the correct time estimate. 387 393 388 394 = 4.3.2 (2026-03-09) = -
groundhogg/tags/4.4/admin/settings/settings-page.php
r3490332 r3497572 1080 1080 ], 1081 1081 ], 1082 'gh_disable_email_detection' => [ 1083 'id' => 'gh_disable_email_detection', 1084 'section' => 'interface', 1085 'label' => _x( 'Disable Admin Contact Peek Feature', 'settings', 'groundhogg' ), 1086 /* translators: white label name of the plugin, usually Groundhogg */ 1087 'desc' => sprintf( _x( 'Disable the appearance of the Contact Peek feature in non-%s admin screens.', 'settings', 'groundhogg' ), white_labeled_name() ), 1088 'type' => 'checkbox', 1089 'atts' => [ 1090 'label' => __( 'Disable', 'groundhogg' ), 1091 'name' => 'gh_disable_email_detection', 1092 'id' => 'gh_disable_email_detection', 1093 'value' => 'on', 1094 ], 1095 ], 1082 1096 'gh_default_contact_tab' => [ 1083 1097 'id' => 'gh_default_contact_tab', -
groundhogg/tags/4.4/assets/js/admin/make-el.js
r3464589 r3497572 847 847 848 848 const close = () => { 849 onClose( )849 onClose( modal ) 850 850 modal.remove() 851 851 } … … 855 855 856 856 // Run before positioning 857 onOpen( )857 onOpen( modal ) 858 858 859 859 let targetElement = selector && target === null ? document.querySelector(selector) : target … … 871 871 } = modal.getBoundingClientRect() 872 872 873 // console.log({right, left, bottom, top, width, height}) 874 873 875 switch (from) { 874 876 case 'left': … … 876 878 break 877 879 case 'right': 878 modal.style.left = ( right - width ) + 'px' 880 modal.style.right = ( window.innerWidth - right ) + 'px' 881 modal.style.left = 'auto' 879 882 break 880 883 } … … 887 890 } 888 891 889 modal.focus() 892 setTimeout(()=>{ 893 modal.focus() 894 }, 100) 890 895 891 896 return modal -
groundhogg/tags/4.4/assets/js/admin/make-el.min.js
r3464589 r3497572 2 2 <span class="on">${onLabel}</span> 3 3 <span class="off">${offLabel}</span> 4 `])};const Div=(attributes={},children=[])=>{return makeEl("div",attributes,children)};const Nav=(attributes={},children=[])=>{return makeEl("nav",attributes,children)};const Dashicon=(icon,children=null)=>{return makeEl("span",{className:`dashicons dashicons-${icon}`},children)};const Fragment=(children,atts={})=>{return makeEl("fragment",atts,children)};const Span=(attributes={},children=[])=>{return makeEl("span",attributes,children)};const Label=(attributes={},children=[])=>{return makeEl("label",attributes,children)};const InputRepeater=({id="",onChange=()=>{},rows=[],cells=[],sortable=false,fillRow=()=>Array(cells.length).fill(""),maxRows=0})=>{const handleChange=rows=>{onChange(rows);morphdom(document.getElementById(id),Repeater())};const removeRow=rowIndex=>{rows.splice(rowIndex,1);handleChange(rows)};const addRow=()=>{rows.push(fillRow());handleChange(rows)};const onCellChange=(rowIndex,cellIndex,value)=>{rows[rowIndex][cellIndex]=value;handleChange(rows)};const RepeaterRow=(row,rowIndex)=>Div({className:"gh-input-repeater-row",dataRow:rowIndex},[...cells.map((cellCallback,cellIndex)=>cellCallback({id:`${id}-cell-${rowIndex}-${cellIndex}`,name:`${id}[${rowIndex}][${cellIndex}]`,value:row[cellIndex]??"",dataRow:rowIndex,dataCell:cellIndex,onChange:e=>onCellChange(rowIndex,cellIndex,e.target.value),setValue:value=>onCellChange(rowIndex,cellIndex,value),onCellChange:onCellChange},row)),sortable?makeEl("span",{className:"handle",dataRow:rowIndex},Dashicon("move")):null,Button({className:"gh-button dashicon remove-row",dataRow:rowIndex,type:"button",onClick:e=>removeRow(rowIndex)},Dashicon("no-alt"))]);const Repeater=()=>Div({id:id,className:"gh-input-repeater",onCreate:el=>{if(!sortable){return}$(el).sortable({handle:".handle",update:(e,ui)=>{let $row=$(ui.item);let oldIndex=parseInt($row.data("row"));let curIndex=$row.index();let row=rows[oldIndex];rows.splice(oldIndex,1);rows.splice(curIndex,0,row);onChange(rows)}})}},[...rows.map((row,i)=>RepeaterRow(row,i)),maxRows===0||rows.length<maxRows?Div({className:"gh-input-repeater-row-add"},[`<div class="spacer"></div>`,Button({id:`${id}-add-row`,className:"add-row gh-button dashicon",onClick:e=>addRow(),type:"button"},Dashicon("plus-alt2"))]):null]);return Repeater()};const InputWithReplacements=({inputCallback=Input,...attributes})=>{return Div({className:"input-wrap"},[inputCallback(attributes),Button({className:"replacements-picker-start gh-button dashicon"},Dashicon("admin-users"))])};const Table=(atts,children)=>makeEl("table",atts,children);const THead=(atts,children)=>makeEl("thead",atts,children);const TBody=(atts,children)=>makeEl("tbody",atts,children);const TFoot=(atts,children)=>makeEl("tfoot",atts,children);const Tr=(atts,children)=>makeEl("tr",atts,children);const Td=(atts,children)=>makeEl("td",atts,children);const Th=(atts,children)=>makeEl("th",atts,children);const Modal=({dialogClasses="",className="",onOpen=()=>{},onClose=()=>{},width,closeButton=true,closeOnOverlayClick=true,overlay=true},children)=>{const Dialog=({header=null,content=null})=>Div({className:`gh-modal-dialog ${dialogClasses}`,style:{width:width}},[header,Div({className:"gh-modal-dialog-content"},content),closeButton&&!header?Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt")):null]);let modal=Div({className:`gh-modal ${className}`,tabindex:0},[overlay?Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}):null,Dialog({header:null,content:null})]);const close=()=>{onClose(modal);modal.remove()};const morph=(args={})=>{let content=getContent();let header=content.querySelector(".modal-header");morphdom(modal.querySelector(".gh-modal-dialog"),Dialog({header:header,content:content}),args)};const getContent=()=>maybeCall(children,{close:close,modal:modal,morph:morph});document.body.appendChild(modal);morph();onOpen({modal:modal,close:close,morph:morph});if(!modal.contains(document.activeElement)){modal.focus()}return modal};const ModalWithHeader=({header="",...args},children)=>Modal(args,methods=>Div({},[Div({className:"gh-header modal-header"},[MakeEl.H3({},header),MakeEl.Button({className:"gh-button icon secondary text",onClick:methods.close},MakeEl.Dashicon("no-alt"))]),maybeCall(children,methods)]));const TextPrompt=({header,text="",submitText="Save",onSubmit=text=>{}})=>MakeEl.ModalWithHeader({header:header,onOpen:()=>{let input=document.getElementById("prompt-text");input.focus();input.select()}},({close})=>MakeEl.Form({onSubmit:e=>{e.preventDefault();let fd=new FormData(e.currentTarget);let text=fd.get("prompt_text");onSubmit(text);close()}},[Div({className:"display-flex gap-5"},[Input({id:"prompt-text",name:"prompt_text",value:text}),Button({className:"gh-button primary",type:"submit"},submitText)])]));const ModalFrame=({onOpen=()=>{},onClose=()=>{},frameAttributes={},closeOnOverlayClick=true,closeOnEscape=true},children)=>{let modal=Div({className:"gh-modal",tabindex:0,onKeydown:e=>{if(closeOnEscape){if(e.key==="Esc"||e.key==="Escape"){close()}}}},[Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}),Div({className:`gh-modal-frame`,...frameAttributes},[])]);const close=()=>{onClose();modal.remove()};modal.querySelector(".gh-modal-frame").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen({close:close});modal.focus();return modal};const MiniModal=({selector="",target=null,from="right",dialogClasses="",onOpen=()=>{},onClose=()=>{},closeOnFocusout=true},children)=>{let modal=Div({className:"gh-modal mini gh-panel",tabindex:0,onFocusout:e=>{if(closeOnFocusout){if(!e.relatedTarget||!clickedIn(e.relatedTarget,".gh-modal.mini")){setTimeout(()=>{if(!clickedIn(document.activeElement,".gh-modal.mini")){close()}},10)}}},onCreate:el=>{el.focus()}},Div({className:`gh-modal-dialog ${dialogClasses}`},[Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt"))]));const close=()=>{onClose( );modal.remove()};modal.querySelector(".gh-modal-dialog").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen();let targetElement=selector&&target===null?document.querySelector(selector):target;let{right,left,bottom,top}=targetElement.getBoundingClientRect();let{width,height}=modal.getBoundingClientRect();switch(from){case"left":modal.style.left=left+"px";break;case"right":modal.style.left=right-width+"px";break}if(top+height>window.innerHeight){modal.style.top=window.innerHeight-height-20+"px"}else{modal.style.top=top+"px"}modal.focus();return modal};const Autocomplete=({fetchResults=async search=>{},onInput,...attributes})=>{let timeout;const State={pointer:0,results:[],input:null};const setValue=()=>{let item=State.results[State.pointer];State.input.value=item.id;State.input.dispatchEvent(new Event("change"));closeResults()};const updateResults=()=>{if(!State.results.length){closeResults();return}let resultsContainer=document.querySelector(".gh-autocomplete-results");let newResults=Results();if(!resultsContainer){document.body.appendChild(newResults)}else{morphdom(resultsContainer,newResults)}};const closeResults=()=>{let resultsContainer=document.querySelector(".gh-autocomplete-results");if(resultsContainer){resultsContainer.remove()}};const Results=()=>{const{results,input}=State;let{height,width,top,left}=input.getBoundingClientRect();return Div({className:"gh-autocomplete-results",style:{zIndex:999999,top:`${top+height}px`,left:`${left}px`,width:`${width}px`}},results.map(({id,text},index)=>makeEl("a",{className:`${index===State.pointer?"pointer":""}`,href:id,onClick:e=>{e.preventDefault();setValue()},onMouseenter:e=>{State.pointer=[...e.target.parentNode.children].indexOf(e.target);updateResults()}},text)))};return Input({...attributes,onFocusout:e=>{const input=e.target;input.classList.remove("has-results");if(e.relatedTarget&&clickedIn(e.relatedTarget,"a.pointer")){setValue()}closeResults()},onKeydown:e=>{const input=e.target;switch(e.key){case"Esc":case"Escape":e.preventDefault();closeResults();return;case"Down":case"ArrowDown":e.preventDefault();if(State.pointer<State.results.length){State.pointer++}break;case"Up":case"ArrowUp":e.preventDefault();if(State.pointer>0){State.pointer--}break;case"Enter":e.preventDefault();setValue();break;default:return}updateResults()},onInput:e=>{if(timeout){clearTimeout(timeout)}timeout=setTimeout(async()=>{const input=e.target;let search=input.value;State.results=await fetchResults(search);State.input=input;State.pointer=0;updateResults();input.classList.add("gh-autocomplete","has-results")},500);onInput(e)}})};const Ellipses=(content,atts={})=>Span({...atts,onCreate:el=>{let ellipses="";let count=0;let interval=setInterval(()=>{if(!el.parentNode){clearInterval(interval);return}count=(count+1)%4;ellipses=".".repeat(count);el.textContent=content+ellipses},500)}},content+"...");const ItemPicker=({id="",label="",placeholder="Type to search...",fetchOptions=(search,resolve)=>{},selected=[],onChange=()=>{},onSelect=()=>{},onCreate=()=>{},onUnselect=()=>{},createOption=val=>Promise.resolve({id:val,text:val}),tags=false,noneSelected="Any",isValidSelection=string=>Boolean(string),multiple=true,clearable=true,...attributes})=>{const state=Groundhogg.createState({search:"",searching:false,choosing:false,options:[],focused:false,morphing:false,clicked:false});const optionsVisible=()=>{return multiple?state.focused&&(state.searching||state.options.length||tags&&isValidSelection(state.search)):state.focused};if(!multiple&&!Array.isArray(selected)){selected=[selected]}let timeout;const setState=(newState,trigger)=>{state.set(newState);morph()};const handleOnChange=selected=>{if(timeout){clearTimeout(timeout)}if(multiple){onChange(selected);return}if(!selected.length){onChange(null);return}onChange(selected[0])};const morph=()=>{if(state.morphing){return}state.set({morphing:true});morphdom(document.getElementById(id),Render());state.set({morphing:false})};const focusSearch=()=>document.getElementById(id)?.querySelector(`input[type=search]`)?.focus();const focusPicker=()=>document.getElementById(id)?.focus();const focusParent=()=>document.getElementById(id)?.parentElement.focus();const handleCreateOption=value=>{state.options.unshift({id:value,text:value,create:true});handleSelectOption(value)};const handleSelectOption=async id=>{let option={...state.options.find(opt=>opt.id==id)};if(option.create){option.text=option.id}if(multiple){selected.push(option)}else{selected=[option]}if(option.create){await createOption(option.id).then(opt=>{selected=selected.map(item=>item.id==id?opt:item);option=opt})}onSelect(option);handleOnChange(selected);if(!multiple){state.set({focused:false})}setState({search:""});morph();if(multiple){focusSearch()}else{focusPicker()}};const handleUnselectOption=id=>{let opt=selected.find(opt=>opt.id===id);selected=selected.filter(opt=>opt.id!=id);onUnselect(opt);handleOnChange(selected);morph();if(multiple){focusSearch()}};const itemPickerItem=({id,text},index)=>{return Div({className:`gh-picker-item ${isValidSelection(id)?"":"is-invalid"}`,id:`item-${id}-${index}`},[Span({className:"gh-picker-item-text"},text),selected.length>1||clearable?Span({id:`delete-${id}-${index}`,className:"gh-picker-item-delete",tabindex:"0",dataId:id,onClick:e=>{handleUnselectOption(id)}},"×"):null])};const itemPickerOption=({id,text},index)=>{return Div({className:"gh-picker-option",dataId:id,tabindex:"0",id:`option-${index}-${id}`,onClick:e=>{handleSelectOption(id)}},text)};const itemPickerOptions=()=>{let picker=document.getElementById(id);let style={};if(picker){const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){style.top=bottom+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=maxHeight+"px"}else{style.bottom=window.innerHeight-top+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=top-20+"px"}}state.options=state.options.filter(opt=>!opt.create);if(!state.searching&&tags&&isValidSelection(state.search)){if(!state.options.find(opt=>opt.id==state.search||opt.text==state.search)){state.options.unshift({id:state.search,text:`Add "${state.search}"`,create:true})}}let options=state.options.filter(opt=>!selected.some(_opt=>opt.id==_opt.id));return Div({className:"gh-picker-options",style:style,onCreate:el=>{setTimeout(()=>{let picker=document.getElementById(id);let optionsContainer=picker.querySelector(".gh-picker-options");const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){optionsContainer.style.top=bottom+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=maxHeight+"px"}else{optionsContainer.style.bottom=window.innerHeight-top+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=top-20+"px"}if(!multiple){focusSearch()}},0)}},[multiple||!selected.length?null:SearchInput(),state.searching?Div({className:"gh-picker-no-options"},Ellipses(wp.i18n.__("Searching"))):null,...options.map((opt,i)=>itemPickerOption(opt,i)),options.length||state.searching?null:Div({className:"gh-picker-no-options"},"No results found.")])};const startSearch=search=>{setState({search:search,searching:true},"start search");if(timeout){clearTimeout(timeout)}timeout=setTimeout(()=>{fetchOptions(search).then(options=>{if(search!==state.search){return}setState({searching:false,options:options},"options fetched")})},500)};const SearchInput=()=>Input({className:"gh-picker-search",value:state.search,name:"search",type:"search",autocomplete:"off",id:`${id}-search-input`,placeholder:selected.length?placeholder:noneSelected,onInput:e=>startSearch(e.target.value),onFocus:e=>{startSearch(e.target.value)},onKeydown:e=>{if(tags){if(e.key!=="Enter"&&e.key!==","){return}handleCreateOption(e.target.value)}}});const Render=()=>Div({id:id,className:`gh-picker-container`,tabindex:"0",...attributes},Div({id:`${id}-picker`,className:`gh-picker ${optionsVisible()?"options-visible":""}`,tabindex:"0",onKeydown:e=>{if(e.key==="Escape"){setState({searching:false,focused:false});morph()}},onCreate:el=>{el.addEventListener("focusout",e=>{setTimeout(()=>{if(state.morphing||document.getElementById(id).contains(document.activeElement)){return}setState({search:"",options:[],searching:false,focused:false},"picker focusout")},10)});el.addEventListener("focusin",e=>{if(state.focused){return}setState({focused:true},"picker focused")})}},[Div({className:`gh-picker-selected ${multiple?"multiple":"single"}`},[selected.length&&label?Span({className:"gh-picker-label"},label):null,...selected.map((item,i)=>itemPickerItem(item,i)),multiple||!selected.length?SearchInput():null]),optionsVisible()?itemPickerOptions():null]));return Render()};const InputGroup=inputs=>Div({className:"gh-input-group"},inputs);const Iframe=({onCreate=()=>{},...attributes},content=null)=>{let blob=new Blob([content],{type:"text/html; charset=utf-8"});let src=URL.createObjectURL(blob);return makeEl("iframe",{...attributes,src:src})};const ToolTip=(content,position="bottom")=>{return Div({className:`gh-tooltip ${position}`},content)};const ButtonToggle=({id="",options=[],selected="",onChange=value=>{}})=>{const render=()=>Div({id:id,className:"gh-input-group"},options.map(opt=>ButtonOption(opt)));const ButtonOption=option=>Button({id:`${id}-opt-${option.id}`,className:`gh-button gh-button small ${selected===option.id?"dark":"grey"}`,onClick:e=>{selected=option.id;morphdom(document.getElementById(id),render());onChange(option.id)}},[option.text,option.tooltip?ToolTip(option.tooltip):null]);return render()};const ProgressBar=({percent=100,error=false,className=""})=>{return Div({className:`gh-progress-bar ${error?"gh-error":""} ${className}`},Div({className:"gh-progress-bar-fill",style:{width:`${percent||1}%`}},Span({className:"fill-amount"},`${Math.ceil(percent)}%`)))};const Img=props=>makeEl("img",props);const Pg=(props,children)=>makeEl("p",props,children);const Bold=(props,children)=>makeEl("b",props,children);const An=(props,children)=>{props={href:"javascript:void(0)",...props};return makeEl("a",props,children)};const Ul=(props,children)=>makeEl("ul",props,children);const Ol=(props,children)=>makeEl("ol",props,children);const Li=(props,children)=>makeEl("li",props,children);const H1=(props,children)=>makeEl("h1",props,children);const H2=(props,children)=>makeEl("h2",props,children);const H3=(props,children)=>makeEl("h3",props,children);const H4=(props,children)=>makeEl("h4",props,children);const Hr=(props,children)=>makeEl("hr",props,children);const Skeleton=(attributes,pieces)=>Div({className:"display-grid gap-10",...attributes},pieces.map(span=>Div({className:`${span} skeleton-loading`,style:{height:`40px`}})));const useState=(initialState,id)=>{const el=document.getElementById(id);if(el&&el.State){return el.State}return Groundhogg.createState(initialState)};const Accordion=({id,items,outlined=false,multiple=false})=>{const State=useState({expanded:null},id);const isExpanded=index=>multiple?State.get(`expand${index+1}`):State.expanded===index;const toggleExpand=index=>{if(multiple){State.set({[`expand${index+1}`]:!isExpanded(index)})}else{State.set({expanded:index})}};return Div({id:id,className:"gh-accordion",State:State},morph=>Fragment(items.map(({title,content},i)=>Div({className:`gh-accordion-item gh-accordion-row ${isExpanded(i)?"expanded":"collapsed"} ${outlined?"outlined":"has-box-shadow"}`,id:`${id}-item-${i+1}`},[Div({id:`${id}-item-toggle-${i+1}`,className:"display-flex gap-10 align-center",onClick:e=>{toggleExpand(i);morph()}},[Pg({className:"gh-accordion-item-title"},title),isExpanded(i)?Dashicon("arrow-up-alt2"):Dashicon("arrow-down-alt2")]),isExpanded(i)?content:null]))))};const TinyMCE=({id="",content="",config={},onChange=content=>{},...props})=>{let openEditor=document.getElementById(id);let height=300;if(openEditor&&openEditor.tinyMceInitialized){height=openEditor.previousElementSibling.getBoundingClientRect().height}return Div({id:`tiny-mce-${id}`,className:"tiny-mce-wrap"},Textarea({id:id,name:id.replaceAll("-","_"),value:content,style:{height:`${height}px`},onCreate:el=>{setTimeout(()=>{Groundhogg.element.tinymceElement(el.id,config,onChange);el.tinyMceInitialized=true})},...props}))};window.MakeEl={Skeleton:Skeleton,TinyMCE:TinyMCE,InputGroup:InputGroup,Ellipses:Ellipses,Input:Input,InputWithReplacements:InputWithReplacements,Textarea:Textarea,Select:Select,Form:Form,ToolTip:ToolTip,Button:Button,Toggle:Toggle,Div:Div,Span:Span,Label:Label,InputRepeater:InputRepeater,Fragment:Fragment,Table:Table,TBody:TBody,THead:THead,TFoot:TFoot,Tr:Tr,Td:Td,Th:Th,Modal:Modal,ModalWithHeader:ModalWithHeader,MiniModal:MiniModal,ModalFrame:ModalFrame,TextPrompt:TextPrompt,ItemPicker:ItemPicker,Iframe:Iframe,Dashicon:Dashicon,ButtonToggle:ButtonToggle,Autocomplete:Autocomplete,ProgressBar:ProgressBar,Accordion:Accordion,Pg:Pg,Bold:Bold,Img:Img,An:An,Ul:Ul,Ol:Ol,Li:Li,H1:H1,H2:H2,H3:H3,H4:H4,Hr:Hr,Nav:Nav,maybeCall:maybeCall,forDom:forDom,forReact:forReact,makeEl:makeEl,makeElForReact:makeElForReact,htmlToReact:htmlToReact,htmlToElement:htmlToElement,htmlToElements:htmlToElements,domElementToReact:domElementToReact,useState:useState}})(jQuery??function(){throw new Error("jQuery was not loaded.")});4 `])};const Div=(attributes={},children=[])=>{return makeEl("div",attributes,children)};const Nav=(attributes={},children=[])=>{return makeEl("nav",attributes,children)};const Dashicon=(icon,children=null)=>{return makeEl("span",{className:`dashicons dashicons-${icon}`},children)};const Fragment=(children,atts={})=>{return makeEl("fragment",atts,children)};const Span=(attributes={},children=[])=>{return makeEl("span",attributes,children)};const Label=(attributes={},children=[])=>{return makeEl("label",attributes,children)};const InputRepeater=({id="",onChange=()=>{},rows=[],cells=[],sortable=false,fillRow=()=>Array(cells.length).fill(""),maxRows=0})=>{const handleChange=rows=>{onChange(rows);morphdom(document.getElementById(id),Repeater())};const removeRow=rowIndex=>{rows.splice(rowIndex,1);handleChange(rows)};const addRow=()=>{rows.push(fillRow());handleChange(rows)};const onCellChange=(rowIndex,cellIndex,value)=>{rows[rowIndex][cellIndex]=value;handleChange(rows)};const RepeaterRow=(row,rowIndex)=>Div({className:"gh-input-repeater-row",dataRow:rowIndex},[...cells.map((cellCallback,cellIndex)=>cellCallback({id:`${id}-cell-${rowIndex}-${cellIndex}`,name:`${id}[${rowIndex}][${cellIndex}]`,value:row[cellIndex]??"",dataRow:rowIndex,dataCell:cellIndex,onChange:e=>onCellChange(rowIndex,cellIndex,e.target.value),setValue:value=>onCellChange(rowIndex,cellIndex,value),onCellChange:onCellChange},row)),sortable?makeEl("span",{className:"handle",dataRow:rowIndex},Dashicon("move")):null,Button({className:"gh-button dashicon remove-row",dataRow:rowIndex,type:"button",onClick:e=>removeRow(rowIndex)},Dashicon("no-alt"))]);const Repeater=()=>Div({id:id,className:"gh-input-repeater",onCreate:el=>{if(!sortable){return}$(el).sortable({handle:".handle",update:(e,ui)=>{let $row=$(ui.item);let oldIndex=parseInt($row.data("row"));let curIndex=$row.index();let row=rows[oldIndex];rows.splice(oldIndex,1);rows.splice(curIndex,0,row);onChange(rows)}})}},[...rows.map((row,i)=>RepeaterRow(row,i)),maxRows===0||rows.length<maxRows?Div({className:"gh-input-repeater-row-add"},[`<div class="spacer"></div>`,Button({id:`${id}-add-row`,className:"add-row gh-button dashicon",onClick:e=>addRow(),type:"button"},Dashicon("plus-alt2"))]):null]);return Repeater()};const InputWithReplacements=({inputCallback=Input,...attributes})=>{return Div({className:"input-wrap"},[inputCallback(attributes),Button({className:"replacements-picker-start gh-button dashicon"},Dashicon("admin-users"))])};const Table=(atts,children)=>makeEl("table",atts,children);const THead=(atts,children)=>makeEl("thead",atts,children);const TBody=(atts,children)=>makeEl("tbody",atts,children);const TFoot=(atts,children)=>makeEl("tfoot",atts,children);const Tr=(atts,children)=>makeEl("tr",atts,children);const Td=(atts,children)=>makeEl("td",atts,children);const Th=(atts,children)=>makeEl("th",atts,children);const Modal=({dialogClasses="",className="",onOpen=()=>{},onClose=()=>{},width,closeButton=true,closeOnOverlayClick=true,overlay=true},children)=>{const Dialog=({header=null,content=null})=>Div({className:`gh-modal-dialog ${dialogClasses}`,style:{width:width}},[header,Div({className:"gh-modal-dialog-content"},content),closeButton&&!header?Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt")):null]);let modal=Div({className:`gh-modal ${className}`,tabindex:0},[overlay?Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}):null,Dialog({header:null,content:null})]);const close=()=>{onClose(modal);modal.remove()};const morph=(args={})=>{let content=getContent();let header=content.querySelector(".modal-header");morphdom(modal.querySelector(".gh-modal-dialog"),Dialog({header:header,content:content}),args)};const getContent=()=>maybeCall(children,{close:close,modal:modal,morph:morph});document.body.appendChild(modal);morph();onOpen({modal:modal,close:close,morph:morph});if(!modal.contains(document.activeElement)){modal.focus()}return modal};const ModalWithHeader=({header="",...args},children)=>Modal(args,methods=>Div({},[Div({className:"gh-header modal-header"},[MakeEl.H3({},header),MakeEl.Button({className:"gh-button icon secondary text",onClick:methods.close},MakeEl.Dashicon("no-alt"))]),maybeCall(children,methods)]));const TextPrompt=({header,text="",submitText="Save",onSubmit=text=>{}})=>MakeEl.ModalWithHeader({header:header,onOpen:()=>{let input=document.getElementById("prompt-text");input.focus();input.select()}},({close})=>MakeEl.Form({onSubmit:e=>{e.preventDefault();let fd=new FormData(e.currentTarget);let text=fd.get("prompt_text");onSubmit(text);close()}},[Div({className:"display-flex gap-5"},[Input({id:"prompt-text",name:"prompt_text",value:text}),Button({className:"gh-button primary",type:"submit"},submitText)])]));const ModalFrame=({onOpen=()=>{},onClose=()=>{},frameAttributes={},closeOnOverlayClick=true,closeOnEscape=true},children)=>{let modal=Div({className:"gh-modal",tabindex:0,onKeydown:e=>{if(closeOnEscape){if(e.key==="Esc"||e.key==="Escape"){close()}}}},[Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}),Div({className:`gh-modal-frame`,...frameAttributes},[])]);const close=()=>{onClose();modal.remove()};modal.querySelector(".gh-modal-frame").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen({close:close});modal.focus();return modal};const MiniModal=({selector="",target=null,from="right",dialogClasses="",onOpen=()=>{},onClose=()=>{},closeOnFocusout=true},children)=>{let modal=Div({className:"gh-modal mini gh-panel",tabindex:0,onFocusout:e=>{if(closeOnFocusout){if(!e.relatedTarget||!clickedIn(e.relatedTarget,".gh-modal.mini")){setTimeout(()=>{if(!clickedIn(document.activeElement,".gh-modal.mini")){close()}},10)}}},onCreate:el=>{el.focus()}},Div({className:`gh-modal-dialog ${dialogClasses}`},[Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt"))]));const close=()=>{onClose(modal);modal.remove()};modal.querySelector(".gh-modal-dialog").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen(modal);let targetElement=selector&&target===null?document.querySelector(selector):target;let{right,left,bottom,top}=targetElement.getBoundingClientRect();let{width,height}=modal.getBoundingClientRect();switch(from){case"left":modal.style.left=left+"px";break;case"right":modal.style.right=window.innerWidth-right+"px";modal.style.left="auto";break}if(top+height>window.innerHeight){modal.style.top=window.innerHeight-height-20+"px"}else{modal.style.top=top+"px"}setTimeout(()=>{modal.focus()},100);return modal};const Autocomplete=({fetchResults=async search=>{},onInput,...attributes})=>{let timeout;const State={pointer:0,results:[],input:null};const setValue=()=>{let item=State.results[State.pointer];State.input.value=item.id;State.input.dispatchEvent(new Event("change"));closeResults()};const updateResults=()=>{if(!State.results.length){closeResults();return}let resultsContainer=document.querySelector(".gh-autocomplete-results");let newResults=Results();if(!resultsContainer){document.body.appendChild(newResults)}else{morphdom(resultsContainer,newResults)}};const closeResults=()=>{let resultsContainer=document.querySelector(".gh-autocomplete-results");if(resultsContainer){resultsContainer.remove()}};const Results=()=>{const{results,input}=State;let{height,width,top,left}=input.getBoundingClientRect();return Div({className:"gh-autocomplete-results",style:{zIndex:999999,top:`${top+height}px`,left:`${left}px`,width:`${width}px`}},results.map(({id,text},index)=>makeEl("a",{className:`${index===State.pointer?"pointer":""}`,href:id,onClick:e=>{e.preventDefault();setValue()},onMouseenter:e=>{State.pointer=[...e.target.parentNode.children].indexOf(e.target);updateResults()}},text)))};return Input({...attributes,onFocusout:e=>{const input=e.target;input.classList.remove("has-results");if(e.relatedTarget&&clickedIn(e.relatedTarget,"a.pointer")){setValue()}closeResults()},onKeydown:e=>{const input=e.target;switch(e.key){case"Esc":case"Escape":e.preventDefault();closeResults();return;case"Down":case"ArrowDown":e.preventDefault();if(State.pointer<State.results.length){State.pointer++}break;case"Up":case"ArrowUp":e.preventDefault();if(State.pointer>0){State.pointer--}break;case"Enter":e.preventDefault();setValue();break;default:return}updateResults()},onInput:e=>{if(timeout){clearTimeout(timeout)}timeout=setTimeout(async()=>{const input=e.target;let search=input.value;State.results=await fetchResults(search);State.input=input;State.pointer=0;updateResults();input.classList.add("gh-autocomplete","has-results")},500);onInput(e)}})};const Ellipses=(content,atts={})=>Span({...atts,onCreate:el=>{let ellipses="";let count=0;let interval=setInterval(()=>{if(!el.parentNode){clearInterval(interval);return}count=(count+1)%4;ellipses=".".repeat(count);el.textContent=content+ellipses},500)}},content+"...");const ItemPicker=({id="",label="",placeholder="Type to search...",fetchOptions=(search,resolve)=>{},selected=[],onChange=()=>{},onSelect=()=>{},onCreate=()=>{},onUnselect=()=>{},createOption=val=>Promise.resolve({id:val,text:val}),tags=false,noneSelected="Any",isValidSelection=string=>Boolean(string),multiple=true,clearable=true,...attributes})=>{const state=Groundhogg.createState({search:"",searching:false,choosing:false,options:[],focused:false,morphing:false,clicked:false});const optionsVisible=()=>{return multiple?state.focused&&(state.searching||state.options.length||tags&&isValidSelection(state.search)):state.focused};if(!multiple&&!Array.isArray(selected)){selected=[selected]}let timeout;const setState=(newState,trigger)=>{state.set(newState);morph()};const handleOnChange=selected=>{if(timeout){clearTimeout(timeout)}if(multiple){onChange(selected);return}if(!selected.length){onChange(null);return}onChange(selected[0])};const morph=()=>{if(state.morphing){return}state.set({morphing:true});morphdom(document.getElementById(id),Render());state.set({morphing:false})};const focusSearch=()=>document.getElementById(id)?.querySelector(`input[type=search]`)?.focus();const focusPicker=()=>document.getElementById(id)?.focus();const focusParent=()=>document.getElementById(id)?.parentElement.focus();const handleCreateOption=value=>{state.options.unshift({id:value,text:value,create:true});handleSelectOption(value)};const handleSelectOption=async id=>{let option={...state.options.find(opt=>opt.id==id)};if(option.create){option.text=option.id}if(multiple){selected.push(option)}else{selected=[option]}if(option.create){await createOption(option.id).then(opt=>{selected=selected.map(item=>item.id==id?opt:item);option=opt})}onSelect(option);handleOnChange(selected);if(!multiple){state.set({focused:false})}setState({search:""});morph();if(multiple){focusSearch()}else{focusPicker()}};const handleUnselectOption=id=>{let opt=selected.find(opt=>opt.id===id);selected=selected.filter(opt=>opt.id!=id);onUnselect(opt);handleOnChange(selected);morph();if(multiple){focusSearch()}};const itemPickerItem=({id,text},index)=>{return Div({className:`gh-picker-item ${isValidSelection(id)?"":"is-invalid"}`,id:`item-${id}-${index}`},[Span({className:"gh-picker-item-text"},text),selected.length>1||clearable?Span({id:`delete-${id}-${index}`,className:"gh-picker-item-delete",tabindex:"0",dataId:id,onClick:e=>{handleUnselectOption(id)}},"×"):null])};const itemPickerOption=({id,text},index)=>{return Div({className:"gh-picker-option",dataId:id,tabindex:"0",id:`option-${index}-${id}`,onClick:e=>{handleSelectOption(id)}},text)};const itemPickerOptions=()=>{let picker=document.getElementById(id);let style={};if(picker){const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){style.top=bottom+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=maxHeight+"px"}else{style.bottom=window.innerHeight-top+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=top-20+"px"}}state.options=state.options.filter(opt=>!opt.create);if(!state.searching&&tags&&isValidSelection(state.search)){if(!state.options.find(opt=>opt.id==state.search||opt.text==state.search)){state.options.unshift({id:state.search,text:`Add "${state.search}"`,create:true})}}let options=state.options.filter(opt=>!selected.some(_opt=>opt.id==_opt.id));return Div({className:"gh-picker-options",style:style,onCreate:el=>{setTimeout(()=>{let picker=document.getElementById(id);let optionsContainer=picker.querySelector(".gh-picker-options");const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){optionsContainer.style.top=bottom+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=maxHeight+"px"}else{optionsContainer.style.bottom=window.innerHeight-top+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=top-20+"px"}if(!multiple){focusSearch()}},0)}},[multiple||!selected.length?null:SearchInput(),state.searching?Div({className:"gh-picker-no-options"},Ellipses(wp.i18n.__("Searching"))):null,...options.map((opt,i)=>itemPickerOption(opt,i)),options.length||state.searching?null:Div({className:"gh-picker-no-options"},"No results found.")])};const startSearch=search=>{setState({search:search,searching:true},"start search");if(timeout){clearTimeout(timeout)}timeout=setTimeout(()=>{fetchOptions(search).then(options=>{if(search!==state.search){return}setState({searching:false,options:options},"options fetched")})},500)};const SearchInput=()=>Input({className:"gh-picker-search",value:state.search,name:"search",type:"search",autocomplete:"off",id:`${id}-search-input`,placeholder:selected.length?placeholder:noneSelected,onInput:e=>startSearch(e.target.value),onFocus:e=>{startSearch(e.target.value)},onKeydown:e=>{if(tags){if(e.key!=="Enter"&&e.key!==","){return}handleCreateOption(e.target.value)}}});const Render=()=>Div({id:id,className:`gh-picker-container`,tabindex:"0",...attributes},Div({id:`${id}-picker`,className:`gh-picker ${optionsVisible()?"options-visible":""}`,tabindex:"0",onKeydown:e=>{if(e.key==="Escape"){setState({searching:false,focused:false});morph()}},onCreate:el=>{el.addEventListener("focusout",e=>{setTimeout(()=>{if(state.morphing||document.getElementById(id).contains(document.activeElement)){return}setState({search:"",options:[],searching:false,focused:false},"picker focusout")},10)});el.addEventListener("focusin",e=>{if(state.focused){return}setState({focused:true},"picker focused")})}},[Div({className:`gh-picker-selected ${multiple?"multiple":"single"}`},[selected.length&&label?Span({className:"gh-picker-label"},label):null,...selected.map((item,i)=>itemPickerItem(item,i)),multiple||!selected.length?SearchInput():null]),optionsVisible()?itemPickerOptions():null]));return Render()};const InputGroup=inputs=>Div({className:"gh-input-group"},inputs);const Iframe=({onCreate=()=>{},...attributes},content=null)=>{let blob=new Blob([content],{type:"text/html; charset=utf-8"});let src=URL.createObjectURL(blob);return makeEl("iframe",{...attributes,src:src})};const ToolTip=(content,position="bottom")=>{return Div({className:`gh-tooltip ${position}`},content)};const ButtonToggle=({id="",options=[],selected="",onChange=value=>{}})=>{const render=()=>Div({id:id,className:"gh-input-group"},options.map(opt=>ButtonOption(opt)));const ButtonOption=option=>Button({id:`${id}-opt-${option.id}`,className:`gh-button gh-button small ${selected===option.id?"dark":"grey"}`,onClick:e=>{selected=option.id;morphdom(document.getElementById(id),render());onChange(option.id)}},[option.text,option.tooltip?ToolTip(option.tooltip):null]);return render()};const ProgressBar=({percent=100,error=false,className=""})=>{return Div({className:`gh-progress-bar ${error?"gh-error":""} ${className}`},Div({className:"gh-progress-bar-fill",style:{width:`${percent||1}%`}},Span({className:"fill-amount"},`${Math.ceil(percent)}%`)))};const Img=props=>makeEl("img",props);const Pg=(props,children)=>makeEl("p",props,children);const Bold=(props,children)=>makeEl("b",props,children);const An=(props,children)=>{props={href:"javascript:void(0)",...props};return makeEl("a",props,children)};const Ul=(props,children)=>makeEl("ul",props,children);const Ol=(props,children)=>makeEl("ol",props,children);const Li=(props,children)=>makeEl("li",props,children);const H1=(props,children)=>makeEl("h1",props,children);const H2=(props,children)=>makeEl("h2",props,children);const H3=(props,children)=>makeEl("h3",props,children);const H4=(props,children)=>makeEl("h4",props,children);const Hr=(props,children)=>makeEl("hr",props,children);const Skeleton=(attributes,pieces)=>Div({className:"display-grid gap-10",...attributes},pieces.map(span=>Div({className:`${span} skeleton-loading`,style:{height:`40px`}})));const useState=(initialState,id)=>{const el=document.getElementById(id);if(el&&el.State){return el.State}return Groundhogg.createState(initialState)};const Accordion=({id,items,outlined=false,multiple=false})=>{const State=useState({expanded:null},id);const isExpanded=index=>multiple?State.get(`expand${index+1}`):State.expanded===index;const toggleExpand=index=>{if(multiple){State.set({[`expand${index+1}`]:!isExpanded(index)})}else{State.set({expanded:index})}};return Div({id:id,className:"gh-accordion",State:State},morph=>Fragment(items.map(({title,content},i)=>Div({className:`gh-accordion-item gh-accordion-row ${isExpanded(i)?"expanded":"collapsed"} ${outlined?"outlined":"has-box-shadow"}`,id:`${id}-item-${i+1}`},[Div({id:`${id}-item-toggle-${i+1}`,className:"display-flex gap-10 align-center",onClick:e=>{toggleExpand(i);morph()}},[Pg({className:"gh-accordion-item-title"},title),isExpanded(i)?Dashicon("arrow-up-alt2"):Dashicon("arrow-down-alt2")]),isExpanded(i)?content:null]))))};const TinyMCE=({id="",content="",config={},onChange=content=>{},...props})=>{let openEditor=document.getElementById(id);let height=300;if(openEditor&&openEditor.tinyMceInitialized){height=openEditor.previousElementSibling.getBoundingClientRect().height}return Div({id:`tiny-mce-${id}`,className:"tiny-mce-wrap"},Textarea({id:id,name:id.replaceAll("-","_"),value:content,style:{height:`${height}px`},onCreate:el=>{setTimeout(()=>{Groundhogg.element.tinymceElement(el.id,config,onChange);el.tinyMceInitialized=true})},...props}))};window.MakeEl={Skeleton:Skeleton,TinyMCE:TinyMCE,InputGroup:InputGroup,Ellipses:Ellipses,Input:Input,InputWithReplacements:InputWithReplacements,Textarea:Textarea,Select:Select,Form:Form,ToolTip:ToolTip,Button:Button,Toggle:Toggle,Div:Div,Span:Span,Label:Label,InputRepeater:InputRepeater,Fragment:Fragment,Table:Table,TBody:TBody,THead:THead,TFoot:TFoot,Tr:Tr,Td:Td,Th:Th,Modal:Modal,ModalWithHeader:ModalWithHeader,MiniModal:MiniModal,ModalFrame:ModalFrame,TextPrompt:TextPrompt,ItemPicker:ItemPicker,Iframe:Iframe,Dashicon:Dashicon,ButtonToggle:ButtonToggle,Autocomplete:Autocomplete,ProgressBar:ProgressBar,Accordion:Accordion,Pg:Pg,Bold:Bold,Img:Img,An:An,Ul:Ul,Ol:Ol,Li:Li,H1:H1,H2:H2,H3:H3,H4:H4,Hr:Hr,Nav:Nav,maybeCall:maybeCall,forDom:forDom,forReact:forReact,makeEl:makeEl,makeElForReact:makeElForReact,htmlToReact:htmlToReact,htmlToElement:htmlToElement,htmlToElements:htmlToElements,domElementToReact:domElementToReact,useState:useState}})(jQuery??function(){throw new Error("jQuery was not loaded.")}); -
groundhogg/tags/4.4/groundhogg.php
r3490332 r3497572 4 4 * Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash 5 5 * Description: CRM and marketing automation for WordPress 6 * Version: 4. 3.36 * Version: 4.4 7 7 * Author: Groundhogg Inc. 8 8 * Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash … … 25 25 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly 26 26 27 define( 'GROUNDHOGG_VERSION', '4. 3.3' );28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3. 2' );27 define( 'GROUNDHOGG_VERSION', '4.4' ); 28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3.3' ); 29 29 30 30 define( 'GROUNDHOGG__FILE__', __FILE__ ); -
groundhogg/tags/4.4/includes/form/form-v2.php
r3458296 r3497572 38 38 use function Groundhogg\process_events; 39 39 use function Groundhogg\remote_post_json; 40 use function Groundhogg\split_name; 40 41 use function Groundhogg\the_funnel; 41 42 use function Groundhogg\utils; … … 320 321 }, 321 322 'before' => function ( $field, $posted_data, &$args ) { 322 $args['first_name'] = sanitize_text_field( $posted_data->first_name ); 323 324 $parts = split_name( $posted_data->first_name ); 325 $args['first_name'] = sanitize_text_field( $parts[0] ); 326 327 if ( ! empty( $parts[1] ) ) { 328 $args['last_name'] = sanitize_text_field( $parts[1] ); 329 } 323 330 }, 324 331 'required' => function ( $field, $posted_data ) { -
groundhogg/tags/4.4/includes/functions.php
r3490332 r3497572 7551 7551 if ( empty( $providers ) ) { 7552 7552 $providers = json_decode( files()->get( GROUNDHOGG_ASSETS_PATH . 'lib/free-email-providers.json' ), true ); 7553 7554 /** 7555 * Allow adding additional free providers 7556 * 7557 * @param $providers array 7558 */ 7559 $providers = apply_filters( 'groundhogg/free_email_providers', $providers ); 7553 7560 } 7554 7561 … … 7567 7574 } 7568 7575 7569 return apply_filters( 'groundhogg/is_free_email_provider', in_array( get_email_address_hostname( $email ), get_free_email_providers() ) ); 7576 /** 7577 * Filter whether a given email is free or not 7578 * 7579 * @param bool $is_free whether the email was already marked as free 7580 * @param string $email the full email address being checked 7581 * @param string $hostname the parsed hostname of the email address 7582 */ 7583 return apply_filters( 'groundhogg/is_free_email_provider', in_array( get_email_address_hostname( $email ), get_free_email_providers() ), $email, get_email_address_hostname( $email ) ); 7570 7584 } 7571 7585 -
groundhogg/tags/4.4/includes/scripts.php
r3442828 r3497572 339 339 ], GROUNDHOGG_VERSION, true ); 340 340 341 wp_register_script( 'groundhogg-admin-email-detector', GROUNDHOGG_ASSETS_URL . 'js/admin/features/email-detector' . $dot_min . '.js', [ 342 'groundhogg-admin-components', 343 ], GROUNDHOGG_VERSION, true ); 344 341 345 wp_register_script( 'groundhogg-admin-notes', GROUNDHOGG_ASSETS_URL . 'js/admin/components/notes' . $dot_min . '.js', [ 342 346 'groundhogg-admin-element', … … 571 575 572 576 wp_enqueue_script( 'groundhogg-admin-functions' ); 577 578 if ( ! is_admin_groundhogg_page() && ! is_option_enabled( 'gh_disable_email_detection' ) && current_user_can( 'view_others_contacts' ) ){ 579 wp_enqueue_script( 'groundhogg-admin-email-detector' ); 580 } 573 581 574 582 wp_localize_script( 'groundhogg-admin', 'groundhogg_endpoints', [ -
groundhogg/trunk/README.txt
r3490332 r3497572 7 7 Tested up to: 6.9 8 8 Requires PHP: 7.1 9 Stable tag: 4. 3.39 Stable tag: 4.4 10 10 License: GPLv3 11 11 License URI: https://www.gnu.org/licenses/gpl.md … … 379 379 == Changelog == 380 380 381 = 4.4 (2026-04-01) = 382 * ADDED "Contact Peek" feature 383 * Allows you to instantly see basic contact details in any non-Groundhogg screen (like WooCommerce or MailHawk) in the WordPress admin whenever an email address is detected. 384 * If a contact does not exist, you'll be given the option to create one and edit it from where you are. 385 * ADDED Developer filter to register additional free inbox providers. 386 381 387 = 4.3.3 (2026-03-11) = 382 388 * ADDED Automatic detection of free inbox providers for contacts. … … 384 390 * ADDED New base components and classes for use in addons. 385 391 * FIXED Settings page pickers were not working due to a missing script if the toolbar widget is disabled. 386 * FIXED Broadcast send time estimate not showingcorrect time estimate.392 * FIXED Broadcast send time estimate is not showing the correct time estimate. 387 393 388 394 = 4.3.2 (2026-03-09) = -
groundhogg/trunk/admin/settings/settings-page.php
r3490332 r3497572 1080 1080 ], 1081 1081 ], 1082 'gh_disable_email_detection' => [ 1083 'id' => 'gh_disable_email_detection', 1084 'section' => 'interface', 1085 'label' => _x( 'Disable Admin Contact Peek Feature', 'settings', 'groundhogg' ), 1086 /* translators: white label name of the plugin, usually Groundhogg */ 1087 'desc' => sprintf( _x( 'Disable the appearance of the Contact Peek feature in non-%s admin screens.', 'settings', 'groundhogg' ), white_labeled_name() ), 1088 'type' => 'checkbox', 1089 'atts' => [ 1090 'label' => __( 'Disable', 'groundhogg' ), 1091 'name' => 'gh_disable_email_detection', 1092 'id' => 'gh_disable_email_detection', 1093 'value' => 'on', 1094 ], 1095 ], 1082 1096 'gh_default_contact_tab' => [ 1083 1097 'id' => 'gh_default_contact_tab', -
groundhogg/trunk/assets/js/admin/make-el.js
r3464589 r3497572 847 847 848 848 const close = () => { 849 onClose( )849 onClose( modal ) 850 850 modal.remove() 851 851 } … … 855 855 856 856 // Run before positioning 857 onOpen( )857 onOpen( modal ) 858 858 859 859 let targetElement = selector && target === null ? document.querySelector(selector) : target … … 871 871 } = modal.getBoundingClientRect() 872 872 873 // console.log({right, left, bottom, top, width, height}) 874 873 875 switch (from) { 874 876 case 'left': … … 876 878 break 877 879 case 'right': 878 modal.style.left = ( right - width ) + 'px' 880 modal.style.right = ( window.innerWidth - right ) + 'px' 881 modal.style.left = 'auto' 879 882 break 880 883 } … … 887 890 } 888 891 889 modal.focus() 892 setTimeout(()=>{ 893 modal.focus() 894 }, 100) 890 895 891 896 return modal -
groundhogg/trunk/assets/js/admin/make-el.min.js
r3464589 r3497572 2 2 <span class="on">${onLabel}</span> 3 3 <span class="off">${offLabel}</span> 4 `])};const Div=(attributes={},children=[])=>{return makeEl("div",attributes,children)};const Nav=(attributes={},children=[])=>{return makeEl("nav",attributes,children)};const Dashicon=(icon,children=null)=>{return makeEl("span",{className:`dashicons dashicons-${icon}`},children)};const Fragment=(children,atts={})=>{return makeEl("fragment",atts,children)};const Span=(attributes={},children=[])=>{return makeEl("span",attributes,children)};const Label=(attributes={},children=[])=>{return makeEl("label",attributes,children)};const InputRepeater=({id="",onChange=()=>{},rows=[],cells=[],sortable=false,fillRow=()=>Array(cells.length).fill(""),maxRows=0})=>{const handleChange=rows=>{onChange(rows);morphdom(document.getElementById(id),Repeater())};const removeRow=rowIndex=>{rows.splice(rowIndex,1);handleChange(rows)};const addRow=()=>{rows.push(fillRow());handleChange(rows)};const onCellChange=(rowIndex,cellIndex,value)=>{rows[rowIndex][cellIndex]=value;handleChange(rows)};const RepeaterRow=(row,rowIndex)=>Div({className:"gh-input-repeater-row",dataRow:rowIndex},[...cells.map((cellCallback,cellIndex)=>cellCallback({id:`${id}-cell-${rowIndex}-${cellIndex}`,name:`${id}[${rowIndex}][${cellIndex}]`,value:row[cellIndex]??"",dataRow:rowIndex,dataCell:cellIndex,onChange:e=>onCellChange(rowIndex,cellIndex,e.target.value),setValue:value=>onCellChange(rowIndex,cellIndex,value),onCellChange:onCellChange},row)),sortable?makeEl("span",{className:"handle",dataRow:rowIndex},Dashicon("move")):null,Button({className:"gh-button dashicon remove-row",dataRow:rowIndex,type:"button",onClick:e=>removeRow(rowIndex)},Dashicon("no-alt"))]);const Repeater=()=>Div({id:id,className:"gh-input-repeater",onCreate:el=>{if(!sortable){return}$(el).sortable({handle:".handle",update:(e,ui)=>{let $row=$(ui.item);let oldIndex=parseInt($row.data("row"));let curIndex=$row.index();let row=rows[oldIndex];rows.splice(oldIndex,1);rows.splice(curIndex,0,row);onChange(rows)}})}},[...rows.map((row,i)=>RepeaterRow(row,i)),maxRows===0||rows.length<maxRows?Div({className:"gh-input-repeater-row-add"},[`<div class="spacer"></div>`,Button({id:`${id}-add-row`,className:"add-row gh-button dashicon",onClick:e=>addRow(),type:"button"},Dashicon("plus-alt2"))]):null]);return Repeater()};const InputWithReplacements=({inputCallback=Input,...attributes})=>{return Div({className:"input-wrap"},[inputCallback(attributes),Button({className:"replacements-picker-start gh-button dashicon"},Dashicon("admin-users"))])};const Table=(atts,children)=>makeEl("table",atts,children);const THead=(atts,children)=>makeEl("thead",atts,children);const TBody=(atts,children)=>makeEl("tbody",atts,children);const TFoot=(atts,children)=>makeEl("tfoot",atts,children);const Tr=(atts,children)=>makeEl("tr",atts,children);const Td=(atts,children)=>makeEl("td",atts,children);const Th=(atts,children)=>makeEl("th",atts,children);const Modal=({dialogClasses="",className="",onOpen=()=>{},onClose=()=>{},width,closeButton=true,closeOnOverlayClick=true,overlay=true},children)=>{const Dialog=({header=null,content=null})=>Div({className:`gh-modal-dialog ${dialogClasses}`,style:{width:width}},[header,Div({className:"gh-modal-dialog-content"},content),closeButton&&!header?Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt")):null]);let modal=Div({className:`gh-modal ${className}`,tabindex:0},[overlay?Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}):null,Dialog({header:null,content:null})]);const close=()=>{onClose(modal);modal.remove()};const morph=(args={})=>{let content=getContent();let header=content.querySelector(".modal-header");morphdom(modal.querySelector(".gh-modal-dialog"),Dialog({header:header,content:content}),args)};const getContent=()=>maybeCall(children,{close:close,modal:modal,morph:morph});document.body.appendChild(modal);morph();onOpen({modal:modal,close:close,morph:morph});if(!modal.contains(document.activeElement)){modal.focus()}return modal};const ModalWithHeader=({header="",...args},children)=>Modal(args,methods=>Div({},[Div({className:"gh-header modal-header"},[MakeEl.H3({},header),MakeEl.Button({className:"gh-button icon secondary text",onClick:methods.close},MakeEl.Dashicon("no-alt"))]),maybeCall(children,methods)]));const TextPrompt=({header,text="",submitText="Save",onSubmit=text=>{}})=>MakeEl.ModalWithHeader({header:header,onOpen:()=>{let input=document.getElementById("prompt-text");input.focus();input.select()}},({close})=>MakeEl.Form({onSubmit:e=>{e.preventDefault();let fd=new FormData(e.currentTarget);let text=fd.get("prompt_text");onSubmit(text);close()}},[Div({className:"display-flex gap-5"},[Input({id:"prompt-text",name:"prompt_text",value:text}),Button({className:"gh-button primary",type:"submit"},submitText)])]));const ModalFrame=({onOpen=()=>{},onClose=()=>{},frameAttributes={},closeOnOverlayClick=true,closeOnEscape=true},children)=>{let modal=Div({className:"gh-modal",tabindex:0,onKeydown:e=>{if(closeOnEscape){if(e.key==="Esc"||e.key==="Escape"){close()}}}},[Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}),Div({className:`gh-modal-frame`,...frameAttributes},[])]);const close=()=>{onClose();modal.remove()};modal.querySelector(".gh-modal-frame").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen({close:close});modal.focus();return modal};const MiniModal=({selector="",target=null,from="right",dialogClasses="",onOpen=()=>{},onClose=()=>{},closeOnFocusout=true},children)=>{let modal=Div({className:"gh-modal mini gh-panel",tabindex:0,onFocusout:e=>{if(closeOnFocusout){if(!e.relatedTarget||!clickedIn(e.relatedTarget,".gh-modal.mini")){setTimeout(()=>{if(!clickedIn(document.activeElement,".gh-modal.mini")){close()}},10)}}},onCreate:el=>{el.focus()}},Div({className:`gh-modal-dialog ${dialogClasses}`},[Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt"))]));const close=()=>{onClose( );modal.remove()};modal.querySelector(".gh-modal-dialog").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen();let targetElement=selector&&target===null?document.querySelector(selector):target;let{right,left,bottom,top}=targetElement.getBoundingClientRect();let{width,height}=modal.getBoundingClientRect();switch(from){case"left":modal.style.left=left+"px";break;case"right":modal.style.left=right-width+"px";break}if(top+height>window.innerHeight){modal.style.top=window.innerHeight-height-20+"px"}else{modal.style.top=top+"px"}modal.focus();return modal};const Autocomplete=({fetchResults=async search=>{},onInput,...attributes})=>{let timeout;const State={pointer:0,results:[],input:null};const setValue=()=>{let item=State.results[State.pointer];State.input.value=item.id;State.input.dispatchEvent(new Event("change"));closeResults()};const updateResults=()=>{if(!State.results.length){closeResults();return}let resultsContainer=document.querySelector(".gh-autocomplete-results");let newResults=Results();if(!resultsContainer){document.body.appendChild(newResults)}else{morphdom(resultsContainer,newResults)}};const closeResults=()=>{let resultsContainer=document.querySelector(".gh-autocomplete-results");if(resultsContainer){resultsContainer.remove()}};const Results=()=>{const{results,input}=State;let{height,width,top,left}=input.getBoundingClientRect();return Div({className:"gh-autocomplete-results",style:{zIndex:999999,top:`${top+height}px`,left:`${left}px`,width:`${width}px`}},results.map(({id,text},index)=>makeEl("a",{className:`${index===State.pointer?"pointer":""}`,href:id,onClick:e=>{e.preventDefault();setValue()},onMouseenter:e=>{State.pointer=[...e.target.parentNode.children].indexOf(e.target);updateResults()}},text)))};return Input({...attributes,onFocusout:e=>{const input=e.target;input.classList.remove("has-results");if(e.relatedTarget&&clickedIn(e.relatedTarget,"a.pointer")){setValue()}closeResults()},onKeydown:e=>{const input=e.target;switch(e.key){case"Esc":case"Escape":e.preventDefault();closeResults();return;case"Down":case"ArrowDown":e.preventDefault();if(State.pointer<State.results.length){State.pointer++}break;case"Up":case"ArrowUp":e.preventDefault();if(State.pointer>0){State.pointer--}break;case"Enter":e.preventDefault();setValue();break;default:return}updateResults()},onInput:e=>{if(timeout){clearTimeout(timeout)}timeout=setTimeout(async()=>{const input=e.target;let search=input.value;State.results=await fetchResults(search);State.input=input;State.pointer=0;updateResults();input.classList.add("gh-autocomplete","has-results")},500);onInput(e)}})};const Ellipses=(content,atts={})=>Span({...atts,onCreate:el=>{let ellipses="";let count=0;let interval=setInterval(()=>{if(!el.parentNode){clearInterval(interval);return}count=(count+1)%4;ellipses=".".repeat(count);el.textContent=content+ellipses},500)}},content+"...");const ItemPicker=({id="",label="",placeholder="Type to search...",fetchOptions=(search,resolve)=>{},selected=[],onChange=()=>{},onSelect=()=>{},onCreate=()=>{},onUnselect=()=>{},createOption=val=>Promise.resolve({id:val,text:val}),tags=false,noneSelected="Any",isValidSelection=string=>Boolean(string),multiple=true,clearable=true,...attributes})=>{const state=Groundhogg.createState({search:"",searching:false,choosing:false,options:[],focused:false,morphing:false,clicked:false});const optionsVisible=()=>{return multiple?state.focused&&(state.searching||state.options.length||tags&&isValidSelection(state.search)):state.focused};if(!multiple&&!Array.isArray(selected)){selected=[selected]}let timeout;const setState=(newState,trigger)=>{state.set(newState);morph()};const handleOnChange=selected=>{if(timeout){clearTimeout(timeout)}if(multiple){onChange(selected);return}if(!selected.length){onChange(null);return}onChange(selected[0])};const morph=()=>{if(state.morphing){return}state.set({morphing:true});morphdom(document.getElementById(id),Render());state.set({morphing:false})};const focusSearch=()=>document.getElementById(id)?.querySelector(`input[type=search]`)?.focus();const focusPicker=()=>document.getElementById(id)?.focus();const focusParent=()=>document.getElementById(id)?.parentElement.focus();const handleCreateOption=value=>{state.options.unshift({id:value,text:value,create:true});handleSelectOption(value)};const handleSelectOption=async id=>{let option={...state.options.find(opt=>opt.id==id)};if(option.create){option.text=option.id}if(multiple){selected.push(option)}else{selected=[option]}if(option.create){await createOption(option.id).then(opt=>{selected=selected.map(item=>item.id==id?opt:item);option=opt})}onSelect(option);handleOnChange(selected);if(!multiple){state.set({focused:false})}setState({search:""});morph();if(multiple){focusSearch()}else{focusPicker()}};const handleUnselectOption=id=>{let opt=selected.find(opt=>opt.id===id);selected=selected.filter(opt=>opt.id!=id);onUnselect(opt);handleOnChange(selected);morph();if(multiple){focusSearch()}};const itemPickerItem=({id,text},index)=>{return Div({className:`gh-picker-item ${isValidSelection(id)?"":"is-invalid"}`,id:`item-${id}-${index}`},[Span({className:"gh-picker-item-text"},text),selected.length>1||clearable?Span({id:`delete-${id}-${index}`,className:"gh-picker-item-delete",tabindex:"0",dataId:id,onClick:e=>{handleUnselectOption(id)}},"×"):null])};const itemPickerOption=({id,text},index)=>{return Div({className:"gh-picker-option",dataId:id,tabindex:"0",id:`option-${index}-${id}`,onClick:e=>{handleSelectOption(id)}},text)};const itemPickerOptions=()=>{let picker=document.getElementById(id);let style={};if(picker){const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){style.top=bottom+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=maxHeight+"px"}else{style.bottom=window.innerHeight-top+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=top-20+"px"}}state.options=state.options.filter(opt=>!opt.create);if(!state.searching&&tags&&isValidSelection(state.search)){if(!state.options.find(opt=>opt.id==state.search||opt.text==state.search)){state.options.unshift({id:state.search,text:`Add "${state.search}"`,create:true})}}let options=state.options.filter(opt=>!selected.some(_opt=>opt.id==_opt.id));return Div({className:"gh-picker-options",style:style,onCreate:el=>{setTimeout(()=>{let picker=document.getElementById(id);let optionsContainer=picker.querySelector(".gh-picker-options");const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){optionsContainer.style.top=bottom+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=maxHeight+"px"}else{optionsContainer.style.bottom=window.innerHeight-top+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=top-20+"px"}if(!multiple){focusSearch()}},0)}},[multiple||!selected.length?null:SearchInput(),state.searching?Div({className:"gh-picker-no-options"},Ellipses(wp.i18n.__("Searching"))):null,...options.map((opt,i)=>itemPickerOption(opt,i)),options.length||state.searching?null:Div({className:"gh-picker-no-options"},"No results found.")])};const startSearch=search=>{setState({search:search,searching:true},"start search");if(timeout){clearTimeout(timeout)}timeout=setTimeout(()=>{fetchOptions(search).then(options=>{if(search!==state.search){return}setState({searching:false,options:options},"options fetched")})},500)};const SearchInput=()=>Input({className:"gh-picker-search",value:state.search,name:"search",type:"search",autocomplete:"off",id:`${id}-search-input`,placeholder:selected.length?placeholder:noneSelected,onInput:e=>startSearch(e.target.value),onFocus:e=>{startSearch(e.target.value)},onKeydown:e=>{if(tags){if(e.key!=="Enter"&&e.key!==","){return}handleCreateOption(e.target.value)}}});const Render=()=>Div({id:id,className:`gh-picker-container`,tabindex:"0",...attributes},Div({id:`${id}-picker`,className:`gh-picker ${optionsVisible()?"options-visible":""}`,tabindex:"0",onKeydown:e=>{if(e.key==="Escape"){setState({searching:false,focused:false});morph()}},onCreate:el=>{el.addEventListener("focusout",e=>{setTimeout(()=>{if(state.morphing||document.getElementById(id).contains(document.activeElement)){return}setState({search:"",options:[],searching:false,focused:false},"picker focusout")},10)});el.addEventListener("focusin",e=>{if(state.focused){return}setState({focused:true},"picker focused")})}},[Div({className:`gh-picker-selected ${multiple?"multiple":"single"}`},[selected.length&&label?Span({className:"gh-picker-label"},label):null,...selected.map((item,i)=>itemPickerItem(item,i)),multiple||!selected.length?SearchInput():null]),optionsVisible()?itemPickerOptions():null]));return Render()};const InputGroup=inputs=>Div({className:"gh-input-group"},inputs);const Iframe=({onCreate=()=>{},...attributes},content=null)=>{let blob=new Blob([content],{type:"text/html; charset=utf-8"});let src=URL.createObjectURL(blob);return makeEl("iframe",{...attributes,src:src})};const ToolTip=(content,position="bottom")=>{return Div({className:`gh-tooltip ${position}`},content)};const ButtonToggle=({id="",options=[],selected="",onChange=value=>{}})=>{const render=()=>Div({id:id,className:"gh-input-group"},options.map(opt=>ButtonOption(opt)));const ButtonOption=option=>Button({id:`${id}-opt-${option.id}`,className:`gh-button gh-button small ${selected===option.id?"dark":"grey"}`,onClick:e=>{selected=option.id;morphdom(document.getElementById(id),render());onChange(option.id)}},[option.text,option.tooltip?ToolTip(option.tooltip):null]);return render()};const ProgressBar=({percent=100,error=false,className=""})=>{return Div({className:`gh-progress-bar ${error?"gh-error":""} ${className}`},Div({className:"gh-progress-bar-fill",style:{width:`${percent||1}%`}},Span({className:"fill-amount"},`${Math.ceil(percent)}%`)))};const Img=props=>makeEl("img",props);const Pg=(props,children)=>makeEl("p",props,children);const Bold=(props,children)=>makeEl("b",props,children);const An=(props,children)=>{props={href:"javascript:void(0)",...props};return makeEl("a",props,children)};const Ul=(props,children)=>makeEl("ul",props,children);const Ol=(props,children)=>makeEl("ol",props,children);const Li=(props,children)=>makeEl("li",props,children);const H1=(props,children)=>makeEl("h1",props,children);const H2=(props,children)=>makeEl("h2",props,children);const H3=(props,children)=>makeEl("h3",props,children);const H4=(props,children)=>makeEl("h4",props,children);const Hr=(props,children)=>makeEl("hr",props,children);const Skeleton=(attributes,pieces)=>Div({className:"display-grid gap-10",...attributes},pieces.map(span=>Div({className:`${span} skeleton-loading`,style:{height:`40px`}})));const useState=(initialState,id)=>{const el=document.getElementById(id);if(el&&el.State){return el.State}return Groundhogg.createState(initialState)};const Accordion=({id,items,outlined=false,multiple=false})=>{const State=useState({expanded:null},id);const isExpanded=index=>multiple?State.get(`expand${index+1}`):State.expanded===index;const toggleExpand=index=>{if(multiple){State.set({[`expand${index+1}`]:!isExpanded(index)})}else{State.set({expanded:index})}};return Div({id:id,className:"gh-accordion",State:State},morph=>Fragment(items.map(({title,content},i)=>Div({className:`gh-accordion-item gh-accordion-row ${isExpanded(i)?"expanded":"collapsed"} ${outlined?"outlined":"has-box-shadow"}`,id:`${id}-item-${i+1}`},[Div({id:`${id}-item-toggle-${i+1}`,className:"display-flex gap-10 align-center",onClick:e=>{toggleExpand(i);morph()}},[Pg({className:"gh-accordion-item-title"},title),isExpanded(i)?Dashicon("arrow-up-alt2"):Dashicon("arrow-down-alt2")]),isExpanded(i)?content:null]))))};const TinyMCE=({id="",content="",config={},onChange=content=>{},...props})=>{let openEditor=document.getElementById(id);let height=300;if(openEditor&&openEditor.tinyMceInitialized){height=openEditor.previousElementSibling.getBoundingClientRect().height}return Div({id:`tiny-mce-${id}`,className:"tiny-mce-wrap"},Textarea({id:id,name:id.replaceAll("-","_"),value:content,style:{height:`${height}px`},onCreate:el=>{setTimeout(()=>{Groundhogg.element.tinymceElement(el.id,config,onChange);el.tinyMceInitialized=true})},...props}))};window.MakeEl={Skeleton:Skeleton,TinyMCE:TinyMCE,InputGroup:InputGroup,Ellipses:Ellipses,Input:Input,InputWithReplacements:InputWithReplacements,Textarea:Textarea,Select:Select,Form:Form,ToolTip:ToolTip,Button:Button,Toggle:Toggle,Div:Div,Span:Span,Label:Label,InputRepeater:InputRepeater,Fragment:Fragment,Table:Table,TBody:TBody,THead:THead,TFoot:TFoot,Tr:Tr,Td:Td,Th:Th,Modal:Modal,ModalWithHeader:ModalWithHeader,MiniModal:MiniModal,ModalFrame:ModalFrame,TextPrompt:TextPrompt,ItemPicker:ItemPicker,Iframe:Iframe,Dashicon:Dashicon,ButtonToggle:ButtonToggle,Autocomplete:Autocomplete,ProgressBar:ProgressBar,Accordion:Accordion,Pg:Pg,Bold:Bold,Img:Img,An:An,Ul:Ul,Ol:Ol,Li:Li,H1:H1,H2:H2,H3:H3,H4:H4,Hr:Hr,Nav:Nav,maybeCall:maybeCall,forDom:forDom,forReact:forReact,makeEl:makeEl,makeElForReact:makeElForReact,htmlToReact:htmlToReact,htmlToElement:htmlToElement,htmlToElements:htmlToElements,domElementToReact:domElementToReact,useState:useState}})(jQuery??function(){throw new Error("jQuery was not loaded.")});4 `])};const Div=(attributes={},children=[])=>{return makeEl("div",attributes,children)};const Nav=(attributes={},children=[])=>{return makeEl("nav",attributes,children)};const Dashicon=(icon,children=null)=>{return makeEl("span",{className:`dashicons dashicons-${icon}`},children)};const Fragment=(children,atts={})=>{return makeEl("fragment",atts,children)};const Span=(attributes={},children=[])=>{return makeEl("span",attributes,children)};const Label=(attributes={},children=[])=>{return makeEl("label",attributes,children)};const InputRepeater=({id="",onChange=()=>{},rows=[],cells=[],sortable=false,fillRow=()=>Array(cells.length).fill(""),maxRows=0})=>{const handleChange=rows=>{onChange(rows);morphdom(document.getElementById(id),Repeater())};const removeRow=rowIndex=>{rows.splice(rowIndex,1);handleChange(rows)};const addRow=()=>{rows.push(fillRow());handleChange(rows)};const onCellChange=(rowIndex,cellIndex,value)=>{rows[rowIndex][cellIndex]=value;handleChange(rows)};const RepeaterRow=(row,rowIndex)=>Div({className:"gh-input-repeater-row",dataRow:rowIndex},[...cells.map((cellCallback,cellIndex)=>cellCallback({id:`${id}-cell-${rowIndex}-${cellIndex}`,name:`${id}[${rowIndex}][${cellIndex}]`,value:row[cellIndex]??"",dataRow:rowIndex,dataCell:cellIndex,onChange:e=>onCellChange(rowIndex,cellIndex,e.target.value),setValue:value=>onCellChange(rowIndex,cellIndex,value),onCellChange:onCellChange},row)),sortable?makeEl("span",{className:"handle",dataRow:rowIndex},Dashicon("move")):null,Button({className:"gh-button dashicon remove-row",dataRow:rowIndex,type:"button",onClick:e=>removeRow(rowIndex)},Dashicon("no-alt"))]);const Repeater=()=>Div({id:id,className:"gh-input-repeater",onCreate:el=>{if(!sortable){return}$(el).sortable({handle:".handle",update:(e,ui)=>{let $row=$(ui.item);let oldIndex=parseInt($row.data("row"));let curIndex=$row.index();let row=rows[oldIndex];rows.splice(oldIndex,1);rows.splice(curIndex,0,row);onChange(rows)}})}},[...rows.map((row,i)=>RepeaterRow(row,i)),maxRows===0||rows.length<maxRows?Div({className:"gh-input-repeater-row-add"},[`<div class="spacer"></div>`,Button({id:`${id}-add-row`,className:"add-row gh-button dashicon",onClick:e=>addRow(),type:"button"},Dashicon("plus-alt2"))]):null]);return Repeater()};const InputWithReplacements=({inputCallback=Input,...attributes})=>{return Div({className:"input-wrap"},[inputCallback(attributes),Button({className:"replacements-picker-start gh-button dashicon"},Dashicon("admin-users"))])};const Table=(atts,children)=>makeEl("table",atts,children);const THead=(atts,children)=>makeEl("thead",atts,children);const TBody=(atts,children)=>makeEl("tbody",atts,children);const TFoot=(atts,children)=>makeEl("tfoot",atts,children);const Tr=(atts,children)=>makeEl("tr",atts,children);const Td=(atts,children)=>makeEl("td",atts,children);const Th=(atts,children)=>makeEl("th",atts,children);const Modal=({dialogClasses="",className="",onOpen=()=>{},onClose=()=>{},width,closeButton=true,closeOnOverlayClick=true,overlay=true},children)=>{const Dialog=({header=null,content=null})=>Div({className:`gh-modal-dialog ${dialogClasses}`,style:{width:width}},[header,Div({className:"gh-modal-dialog-content"},content),closeButton&&!header?Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt")):null]);let modal=Div({className:`gh-modal ${className}`,tabindex:0},[overlay?Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}):null,Dialog({header:null,content:null})]);const close=()=>{onClose(modal);modal.remove()};const morph=(args={})=>{let content=getContent();let header=content.querySelector(".modal-header");morphdom(modal.querySelector(".gh-modal-dialog"),Dialog({header:header,content:content}),args)};const getContent=()=>maybeCall(children,{close:close,modal:modal,morph:morph});document.body.appendChild(modal);morph();onOpen({modal:modal,close:close,morph:morph});if(!modal.contains(document.activeElement)){modal.focus()}return modal};const ModalWithHeader=({header="",...args},children)=>Modal(args,methods=>Div({},[Div({className:"gh-header modal-header"},[MakeEl.H3({},header),MakeEl.Button({className:"gh-button icon secondary text",onClick:methods.close},MakeEl.Dashicon("no-alt"))]),maybeCall(children,methods)]));const TextPrompt=({header,text="",submitText="Save",onSubmit=text=>{}})=>MakeEl.ModalWithHeader({header:header,onOpen:()=>{let input=document.getElementById("prompt-text");input.focus();input.select()}},({close})=>MakeEl.Form({onSubmit:e=>{e.preventDefault();let fd=new FormData(e.currentTarget);let text=fd.get("prompt_text");onSubmit(text);close()}},[Div({className:"display-flex gap-5"},[Input({id:"prompt-text",name:"prompt_text",value:text}),Button({className:"gh-button primary",type:"submit"},submitText)])]));const ModalFrame=({onOpen=()=>{},onClose=()=>{},frameAttributes={},closeOnOverlayClick=true,closeOnEscape=true},children)=>{let modal=Div({className:"gh-modal",tabindex:0,onKeydown:e=>{if(closeOnEscape){if(e.key==="Esc"||e.key==="Escape"){close()}}}},[Div({className:"gh-modal-overlay",onClick:e=>{if(closeOnOverlayClick){close()}}}),Div({className:`gh-modal-frame`,...frameAttributes},[])]);const close=()=>{onClose();modal.remove()};modal.querySelector(".gh-modal-frame").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen({close:close});modal.focus();return modal};const MiniModal=({selector="",target=null,from="right",dialogClasses="",onOpen=()=>{},onClose=()=>{},closeOnFocusout=true},children)=>{let modal=Div({className:"gh-modal mini gh-panel",tabindex:0,onFocusout:e=>{if(closeOnFocusout){if(!e.relatedTarget||!clickedIn(e.relatedTarget,".gh-modal.mini")){setTimeout(()=>{if(!clickedIn(document.activeElement,".gh-modal.mini")){close()}},10)}}},onCreate:el=>{el.focus()}},Div({className:`gh-modal-dialog ${dialogClasses}`},[Button({className:"dashicon-button gh-modal-button-close-top gh-modal-button-close",onClick:e=>{close()}},Dashicon("no-alt"))]));const close=()=>{onClose(modal);modal.remove()};modal.querySelector(".gh-modal-dialog").appendChild(Fragment(maybeCall(children,{close:close})));document.body.appendChild(modal);onOpen(modal);let targetElement=selector&&target===null?document.querySelector(selector):target;let{right,left,bottom,top}=targetElement.getBoundingClientRect();let{width,height}=modal.getBoundingClientRect();switch(from){case"left":modal.style.left=left+"px";break;case"right":modal.style.right=window.innerWidth-right+"px";modal.style.left="auto";break}if(top+height>window.innerHeight){modal.style.top=window.innerHeight-height-20+"px"}else{modal.style.top=top+"px"}setTimeout(()=>{modal.focus()},100);return modal};const Autocomplete=({fetchResults=async search=>{},onInput,...attributes})=>{let timeout;const State={pointer:0,results:[],input:null};const setValue=()=>{let item=State.results[State.pointer];State.input.value=item.id;State.input.dispatchEvent(new Event("change"));closeResults()};const updateResults=()=>{if(!State.results.length){closeResults();return}let resultsContainer=document.querySelector(".gh-autocomplete-results");let newResults=Results();if(!resultsContainer){document.body.appendChild(newResults)}else{morphdom(resultsContainer,newResults)}};const closeResults=()=>{let resultsContainer=document.querySelector(".gh-autocomplete-results");if(resultsContainer){resultsContainer.remove()}};const Results=()=>{const{results,input}=State;let{height,width,top,left}=input.getBoundingClientRect();return Div({className:"gh-autocomplete-results",style:{zIndex:999999,top:`${top+height}px`,left:`${left}px`,width:`${width}px`}},results.map(({id,text},index)=>makeEl("a",{className:`${index===State.pointer?"pointer":""}`,href:id,onClick:e=>{e.preventDefault();setValue()},onMouseenter:e=>{State.pointer=[...e.target.parentNode.children].indexOf(e.target);updateResults()}},text)))};return Input({...attributes,onFocusout:e=>{const input=e.target;input.classList.remove("has-results");if(e.relatedTarget&&clickedIn(e.relatedTarget,"a.pointer")){setValue()}closeResults()},onKeydown:e=>{const input=e.target;switch(e.key){case"Esc":case"Escape":e.preventDefault();closeResults();return;case"Down":case"ArrowDown":e.preventDefault();if(State.pointer<State.results.length){State.pointer++}break;case"Up":case"ArrowUp":e.preventDefault();if(State.pointer>0){State.pointer--}break;case"Enter":e.preventDefault();setValue();break;default:return}updateResults()},onInput:e=>{if(timeout){clearTimeout(timeout)}timeout=setTimeout(async()=>{const input=e.target;let search=input.value;State.results=await fetchResults(search);State.input=input;State.pointer=0;updateResults();input.classList.add("gh-autocomplete","has-results")},500);onInput(e)}})};const Ellipses=(content,atts={})=>Span({...atts,onCreate:el=>{let ellipses="";let count=0;let interval=setInterval(()=>{if(!el.parentNode){clearInterval(interval);return}count=(count+1)%4;ellipses=".".repeat(count);el.textContent=content+ellipses},500)}},content+"...");const ItemPicker=({id="",label="",placeholder="Type to search...",fetchOptions=(search,resolve)=>{},selected=[],onChange=()=>{},onSelect=()=>{},onCreate=()=>{},onUnselect=()=>{},createOption=val=>Promise.resolve({id:val,text:val}),tags=false,noneSelected="Any",isValidSelection=string=>Boolean(string),multiple=true,clearable=true,...attributes})=>{const state=Groundhogg.createState({search:"",searching:false,choosing:false,options:[],focused:false,morphing:false,clicked:false});const optionsVisible=()=>{return multiple?state.focused&&(state.searching||state.options.length||tags&&isValidSelection(state.search)):state.focused};if(!multiple&&!Array.isArray(selected)){selected=[selected]}let timeout;const setState=(newState,trigger)=>{state.set(newState);morph()};const handleOnChange=selected=>{if(timeout){clearTimeout(timeout)}if(multiple){onChange(selected);return}if(!selected.length){onChange(null);return}onChange(selected[0])};const morph=()=>{if(state.morphing){return}state.set({morphing:true});morphdom(document.getElementById(id),Render());state.set({morphing:false})};const focusSearch=()=>document.getElementById(id)?.querySelector(`input[type=search]`)?.focus();const focusPicker=()=>document.getElementById(id)?.focus();const focusParent=()=>document.getElementById(id)?.parentElement.focus();const handleCreateOption=value=>{state.options.unshift({id:value,text:value,create:true});handleSelectOption(value)};const handleSelectOption=async id=>{let option={...state.options.find(opt=>opt.id==id)};if(option.create){option.text=option.id}if(multiple){selected.push(option)}else{selected=[option]}if(option.create){await createOption(option.id).then(opt=>{selected=selected.map(item=>item.id==id?opt:item);option=opt})}onSelect(option);handleOnChange(selected);if(!multiple){state.set({focused:false})}setState({search:""});morph();if(multiple){focusSearch()}else{focusPicker()}};const handleUnselectOption=id=>{let opt=selected.find(opt=>opt.id===id);selected=selected.filter(opt=>opt.id!=id);onUnselect(opt);handleOnChange(selected);morph();if(multiple){focusSearch()}};const itemPickerItem=({id,text},index)=>{return Div({className:`gh-picker-item ${isValidSelection(id)?"":"is-invalid"}`,id:`item-${id}-${index}`},[Span({className:"gh-picker-item-text"},text),selected.length>1||clearable?Span({id:`delete-${id}-${index}`,className:"gh-picker-item-delete",tabindex:"0",dataId:id,onClick:e=>{handleUnselectOption(id)}},"×"):null])};const itemPickerOption=({id,text},index)=>{return Div({className:"gh-picker-option",dataId:id,tabindex:"0",id:`option-${index}-${id}`,onClick:e=>{handleSelectOption(id)}},text)};const itemPickerOptions=()=>{let picker=document.getElementById(id);let style={};if(picker){const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){style.top=bottom+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=maxHeight+"px"}else{style.bottom=window.innerHeight-top+"px";style.left=left+"px";style.width=width+"px";style.maxHeight=top-20+"px"}}state.options=state.options.filter(opt=>!opt.create);if(!state.searching&&tags&&isValidSelection(state.search)){if(!state.options.find(opt=>opt.id==state.search||opt.text==state.search)){state.options.unshift({id:state.search,text:`Add "${state.search}"`,create:true})}}let options=state.options.filter(opt=>!selected.some(_opt=>opt.id==_opt.id));return Div({className:"gh-picker-options",style:style,onCreate:el=>{setTimeout(()=>{let picker=document.getElementById(id);let optionsContainer=picker.querySelector(".gh-picker-options");const{left,right,top,bottom,width}=picker.getBoundingClientRect();let maxHeight=window.innerHeight-bottom-20;if(maxHeight>100){optionsContainer.style.top=bottom+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=maxHeight+"px"}else{optionsContainer.style.bottom=window.innerHeight-top+"px";optionsContainer.style.left=left+"px";optionsContainer.style.width=width+"px";optionsContainer.style.maxHeight=top-20+"px"}if(!multiple){focusSearch()}},0)}},[multiple||!selected.length?null:SearchInput(),state.searching?Div({className:"gh-picker-no-options"},Ellipses(wp.i18n.__("Searching"))):null,...options.map((opt,i)=>itemPickerOption(opt,i)),options.length||state.searching?null:Div({className:"gh-picker-no-options"},"No results found.")])};const startSearch=search=>{setState({search:search,searching:true},"start search");if(timeout){clearTimeout(timeout)}timeout=setTimeout(()=>{fetchOptions(search).then(options=>{if(search!==state.search){return}setState({searching:false,options:options},"options fetched")})},500)};const SearchInput=()=>Input({className:"gh-picker-search",value:state.search,name:"search",type:"search",autocomplete:"off",id:`${id}-search-input`,placeholder:selected.length?placeholder:noneSelected,onInput:e=>startSearch(e.target.value),onFocus:e=>{startSearch(e.target.value)},onKeydown:e=>{if(tags){if(e.key!=="Enter"&&e.key!==","){return}handleCreateOption(e.target.value)}}});const Render=()=>Div({id:id,className:`gh-picker-container`,tabindex:"0",...attributes},Div({id:`${id}-picker`,className:`gh-picker ${optionsVisible()?"options-visible":""}`,tabindex:"0",onKeydown:e=>{if(e.key==="Escape"){setState({searching:false,focused:false});morph()}},onCreate:el=>{el.addEventListener("focusout",e=>{setTimeout(()=>{if(state.morphing||document.getElementById(id).contains(document.activeElement)){return}setState({search:"",options:[],searching:false,focused:false},"picker focusout")},10)});el.addEventListener("focusin",e=>{if(state.focused){return}setState({focused:true},"picker focused")})}},[Div({className:`gh-picker-selected ${multiple?"multiple":"single"}`},[selected.length&&label?Span({className:"gh-picker-label"},label):null,...selected.map((item,i)=>itemPickerItem(item,i)),multiple||!selected.length?SearchInput():null]),optionsVisible()?itemPickerOptions():null]));return Render()};const InputGroup=inputs=>Div({className:"gh-input-group"},inputs);const Iframe=({onCreate=()=>{},...attributes},content=null)=>{let blob=new Blob([content],{type:"text/html; charset=utf-8"});let src=URL.createObjectURL(blob);return makeEl("iframe",{...attributes,src:src})};const ToolTip=(content,position="bottom")=>{return Div({className:`gh-tooltip ${position}`},content)};const ButtonToggle=({id="",options=[],selected="",onChange=value=>{}})=>{const render=()=>Div({id:id,className:"gh-input-group"},options.map(opt=>ButtonOption(opt)));const ButtonOption=option=>Button({id:`${id}-opt-${option.id}`,className:`gh-button gh-button small ${selected===option.id?"dark":"grey"}`,onClick:e=>{selected=option.id;morphdom(document.getElementById(id),render());onChange(option.id)}},[option.text,option.tooltip?ToolTip(option.tooltip):null]);return render()};const ProgressBar=({percent=100,error=false,className=""})=>{return Div({className:`gh-progress-bar ${error?"gh-error":""} ${className}`},Div({className:"gh-progress-bar-fill",style:{width:`${percent||1}%`}},Span({className:"fill-amount"},`${Math.ceil(percent)}%`)))};const Img=props=>makeEl("img",props);const Pg=(props,children)=>makeEl("p",props,children);const Bold=(props,children)=>makeEl("b",props,children);const An=(props,children)=>{props={href:"javascript:void(0)",...props};return makeEl("a",props,children)};const Ul=(props,children)=>makeEl("ul",props,children);const Ol=(props,children)=>makeEl("ol",props,children);const Li=(props,children)=>makeEl("li",props,children);const H1=(props,children)=>makeEl("h1",props,children);const H2=(props,children)=>makeEl("h2",props,children);const H3=(props,children)=>makeEl("h3",props,children);const H4=(props,children)=>makeEl("h4",props,children);const Hr=(props,children)=>makeEl("hr",props,children);const Skeleton=(attributes,pieces)=>Div({className:"display-grid gap-10",...attributes},pieces.map(span=>Div({className:`${span} skeleton-loading`,style:{height:`40px`}})));const useState=(initialState,id)=>{const el=document.getElementById(id);if(el&&el.State){return el.State}return Groundhogg.createState(initialState)};const Accordion=({id,items,outlined=false,multiple=false})=>{const State=useState({expanded:null},id);const isExpanded=index=>multiple?State.get(`expand${index+1}`):State.expanded===index;const toggleExpand=index=>{if(multiple){State.set({[`expand${index+1}`]:!isExpanded(index)})}else{State.set({expanded:index})}};return Div({id:id,className:"gh-accordion",State:State},morph=>Fragment(items.map(({title,content},i)=>Div({className:`gh-accordion-item gh-accordion-row ${isExpanded(i)?"expanded":"collapsed"} ${outlined?"outlined":"has-box-shadow"}`,id:`${id}-item-${i+1}`},[Div({id:`${id}-item-toggle-${i+1}`,className:"display-flex gap-10 align-center",onClick:e=>{toggleExpand(i);morph()}},[Pg({className:"gh-accordion-item-title"},title),isExpanded(i)?Dashicon("arrow-up-alt2"):Dashicon("arrow-down-alt2")]),isExpanded(i)?content:null]))))};const TinyMCE=({id="",content="",config={},onChange=content=>{},...props})=>{let openEditor=document.getElementById(id);let height=300;if(openEditor&&openEditor.tinyMceInitialized){height=openEditor.previousElementSibling.getBoundingClientRect().height}return Div({id:`tiny-mce-${id}`,className:"tiny-mce-wrap"},Textarea({id:id,name:id.replaceAll("-","_"),value:content,style:{height:`${height}px`},onCreate:el=>{setTimeout(()=>{Groundhogg.element.tinymceElement(el.id,config,onChange);el.tinyMceInitialized=true})},...props}))};window.MakeEl={Skeleton:Skeleton,TinyMCE:TinyMCE,InputGroup:InputGroup,Ellipses:Ellipses,Input:Input,InputWithReplacements:InputWithReplacements,Textarea:Textarea,Select:Select,Form:Form,ToolTip:ToolTip,Button:Button,Toggle:Toggle,Div:Div,Span:Span,Label:Label,InputRepeater:InputRepeater,Fragment:Fragment,Table:Table,TBody:TBody,THead:THead,TFoot:TFoot,Tr:Tr,Td:Td,Th:Th,Modal:Modal,ModalWithHeader:ModalWithHeader,MiniModal:MiniModal,ModalFrame:ModalFrame,TextPrompt:TextPrompt,ItemPicker:ItemPicker,Iframe:Iframe,Dashicon:Dashicon,ButtonToggle:ButtonToggle,Autocomplete:Autocomplete,ProgressBar:ProgressBar,Accordion:Accordion,Pg:Pg,Bold:Bold,Img:Img,An:An,Ul:Ul,Ol:Ol,Li:Li,H1:H1,H2:H2,H3:H3,H4:H4,Hr:Hr,Nav:Nav,maybeCall:maybeCall,forDom:forDom,forReact:forReact,makeEl:makeEl,makeElForReact:makeElForReact,htmlToReact:htmlToReact,htmlToElement:htmlToElement,htmlToElements:htmlToElements,domElementToReact:domElementToReact,useState:useState}})(jQuery??function(){throw new Error("jQuery was not loaded.")}); -
groundhogg/trunk/groundhogg.php
r3490332 r3497572 4 4 * Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash 5 5 * Description: CRM and marketing automation for WordPress 6 * Version: 4. 3.36 * Version: 4.4 7 7 * Author: Groundhogg Inc. 8 8 * Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash … … 25 25 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly 26 26 27 define( 'GROUNDHOGG_VERSION', '4. 3.3' );28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3. 2' );27 define( 'GROUNDHOGG_VERSION', '4.4' ); 28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3.3' ); 29 29 30 30 define( 'GROUNDHOGG__FILE__', __FILE__ ); -
groundhogg/trunk/includes/form/form-v2.php
r3458296 r3497572 38 38 use function Groundhogg\process_events; 39 39 use function Groundhogg\remote_post_json; 40 use function Groundhogg\split_name; 40 41 use function Groundhogg\the_funnel; 41 42 use function Groundhogg\utils; … … 320 321 }, 321 322 'before' => function ( $field, $posted_data, &$args ) { 322 $args['first_name'] = sanitize_text_field( $posted_data->first_name ); 323 324 $parts = split_name( $posted_data->first_name ); 325 $args['first_name'] = sanitize_text_field( $parts[0] ); 326 327 if ( ! empty( $parts[1] ) ) { 328 $args['last_name'] = sanitize_text_field( $parts[1] ); 329 } 323 330 }, 324 331 'required' => function ( $field, $posted_data ) { -
groundhogg/trunk/includes/functions.php
r3490332 r3497572 7551 7551 if ( empty( $providers ) ) { 7552 7552 $providers = json_decode( files()->get( GROUNDHOGG_ASSETS_PATH . 'lib/free-email-providers.json' ), true ); 7553 7554 /** 7555 * Allow adding additional free providers 7556 * 7557 * @param $providers array 7558 */ 7559 $providers = apply_filters( 'groundhogg/free_email_providers', $providers ); 7553 7560 } 7554 7561 … … 7567 7574 } 7568 7575 7569 return apply_filters( 'groundhogg/is_free_email_provider', in_array( get_email_address_hostname( $email ), get_free_email_providers() ) ); 7576 /** 7577 * Filter whether a given email is free or not 7578 * 7579 * @param bool $is_free whether the email was already marked as free 7580 * @param string $email the full email address being checked 7581 * @param string $hostname the parsed hostname of the email address 7582 */ 7583 return apply_filters( 'groundhogg/is_free_email_provider', in_array( get_email_address_hostname( $email ), get_free_email_providers() ), $email, get_email_address_hostname( $email ) ); 7570 7584 } 7571 7585 -
groundhogg/trunk/includes/scripts.php
r3442828 r3497572 339 339 ], GROUNDHOGG_VERSION, true ); 340 340 341 wp_register_script( 'groundhogg-admin-email-detector', GROUNDHOGG_ASSETS_URL . 'js/admin/features/email-detector' . $dot_min . '.js', [ 342 'groundhogg-admin-components', 343 ], GROUNDHOGG_VERSION, true ); 344 341 345 wp_register_script( 'groundhogg-admin-notes', GROUNDHOGG_ASSETS_URL . 'js/admin/components/notes' . $dot_min . '.js', [ 342 346 'groundhogg-admin-element', … … 571 575 572 576 wp_enqueue_script( 'groundhogg-admin-functions' ); 577 578 if ( ! is_admin_groundhogg_page() && ! is_option_enabled( 'gh_disable_email_detection' ) && current_user_can( 'view_others_contacts' ) ){ 579 wp_enqueue_script( 'groundhogg-admin-email-detector' ); 580 } 573 581 574 582 wp_localize_script( 'groundhogg-admin', 'groundhogg_endpoints', [
Note: See TracChangeset
for help on using the changeset viewer.