<?php

# PLUGIN PREVIEW BY TEXTPATTERN.INFO


/*
    adi_contact - Form field validation, spam prevention, email header/body extras, database update, MailChimp subscription

    Written by Adi Gilbert

    Released under the GNU General Public License

    Version history:
    0.6        - fixed: problem with validate type="equal" error message
            - enhancement: database update functionality
            - enhancement: MailChimp subscription
            - TXP 4.6 compatibility
            - new tags: adi_contact_headers & adi_contact_body
            - ZCR 4.5+ compatibility: "errorElement" class used in addition to "zemRequirederrorElement"
    0.5        - fixed: javascript doesn't find contact <form> if zcr input & error forms are separate (thanks jakob)
            - fixed: error message if no fields filled in when using "value" attribute
            - fixed: errors in checkbox processing (thanks jakob)
            - enhancement: extended "name"+"value" dependency to "restrict" & "number" (for jakob)
    0.4        - change: new adi_contact_checkboxes tag (adi_contact_combo 'checkbox' attribute withdrawn)
            - enhancement: adi_contact_combo if "value" then "required" (for sacripant)
            - fix: internationalised 'Yes' & 'No' in checkboxes (thanks sacripant)
            - help section formatting errors (thanks net-carver) !!!!!!!! HAVEN'T ACTUALLY FIXED ANYTHING !!!!!!!!!
            - fix: no more duplicate javascript insertion
            - class action: 'adi_contact_combo_error', 'adi_contact_validate_error', 'adi_contact_spam_error' (for net-carver)
            - new adi_contact_validate type 'base64' (for net-carver)
            - javascript code rewrite (& bug fixes: <select>, multiple forms)
    0.3        - enhancement: new adi_contact_validate option type="equal" (for kevinpotts)
            - fix: adi_contact_combo now processes an unticked checkbox correctly (as not being a field of interest) (thanks Logoleptic)
            - fix: adi_contact_validate coding error (thanks planeth)
    0.2        - enhancement: new adi_contact_combo attribute 'required'; equivalent to restrict="equal" number="1", enables custom error messages (inspired by Bloke)
            - enhancement: new adi_contact_validate attribute & option type="custom" pattern="pattern" (for Bloke)
            - enhancement: new adi_contact_validate attribute option type="url" (inspired by Bloke)
            - enhancement: new adi_contact_spam attribute 'exclude' (e.g. use in conjunction with adi_contact_validate type="url")
            - fix: adi_contact_combo fails to cope with checkboxes (new attribute 'checkbox') (thanks valpc)
    0.1.1    - fix: adi_contact_spam error message field lists incorrectly generated (both standard & custom)
    0.1        - initial release

*/

// TXP 4.6 tag registration
if (class_exists('\Textpattern\Tag\Registry')) {
    
Txp::get('\Textpattern\Tag\Registry')
        ->
register('adi_contact_body')
        ->
register('adi_contact_checkboxes')
        ->
register('adi_contact_combo')
        ->
register('adi_contact_db_data')
        ->
register('adi_contact_db')
        ->
register('adi_contact_debug')
        ->
register('adi_contact_headers')
        ->
register('adi_contact_javascript')
        ->
register('adi_contact_labels')
        ->
register('adi_contact_mailchimp_data')
        ->
register('adi_contact_mailchimp_email')
        ->
register('adi_contact_mailchimp')
        ->
register('adi_contact_spam')
        ->
register('adi_contact_validate')
    ;
}

global 
$prefs,$zem_contact_form,$zem_contact_submit,$zem_contact_error,$zem_contact_labels,$zem_contact_values,$adi_contact_js_added,$txp_using_svn;

if (@
txpinterface == 'admin') {
    
register_callback('adi_contact_lifecycle','plugin_lifecycle.adi_contact');
}

if (
txpinterface === 'public') {
    
register_callback('adi_contact_db_update','zemcontact.submit');
    
register_callback('adi_contact_mailchimp_update','zemcontact.submit');
    
register_callback('adi_contact_deliver','zemcontact.deliver');
}

function 
adi_contact_lifecycle($event,$step) {
// enforce the load order
    
if ($step == 'installed')
        
$result safe_update('txp_plugin',"load_order = 9","name = 'adi_contact'");
}

function 
adi_contact_javascript() {
// Javascript function for injecting error classes

    
$javascript = <<<END_SCRIPT
<script type="text/javascript">
function addErrorClass(list,thisFunction) {

    var zemRequiredClass = 'zemRequirederrorElement errorElement', zemErrorElementClass = 'errorElement' , adiContactClass = 'adi_contact_error';

    if (thisFunction === undefined)
        thisClass = '';
    else
        thisClass = thisFunction + '_error';
    thisClass = thisClass + ' ' + adiContactClass;
    if (thisFunction == 'adi_contact_combo')
        thisClass = thisClass + ' ' + zemRequiredClass;
    if (thisFunction == 'adi_contact_validate')
        thisClass = thisClass + ' ' + zemErrorElementClass;
    if (thisFunction == 'adi_contact_spam')
        thisClass = thisClass + ' ' + zemErrorElementClass;

    // former method of identifying form don't work all the time - if zcr input & error forms are separate then the <form> tags lose their id!
    // var formId = window.location.hash.substr(1); // convert #zcr... to zcr...
    // var thisForm = document.getElementById(formId);

    // new method
    // get #zcr... anchor from URL
    var anchor = window.location.hash;

    // find <form> that has the action="" that matches the #zcr
    var formsArray = document.getElementsByTagName("form");
    for (i=0; i<formsArray.length; i++) {
        var action = formsArray[i].getAttribute('action');
        var pattern = RegExp(anchor,'i'); // i.e. /#zcr.../i
        if (pattern.test(action)) // is #zcr... in form action="blah blah"?
            thisForm = formsArray[i];
    }

    // apply error class to list
    var labels = thisForm.getElementsByTagName('label'); // find all <label> in this form
    var types = ["input","textarea","select"]; // tag types we're interested in
    for (var x = 0; (thisType = types[x]); x++) { // for each tag type
        var tags = thisForm.getElementsByTagName(thisType); // get all instances of tag type in this form
        for (var i = 0; (thisTag = tags[i]); i++) { // for each tag of this type
            if (list[thisTag.name]) { // name is on the list
                for (var j = 0; (label = labels[j]); j++) {
                    if (label.htmlFor == thisTag.id) { // label 'for' matches input id
                        label.className += ' ' + thisClass;
                        thisTag.className += ' ' + thisClass;
//                         console.log("type=",thisTag.type,"name=",thisTag.name,"id=",thisTag.id,"label for=",label.htmlFor,"classes=",thisClass);
                    }
                }
            }
        }
    }

}
</script>
END_SCRIPT;

    return 
$javascript;
}

function 
adi_contact_insert_javascript() {
// insert javascript function
    
global $adi_contact_js_added;

    
// only want to insert javascript function once
    
if (isset($adi_contact_js_added))
        return 
'';
    else {
        
$adi_contact_js_added '';
        return 
adi_contact_javascript();
    }
}

function 
adi_contact_literal($list) {
// create string in form of an object literal {"field1":1,"field2":1 ... }
    
$list explode(',',$list); // convert comma separated list to array
    
foreach ($list as $index => $value// create literal as array
        
$literal[] = '"'.$value.'":1';

    return 
'{'.join(',',$literal).'}'// return as {comma separated list}
}

function 
adi_contact_message($default_msg,$user_msg) {
// generate zem_contact error message
// ZCR will strip out duplicate error messages in $zem_contact_error
    
global $zem_contact_error,$label_list,$number,$require_label_list;

    if (
$user_msg) {
        
$user_msg str_replace('[NAMES]',$label_list,$user_msg);
        
$user_msg str_replace('[REQUIRE]',$require_label_list,$user_msg);
        
$user_msg str_replace('[NUMBER]',$number,$user_msg);
        
$zem_contact_error[] = $user_msg;
    }
    else
        
$zem_contact_error[] = $default_msg;
}

function 
adi_contact_get_labels($name_list) {
// translate list of names into list of labels
    
global $adi_contact_labels,$zem_contact_labels;

    
$label_list = array();

    if (!isset(
$adi_contact_labels)) // initialise to empty array (<txp:adi_contact_labels not used or not placed before <txp:adi_contact_combo /> etc.
        
$adi_contact_labels = array();

    foreach (
$name_list as $index => $value// try three options
        
if (array_key_exists($value,$adi_contact_labels)) // 1. user supplied label via adi_contact_labels tag
            
$label_list[] = $adi_contact_labels[$value];
        else if (isset(
$zem_contact_labels)) // check for presence of label supplied by submitted form
            
if (array_key_exists($value,$zem_contact_labels)) // 2. use label supplied by submitted form
                
$label_list[] = $zem_contact_labels[$value];
            else 
// 3. use the name itself, but pretty it up
                
$label_list[] = ucfirst(strtolower($value));
        else 
// 3. use the name itself, but pretty it up
            
$label_list[] = ucfirst(strtolower($value));

    return 
join(', ',$label_list);
}

function 
adi_contact_spam($atts) {
// tag to check form fields for mail headers and link tags
    
global $zem_contact_form,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$adi_contact_labels,$label_list;

    
extract(lAtts(array(
        
'names'                    => '',    // list of form fields we're interested in (default = all submitted fields)
        
'exclude'                => '',    // list of form fields to be ignored (default = none)
        
'message'                => '',    // alternative error message (with [NAMES] & [NUMBER])
        
'detect_mail_headers'    => '1',
        
'detect_link_tags'        => '1',
        
'allow_www'                => '0',    // allow presence of simple www.... links
        
'debug'                    => '0'
    
), $atts));

    
$return adi_contact_insert_javascript();

    if (
$names) {
        
$name_list explode(',',$names); // convert comma separated list to array
        
foreach ($name_list as $index => $field// remove extraneous spaces
            
$name_list[$index] = trim($field);
    }
    else 
// get list of all submitted fields
        
isset($zem_contact_values) ?
            
$name_list array_keys($zem_contact_values) :
            
$name_list = array();
    if (
$exclude) {
        
$exclude_list explode(',',$exclude); // convert comma separated list to array
        
foreach ($exclude_list as $index => $field// remove extraneous spaces
            
$exclude_list[$index] = trim($field);
    }
    else
        
$exclude_list = array();
    
$original_name_list $name_list// remember original list of fields
    
$name_list array_diff($name_list,$exclude_list); // include minus exclude
    
if ($name_list) { // there's work to do
        // link tags
        
$allow_www ?
            
$link_tags '/(\[url|http|href)/i' :
            
$link_tags '/(\[url|http|www|href)/i';
        
$link_tag_list str_replace('/(\\','',$link_tags);
        
$link_tag_list str_replace(')/i','',$link_tag_list);
        
$link_tag_list str_replace('|',', ',$link_tag_list);
        
// mail headers
        
$mail_headers '/(to\:|from\:|bcc\:|multipart|cc\:|Content\-Transfer\-Encoding\:|Content\-Type\:|Mime\-Version\:)/i';
        
$mail_headers_list str_replace('/(','',$mail_headers);
        
$mail_headers_list str_replace(')/i','',$mail_headers_list);
        
$mail_headers_list str_replace('\\','',$mail_headers_list);
        
$mail_headers_list str_replace('|',', ',$mail_headers_list);
        
$mail_header_error FALSE;
        
$link_tag_error FALSE;
        
$error_list = array();
        foreach (
$name_list as $field => $value) { // scan fields for spam
            
if ($detect_mail_headers)
                if (
preg_match($mail_headers,$zem_contact_values[$value])) {
                    
$mail_header_error TRUE;
                    
$error_list[] = $value// add field name to list
                
}
            if (
$detect_link_tags)
                if (
preg_match($link_tags,$zem_contact_values[$value])) {
                    
$link_tag_error TRUE;
                    
$error_list[] = $value// add field name to list
                
}
        }
        
$label_list adi_contact_get_labels($error_list); // create list of labels for spam fields
        
if ($mail_header_error)
            
adi_contact_message("Mail header injection detected in &#8220;<strong>$label_list</strong>&#8221; ($mail_headers_list not allowed).",$message);
        if (
$link_tag_error)
            
adi_contact_message("Link detected in &#8220;<strong>$label_list</strong>&#8221; ($link_tag_list not allowed).",$message);
        
$error_fields adi_contact_literal(join(',',$error_list)); // don't want spaces in list
        
if ($mail_header_error || $link_tag_error)
            
$return .= '<script type="text/javascript">addErrorClass('.$error_fields.',"'.__FUNCTION__.'")</script>';
    }
    if (
$debug) {
        
adi_contact_debug(__FUNCTION__,$atts,$name_list);
        echo 
"FIELD LIST:<br/>";
        
dmp($original_name_list);
        echo 
"EXCLUDE LIST:<br/>";
        
dmp($exclude_list);
        if (isset(
$error_list)) {
            echo 
"FIELD NAMES WITH SPAM ERRORS:<br/>";
            
dmp($error_list);
            echo 
"SPAM FIELD LABELS:<br/>";
            
dmp($label_list);
        }
    }
    return 
$return;

}

function 
adi_contact_labels($atts) {
// tag to record name/label mappings
    
global $adi_contact_labels;

    
$adi_contact_labels $atts;
}

function 
adi_contact_checkboxes($atts) {
// tag to manually define which fields are checkboxes
    
global $zem_contact_submit,$adi_contact_is_checkbox,$zem_contact_values;

    
extract(lAtts(array(
        
'names'    => '',    // list of form fields that are checkboxes
        
'debug'    => '0'
    
), $atts));

    
$adi_contact_is_checkbox = array();

    if (
$names) {
        
$name_list explode(',',$names); // convert comma separated list to array
        
foreach ($name_list as $index => $field// remove extraneous spaces
            
$name_list[$index] = trim($field);
        foreach (
$name_list as $index => $field_name)
            
$adi_contact_is_checkbox[$field_name] = TRUE;
    }

    if (
$debug)
        
adi_contact_debug(__FUNCTION__,$atts,$name_list);
}

function 
adi_contact_error($atts) {
// insert an extra <span>item</span> at the beginning of $zem_contact_error array - to masquerade as a introductory message (will be output as the first <li>)
    
global $zem_contact_error;

    
extract(lAtts(array(
        
'preamble'    => '',        // text to precede error messages
    
), $atts));

    if (
$preamble && $zem_contact_error)
        
array_unshift($zem_contact_error,'<span>'.htmlentities($preamble).'</span');
}

function 
adi_contact_sed_is_base64_encoded($text) {
    
$text strtr($text, array("\n"=>''"\r"=>''"\t"=>''' '=>''));
    
$b64_all   'A-Za-z0-9+/';
    
$b64_mini  'AEIMQUYcgkosw048';
    
$b64_micro 'AQgw';

    
$b64_pat "@
    ^                                         # Start of text followed by...
    (?: [
$b64_all]{4})*                       # Any number of groups of 4 chars from all chars.
    (?:                                       # ...optionally (see ? at end of this subpattern) followed by...
        [
$b64_all]{2} [$b64_mini] =           #   Any two chars from all, then one from mini, then '='
    |                                         #   -- or --
        [
$b64_all]    [$b64_micro] ==         #   one from all, then one from micro then two '='s.
    )?                                        # ...followed by...
    \$                                        # End of text
    @x"
;

    
$b64 = (bool)preg_match($b64_pat$text);
    return 
$b64;
}

function 
adi_contact_validate($atts) {
// tag to validate form field contents
    
global $zem_contact_form,$zem_contact_submit,$zem_contact_error,$zem_contact_values,$adi_contact_js_added;

    
extract(lAtts(array(
        
'names'                => '',        // list of form fields we're interested in
        
'type'                => '',        // 'alpha', 'alphanum', 'integer', 'numeric', 'equal', 'base64' etc
        
'values'            => '',        // list of acceptable values
        
'allow_whitespace'    => '0',        // allow spaces as well as 'type'
        
'pattern'            => '',        // custom pattern used with type="custom'
        
'message'            => '',        // alternative error message (with [NAMES])
        
'debug'                => '0'
    
), $atts));

    
$return adi_contact_insert_javascript();

    
// tidy up supplied attributes
    
$name_list explode(',',$names); // convert comma separated list to array
    
foreach ($name_list as $index => $field// remove extraneous spaces
        
$name_list[$index] = trim($field);

    
$invalids = array();
    
$valid_values = array();

    if (
$zem_contact_submit) { // form has been submitted

        
$ok TRUE// benefit of the doubt
        
if (isset($zem_contact_form))
            if (
$type == 'equal') {
                
$undefined_field_value 'thechancesofsomeonetypingthisinisextremelyremote...bim';
                
$ok = (count($name_list) > 1); // must be at least two fields supplied
                
if ($ok) {
                    
// remember first field value
                    
if (array_key_exists($name_list[0], $zem_contact_values))
                        
$first_field_value $zem_contact_values[$name_list[0]];
                    else
                        
$first_field_value $undefined_field_value;
                    
// compare all field values with the first
                    
foreach ($name_list as $index => $field) {
                        if (
array_key_exists($field$zem_contact_values))
                            
$this_field_value $zem_contact_values[$field];
                        else
                            
$this_field_value $undefined_field_value;
                        
$ok &= strcmp($this_field_value,$first_field_value) == 0;
                    }
                    if (!
$ok) { // add to list of invalids ??? TEST THIS - ORIGINALLY BODGIED UP IN OBI
                        
$invalids[] = $name_list[0]; // first field
                        
$invalids[] = $field// this field
                    
}
                }
            }
            else if (
$values != '') {
                
$type '';
                
$valid_values explode(',',$values);
                foreach (
$name_list as $field) {
                    if (
array_key_exists($field$zem_contact_values))
                        
$this_field_value $zem_contact_values[$field];
                    else
                        
$this_field_value '';
                    
$ok = (array_search($this_field_value,$valid_values) !== FALSE);
                    if (!
$ok) { // add to list of invalids
                        
$invalids[] = $field// this field
                    
}
                }
            }
            else
                foreach (
$name_list as $index => $field) {
                    if (
array_key_exists($field$zem_contact_values)) {
                        
$field_value $zem_contact_values[$field];
                        if (
$allow_whitespace)
                            
$field_value preg_replace('/[\s]/',''$field_value);
                        switch (
$type) {
                            case 
'alpha':
                                
$ok &= ctype_alpha($field_value);
                                break;
                            case 
'alphanum':
                                
$ok &= ctype_alnum($field_value);
                                break;
                            case 
'integer':
                                
$ok &= ctype_digit($field_value);
                                break;
                            case 
'numeric':
                                
$ok &= is_numeric($field_value);
                                break;
                            case 
'phone':
                                
$ok &= preg_match("/^[0-9x\ \+\-\(\)\.,]*$/i",$field_value);
                                break;
                            case 
'url':
                                
$ok &= preg_match("/^(?:http:\/\/)?(?:[\w-]+\.)+[a-z]{2,6}$/i",$field_value);
                                break;
                            case 
'base64':
                                
$ok &= adi_contact_sed_is_base64_encoded($field_value);
                                break;
                            case 
'custom':
                                if (
$pattern)
                                    
$ok &= preg_match($pattern,$field_value);
                                break;
                            default:
                                break;
                        }
                        if (!
$ok)
                            
$invalids[] = $field// add to list of invalids
                    
}
                }
        if (!
$ok) {
            
$label_list adi_contact_get_labels($invalids); // create list of labels
            
switch ($type) {
                case 
'alpha':
                    
adi_contact_message("Non-alphabetical characters found in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'alphanum':
                    
adi_contact_message("Non-alphanumeric characters found in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'integer':
                    
adi_contact_message("Non-digits found in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'numeric':
                    
adi_contact_message("Non-numeric characters found in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'phone':
                    
adi_contact_message("Only 0-9 + - . , () xX allowed in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'url':
                    
adi_contact_message("Invalid URL in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'base64':
                    
adi_contact_message("Invalid Base64 encoded characters found in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'custom':
                    
adi_contact_message("Invalid data in &#8220;<strong>$label_list</strong>&#8221;",$message);
                    break;
                case 
'equal':
                    
adi_contact_message("&#8220;<strong>$label_list</strong>&#8221; are not the same",$message);
                    break;
                default:
                    
adi_contact_message("Data in &#8220;<strong>$label_list</strong>&#8221; is not recognised",$message); //??? IMPROVE THIS
                    
break;
            }
            
$invalid_list adi_contact_literal(join(',',$invalids));
            
$return .= '<script type="text/javascript">addErrorClass('.$invalid_list.',"'.__FUNCTION__.'")</script>';
        }
        if (
$debug) {
            
adi_contact_debug(__FUNCTION__,$atts,$name_list);
            if (
$valid_values) {
                echo 
'VALID VALUES:<br/>';
                
dmp($valid_values);
            }
            echo 
'INVALID LIST:<br/>';
            
dmp($invalids);
        }
    }
    return 
$return;
}

function 
adi_contact_combo($atts) {
// tag to check for specified field combinations
    
global $zem_contact_form,$zem_contact_submit,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$label_list,$number,$require_label_list,$adi_contact_labels,$adi_contact_js_added,$adi_contact_is_checkbox;

    
extract(lAtts(array(
        
'names'                => '',        // list of form fields we're interested in
        
'require'            => '',        // list of form fields required
        
'restrict'            => '',        // 'min', 'max' or 'equal'
        
'number'            => '0',        // number of fields required
        
'value'                => '',        // pertinent value to trigger a 'require'
        
'case_sensitive'    => '0',        // when checking value
        
'message'            => '',        // alternative error message (with [NAMES],[NUMBER],[REQUIRE])
        
'required'            => '0',        // forces restrict="equal" number="1", e.g. for custom error messages or checkboxes that must be ticked
        
'checkbox'            => '0',        // indicates that fields are checkboxes (i.e. check for 'Yes' or 'No' - in the appropriate language) - DEPRECATED
        
'debug'                => '0'
    
), $atts));

    
$return adi_contact_insert_javascript();

    
// just in case adi_contact_checkboxes hasn't been called
    
if (!isset($adi_contact_is_checkbox))
        
$adi_contact_is_checkbox = array();

    
// tidy up supplied attributes
    
$name_list explode(',',$names); // convert comma separated list to array
    
foreach ($name_list as $index => $field// process fields of interest
        
$name_list[$index] = trim($field); // remove extraneous spaces
    
$label_list adi_contact_get_labels($name_list); // put field list back together tidily spaced out: field1, field2, field3
    
$require_list explode(',',$require); // convert comma separated list to array
    
foreach ($require_list as $index => $field// remove extraneous spaces
        
$require_list[$index] = trim($field);
    
$require_label_list adi_contact_get_labels($require_list); // put field list back together tidily spaced out: field1, field2, field3
    
$restrict strtolower($restrict);
    if (
$required) { // set other attribute values
        
$restrict 'equal';
        
$number '1';
    }
    if (
$checkbox// old checkbox attribute is no longer used, so force the issue
        
echo "<b>adi_contact_combo tag error:</b> The checkbox attribute is no longer valid (use adi_contact_checkboxes tag instead).";

    
// initial settings
    
$field_count 0;
    
$restrict_error FALSE;
    
$require_error FALSE;
    
$switch_name_require_list FALSE;
    
$missing_required = array();

    if (
$zem_contact_submit) { // form has been submitted

        
foreach ($name_list as $index => $name) { // go through supplied fields of interest

            
$flagged FALSE// flag will be set if field filled in or it matches supplied value

            
if (isset($zem_contact_values)) // NULL if all fields empty on submit!
                
if (array_key_exists($name$zem_contact_values)) { // see if field has been submitted
                    
if ($value == '') { // anything (or a tick) will do
                        
if (array_key_exists($name,$adi_contact_is_checkbox)) { // it's a checkbox
                            
if (strcasecmp($zem_contact_values[$name],gTxt('yes')) == 0// it's ticked
                                
$flagged TRUE;
                        }
                        else
                            if (
$zem_contact_values[$name] != ''// field filled in
                                
$flagged TRUE;
                    }
                    else { 
// specific value required
                        
if ($case_sensitive)
                            
$flagged = ($zem_contact_values[$name] == $value);
                        else
                            
$flagged = (strtolower($zem_contact_values[$name]) == strtolower($value));
                    }
                }

            if (
$flagged)
                if (
$require && $restrict) { // SPECIAL CASE: require list contains "secondary" fields of interest
                    
foreach ($require_list as $index => $required_field) { // see if they're filled
                        
if (array_key_exists($required_field,$adi_contact_is_checkbox)) {
                            if (
strcasecmp($zem_contact_values[$required_field],gTxt('yes')) == 0// "filled in" if set to 'Yes', so that's OK then
                                
$field_count += 1;
                        }
                        else if (
array_key_exists($required_field$zem_contact_values))
                                
$field_count += 1;
                    }
                    
$switch_name_require_list TRUE// will need to change list later for error message
                
}
                else if (
$require) { // SIMPLE REQUIREMENT
                    
foreach ($require_list as $index => $required_field) {
                        if (
array_key_exists($required_field,$adi_contact_is_checkbox)) {
                            if (
strcasecmp($zem_contact_values[$required_field],gTxt('yes')) == 0// "filled in" if set to 'Yes', so that's OK then
                                
continue;
                        }
                        else if (
array_key_exists($required_field$zem_contact_values))
                            continue;
                        
// not filled in then!
                        
$require_error TRUE;
                        
$missing_required[] = $required_field// list the missing
                    
}
                }
                else if (
$restrict) { // SIMPLE RESTRICTION
                    
$field_count += 1;
                }

        }

        
// SPECIAL CASE: require list contains "secondary" fields of interest
        
if ($require && $restrict)
            if (
$switch_name_require_list) { // "secondary" fields of interest have come into play
                
$name_list $require_list;
                
$label_list adi_contact_get_labels($require_list);
            }
            else 
// forget about restriction, coz it don't apply i.e. no flags raised
                
$restrict '';

        
// 'restrict' mode error messages
        
switch ($restrict) {
            case 
'min':
                if (
$field_count $number) {
                    
$restrict_error TRUE;
                    
adi_contact_message("At least $number of &#8220;<strong>$label_list</strong>&#8221; required.",$message);
                }
                break;
            case 
'max':
                if (
$field_count $number) {
                    
$restrict_error TRUE;
                    
adi_contact_message("Maximum $number of &#8220;<strong>$label_list</strong>&#8221; required.",$message);
                }
                break;
            case 
'equal':
                if (
$field_count != $number) {
                    
$restrict_error TRUE;
                    
adi_contact_message("Exactly $number of &#8220;<strong>$label_list</strong>&#8221; required.",$message);
                }
                break;
            default:
                break;
        }

        if (
$debug) {
            
adi_contact_debug(__FUNCTION__,$atts,$name_list);
            echo 
"LABELS OF INTEREST:<br/>";
            
dmp($label_list);
            if (
$restrict) {
                if (
$number) {
                    echo 
"VALID FIELD COUNT:<br/>";
                    echo 
'<pre>'.$field_count.'</pre>';
                }
            }
            if (
$require) {
                echo 
"REQUIRED FIELDS:<br/>";
                
dmp($require_list);
                if (isset(
$missing_required))
                {
                    echo 
"MISSING REQUIRED FIELDS:<br/>";
                    
dmp($missing_required);
                }
            }
        }

        
// finally some javascript action
        
if ($restrict_error) {
            
$missing_list adi_contact_literal(join(',',$name_list)); // don't want spaces in list
            
if ($debug)
                echo 
"MISSING LIST:<br/><pre>$missing_list</pre>";
            
$return .= '<script type="text/javascript">addErrorClass('.$missing_list.',"'.__FUNCTION__.'")</script>';
        }
        else if (
$require_error) { // 'require' error messages
            
adi_contact_message("If &#8220;<strong>$label_list</strong>&#8221; supplied then &#8220;<strong>$require_label_list</strong>&#8221; required.",$message);
            
$missing_list adi_contact_literal(join(',',$missing_required)); // don't want spaces in list
            
$return .= '<script type="text/javascript">addErrorClass('.$missing_list.',"'.__FUNCTION__.'")</script>';
        }
    }
    return 
$return;
}

function 
adi_contact_headers($atts) {
// tag for extra headers, e.g. CC or BCC
// adi_contact_deliver() does the actual doing
    
global $adi_contact_headers;

    
extract(lAtts(array(
        
'cc'    => '',        // CC email address
        
'bcc'    => '',        // BCC email address
    
), $atts));

    if (!isset(
$adi_contact_headers))
        
$adi_contact_headers = array();

    
$adi_contact_headers['cc'] = $cc;
    
$adi_contact_headers['bcc'] = $bcc;

    return;
}

function 
adi_contact_body($atts) {
// for appending extra information onto end of email
// accepts string (internal function use) OR array (tag mode)
// adi_contact_deliver() does the actual doing
    
global $adi_contact_body,$zem_contact_submit;

    if (!isset(
$adi_contact_body))
        
$adi_contact_body = array(''); // start a new line otherwise extra body tacked onto end of last field/value line

    
if (is_array($atts))
        
extract(lAtts(array(
            
'line'        => '',    // a line to add to the body
        
), $atts));
    else
        
$line =$atts;

    if(
$zem_contact_submit// don't add unless form submitted (to avoid combining lines from separate forms on same page)
        
$adi_contact_body[] = $line;

    return;
}

function 
adi_contact_deliver($event,$step,&$payload) {
// modify the email $payload (event="zemcontact.deliver",step="send/copysender")
    
global $adi_contact_headers,$adi_contact_body;

    
// extra headers - provided by adi_contact_headers()
    
if ($adi_contact_headers['cc'] != '')
        
$payload['headers']['Cc'] = $adi_contact_headers['cc'];
    if (
$adi_contact_headers['bcc'] != '')
        
$payload['headers']['Bcc'] = $adi_contact_headers['bcc'];

    if (
$step !== 'copysender') { // making big assumption that copy of email going to form submitter shouldn't contain extra body (e.g. MailChimp diags)
        // extra body (message text already formed at this point, so simply adding extra lines to email)
        
if ($adi_contact_body) {
            foreach (
$adi_contact_body as $line)
                
$payload['body'] .= $line."\n";
        }
    }

    return 
'zemcontact.send'// I command thee to send thy email
}

function 
adi_contact_db($atts,$thing='') {
// database mechanism enclosing tag
    
global $zem_contact_submit,$zem_contact_error,$adi_contact_db_table,$production_status;

    
// CREATE TABLE contacts (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,`timestamp` datetime NOT NULL default '0000-00-00 00:00:00',`fullname` VARCHAR(255) NOT NULL,`email` VARCHAR(255) NOT NULL,`mailout` VARCHAR(32) NOT NULL default '',`general` VARCHAR(16) NOT NULL default '',`sales` VARCHAR(16) NOT NULL default '',`technical` VARCHAR(16) NOT NULL default '',`feedback` VARCHAR(16) NOT NULL default '',`region` VARCHAR(32) NOT NULL default '');

    
extract(lAtts(array(
        
'table'        => '',            // the database table
         
'form'        => '',            // the adi_contact_db form
//         'mode'        => 'insert',    // insert or update
    
), $atts));

    
$diags = array();

    
$adi_contact_db_data = array(); // array indexed by db_field name: ['contact_field'] => contact field name, ['default'] => default value (if supplied)
    
$adi_contact_db_table $table;

    
// validate supplied table
    
if (($production_status === 'debug') && ($table != '')) {
        
$rs safe_query("SHOW TABLES LIKE '".safe_pfx($table)."'");
        
$a nextRow($rs);
        if (
$a)
            
$diags[] = "table - '$table' found in database";
        else
            
$diags[] = "table - '$table' NOT FOUND in database";
    }
    if (
$table == '')
        
$diags[] = 'table not set';

    
// form submit status
    
if ($zem_contact_submit)
        
$diags[] = 'form - submitted';
    else
        
$diags[] = 'form - not submitted';
    if (
$zem_contact_error)
        
$diags[] = 'form - submit errors';

    
adi_contact_trace($diags);

    
// do the doing
    
$form = ($form) ? fetch_form($form) : $thing// only get tag errors about invalid DB fields if using form (i.e. not enclosing tag)?

    
return parse($form);
}

function 
adi_contact_db_data($atts) {
// contact form/database field mapping tag - updates $adi_contact_db_data
    
global $zem_contact_submit,$zem_contact_labels,$production_status,$adi_contact_db_table,$adi_contact_db_data;

    
extract(lAtts(array(
        
'db_field'        => '',    // the database field (required)
        
'contact_field'    => '',    // the contact form input field (default same name as db_field)
        
'default'        => '',    // optional default value
        
'timestamp'        => ''    // database field is a timestamp (datetime, date or timestamp)
    
), $atts));

    
$diags = array();

    
// contact field same as database field?
    
if (($contact_field == '') && !$timestamp)
        
$contact_field $db_field;

    
// validate contact form field
    
if ($zem_contact_submit) {
        if (isset(
$zem_contact_labels[$contact_field]))
            
$diags[] = "field - '$contact_field' found in contact form";
        else
            
$diags[] = "field - '$contact_field' NOT FOUND OR BLANK in contact form";
    }

    
// check required attributes
    
if ($db_field == '')
        
$diags[] = "db_field - ATTRIBUTE MISSING";
    else {
        
// db column -> contact field mapping
        
$adi_contact_db_data[$db_field]['contact_field'] = $contact_field;

        
// default values
        
if (isset($atts['default'])) // default value supplied
            
$adi_contact_db_data[$db_field]['default'] = $default;
        if (isset(
$atts['timestamp'])) { // set timestamp according to MySQL formats: DATETIME, DATE, TIMESTAMP
            
$timestamp strtolower($timestamp);
            switch (
$timestamp) {
                case 
'date'// yyyy-mm-dd
                    
$adi_contact_db_data[$db_field]['default'] = date("Y-m-d",time()+tz_offset());
                    break;
                case 
'timestamp'// UTC yyyy-mm-dd hh:mm:ss
                    
$adi_contact_db_data[$db_field]['default'] = gmstrftime("%Y-%m-%d %H:%M:%S");
                    break;
                default: 
// default to "datetime" - yyyy-mm-dd hh:mm:ss
                    
$adi_contact_db_data[$db_field]['default'] = date("Y-m-d H:i:s",time()+tz_offset());
                    break;
            }
        }

        
$diags[] = "map - '".$adi_contact_db_data[$db_field]['contact_field']."' -> db '$db_field'".(isset($adi_contact_db_data[$db_field]['default']) ? ' (default = "'.$adi_contact_db_data[$db_field]['default'].'")' '');

        
// validate database column
        
if ($production_status === 'debug') {
            if (isset(
$adi_contact_db_table)) {
                
$rs safe_query('SHOW COLUMNS FROM `'.safe_pfx($adi_contact_db_table)."` WHERE FIELD = '$db_field'");
                if (
$rs !== FALSE) { // table exists
                    
$a nextRow($rs);
                    if (
$a)
                        
$diags[] = "column - '$db_field' found in table '$adi_contact_db_table'";
                    else
                        
$diags[] = "column - '$db_field' NOT FOUND in table '$adi_contact_db_table'";
                }
            }
        }
    }

    
adi_contact_trace($diags);
}

function 
adi_contact_db_update() {
// update database - called back during form submit
    
global $zem_contact_values,$adi_contact_db_table,$adi_contact_db_data;

    
$diags = array();

    if (!isset(
$adi_contact_db_table)) return; // adi_contact_db hasn't been called so bail out

    // check spammage
    
$evaluation =& get_zemcontact_evaluator();
    if (
$evaluation->get_zemcontact_status()) {
        
$diags[] = "spam evaluation - DIRTY";
        return;
    }

    
// build set
    
$set = array();
    if (
$adi_contact_db_data) {
        foreach (
$adi_contact_db_data as $db_field => $map) {
            if (isset(
$zem_contact_values[$map['contact_field']]))
                
$set[]= "`$db_field`='".doSlash($zem_contact_values[$map['contact_field']])."'"// use value from contact form
            
else if (isset($map['default']))
                
$set[]= "`$db_field`='".doSlash($map['default'])."'"// use supplied default value
//             else
//                 let the database fill in its own default
        
}
    }
    else
        
$diags[] = "map - EMPTY";

    
// update the database
    
if ($set && $adi_contact_db_table) {
        
$diags[] = 'set - '.implode(', ',$set);
        if (!
safe_insert($adi_contact_db_table,implode(',',$set)))
            
$diags[] = "safe_insert: FAIL";
    }

    
adi_contact_trace($diags);
}

function 
adi_contact_mailchimp($atts,$thing=NULL) {
// MailChimp subscription container tag
    
global $zem_contact_submit,$zem_contact_error,$adi_contact_mailchimp_data,$adi_contact_mailchimp_info;

    
extract(lAtts(array(
        
'api_key'        => '',                // MailChimp API key (required)
         
'list_id'        => '',                // MailChimp list ID (required)
         
'status'        => 'pending',        // 'subscribed', 'unsubscribed', 'pending', 'cleaned'
         
'opt_in_field'    => 'mailing_list',    // contact form field name, used to trigger MailChimp action
          
'opt_in_value'    => gTxt('yes'),        // contact form field value, used to trigger MailChimp action (defaults to checkbox value)
        
'form'            => '',                // adi_contact_mailchimp form
    
), $atts));

    
// status values
    //         'pending' - confirmation email will be sent to user
    //        'subscribed' - subscribed immediately, no confirmation sent/required

    
$diags = array();

    
$default_form '<txp:adi_contact_mailchimp_email />';

    
// array containing data required for MailChimp connect (api_key, list_id, email, status, merge_fields) as well as opt-in info
    
$adi_contact_mailchimp_info = array('api_key'=>$api_key,'list_id'=>$list_id,'status'=>$status,'opt_in_field'=>$opt_in_field,'opt_in_value'=>$opt_in_value);
    
// array indexed by MailChimp field (email,FNAME,LNAME): ['contact_field'] => contact field name, ['default'] => default value (DEFAULT VALUES NOT CURRENTLY USED)
    
$adi_contact_mailchimp_data = array();

    
// check attributes
    
if ($api_key == '')
        
$diags[] = 'API key missing';
    if (
$list_id == '')
        
$diags[] = 'list ID missing';
    if (
$status == '')
        
$diags[] = 'status missing';

    
// form submit status
    
if ($zem_contact_submit)
        
$diags[] = 'form - submitted';
    else
        
$diags[] = 'form - not submitted';
    if (
$zem_contact_error)
        
$diags[] = 'form - submit errors';

    
adi_contact_trace($diags);

    
// form guide
    
$form = ($form) ? fetch_form($form) : ($thing === NULL $default_form $thing);

    return 
parse($form);
}

function 
adi_contact_mailchimp_data($atts) {
// contact form/mailchimp field mapping tag - updates $adi_contact_mailchimp_data
    
global $zem_contact_submit,$zem_contact_labels,$adi_contact_mailchimp_data;

    
extract(lAtts(array(
        
'mailchimp_field'    => '',    // MailChimp field, e.g. FNAME, LNAME (required)
        
'contact_field'        => '',    // the contact form input field (required)
    
), $atts));

    
$diags = array();

    
// validate contact form field
    
if ($zem_contact_submit) {
        if (isset(
$zem_contact_labels[$contact_field]))
            
$diags[] = "field - '$contact_field' found in contact form";
        else
            
$diags[] = "field - '$contact_field' NOT FOUND OR EMPTY in contact form";
    }

    
// check required attributes
    
if ($mailchimp_field == '')
        
$diags[] = "mailchimp_field - ATTRIBUTE MISSING";
    else if (
$contact_field == '')
        
$diags[] = "contact_field - ATTRIBUTE MISSING";
    else {
        
// mailchimp column -> contact field mapping
        
$adi_contact_mailchimp_data[$mailchimp_field]['contact_field'] = $contact_field;

        
// default values (NOT CURRENTLY USED)
        
if (isset($atts['default'])) // default value supplied
            
$adi_contact_mailchimp_data[$mailchimp_field]['default'] = $default;

        
$diags[] = "map - '".$adi_contact_mailchimp_data[$mailchimp_field]['contact_field']."' -> mailchimp '$mailchimp_field'".(isset($adi_contact_mailchimp_data[$mailchimp_field]['default']) ? ' (default = "'.$adi_contact_mailchimp_data[$mailchimp_field]['default'].'")' '');
    }

    
adi_contact_trace($diags);
}

function 
adi_contact_mailchimp_email($atts) {
// mailchimp email field mapping tag

    
extract(lAtts(array(
        
'contact_field'    => 'email',    // the contact form input field
    
), $atts));

    
adi_contact_mailchimp_data(array('mailchimp_field'=>'email''contact_field'=>$contact_field));
}

function 
adi_contact_mailchimp_update() {
// update MailChimp list - called back during form submit
    
global $zem_contact_values,$adi_contact_mailchimp_info,$adi_contact_mailchimp_data;

    
$diags = array();

    if (!isset(
$adi_contact_mailchimp_info)) return; // adi_contact_mailchimp hasn't been called so bail out

    // check spammage
    
$evaluation =& get_zemcontact_evaluator();
    if (
$evaluation->get_zemcontact_status()) {
        
adi_contact_trace("FAIL - spam evaluation, DIRTY");
        return;
    }

    
// check opt-in
    
$opt_in_field $adi_contact_mailchimp_info['opt_in_field'];
    
$opt_in_value $adi_contact_mailchimp_info['opt_in_value'];
    if (
$opt_in_field != '') { // if opt_in_field attribute set, check it exists in contact form
        
if (!isset($zem_contact_values[$opt_in_field])) {
            
adi_contact_trace("FAIL - opt-in field '$opt_in_field' NOT FOUND");
            return;
        }
        if (
strcasecmp($zem_contact_values[$opt_in_field],$opt_in_value) != 0) { // opt_in_field value matches opt_in_value attribute?
            
$opt_in_field_value $zem_contact_values[$opt_in_field];
            
adi_contact_trace("DISABLED - opt-in field '$opt_in_field' value '$opt_in_field_value', doesn't match opt_in_value '$opt_in_value'");
            return;
        }
    }

    
// build up the chimp
    
$adi_contact_mailchimp_info['email'] = '';
    
$adi_contact_mailchimp_info['merge_fields'] = array();
    if (
$adi_contact_mailchimp_data) {
        foreach (
$adi_contact_mailchimp_data as $mailchimp_field => $map) {
            if (
$mailchimp_field == 'email') { // the email
                
if (isset($zem_contact_values[$map['contact_field']]))
                    
$adi_contact_mailchimp_info['email'] = $zem_contact_values[$map['contact_field']]; // use email from contact form;
            
}
            else { 
// merge fields
                
$value = (isset($zem_contact_values[$map['contact_field']]) ? $zem_contact_values[$map['contact_field']] : '');
                
$adi_contact_mailchimp_info['merge_fields'][$mailchimp_field] = $value;
            }
        }
    }
    else
        
$diags[] = "map - EMPTY";

    
// update the chimp
    
if ($adi_contact_mailchimp_info['email']) { // need an email address as bare minimum
        
$diag_info $adi_contact_mailchimp_info;
        unset(
$diag_info['merge_fields']);
        
$diags[] = 'info - '.implode(', ',$diag_info);
        
$diags[] = 'merge_fields - '.implode(', ',$adi_contact_mailchimp_info['merge_fields']);
        
$mc_result adi_contact_mailchimp_connect($adi_contact_mailchimp_info);
        
$email_msg "MailChimp:\n";
        
// see adi_contact_mailchimp_connect for returns
        
if ($mc_result === FALSE) {
            
$reason 'unable to connect';
            
$diags[] = 'FAIL - '.$reason;
            
$email_msg .= "- subscribe failed ($reason)\n";
        }
        if (
$mc_result === NULL) {
            
$reason 'software error';
            
$diags[] = 'FAIL - '.$reason;
            
$email_msg .= "- subscribe failed ($reason)\n";
        }
        if (
is_array($mc_result))
            if (isset(
$mc_result['title'])) {
                
$reason 'status '.$mc_result['status'].', '.$mc_result['title'];
                
$diags[] = 'FAIL - '.$reason;
                
$email_msg .= "- subscribe failed ($reason)\n";
                
$diags[] = 'FAIL - '.trim($mc_result['detail']);
                
$email_msg .= '- '.trim($mc_result['detail'])."\n";
                foreach (
$mc_result['errors'] as $error) {
                    
$email_msg .= "- ";
                    
$error_msg '';
                    foreach (
$error as $index => $value)
                        
$error_msg .= "$index$value; ";
                    
$diags[] = "ERROR - $error_msg";
                    
$email_msg .= "$error_msg\n";
                }
            }
            else {
                
$reason 'status '.$mc_result['status'];
                
$diags[] = 'success - '.$mc_result['status'];
                
$email_msg .= "- subscribed ($reason)\n";
            }

        
// maybe attributed option to include mc diags in email or not?
        
adi_contact_body("\n"); // two blank lines
        
adi_contact_body($email_msg);

    }
    else
        
$diags[] = 'FAIL - no contact field to MailChimp "email" field mapping';

    
adi_contact_trace($diags);
}

function 
adi_contact_mailchimp_connect($mailchimp_info) {
// connect to MailChimp & update mailing list
// - much code taken from stackoverflow.com/questions/30481979/adding-subscribers-to-a-list-using-mailchimps-api-v3
//
// - success
//        - return array('status')
//            status = 'pending' or 'subscribed' etc
// - failure
//        - return array('status','title')
//            status = 400, 401, 404 etc
//            title = informative summary
//                400 - Member Exists
//                401 - API Key Invalid
//                404 - Resource Not Found (dodgy URL)
//            detail = further explanation
//            errors = optional array of field/error message
//      - return FALSE
//            connection failure (e.g. timeout)
//      - return NULL
//            software problem (e.g. curl functions not available)

    
extract($mailchimp_info); // i.e. $api_key, $list_id, $status, $email, $merge_fields[...]
    
list(,$datacentre) = explode('-',$api_key); // datacentre is the "us" bit at end of API key
    
$url 'https://'.$datacentre.'.api.mailchimp.com/3.0/lists/'.$list_id.'/members/';

    
$data = array(
        
'apikey'        => $api_key,
        
'email_address'    => $email,
        
'status'        => $status,
        
'merge_fields'    => $merge_fields
    
);

    
// there seems to be an intermittant issue with MailChimp API v3.0
    //        scenario: when merge_fields is an empty array (i.e. there are no adi_contact_mailchimp_data mappings other than "email")
    //        symptoms/diags:
    //            subscribe failed (status 400, Invalid Resource)
    //            "The resource submitted could not be validated. For field-specific details, see the 'errors' array."
    //            field: merge_fields; message: Schema describes object, array found instead;
    //        intermittant: may only occur on certain servers (e.g. local & staging OK, live COL fails), possibly a PHP version/server config thing
    //        discussed here: https://github.com/drewm/mailchimp-api/issues/54#issuecomment-171263356
    //        fix: omit 'merge_fields' item from $data if empty
    
if (empty($merge_fields)) unset($data['merge_fields']);

//     echo 'adi_contact_mailchimp_connect:';
//     dmp($data);

    
$json_data json_encode($data);

    
// curly wurly check
    //??? IMPLEMENT ALT CONNECTION METHOD using file_get_contents() - see smd_mailchimp
    
if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
        
adi_contact_trace("FAIL - curl_init(), curl_setopt() not available");
        return 
NULL;
    }

    
$ch curl_init();
    
curl_setopt($chCURLOPT_URL$url);
    
curl_setopt($chCURLOPT_HTTPHEADER, array('Content-Type: application/json'));
    
curl_setopt($chCURLOPT_USERPWD"user:" $api_key);
    
curl_setopt($chCURLOPT_USERAGENT'PHP-MCAPI/2.0');
    
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
    
curl_setopt($chCURLOPT_TIMEOUT10);
    
curl_setopt($chCURLOPT_POSTtrue);
    
curl_setopt($chCURLOPT_SSL_VERIFYPEERfalse);
    
curl_setopt($chCURLOPT_POSTFIELDS$json_data);

    
$result curl_exec($ch); // FALSE = connection error, JSON data if contact made with MAilChimp

    
$arr json_decode($result,TRUE); // json_decode into array

//     echo __FUNCTION__.' response/status:';
//     dmp($arr);

    // not using extract() coz there's a shed load of variously named indexes in $arr (especially in a successful subscribe)
    
$status '';
    if (isset(
$arr['status'])) $status $arr['status'];

    
$title '';
    if (isset(
$arr['title'])) $title $arr['title'];

    
$detail '';
    if (isset(
$arr['detail'])) $detail $arr['detail'];

    
$errors = array();
    if (isset(
$arr['errors'])) $errors $arr['errors'];

//     echo __FUNCTION__.' errors:';
//     dmp($errors);

    // final analysis of results
    
if ($result === FALSE// connection error, return FALSE
        
return $result;
    else
        if (
is_int($status)) // status is a number, return descriptive error
            
return array('status'=>$status,'title'=>$title,'detail'=>$detail,'errors'=>$errors);
        else 
// return status (string)
            
return array('status'=>$status);
}

function 
adi_contact_debug($function='',$atts=array(),$name_list=array(),$diags=array()) {
// debug function for debug="1" attributes or can be used as a standalone tag (<txp:adi_contact_debug />) to output general diags
    
global $zem_contact_form,$zem_contact_submit,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$label_list,$number,$require_label_list,$adi_contact_labels,$prefs,$adi_contact_is_checkbox,$adi_contact_zcr_4500;

    if (
$function) {
        echo 
'********** '.strtoupper($function).' **********'.br.br;
        if (
$atts) {
            echo 
'SUPPLIED ATTRIBUTES:'.br;
            
dmp($atts);
        }
        if (
$name_list) {
            echo 
'FIELDS OF INTEREST:'.br;
            
dmp($name_list);
        }
        if (
$diags) {
            echo 
'DIAGNOSTICS:'.br;
            echo 
tag(implode(n,$diags),'pre');
        }
    }
    else {
        echo 
'********** ADI_CONTACT DEBUG **********'.br.br;
        echo 
'VERSIONS:'.br;
        echo 
'<pre>';
        
$plugin_list = array('adi_contact','zem_contact_reborn','pap_contact_cleaner');
        foreach (
$plugin_list as $index => $plugin) {
            
$row safe_row('version,status,load_order','txp_plugin',"name='$plugin'");
            if (
$row) {
                
extract($row);
                echo 
"$plugin$version".($status?' (active)':' (not active)').", load order $load_order".br;
            }
            if (!
$row || !$status) {
                
$func_list = array('adi_contact_combo','zem_contact','pap_zemcontact_form');
                if (
function_exists($func_list[$index]))
                    echo 
$plugin.': in plugin cache, load order 0'.br;
                else
                    echo 
$plugin.': not installed'.br;
            }
        }
        echo 
'TXP: '.$prefs['version'].br;
        echo 
'PHP: '.phpversion().br;
        echo 
'MySQL: '.mysql_get_server_info().br;
        echo 
'Language: yes = '.gTxt('yes').br;
        echo 
'TXP date/time = '.date("Y-m-d H:i:s",time()+tz_offset()); // this is actual (TXP adjusted) local time (incl DST) regardless of server timezone
        
echo '</pre>';
        echo 
'ZEM CONTACT FORM:'.br;
        echo 
'<pre>';
        if (isset(
$_POST['zem_contact_form_id']))
            echo 
'id = '.$_POST['zem_contact_form_id'].br;
        if (isset(
$_POST['zem_contact_nonce']))
            echo 
'nonce = '.$_POST['zem_contact_nonce'].br;
        echo 
'</pre>';
        echo 
'SUBMITTED FORM LABEL/VALUE ($zem_contact_form):'.br;
        
dmp($zem_contact_form); // array keyed by labels (null if no fields filled in or not submitted)
        
echo 'SUBMITTED FORM NAME/LABEL ($zem_contact_labels):'.br;
        
dmp($zem_contact_labels); // array keyed by names (null if no fields filled in or not submitted)
        
echo 'SUBMITTED FORM NAME/VALUE ($zem_contact_values):'.br;
        
dmp($zem_contact_values); // array keyed by names (null if no fields filled in or not submitted)
        
if (isset($zem_contact_error)) {
            echo 
'ERROR LIST ($zem_contact_error):'.br;
            
dmp($zem_contact_error);
        }
        if (isset(
$adi_contact_labels)) {
            echo 
'SUPPLIED LABELS FROM adi_contact_labels:'.br;
            
dmp($adi_contact_labels);
        }
        echo 
'CHECKBOXES ($adi_contact_is_checkbox):'.br;
        
dmp($adi_contact_is_checkbox);
        if (
$diags) {
            echo 
'DIAGNOSTICS:'.br;
            echo 
tag(implode(n,$diags),'pre');
        }
    }
}

function 
adi_contact_trace($diags) {
// captain's log
    
global $trace;

    if (
is_string($diags))
        
$diags str_split($diags,9999); // convert string to array

    // get meaningful calling function
    
if (is_callable('debug_backtrace')) {
        
$backtrace debug_backtrace();
        
$function = isset($backtrace[1]['function']) ? $backtrace[1]['function'] : __FUNCTION__;
    }
    else
        
$function __FUNCTION__;

    if (
$function == "include"$function __FUNCTION__// plugin cache
    
if ($function == "eval"$function __FUNCTION__// plugin installed

    
if (is_object($trace)) // TXP 4.6
        
foreach ($diags as $diag)
            
$trace->log('['.$function.': '.$diag.']');
    else
        foreach (
$diags as $diag)
            
trace_add('['.$function.': '.$diag.']');
}