Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/plugins/_plugin.funcs.php
<?php
/**
 * Functions for Plugin handling.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * Recursive helper function to display a field of the plugin's settings (by manipulating a Form).
 *
 * Used for PluginSettings ("Edit plugin") and PluginUserSettings ("Edit user settings") as well as widgets.
 *
 * @todo dh> Allow to move setting sets up and down (order). Control goes into /inc/CONTROL/settings/plugins.php.
 * @todo NOTE: fp> I'm using this outside of Plugins; I'm not sure about proper factorization yet.
 *       This should probably be an extension of the Form class. Sth like "AutoForm" ;)
 *
 * @param string Settings path, e.g. 'locales[0]' or 'setting'
 * @param array Meta data for this setting.
 * @param Form (by reference)
 * @param string Settings type ('Settings' or 'UserSettings' or 'GroupSettings' or 'Widget' or 'Skin')
 * @param Plugin|Widget
 * @param mixed Target (User object for 'UserSettings', Group object for 'GroupSettings')
 * @param mixed Value to really use (used for recursion into array type settings)
 */
function autoform_display_field( $parname, $parmeta, & $Form, $set_type, $Obj, $set_target = NULL, $use_value = NULL )
{
    global
$debug;
    static
$has_array_type;

    if( !
is_array( $parmeta ) )
    {    
// Must be array:
       
return;
    }

    if( ! empty(
$parmeta['no_edit']) )
    {
// this setting is not editable
       
return;
    }

   
$params = array();

    if(
$use_value === NULL )
    {
// outermost level
       
$has_array_type = false; // for adding a note about JS
       
$outer_most = true;
    }
    else
    {
       
$outer_most = false;
    }

   
// Set input group
   
if( isset( $parmeta['group'] ) )
    {
       
$group = $parmeta['group'];
        if(
substr( $group, -1 ) === ']' )
        {    
// If group name is in array format like "edit_plugin_1_set_sample_sets[0][group_name]",
            // then param name must be like "edit_plugin_1_set_sample_sets[0][group_name_param_name]":
           
$parname = substr( $group, 0, strlen( $group ) - 1 ).$parname.']';
        }
        else
        {    
// If group name is simple like "group_name",
            // then param name must be like "group_name_param_name"
           
$parname = $group.$parname;
        }
    }
    else
    {
       
$group = NULL;
    }

   
// Passthrough some attributes to elements:
   
foreach( $parmeta as $k => $v )
    {
        if(
in_array( $k, array( 'id', 'class', 'onchange', 'onclick', 'onfocus', 'onkeyup', 'onkeydown', 'onreset', 'onselect', 'cols', 'rows', 'maxlength', 'placeholder' ) ) )
        {
           
$params[$k] = $v;
        }
    }
    if( ! empty(
$parmeta['multiple']) )
    {
// "multiple" attribute for "select" inputs:
       
$params['multiple'] = 'multiple';
    }

    if( isset(
$parmeta['note']) )
    {
       
$params['note'] = $parmeta['note'];
    }

    if( ! isset(
$parmeta['type']) || $parmeta['type'] == 'html_input' )
    {
       
$parmeta['type'] = 'text';
    }
    elseif(
$parmeta['type'] == 'html_textarea' )
    {
       
$parmeta['type'] = 'textarea';
    }

    if(
strpos($parmeta['type'], 'select_') === 0 )
    {
// 'allow_none' setting for select_* types
       
if( isset($parmeta['allow_none']) )
        {
           
$params['allow_none'] = $parmeta['allow_none'];
        }
    }

   
$help_icon = NULL;
    if( isset(
$parmeta['help']) )
    {
        if(
$parmeta['help'] === true )
        {
// link to $parname-target:
           
$help_target = '#'.preg_replace( array('~\]?\[\d+\]\[~', '~\]$~'), array('_',''), $parname );
        }
        else
        {
           
$help_target = $parmeta['help'];
        }
       
$help_icon = $Obj->get_help_link( $help_target );
    }

   
$set_label = isset($parmeta['label']) ? $parmeta['label'] : '';

    if( ! empty(
$parmeta['disabled']) )
    {
       
$params['disabled'] = 'disabled';
    }


   
// "Layout" settings:
   
if( isset($parmeta['layout']) )
    {
        switch(
$parmeta['layout'] )
        {
            case
'begin_fieldset':
               
$fieldset_title = $set_label;
                if(
$Form->layout == 'accordion' && ! empty( $Form->_opentags['fieldset'] ) )
                {    
// Use accordion style only for top level:
                   
$Form->switch_layout( 'fieldset' );
                }
               
$fieldset_params = array();
                if( isset(
$parmeta['fold'] ) && $parmeta['fold'] === true )
                {    
// Enable folding for the fieldset:
                   
$fieldset_params['fold'] = $parmeta['fold'];
                    if( isset(
$parmeta['deny_fold'] ) )
                    {    
// TRUE to don't allow fold the block and keep it opened always on page loading:
                       
$fieldset_params['deny_fold'] = $parmeta['deny_fold'];
                    }
                   
// Unique ID of fieldset to store in user  settings or in user per collection settings:
                   
$fieldset_params['id'] = isset( $parmeta['id'] ) ? $parmeta['id'] : $parname;
                }
               
$Form->begin_fieldset( $fieldset_title.$help_icon, $fieldset_params );
                break;

            case
'end_fieldset':
               
$Form->end_fieldset();
                if( isset(
$Form->saved_layouts[0] ) && $Form->saved_layouts[0] == 'accordion' && ! empty( $Form->_opentags['fieldset'] ) )
                {    
// We should switch back to accordion style:
                   
$Form->switch_layout( NULL );
                }
                break;

            case
'separator':
                echo
'<hr />';
                break;

            case
'html': // Output HTML code here
               
if( ! isset($parmeta['value']) )
                {
                   
$parmeta['value'] = '<div class="error">HTML layout usage:<pre>'.
                           
htmlentities("'layout' => 'html',\n'value' => '<em>My HTML code</em>',").'</pre></div>';
                }
                echo
$parmeta['value'];
                break;
        }
        return;
    }

    if( ! empty(
$help_icon) )
    {
// Append help icon to note:
       
if( empty($params['note']) )
        {
           
$params['note'] = $help_icon;
        }
        else
        {
           
$params['note'] .= ' '.$help_icon;
        }
    }

    if( isset(
$parmeta['input_prefix'] ) )
    {    
// Use prefix before input element if it is defined:
       
$params['input_prefix'] = $parmeta['input_prefix'];
    }

    if( isset(
$parmeta['input_suffix'] ) )
    {    
// Use suffix after input element if it is defined:
       
$params['input_suffix'] = $parmeta['input_suffix'];
    }

    if(
$set_type == 'Skin' &&
       
in_array( $parmeta['type'], array( 'text', 'integer', 'color', 'select', 'select_object' ) ) )
    {    
// Append action icon to restore setting to default value by JavaScript:
       
$params['input_suffix'] = ( isset( $params['input_suffix'] ) ? $params['input_suffix'] : '' ).
           
' '.get_icon( 'reload', 'imgtag', array(
               
'data-default-value' => $Obj->get_setting_default_value( $parname, $group ),
               
'title' => T_('Restore to default value'),
               
'class' => 'pointer'
           
) );
    }

    if( isset(
$use_value) )
    {
       
$set_value = $use_value;
    }
    else
    {
        switch(
$set_type )
        {
            case
'CollSettings':
               
$set_value = $Obj->get_coll_setting( $parname, $set_target, false, $group );
               
$error_value = NULL;
                break;

            case
'MsgSettings':
               
$set_value = $Obj->get_msg_setting( $parname, $group );
               
$error_value = NULL;
                break;

            case
'EmailSettings':
               
$set_value = $Obj->get_email_setting( $parname, $group );
               
$error_value = NULL;
                break;

            case
'Skin':
               
$set_value = $Obj->get_setting( $parname, $group );
               
$error_value = NULL;
                break;

            case
'Widget':
            case
'PluginWidget':
               
$set_value = $Obj->get_param( $parname, NULL, $group );
               
$error_value = NULL;
                break;

            case
'UserSettings':
               
// NOTE: this assumes we come here only on recursion or with $use_value set..!
               
$set_value = $Obj->UserSettings->get( $parname, $set_target->ID );
               
$tmp_params = array(
                   
'name' => $parname,
                   
'value' => & $set_value,
                   
'meta' => $parmeta,
                   
'User' => $set_target,
                   
'action' => 'display' );
               
$error_value = $Obj->PluginUserSettingsValidateSet( $tmp_params );
                break;

            case
'GroupSettings':
               
// NOTE: this assumes we come here only on recursion or with $use_value set..!
               
$set_value = $Obj->GroupSettings->get( $parname, $set_target->ID );
               
$tmp_params = array(
                   
'name'   => $parname,
                   
'value'  => & $set_value,
                   
'meta'   => $parmeta,
                   
'Group'  => $set_target,
                   
'action' => 'display' );
               
$error_value = $Obj->PluginGroupSettingsValidateSet( $tmp_params );
                break;

            case
'Settings':
               
// NOTE: this assumes we come here only on recursion or with $use_value set..!
               
$set_value = $Obj->Settings->get( $parname );
               
$tmp_params = array(
                   
'name'   => $parname,
                   
'value'  => & $set_value,
                   
'meta'   => $parmeta,
                   
'action' => 'display' );
               
$error_value = $Obj->PluginSettingsValidateSet( $tmp_params );
                break;

            case
'SharedSettings':
               
$set_value = $Obj->get_shared_setting( $parname, $group );
               
$error_value = NULL;
                break;

            default:
               
debug_die( "unhandled set_type $set_type" );
                break;
        }

        if(
$error_value )
        {
// add error
           
param_error( $Obj->get_param_prefix().$parname, NULL, $error_value ); // only add the error to the field
       
}
    }

   
// Display input element:
   
$input_name = $Obj->get_param_prefix().$parname;
    if(
substr($parmeta['type'], 0, 6) == 'select' && ! empty($parmeta['multiple']) )
    {
// a "multiple" select:
       
$input_name .= '[]';
    }

   
// Get a value from _POST request to display it e.g. when some error was created during update
   
$value_from_request = get_param( $input_name );
    if(
$value_from_request !== NULL )
    {
       
$set_value = $value_from_request;
    }

    if( ! empty(
$parmeta['hide'] ) )
    {    
// Hide this field on the editing form:
       
$original_form_fieldstart = $Form->fieldstart;
       
$Form->fieldstart = preg_replace( '/>$/', 'style="display:none">', $Form->fieldstart );
        if( isset(
$Form->fieldstart_checkbox ) )
        {
           
$original_form_fieldstart_checkbox = $Form->fieldstart_checkbox;
           
$Form->fieldstart_checkbox = preg_replace( '/>$/', 'style="display:none">', $Form->fieldstart_checkbox );
        }
        if( isset(
$Form->fieldstart_radio ) )
        {
           
$original_form_fieldstart_radio = $Form->fieldstart_radio;
           
$Form->fieldstart_radio = preg_replace( '/>$/', 'style="display:none">', $Form->fieldstart_radio );
        }
    }

    switch(
$parmeta['type'] )
    {
        case
'begin_line':
           
$Form->begin_line( $set_label );
            break;

        case
'end_line':
           
$Form->end_line( $set_label );
            break;

        case
'string':
            echo
$set_label;
            break;

        case
'checkbox':
           
$Form->checkbox_input( $input_name, $set_value, $set_label, $params );
            break;

        case
'checklist':
           
$options = array();
            foreach(
$parmeta['options'] as $meta_option )
            {
               
$meta_option_checked = $set_value === NULL ?
                   
/* default value */ $meta_option[2] :
                   
/* saved value */   ! empty( $set_value[ $meta_option[0] ] );

               
$meta_option_disabled = isset( $meta_option[4] ) ? $meta_option[4] : NULL;
               
$meta_option_note = isset( $meta_option[5] ) ? $meta_option[5] : NULL;
               
$meta_option_class = isset( $meta_option[6] ) ? $meta_option[6] : NULL;
               
$meta_option_hidden = isset( $meta_option[7] ) ? $meta_option[7] : NULL;
               
$meta_option_label_attribs = isset( $meta_option[8] ) ? $meta_option[8] : NULL;
               
$options[] = array( $input_name.'['.$meta_option[0].']', 1, $meta_option[1], $meta_option_checked, $meta_option_disabled, $meta_option_note, $meta_option_class, $meta_option_hidden, $meta_option_label_attribs );
            }
           
$Form->checklist( $options, $input_name, $set_label, false, false, $params );
            break;

        case
'textarea':
           
$textarea_rows = isset($parmeta['rows']) ? $parmeta['rows'] : 3;
           
$Form->textarea_input( $input_name, $set_value, $textarea_rows, $set_label, $params );
            break;

        case
'select':
           
$params['force_keys_as_values'] = true; // so that numeric keys get used as values! autoform_validate_param_value() checks for the keys only.
           
if( ! empty( $parmeta['multiple'] ) )
            {    
// Set specific size of multiple selector or use automatic size depending on count of options:
               
$params['size'] = ( isset( $parmeta['size'] ) ? $parmeta['size'] : count( $parmeta['options'] ) );
            }
           
$Form->select_input_array( $input_name, $set_value, $parmeta['options'], $set_label, isset($parmeta['note']) ? $parmeta['note'] : NULL, $params );
            break;

        case
'select_object':
           
$Form->select_input_object( $input_name, $set_value, $parmeta['object'], $set_label, $params );
            break;

        case
'select_blog':
           
$BlogCache = & get_BlogCache();
           
$Form->select_input_object( $input_name, $set_value, $BlogCache, $set_label, $params );
            break;

        case
'select_group':
           
$GroupCache = & get_GroupCache();
           
$Form->select_input_object( $input_name, $set_value, $GroupCache, $set_label, $params );
            break;

        case
'select_user':
           
$UserCache = & get_UserCache();
           
$UserCache->clear();
           
$users_SQL = $UserCache->get_SQL_object();
           
$users_SQL->LIMIT( isset( $parmeta['users_limit'] ) ? intval( $parmeta['users_limit'] ) : 20 );
           
$UserCache->load_by_sql( $users_SQL );
            if( ! isset(
$params['loop_object_method'] ) )
            {
               
$params['loop_object_method'] = 'get_preferred_name';
            }
           
$Form->select_input_object( $input_name, $set_value, $UserCache, $set_label, $params );
            break;

        case
'radio':
            if( isset(
$parmeta['field_lines'] ) )
            {
               
$params['lines'] = $parmeta['field_lines'];
            }
           
$options = array();
            foreach(
$parmeta['options'] as $l_key => $l_options )
            {
               
$options[$l_key] = array(
                   
'value' => $l_options[0],
                   
'label' => $l_options[1]
                );

                if( isset(
$l_options[2] ) )
                {
                   
$options[$l_key]['note'] = $l_options[2];
                }
                if( isset(
$l_options[4] ) )
                {    
// Convert "inline attribs" to "params" array:
                   
preg_match_all( '#(\w+)=[\'"](.*)[\'"]#', $l_options[4], $matches, PREG_SET_ORDER );

                    foreach(
$matches as $l_set_nr => $l_match )
                    {
                       
$options[$l_key][$l_match[1]] = $l_match[2];
                    }
                }

                if( isset(
$l_options[3] ) )
                {
                   
$options[$l_key]['suffix'] = $l_options[3];
                }
            }
           
$Form->radio_input( $input_name, $set_value, $options, $set_label, $params );
            break;

        case
'array':
        case
'array:integer':
        case
'array:array:integer':
        case
'array:string':
        case
'array:array:string':
        case
'array:regexp':
           
$has_array_type = true;
           
$has_color_field = false;

           
// Always use 'fieldset' layout to display it the same way from normal and ajax calls
           
$Form->switch_layout( 'fieldset' );
            if(
substr_count( $parname, '[' ) % 2 )
            {
// this refers to a specific array type set (with index pos at the end), e.g. when adding a field through AJAX:
               
$pos_last_bracket = strrpos($parname, '[');
               
$k_nb = substr( $parname, $pos_last_bracket+1, -1 );
               
$disp_arrays = array( '' => $set_value ); // empty key..
               
$parname = substr($parname, 0, $pos_last_bracket);
            }
            else
            {
// display all values hold in this set:
               
$disp_whole_set = true;
               
$disp_arrays = $set_value;
               
$fieldset_title = $set_label;
                if(
$debug )
                {
                   
$fieldset_title .= ' [debug: '.$parname.']';
                }
               
$fieldset_params = array();
                if( isset(
$parmeta['fold'] ) && $parmeta['fold'] === true )
                {    
// Enable folding for the fieldset:
                   
$fieldset_params['fold'] = $parmeta['fold'];
                    if( isset(
$parmeta['deny_fold'] ) )
                    {    
// TRUE to don't allow fold the block and keep it opened always on page loading:
                       
$fieldset_params['deny_fold'] = $parmeta['deny_fold'];
                    }
                }
               
// Unique ID of fieldset to store in user settings or in user per collection settings:
               
$fieldset_params['id'] = isset( $parmeta['id'] ) ? $parmeta['id'] : $parname;
               
$Form->begin_fieldset( $fieldset_title, $fieldset_params );

                if( ! empty(
$params['note']) )
                {
                    echo
'<p class="notes">'.$params['note'].'</p>';
                }
               
$k_nb = 0;
            }

           
// check if a color field is among the entries
           
foreach( $parmeta['entries'] as $entry )
            {
                if( isset(
$entry['type'] ) && $entry['type'] == 'color' )
                {
                   
$has_color_field = true;
                    break;
                }
            }

           
$user_ID = $set_type == 'UserSettings' ? $set_target->ID : '';
            if(
is_array( $set_value ) && ! empty($set_value) )
            {
// Display value of the setting. It may be empty, if there's no set yet.
               
foreach( $disp_arrays as $k => $v )
                {
                   
$fieldset_params = array(
                           
'class' => 'bordered',
                           
// Unique ID of fieldset(Also used to store a folding state in user settings or in user per collection settings):
                           
'id'    => isset( $parmeta['id'] ) ? $parmeta['id'] : $parname.'_'.$k_nb,
                        );
                   
$remove_action = '';
                    if( ! isset(
$parmeta['min_count']) || count($set_value) > $parmeta['min_count'] )
                    {
// provide icon to remove this set
                       
$remove_action = '<span class="pull-right">'.action_icon(
                               
T_('Remove'),
                               
'minus',
                               
regenerate_url( 'action', array('action=del_settings_set&amp;set_path='.$parname.'['.$k.']'.( $set_type == 'UserSettings' ? '&amp;user_ID='.$user_ID : '' ), 'plugin_ID='.$Obj->ID) ),
                               
T_('Remove'),
                               
5, 3, /* icon/text prio */
                                // attach onclick event to remove the whole fieldset:
                               
array(
                                   
'onclick' => '
                                        jQuery( \'#fieldset_wrapper_'
.$parname.'_'.$k_nb.'\' ).remove();
                                        '
.( isset( $parmeta['max_number'] ) ? '
                                        if( jQuery( \'[id^=fieldset_wrapper_'
.$parname.'_\' ).length < '.intval( $parmeta['max_number'] ).' )
                                        {
                                            jQuery( \'#button_add_new_setting_'
.$parname.'\' ).show();
                                        }'
: '' ).'
                                        return false;'
,
                                    )
                                ).
'</span>';
                    }
                    if( isset(
$parmeta['fold'] ) && $parmeta['fold'] === true )
                    {    
// Enable folding for the fieldset:
                       
$fieldset_params['fold'] = $parmeta['fold'];
                        if( isset(
$parmeta['deny_fold'] ) )
                        {    
// TRUE to don't allow fold the block and keep it opened always on page loading:
                           
$fieldset_params['deny_fold'] = $parmeta['deny_fold'];
                        }

                       
$fieldset_params['id'] = $parname;
                    }
                   
$Form->begin_fieldset( '#'.$k_nb.$remove_action, $fieldset_params );

                    if( isset(
$parmeta['key']) )
                    {
// KEY FOR THIS ENTRY:
                       
if( ! strlen($k) && isset($parmeta['key']['defaultvalue']) )
                        {
// key is not given/set and we have a default:
                           
$l_value = $parmeta['key']['defaultvalue'];
                        }
                        else
                        {
                           
$l_value = $k;
                        }
                       
// RECURSE:
                       
autoform_display_field( $parname.'['.$k_nb.'][__key__]', $parmeta['key'], $Form, $set_type, $Obj, $set_target, $l_value );
                    }

                    foreach(
$parmeta['entries'] as $l_set_name => $l_set_entry )
                    {
                        if( isset(
$set_value[$k][$l_set_name] ) )
                        {    
// Use a saved value:
                           
$l_value = $set_value[$k][$l_set_name];
                        }
                        else
                        {    
// Use default value if it is defined:
                           
$l_value = isset( $l_set_entry['defaultvalue'] ) ? $l_set_entry['defaultvalue'] : NULL;
                        }
                       
// RECURSE:
                       
autoform_display_field( $parname.'['.$k_nb.']['.$l_set_name.']', $l_set_entry, $Form, $set_type, $Obj, $set_target, $l_value );
                    }
                   
$Form->end_fieldset();
                   
$k_nb++;
                }
            }

           
// This div is used to insert new set of setting:
           
echo '<div id="block_add_new_setting_'.$parname.'_'.$Obj->ID.'" data-param-num="'.$k_nb.'"></div>';

           
// TODO: fix this for AJAX callbacks, when removing and re-adding items (dh):
           
if( ! is_ajax_request() )
            {    
// Don't display "Add" button twice after AJAX request:
               
global $Blog;
               
$set_path = $parname.'['.$k_nb.']';

                echo
action_icon(
                   
sprintf( T_('Add a new set of &laquo;%s&raquo;'), $set_label ),
                   
'add',
                   
regenerate_url( 'action', array('action=add_settings_set', 'set_path='.$set_path.( $set_type == 'UserSettings' ? '&amp;user_ID='.get_param('user_ID') : '' ), 'plugin_ID='.$Obj->ID) ),
                   
T_('Add'),
                   
5, 3, /* icon/text prio */
                   
array(
                       
// Set button id to hide/show it depending on current number of settings:
                       
'id' => 'button_add_new_setting_'.$parname,
                       
// Hide button to add new set of setting when max number is reached:
                       
'style' => ( isset( $parmeta['max_number'] ) && $k_nb >= $parmeta['max_number'] ? 'display:none' : '' ),
                       
// Replace the 'add new' div with a new set of setting:
                       
'onclick' => 'jQuery.get( \''.get_htsrv_url().'async.php\',
                            {
                                action: \'add_plugin_sett_set\',
                                plugin_ID: '
.$Obj->ID.',
                                set_type: \''
.$set_type.'\',
                                param_name: \''
.$parname.'\',
                                //param_num: jQuery( \'[id^=fieldset_wrapper_'
.$parname.'_\' ).length
                                param_num: jQuery( \'#block_add_new_setting_'
.$parname.'_'.$Obj->ID.'\' ).data( \'param-num\' )
                                '
.( isset( $Blog ) ? ',blog: '.$Blog->ID : '' ).'
                                '
.( $set_type == 'UserSettings' ? ',user_ID: '.get_param( 'user_ID' ) : '' ).'
                            },
                            function( r, status )
                            {
                                jQuery( \'#block_add_new_setting_'
.$parname.'_'.$Obj->ID.'\' ).replaceWith( ajax_debug_clear( r ) );
                                '
.( $has_color_field ? 'evo_initialize_colorpicker_inputs();' : '' ).'
                                '
.( isset( $parmeta['max_number'] ) ? '
                                if( jQuery( \'[id^=fieldset_wrapper_'
.$parname.'_\' ).length >= '.intval( $parmeta['max_number'] ).' )
                                {
                                    jQuery( \'#button_add_new_setting_'
.$parname.'\' ).hide();
                                }'
: '' ).'
                            }
                        );
                        return false;'
                   
)
                );
            }

            if( ! empty(
$disp_whole_set) )
            {
// close the surrounding fieldset:
               
$Form->end_fieldset();
            }
           
$Form->switch_layout( NULL );

            break;

        case
'password':
           
$params['type'] = 'password'; // same as text input, but type=password

       
case 'float':
        case
'integer':
        case
'text':
           
// Default: "text input"
           
if( isset($parmeta['size']) )
            {
               
$size = (int)$parmeta['size'];
            }
            else
            {
// Default size:
               
$size = 15;
            }
            if( isset(
$parmeta['maxlength']) )
            {
               
$params['maxlength'] = (int)$parmeta['maxlength'];
            }
            else
            {
// do not use size as maxlength, if not given!
               
$params['maxlength'] = '';
            }

            if( isset(
$parmeta['hide_label'] ) )
            {
// This param is used to hide a label
               
$params['hide_label'] = $parmeta['hide_label'];
            }

            if(
$parmeta['type'] == 'integer' )
            {    
// Set special type 'number' for integer param to initialize control arrows to allow increase/decrease a value:
               
$params['type'] = 'number';
                if( isset(
$parmeta['valid_range']['min'] ) )
                {    
// Restrict with min value:
                   
$params['min'] = $parmeta['valid_range']['min'];
                }
                if( isset(
$parmeta['valid_range']['max'] ) )
                {    
// Restrict with max value:
                   
$params['max'] = $parmeta['valid_range']['max'];
                }
               
// Input number element doesn't support attribute "size", so we have only one way to set width with style:
               
$params['style'] = 'width:'.( 40 + $size * 8 ).'px';
            }

            if( isset(
$parmeta['suffix'] ) )
            {    
// Set suffix text after input element:
               
$params['input_suffix'] = $parmeta['suffix'];
            }

           
$Form->text_input( $input_name, $set_value, $size, $set_label, '', $params ); // TEMP: Note already in params
           
break;

        case
'usertag':
           
$size = isset( $parmeta['size'] ) ? intval( $parmeta['size'] ) : 30;
           
$Form->usertag_input( $input_name, $set_value, $size, $set_label, '', $params );
            break;

        case
'itemtag':
           
$size = isset( $parmeta['size'] ) ? intval( $parmeta['size'] ) : 30;
           
$Form->tag_input( $input_name, $set_value, $size, $set_label, '', $params );
            break;

        case
'info':
           
$Form->info( $parmeta['label'], $parmeta['info'] );
            break;

        case
'color':
            if( isset(
$parmeta['transparency'] ) )
            {
               
$params['transparency'] = $parmeta['transparency'];
            }
           
$Form->color_input( $input_name, $set_value, $set_label, '', $params );
            break;

        case
'fileselect':
            if( isset(
$parmeta['size'] ) )
            {
               
$params['max_file_num'] = $parmeta['size'];
            }

           
$params['root'] = isset( $parmeta['root'] ) ? $parmeta['root'] : '';
           
$params['path'] = isset( $parmeta['path'] ) ? $parmeta['path'] : '';
           
$params['size_name'] = isset( $parmeta['thumbnail_size'] ) ? $parmeta['thumbnail_size'] : 'crop-64x64';
           
$params['max_file_num'] = isset( $parmeta['max_file_num'] ) ? $parmeta['max_file_num'] : 1;
           
$params['initialize_with'] = isset( $parmeta['initialize_with'] ) ? $parmeta['initialize_with'] : '';
           
$params['note'] = isset( $parmeta['note'] ) ? $parmeta['note'] : '';

           
$Form->fileselect( $input_name, $set_value, $set_label, $params['note'], $params );
            break;

        case
'input_group':
            if( ! empty(
$parmeta['inputs'] ) && is_array( $parmeta['inputs'] ) )
            {
               
$Form->begin_line( $parmeta['label'], $input_name );
                foreach(
$parmeta['inputs'] as $l_parname => $l_parmeta )
                {
                   
$l_parmeta['group'] = $parname; // inject group
                   
autoform_display_field( $l_parname, $l_parmeta, $Form, $set_type, $Obj, $set_target, $use_value );
                }
               
$Form->end_line();
            }
            break;

        case
'item_selector':
           
$Form->item_selector( $input_name, $set_value, $set_label, $parmeta );
            break;

        default:
           
debug_die( 'Unsupported type ['.$parmeta['type'].'] from GetDefaultSettings()!' );
    }

    if( isset(
$original_form_fieldstart ) )
    {    
// Revert original field start html code:
       
$Form->fieldstart = $original_form_fieldstart;
    }
    if( isset(
$original_form_fieldstart_checkbox ) )
    {    
// Revert original field start html code:
       
$Form->fieldstart_checkbox = $original_form_fieldstart_checkbox;
    }
    if( isset(
$original_form_fieldstart_radio ) )
    {    
// Revert original field start html code:
       
$Form->fieldstart_radio = $original_form_fieldstart_radio;
    }

    if(
$outer_most && $has_array_type )
    {
// Note for Non-Javascript users:
       
echo '<script></script><noscript>';
        echo
'<p class="note">'.T_('Note: before adding a new set you have to save any changes.').'</p>';
        echo
'</noscript>';
    }
}


/**
 * Helper method for "add_settings_set" and "delete_settings_set" action.
 *
 * Walks the given settings path and either inits the target entry or unsets it ($init_value=NULL).
 *
 * @param object Plugin or Skin
 * @param string Settings type ("Settings" or "UserSettings")
 * @param string The settings path, e.g. 'setting[0]foo[1]'. (Is used as array internally for recursion.)
 * @param mixed The initial value of the setting, typically array() - NULL to unset it (action "delete_settings_set" uses it)
 * @return array|false
 */
function _set_setting_by_path( & $Plugin, $set_type, $path, $init_value = array() )
{
   
$r = get_plugin_settings_node_by_path( $Plugin, $set_type, $path, true );
    if(
$r === false )
    {
        return
false;
    }

   
// Make return value handier. Note: list() would copy and destroy the references (setting and set_node)!
   
$set_name = & $r['set_name'];
   
$set_node = & $r['set_node'];
   
$set_meta = & $r['set_meta'];
   
$set_parent = & $r['set_parent'];
   
$set_key  = & $r['set_key'];
   
$setting  = & $r['setting'];
   
#pre_dump( $r );

    #if( isset($set_node) && $init_value !== NULL )
    #{ // Setting already exists (and we do not want to delete), e.g. page reload!
    #    return false;
    #    /*
    #    while( isset($l_setting[ $path[0] ]) )
    #    { // bump the index until not set
    #        $path[0]++;
    #    }
    #    */
    #}
    #else
   
if( is_null($init_value) )
    {
// NULL is meant to unset it
       
unset($set_parent[$set_key]);
    }
    else
    {
// Init entries:
        // destroys reference: $set_node = $init_value;

        // Copy meta entries:
       
foreach( $set_meta['entries'] as $k => $v )
        {
            if( isset(
$v['defaultvalue'] ) )
            {
// set to defaultvalue
               
$set_node[$k] = $v['defaultvalue'];
            }
            else
            {
                if( isset(
$v['type']) && strpos( $v['type'], 'array' ) === 0 )
                {
                   
$set_node[$k] = array();
                }
                else
                {
                   
$set_node[$k] = '';
                }
            }
        }
    }

    switch(
$set_type )
    {
        case
'Settings':
        case
'CollSettings':
        case
'PluginWidget':
           
$Plugin->Settings->set( $set_name, $setting );
            break;

        case
'Widget':
           
$Plugin->set( $set_name, $setting );
            break;

        case
'UserSettings':
           
$Plugin->UserSettings->set( $set_name, $setting );
            break;

        case
'GroupSettings':
           
$Plugin->GroupSettings->set( $set_name, $setting );
            break;

        case
'MsgSettings':
           
$set_name = ( $set_name == 'msg_apply_rendering' ? '' : 'msg_' ).$set_name;
           
$Plugin->Settings->set( $set_name, $setting );
            break;

        case
'EmailSettings':
           
$set_name = ( $set_name == 'email_apply_rendering' ? '' : 'email_' ).$set_name;
           
$Plugin->Settings->set( $set_name, $setting );
            break;

        case
'Skin':
           
$Skin = & $Plugin;
           
$Skin->set_setting( $set_name, $setting );
            break;

        case
'SharedSettings':
           
$set_name = ( $set_name == 'shared_apply_rendering' ? '' : 'shared_' ).$set_name;
           
$Plugin->Settings->set( $set_name, $setting );
            break;

        default:
           
debug_die( 'Invalid plugin type param!' );
    }


    return
$setting;
}


/**
 * Get a node from settings by path (e.g. "locales[0][questions]")
 *
 * @param object Plugin or Skin
 * @param string Settings type ("Settings" or "UserSettings")
 * @param string The settings path, e.g. 'setting[0]foo[1]' or even 'setting[]'. (Is used as array internally for recursion.)
 * @return array Array(
 *          - 'set_name': setting name (string); key of the first level
 *          - 'set_node': selected setting node, may be NULL (by reference)
 *          - 'set_meta': meta info (from GetDefault[User]Settings()) for selected node (array)
 *          - 'set_parent': parent node (by reference)
 *          - 'set_key': key in parent node (by reference)
 *          - 'setting': whole settings (array)
 */
function get_plugin_settings_node_by_path( & $Plugin, $set_type, $path, $create = false )
{
   
// Init:
   
if( ! preg_match( '~^\w+(\[\w+\])+$~', $path ) )
    {
       
debug_die( 'Invalid path param!' );
    }

   
$path = preg_split( '~(\[|\]\[?)~', $path, -1 ); // split by "[" and "][", so we get an array with setting name and index alternating
   
$foo = array_pop($path); // remove last one
   
if( ! empty($foo) )
       
debug_die('Assertion failed!');

   
$set_name = $path[0];

   
// meta info for this setting:
   
$tmp_params = array( 'for_editing' => true );

    switch(
$set_type )
    {
        case
'Settings':
           
$setting = $Plugin->Settings->get( $set_name );
           
$defaults = $Plugin->GetDefaultSettings( $tmp_params );
            break;

        case
'UserSettings':
           
$setting = $Plugin->UserSettings->get( $set_name );
           
$defaults = $Plugin->GetDefaultUserSettings( $tmp_params );
            break;

        case
'GroupSettings':
           
$setting = $Plugin->GroupSettings->get( $set_name );
           
$defaults = $Plugin->GetDefaultGroupSettings( $tmp_params );
            break;

        case
'CollSettings':
           
$setting = $Plugin->Settings->get( $set_name );
           
$defaults = $Plugin->get_coll_setting_definitions( $tmp_params );
            break;

        case
'MsgSettings':
           
$param_name = ( $set_name == 'msg_apply_rendering' ? '' : 'msg_' ).$set_name;
           
$setting = $Plugin->Settings->get( $param_name );
           
$defaults = $Plugin->get_msg_setting_definitions( $tmp_params );
            break;

        case
'EmailSettings':
           
$param_name = ( $set_name == 'email_apply_rendering' ? '' : 'email_' ).$set_name;
           
$setting = $Plugin->Settings->get( $param_name );
           
$defaults = $Plugin->get_email_setting_definitions( $tmp_params );
            break;

        case
'PluginWidget':
           
$setting = $Plugin->Settings->get( $set_name );
           
$defaults = $Plugin->get_widget_param_definitions( $tmp_params );
            break;

        case
'Widget':
           
$setting = $Plugin->get_param( $set_name );
           
$defaults = $Plugin->get_param_definitions( $tmp_params );
            break;

        case
'Skin':
           
$Skin = & $Plugin;
           
$setting = $Skin->get_setting( $set_name );
           
$defaults = $Skin->get_param_definitions( $tmp_params );
            break;

        case
'SharedSettings':
           
$param_name = ( $set_name == 'shared_apply_rendering' ? '' : 'shared_' ).$set_name;
           
$setting = $Plugin->Settings->get( $param_name );
           
$defaults = $Plugin->get_shared_setting_definitions( $tmp_params );
            break;

        default:
           
debug_die( 'Invalid plugin type param!' );
    }

    if( !
is_array( $setting ) )
    {
// this may happen, if there was a non-array setting stored previously:
        // discard those!
       
$setting = array();
    }

    if( ! isset(
$defaults[ $set_name ] ) )
    {
       
//debug_die( 'Invalid setting ('.$set_name.') - no meta data!' );
       
return false;
    }

   
$found_node = & $setting;
   
$defaults_node = & $defaults;
   
$set_meta = $defaults[$set_name];
   
$set_parent = NULL;
   
$set_key = NULL;

   
$count = 0;
    while(
count($path) )
    {
       
$count++;

       
$loop_name = array_shift($path);

        if(
$count > 1 )
        {
           
$set_parent = & $found_node[$loop_name];
           
$set_key = NULL;
           
$defaults_node = & $defaults_node['entries'][$loop_name];
           
$found_node = & $found_node[$loop_name];
        }
        else
        {
           
$defaults_node = & $defaults_node[$loop_name];
           
$set_parent = & $setting;
        }

        if(
count($path) )
        {
// has an index => array
           
$loop_index = array_shift($path);

           
#$set_parent = & $set_parent[$loop_name];
           
$set_key = $loop_index;

            if(
$set_key === '' )
            {
// []-syntax: append entry
               
if( $create && ! count($path) )
                {
// only create, if at the end
                   
$found_node[] = array();
                }
               
$found_node = & $found_node[ array_pop(array_keys($found_node)) ];
            }
            else
            {
// specific key:
               
if( ! isset($found_node[$loop_index]) )
                {
                   
$found_node[$loop_index] = array();
                }
               
$found_node = & $found_node[$loop_index];
            }
        }
    }

   
#echo '<h1>RETURN</h1>'; pre_dump( $set_parent, $set_key );
   
return array(
       
'set_name' => $set_name,
       
'set_node' => & $found_node,
       
'set_meta' => $defaults_node,
       
'set_parent' => & $set_parent,
       
'set_key' => & $set_key,
       
'setting' => & $setting );
}


/**
 * Set Plugin settings from params.
 *
 * fp> WARNING: also used outside of plugins. Work in progress.
 *
 * This handled plugin specific params when saving a user profile (PluginUserSettings) or plugin settings (PluginSettings).
 *
 * @param string Settings path, e.g. 'locales[0]' or 'setting'
 * @param array Meta data for this setting.
 * @param Plugin|Widget
 * @param string Type of Settings (either 'Settings' or 'UserSettings' or 'GroupSettings').
 * @param mixed Target (User object for 'UserSettings', Group object for 'GroupSettings')
 * @param mixed NULL to use value from request, OR set value what you want to force
 */
function autoform_set_param_from_request( $parname, $parmeta, & $Obj, $set_type, $set_target = NULL, $set_value = NULL )
{
    if( !
is_array( $parmeta ) )
    {    
// Must be array:
       
return;
    }

    if( isset(
$parmeta['layout']) )
    {
// a layout "setting"
       
return;
    }

    if( ( ! empty(
$parmeta['disabled'] ) || ! empty( $parmeta['no_edit'] ) )
        &&
$set_value === NULL )
    {
// the setting is disabled, but allow to update the value when it is forced by $set_value
       
return;
    }

    if( ! empty(
$parmeta['inputs'] ) )
    {
        foreach(
$parmeta['inputs'] as $l_parname => $l_parmeta )
        {
           
$l_parmeta['group'] = $parname; // inject group into meta
           
autoform_set_param_from_request( $l_parname, $l_parmeta, $Obj, $set_type, $set_target, $set_value );
        }
        return;
    }

   
// set input group
   
if( isset( $parmeta['group'] ) )
    {
       
$parname = $parmeta['group'].$parname;
    }

   
$l_param_type = 'string';
   
$l_param_default = '';
    if( isset(
$parmeta['type']) )
    {
        if(
substr($parmeta['type'], 0, 6) == 'select' && ! empty($parmeta['multiple']) )
        {
// a "multiple" select:
           
$l_param_type = 'array';
        }
        switch(
$parmeta['type'] )
        {
            case
'array':
            case
'array:integer':
            case
'array:array:integer':
            case
'array:string':
            case
'array:array:string':
            case
'array:regexp':
               
// this settings has a type
               
$l_param_type = $parmeta['type'];
                break;

            case
'checkbox':
               
$l_param_type = 'integer';
               
$l_param_default = 0;
                break;

            case
'checklist':
               
$l_param_type = 'array';
               
$l_param_default = array();
                break;

            case
'textarea':
               
$l_param_type = 'text';
                break;

            case
'html_input':
            case
'html_textarea':
               
$l_param_type = 'html';
                break;

            default:
        }
    }

    if(
$set_value === NULL )
    {
// Get the value from request:
       
$l_value = param( $Obj->get_param_prefix().$parname, $l_param_type, $l_param_default );
       
// Store values of unchecked checkboxes manually because their empty values are not passed to a submitted form:
       
switch( $l_param_type )
        {
            case
'array':
            case
'array:integer':
            case
'array:array:integer':
            case
'array:string':
            case
'array:array:string':
            case
'array:regexp':
                if( ! empty(
$parmeta['entries'] ) )
                {
                    foreach(
$l_value as $l_index => $l_index_values )
                    {
                        if(
count( $parmeta['entries'] ) != count( $l_index_values ) )
                        {    
// If some entry(like checkbox) is missed:
                           
foreach( $parmeta['entries'] as $parmeta_entry_key => $parmeta_entry_data )
                            {
                                if( isset(
$parmeta_entry_data['type'] ) &&
                                   
$parmeta_entry_data['type'] == 'checkbox' &&
                                    ! isset(
$l_index_values[ $parmeta_entry_key ] ) )
                                {    
// If field is checkbox but value is not passed to a submitted form becauase it was unchecked:
                                   
$l_value[ $l_index ][ $parmeta_entry_key ] = 0;
                                }
                            }
                        }
                    }
                }
                break;
        }
    }
    else
    {
// Force value
       
$l_value = $set_value;
    }

    if( isset(
$parmeta['type'] ) )
    {    
// Prepare values before store them in DB:
       
if( strpos( $parmeta['type'], 'array' ) === 0 )
        {    
// Make keys (__key__) in arrays unique and remove them
           
handle_array_keys_in_plugin_settings( $l_value );
        }
        elseif(
$parmeta['type'] == 'integer' )
        {    
// Convert to correct integer value:
           
if( $l_value !== '' )
            {    
// Don't convert empty string '' to integer 0:
               
$l_value = intval( $l_value );
            }
            if( empty(
$l_value ) && ! empty( $parmeta['allow_empty'] ) &&
                    isset(
$parmeta['valid_range'], $parmeta['valid_range']['min'] ) && $parmeta['valid_range']['min'] > 0 )
            {    
// Convert 0 to empty value for integer field if it allows empty values:
               
$l_value = NULL;
            }
        }
    }

    if( !
autoform_validate_param_value( $Obj->get_param_prefix().$parname, $l_value, $parmeta ) )
    {
        return;
    }

   
// Validate form values:
   
switch( $set_type )
    {
        case
'CollSettings':
           
$error_value = NULL;
           
$Obj->set_coll_setting( $parname, $l_value );
            break;

        case
'Skin':
           
$error_value = NULL;
           
$Obj->set_setting( $parname, $l_value );
            break;

        case
'Widget':
           
$error_value = NULL;
            if( isset(
$parmeta['type'] ) && $parmeta['type'] == 'checklist' && $parname == 'renderers' )
            {    
// Save "stealth" and "always" plugin render options:
                // (they are hidden or disabled checkboxes of the form and cannot be submitted automatically)
               
global $Plugins;
               
$widget_Blog = & $Obj->get_Blog();
               
$l_value = $Plugins->validate_renderer_list( array_keys( $l_value ), array(
                       
'Blog'         => & $widget_Blog,
                       
'setting_name' => 'shared_apply_rendering',
                    ) );
               
$l_value = array_fill_keys( $l_value, 1 );
            }
           
$Obj->set( $parname, $l_value, false, ( isset( $parmeta['group'] ) ? $parmeta['group'] : NULL ) );
            break;

        case
'UserSettings':
           
// Plugin User settings:
           
$dummy = array(
               
'name' => $parname,
               
'value' => & $l_value,
               
'meta' => $parmeta,
               
'User' => $set_target,
               
'action' => 'set' );
           
$error_value = $Obj->PluginUserSettingsValidateSet( $dummy );
           
// Update the param value, because a plugin might have changed it (through reference):
           
$GLOBALS[ $Obj->get_param_prefix().$parname ] = $l_value;

            if( empty(
$error_value ) )
            {
               
$Obj->UserSettings->set( $parname, $l_value, $set_target->ID );
            }
            break;

        case
'GroupSettings':
           
// Plugin Group settings:
           
$dummy = array(
               
'name'   => $parname,
               
'value'  => & $l_value,
               
'meta'   => $parmeta,
               
'Group'  => $set_target,
               
'action' => 'set' );
           
$error_value = $Obj->PluginGroupSettingsValidateSet( $dummy );
           
// Update the param value, because a plugin might have changed it (through reference):
           
$GLOBALS[ $Obj->get_param_prefix().$parname ] = $l_value;

            if( empty(
$error_value ) )
            {
               
$Obj->GroupSettings->set( $parname, $l_value, $set_target->ID );
            }
            break;

        case
'Settings':
           
// Plugin global settings:
       
case 'MsgSettings':
           
// Plugin messages settings:
       
case 'EmailSettings':
           
// Plugin emails settings:
       
case 'SharedSettings':
           
// Plugin shared settings:
           
$dummy = array(
               
'name'   => $parname,
               
'value'  => & $l_value,
               
'meta'   => $parmeta,
               
'action' => 'set' );
           
$error_value = $Obj->PluginSettingsValidateSet( $dummy );
           
// Update the param value, because a plugin might have changed it (through reference):
           
$GLOBALS[ $Obj->get_param_prefix().$parname ] = $l_value;

            if( empty(
$error_value ) )
            {    
// Set new value:
               
if( $set_type == 'MsgSettings' && $parname != 'msg_apply_rendering' )
                {    
// Use prefix 'msg_' for all message settings except of "msg_apply_rendering":
                   
$Obj->Settings->set( 'msg_'.$parname, $l_value );
                }
                elseif(
$set_type == 'EmailSettings' && $parname != 'email_apply_rendering' )
                {    
// Use prefix 'email_' for all email settings except of "email_apply_rendering":
                   
$Obj->Settings->set( 'email_'.$parname, $l_value );
                }
                elseif(
$set_type == 'SharedSettings' && $parname != 'shared_apply_rendering' )
                {    
// Use prefix 'shared_' for all shared settings except of "shared_apply_rendering":
                   
$Obj->Settings->set( 'shared_'.$parname, $l_value );
                }
                else
                {    
// Global settings:
                   
$Obj->Settings->set( $parname, $l_value );
                }
            }
            break;

        default:
           
debug_die( "unhandled set_type $set_type" );
            break;
    }

    if(
$error_value )
    {
// A validation error has occured, record error message:
       
param_error( $Obj->get_param_prefix().$parname, $error_value );
    }
}


/**
 * Validates settings according to their meta info recursively.
 *
 * @todo Init "checkbox" values in "array" type settings (they do not get send) (dh)
 * @param string Param name
 * @param array Meta info
 * @return boolean
 */
function autoform_validate_param_value( $param_name, $value, $meta )
{
    global
$Messages;

    if( !
is_array( $meta ) )
    {    
// Must be array:
       
return;
    }

    if(
is_array( $value ) && isset( $meta['entries'] ) )
    {
       
$r = true;
        if( isset(
$meta['key'] ) )
        {
// validate keys:
           
foreach( array_keys( $value ) as $k )
            {
                if( !
autoform_validate_param_value( $param_name.'['.$k.'][__key__]', $k, $meta['key'] ) )
                {
                   
$r = false;
                }
            }
        }

       
// Check max_count/min_count
        // dh> TODO: find a way to link it to the form's fieldset (and add an "error" class to it)
       
if( isset( $meta['max_count'] ) && count( $value ) > $meta['max_count'] )
        {
           
$r = false;
           
$label = isset( $meta['label'] ) ? $meta['label'] : $param_name;
           
$Messages->add( sprintf( T_('Too many entries in the "%s" set. It must have %d at most.'), $label, $meta['max_count'] ), 'error' );
        }
        elseif( isset(
$meta['min_count'] ) && count( $value ) < $meta['min_count'] )
        {
           
$r = false;
           
$label = isset( $meta['label'] ) ? $meta['label'] : $param_name;
           
$Messages->add( sprintf( T_('Too few entries in the "%s" set. It must have %d at least.'), $label, $meta['min_count'] ), 'error' );
        }

        foreach(
$meta['entries'] as $mk => $mv )
        {
            foreach(
$value as $vk => $vv )
            {
                if( isset(
$mv['type'] ) && $mv['type'] == 'input_group' &&
                    ! empty(
$mv['inputs'] ) && is_array( $mv['inputs'] ) )
                {    
// Validate each input field of the setting with type "input_group":
                   
foreach( $mv['inputs'] as $k => $v )
                    {
                        if( isset(
$vv[ $mk.$k ] ) && ! autoform_validate_param_value( $param_name.'['.$vk.']['.$mk.$k.']', $vv[ $mk.$k ], $v ) )
                        {    
// If at least one setting of the grouped inputs has an error:
                           
$r = false;
                        }
                    }
                }
                elseif( isset(
$vv[ $mk ] ) && ! autoform_validate_param_value( $param_name.'['.$vk.']['.$mk.']', $vv[ $mk ], $mv ) )
                {    
// If single setting has an error:
                   
$r = false;
                }
            }
        }
        return
$r;
    }


    if( isset(
$meta['type'] ) )
    {
        if( isset(
$meta['allow_empty'] ) && $meta['allow_empty'] && empty( $value ) )
        {
// Allow an empty value
           
return true;
        }

        switch(
$meta['type'] )
        {
            case
'text':
                if( isset(
$meta['allow_empty'] ) && ! $meta['allow_empty'] && $value === '' )
                {    
// Display error if the text field is required to be not empty:
                   
param_error( $param_name, sprintf( T_('The field &laquo;%s&raquo; cannot be empty.'), $meta['label'] ), T_('This field cannot be empty.') );
                    return
false;
                }
                break;

            case
'integer':
                if( !
preg_match( '~^[-+]?\d+$~', $value ) )
                {
                   
param_error( $param_name, sprintf( T_('The value for &laquo;%s&raquo; must be numeric.'), $meta['label'] ), T_('The value must be numeric.') );
                    return
false;
                }
                break;

            case
'float':
                if( !
preg_match( '~^[-+]?\d+(\.\d+)?$~', $value ) )
                {
                   
param_error( $param_name, sprintf( T_('The value for &laquo;%s&raquo; must be numeric.'), $meta['label'] ), T_('The value must be numeric.') );
                    return
false;
                }
                break;

            case
'radio':
               
$check_value = false;
                foreach(
$meta['options'] as $arr)
                {
                    if( !
is_array($arr) )
                    {
                       
param_error( $param_name, sprintf( T_('Invalid option &laquo;%s&raquo;.'), $arr ) );
                        return
false;
                    }
                    if(
$value == $arr[0] )
                    {
                       
$check_value = true;
                        break;
                    }
                }
                if ( !
$check_value )
                {
                   
param_error( $param_name, sprintf( T_('Invalid option &laquo;%s&raquo;.'), $value ) );
                    return
false;
                }
                break;

            case
'select':
               
$check_options = $value;
                if( !
is_array($check_options) )
                {
// no "multiple" select:
                   
$check_options = array($check_options);
                }

               
// Get all possible values for the select element:
               
$meta_options = array();
                foreach(
$meta['options'] as $meta_option_key => $meta_option_value )
                {
                    if(
is_array( $meta_option_value ) )
                    {    
// It is a grouped options:
                       
foreach( $meta_option_value as $meta_group_option_key => $meta_group_option_value )
                        {
                           
$meta_options[] = $meta_group_option_key;
                        }
                    }
                    else
                    {    
// Single option:
                       
$meta_options[] = $meta_option_key;
                    }
                }

               
// Check if the selected values can be used for the select element:
               
foreach( $check_options as $v )
                {
                    if( empty(
$v ) && ! empty( $meta['allow_none'] ) )
                    {
// empty is ok:
                       
continue;
                    }
                    if( !
in_array( $v, $meta_options ) )
                    {
                       
param_error( $param_name, sprintf( T_('Invalid option &laquo;%s&raquo;.'), $v ) );
                        return
false;
                    }
                }
                break;

            case
'select_blog':
            case
'select_group':
            case
'select_user':
                if(
is_array($value) && empty($value) // empty "multiple" select
                   
|| ( ! is_array($value) && ! strlen($value) ) )
                {
                    if( empty(
$meta['allow_none']) )
                    {
// empty is not ok
                       
param_error( $param_name, sprintf( T_('Invalid option &laquo;%s&raquo;.'), $value ) );
                        return
false;
                    }
                }
                else
                {
// Try retrieving the value from the corresponding Cache:
                   
switch( $meta['type'] )
                    {
                        case
'select_blog':
                           
$Cache = & get_BlogCache();
                            break;

                        case
'select_group':
                           
$Cache = & get_GroupCache();
                            break;

                        case
'select_user':
                           
$Cache = & get_UserCache();
                            break;
                    }

                   
$check_options = $value;
                    if( !
is_array($check_options) )
                    {
// no "multiple" select:
                       
$check_options = array($check_options);
                    }

                    foreach(
$check_options as $v)
                    {
                        if( empty(
$v) && ! empty($meta['allow_none']) )
                        {
// empty is ok:
                           
continue;
                        }
                        if( !
$Cache->get_by_ID($v, false, false) )
                        {
                           
param_error( $param_name, sprintf( T_('Invalid option &laquo;%s&raquo;.'), $v ) );
                            return
false;
                        }
                    }
                }
                break;

            case
'color':
                if( !
is_color( $value ) )
                {
                   
param_error( $param_name, sprintf(  T_('Invalid color code for &laquo;%s&raquo;.'), $meta['label'] ) );
                    return
false;
                }
                break;
        }
    }

   
// Check maxlength:
   
if( isset($meta['maxlength']) )
    {
        if(
utf8_strlen($value) > $meta['maxlength'] )
        {
           
param_error( $param_name, sprintf( T_('The value is too long.'), $value ) );
        }
    }

   
// Check valid pattern:
   
if( isset( $meta['valid_pattern'] ) )
    {
       
$param_pattern = is_array( $meta['valid_pattern'] ) ? $meta['valid_pattern']['pattern'] : $meta['valid_pattern'];
        if( !
preg_match( $param_pattern, $value ) )
        {
           
$param_error = is_array( $meta['valid_pattern'] ) ? $meta['valid_pattern']['error'] : sprintf( T_('The value is invalid. It must match the regular expression &laquo;%s&raquo;.'), $param_pattern );
           
param_error( $param_name, $param_error );
            return
false;
        }
    }

   
// Check valid range:
   
if( isset($meta['valid_range']) )
    {
       
// Transform numeric indexes into associative keys:
       
if( ! isset($meta['valid_range']['min'], $meta['valid_range']['max'])
            && isset(
$meta['valid_range'][0], $meta['valid_range'][1]) )
        {
           
$meta['valid_range']['min'] = $meta['valid_range'][0];
           
$meta['valid_range']['max'] = $meta['valid_range'][1];
        }
        if( isset(
$meta['valid_range'][2]) && ! isset($meta['valid_range']['error']) )
        {
           
$meta['valid_range']['error'] = $meta['valid_range'][2];
        }

        if( ( isset(
$meta['valid_range']['min'] ) && $value < $meta['valid_range']['min'] )
                || ( isset(
$meta['valid_range']['max'] ) && $value > $meta['valid_range']['max'] ) )
        {
            if( isset(
$meta['valid_range']['error'] ) )
            {
               
$param_error = $meta['valid_range']['error'];
            }
            else
            {
                if( isset(
$meta['valid_range']['min'] ) && isset( $meta['valid_range']['max'] ) )
                {
                   
$param_error = sprintf( T_('The value is invalid. It must be in the range from %s to %s.'), $meta['valid_range']['min'], $meta['valid_range']['max'] );
                }
                elseif( isset(
$meta['valid_range']['max'] ) )
                {
                   
$param_error = sprintf( T_('The value is invalid. It must be smaller than or equal to %s.'), $meta['valid_range']['max'] );
                }
                else
                {
                   
$param_error = sprintf( T_('The value is invalid. It must be greater than or equal to %s.'), $meta['valid_range']['min'] );
                }
            }

           
param_error( $param_name, $param_error );
            return
false;
        }
    }

    return
true;
}


/**
 * This handles the special "__key__" index in all array type values
 * in the given array. It makes sure, that "__key__" is unique and
 * replaces the original key of the value with it.
 * @param array (by reference)
 */
function handle_array_keys_in_plugin_settings( & $a )
{
    if( !
is_array( $a ) )
    {
        return;
    }

   
$new_arr = array(); // use a new array to maintain order, also for "numeric" keys

   
foreach( array_keys( $a ) as $k )
    {
       
$v = & $a[$k];

        if(
is_array( $v ) && isset( $v['__key__'] ) )
        {
            if(
$k != $v['__key__'] )
            {
               
$k = $v['__key__'];
                if( !
strlen( $k ) || isset($a[ $k ]) )
                {
// key already exists (or is empty):
                   
$c = 1;

                    while( isset(
$a[ $k.'_'.$c ] ) )
                    {
                       
$c++;
                    }
                   
$k = $k.'_'.$c;
                }
            }
            unset(
$v['__key__'] );

           
$new_arr[$k] = $v;
        }
        else
        {
           
$new_arr[$k] = $v;
        }

       
// Recurse:
       
foreach( array_keys( $v ) as $rk )
        {
            if(
is_array( $v[$rk] ) )
            {
               
handle_array_keys_in_plugin_settings($v[$rk]);
            }
        }
    }
   
$a = $new_arr;
}


/**
 * Helper function to do the action part of DB schema upgrades for "enable" and "install"
 * actions.
 *
 * @param object Plugin
 * @param boolean Force install DB for the plugin (used in installation of b2evo)
 * @return boolean True, if no changes needed or done; false if we should break out to display "install_db_schema" action payload.
 */
function install_plugin_db_schema_action( & $Plugin, $force_install_db_deltas = false )
{
    global
$inc_path, $install_db_deltas, $DB, $Messages;

   
// Prepare vars for DB layout changes
   
$install_db_deltas_confirm_md5 = param( 'install_db_deltas_confirm_md5', 'string' );

   
$db_layout = $Plugin->GetDbLayout();
   
$install_db_deltas = array(); // This holds changes to make, if any (just all queries)
    //pre_dump( $db_layout );

   
if( ! empty( $db_layout ) )
    {
// The plugin has a DB layout attached
       
load_funcs('_core/model/db/_upgrade.funcs.php');

       
// Get the queries to make:
       
foreach( db_delta( $db_layout ) as $table => $queries )
        {
            foreach(
$queries as $query_info )
            {
                foreach(
$query_info['queries'] as $query )
                {
// subqueries for this query (usually one, but may include required other queries)
                   
$install_db_deltas[] = $query;
                }
            }
        }

        if( ! empty(
$install_db_deltas ) )
        {
// delta queries to make
           
if( empty( $install_db_deltas_confirm_md5 ) && !$force_install_db_deltas )
            {
// delta queries have to be confirmed in payload
               
return false;
            }
            elseif(
$install_db_deltas_confirm_md5 == md5( implode( '', $install_db_deltas ) ) || $force_install_db_deltas )
            {
// Confirmed in first step:
               
foreach( $install_db_deltas as $query )
                {
                   
$DB->query( $query );
                }

               
$Messages->add( T_('The database has been updated.'), 'success' );
            }
            else
            {
// should not happen
               
$Messages->add( T_('The DB schema has been changed since confirmation.'), 'error' );

               
// delta queries have to be confirmed (again) in payload
               
return false;
            }
        }
    }

    return
true;
}

?>