<?php

# PLUGIN PREVIEW BY TEXTPATTERN.INFO

//<?php
/**
 * com_connect: A Textpattern CMS plugin for mail delivery of contact forms.
 */

// Register tags if necessary.
if (class_exists('\Textpattern\Tag\Registry')) {
    
Txp::get('\Textpattern\Tag\Registry')
        ->
register('com_connect')
        ->
register('com_connect_text')
        ->
register('com_connect_email')
        ->
register('com_connect_textarea')
        ->
register('com_connect_select')
        ->
register('com_connect_option')
        ->
register('com_connect_checkbox')
        ->
register('com_connect_radio')
        ->
register('com_connect_serverinfo')
        ->
register('com_connect_secret')
        ->
register('com_connect_submit')
        ->
register('com_connect_send_article')
        ->
register('com_connect_value')
        ->
register('com_connect_label')
        ->
register('com_connect_if');
}

/**
 * Tag: encapsulate a contact form.
 *
 * Triggers the following callbacks:
 *  -> 'comconnect.form' during form rendering so additional fields (e.g. spam honeypots) can be injected.
 *  -> 'comconnect.render' immediately prior to form rendering so other parts of the form content may be altered.
 *  -> 'comconnect.submit' on successful posting of form data. Primarily of use for spam
 *     plugins: they can return a non-zero value to signal that the form should NOT be sent.
 *
 * @param array  $atts  Tag attributes
 * @param string $thing Tag's container content
 */
function com_connect($atts$thing null)
{
    global 
$sitename$textarray$com_connect_flags$com_connect_from,
        
$com_connect_recipient$com_connect_error$com_connect_submit,
        
$com_connect_form$com_connect_labels$com_connect_values;

    
extract(com_connect_lAtts(array(
        
'body_form'        => '',
        
'class'            => 'comConnectForm',
        
'classes'          => '',
        
'copysender'       => 0,
        
'expire'           => 600,
        
'form'             => '',
        
'from'             => '',
        
'from_form'        => '',
        
'label'            => null,
        
'browser_validate' => 1,
        
'redirect'         => '',
        
'required'         => '1',
        
'show_error'       => 1,
        
'show_input'       => 1,
        
'send_article'     => 0,
        
'subject'          => null,
        
'subject_form'     => '',
        
'to'               => '',
        
'to_form'          => '',
        
'thanks'           => null,
        
'thanks_form'      => ''
    
), $atts));

    if (!empty(
$lang)) {
        
$strings com_connect_load_lang($lang);
        
$textarray array_merge($textarray$strings);
    }

    
// Set defaults, in the local language if necessary.
    
if ($label === null) {
        
$label gTxt('com_connect_contact');
    }

    if (
$subject === null) {
        
$subject gTxt('com_connect_email_subject', array('{site}' => html_entity_decode($sitename,ENT_QUOTES)));
    }

    if (
$thanks === null) {
        
$thanks graf(gTxt('com_connect_email_thanks'));
    }

    unset(
$atts['show_error'], $atts['show_input']);

    
$defaultClassNames = array(
        
'element'  => 'errorElement',
        
'wrapper'  => 'comError',
        
'required' => 'comRequired',
        
'thanks'   => 'comThanks',
        );

    
$com_connect_form_id md5(serialize($atts) . preg_replace('/[\t\s\r\n]/'''$thing));
    
$com_connect_submit = (ps('com_connect_form_id') == $com_connect_form_id);
    
$override_email_charset = (get_pref('override_emailcharset') && is_callable('utf8_decode'));
    
$userClassNames do_list($classes);

    foreach (
array_merge($defaultClassNames$userClassNames) as $classKey => $classValue) {
        if (
strpos($classValue':') !== false) {
            
$classParts do_list($classValue':');

            if (
count($classParts) === 2) {
                
$com_connect_flags['cls_' $classParts[0]] = $classParts[1];
            }
        } elseif (
$classKey && $classValue) {
            
$com_connect_flags['cls_' $classKey] = $classValue;
        }
    }

    
// The $com_connect_flags['this_form'] global is set if an id is supplied for the <form>.
    // This value then becomes the default value for all html_form (a.k.a. form=)
    // attributes for any input tags in this tag's container, providing HTML5 is in use.
    
$com_connect_flags['this_form'] = 'com' $com_connect_form_id;

    
// Global toggle for required attribute.
    
$com_connect_flags['required'] = $required;

    
$now time();
    
$now_date date('Y-m-d H:i:s'$now);

    
$expire abs(assert_int($expire));

    static 
$headers_sent false;
    if (!
$headers_sent) {
        
header('Last-Modified: ' gmdate('D, d M Y H:i:s'$now - (3600 24 7)) . ' GMT');
        
header('Expires: ' gmdate('D, d M Y H:i:s'$now $expire) . ' GMT');
        
header('Cache-Control: no-cache, must-revalidate');
        
$headers_sent true;
    }

    
$nonce doSlash(ps('com_connect_nonce'));
    
$renonce false;

    if (
$com_connect_submit) {
        
// Could use "interval $expire second" but multiple com_connect forms could delete data
        // that might be in use by other forms.
        // TODO: use max(600, $expire)? Not perfect, but ensures a safe minimum deletion rate.
        
safe_delete('txp_discuss_nonce'"issue_time < date_sub('$now_date', interval 10 minute)");
        if (
$rs safe_row('used''txp_discuss_nonce'"nonce = '$nonce'")) {
            if (
$rs['used']) {
                unset(
$com_connect_error);
                
$com_connect_error[] = gTxt('com_connect_form_used');
                
$renonce true;
                
$_POST = array();
                
$_POST['com_connect_submit'] = true;
                
$_POST['com_connect_form_id'] = $com_connect_form_id;
                
$_POST['com_connect_nonce'] = $nonce;
            }
        } else {
            
$com_connect_error[] = gTxt('com_connect_form_expired');
            
$renonce true;
        }
    }

    if (
$com_connect_submit && $nonce && !$renonce) {
        
$com_connect_nonce $nonce;
    } elseif (!
$show_error || $show_input) {
        
$com_connect_nonce md5(uniqid(rand(), true));
        
safe_insert('txp_discuss_nonce'"issue_time = '" $now_date "', nonce = '$com_connect_nonce'");
    }

    
$form = ($form) ? fetch_form($form) : $thing;

    if (empty(
$form)) {
        
$form '
<txp:com_connect_text label="'
.gTxt('com_connect_name').'" /><br />
<txp:com_connect_email /><br />'
.
(
$send_article '<txp:com_connect_email send_article="1" label="'.gTxt('com_connect_recipient').'" /><br />' '').
'<txp:com_connect_textarea /><br />
<txp:com_connect_submit />
'
;
    }

    
$form parse($form);

    
// Perform aggregate functions for checking radio sets.
    
if ($com_connect_submit) {
        
com_connect_group_validate();
    }

    if (
$to_form) {
        
$to parse_form($to_form);
    }

    if (!
$to && !$send_article) {
        return 
gTxt('com_connect_to_missing');
    }

    
$out '';

    if (!
$com_connect_submit) {
        
// Don't show errors or send mail.
    
} elseif (!empty($com_connect_error)) {
        if (
$show_error || !$show_input) {
            
$out .= n.doWrap(array_unique($com_connect_error), 'ul''li'$com_connect_flags['cls_wrapper']).n;

            if (!
$show_input) {
                return 
$out;
            }
        }
    } elseif (
$show_input && is_array($com_connect_form)) {
        
// Load and check spam plugins.
        
callback_event('comconnect.submit');
        
$evaluation =& get_comconnect_evaluator();
        
$clean $evaluation->get_comconnect_status();

        if (
$clean != 0) {
            return 
gTxt('com_connect_spam');
        }

        if (
$from_form) {
            
$from parse_form($from_form);
        }

        if (
$subject_form) {
            
$subject parse_form($subject_form);
        }

        
$sep IS_WIN "\r\n" "\n";
        
$msg = array();
        
$fields = array();

        foreach (
$com_connect_labels as $name => $lbl) {
            if (!
is_array($com_connect_values[$name])) {
                if (
trim($com_connect_values[$name]) === false) {
                    continue;
                }

                
$msg[] = $lbl ': ' $com_connect_values[$name];
            }

            
$fields[$name] = $com_connect_values[$name];
        }

        if (
$send_article) {
            global 
$thisarticle;

            
$subject str_replace('&#38;''&'$thisarticle['title']);
            
$msg[] = permlinkurl($thisarticle);
            
$msg[] = $subject;
            
$s_ar = array('&#8216;''&#8217;''&#8220;''&#8221;''&#8217;''&#8242;''&#8243;''&#8230;''&#8211;''&#8212;''&#215;''&#8482;''&#174;''&#169;''&lt;''&gt;''&quot;''&amp;''&#38;'"\t"'<p');

            if (
$override_email_charset) {
                
$r_ar = array("'""'"'"''"'"'""'"'"''...''-''--''x''[tm]''(r)''(c)''<''>''"''&''&'' '"\n<p");
            } else {
                
$r_ar = array('‘''’''“''”''’''?''?''…''–''—''×''™''®''©''<''>''"''&''&'' '"\n<p");
            }

            
$msg[] = trim(strip_tags(str_replace($s_ar$r_ar, (trim(strip_tags($thisarticle['excerpt'])) ? $thisarticle['excerpt'] : $thisarticle['body']))));

            if (empty(
$com_connect_recipient)) {
                return 
gTxt('com_connect_field_missing', array('{field}' => gTxt('com_connect_recipient')));
            } else {
                
$to $com_connect_recipient;
            }
        }

        
$msg implode("\n\n"$msg);

        if (
$body_form) {
            
$msg parse_form($body_form);
        }

        
$msg str_replace(array("\r\n""\r""\n"), array("\n""\n"$sep), $msg);

        
$reply   com_connect_strip($from $com_connect_from '');
        
$from    com_connect_strip($from $from $com_connect_from);
        
$to      com_connect_strip($to);
        
$subject com_connect_strip($subject);
        
$msg     com_connect_strip($msgfalse);

        if (
$override_email_charset) {
            
$charset 'ISO-8859-1';
            
$subject utf8_decode($subject);
            
$msg     utf8_decode($msg);
        } else {
            
$charset 'UTF-8';
        }

        
// TODO: function deprecated in 4.6.0.
        
$subject encode_mailheader($subject'text');

        
$headers = array(
            
'from'          => $from,
            
'separator'     => $sep,
            
'reply'         => $reply,
            
'charset'       => $charset,
            
'content_type'  => 'text/plain',
            
'xfer_encoding' => '8bit',
        );

        
safe_update('txp_discuss_nonce'"used = '1', issue_time = '$now_date'""nonce = '$nonce'");

        if (
com_connect_deliver($to$subject$msg$headers$fields, array('isCopy' => false))) {
            
$_POST = array();

            if (
$copysender && $com_connect_from) {
                
com_connect_deliver(com_connect_strip($com_connect_from), $subject$msg$headers$fields, array('isCopy' => true));
            }

            if (
$redirect) {
                while (@
ob_end_clean());
                
$uri hu.ltrim($redirect,'/');

                if (empty(
$_SERVER['FCGI_ROLE']) && empty($_ENV['FCGI_ROLE'])) {
                    
txp_status_header('303 See Other');
                    
header('Location: '.$uri);
                    
header('Connection: close');
                    
header('Content-Length: 0');
                } else {
                    
$uri txpspecialchars($uri);
                    
$refresh gTxt('com_connect_refresh');
                    echo <<<END
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>
$sitename</title>
    <meta http-equiv="refresh" content="0;url=
$uri">
</head>
<body>
    <a href="
$uri">$refresh</a>
</body>
</html>
END;
                }

                exit;
            } else {
                return 
'<div class="' $com_connect_flags['cls_thanks'] . '" id="com'.$com_connect_form_id.'">' .
                    (
$thanks_form parse_form($thanks_form) : $thanks) .
                    
'</div>';
            }
        } else {
            
// Plugin modules may have set error messages: display if appropriate.
            
if ($com_connect_error) {
                
$out .= n.doWrap(array_unique($com_connect_error), 'ul''li'$com_connect_flags['cls_wrapper']).n;
            } else {
                
$out .= graf(gTxt('com_connect_mail_sorry'));
            }
        }
    }

    if (
$show_input && !$send_article || gps('com_connect_send_article')) {
        
$contactForm '<form method="post"' . ((!$show_error && $com_connect_error) ? '' ' id="com' $com_connect_form_id '"') .
            (
$class ' class="' $class '"' '') .
            (
$browser_validate '' ' novalidate') .
            
' action="' txpspecialchars(serverSet('REQUEST_URI')) . '#com' $com_connect_form_id '">' .
            (
$label '<fieldset>' '') .
            (
$label '<legend>' txpspecialchars($label) . '</legend>' '') .
            
$out .
            
'<input type="hidden" name="com_connect_nonce" value="' $com_connect_nonce '" />' .
            
'<input type="hidden" name="com_connect_form_id" value="' $com_connect_form_id '" />' .
            
$form .
            
callback_event('comconnect.form') .
            (
$label ? ('</fieldset>') : '') .
            
'</form>';

        
callback_event_ref('comconnect.render'''0$contactForm$atts);

        return 
$contactForm;
    }

    return 
'';
}

/**
 * Tag: Render a text input field.
 *
 * Many different $types are allowed. In fact, the $type attribute is not
 * restricted in any way to allow for any future types. This does bring some
 * developer responsibility if you wish your document to validate because
 * not all attributes will be valid for all input types, despite the plugin
 * faithfully rendering whatever attributes it is given.
 *
 * A lot of invalid situations are recognised (e.g. only permitting max/min/step
 * to occur on 'numeric' input types) but some are not easily trappable so they
 * are left to user discretion.
 *
 * @param  array  $atts Tag attributes
 * @return string HTML
 */
function com_connect_text($atts)
{
    global 
$com_connect_error$com_connect_submit$com_connect_flags;

    
extract(com_connect_lAtts(array(
        
'autocomplete'   => '',
        
'break'          => br,
        
'class'          => 'comText',
        
'default'        => '',
        
'html_form'      => $com_connect_flags['this_form'],
        
'isError'        => '',
        
'inputmode'      => '',
        
'label'          => gTxt('com_connect_text'),
        
'label_position' => 'before',
        
'max'            => null,
        
'min'            => null,
        
'name'           => '',
        
'pattern'        => '',
        
'placeholder'    => '',
        
'required'       => $com_connect_flags['required'],
        
'size'           => '',
        
'step'           => '',
        
'type'           => 'text',
    ), 
$atts));

    
$doctype get_pref('doctype''xhtml');

    
$datetime_types = array(
        
'date',
        
'datetime',
        
'datetime-local',
        
'month',
        
'time',
        
'week',
    );

    
$numeric_types = array(
        
'number',
        
'range',
    );

    
$is_datetime = (in_array($type$datetime_types));
    
$is_numeric = (in_array($type$numeric_types));

    
// Dates / times get special treatment: no default min/max if not set by tag.
    
if (!$is_datetime && $min === null) {
        
$min 0;
    }

    if (!
$is_datetime && $max === null) {
        
$max 100;
    }

    if (empty(
$name)) {
        
$name com_connect_label2name($label);
    }

    if (
$com_connect_submit) {
        
$value trim(ps($name));
        
$utf8len preg_match_all("/./su"$value$utf8ar);
        
$hlabel txpspecialchars($label);
        
$datetime_ok true;

        if (
$is_datetime) {
            
$minval $min;
            
$maxval $max;
            
$cmpval $value;

            try {
                
$dt = new DateTime($cmpval);
                
$cmpval $dt->format('U');

                if (
$min) {
                    
$dt = new DateTime($min);
                    
$minval $dt->format('U');
                }

                if (
$max) {
                    
$dt = new DateTime($max);
                    
$maxval $dt->format('U');
                }
            } catch (
Exception $e) {
                
$datetime_ok false;
            }
        }

        if (
strlen($value)) {
            if (!
$utf8len) {
                
$com_connect_error[] = gTxt('com_connect_invalid_utf8', array('{field}' => $hlabel));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$is_datetime && !$datetime_ok) {
                
$com_connect_error[] = gTxt('com_connect_format_warning', array('{field}' => $hlabel'{value}' => $value));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$min && !$is_numeric && !$is_datetime && $utf8len $min) {
                
$com_connect_error[] = gTxt('com_connect_min_warning', array('{field}' => $hlabel'{value}' => $min));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$max && !$is_numeric && !$is_datetime && $utf8len $max) {
                
$com_connect_error[] = gTxt('com_connect_max_warning', array('{field}' => $hlabel'{value}' => $max));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$min && $is_datetime && $cmpval $minval) {
                
$com_connect_error[] = gTxt('com_connect_minval_warning', array('{field}' => $hlabel'{value}' => $min));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$max && $is_datetime && $cmpval $maxval) {
                
$com_connect_error[] = gTxt('com_connect_maxval_warning', array('{field}' => $hlabel'{value}' => $max));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$min && $is_numeric && $value $min) {
                
$com_connect_error[] = gTxt('com_connect_minval_warning', array('{field}' => $hlabel'{value}' => $min));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$max && $is_numeric && $value $max) {
                
$com_connect_error[] = gTxt('com_connect_maxval_warning', array('{field}' => $hlabel'{value}' => $max));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$pattern and !preg_match('/^'.$pattern.'$/'$value)) {
                
$com_connect_error[] = gTxt('com_connect_pattern_warning', array('{field}' => $hlabel'{value}' => $pattern));
                
$isError $com_connect_flags['cls_element'];
            } else {
                
com_connect_store($name$label$value);
            }
        } elseif (
$required) {
            
$com_connect_error[] = gTxt('com_connect_field_missing', array('{field}' => $hlabel));
            
$isError $com_connect_flags['cls_element'];
        }
    } else {
        
$value $default;
    }

    
// Core attributes.
    
$attr com_connect_build_atts(array(
        
'id'    => (isset($id) ? $id $name),
        
'name'  => $name,
        
'type'  => $type,
        
'value' => $value,
    ));

    if (
$size && !$is_numeric) {
        
$attr['size'] = 'size="' intval($size) . '"';
    }

    if (
$min && !$is_numeric) {
        
$attr['minlength'] = 'minlength="' intval($min) . '"';
    }

    if (
$max && !$is_numeric) {
        
$attr['maxlength'] = 'maxlength="' intval($max) . '"';
    }

    if (
$doctype !== 'xhtml' && ($is_numeric || $is_datetime)) {
        
// Not using intval() because min/max/step can be floating point values.
        
$attr += com_connect_build_atts(array(
            
'min'  => $min,
            
'max'  => $max,
            
'step' => $step,
        ));
    }

    
// HTML5 attributes.
    
$required = ($required) ? 'required' '';
    if (
$doctype !== 'xhtml') {
        
$attr += com_connect_build_atts(array(
            
'autocomplete' => $autocomplete,
            
'form'         => $html_form,
            
'inputmode'    => $inputmode,
            
'pattern'      => $pattern,
            
'placeholder'  => $placeholder,
            
'required'     => $required,
        ));

        if (
$isError) {
            
$attr += com_connect_build_atts(array(
                
'aria-invalid' => 'true',
            ));
        }
    }

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classes = array();

    foreach (array(
$class, ($required $com_connect_flags['cls_required'] : ''), $isError) as $cls) {
        if (
$cls) {
            
$classes[] = $cls;
        }
    }

    
$classStr = ($classes ' class="' implode(' '$classes) . '"' '');
    
$labelStr '<label for="' $name '"' $classStr '>' txpspecialchars($label) . '</label>';

    return (
$label_position === 'before' $labelStr $break '') .
        
'<input' $classStr . ($attr ' ' implode(' '$attr) : '') . ' />' .
        (
$label_position === 'after' $break $labelStr '');
}

/**
 * Tag: Render an email input field.
 *
 * @param  array  $atts Tag attributes
 * @return string HTML
 */
function com_connect_email($atts)
{
    global 
$com_connect_error$com_connect_submit$com_connect_from$com_connect_recipient$com_connect_flags;

    
// TODO: 'multiple' attribute?
    
$defaults = array(
        
'autocomplete'   => '',
        
'break'          => br,
        
'class'          => 'comEmail',
        
'default'        => '',
        
'html_form'      => $com_connect_flags['this_form'],
        
'isError'        => '',
        
'label'          => gTxt('com_connect_email'),
        
'label_position' => 'before',
        
'max'            => 100,
        
'min'            => 0,
        
'name'           => '',
        
'pattern'        => '',
        
'placeholder'    => '',
        
'required'       => $com_connect_flags['required'],
        
'send_article'   => 0,
        
'size'           => '',
        
'type'           => 'email',
    );

    
extract(com_connect_lAtts($defaults$atts));

    if (empty(
$name)) {
        
$name com_connect_label2name($label);
    }

    
$email $com_connect_submit trim(ps($name)) : $default;

    if (
$com_connect_submit && strlen($email)) {
        if (!
is_valid_email($email)) {
            
$com_connect_error[] = gTxt('com_connect_invalid_email', array('{email}' => txpspecialchars($email)));
            
$isError $com_connect_flags['cls_element'];
        } else {
            
preg_match("/@(.+)$/"$email$match);
            
$domain $match[1];

            if (
is_callable('checkdnsrr') && checkdnsrr('textpattern.com.','A') && !checkdnsrr($domain.'.','MX') && !checkdnsrr($domain.'.','A')) {
                
$com_connect_error[] = gTxt('com_connect_invalid_host', array('{host}' => txpspecialchars($domain)));
                
$isError $com_connect_flags['cls_element'];
            } else {
                if (
$send_article) {
                    
$com_connect_recipient $email;
                } else {
                    
$com_connect_from $email;
                }
            }
        }
    }

    
$passed_atts = array();

    foreach (
$defaults as $key => $value) {
        
$passed_atts[$key] = $$key;
    }

    
$passed_atts['isError'] = $isError;
    unset (
$passed_atts['send_article']);

    return 
com_connect_text($passed_atts);
}

/**
 * Tag: Render a textarea input field.
 *
 * @param  array  $atts Tag attributes
 * @return string HTML
 */
function com_connect_textarea($atts)
{
    global 
$com_connect_error$com_connect_submit$com_connect_flags;

    
extract(com_connect_lAtts(array(
        
'autocomplete'   => '',
        
'break'          => br,
        
'class'          => 'comTextarea',
        
'cols'           => 58,
        
'default'        => '',
        
'html_form'      => $com_connect_flags['this_form'],
        
'isError'        => '',
        
'label'          => gTxt('com_connect_message'),
        
'label_position' => 'before',
        
'max'            => 10000,
        
'min'            => 0,
        
'name'           => '',
        
'placeholder'    => '',
        
'required'       => $com_connect_flags['required'],
        
'rows'           => 8,
        
'wrap'           => '',
    ), 
$atts));

    
$min intval($min);
    
$max intval($max);

    if (empty(
$name)) {
        
$name com_connect_label2name($label);
    }

    
$doctype get_pref('doctype''xhtml');

    if (
$com_connect_submit) {
        
$value preg_replace('/^\s*[\r\n]/'''rtrim(ps($name)));
        
$utf8len preg_match_all("/./su"ltrim($value), $utf8ar);
        
$hlabel txpspecialchars($label);

        if (
strlen(ltrim($value))) {
            if (!
$utf8len) {
                
$com_connect_error[] = gTxt('com_connect_invalid_utf8', array('{field}' => $hlabel));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$min && $utf8len $min) {
                
$com_connect_error[] = gTxt('com_connect_min_warning', array('{field}' => $hlabel'{value}' => $min));
                
$isError $com_connect_flags['cls_element'];
            } elseif (
$max && $utf8len $max) {
                
$com_connect_error[] = gTxt('com_connect_max_warning', array('{field}' => $hlabel'{value}' => $max));
                
$isError $com_connect_flags['cls_element'];
            } else {
                
com_connect_store($name$label$value);
            }
        } elseif (
$required) {
            
$com_connect_error[] = gTxt('com_connect_field_missing', array('{field}' => $hlabel));
            
$isError $com_connect_flags['cls_element'];
        }
    } else {
        
$value $default;
    }

    
// Core attributes.
    
$attr com_connect_build_atts(array(
        
'id'        => (isset($id) ? $id $name),
        
'name'      => $name,
        
'cols'      => intval($cols),
        
'rows'      => intval($rows),
        
'maxlength' => $max,
    ));

    
// HTML5 attributes.
    
$required = ($required) ? 'required' '';
    if (
$doctype !== 'xhtml') {
        
$attr += com_connect_build_atts(array(
            
'autocomplete' => $autocomplete,
            
'form'         => $html_form,
            
'placeholder'  => $placeholder,
            
'required'     => $required,
        ));

        if (
$isError) {
            
$attr += com_connect_build_atts(array(
                
'aria-invalid' => 'true',
            ));
        }
    }

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classes = array();

    foreach (array(
$class, ($required $com_connect_flags['cls_required'] : ''), $isError) as $cls) {
        if (
$cls) {
            
$classes[] = $cls;
        }
    }

    
$classStr = ($classes ' class="' implode(' '$classes) . '"' '');
    
$labelStr '<label for="' $name '"' $classStr '>' txpspecialchars($label) . '</label>';

    return (
$label_position === 'before' $labelStr $break '') .
        
'<textarea' $classStr . ($attr ' ' implode(' '$attr) : '') . '>' txpspecialchars($value) . '</textarea>' .
        (
$label_position === 'after' $break $labelStr '');
}

/**
 * Tag: Render a select/option input list.
 *
 * @param  array  $atts Tag attributes
 * @return string HTML
 */
function com_connect_select($atts$thing null)
{
    global 
$com_connect_error$com_connect_submit$com_connect_flags;

    
// TODO: multiple attribute?
    
extract(com_connect_lAtts(array(
        
'break'          => br,
        
'class'          => 'comSelect',
        
'delimiter'      => ',',
        
'html_form'      => $com_connect_flags['this_form'],
        
'isError'        => '',
        
'label'          => gTxt('com_connect_option'),
        
'label_position' => 'before',
        
'list'           => ''// TODO: remove from here in favour of the global list attribute.
        
'options'        => gTxt('com_connect_general_inquiry'),
        
'name'           => '',
        
'required'       => $com_connect_flags['required'],
        
'selected'       => '',
        
'size'           => '',
    ), 
$atts));

    
// Detect old-school use of the list attribute. Note that deprecated_function_with
    // isn't strictly the correct error to throw, but it's close enough until core has
    // a dedicated deprecated_attribute_with string.
    
if (!empty($list) && strpos($list$delimiter) !== false) {
        
$options $list;
        
trigger_error(gTxt('deprecated_function_with', array('{name}' => 'list''{with}' => 'options')), E_USER_NOTICE);
        unset(
$list$atts['list']);
    }

    if (empty(
$name)) {
        
$name com_connect_label2name($label);
    }

    
$value = ($com_connect_submit) ? trim(ps($name)) : $selected;
    
$doctype get_pref('doctype''xhtml');

    if (
$thing) {
        
com_connect_option(null$value);
        
$out parse($thing);
        
$options com_connect_option(nullnull);
    } else {
        
$options array_map('trim'explode($delimiterpreg_replace('/[\r\n\t\s]+/'' '$options)));
        
$out '';

        foreach (
$options as $item) {
            
$out .= n.t.'<option' . ($item == $value ' selected' . (($doctype === 'xhtml') ? '="selected"' '') . '>' '>') . (strlen($item) ? txpspecialchars($item) : ' ') . '</option>';
        }
    }

    if (
$com_connect_submit) {
        if (
strlen($value)) {
            if (
in_array($value$options)) {
                
com_connect_store($name$label$value);
            } else {
                
$com_connect_error[] = gTxt('com_connect_invalid_value', array('{field}' => txpspecialchars($label), '{value}' => txpspecialchars($value)));
                
$isError $com_connect_flags['cls_element'];
            }
        } elseif (
$required) {
            
$com_connect_error[] = gTxt('com_connect_field_missing', array('{field}' => txpspecialchars($label)));
            
$isError $com_connect_flags['cls_element'];
        }
    } else {
        
$value $selected;
    }

    
// Core attributes.
    
$attr com_connect_build_atts(array(
        
'id'   => (isset($id) ? $id $name),
        
'name' => $name,
    ));

    if (
$size && is_numeric($size)) {
        
$attr['size'] = 'size="' intval($size) . '"';
    }

    
// HTML5 attributes.
    
$required = ($required) ? 'required' '';
    if (
$doctype !== 'xhtml') {
        
$attr += com_connect_build_atts(array(
            
'form'     => $html_form,
            
'required' => $required,
        ));

        if (
$isError) {
            
$attr += com_connect_build_atts(array(
                
'aria-invalid' => 'true',
            ));
        }
    }

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classes = array();

    foreach (array(
$class, ($required $com_connect_flags['cls_required'] : ''), $isError) as $cls) {
        if (
$cls) {
            
$classes[] = $cls;
        }
    }

    
$classStr = ($classes ' class="' implode(' '$classes) . '"' '');
    
$labelStr '<label for="' $name '"' $classStr '>' txpspecialchars($label) . '</label>';

    return (
$label_position === 'before' $labelStr $break '') .
        
'<select' $classStr . ($attr ' ' implode(' '$attr) : '') . '>' .
            
$out .
        
'</select>' .
        (
$label_position === 'after' $break $labelStr '');
}

/**
 * Tag: Render a set of select options.
 *
 * @param  array  $atts  Tag attributes
 * @param  string $thing Tag's container
 * @return string HTML
 */
function com_connect_option($atts$thing null)
{
    static 
$options;
    static 
$match;

    if (
$atts === null) {
        if (
$thing === null) {
            return 
$options;
        } else {
            
$match $thing;
            return;
        }
    }

    global 
$com_connect_error$com_connect_submit;

    
extract(com_connect_lAtts(array(
        
'class'    => 'comOption',
        
'label'    => null,
        
'selected' => null,
        
'value'    => null,
    ), 
$atts));

    
$val = ($value === null) ? (!empty($thing) ? $thing : (!empty($label) ? $label null)) : $value;
    
$label = ($label === null) ? (!empty($thing) ? $thing : (!empty($value) ? $value null)) : $label;

    
$attr = array();
    
$doctype get_pref('doctype''xhtml');

    if (
$com_connect_submit) {
        
$options[] = $val;

        if (
$val !== null && ((string)$val === (string)$match)) {
            
$attr[] = 'selected' . (($doctype === 'xhtml') ? '="selected"' '');
        }
    } elseif (
$selected || ($val !== null && ((string)$val === (string)$match))) {
        
$attr[] = 'selected' . (($doctype === 'xhtml') ? '="selected"' '');
    }

    
// Core attributes.
    
$attr += com_connect_build_atts(array(
        
'value' => $val,
    ));

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classStr = (($class) ? ' class="' $class '"' '');

    return 
'<option' $classStr . ($attr ' ' implode(' '$attr) : '') . '>' txpspecialchars($label) . '</option>';
}

/**
 * Tag: Render a checkbox.
 *
 * @param  array  $atts  Tag attributes
 * @return string HTML
 * @todo checkbox groups?
 */
function com_connect_checkbox($atts)
{
    global 
$com_connect_error$com_connect_submit$com_connect_flags;

    
extract(com_connect_lAtts(array(
        
'break'          => ' ',
        
'checked'        => 0,
        
'class'          => 'comCheckbox',
        
'html_form'      => $com_connect_flags['this_form'],
        
'isError'        => '',
        
'label'          => gTxt('com_connect_checkbox'),
        
'label_position' => 'after',
        
'name'           => '',
        
'required'       => $com_connect_flags['required'],
        
'value'          => null,
    ), 
$atts));

    if (empty(
$name)) {
        
$name com_connect_label2name($label);
    }

    
$doctype get_pref('doctype''xhtml');

    if (
$com_connect_submit) {
        
$theValue = (bool) ps($name);

        if (
$required && !$theValue) {
            
$com_connect_error[] = gTxt('com_connect_field_missing', array('{field}' => txpspecialchars($label)));
            
$isError $com_connect_flags['cls_element'];
        } else {
            
$toStore = (($value !== null && $theValue) ? $value : ($theValue gTxt('yes') : gTxt('no')));
            
com_connect_store($name$label$toStore);
        }
    } else {
        
$theValue $checked;
    }

    
// Core attributes.
    
$attr com_connect_build_atts(array(
        
'id'    => (isset($id) ? $id $name),
        
'name'  => $name,
    ));

    if (
$value !== null) {
        
$attr += com_connect_build_atts(array(
            
'value' => $value,
        ));
    }

    
// HTML5 attributes.
    
$required = ($required) ? 'required' '';
    if (
$doctype !== 'xhtml') {
        
$attr += com_connect_build_atts(array(
            
'form'     => $html_form,
            
'required' => $required,
        ));

        if (
$isError) {
            
$attr += com_connect_build_atts(array(
                
'aria-invalid' => 'true',
            ));
        }
    }

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classes = array();

    foreach (array(
$class, ($required $com_connect_flags['cls_required'] : ''), $isError) as $cls) {
        if (
$cls) {
            
$classes[] = $cls;
        }
    }

    
$classStr = ($classes ' class="' implode(' '$classes) . '"' '');
    
$labelStr '<label for="' $name '"' $classStr '>' txpspecialchars($label) . '</label>';

    return (
$label_position === 'before' $labelStr $break '') .
        
'<input type="checkbox"' $classStr .
            (
$theValue ' checked' . (($doctype === 'xhtml') ? '="checked"' '') : '') . ($attr ' ' implode(' '$attr) : '') . ' />' .
        (
$label_position === 'after' $break $labelStr '');
}

/**
 * Tag: Render a radio button.
 *
 * @param  array  $atts  Tag attributes
 * @return string HTML
 */
function com_connect_radio($atts)
{
    global 
$com_connect_error$com_connect_submit$com_connect_values$com_connect_flags$com_connect_group;

    
extract(com_connect_lAtts(array(
        
'break'          => ' ',
        
'checked'        => 0,
        
'class'          => 'comRadio',
        
'group'          => '',
        
'html_form'      => $com_connect_flags['this_form'],
        
'isError'        => '',
        
'label'          => gTxt('com_connect_option'),
        
'label_position' => 'after',
        
'name'           => '',
        
'required'       => null,
        
'value'          => null,
    ), 
$atts));

    static 
$cur_name = array();
    static 
$cur_req = array();
    static 
$cur_group null;

    if (!
$group) {
        if (
$cur_group === null) {
            
$group gTxt('com_connect_radio');
        } else {
            
$group $cur_group;
        }
    } else {
        if (!
$name) {
            
$name $group;
        }
    }

    if (isset(
$cur_name[$group])) {
        if (!
$name) {
            
$name $cur_name[$group];
        }
    } else {
        if (
$name) {
            
$cur_name[$group] = $name;
        } else {
            
$name $cur_name[$group] = gTxt('com_connect_radio');
        }
    }

    if (isset(
$cur_req[$group])) {
        if (
$required === null) {
            
$required $cur_req[$group];
        }
    } else {
        if (
$required === null) {
            
$required $cur_req[$group] = $com_connect_flags['required'];
        } else {
            
$cur_req[$group] = $required;
        }
    }

    
$cur_group $group;

    
$id 'q' md5($name '=>' $label);
    
$name com_connect_label2name($name);
    
$doctype get_pref('doctype''xhtml');
    
$com_connect_group[$name][$id]['req'] = $required;
    
$com_connect_group[$name][$id]['label'] = $group;

    if (
$com_connect_submit) {
        
$toCompare = ($value === null $id $value);
        
$is_checked = (ps($name) == $toCompare);
        
$com_connect_group[$name][$id]['isSet'] = $is_checked;

        if (
$is_checked || $checked && !isset($com_connect_values[$name])) {
            
com_connect_store($name$group, ($value !== null $value $label));
        }
    } else {
        
$is_checked $checked;
    }

    
// Core attributes.
    
$attr com_connect_build_atts(array(
        
'id'    => $id,
        
'name'  => $name,
        
'value' => ($value !== null $value $id),
    ));

    
// HTML5 attributes.
    
$required = ($required) ? 'required' '';
    if (
$doctype !== 'xhtml') {
        
$attr += com_connect_build_atts(array(
            
'form'     => $html_form,
            
'required' => $required,
        ));
    }

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classes = array();

    foreach (array(
$class, ($required $com_connect_flags['cls_required'] : ''), $isError) as $cls) {
        if (
$cls) {
            
$classes[] = $cls;
        }
    }

    
$classStr = ($classes ' class="' implode(' '$classes) . '"' '');
    
$labelStr '<label for="' $id '"' $classStr '>' txpspecialchars($label) . '</label>';

    return (
$label_position === 'before' $labelStr $break '') .
        
'<input type="radio"'$classStr . ($attr ' ' implode(' '$attr) : '') .
            ( 
$is_checked ' checked' . (($doctype === 'xhtml') ? '="checked"' ''). ' />' ' />') .
        (
$label_position === 'after' $break $labelStr '');
}

/**
 * Tag: Store server information in the payload.
 *
 * @param  array  $atts  Tag attributes
 */
function com_connect_serverinfo($atts)
{
    global 
$com_connect_submit;

    
extract(com_connect_lAtts(array(
        
'label' => '',
        
'name'  => '',
    ), 
$atts));

    if (empty(
$name)) {
        
$name com_connect_label2name($label);
    }

    if (
strlen($name) && $com_connect_submit) {
        if (!
$label) {
            
$label $name;
        }

        
com_connect_store($name$labelserverSet($name));
    }
}

/**
 * Tag: Store a secret value in the payload.
 *
 * @param  array  $atts  Tag attributes
 */
function com_connect_secret($atts$thing null)
{
    global 
$com_connect_submit;

    
extract(com_connect_lAtts(array(
        
'label' => gTxt('com_connect_secret'),
        
'name'  => '',
        
'value' => '',
    ), 
$atts));

    
$name com_connect_label2name($name $name $label);

    if (
$com_connect_submit) {
        if (
$thing) {
            
$value trim(parse($thing));
        }

        
com_connect_store($name$label$value);
    }

    return 
'';
}

/**
 * Tag: Render a submit button.
 *
 * If the container is used, a button tag is rendered, otherwise a
 * standard input type=submit button is rendered.
 *
 * @param  array  $atts  Tag attributes
 * @param  string $thing Tag's container
 * @return string HTML
 */
function com_connect_submit($atts$thing null)
{
    global 
$com_connect_flags;

    
extract(com_connect_lAtts(array(
        
'class'     => 'comSubmit',
        
'html_form' => $com_connect_flags['this_form'],
        
'label'     => gTxt('com_connect_send'),
    ), 
$atts));

    
$label txpspecialchars($label);
    
$doctype get_pref('doctype''xhtml');

    
$attr = array();

    
// HTML5 attributes.
    
if ($doctype !== 'xhtml') {
        
$attr += com_connect_build_atts(array(
            
'form' => $html_form,
        ));
    }

    
// Global attributes.
    
$attr += com_connect_build_atts($com_connect_globals$atts);

    
$classStr = ($class ' class="' $class '"' '');

    if (
strlen($thing)) {
        return 
'<button type="submit"' $classStr ' name="com_connect_submit" value="' $label '"' . ($attr ' ' implode(' '$attr) : '') . '>' . ($thing trim(parse($thing)) : $label) . '</button>';
    } else {
        return 
'<input type="submit"' $classStr ' name="com_connect_submit" value="' $label '"' . ($attr ' ' implode(' '$attr) : '') . ' />';
    }
}

/**
 * Tag: Render a link to allow someone to forward a page to a friend.
 *
 * @param  array  $atts  Tag attributes
 * @return string HTML
 */
function com_connect_send_article($atts)
{
    if (!isset(
$_REQUEST['com_connect_send_article'])) {
        
$linktext = (empty($atts['linktext'])) ? gTxt('com_connect_send_article') : $atts['linktext'];
        
$join = (empty($_SERVER['QUERY_STRING'])) ? '?' '&';
        
$href $_SERVER['REQUEST_URI'] . $join 'com_connect_send_article=yes';

        return 
'<a href="' txpspecialchars($href) . '" rel="nofollow">' txpspecialchars($linktext) . '</a>';
    }

    return;
}

/**
 * Perform post-processing for aggregate (group) controls like radio sets.
 *
 * @todo Can this be done any neater?
 * @todo Should this be exposed as a callback to allow plugins to extend the functionality?
 */
function com_connect_group_validate()
{
    global 
$com_connect_group$com_connect_error;

    
$flags = array();

    if (
$com_connect_group) {
        foreach (
$com_connect_group as $key => $grp) {
            foreach (
$grp as $id => $atts) {
                
$flags[$key]['label'] = $atts['label'];

                if (!empty(
$atts['req']) && !isset($flags[$key]['req'])) {
                    
$flags[$key]['req'] = 1;
                }

                if (!empty(
$atts['isSet']) && !isset($flags[$key]['isSet'])) {
                    
$flags[$key]['isSet'] = 1;
                }
            }
        }

        foreach (
$flags as $key => $data) {
            if (!empty(
$data['req']) && !isset($data['isSet'])) {
                
$com_connect_error[] = gTxt('com_connect_field_missing', array('{field}' => txpspecialchars($data['label'])));
            }
        }
    }
}

/**
 * Process tag attributes and set defaults.
 *
 * Handles HTML 'global' attributes too, though what we consider global differs slightly
 * from the W3C: any attributes that apply to most of the <input> tags is considered
 * global. Just saves repetitive code in each tag handler.
 *
 * Note that 'class' is omitted, because that has different default values per input
 * widget, so it is dealt with explicitly in each tag handler.
 *
 * @param  array $arr  Acceptable tag attributes
 * @param  array $atts User-submitted attribute values
 * @return array
 */
function com_connect_lAtts($pairs$atts)
{
    
lAtts(array('label' => ''), $attsfalse);

    foreach (array(
'button''copysender''checked''required''send_article''show_input''show_error') as $key) {
        if (isset(
$atts[$key])) {
            
$atts[$key] = ($atts[$key] === 'yes' || intval($atts[$key])) ? 0;
        }
    }

    if (isset(
$atts['break']) && $atts['break'] === 'br') {
        
$atts['break'] = '<br />';
    }

    
$com_connect_globals = array(
        
'accesskey'  => '',
        
'autofocus'  => '',
        
'dir'        => '',
        
'disabled'   => '',
        
'hidden'     => '',
        
'id'         => '',
        
'lang'       => '',
        
'list'       => '',
        
'readonly'   => '',
        
'spellcheck' => '',
        
'style'      => '',
        
'tabindex'   => '',
        
'title'      => '',
        
'translate'  => '',
    );

    foreach (
$atts as $name => $value) {
        if (
array_key_exists($name$pairs) || substr($name02) === 'on' || array_key_exists($name$com_connect_globals)) {
            
$pairs[$name] = $value;
        } elseif (
get_pref('production_status') != 'live') {
            
trigger_error(gTxt('unknown_attribute', array('{att}' => $name)));
        }
    }

    
$pairs['com_connect_globals'] = $com_connect_globals;

    return (
$pairs) ? $pairs false;
}

/**
 * Build a sanitized, unique attribute array from the given pairs.
 *
 * If called multiple times, earlier values supersede later values
 * if the keys clash.
 *
 * @param  array $pairs    Attribute tuples to compile
 * @param  array $defaults Attribute values to use if the $pairs tuple value is empty
 * @return array
 */
function com_connect_build_atts($pairs$defaults = array())
{
    
$attr = array();

    
$booleans = array(
        
'autofocus',
        
'checked',
        
'disabled',
        
'hidden',
        
'multiple',
        
'readonly',
        
'required',
        
'selected',
        );

    foreach (
$pairs as $key => $value) {
        if (
$value !== '' && $value !== null) {
            
$attr[$key] = $key . (in_array($key$booleans) ? '' '="' txpspecialchars($value) . '"');
        } else {
            if (isset(
$defaults[$key])) {
                
$attr[$key] = $key . (in_array($key$booleans) ? '' '="' txpspecialchars($defaults[$key]) . '"');
            }
        }
    }

    return 
$attr;
}

/**
 * Replace nulls and newlines in content with spaces.
 *
 * @param string $str    String to sanitize
 * @param bool   $header Whether the string is a header or not
 */
function com_connect_strip($str$header true)
{
    if (
$header) {
        
// TODO: strip_rn will be deprecated in 4.6.0.
        
$str strip_rn($str);
    }

    return 
preg_replace('/[\x00]/'' '$str);
}

/**
 * Handle content delivery of payload.
 *
 * Triggers a 'comconnect.deliver' callback event to override or augment the
 * delivery mechanism. Third party plugins can make alterations to the $payload,
 * then return one of the strings:
 *  -> "comconnect.send" (or no return value) to allow the plugin to continue mailing after the 3rd party plugin completes
 *  -> "comconnect.skip" to skip com_connect's mailing (i.e. the 3rd party handles mailing) and return 'success'
 *  -> "comconnect.fail" to skip com_connect's mailing and return 'fail'
 *
 * By hooking into the callback's step you can target either the main 'send'
 * process or the 'copysender' process. Examples of things you could do:
 * -> Add Multi-part MIME headers for HTML emails.
 * -> Add CC: or BCC: fields.
 * -> Subscribe people to mailing lists.
 * -> Handle the mailing process independently of Textpattern.
 *
 * @param string $to      Delivery address
 * @param string $subject Subject of message
 * @param string $body    Message content
 * @param array  $headers Message headers as tuples
 * @param array  $fields  Message field names and content as tuples
 * @param array  $flags   Signals to govern delivery / callback behaviour
 */
function com_connect_deliver($to$subject$body$headers$fields$flags)
{
    
$payload = array(
        
'to'      => $to,
        
'subject' => $subject,
        
'headers' => $headers,
        
'body'    => $body,
        
'fields'  => $fields,
    );

    
$flavour = ($flags['isCopy'] === true) ? 'copysender' 'send';

    
// Allow plugins to override or alter default action (mail) if required.
    // TODO: use has_handler() from 4.6.0+.
    
$ret callback_event_ref('comconnect.deliver'$flavour0$payload);

    if (
in_array('comconnect.fail'$ret)) {
        return 
false;
    } elseif (
in_array('comconnect.skip'$ret)) {
        return 
true;
    }

    
extract($payload);

    
$smtp_from get_pref('smtp_from');

    if (!
is_callable('mail')) {
        return (
get_pref('production_status') === 'live')
            ? 
gTxt('com_connect_mail_sorry')
            : 
gTxt('warn_mail_unavailable');
    }

    
$sep = (!empty($headers['separator'])) ? $headers['separator'] : (IS_WIN "\r\n" "\n");
    
$xfer_encoding = (!empty($headers['xfer_encoding'])) ? $headers['xfer_encoding'] : '8bit';
    
$content_type = (!empty($headers['content_type'])) ? $headers['content_type'] : 'text/plain';
    
$reply = (!empty($headers['reply'])) ? $headers['reply'] : '';
    
$charset = (!empty($headers['charset'])) ? $headers['charset'] : 'UTF-8';
    
$x_mailer = (!empty($headers['x_mailer'])) ? $headers['x_mailer'] : 'Textpattern (com_connect)';

    
$header_string 'From: ' $headers['from'] .
        (
$reply ? ($sep 'Reply-To: ' $reply) : '') .
        
$sep 'X-Mailer: ' $x_mailer .
        
$sep 'X-Originating-IP: ' com_connect_strip((!empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] . ' via ' '') . $_SERVER['REMOTE_ADDR']) .
        
$sep 'Content-Transfer-Encoding: ' $xfer_encoding .
        
$sep 'Content-Type: ' $content_type '; charset="' $charset '"';

    
// Remove all the header entries that have already been handled.
    
unset(
        
$headers['separator'],
        
$headers['xfer_encoding'],
        
$headers['content_type'],
        
$headers['reply'],
        
$headers['charset'],
        
$headers['x_mailer'],
        
$headers['from']
    );

    
// Any remaining headers set by plugins are appended as-is.
    
foreach ($headers as $name => $value) {
        
$header_string .= $sep $name ': ' $value;
    }

    if (
is_valid_email($smtp_from)) {
        if (
IS_WIN) {
            
ini_set('sendmail_from'$smtp_from);
        } elseif (!
ini_get('safe_mode')) {
            return 
mail($to$subject$body$header_string'-f'.$smtp_from);
        }
    }

    return 
mail($to$subject$body$header_string);
}

/**
 * Evaluate return values from plugins.
 */
class comconnect_evaluation
{
    private 
$status;

    
/**
     * Constructor.
     */
    
function __construct()
    {
        
$this->status 0;
    }

    
/**
     * Append the given status to the counter.
     */
    
function add_comconnect_status($check)
    {
        
$this->status += $check;
    }

    
/**
     * Fetch the current evaluator status.
     */
    
function get_comconnect_status()
    {
        return 
$this->status;
    }
}

/**
 * Evaluator singleton for checking return values from plugins.
 */
function &get_comconnect_evaluator()
{
    static 
$instance;

    if (!isset(
$instance)) {
        
$instance = new comconnect_evaluation();
    }

    return 
$instance;
}

/**
 * Convert the given label to a suitably unique name.
 *
 * @param string $label Label to convert to name.
 */
function com_connect_label2name($label)
{
    
$label trim($label);

    if (
strlen($label) == 0) {
        return 
'invalid';
    }

    if (
strlen($label) <= 32 && preg_match('/^[a-zA-Z][A-Za-z0-9:_-]*$/'$label)) {
        return 
$label;
    } else {
        return 
'q' md5($label);
    }
}

/**
 * Store the given names/values in the global arrays.
 *
 * @param  string $name  Parameter name
 * @param  string $label Parameter label
 * @param  string $value Parameter value
 */
function com_connect_store($name$label$value)
{
    global 
$com_connect_form$com_connect_labels$com_connect_values;

    
$com_connect_form[$label] = $value;
    
$com_connect_labels[$name] = $label;
    
$com_connect_values[$name] = $value;
}

/**
 * Override the language strings if necessary.
 *
 * @param  string $lang Language designator (e.g. fr-fr)
 * @return array        Partial language array to merge with $textarray
 */
function com_connect_load_lang($lang LANG)
{
    
$out = array();

    if (
$lang != LANG) {

        
$rs safe_rows("name, data"'txp_lang'"lang = '" doSlash($lang) . "' AND name like 'com\_connect\_%'");

        if (!empty(
$rs)) {
            foreach (
$rs as $a) {
                
$out[$a['name']] = $a['data'];
            }
        }
    }

    return 
$out;
}

/**
 * Return the value of the given attribute, by name or its label.
 *
 * @param  array $atts Attribute to return
 * @return string
 */
function com_connect_value($atts)
{
    global 
$com_connect_values$com_connect_form;

    
extract(lAtts(array(
        
'label' => '',
        
'name'  => '',
    ), 
$atts));

    
$str '';

    if (
$name) {
        
$str = isset($com_connect_values[$name]) ? $com_connect_values[$name] : '';
    } elseif (
$label) {
        
$str = isset($com_connect_form[$name]) ? $com_connect_form[$name] : '';
    }

    return 
trim($str);
}

/**
 * Return the label for the given attribute.
 *
 * @param  array $atts Attribute name to return
 * @return string
 */
function com_connect_label($atts)
{
    global 
$com_connect_labels;

    
extract(lAtts(array(
        
'name' => '',
    ), 
$atts));

    if (
$name) {
        return isset(
$com_connect_labels[$name]) ? $com_connect_labels[$name] : '';
    }

    return 
'';
}

/**
 * Conditional tag for checking variable conditions.
 *
 * @param  array  $atts  Attributes to check
 * @param  string $thing Tag's container content
 * @return string
 */
function com_connect_if($atts$thing null)
{
    
extract(lAtts(array(
        
'label' => '',
        
'name'  => '',
        
'value' => null,
    ), 
$atts));

    
$val com_connect_value(array(
        
'label' => $label,
        
'name'  => $name,
    ));

    if (
$val) {
        
$cond = ($value === null || $val == $value);
    } else {
        
$cond false;
    }

    return 
parse(EvalElse($thing$cond));
}