Plugin Directory

Changeset 3497572


Ignore:
Timestamp:
04/02/2026 02:27:04 PM (14 hours ago)
Author:
trainingbusinesspros
Message:

Update to version 4.4 from GitHub

Location:
groundhogg
Files:
4 added
16 edited
1 copied

Legend:

Unmodified
Added
Removed
  • groundhogg/tags/4.4/README.txt

    r3490332 r3497572  
    77Tested up to: 6.9
    88Requires PHP: 7.1
    9 Stable tag: 4.3.3
     9Stable tag: 4.4
    1010License: GPLv3
    1111License URI: https://www.gnu.org/licenses/gpl.md
     
    379379== Changelog ==
    380380
     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
    381387= 4.3.3 (2026-03-11) =
    382388* ADDED Automatic detection of free inbox providers for contacts.
     
    384390* ADDED New base components and classes for use in addons.
    385391* 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 showing correct time estimate.
     392* FIXED Broadcast send time estimate is not showing the correct time estimate.
    387393
    388394= 4.3.2 (2026-03-09) =
  • groundhogg/tags/4.4/admin/settings/settings-page.php

    r3490332 r3497572  
    10801080                ],
    10811081            ],
     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            ],
    10821096            'gh_default_contact_tab'                 => [
    10831097                'id'      => 'gh_default_contact_tab',
  • groundhogg/tags/4.4/assets/js/admin/make-el.js

    r3464589 r3497572  
    847847
    848848    const close = () => {
    849       onClose()
     849      onClose( modal )
    850850      modal.remove()
    851851    }
     
    855855
    856856    // Run before positioning
    857     onOpen()
     857    onOpen( modal )
    858858
    859859    let targetElement = selector && target === null ? document.querySelector(selector) : target
     
    871871    } = modal.getBoundingClientRect()
    872872
     873    // console.log({right, left, bottom, top, width, height})
     874
    873875    switch (from) {
    874876      case 'left':
     
    876878        break
    877879      case 'right':
    878         modal.style.left = ( right - width ) + 'px'
     880        modal.style.right = ( window.innerWidth - right ) + 'px'
     881        modal.style.left = 'auto'
    879882        break
    880883    }
     
    887890    }
    888891
    889     modal.focus()
     892    setTimeout(()=>{
     893      modal.focus()
     894    }, 100)
    890895
    891896    return modal
  • groundhogg/tags/4.4/assets/js/admin/make-el.min.js

    r3464589 r3497572  
    22      <span class="on">${onLabel}</span>
    33      <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)}},"&times;"):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)}},"&times;"):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  
    44 * Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash
    55 * Description: CRM and marketing automation for WordPress
    6  * Version: 4.3.3
     6 * Version: 4.4
    77 * Author: Groundhogg Inc.
    88 * Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash
     
    2525if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
    2626
    27 define( 'GROUNDHOGG_VERSION', '4.3.3' );
    28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3.2' );
     27define( 'GROUNDHOGG_VERSION', '4.4' );
     28define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3.3' );
    2929
    3030define( 'GROUNDHOGG__FILE__', __FILE__ );
  • groundhogg/tags/4.4/includes/form/form-v2.php

    r3458296 r3497572  
    3838use function Groundhogg\process_events;
    3939use function Groundhogg\remote_post_json;
     40use function Groundhogg\split_name;
    4041use function Groundhogg\the_funnel;
    4142use function Groundhogg\utils;
     
    320321                },
    321322                '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                    }
    323330                },
    324331                'required' => function ( $field, $posted_data ) {
  • groundhogg/tags/4.4/includes/functions.php

    r3490332 r3497572  
    75517551    if ( empty( $providers ) ) {
    75527552        $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 );
    75537560    }
    75547561
     
    75677574    }
    75687575
    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 ) );
    75707584}
    75717585
  • groundhogg/tags/4.4/includes/scripts.php

    r3442828 r3497572  
    339339        ], GROUNDHOGG_VERSION, true );
    340340
     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
    341345        wp_register_script( 'groundhogg-admin-notes', GROUNDHOGG_ASSETS_URL . 'js/admin/components/notes' . $dot_min . '.js', [
    342346            'groundhogg-admin-element',
     
    571575
    572576        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        }
    573581
    574582        wp_localize_script( 'groundhogg-admin', 'groundhogg_endpoints', [
  • groundhogg/trunk/README.txt

    r3490332 r3497572  
    77Tested up to: 6.9
    88Requires PHP: 7.1
    9 Stable tag: 4.3.3
     9Stable tag: 4.4
    1010License: GPLv3
    1111License URI: https://www.gnu.org/licenses/gpl.md
     
    379379== Changelog ==
    380380
     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
    381387= 4.3.3 (2026-03-11) =
    382388* ADDED Automatic detection of free inbox providers for contacts.
     
    384390* ADDED New base components and classes for use in addons.
    385391* 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 showing correct time estimate.
     392* FIXED Broadcast send time estimate is not showing the correct time estimate.
    387393
    388394= 4.3.2 (2026-03-09) =
  • groundhogg/trunk/admin/settings/settings-page.php

    r3490332 r3497572  
    10801080                ],
    10811081            ],
     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            ],
    10821096            'gh_default_contact_tab'                 => [
    10831097                'id'      => 'gh_default_contact_tab',
  • groundhogg/trunk/assets/js/admin/make-el.js

    r3464589 r3497572  
    847847
    848848    const close = () => {
    849       onClose()
     849      onClose( modal )
    850850      modal.remove()
    851851    }
     
    855855
    856856    // Run before positioning
    857     onOpen()
     857    onOpen( modal )
    858858
    859859    let targetElement = selector && target === null ? document.querySelector(selector) : target
     
    871871    } = modal.getBoundingClientRect()
    872872
     873    // console.log({right, left, bottom, top, width, height})
     874
    873875    switch (from) {
    874876      case 'left':
     
    876878        break
    877879      case 'right':
    878         modal.style.left = ( right - width ) + 'px'
     880        modal.style.right = ( window.innerWidth - right ) + 'px'
     881        modal.style.left = 'auto'
    879882        break
    880883    }
     
    887890    }
    888891
    889     modal.focus()
     892    setTimeout(()=>{
     893      modal.focus()
     894    }, 100)
    890895
    891896    return modal
  • groundhogg/trunk/assets/js/admin/make-el.min.js

    r3464589 r3497572  
    22      <span class="on">${onLabel}</span>
    33      <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)}},"&times;"):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)}},"&times;"):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  
    44 * Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash
    55 * Description: CRM and marketing automation for WordPress
    6  * Version: 4.3.3
     6 * Version: 4.4
    77 * Author: Groundhogg Inc.
    88 * Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash
     
    2525if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
    2626
    27 define( 'GROUNDHOGG_VERSION', '4.3.3' );
    28 define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3.2' );
     27define( 'GROUNDHOGG_VERSION', '4.4' );
     28define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.3.3' );
    2929
    3030define( 'GROUNDHOGG__FILE__', __FILE__ );
  • groundhogg/trunk/includes/form/form-v2.php

    r3458296 r3497572  
    3838use function Groundhogg\process_events;
    3939use function Groundhogg\remote_post_json;
     40use function Groundhogg\split_name;
    4041use function Groundhogg\the_funnel;
    4142use function Groundhogg\utils;
     
    320321                },
    321322                '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                    }
    323330                },
    324331                'required' => function ( $field, $posted_data ) {
  • groundhogg/trunk/includes/functions.php

    r3490332 r3497572  
    75517551    if ( empty( $providers ) ) {
    75527552        $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 );
    75537560    }
    75547561
     
    75677574    }
    75687575
    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 ) );
    75707584}
    75717585
  • groundhogg/trunk/includes/scripts.php

    r3442828 r3497572  
    339339        ], GROUNDHOGG_VERSION, true );
    340340
     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
    341345        wp_register_script( 'groundhogg-admin-notes', GROUNDHOGG_ASSETS_URL . 'js/admin/components/notes' . $dot_min . '.js', [
    342346            'groundhogg-admin-element',
     
    571575
    572576        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        }
    573581
    574582        wp_localize_script( 'groundhogg-admin', 'groundhogg_endpoints', [
Note: See TracChangeset for help on using the changeset viewer.