<?php

# PLUGIN PREVIEW BY TEXTPATTERN.INFO

/**
 * smd_bio
 *
 * A Textpattern CMS plugin for storing additional biographical author data:
 *  -> Create checkboxes, select lists, radio buttons, text input, image, etc fields
 *  -> Data is captured and stored when a user is created
 *  -> Allow people to manage their own profiles via smd_user_manager
 *
 * @author Stef Dawson
 * @link   http://stefdawson.com/
 *
 * @todo Stop smd_bio_iterate from trashing the bio data for future fields of the same name.
 * @todo Attempt table repair if bio/bio_meta get out of sync.
 * @todo Ability to specify wildcards/matches for fields/authors in the client tag.
 * @todo Specify tooltip extended bio information in prefs (cf. what to do about touch devices).
 */
if (txpinterface === 'admin') {
    
add_privs('smd_bio''1');
    
register_tab('extensions''smd_bio'gTxt('smd_bio_admin_tab'));
    
register_callback('smd_bio_dispatcher''smd_bio');
    
register_callback('smd_bio_fields''author_ui''extend_detail_form');
    
register_callback('smd_bio_admin_js''admin_side''head_end');

    
// Note these are all pre Txp's involvement
    
register_callback('smd_bio_save''admin''author_save'1);
    
register_callback('smd_bio_save''admin''author_save_new'1);
    
register_callback('smd_bio_delete''admin''admin_multi_edit'1);
    
register_callback('smd_bio_welcome''plugin_lifecycle.smd_bio');

    
// Doesn't hurt much to add these if the plugin's not installed.
    // Since plugins are loaded in load_order and then alphabetical,
    // at this point smd_user_manager doesn't 'exist' (b < u)
    
register_callback('smd_bio_save''smd_um''smd_um_save'1);
    
register_callback('smd_bio_save''smd_um''smd_um_save_new'1);
    
register_callback('smd_bio_delete''smd_um''smd_um_multi_edit'1);
} elseif (
txpinterface === 'public') {
    if (
class_exists('\Textpattern\Tag\Registry')) {
        
Txp::get('\Textpattern\Tag\Registry')
            ->
register('smd_bio_author')
            ->
register('smd_bio_info')
            ->
register('smd_bio_data')
            ->
register('smd_bio_iterate')
            ->
register('smd_bio_articles')
            ->
register('smd_if_bio')
            ->
register('smd_if_bio_is')
            ->
register('smd_if_bio_first_author')
            ->
register('smd_if_bio_last_author');
    }
}

register_callback('smd_bio_form_submit''mem_form.submit');

// Intercept image and extended bio display on Admin->Users panel
$smd_bio_step gps('smd_bio_step');
if (
$smd_bio_step == 'smd_bio_get_image') {
    
smd_bio_get_image();
}
if (
$smd_bio_step == 'smd_bio_get_ebio') {
    
smd_bio_get_ebio();
}

if (!
defined('SMD_BIO')) {
    
define("SMD_BIO"'smd_bio');
}
if (!
defined('SMD_BIO_META')) {
    
define("SMD_BIO_META"'smd_bio_meta');
}

// -------------------------------------------------------------
function smd_bio_get_styles() {
    
$smd_bio_styles = array(
        
'meta' =>
         
'.smd_bio_toggler { display:none; }',
        
'tooltip' =>
         
'#tooltip { position:absolute; border:1px solid #333; background:#f7f5d1; padding:10px 15px; opacity:.9; color:#333; display:none; max-width:60%; }',
    );
    return 
$smd_bio_styles;
}

// -------------------------------------------------------------
// Install/uninstall jumpoff point
function smd_bio_welcome($evt$stp) {
    
$msg '';
    switch (
$stp) {
        case 
'installed':
            
smd_bio_table_install(0);
            
$msg 'Pimp your users';
            break;
        case 
'deleted':
            
smd_bio_table_remove(0);
            break;
    }
    return 
$msg;
}

// ************************
// BIO CONFIGURATION
// ------------------------
function smd_bio_dispatcher($evt$stp) {
    
$available_steps = array(
        
'smd_bio_config'          => false,
        
'smd_bio_table_install'   => false,
        
'smd_bio_table_remove'    => false,
        
'smd_bio_meta_add'        => true,
        
'smd_bio_multi_edit'      => true,
        
'smd_bio_meta_save'       => true,
        
'smd_bio_save_pane_state' => true,
    );

    if (!
$stp or !bouncer($stp$available_steps)) {
        
$stp 'smd_bio_config';
    }
    
$stp();
}

// ------------------------
// The Extensions->Bio config panel, made up of two areas: the edit pane and the list pane
function smd_bio_config($msg='') {
    
smd_bio_table_install(0);

    
pagetop(gTxt('smd_bio_admin_tab'), $msg);

    echo 
n.'<div id="smd_bio_container" class="txp-container">'.
        
n.smd_bio_meta_edit().
        
n.smd_bio_meta_list().
        
n.'</div>';
}

// ------------------------
function smd_bio_meta_edit() {
    
$smd_bio_types smd_bio_get_types();
    
$smd_bio_coltypes smd_bio_get_coltypes();

    
$vars = array('step''id''title''name''type''coltype''colsize''size''val''position');
    
$rs = array();

    
extract(gpsa($vars));
    
$colsize = (int)$colsize;

    if (
$id && $step == 'meta_edit') {
        
$id assert_int($id);
        
$rs safe_row('*'SMD_BIO_META"id = $id");
        
extract($rs);
    }

    if (
$step == 'smd_bio_meta_save' || $step == 'smd_bio_meta_add' || $step == 'smd_bio_multi_edit') {
        foreach (
$vars as $var) {
            $
$var '';
        }
    }

    
$caption gTxt(($step == 'meta_edit') ? 'smd_bio_meta_edit' 'smd_bio_meta_add');

    
// Make the name/val pairs for the type selectInput
    
$selv = array();
    foreach (
$smd_bio_types as $widx => $wval) {
        
$selv[$widx] = $wval['name'];
    }

    
// Make the name/val pairs for the coltype selectInput
    
foreach($smd_bio_coltypes as $ctype => $cdata) {
        
$coltypes[$ctype] = $cdata['title'];
    }

    
$toggleState get_pref('pane_smd_bio_coltype_visible') ? true false;

    return 
hed($caption1' class="txp-heading"').
        
form(
            
'<div class="txp-edit">'.
            
inputLabel('name', ($id && $step == 'meta_edit' strong($name) : fInput('text''name'$name'''''''''''name')), 'name').
            
inputLabel('title'fInput('text''title'$title'''''''''''title'), 'title').
            
inputLabel('smd_bio_widget_type'selectInput('type'$selv$typefalse'''smd_bio_widget_type') .sp'<a id="smd_bio_colgroup" class="txp-summary lever'.(($toggleState) ? ' expanded' '').'" href="#">'.gTxt('smd_bio_more').'</a>''type').
            
inputLabel('smd_bio_coltype'selectInput('coltype'$coltypes$coltypefalse'''smd_bio_coltype'), 'smd_bio_coltype''''txp-form-field smd_bio_coltype '.(($toggleState) ? '' ' smd_bio_toggler')).
            
inputLabel('smd_bio_colsize'fInput('number''colsize'$colsize'''''''''''smd_bio_colsize'), 'smd_bio_colsize''''txp-form-field smd_bio_coltype '.(($toggleState) ? '' ' smd_bio_toggler')).
            
inputLabel('smd_bio_size'fInput('text''size'$size'''''''''''smd_bio_size'), 'smd_bio_size''smd_bio_size').
            
inputLabel('smd_bio_value'text_area('val''100''300'$val'smd_bio_value'), 'smd_bio_value''smd_bio_val').
            
inputLabel('smd_bio_position'fInput('text''position'$position'''''''''''smd_bio_position'), 'smd_bio_position').
            
graf(
                
fInput('submit''save'gTxt('save'), 'publish')
            ).

            
eInput('smd_bio').
            (
$id hInput('id'$id).hInput('name'$name).sInput('smd_bio_meta_save') : sInput('smd_bio_meta_add')).
            
tag(' ''span'' id="smd_bio_size_help_text" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_number" title="'.gTxt('smd_bio_sizehelp_numrange').'"').
            
tag(' ''span'' id="smd_bio_size_help_range" title="'.gTxt('smd_bio_sizehelp_numrange').'"').
            
tag(' ''span'' id="smd_bio_size_help_list" title="'.gTxt('smd_bio_help_unused').'"').
            
tag(' ''span'' id="smd_bio_size_help_multilist" title="'.gTxt('smd_bio_help_unused').'"').
            
tag(' ''span'' id="smd_bio_size_help_radio" title="'.gTxt('smd_bio_help_unused').'"').
            
tag(' ''span'' id="smd_bio_size_help_yesnoradio" title="'.gTxt('smd_bio_help_unused').'"').
            
tag(' ''span'' id="smd_bio_size_help_checkbox" title="'.gTxt('smd_bio_help_unused').'"').
            
tag(' ''span'' id="smd_bio_size_help_textarea" title="'.gTxt('smd_bio_sizehelp_textarea').'"').
            
tag(' ''span'' id="smd_bio_size_help_image" title="'.gTxt('smd_bio_sizehelp_image').'"').
            
tag(' ''span'' id="smd_bio_size_help_email" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_url" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_datetime" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_date" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_month" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_week" title="'.gTxt('smd_bio_sizehelp_text').'"').
            
tag(' ''span'' id="smd_bio_size_help_time" title="'.gTxt('smd_bio_sizehelp_text').'"').

            
tag(' ''span'' id="smd_bio_val_help_text" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_number" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_range" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_list" title="'.gTxt('smd_bio_valhelp_lrc').'"').
            
tag(' ''span'' id="smd_bio_val_help_multilist" title="'.gTxt('smd_bio_valhelp_lrc').'"').
            
tag(' ''span'' id="smd_bio_val_help_radio" title="'.gTxt('smd_bio_valhelp_lrc').'"').
            
tag(' ''span'' id="smd_bio_val_help_yesnoradio" title="'.gTxt('smd_bio_valhelp_ynr').'"').
            
tag(' ''span'' id="smd_bio_val_help_checkbox" title="'.gTxt('smd_bio_valhelp_lrc').'"').
            
tag(' ''span'' id="smd_bio_val_help_textarea" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_image" title="'.gTxt('smd_bio_valhelp_image').'"').
            
tag(' ''span'' id="smd_bio_val_help_email" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_url" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_datetime" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_date" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_month" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_week" title="'.gTxt('smd_bio_valhelp_text').'"').
            
tag(' ''span'' id="smd_bio_val_help_time" title="'.gTxt('smd_bio_valhelp_text').'"').
        
'</div>'
    
);
}

// ------------------------
function smd_bio_meta_list() {

    
$smd_bio_types smd_bio_get_types();

    
extract(gpsa(array('sort''dir''crit''search_method')));
    if (
$sort === ''$sort get_pref('smd_bio_meta_sort_column''name');
    if (
$dir === ''$dir get_pref('smd_bio_meta_sort_dir''asc');
    
$dir = ($dir == 'desc') ? 'desc' 'asc';

    if (!
in_array($sort, array('name''title''type''size''val''position'))) $sort 'position';

    
$sort_sql   $sort.' '.$dir;

    
set_pref('smd_bio_meta_sort_column'$sort'smd_bio'2''0PREF_PRIVATE);
    
set_pref('smd_bio_meta_sort_dir'$dir'smd_bio'2''0PREF_PRIVATE);

    
$switch_dir = ($dir == 'desc') ? 'asc' 'desc';

    
$rs safe_rows_start('*'SMD_BIO_META'1=1 ORDER BY '.$sort_sql);
    
$out = array();

    if (
$rs) {
        
$out[] =
        
n'<form id="smd_bio_form" action="index.php" method="post" name="longform" class="multi_edit_form">'.

        
n.'<div class="txp-listtables">'.
        
startTable('''''txp-list').
        
n.'<thead>'.
        
tr(
            
hCell(fInput('checkbox''select_all'0'''''''''''select_all'), ''' scope="col" title="'.gTxt('toggle_all_selected').'" class="multi-edit"').
            
column_head('name''name''smd_bio'true$switch_dir'''', ('name' == $sort) ? $dir '').
            
column_head('title''title''smd_bio'true$switch_dir'''', ('title' == $sort) ? $dir '').
            
column_head('type''type''smd_bio'true$switch_dir'''', ('type' == $sort) ? $dir '').
            
column_head(gTxt('smd_bio_size'), 'size''smd_bio'true$switch_dir'''', ('size' == $sort) ? $dir '').
            
column_head('value''val''smd_bio'true$switch_dir'''', ('val' == $sort) ? $dir '').
            
column_head(gTxt('smd_bio_position'), 'position''smd_bio'true$switch_dir'''', ('position' == $sort) ? $dir '')
        ).
        
n.'</thead>'.
        
n.'<tbody>';

        while (
$a nextRow($rs)) {
            
extract(doSpecial($a));

            
$out[] = tr(
                
td(fInput('checkbox''selected[]'$name)).
                
td(eLink('smd_bio''meta_edit''id'$id$name)).
                
td($title).
                
td($smd_bio_types[$type]['name']).
                
td($size).
                
td($val).
                
td($position)
            );
        }

        
$out[] = '</tbody>'.
            
endTable().
            
n.'</div>'.
            
smd_bio_multiedit_form(''$sort$dir$crit$search_method).
            
tInput().
            
'</form>';
    }
    return 
join(n$out);
}

// ------------------------
function smd_bio_multiedit_form($page$sort$dir$crit$search_method) {

    
$methods = array(
        
'delete' => gTxt('delete'),
    );

    return 
multi_edit($methods'smd_bio''smd_bio_multi_edit'$page$sort$dir$crit$search_method);
}

// ------------------------
function smd_bio_multi_edit()
{
    
$selected ps('selected');

    if (!
$selected or !is_array($selected))
    {
        
smd_bio();
    }

    
$selected array_map('assert_string'$selected);
    
$method   ps('edit_method');
    
$changed  = array();
    
$key '';

    switch (
$method)
    {
        case 
'delete':
            return 
smd_bio_meta_del($selected);
            break;

        default:
            
$key '';
            
$val '';
            break;
    }

    
smd_bio();
}

// ------------------------
function smd_bio_meta_make_list($val) {

    if (
strpos(strtolower($val), 'smd_bio_fn') !== false) {
        
// Special function syntax so call the designated
        // function to retrieve the value(s)
        
$params do_list($val'|');
        
array_shift($params); // Remove SMD_BIO_FN
        
$fn array_shift($params);

        
$fnbits do_list($fn'::');
        
$fncall = (isset($fnbits[1])) ? array($fnbits[0], $fnbits[1]) : $fnbits[0];
        
$val call_user_func_array($fncall$params);

        if (
is_array($val)) {
            
// Fake a string list of name => val pairs
            
$out = array();
            foreach (
$val as $idx => $item) {
                
$out[] = str_replace(',''&#44;'$idx ' => ' $item); // Preserve commas by encoding them
            
}
            
$val join(', '$out);
        }
    }

    
// Parse the values
    
$wvals do_list($val'\r\n');
    if (
count($wvals) == 1) {
        
$wvals do_list($val);
    }
    return 
join(', 'doArray($wvals'trim'));
}

// ------------------------
function smd_bio_meta_save() {
    
$smd_bio_coltypes smd_bio_get_coltypes();
    
$smd_bio_unused smd_bio_get_unused();

    
extract(doSlash(psa(array('id''name''title''type''coltype''colsize''size''position'))));
    
$val ps('val'); // Defer doSlash() until later

    
$id assert_int($id);
    
$val doSlash(smd_bio_meta_make_list($val));
    
$hasDefault $smd_bio_coltypes[$coltype]['has_default'];

    
// Validate the input to ensure items are emptied for types where they're unused
    
foreach ($smd_bio_unused as $unused => $excludes) {
        if (
in_array($type$excludes)) {
            $
$unused '';
        }
    }

    
// Validate the input to ensure items that require a column size have one...
    
if (empty($colsize) && $smd_bio_coltypes[$coltype]['size_req'] !== false) {
        
$colsize $smd_bio_coltypes[$coltype]['size_req'];
    }

    
// ... and that ones that don't require one are removed
    
if ($smd_bio_coltypes[$coltype]['size_req'] === false) {
        
$colsize null;
    }

    
// Try to adjust column type/size if applicable
    
$rs safe_alter(SMD_BIO"CHANGE `$name` `$name$coltype. (($colsize) ? "($colsize)" '') . " NULL" . (($hasDefault) ? " DEFAULT NULL" ''));

    if (
$rs) {
        
$rs safe_update(SMD_BIO_META"
            title = '
$title',
            type = '
$type',
            size = '
$size',
            coltype = '
$coltype',
            " 
. (($colsize) ? "colsize = $colsize," '') . "
            val = '
$val',
            position = '
$position'",
            
"id = $id"
        
);

        if (
$rs) {
            
$msg gTxt('smd_bio_meta_updated', array('{name}' => $name));
        } else {
            
$msg = array(gTxt('smd_bio_meta_update_partial', array('{name}' => $name)), E_WARNING);
        }
    } else {
        
$msg = array(gTxt('smd_bio_meta_update_failed', array('{name}' => $name)), E_WARNING);
    }

    
smd_bio_config($msg);
}

// ------------------------
function smd_bio_meta_add() {
    
$smd_bio_coltypes smd_bio_get_coltypes();
    
$smd_bio_unused smd_bio_get_unused();

    
extract(doSlash(psa(array('name''title''type''coltype''colsize''size''position'))));
    
$val ps('val'); // Defer doSlash() until later

    // Use the title as name if it's omitted
    
if ($name === '' && $title !== '') {
        
$name $title;
    }

    
$name smd_bio_sanitize($name);

    if (!empty(
$name) && smd_bio_meta_check($name)) {
        
$size = (empty($size)) ? 25 $size;
        
$hasDefault $smd_bio_coltypes[$coltype]['has_default'];

        
// Validate the input to ensure items are emptied for types where they're unused
        
foreach ($smd_bio_unused as $unused => $excludes) {
            if (
in_array($type$excludes)) {
                $
$unused '';
            }
        }

        
// Validate the input to ensure items that require a column size have one...
        
if (empty($colsize) && $smd_bio_coltypes[$coltype]['size_req'] !== false) {
            
$colsize $smd_bio_coltypes[$coltype]['size_req'];
        }
        
// ... and that ones that don't require one are removed
        
if ($smd_bio_coltypes[$coltype]['size_req'] === false) {
            
$colsize null;
        }

        
$ret safe_alter(SMD_BIO"ADD `$name$coltype. (($colsize) ? "($colsize)" '') . " NULL" . (($hasDefault) ? " DEFAULT NULL" ''));
        
$val doSlash(smd_bio_meta_make_list($val));

        if (
$ret) {
            
$rs safe_insert(SMD_BIO_META"
                name = '
$name',
                title = '
$title',
                type = '
$type',
                size = '
$size',
                coltype = '
$coltype',
                " 
. (($colsize) ? "colsize = $colsize," '') . "
                val = '
$val',
                position = '
$position'
            "
);

            if (
$rs) {
                
smd_bio_config(gTxt('smd_bio_meta_added'));
                return;
            }
        }
    }

    
smd_bio_config(array(gTxt('smd_bio_meta_not_added'), E_ERROR));
}

// ------------------------
function smd_bio_meta_del($names) {

    
$changed = array();

    
$names  $names array_map('assert_string'$names) : array(assert_string(ps('name')));
    
$message '';

    foreach (
$names as $name) {
        
$exists smd_bio_meta_check($name);
        
$ret = @safe_alter(SMD_BIO"DROP COLUMN `$name`");
        if (
$ret || $exists) {
            
$ret safe_delete(SMD_BIO_META"name='$name'");
            
$changed[] = $name;
        }
    }
    
smd_bio_config(gTxt('smd_bio_meta_deleted', array('{affected}' => join(', '$changed))));
}

// ------------------------
function smd_bio_meta_check($col) {
    
$ucols getThings('describe `'.PFX.'txp_users`');
    
$bcols getThings('describe `'.PFX.SMD_BIO.'`');
    
$cols array_merge($ucols$bcols);
    return (!
in_array($col$cols));
}

// ************************
// ADMIN -> USERS PANEL
// ------------------------
function smd_bio_fields($evt$stp$mt$data) {
    global 
$smd_um_event$txp_user;

    
$out $vals = array();

    
$rstep gps('step');
    if(
smd_bio_table_exist()) {
        if (
in_array($rstep, array('''author_edit''smd_um_edit''smd_um_save''smd_um_change_pass'))) {
            
extract(gpsa(array('user_id')));

            if (empty(
$user_id) && !has_privs('smd_um.usr.create')) {
                
// This is a self-edit from smd_user_manager, thus the user_id has not been sent
                
$user_id safe_field('user_id','txp_users',"name = '".doSlash($txp_user)."'");
            }
            
// Shame we have to double de-clutch here but we can't index on user_id unfortunately ('cos when
            // inserting new users, the bio functions run _BEFORE_ an auto_increment ID has been generated by Txp)
            
$uname safe_field('name','txp_users',"user_id = '".doSlash($user_id)."'");
            
$vals safe_row('*'SMD_BIO"user_ref='".doSlash($uname)."'");
        }

        
$widgets safe_rows('*'SMD_BIO_META'1=1 ORDER BY position');
        
$tdac_image ' class="smd_bio_image_cell"';
        foreach (
$widgets as $widget) {
            
$val = ($vals && isset($vals[$widget['name']])) ? $vals[$widget['name']] : $widget['val'];
            
$title = ($widget['title']) ? $widget['title'] : $widget['name'];
            
$sizeopts do_list($widget['size']);
            
$size1 $sizeopts[0];
            
$size2 = (isset($sizeopts[1])) ? $sizeopts[1] : '';
            
$size3 = (isset($sizeopts[2])) ? $sizeopts[2] : '';
            
$name 'smd_bio_'.$widget['name'];
            switch(
$widget['type']) {
                case 
'list':
                    
$selv smd_bio_splitval($widget['val']);
                    list(
$selv$dflt) = smd_bio_get_default($selv$val);
                    
$out[] = inputLabel(
                        
$name,
                        
selectInput($name$selv$dfltfalse''$name),
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_select '.$name
                    
);
                    break;
                case 
'multilist':
                    
$val = ($vals && isset($vals[$widget['name']])) ? $vals[$widget['name']] : '';
                    
$selv smd_bio_splitval($widget['val']);
                    list(
$selv$dflt) = smd_bio_get_default($selv$val);
                    
$use_val = (isset($vals[$widget['name']]) && ($vals[$widget['name']] !== '' || $vals[$widget['name']] !== null)) ? $val $dflt// Don't use defaults if this field has been previously saved
                    
$selectedVals do_list($use_val);
                    
$items = array();
                    
$items[] = '<select name="'.$name.'" class="list multiple" multiple="multiple" onchange="smd_bio_multisel(\''.$name.'\');">';
                    foreach (
$selv as $idx => $lbl) {
                        
// Not using selectInput() because it doesn't support multiples
                        
$items[] = '<option value="ms_'.$idx.'" '.((in_array($idx$selectedVals)) ? ' selected="selected"' '') . '>' $lbl '</option>';
                    }
                    
$items[] = '</select>';
                    
$out[] = inputLabel(
                        
$name,
                        
join(n,$items).fInput('hidden',$name,$use_val,'','','','','',$name),
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_select '.$name
                    
);
                    break;
                case 
'radio':
                    
$selv smd_bio_splitval($widget['val']);
                    list(
$selv$dflt) = smd_bio_get_default($selv$val);
                    
$out[] = inputLabel(
                        
$name,
                        
radioSet($selv$name$dflt),
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_radio '.$name
                    
);
                    break;
                case 
'yesnoradio':
                    
$out[] = inputLabel(
                        
$name,
                        
yesnoRadio($name$val),
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_radio '.$name
                    
);
                    break;
                case 
'checkbox':
                    
$val = ($vals && isset($vals[$widget['name']])) ? $vals[$widget['name']] : '';
                    
$selv smd_bio_splitval($widget['val']);
                    list(
$selv$dflt) = smd_bio_get_default($selv$val);
                    
$use_val = (isset($vals[$widget['name']]) && ($vals[$widget['name']] !== '' || $vals[$widget['name']] !== null)) ? $val $dflt// Don't use defaults if this field has been previously saved
                    
$checkedVals do_list($use_val);
                    
$items = array();
                    foreach (
$selv as $idx => $lbl) {
                        
// Not using checkbox() because it doesn't support onclick in 4.5.x
                        
$items[] = '<input type="checkbox" name="cb_'.$name.'" value="'.$idx.'"'.((in_array($idx$checkedVals)) ? ' checked="checked"' '') . ' class="checkbox" onclick="smd_bio_checkbox(\''.$name.'\');" />'.$lbl;
                    }
                    
$out[] = inputLabel(
                        
$name,
                        
join(''$items).fInput('hidden',$name,$use_val,'','','','','',$name),
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_checkbox '.$name
                    
);
                    break;
                case 
'textarea':
                    
// Not using text_area() because it doesn't have class attribute in 4.5.x
                    
$size1 = ($size1 == '' || $size1 == 0) ? 40 $size1;
                    
$size2 = ($size2 == '' || $size2 == 0) ? $size2;
                    
$out[] = inputLabel(
                        
$name,
                        
'<textarea id="'.$name.'" name="'.$name.'" class="smd_bio_textarea" cols="'.$size1.'" rows="'.$size2.'">'.txpspecialchars($val).'</textarea>',
                        
$title,
                        
'',
                        
'txp-form-field txp-form-field-textarea smd_bio_textarea '.$name
                    
);
                    break;
                case 
'image':
                    
$parent $widget['val'];
                    
$val = ($vals && isset($vals[$widget['name']])) ? $vals[$widget['name']] : '';
                    
$where = ($parent) ? "category='".doSlash($parent)."'" '1=1';
                    
$tree safe_rows('*''txp_image'$where' ORDER BY name');
                    
$selv = array();
                    foreach (
$tree as $row) {
                        
$selv[$row['id']] = $row['name'];
                    }
                    
$out[] = inputLabel(
                        
$name,
                        
'<input type="text" value="'.txpspecialchars($val).'" id="'.$name.'" name="'.$name.'" size='.INPUT_XSMALL.' class="smd_bio_image_id input_xsmall" />'.selectInput($name.'_list'$selv$valtrue'').'<span class="smd_bio_image"></span><span class="smd_bio_image_data" title="'.(($size1) ? $size1 '').','.(($size2) ? $size2 $size1).'"></span>',
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_image_cell '.$name
                    
);
                    break;
                case 
'number':
                case 
'range':
                    
$min = ($size1 == '') ? '' " min={$size1}";
                    
$max = ($size2 == '') ? '' " max={$size2}";
                    
$jmp = ($size3 == '') ? '' " step={$size3}";
                    
$out[] = inputLabel(
                        
$name,
                        
'<input type="'.$widget['type'].'" value="'.txpspecialchars($val).'" name="'.$name.'" id="'.$name.'"'.$min.$max.$jmp.'" class="smd_bio_'.$widget['type'].'" />',
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_'.$widget['type'].' '.$name
                    
);
                    break;
                case 
'date':
                case 
'month':
                case 
'week':
                case 
'time':
                case 
'datetime':
                case 
'email':
                case 
'url':
                case 
'text':
                    
// Not using fInput() because it has no MAXLENGTH property and doesn't support HTML 5 elements
                    
$size1 = ($size1 == '' || $size1 == 0) ? 25 $size1;
                    
$size2 = ($size2 == '' || $size2 == 0) ? $size1 $size2;
                    
$out[] = inputLabel(
                        
$name,
                        
'<input type="'.$widget['type'].'" value="'.txpspecialchars($val).'" name="'.$name.'" id="'.$name.'" size="'.$size1.'" maxlength="'.$size2.'" class="smd_bio_'.$widget['type'].'" />',
                        
$title,
                        
'',
                        
'txp-form-field smd_bio_'.$widget['type'].' '.$name
                    
);
                    break;
            }
        }
    }
    return 
join(''$out);
}

// ------------------------
// Read a name/val array and remove any [*] marker which indicates a default item
function smd_bio_get_default($list$curr) {
    
$out = array();
    
$dflt '';
    
$new_dflt = array();
    foreach (
$list as $item => $value) {
        
$value str_replace('&#44;'','$value); // Revert encoded commas to real literals @see smd_bio_meta_make_list
        
if (($pos strpos($value'[*]')) !== false) {
            
$out[$item] = substr($value0$pos);
            
$new_dflt[] = $item;
        } else {
            
$out[$item] = $value;
        }
        if (
$item == $curr) {
            
$dflt $item;
        }
    }
    
$dflt = ($dflt) ? $dflt join(','$new_dflt);

    return array(
$out$dflt);
}

// ------------------------
// AJAX calls
function smd_bio_get_image() {
    global 
$img_dir;

    
$id gps('id');
    if (
$id) {
        
$rs safe_row('*''txp_image'"id = '".doSlash($id)."'");
        
extract($rs);
        
$out = array();
        
$out['thumb'] = ($thumbnail) ? hu.$img_dir.'/'.$id.'t'.$ext '';
        
$out['image'] = hu.$img_dir.'/'.$id.$ext;
        
$out['w'] = $w;
        
$out['h'] = $h;
        
$out['thw'] = ($thumb_w) ? $thumb_w '';
        
$out['thh'] = ($thumb_h) ? $thumb_h '';
        
send_xml_response($out);
    } else {
        
send_xml_response();
    }
    exit;
}

// ------------------------
// IMPORTANT: constants NOT used for table names
function smd_bio_get_ebio() {
    include_once 
txpath.'/publish/taghandlers.php';

    
$id gps('id');
    
$core = array('user_ref');
    
$rs safe_row('*''smd_bio'"user_ref = '".doSlash($id)."'");
    
$meta safe_rows('*''smd_bio_meta'"1=1");
//  $include = array_merge($core, array('mug', 'cell', 'department')); // TODO: get these from prefs/meta table
    
$allowed_types = array('text''textarea''email''url''date''month''week''time''datetime''number''range');
    
$out = array();
    foreach (
$rs as $idx => $val) {
//      if (in_array($idx, $include)) {
            
if (in_array($idx$core)) {
                
$out[$idx] = doSlash($val);
            } else {
                foreach(
$meta as $row) {
                    if (
$row['name'] == $idx) {
                        if (
$row['type'] == "image") {
                            
// Crude str_replace() to remove javascript-breaking single quotes
                            
$out[$idx] = 'smd_image::'.str_replace("'"''thumbnail(array('id'=> $val)));
                        } else if (
in_array($row['type'], $allowed_types)) {
                            
$out[$idx] = $row['title'].'::'.txpspecialchars(strip_tags($val), ENT_QUOTES);
                        }
                        break;
                    }
                }
            }
//      }
    
}
    
send_xml_response($out);
    exit;
}

// Inject admin-side javascript
// ------------------------
function smd_bio_admin_js($evt$stp) {
    global 
$event$step;

    
$smd_bio_styles smd_bio_get_styles();

    
$runon = array(
        
'admin' => array(
            
'evt' => array('admin''smd_um'),
            
'stp' => array('''smd_um''smd_um_edit''smd_um_edit''smd_um_save''smd_um_save_new''smd_um_change_pass''author_edit''author_save''author_save_new'),
        ),
        
'bio' => array(
            
'evt' => array('smd_bio'),
        ),
    );

    
// ********
    // js+css for Admin->Users tab
    // ********
    
if (in_array($event$runon['admin']['evt']) !== false && in_array($step$runon['admin']['stp']) !== false) {
        
$css_custom safe_field('css''txp_css'"name='smd_bio'");
        
$css '<style type="text/css">' $css_custom .n$smd_bio_styles['tooltip'] . '</style>';

    echo <<<EOJS
<script type="text/javascript">
//<![CDATA[

// Concatenate checkbox options for storage
function smd_bio_checkbox(dest) {
    var out = [];
    jQuery("#user_edit :checkbox").each(function() {
        var item = jQuery(this);
        if (item.attr('name').replace('cb_','') == dest) {
            if (item.prop('checked') == true) {
                out.push(item.val());
            }
        }
    });
    jQuery('#'+dest).val(out.join(','));
}
// Concatenate multi select list options for storage
function smd_bio_multisel(dest) {
    var out = [];
    jQuery("#user_edit select.multiple").each(function() {
        var item = jQuery(this);
        if (item.attr('name') == dest) {
            // You're the one that I want, ooh ooh oooohhh
            jQuery(item).children(":selected").each(function() {
                out.push(jQuery(this).val().replace('ms_',''));
            });
        }
    });
    jQuery('#'+dest).val(out.join(','));
}
jQuery(function() {
    // Grab images from the server when the select/textbox change
    jQuery(".smd_bio_image_id").blur(function() {
        id = jQuery(this).attr('id');
        val = jQuery(this).val();
        smd_bio_get_image(id, val);
    }).blur();
    jQuery(".smd_bio_image_cell select").change(function() {
        id = jQuery(this).parent().find('.smd_bio_image_id').attr('id');
        val = jQuery(this).val();
        smd_bio_get_image(id, val);
    });
    function smd_bio_get_image(id, val) {
        jQuery("#" + id + " ~ select option[value='"+val+"']").prop("selected", true);
        jQuery("#" + id).val(val);
        var dims = jQuery("#" + id + " ~ .smd_bio_image_data").attr('title');
        var size = new Array();
        if (dims) {
            size = dims.split(",");
        }
        sendAsyncEvent({
                event: textpattern.event,
                smd_bio_step: 'smd_bio_get_image',
                id: val
            }, function(data) {
                data = jQuery(data);
                if (data) {
                    var full = 0;
                    var imgLink = data.find('thumb').attr('value');
                    if (imgLink == '') {
                        full = 1;
                        var imgLink = data.find('image').attr('value');
                    }
                    if (imgLink) {
                        if (size[0] == '') {
                            if (full == 1) {
                                size[0] = data.find('w').attr('value');
                            } else {
                                size[0] = data.find('thw').attr('value');
                            }
                        }
                        if (size[1] == '') {
                            if (full == 1) {
                                size[1] = data.find('h').attr('value');
                            } else {
                                size[1] = data.find('thh').attr('value');
                            }
                        }
                        jQuery("#" + id + " ~ .smd_bio_image").fadeIn().html('<img src="'+imgLink+'" width="'+size[0]+'" height="'+size[1]+'" />');
                    } else {
                        jQuery("#" + id + " ~ .smd_bio_image").fadeOut().empty();
                    }
                } else {
                    jQuery("#" + id + " ~ .smd_bio_image").fadeOut().empty();
                }
            }
        );
    }

    // Grab the extended info when hovering an author in the list
    jQuery("#users_form tbody tr, #smd_um_form tbody tr").hover(function(e) {
        var tt = '';
        var row = jQuery(this);
        var hovItem = row.find(".login-name");

        if (row.data('tooltip') == undefined) {
            var person = row.find(".login-name").text();

            sendAsyncEvent({
                    event: textpattern.event,
                    smd_bio_step: 'smd_bio_get_ebio',
                    id: person
                }, function(data) {
                    data = jQuery(data);
                        var entry = data.find('user_ref');
                        out = '';
                        entry.nextAll().each(function(item) {
                            node = jQuery(this).context.nodeName;
                            if (node != 'http-status') {
                                vall = jQuery(this).attr('value');
                                if (vall) {
                                    vsplit = vall.split('::');
                                    if (vsplit[0].indexOf('smd_image') < 0) {
                                        out += ((vsplit.length>1) ? vsplit[0] : node) + ': ';
                                    }
                                    vall = ((vsplit.length>1) ? vsplit[1] : vsplit[0]);
                                    out += vall + '<br/>';
                                } else {
                                    out += node+': ';
                                }
                            }
                        });
                        row.data('tooltip', out);
//                      hovItem.trigger('mouseover'); // Trigger the hover state when the data is loaded
                    }
            );
        }

        xOffset = 30;
        yOffset = 25;
        hovItem.hover(function(e) {
            var tt = jQuery(this).parent().data('tooltip');
            if (tt != '') {
                jQuery("body").append("<p id='tooltip'>"+ tt +"</p>");
                jQuery("#tooltip")
                    .css("top",(e.pageY - xOffset) + "px")
                    .css("left",(e.pageX + yOffset) + "px")
                    .fadeIn("fast");
            }
        },
        function() {
            jQuery("#tooltip").remove();
        });
        hovItem.mousemove(function(e) {
            jQuery("#tooltip")
                .css("top",(e.pageY - xOffset) + "px")
                .css("left",(e.pageX + yOffset) + "px");
        });
    });
});
//]]>
</script>
{$css}
EOJS;
    }

    
// ********
    // js for Extensions->Bio config tab
    // ********
    
if (in_array($event$runon['bio']['evt'])) {
        
$smd_bio_types smd_bio_get_types();
        
$smd_bio_unused smd_bio_get_unused();

        
$css '<style type="text/css">' $smd_bio_styles['meta'] . '</style>';
        
$js_unused join(','doArray($smd_bio_unused['size'], 'doQuote'));

        foreach (
$smd_bio_types as $type => $data) {
            
$type_json[] = 'smd_bio_types["'.$type.'"] = { "dflt_type": "'.$data['coltype'].'", "dflt_size": "'.$data['colsize'].'", "fixed": "'.$data['fixed'].'" };';
        }
        
$type_json join(n$type_json);

        echo <<<EOJS
<script type="text/javascript">
//<![CDATA[
var smd_bio_unused = [
{$js_unused}];
var smd_bio_types = [];
var destColtype = "#page-smd_bio select[name='coltype']";
var destColsize = "#page-smd_bio input[name='colsize']";
{$type_json}

jQuery(function() {
    // Perform actions when the Type is changed
    // Action #1: size box. Auto-update this when Edit clicked as well as when select list is altered
    jQuery("#smd_bio_widget_type").change(function() {
        var theType = jQuery("#smd_bio_widget_type option:selected").val();
        var destSize = "#page-smd_bio input[name='size']";

        // Grey out the Size box for those items that don't use it
        if (jQuery.inArray(theType, smd_bio_unused) > -1) {
            jQuery(destSize).prop("disabled", true);
            jQuery(destSize).parent().prev().css("color", '#999');
        } else {
            jQuery(destSize).prop("disabled", false);
            jQuery(destSize).parent().prev().css("color", '');
        }
    }).change();

    // Action #2: coltype/colsize. Can't amalgamate these with the 'size' onchange because
    // this only changes when select list changed (i.e. not when Edit first clicked)
    jQuery("#smd_bio_widget_type").change(function() {
        var theType = jQuery("#smd_bio_widget_type option:selected").val();

        // Preselect the coltype + colsize based on the defaults
        jQuery(destColtype + " option[value='"+smd_bio_types[theType].dflt_type+"']").prop('selected', true);
        jQuery(destColsize).val(smd_bio_types[theType].dflt_size);

        if (smd_bio_types[theType].fixed == '') {
            jQuery(destColtype).prop("disabled", false);
            jQuery(destColsize).prop("disabled", false);
            jQuery(destColtype).parent().prev().css("color", '');
            jQuery(destColsize).parent().prev().css("color", '');
        } else {
            jQuery(destColtype).prop("disabled", true);
            jQuery(destColsize).prop("disabled", true);
            jQuery(destColtype).parent().prev().css("color", '#999');
            jQuery(destColsize).parent().prev().css("color", '#999');
        }
    });

    // Force the auto-change if the type is of a fixed variety
    theType = jQuery("#smd_bio_widget_type option:selected").val();
    if (smd_bio_types[theType].fixed != '') {
        jQuery("#smd_bio_widget_type").change();
    }

    // When clicking Save, enable the coltype/colsize boxes so the values are transmitted in the _POST array
    jQuery("#page-smd_bio input[name='save']").click(function() {
        jQuery(destColtype).prop("disabled", false);
        jQuery(destColsize).prop("disabled", false);
    });

    // Pop up the help tooltips based on the current Type
    jQuery(".edit-smd-bio-size .pophelp").hover(
        function(e) {
            var theType = jQuery("#smd_bio_widget_type option:selected").val();
            spanid = 'span#smd_bio_size_help_'+theType;
            this.title = jQuery(spanid).attr("title");
        }, function(e) {
            this.title = '';
        });
    jQuery(".edit-smd-bio-value .pophelp").hover(
        function(e) {
            var theType = jQuery("#smd_bio_widget_type option:selected").val();
            spanid = 'span#smd_bio_val_help_'+theType;
            this.title = jQuery(spanid).attr("title");
        }, function(e) {
            this.title = '';
        });

    // Handle opening/closing the coltype area
    jQuery('#smd_bio_colgroup').click(function(ev) {
        ev.preventDefault();
        jQuery(this).toggleClass('expanded');
        jQuery('.smd_bio_coltype').toggle();

        sendAsyncEvent(
            {
                event: textpattern.event,
                step: 'smd_bio_save_pane_state',
                pane: 'coltype',
                visible: (jQuery(this).hasClass('expanded'))
            }
        );
    });
});
//]]>
</script>
{$css}
EOJS;
    }
}

// ------------------------
// Make the name/val pairs for selectInput / radio / checkbox sets
function smd_bio_splitval($val) {
    
$selv = array();

    
$wvals preg_split("/[\r\n,]+/"$val, -1PREG_SPLIT_NO_EMPTY);
    foreach (
$wvals as $wval) {
        
$prts explode('=>'$wval);
        if (
count($prts) == 1) {
            
$prts[1] = $prts[0];
            
$prts[0] = smd_bio_sanitize($prts[0]);
        }
        
$selv[trim($prts[0])] = trim($prts[1]);
    }
    return 
$selv;
}

// ------------------------
// Super-sanitize the passed value so we can make variable names from the returned string
function smd_bio_sanitize($val) {
    return 
strtolower(str_replace("-""_"sanitizeForUrl($val)));
}

// ------------------------
function smd_bio_save($evt$stp) {
    global 
$prefs;

    if (
smd_bio_table_exist()) {
        
$targetvars = array();
        
extract(doSlash(psa(array('privs''name''email''RealName''user_id'))));
        if (
get_pref('smd_bio_sanitize_name'0) > 0) {
            
// Sanitize and pass the new name forward to the actual txp_user save routine
            
$name strtolower(sanitizeForUrl($name));
            
$_POST['name'] = $name;
        }
        
$length function_exists('mb_strlen') ? mb_strlen($name'8bit') : strlen($name);

        if ((
$user_id || $name) and $length <= 64 and is_valid_email($email)) {
            foreach (
$_POST as $idx => $item) {
                if (
strpos($idx'smd_bio_') === 0) {
                    
$targetvars[] = $idx;
                }
            }

            
// Double de-clutch again... dammit :-(
            
$user_id gps('user_id');
            if (
$user_id) {
                
$user_ref safe_field('name','txp_users',"user_id = '$user_id'");
            } else {
                
$user_ref $name;
            }
            
extract(gpsa($targetvars));
            
$bcols getThings('describe `'.PFX.SMD_BIO.'`');
            
$sqlSet = array();
            foreach (
$targetvars as $var) {
                
$colname str_replace('smd_bio_'''$var);
                if (
in_array($colname$bcols)) {
                    
$sqlSet[] = "`$colname` = '".doSlash($$var)."'";
                }
            }
            if (
$sqlSet) {
                
$rs safe_upsert(SMD_BIOjoin(','$sqlSet), "`user_ref` = '".doSlash($user_ref)."'");
            }
        }
    }
}

// ------------------------
function smd_bio_delete($evt$stp) {
    global 
$txp_user;

    if (
smd_bio_table_exist()) {
        
// Since we are executing 'pre' delete we need to unfortunately duplicate some of the checks
        // from txp_admin.php so we minimise the opportunity to delete someone by mistake
        
$selected ps('selected');
        
$method ps('edit_method');
        if (!
$selected or !is_array($selected)) {
            return;
        }
        if (
$method != 'delete') {
            return;
        }

        
$names safe_column('name''txp_users'"name IN ('".join("','"doSlash($selected))."') AND name != '".doSlash($txp_user)."'");
        if (!
$names) return;

        
$assign_assets ps('assign_assets');
        if (
$assign_assets === '') {
            return;
        } elseif (
in_array($assign_assets$names)) {
            return;
        } else {
            
// All the checks passed -- do it
            
safe_delete(SMD_BIO"user_ref IN ('".join("','"doSlash($names))."')");
        }
    }
}

// -------------------------------------------------------------
function smd_bio_save_pane_state() {
    global 
$event;
    
$panes = array('coltype');
    
$pane gps('pane');
    if (
in_array($pane$panes)) {
        
set_pref("pane_smd_bio_{$pane}_visible", (gps('visible') == 'true' '1' '0'), $eventPREF_HIDDEN'yesnoradio'0PREF_PRIVATE);
        
send_xml_response();
    } else {
        
send_xml_response(array('http-status' => '400 Bad Request'));
    }
}

// -------------------------------------------------------------
function smd_bio_form_submit() {
    global 
$mem_form_type;

    if (!
in_array($mem_form_type, array('smd_bio''mem_self_register''mem_self_user_edit'))) return;

    
$author smd_bio_find_author();

    if (
$author) {
        
$core_cols smd_bio_core_cols();
        
$meta_cols safe_column('name'SMD_BIO_META'1=1');
        
$query_params = array(
            
'core' => array(
                
'tbl' => 'txp_users',
                
'ref' => 'name',
            ),
            
'bio' => array(
                
'tbl' => SMD_BIO,
                
'ref' => 'user_ref',
            )
        );

        foreach (
stripPost() as $key => $val) {
            
// Only care about smd_bio_ prefixed entries in $_POST
            
if (strpos($key'smd_bio_') !== false) {
                
// Strip off the known prefix
                
$raw_key str_replace('smd_bio_'''$key);
                if (
in_array($raw_key$core_cols)) {
                    
$query_params['core']['cols'][] = doSlash($raw_key) . '='doQuote(doSlash($val));
                } else if (
in_array($raw_key$meta_cols)) {
                    
$query_params['bio']['cols'][] = doSlash($raw_key) . '='doQuote(doSlash($val));
                }
            }
        }

        
// If there are some cols set we're good to update/insert depending if $author exists or not
        
foreach ($query_params as $type => $data) {
            if (isset(
$data['cols'])) {
                
$params join(', '$data['cols']);
                
safe_upsert($data['tbl'], $params$data['ref'] .'='doQuote(doSlash($author)));
            }
        }
    }
}

// Try some of the usual suspects for locating authorship
function smd_bio_find_author($author_in=''$places=array('biotag''txpuser''ili''profile''selfreg''list''article''image''file''link') ) {
    global 
$smd_bio_author$txp_user$mem_form_type$mem_profile$pretext$thisarticle$thisimage$thisfile$thislink;
    static 
$smd_bio_ili 0;

    
$places is_array($places) ? $places do_list($places);

    
// Check for any passed-in author first
    
$author $author_in;

    foreach (
$places as $place) {
        if (
$author != '') break;
        switch (
$place) {
            case 
'biotag':
                
// From smd_bio_author tag
                
$author = ($smd_bio_author != '') ? $smd_bio_author $author;
            break;
            case 
'txpuser':
                
// From global user variable
                
$author = ($txp_user != '') ? $txp_user $author;
            break;
            case 
'ili':
                
// From currently logged-in user
                
$smd_bio_ili = ($smd_bio_ili === 0) ? is_logged_in() : $smd_bio_ili;
                if (
$smd_bio_ili) {
                    
$author $smd_bio_ili['name'];
                }
            break;
            case 
'profile':
                
// From current self-edit user profile
                
$author = (isset($mem_profile['name'])) ? $mem_profile['name'] : $author;
            break;
            case 
'selfreg':
                
// New author from mem_self_reg?
                
$aname ps('name');
                if (
$aname == '') {
                    
// New author from custom mem_form?
                    
$aname ps('smd_bio_name');
                }
                if ( (
$mem_form_type == 'mem_self_register' || $mem_form_type == 'smd_bio') && ($aname != '') ) {
                    
// As long as $aname doesn't exist, let this new author in
                    //TODO: Cache this?
                    
$exists safe_row('*''txp_user'"name='".doSlash($aname)."'");
                    if (!
$exists) {
                        
$author $aname;
                    }
                }
            break;
            case 
'list':
                
$author = (isset($pretext['author'])) ? $pretext['author'] : $author;
            break;
            case 
'article':
                
$author = (isset($thisarticle['authorid'])) ? $thisarticle['authorid'] : $author;
            break;
            case 
'image':
                
$author = (isset($thisimage['author'])) ? $thisimage['author'] : $author;
            break;
            case 
'file':
                
$author = (isset($thisfile['author'])) ? $thisfile['author'] : $author;
            break;
            case 
'link':
                
$author = (isset($thislink['author'])) ? $thislink['author'] : $author;
            break;
        }
    }

    return 
$author;
}

// -------------------------------------------------------------
// Return a list of core columns in the txp_users table.
// This could be done programmatically but isn't: save a
// query, save the world
function smd_bio_core_cols() {
    
// The indices are the gTxt() names for the associated field
    
return array(
        
'id'         => 'user_id',
        
'name'       => 'name',
        
'real_name'  => 'RealName',
        
'email'      => 'email',
        
'privileges' => 'privs',
        
'date'       => 'last_access'
    
);
}

// -------------------------------------------------------------
// In alphabetical order or sorting on the admin panel gets screwy
function smd_bio_get_types() {
    
$smd_bio_types = array(
        
'checkbox' => array(
                
'name'    => 'Checkbox(es)',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => true,
                ),
        
'date' => array(
                
'name'    => 'Date',
                
'coltype' => 'date',
                
'colsize' => null,
                
'fixed'   => false,
                ),
        
'datetime' => array(
                
'name'    => 'Date/Time',
                
'coltype' => 'datetime',
                
'colsize' => null,
                
'fixed'   => false,
                ),
        
'email' => array(
                
'name'    => 'E-mail',
                
'coltype' => 'varchar',
                
'colsize' => 254,
                
'fixed'   => false,
                ),
        
'image' => array(
                
'name'    => 'Image',
                
'coltype' => 'int',
                
'colsize' => 11,
                
'fixed'   => true,
                ),
        
'list' => array(
                
'name'    => 'Select list',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => true,
                ),
        
'month' => array(
                
'name'    => 'Month',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => false,
                ),
        
'multilist' => array(
                
'name'    => 'Multi select list',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => true,
                ),
        
'number' => array(
                
'name'    => 'Number',
                
'coltype' => 'int',
                
'colsize' => 11,
                
'fixed'   => false,
                ),
        
'radio' => array(
                
'name'    => 'Radio set',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => true,
                ),
        
'range' => array(
                
'name'    => 'Range',
                
'coltype' => 'int',
                
'colsize' => 11,
                
'fixed'   => false,
                ),
        
'text' => array(
                
'name'    => 'Text box',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => false,
                ),
        
'textarea' => array(
                
'name'    => 'Text area',
                
'coltype' => 'mediumtext',
                
'colsize' => 4096,
                
'fixed'   => false,
                ),
        
'time' => array(
                
'name'    => 'Time',
                
'coltype' => 'time',
                
'colsize' => null,
                
'fixed'   => false,
                ),
        
'url' => array(
                
'name'    => 'URL',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => false,
                ),
        
'week' => array(
                
'name'    => 'Week',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => false,
                ),
        
'yesnoradio' => array(
                
'name'    => 'Yes/no radio',
                
'coltype' => 'varchar',
                
'colsize' => 255,
                
'fixed'   => true,
                ),
    );

    return 
$smd_bio_types;
}

// -------------------------------------------------------------
// In alphabetical order or sorting on the admin panel gets screwy
function smd_bio_get_coltypes() {
    
$smd_bio_coltypes = array(
        
'date' => array(
            
'title'       => 'Date [YYYY-MM-DD]',
            
'size_req'    => false,
            
'has_default' => true,
        ),
        
'datetime' => array(
            
'title'       => 'Date + time [YYYY-MM-DD HH:MN:SS]',
            
'size_req'    => false,
            
'has_default' => true,
        ),
        
'double' => array(
            
'title'       => 'Double precision float',
            
'size_req'    => false,
            
'has_default' => true,
        ),
        
'float' => array(
            
'title'       => 'Floating point number',
            
'size_req'    => false,
            
'has_default' => true,
        ),
        
'int' => array(
            
'title'       => 'Integer [up to 4294967295]',
            
'size_req'    => '11',
            
'has_default' => true,
        ),
        
'longtext' => array(
            
'title'       => 'Long text [up to 4GB]',
            
'size_req'    => false,
            
'has_default' => false,
        ),
        
'mediumint' => array(
            
'title'       => 'Medium integer [up to 16777215]',
            
'size_req'    => '8',
            
'has_default' => true,
        ),
        
'mediumtext' => array(
            
'title'       => 'Medium text [up to 16MB]',
            
'size_req'    => false,
            
'has_default' => false,
        ),
        
'smallint' => array(
            
'title'       => 'Small integer [up to 65535]',
            
'size_req'    => '4',
            
'has_default' => true,
        ),
        
'text' => array(
            
'title'       => 'Text [up to 64KB chars]',
            
'size_req'    => false,
            
'has_default' => false,
        ),
        
'time' => array(
            
'title'       => 'Time [HH:MN:SS]',
            
'size_req'    => false,
            
'has_default' => true,
        ),
        
'timestamp' => array(
            
'title'       => 'Timestamp [seconds since UNIX epoch]',
            
'size_req'    => false,
            
'has_default' => true,
        ),
        
'tinyint' => array(
            
'title'       => 'Tiny integer [up to 255]',
            
'size_req'    => '3',
            
'has_default' => true,
        ),
        
'tinytext' => array(
            
'title'       => 'Tiny text [up to 255 chars]',
            
'size_req'    => false,
            
'has_default' => false,
        ),
        
'varbinary' => array(
            
'title'       => 'Binary varchar [size subject to max row size]',
            
'size_req'    => '255',
            
'has_default' => true,
        ),
        
'varchar' => array(
            
'title'       => 'Varchar [size subject to max row size]',
            
'size_req'    => '255',
            
'has_default' => true,
        ),
    );

    return 
$smd_bio_coltypes;
}

// -------------------------------------------------------------
function smd_bio_get_unused() {
    
$smd_bio_unused = array(
        
'size' => array('list''multilist''radio''yesnoradio''checkbox'),
    );

    return 
$smd_bio_unused;
}

// ************************
// TABLE MANAGEMENT
// ------------------------
function smd_bio_table_exist() {
    static 
$smd_bio_table_ok = array();

    if (isset(
$smd_bio_table_ok['meta'])) {
        return (
$smd_bio_table_ok['meta'] === $smd_bio_table_ok['field']);
    }

    
$meta safe_count(SMD_BIO_META'1=1');
    
$flds = @safe_show('columns'SMD_BIO);

    
$smd_bio_table_ok['meta'] = (int)$meta;
    
$smd_bio_table_ok['field'] = (int)count($flds) - 1// Subtract the user_ref column as it's always present

    
return($smd_bio_table_ok['meta'] === $smd_bio_table_ok['field']);
}

// ------------------------
function smd_bio_table_install($showpane=1) {
    global 
$DB;

    
$smd_bio_types smd_bio_get_types();
    
$smd_bio_coltypes smd_bio_get_coltypes();

    
$GLOBALS['txp_err_count'] = 0;
    
$msg '';
    
$debug gps('debug');

    
$ret '';
    
$sql = array();
    
$sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_BIO."` (
        `user_ref` varchar(64) NOT NULL default '',
        UNIQUE KEY `user_ref` (`user_ref`)
    ) ENGINE=MyISAM, CHARACTER SET=utf8, PACK_KEYS=1"
;

    
$sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_BIO_META."` (
        `id` int(4) NOT NULL auto_increment,
        `title` varchar(64) NULL default '' COLLATE utf8_general_ci,
        `name` varchar(64) NOT NULL default '' COLLATE utf8_general_ci,
        `type` set("
.doQuote(join("','"array_keys($smd_bio_types))).") NOT NULL default 'text',
        `size` varchar(10) NULL default 0,
        `coltype` set("
.doQuote(join("','"array_keys($smd_bio_coltypes))).") NOT NULL default 'varchar',
        `colsize` smallint(4) NULL default 0,
        `val` text NULL COLLATE utf8_general_ci,
        `position` varchar(16) NULL default '',
        PRIMARY KEY (`id`),
        UNIQUE KEY (`name`)
    ) ENGINE=MyISAM, CHARACTER SET=utf8, AUTO_INCREMENT=1"
;

    if (
$debug) {
        
dmp($sql);
    }
    foreach (
$sql as $qry) {
        
$ret safe_query($qry);
        if (
$ret===false) {
            
$GLOBALS['txp_err_count']++;
            echo 
"<b>".$GLOBALS['txp_err_count'].".</b> ".mysql_error()."<br />\n";
            echo 
"<!--\n $qry \n-->\n";
        }
    }

    
// Handle upgrades from v0.3x to v0.40.
    // Upgrade table collation if necessary
    
$ret getRows("SHOW TABLE STATUS WHERE name IN ('".PFX.SMD_BIO."', '".PFX.SMD_BIO_META."')");
    if (
$ret[0]['Collation'] != 'utf8_general_ci') {
        
$ret safe_alter(SMD_BIO_META'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci');
    }
    if (
$ret[1]['Collation'] != 'utf8_general_ci') {
        
$ret safe_alter(SMD_BIO'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci');
    }

    
// Alter the position field from int to varchar so positioning can be non-numeric
    
$ret = @safe_field("DATA_TYPE""INFORMATION_SCHEMA.COLUMNS""table_name = '".PFX.SMD_BIO_META."' AND table_schema = '" $DB->db "' AND column_name = 'position'");
    if (
$ret != 'varchar') {
        
safe_alter(SMD_BIO_META"CHANGE `position` `position` VARCHAR( 16 ) NULL DEFAULT ''"$debug);
    }
    
// Add the coltype and colsize columns
    
$flds getThings('SHOW COLUMNS FROM `'.PFX.SMD_BIO_META.'`');
    if (!
in_array('coltype'$flds)) {
        
safe_alter(SMD_BIO_META"ADD `coltype` SET(".doQuote(join("','"array_keys($smd_bio_coltypes))).") NOT NULL default '' after `size`"$debug);
        
safe_alter(SMD_BIO_META"ADD `colsize` SMALLINT(4) NULL default 0 after `coltype`"$debug);
    }
    
// Add multiple select list & date flavour support to the 'type' set, and rename 'text_input' to just 'text'
    
$toChange safe_column('id'SMD_BIO_META'type="text_input"');
    
$fld getRows("SHOW FULL COLUMNS FROM `".PFX.SMD_BIO_META."` LIKE 'type'");
    
$ft $fld[0]['Type'];
    if ( (
strpos($ft'multilist') === false) || (strpos($ft'text_input') !== false) || (strpos($ft'month') === false) ) {
        
safe_alter(SMD_BIO_META"CHANGE `type` `type` SET(".doQuote(join("','"array_keys($smd_bio_types))).")"$debug);
    }
    if (
$toChange) {
        
safe_update(SMD_BIO_META'type="text"'"id in ('"join("','"$toChange) ."')"$debug);
    }

    if (
$GLOBALS['txp_err_count'] == 0) {
        
$msg gTxt('smd_bio_tbl_installed');
    } else {
        
$msg gTxt('smd_bio_tbl_not_installed');
    }

    if (
$showpane) {
        
smd_bio_config($msg);
    }
}

// ------------------------
// Drop table if in database
function smd_bio_table_remove($showpane=1) {
    
$ret $msg '';
    
$sql = array();
    
$GLOBALS['txp_err_count'] = 0;
    
$sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_BIO"; ";
    
$sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_BIO_META"; ";
    if(
gps('debug')) {
        
dmp($sql);
    }
    foreach (
$sql as $qry) {
        
$ret safe_query($qry);
        if (
$ret===false) {
            
$GLOBALS['txp_err_count']++;
            echo 
"<b>".$GLOBALS['txp_err_count'].".</b> ".mysql_error()."<br />\n";
            echo 
"<!--\n $qry \n-->\n";
        }
    }
    if (
$GLOBALS['txp_err_count'] == 0) {
        
$msg gTxt('smd_bio_tbl_removed');
    } else {
        
$msg gTxt('smd_bio_tbl_not_removed');
    }

    if (
$showpane) {
        
smd_bio_config($msg);
    }
}

// ************************
// PUBLIC-SIDE TAGS
// ------------------------
// Wrapper to permit bio info to be displayed for multiple authors
function smd_bio_author($atts$thing null) {
    global 
$smd_bio_author$smd_bio_meta_data;

    
extract(lAtts(array(
        
'author'      => '',
        
'form'        => '',
        
'sort'        => 'RealName asc',
        
'wraptag'     => '',
        
'break'       => '',
        
'breakclass'  => '',
        
'class'       => '',
        
'label'       => '',
        
'labeltag'    => '',
        
'auto_detect' => 'profile, list, article, image, file, link',
    ), 
$atts));

    
$author smd_bio_find_author($author$auto_detect);
    
$author do_list($author);
    
$authors = array();
    
$thing = (empty($form)) ? $thing fetch_form($form);

    
// Set up any sorting clause
    
if ($sort != '') {
        
$sort do_list($sort);
        
$sortout = array();
        foreach (
$sort as $sitems) {
            
$sortbits do_list($sitems' ');
            
$sort_col $sortbits[0];
            
$sort_ord = (isset($sortbits[1]) && in_array($sortbits[1], array('asc''desc'))) ? $sortbits[1] : 'asc';
            if (
$sort_col) {
                
$sortout[] = '`' $sort_col '` ' $sort_ord;
            }
        }
        if (
$sortout) {
            
$sort ' ORDER BY '.join(','$sortout);
        }
    }

    
// Expand any priv levels
    
foreach ($author as $user) {
        if (
strpos(strtolower($user), "smd_privs") === 0) {
            
$aprivs explode(':'$user);
            
array_shift($aprivs); // Remove smd_privs token from the array
            
$rows safe_query('SELECT txpu.name FROM ' PFX 'txp_users AS txpu LEFT JOIN ' PFX.SMD_BIO ' AS smdb ON txpu.name = smdb.user_ref WHERE privs in (' doQuote(join("','"$aprivs)) . ')'.$sort);
            if (
$rows) {
                while (
$a nextRow($rows)) {
                    
$authors[] = $a['name'];
                }
            }
        } else if (
strpos(strtolower($user), "smd_all") === 0) {
            
$rows safe_query('SELECT txpu.name FROM ' PFX 'txp_users AS txpu LEFT JOIN ' PFX.SMD_BIO ' AS smdb ON txpu.name = smdb.user_ref WHERE 1=1'.$sort);
            if (
$rows) {
                while (
$a nextRow($rows)) {
                    
$authors[] = $a['name'];
                }
            }
        } else {
            
$authors[] = $user;
        }
    }

    
// Parse content for each author: inject current author into global
    
$out = array();
    
$author_count count($authors) - 1;
    foreach (
$authors as $idx => $smd_bio_author) {
        
$smd_bio_meta_data['author']['first'] = ($idx === 0);
        
$smd_bio_meta_data['author']['last'] = ($idx === $author_count);
        
$toParse = (empty($thing)) ? $smd_bio_author $thing;
        
$out[] = parse($toParse);
    }

    return 
doLabel($label$labeltag).doWrap($out$wraptag$break$class$breakclass);
}

// Display biographical field data from a given user's profile
function smd_bio_info($atts$thing null) {
    global 
$smd_bio_data$smd_bio_option_data;

    
// Data cache
    
static $bio_info = array();
    static 
$meta = array();
    static 
$cmeta = array();
    static 
$metacols = array();

    
extract(lAtts(array(
        
'author'      => ''// Deprecated: use smd_bio_author tag as a wrapper instead
        
'fields'      => 'SMD_ALL',
        
'items'       => 'SMD_ALL'// Deprecated: use fields instead
        
'exclude'     => '',
        
'form'        => '',
        
'wraptag'     => '',
        
'break'       => '',
        
'class'       => '',
        
'label'       => '1',
        
'labeltag'    => '',
        
'labelclass'  => 'SMD_DFLT'// Deprecated
        
'itemwraptag' => ''// Deprecated: use break
        
'itemclass'   => 'SMD_DFLT'// Deprecated: use breakclass
        
'breakclass'  => 'SMD_DFLT',
        
'show_empty'  => 0,
        
'prefix'      => 'smd_bio_'// Only for replacement variables
        
'auto_detect' => 'biotag, list, article, image, file, link',
        
'debug'       => 0,
    ), 
$atts));

    if (isset(
$atts['author'])) {
        
trigger_error(gTxt('deprecated_attribute', array('{name}' => 'author')) . '. Use the smd_bio_author tag as a wrapper instead.'E_USER_NOTICE);
        unset(
$author);
    }
    if (isset(
$atts['items'])) {
        
trigger_error(gTxt('deprecated_attribute', array('{name}' => 'items')) . '. Use fields instead.'E_USER_NOTICE);
        
$fields $items;
        unset(
$items);
    }
    if (isset(
$atts['itemwraptag'])) {
        
trigger_error(gTxt('deprecated_attribute', array('{name}' => 'itemwraptag')) . '. Use break instead.'E_USER_NOTICE);
        
$break $itemwraptag;
        unset(
$itemwraptag);
    }
    if (isset(
$atts['itemclass'])) {
        
trigger_error(gTxt('deprecated_attribute', array('{name}' => 'itemclass')) . '. Use breakclass instead.'E_USER_NOTICE);
        
$breakclass $itemclass;
        unset(
$itemclass);
    }
    if (isset(
$atts['labelclass'])) {
        
trigger_error(gTxt('deprecated_attribute', array('{name}' => 'labelclass')), E_USER_NOTICE);
        unset(
$labelclass);
    }

    
$author smd_bio_find_author(''$auto_detect);

    
$thing = (empty($form)) ? $thing fetch_form($form);
    
$smd_bio_data $smd_bio_option_data = array();
    
$bio_types array_keys(smd_bio_get_types());

    
$fields do_list($fields);
    
$exclude do_list($exclude);
    
$final '';

    
$coreCols smd_bio_core_cols();
    
$list_types = array('checkbox''radio''yesnoradio''list''multilist');
    
$mm_types = array('number''range');

    if (
$author) {
        
$meta = (empty($meta)) ? safe_rows('*'SMD_BIO_META'1=1') : $meta;
        
$cmeta = (empty($cmeta)) ? safe_show('columns''txp_users') : $cmeta;
        
$metacols = (empty($metacols)) ? safe_column('name'SMD_BIO_META'1=1') : $metacols;
        
$num count($meta);

        foreach(
$cmeta as $info) {
            if (!
in_array($info['Field'], $coreCols)) continue;
            
$meta[$num]['name'] = $info['Field'];
            
$tField join(''array_keys($coreCols$info['Field']));
            
$meta[$num]['title'] = ((gTxt($tField) == $tField) ? gTxt('smd_bio_'.$tField) : gTxt($tField));
            
$length = (($off strpos($info['Type'], '(')) !== false) ? $off strlen($info['Type']); // Find the first open bracket or end of string
            
$cmtype substr($info['Type'], 0$length);
            
$cmtype in_array($cmtype$bio_types)? $cmtype 'text';
            
$meta[$num]['type'] = $cmtype;
            
$num++;
        }
        if (
$debug 1) {
            echo 
'++ META DATA ++';
            
dmp($meta);
        }

        
// Exclusions override given fields
        
if ($exclude[0] != '' || in_array('SMD_ALL'$fields)) {
            
$fields $metacols;
            
$fields array_merge($fields$coreCols);
        }

        if (isset(
$bio_info[$author])) {
            
$cbio $bio_info[$author]['cbio'];
            
$ebio $bio_info[$author]['ebio'];
        } else {
            
$cbio $bio_info[$author]['cbio'] = safe_row('*''txp_users'"name='".doSlash($author)."'");
            
$ebio $bio_info[$author]['ebio'] = safe_row('*'SMD_BIO"user_ref='".doSlash($author)."'");
        }

        
$replacements $out $toParse = array();
        
$isSingle = ($thing === null) ? true false;
        
$numFields count($fields);

        foreach (
$fields as $iref => $whatnot) {
            
$idx = -1;
            if (
in_array($whatnot$exclude)) continue;

            
// Find the meta row
            
foreach ($meta as $num => $data) {
                if (
$data['name'] == $whatnot) {
                    
$idx $num;
                    break;
                }
            }

            if (
$idx > -1) {
                if (
in_array($whatnot$coreCols)) {
                    
$field = isset($cbio[$whatnot]) ? $cbio[$whatnot] : '';
                } else {
                    
$field = isset($ebio[$whatnot]) ? $ebio[$whatnot] : '';
                }

                
$theName $meta[$idx]['name'];
                
$theTitle $meta[$idx]['title'];
                
$prefixedName $prefix.$theName;
                
$fixedName 'smd_bio_'.$theName// Used for widget names so we know the indices in the $_POST array
                
$theClass = ($breakclass=='SMD_DFLT') ? $prefixedName : (($breakclass) ? $breakclass '');
                
$replacements['{'.$prefixedName.'_name}'] = $theName;
                
$replacements['{'.$prefixedName.'_title}'] = $theTitle;
                
$replacements['{'.$prefixedName.'_class}'] = $theClass;
                
$replacements['{'.$prefixedName.'_type}'] = $meta[$idx]['type'];
                
$smd_bio_data[$theName]['name'] = $theName;
                
$smd_bio_data[$theName]['title'] = $theTitle;
                
$smd_bio_data[$theName]['class'] = $theClass;
                
$smd_bio_data[$theName]['type'] = $meta[$idx]['type'];
                if (!
in_array($meta[$idx]['type'], $list_types) && isset($meta[$idx]['val'])) {
                    
$smd_bio_data[$theName]['default'] = $meta[$idx]['val'];
                    
$replacements['{'.$prefixedName.'_default}'] = $meta[$idx]['val'];
                }
                
$widget '';

                if (
$field || $show_empty) {
                    if (
in_array($meta[$idx]['type'], $list_types)) {
                        
$field do_list($field);
                        
$field join(', ',$field);
                    }

                    
$fieldContent = ($meta[$idx]['type'] == 'textarea') ? nl2br($field) : $field;
                    
$replacements['{'.$prefixedName.'}'] = $fieldContent;
                    
$smd_bio_data[$theName]['value'] = $fieldContent;

                    
// For backwards compatibility(ish) with v0.3x
                    
if ($numFields == 1) {
                        
$replacements['{'.$prefix.'info_item}'] = $fieldContent// Deprecated, use info_value instead
                        
$replacements['{'.$prefix.'info_value}'] = $fieldContent;
                        
$replacements['{'.$prefix.'info_name}'] = $theName;
                        
$replacements['{'.$prefix.'info_title}'] = $theTitle;
                        
$replacements['{'.$prefix.'info_itemclass}'] = $theClass// Deprecated: use info_class instead
                        
$replacements['{'.$prefix.'info_class}'] = $theClass;
                    }

                    if (
in_array($meta[$idx]['type'], $list_types)) {
                        
$chosens do_list($field);
                        
$nv smd_bio_splitval($meta[$idx]['val']);
                        list(
$nv$dflt) = smd_bio_get_default($nv$field);
                        
$dflts do_list($dflt);
                        
$listctr=1;
                        
$chosenctr=0;
                        foreach(
$nv as $listitem => $listlabel) {
                            
$replacements['{'.$prefixedName.'_option_'.$listctr.'}'] = $listitem;
                            
$replacements['{'.$prefixedName.'_title_'.$listctr.'}'] = $listlabel;
                            
$smd_bio_data[$theName]['option_'.$listctr] = $listitem;
                            
$smd_bio_data[$theName]['title_'.$listctr] = $listlabel;
                            if (
in_array($listitem$chosens)) {
                                
$chosenctr++;
                                
$replacements['{'.$prefixedName.'_chosen_option_'.$chosenctr.'}'] = $listitem;
                                
$replacements['{'.$prefixedName.'_chosen_title_'.$chosenctr.'}'] = $listlabel;
                                
$smd_bio_data[$theName]['chosen_option_'.$chosenctr] = $listitem;
                                
$smd_bio_data[$theName]['chosen_title_'.$chosenctr] = $listlabel;
                            }
                            
$listctr++;
                        }
                        
$dfltctr 1;
                        foreach(
$dflts as $dfltitem) {
                            
$replacements['{'.$prefixedName.'_default_option_'.$dfltctr.'}'] = $dfltitem;
                            
$smd_bio_data[$theName]['default_option_'.$dfltctr] = $dfltitem;
                            
$dfltctr++;
                        }

                        
// TODO: maybe hand-code all input types so they can have classes added
                        
switch ($meta[$idx]['type']) {
                            case 
'checkbox':
                                
$citems = array();
                                foreach (
$nv as $idx => $lbl) {
                                    
$citems[] = checkbox($fixedName'cb_'.$idx, (in_array($idx$dflts) ? '1' '0')) . $lbl;
                                }
                                
$widget join(n$citems);
                            break;
                            case 
'yesnoradio':
                                
$widget yesnoRadio($fixedName$dflt);
                            break;
                            case 
'radio':
                                
$widget radioSet($nv$fixedName$dflt);
                            break;
                            case 
'list':
                                
$widget selectInput($fixedName$nv$dfltfalse''$prefixedName);
                            break;
                            case 
'multilist':
                                
$mitems = array();
                                
$mitems[] = '<select name="'.$fixedName.'" class="list multiple '.$theClass.'" multiple="multiple">';
                                foreach (
$nv as $idx => $lbl) {
                                    
// Not using selectInput() because it doesn't support multiples
                                    
$mitems[] = '<option value="ms_'.$idx.'" '.((in_array($idx$dflts)) ? ' selected="selected"' '') . '>' $lbl '</option>';
                                }
                                
$mitems[] = '</select>';
                                
$widget join(n$mitems);
                            break;
                        }

                        
$replacements['{'.$prefixedName.'_option_count}'] = $listctr-1;
                        
$replacements['{'.$prefixedName.'_chosen_count}'] = $chosenctr;
                        
$replacements['{'.$prefixedName.'_default_count}'] = $dfltctr-1;
                        
$smd_bio_data[$theName]['option_count'] = $listctr-1;
                        
$smd_bio_data[$theName]['chosen_count'] = $chosenctr;
                        
$smd_bio_data[$theName]['default_count'] = $dfltctr-1;
                    } else if (
in_array($meta[$idx]['type'], $mm_types)) {
                        
$sizes do_list($meta[$idx]['size']);
                        
$min = ($sizes[0] === '') ? '' ' min="' $sizes[0] . '"';
                        
$max = (isset($sizes[1]) && $sizes[1] !== '') ? ' max="' $sizes[1] . '"' '';
                        
$jmp = (isset($sizes[2]) && $sizes[2] !== '') ? ' step="' $sizes[2] . '"''';
                        
$cls = ($theClass) ? ' class="'.$theClass.'"' '';
                        
$widget '<input type="'.$meta[$idx]['type'].'" value="'.$field.'" name="'.$fixedName.'"'.$cls.$min.$max.$jmp.' />';
                    } else if (
$meta[$idx]['type'] == 'textarea') {
                        
$sizes do_list($meta[$idx]['size']);
                        
$w = ($sizes[0] === '') ? '' ' cols="' $sizes[0] . '"';
                        
$h = (isset($sizes[1]) && $sizes[1] !== '') ? ' rows="' $sizes[1] . '"''';
                        
$cls = ($theClass) ? ' class="'.$theClass.'"' '';
                        
// TODO: maybe use text_area() when 4.6.0 released as it may support classes
                        
$widget '<textarea name="' $fixedName '" id="' $prefixedName '"'$cls.$w.$h.'>'.$field.'</textarea>';
                    } else if (
$meta[$idx]['type'] == 'image') {
                        
$widget fInput('text'$fixedName$field$theClass''''''''$prefixedName);
                    } else {
                        
$widget fInput($meta[$idx]['type'], $fixedName$field$theClass''''''''$prefixedName);
                    }

                    
$smd_bio_data[$theName]['widget'] = $widget;
                    
$replacements['{'.$prefixedName.'_widget}'] = $widget;

                    
// Without container/form, build up generic output
                    
if ($isSingle) {
                        
$taglab = (($label==1) ? $theTitle : (($label=='') ? '' : (($label) ? $label $theName)));
                        
$toParse[] = doLabel($taglab$labeltag) . (($break) ? doTag($field$break$theClass) : $field);
                    }
                }
            }
        }

        if (
$debug) {
            echo 
'++ BIO REPLACEMENTS FOR ' .$author' ++';
            
dmp($replacements);
        }

        if (!
$isSingle) {
            
$toParse[] = $thing;
        }

        
$out[] = parse(strtr(join(n$toParse), $replacements));
        
$final doWrap($out$wraptag''$class);
    }
    return 
$final;
}

// Output data
function smd_bio_data($atts$thing null) {
    global 
$smd_bio_data$smd_bio_option_data;

    
extract(lAtts(array(
        
'field'   => '',
        
'item'    => 'value',
        
'wraptag' => '',
        
'break'   => '',
        
'class'   => '',
        
'debug'   => 0,
    ), 
$atts));

    
$bdata is_array($smd_bio_data) ? $smd_bio_data : array();
    
$idata is_array($smd_bio_option_data) ? $smd_bio_option_data : array();
    
$datapool array_merge($bdata$idata);

    if (
$debug) {
        echo 
'++ AVAILABLE BIO DATA ++';
        
dmp($datapool);
    }

    
$items do_list($item);
    
$out = array();
    foreach (
$items as $it) {
        if (isset(
$datapool[$field][$it])) {
            
$out[] = $datapool[$field][$it];
        }
    }

    return 
doWrap($out$wraptag$break$class);
}

// Iterate over N multi-items
function smd_bio_iterate($atts$thing null) {
    global 
$smd_bio_data$smd_bio_option_data;

    
extract(lAtts(array(
        
'field'      => '',
        
'using'      => 'chosen'// chosen, default, all
        
'display'    => 'title'// option, title
        
'prefix'     => 'smd_bio_'// Only for replacement variables
        
'wraptag'    => '',
        
'class'      => '',
        
'break'      => '',
        
'form'       => '',
        
'limit'      => 0,
        
'offset'     => 0,
        
'debug'      => 0,
    ), 
$atts));

    if (
$debug 1) {
        echo 
'++ AVAILABLE BIO DATA ++';
        
dmp($smd_bio_data);
    }

    
// Validation 1
    
if (!isset($smd_bio_data[$field])) {
        return;
    }

    
// Validation 2
    
$usingMap = array('all' => '''chosen' => 'chosen''default' => 'default');
    
$using = isset($usingMap[$using]) ? $using 'chosen';

    
// Validation 3
    
$displayMap = array('option' => 'option''title' => 'title');
    
$display = isset($displayMap[$display]) ? $display 'title';

    switch (
$using) {
        case 
'chosen':
        case 
'default':
            
$countType $using.'_count';
            break;
        case 
'all':
        default:
            
$countType 'option_count';
        break;
    }

    
$out $reps $stash = array();

    
// Whip through the lists to gather the info for later.
    
if (isset($smd_bio_data[$field]['option_count'])) {
        for (
$idx 1$idx <= $smd_bio_data[$field]['option_count']; $idx++) {
            
$opt $smd_bio_data[$field]['option_'.$idx];
            
$ttl $smd_bio_data[$field]['title_'.$idx];
            
$stash['option'][$opt] = $ttl;
        }
    }
    if (isset(
$smd_bio_data[$field]['chosen_count'])) {
        for (
$idx 1$idx <= $smd_bio_data[$field]['chosen_count']; $idx++) {
            
$opt $smd_bio_data[$field]['chosen_option_'.$idx];
            
$ttl $smd_bio_data[$field]['chosen_title_'.$idx];
            
$stash['chosen'][$opt] = $ttl;
        }
    }
    if (isset(
$smd_bio_data[$field]['default_count'])) {
        for (
$idx 1$idx <= $smd_bio_data[$field]['default_count']; $idx++) {
            
$opt $smd_bio_data[$field]['default_option_'.$idx];
            
$stash['default'][] = $opt;
        }
    }

    
$checked_types = array('checkbox''radio''yesnoradio');
    
$selected_types = array('list''multilist');

    
// Do the iteration for real now, taking limit and offset into account.
    // Can only iterate over items that have a count.
    
if (isset($smd_bio_data[$field][$countType])) {
        for (
$idx 1$idx <= $smd_bio_data[$field][$countType]; $idx++) {
            if (( (
$idx-1) >= $offset ) && ( $limit == || (($idx-1) < $limit+$offset) )) {
                
$key = (($usingMap[$using]) ? $usingMap[$using]. '_' '') . ($using === 'default' && $display === 'title''option' $displayMap[$display]) . '_' $idx;
                
$uval $usingMap[$using] ? $usingMap[$using].'_' '';
                
$opt $smd_bio_data[$field][$uval 'option_'.$idx];

                
// No title in smd_bio_data for default, so fudge it
                
if ($using === 'default') {
                    
$ttl $stash['option'][$opt];
                } else {
                    
$ttl $smd_bio_data[$field][$uval 'title_'.$idx];
                }

                
$reps = array(
                    
'{'.$prefix.'iterate_option}' => $opt,
                    
'{'.$prefix.'iterate_title}' => $ttl,
                    
'{'.$prefix.'iterate_count}' => $idx,
                );

                
// When iterating all options, indicate which are default and which are chosen
                
if ($using === 'all') {
                    if (isset(
$stash['default']) && in_array($opt$stash['default'])) {
                        
$reps['{'.$prefix.'iterate_is_default}'] = '1';
                        
$smd_bio_option_data[$field]['iterate_is_default'] = '1';
                    } else {
                        
$reps['{'.$prefix.'iterate_is_default}'] = '0';
                        
$smd_bio_option_data[$field]['iterate_is_default'] = '0';
                    }

                    if (isset(
$stash['chosen']) && array_key_exists($opt$stash['chosen'])) {
                        
$flavour $smd_bio_data[$field]['type'];
                        
$html_sel = (in_array($flavour$checked_types)) ? 'checked' : ((in_array($flavour$selected_types)) ? 'selected' '1');
                        
$reps['{'.$prefix.'iterate_is_chosen}'] = $html_sel;
                        
$smd_bio_option_data[$field]['iterate_is_chosen'] = $html_sel;
                    } else {
                        
$reps['{'.$prefix.'iterate_is_chosen}'] = '';
                        
$smd_bio_option_data[$field]['iterate_is_chosen'] = '';
                    }
                }

                
$smd_bio_option_data[$field]['iterate_option'] = $opt;
                
$smd_bio_option_data[$field]['iterate_title'] = $ttl;
                
$smd_bio_option_data[$field]['iterate_count'] = $idx;
                if (
$debug) {
                    echo 
'++ ITERATOR REPLACEMENTS ++';
                    
dmp($reps);
                }
                
$out[] = ($thing !== null) ? parse(strtr($thing$reps)) : ( ($form !== '') ? parse_form(strtr($form$reps)) : $smd_bio_data[$field][$key] );
            }
        }
    }

    return 
doWrap($out$wraptag$break$class);
}

// Convenience conditional to test a field/item. Use smd_if for more advanced conditional logic
function smd_if_bio($atts$thing null) {
    global 
$smd_bio_data$smd_bio_option_data;

    
extract(lAtts(array(
        
'field'          => '',
        
'item'           => 'value',
        
'value'          => null,
        
'case_sensitive' => 1,
        
'debug'          => 0,
    ), 
$atts));

    
$bdata is_array($smd_bio_data) ? $smd_bio_data : array();
    
$idata is_array($smd_bio_option_data) ? $smd_bio_option_data : array();
    
$datapool array_merge($bdata$idata);
    if (
$debug) {
        echo 
'++ AVAILABLE BIO DATA ++';
        
dmp($datapool);
    }

    
$result false;

    if (isset(
$datapool[$field][$item])) {
        if (
$value !== null) {
            
$cval = ($case_sensitive) ? $value strtolower($value);
            
$citm = ($case_sensitive) ? $datapool[$field][$item] : strtolower($datapool[$field][$item]);
            
$result = ((string)$citm === (string)$cval);
        } else {
            
$result = ($datapool[$field][$item] !== '');
        }
    }

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

// Conditional to test bio meta data
function smd_if_bio_is($atts$thing null) {
    global 
$smd_bio_meta_data;

    
extract(lAtts(array(
        
'type'  => 'author'// author, field
        
'item'  => 'first'// first, last
        
'debug' => 0,
    ), 
$atts));

    
$datapool is_array($smd_bio_meta_data) ? $smd_bio_meta_data : array();
    if (
$debug) {
        echo 
'++ AVAILABLE BIO META DATA ++';
        
dmp($datapool);
    }

    
$result false;

    if (isset(
$datapool[$type][$item])) {
        
$result $datapool[$type][$item];
    }

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

// Convenience conditional
function smd_if_bio_first_author($atts$thing null) {
    return 
smd_if_bio_is(array('type' => 'author''item' => 'first'), $thing);
}
// Convenience conditional
function smd_if_bio_last_author($atts$thing null) {
    return 
smd_if_bio_is(array('type' => 'author''item' => 'last'), $thing);
}


// ------------------------
// A wrapper to article_custom that auto sets the user to the one specified
// or the current article's author.
// NOTE: lAtts() is NOT used because that limits the plugin attributes.
function smd_bio_articles($atts$thing null) {
    global 
$thisarticle;

    
$author = (isset($atts['author'])) ? $atts['author'] : (isset($thisarticle['authorid']) ? $thisarticle['authorid'] : '');
    
$atts['author'] = $author;
    return 
parseArticles($atts'1'$thing);
}