<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  ocProducts Ltd
 * @package    core_form_interfaces
 */

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__form_templates()
{
    require_javascript('checking');
    require_javascript('editing');

    global $WYSIWYG_ATTACHED;
    $WYSIWYG_ATTACHED = false;

    global $BLOCK_EXTRA_POSTING_FIELDS;
    $BLOCK_EXTRA_POSTING_FIELDS = false; // Used to signal that there's a main posting form, so we can't allow extra custom posting form fields

    global $TABINDEX;
    $TABINDEX = 50; // Base

    global $NO_DEV_MODE_FULLSTOP_CHECK;
    $NO_DEV_MODE_FULLSTOP_CHECK = false;

    require_code('input_filter');

    global $DOING_ALTERNATE_FIELDS_SET;
    $DOING_ALTERNATE_FIELDS_SET = mixed();

    global $SKIPPING_LABELS;
    $SKIPPING_LABELS = false;

    require_css('forms');

    if (function_exists('get_member')) {
        if ((has_privilege(get_member(), 'allow_html')) && (get_value('edit_with_my_comcode_perms') === '1') && ((strpos(get_param_string('type', ''), 'edit') !== false) || (strpos(get_param_string('type', ''), 'edit_category') !== false))) {
            attach_message('You have enabled content editing to assume your permissions via a hidden option. Be VERY careful to check whatever Comcode/HTML you edit.', 'warn');
        }
    }

    set_no_clickjacking_csp();
}

/**
 * Read a multi code from a named parameter stub.
 *
 * @param  ID_TEXT $param The parameter stub (stub of a series of POST parameters, made by cns_get_forum_multi_code_field's field or similar).
 * @return SHORT_TEXT The multi code.
 */
function read_multi_code($param)
{
    $type = post_param_string($param);
    if ($type == '*') {
        return $type;
    }
    if (!array_key_exists($param . '_list', $_POST)) {
        return '';
    }
    $in = implode(',', $_POST[$param . '_list']);
    return $type . $in;
}

/**
 * Ensure Suhosin is not going to break a request due to number of request form fields. Call this each time a field is added to the output.
 *
 * @param  integer $inc How much to increment the counter by
 * @param  integer $name_length The name length being checked
 */
function check_suhosin_request_quantity($inc = 1, $name_length = 0)
{
    static $count = 0;
    $count += $inc;

    static $failed_already = false;
    if ($failed_already) {
        return;
    }

    static $max_values = null;
    if ($max_values === null) {
        $max_values = array();
        foreach (array('max_input_vars', 'suhosin.post.max_vars', 'suhosin.request.max_vars') as $setting) {
            if (is_numeric(ini_get($setting))) {
                $max_values[$setting] = intval(ini_get($setting));
            }
        }
    }
    foreach ($max_values as $setting => $max_value) {
        if ($max_value < $count) {
            global $MODSECURITY_WORKAROUND_ENABLED;
            if (!$MODSECURITY_WORKAROUND_ENABLED) {
                attach_message(do_lang_tempcode('SUHOSIN_MAX_VARS_TOO_LOW', escape_html($setting)), 'warn');
            }
            $failed_already = true;
        }
    }

    static $max_length_values = null;
    if ($max_length_values === null) {
        $max_length_values = array();
        foreach (array('suhosin.post.max_name_length', 'suhosin.request.max_name_length', 'suhosin.post.max_totalname_length', 'suhosin.request.max_totalname_length') as $setting) {
            if (is_numeric(ini_get($setting))) {
                $max_length_values[$setting] = intval(ini_get($setting));
            }
        }
    }
    foreach ($max_length_values as $setting => $max_length_value) {
        if ($max_length_value < $name_length) {
            attach_message(do_lang_tempcode('SUHOSIN_MAX_VARS_TOO_LOW', escape_html($setting)), 'warn');
            $failed_already = true;
        }
    }
}

/**
 * Ensure Suhosin is not going to break a request due to request size.
 *
 * @param  integer $size Most determinitve size within wider request size (we'll assume we actually need 500 more bytes than this)
 */
function check_suhosin_request_size($size)
{
    foreach (array('suhosin.request.max_value_length', 'suhosin.post.max_value_length') as $setting) {
        if ((is_numeric(ini_get($setting))) && (intval(ini_get($setting)) - 500 < $size)) {
            attach_message(do_lang_tempcode('SUHOSIN_MAX_VALUE_TOO_SHORT', escape_html($setting)), 'warn');
        }
    }
}

/**
 * Enable reading in default parameters from the GET environment. This is typically called before 'add' forms.
 */
function url_default_parameters__enable()
{
    global $URL_DEFAULT_PARAMETERS_ENABLED;
    $URL_DEFAULT_PARAMETERS_ENABLED = true;
}

/**
 * Disable reading in default parameters from the GET environment. This is typically called after 'add' forms.
 */
function url_default_parameters__disable()
{
    global $URL_DEFAULT_PARAMETERS_ENABLED;
    $URL_DEFAULT_PARAMETERS_ENABLED = false;
}

/**
 * Find a default property, defaulting to the average of what is there already, or the given default if really necessary.
 *
 * @param  ?integer $setting The current setting (null: we have to work it out); if non-null, the function will immediately return
 * @param  ID_TEXT $db_property The property
 * @param  ID_TEXT $table The table to average within
 * @param  integer $default The last-resort default
 * @return integer The value
 */
function take_param_int_modeavg($setting, $db_property, $table, $default)
{
    if (!is_null($setting)) {
        return $setting;
    }

    if (running_script('install')) {
        return $default;
    }

    $db = $GLOBALS[(substr($table, 0, 2) == 'f_') ? 'FORUM_DB' : 'SITE_DB'];
    $val = $db->query_value_if_there('SELECT ' . $db_property . ',count(' . $db_property . ') AS qty FROM ' . $db->get_table_prefix() . $table . ' GROUP BY ' . $db_property . ' ORDER BY qty DESC', false, true); // We need the mode here, not the mean
    if (!is_null($val)) {
        return $val;
    }

    return $default;
}

/**
 * Attach the WYSIWYG editor.
 */
function attach_wysiwyg()
{
    global $WYSIWYG_ATTACHED;
    if (!$WYSIWYG_ATTACHED) {
        require_code('site');
        attach_to_javascript(do_template('WYSIWYG_LOAD'));
    }
    $WYSIWYG_ATTACHED = true;
}

/**
 * Insert hidden data for the maximum file size of form fields.
 *
 * @param  Tempcode $hidden Hidden fields
 * @param  ID_TEXT $regular_max_size_type Code representing the media types we are using limits for
 * @set image file
 */
function handle_max_file_size(&$hidden, $regular_max_size_type = 'file')
{
    require_code('files2');
    if (!$GLOBALS['FORUM_DRIVER']->is_staff(get_member())) {
        switch ($regular_max_size_type) {
            case 'image':
                require_code('images');
                $regular_max_size = get_max_image_size();
                break;
            case 'file':
            default:
                $regular_max_size = get_max_file_size();
                break;
        }
        $hidden->attach(form_input_hidden('MAX_FILE_SIZE', strval($regular_max_size)));
    } else {
        $hidden->attach(form_input_hidden('MAX_FILE_SIZE', strval(get_max_file_size())));
    }
}

/**
 * Get what we need to get attachments in a form-field interface.
 *
 * @param  ID_TEXT $posting_field_name The name of the field attachments are for
 * @return array A pair: the attachments UI (Tempcode), the hidden attachment field
 */
function get_attachments($posting_field_name)
{
    $image_types = str_replace(',', ', ', get_option('valid_images'));

    require_lang('comcode');
    require_javascript('plupload');
    require_css('widget_plupload');
    require_javascript('editing');
    require_javascript('checking');
    require_javascript('posting');

    require_code('upload_syndication');
    list($syndication_json, $filter) = get_upload_syndication_json(CMS_UPLOAD_ANYTHING);

    if (get_forum_type() == 'cns') {
        require_code('cns_groups');
        require_lang('cns');
        $max_attachments = cns_get_member_best_group_property(get_member(), 'max_attachments_per_post');
    } else {
        $max_attachments = 100;
    }
    if ($max_attachments == 0) {
        return array(new Tempcode(), new Tempcode());
    }

    require_code('files2');
    $max_attach_size = get_max_file_size(is_null($syndication_json) ? get_member() : null, $GLOBALS['SITE_DB']);
    $no_quota = ((get_forum_type() == 'cns') && (cns_get_member_best_group_property(get_member(), 'max_daily_upload_mb') == 0));
    if ($no_quota) {
        if (is_null($syndication_json)) {
            return array(new Tempcode(), new Tempcode());
        }
    } else {
        $filter = mixed();
    }
    $attach_size_field = form_input_hidden('MAX_FILE_SIZE', strval($max_attach_size));

    $num_attachments = post_param_integer('num_attachments', has_js() ? 1 : 3);

    $attachments = new Tempcode();
    for ($i = 1; $i <= $num_attachments; $i++) {
        $attachments->attach(do_template('ATTACHMENT', array(
            '_GUID' => 'c3b38ca70cbd1c5f9cf91bcae9ed1134',
            'POSTING_FIELD_NAME' => $posting_field_name,
            'I' => strval($i),
            'SYNDICATION_JSON' => $syndication_json,
            'NO_QUOTA' => $no_quota,
            'FILTER' => $filter,
        )));
    }

    $attachment_template = do_template('ATTACHMENT', array(
        '_GUID' => 'c3b38ca70cbd1c5f9cf91bcae9ed11dsds',
        'POSTING_FIELD_NAME' => $posting_field_name,
        'I' => '__num_attachments__',
        'SYNDICATION_JSON' => $syndication_json,
        'NO_QUOTA' => $no_quota,
        'FILTER' => $filter,
    ));
    $attachments = do_template('ATTACHMENTS', array(
        '_GUID' => '054921e7c09412be479676759accf222',
        'POSTING_FIELD_NAME' => $posting_field_name,
        'ATTACHMENT_TEMPLATE' => $attachment_template,
        'IMAGE_TYPES' => $image_types,
        'ATTACHMENTS' => $attachments,
        'MAX_ATTACHMENTS' => strval($max_attachments),
        'NUM_ATTACHMENTS' => strval($num_attachments),
        'FILTER' => $filter,
    ));

    return array($attachments, $attach_size_field);
}

/**
 * Creates a posting form, with attachment support.
 *
 * @param  mixed $submit_name The title of the form submission button
 * @param  ID_TEXT $submit_icon The submit icon CSS class.
 * @param  LONG_TEXT $post The default post to put in.
 * @param  mixed $post_url Where the form is sent (URLPATH or Tempcode).
 * @param  Tempcode $hidden_fields A form_input_hidden buildup of hidden fields (additional parameters sent to the target URL).
 * @param  Tempcode $specialisation A buildup of leading extra fields, in a format compatible with the templates used by this function.
 * @param  ?mixed $post_comment The post comment (string or Tempcode). This gives information about how you should post. (null: no post comment)
 * @param  string $extra Extra info to put on the posting form
 * @param  ?Tempcode $specialisation2 A buildup of trailing extra fields, in a format compatible with the templates used by this function. (null: none)
 * @param  ?Tempcode $default_parsed The parsed Comcode. (null: calculate)
 * @param  ?string $javascript JavaScript code to include (null: none)
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $required Whether this is a required input field
 * @param  boolean $has_preview Whether the form supports previewing
 * @param  boolean $avoid_wysiwyg Whether to avoid WYSIWYG mode
 * @param  boolean $support_autosave Whether to support auto-save
 * @param  boolean $specialisation2_hidden Whether to support auto-save
 * @param  mixed $description A description for this input field
 * @return Tempcode The posting form
 */
function get_posting_form($submit_name, $submit_icon, $post, $post_url, $hidden_fields, $specialisation, $post_comment = null, $extra = '', $specialisation2 = null, $default_parsed = null, $javascript = null, $tabindex = null, $required = true, $has_preview = true, $avoid_wysiwyg = false, $support_autosave = true, $specialisation2_hidden = false, $description = '')
{
    require_javascript('posting');
    require_javascript('ajax');
    require_javascript('plupload');
    require_css('widget_plupload');

    require_lang('comcode');

    $tabindex = get_form_field_tabindex($tabindex);

    $post = filter_form_field_default(is_object($submit_name) ? $submit_name->evaluate() : $submit_name, $post);

    $required = filter_form_field_required('post', $required);

    check_suhosin_request_size(strlen($post));

    if (is_null($post_comment)) {
        $post_comment = do_lang_tempcode('POST_COMMENT');
    }
    if (is_null($specialisation2)) {
        $specialisation2 = new Tempcode();
    }

    list($attachments, $attach_size_field) = get_attachments('post');

    $hidden_fields->attach($attach_size_field);

    $continue_url = get_self_url();

    $help_zone = get_comcode_zone('userguide_comcode', false);

    $emoticon_chooser = $GLOBALS['FORUM_DRIVER']->get_emoticon_chooser();

    $comcode_editor = get_comcode_editor('post', false, true);
    $comcode_editor_small = get_comcode_editor('post', true, true);

    $w = (!$avoid_wysiwyg) && (has_js()) && (browser_matches('wysiwyg', $post) && (strpos($post, '{$,page hint: no_wysiwyg}') === false));

    $class = '';
    attach_wysiwyg();
    if ($w) {
        $class .= ' wysiwyg';
    }

    global $LAX_COMCODE;
    $temp = $LAX_COMCODE;
    $LAX_COMCODE = true;
    $GLOBALS['COMCODE_PARSE_URLS_CHECKED'] = 100; // Little hack to stop it checking any URLs
    /*Actually we reparse always to ensure it is done in semiparse mode if (is_null($default_parsed)) */
    $default_parsed = @comcode_to_tempcode($post, null, false, null, null, null, true);
    $LAX_COMCODE = $temp;

    global $MODSECURITY_WORKAROUND_ENABLED;

    return do_template('POSTING_FORM', array(
        '_GUID' => '41259424ca13c437d5bc523ce18980fe',
        'REQUIRED' => $required,
        'TABINDEX_PF' => strval($tabindex)/*not called TABINDEX due to conflict with FORM_STANDARD_END*/,
        'JAVASCRIPT' => $javascript,
        'PREVIEW' => $has_preview,
        'COMCODE_EDITOR' => $comcode_editor,
        'COMCODE_EDITOR_SMALL' => $comcode_editor_small,
        'CLASS' => $class,
        'COMCODE_URL' => is_null($help_zone) ? new Tempcode() : build_url(array('page' => 'userguide_comcode'), $help_zone),
        'EXTRA' => $extra,
        'POST_COMMENT' => $post_comment,
        'EMOTICON_CHOOSER' => $emoticon_chooser,
        'SUBMIT_ICON' => $submit_icon,
        'SUBMIT_NAME' => $submit_name,
        'HIDDEN_FIELDS' => $hidden_fields,
        'URL' => $post_url,
        'POST' => $post,
        'DEFAULT_PARSED' => $default_parsed,
        'CONTINUE_URL' => $continue_url,
        'ATTACHMENTS' => $attachments,
        'SPECIALISATION' => $specialisation,
        'SPECIALISATION2' => $specialisation2,
        'SPECIALISATION2_HIDDEN' => $specialisation2_hidden,
        'SUPPORT_AUTOSAVE' => $support_autosave,
        'DESCRIPTION' => $description,
        'MODSECURITY_WORKAROUND' => $MODSECURITY_WORKAROUND_ENABLED,
    ));
}

/**
 * Creates a Comcode editor.
 *
 * @param  string $field_name The name of the field the editor is working for
 * @param  boolean $cut_down Whether to make a cut-down version
 * @param  boolean $is_posting_field Whether this is for a posting field (i.e. has attachment support)
 * @return Tempcode The Comcode editor
 */
function get_comcode_editor($field_name = 'post', $cut_down = false, $is_posting_field = false)
{
    require_lang('comcode');

    $buttons = new Tempcode();
    $_buttons = array();

    // Non-wrappers
    if (!$cut_down) {
        $_buttons[] = (function_exists('imagepng') ? 'thumb' : 'img');
    }
    if (has_privilege(get_member(), 'comcode_dangerous')) {
        $_buttons[] = 'block';
    }
    $_buttons[] = 'comcode';
    if (!$cut_down) {
        $_buttons[] = 'list'; // NB: list isn't actually a comcode tag, it's a textcode syntax
    }

    // Links
    if (!$cut_down) {
        $_buttons[] = 'url';
    }
    if (has_zone_access(get_member(), 'adminzone')) {
        $_buttons[] = 'page';
    }
    //if (!$cut_down) $_buttons[]='email';   Not enough space anymore

    // Wrappers
    $_buttons[] = 'quote';
    if ((get_option('simplify_wysiwyg_by_permissions') == '0') || (has_privilege(get_member(), 'allow_html'))) {
        $_buttons[] = 'box';
    }
    $_buttons[] = 'code';
    if (has_privilege(get_member(), 'allow_html')) {
        if (!$cut_down) {
            $_buttons[] = 'html';
        }
    }
    foreach ($_buttons as $i => $button) {
        $divider = false;
        if (($button == 'url') || ($button == 'quote') || ($i == 0)) {
            $divider = true;
        }
        $buttons->attach(do_template('COMCODE_EDITOR_BUTTON', array(
            '_GUID' => 'e4fe3bc16cec070e06532fedc598d075',
            'DIVIDER' => $divider,
            'FIELD_NAME' => $field_name,
            'TITLE' => do_lang_tempcode('INPUT_COMCODE_' . $button),
            'B' => $button,
            'IS_POSTING_FIELD' => $is_posting_field,
        )));
    }

    $micro_buttons = new Tempcode();
    if (!$cut_down) {
        $_micro_buttons = array(
            array('t' => 'b'),
            array('t' => 'i'),
        );
        foreach ($_micro_buttons as $button) {
            $micro_buttons->attach(do_template('COMCODE_EDITOR_MICRO_BUTTON', array(
                '_GUID' => 'dbab001b3fa5480bb590ffed3ca81eaf',
                'FIELD_NAME' => $field_name,
                'TITLE' => do_lang_tempcode('INPUT_COMCODE_' . $button['t']),
                'B' => $button['t'],
                'IS_POSTING_FIELD' => $is_posting_field,
            )));
        }
    }
    return do_template('COMCODE_EDITOR', array(
        '_GUID' => 'ebff3145776a0441d115f2e4e13617d6',
        'POSTING_FIELD' => $field_name,
        'BUTTONS' => $buttons,
        'MICRO_BUTTONS' => $micro_buttons,
        'IS_POSTING_FIELD' => $is_posting_field,
    ));
}

/**
 * Find whether WYSIWYG is currently on.
 *
 * @param  ?string $default Comcode that might be WYSIWYG edited (null: none)
 * @return boolean Whether it is
 */
function wysiwyg_on($default = null)
{
    return ((browser_matches('wysiwyg', $default)) && ((!array_key_exists('use_wysiwyg', $_COOKIE)) || ($_COOKIE['use_wysiwyg'] != '0')));
}

/**
 * Find if a form field is required via fields.xml filters.
 *
 * @param  ID_TEXT $name The codename for this field
 * @param  boolean $required Whether it is required by default
 * @return boolean Whether it is required
 */
function filter_form_field_required($name, $required)
{
    if (!$required) {
        $minlength = get_field_restrict_property('minlength', $name);
        if ((!empty($minlength)) && (intval($minlength) > 0)) {
            $required = true;
        }
    }
    return $required;
}

/**
 * Get the value of a scoped field restriction property. Returns "first-found".
 *
 * @param  string $property The name of the property
 * @param  string $field The name of the field it's scoped for
 * @param  ?string $page The page name scoped for (null: current page)
 * @param  ?string $type The page type scoped for (null: current type)
 * @return ?string The property (null: non-existent)
 */
function get_field_restrict_property($property, $field, $page = null, $type = null)
{
    if (is_null($page)) {
        $page = get_page_name();
    }
    if (is_null($type)) {
        $type = get_param_string('type', post_param_string('type', 'browse'));
    }

    $restrictions = load_field_restrictions($page, $type);
    foreach ($restrictions as $_r => $_restrictions) {
        $_r_exp = explode(',', $_r);
        foreach ($_r_exp as $__r) {
            if (simulated_wildcard_match($field, trim($__r), true)) {
                foreach ($_restrictions as $bits) {
                    list($restriction, $attributes) = $bits;
                    if (strtolower($restriction) == strtolower($property)) {
                        return $bits[1]['embed'];
                    }
                }
            }
        }
    }
    return null;
}

/**
 * Get the Tempcode for a codename input line.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  ?integer $_maxlength The maximum length of the field (null: default 80)
 * @param  ?array $extra_chars List of extra characters to allow (null: none)
 * @param  ?string $placeholder The placeholder value for this input field (null: none)
 * @return Tempcode The input field
 */
function form_input_codename($pretty_name, $description, $name, $default, $required, $tabindex = null, $_maxlength = 40, $extra_chars = null, $placeholder = null)
{
    if (is_null($default)) {
        $default = '';
    }

    if (is_null($extra_chars)) {
        $extra_chars = array();
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $maxlength = get_field_restrict_property('maxlength', $name);
    if ((is_null($maxlength)) && (!is_null($_maxlength))) {
        $maxlength = strval($_maxlength);
    }
    $input = do_template('FORM_SCREEN_INPUT_CODENAME', array('_GUID' => '4b1a3a3ebe6ac85c7c14bcec9d8ab88d', 'PLACEHOLDER' => $placeholder, 'MAXLENGTH' => $maxlength, 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default, 'EXTRA_CHARS' => $extra_chars));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a text input line (an <input type="text" ... />).
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  ?integer $_maxlength The maximum length of the field (null: default 255)
 * @param  string $type The input type
 * @param  ?string $placeholder The placeholder value for this input field (null: none)
 * @param  ?string $pattern Custom regex pattern, covers whole field value (null: none)
 * @param  ?string $pattern_error Custom regex pattern validation error (null: none)
 * @return Tempcode The input field
 */
function form_input_line($pretty_name, $description, $name, $default, $required, $tabindex = null, $_maxlength = null, $type = 'text', $placeholder = null, $pattern = null, $pattern_error = null)
{
    if (is_null($default)) {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $maxlength = get_field_restrict_property('maxlength', $name);
    if ((is_null($maxlength)) && (!is_null($_maxlength))) {
        $maxlength = strval($_maxlength);
    }
    $input = do_template('FORM_SCREEN_INPUT_LINE', array('_GUID' => '02789c9af25cbc971e86bfcc0ad322d5', 'PLACEHOLDER' => $placeholder, 'MAXLENGTH' => $maxlength, 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default, 'TYPE' => $type, 'PATTERN' => $pattern));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex, false, false, '', (is_null($pattern_error) && !is_null($pattern)) ? strip_html($description->evaluate()) : $pattern_error);
}

/**
 * Get the Tempcode for a URL.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_url($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    if (is_null($default)) {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_URL', array('_GUID' => '12789c9af25cbc971e86bfcc0ad322d5', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a username input line.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  boolean $needs_match Whether it is required than a valid username is given
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_username($pretty_name, $description, $name, $default, $required, $needs_match = true, $tabindex = null)
{
    if (is_null($default)) {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    require_javascript('ajax');
    require_javascript('ajax_people_lists');

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_USERNAME', array('_GUID' => '591b5fe23f0cc0a4975a52d52aa5701e', 'TABINDEX' => strval($tabindex), 'NEEDS_MATCH' => $needs_match, 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a author/username input line.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_author($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    if (!addon_installed('authors')) {
        return form_input_username($pretty_name, $description, $name, $default, $required, true, $tabindex);
    }

    if (is_null($default)) {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    require_javascript('ajax');
    require_javascript('ajax_people_lists');

    $tabindex = get_form_field_tabindex($tabindex);

    $_description = new Tempcode();
    $_description->attach($description);
    if (has_js()) {
        if (!$_description->is_empty()) {
            $_description->attach(do_template('FORM_DESCRIP_SEP'));
        }
        $keep = symbol_tempcode('KEEP');
        $extra = do_template('HYPERLINK_POPUP_WINDOW', array('_GUID' => 'fb25dc4777a166c143a1bc32ff0c3239', 'URL' => find_script('authors') . '?field_name=' . urlencode($name) . $keep->evaluate(), 'TITLE' => do_lang_tempcode('AUTHOR'), 'CAPTION' => do_lang_tempcode('BROWSE_SENTENCE')));
        $_description->attach($extra);
    }

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_AUTHOR', array('_GUID' => '2662a51e494120078b4022915593e28a', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default));
    return _form_input($name, $pretty_name, $_description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a email-address input line.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_email($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    if (is_null($default)) {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_EMAIL', array('_GUID' => '2ff1d9e21894710b8f09598fd92049c7', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a colour input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_colour($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    if (!has_js()) {
        return form_input_line($pretty_name, $description, $name, $default, $required, $tabindex);
    }

    if (is_null($default)) {
        $default = '';
    }

    if ($default == 'inherit') {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    return do_template('FORM_SCREEN_INPUT_COLOUR', array('_GUID' => '9a1a8061cebd717ea98522984d9465af', 'RAW_FIELD' => false, 'REQUIRED' => $required, 'PRETTY_NAME' => $pretty_name, 'DESCRIPTION' => $description, 'TABINDEX' => strval($tabindex), '_REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default));
}

/**
 * Get the Tempcode for a page-link input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  ?ID_TEXT $page_type Page type to show (null: all)
 * @param  boolean $get_title_too Whether to also get the title for the page
 * @return Tempcode The input field
 */
function form_input_page_link($pretty_name, $description, $name, $default, $required, $tabindex = null, $page_type = null, $get_title_too = false)
{
    if (!has_js()) {
        return form_input_line($pretty_name, $description, $name, $default, $required, $tabindex);
    }

    require_lang('menus');

    require_javascript('ajax');
    require_javascript('tree_list');

    // Display
    $input = do_template('PAGE_LINK_CHOOSER', array('_GUID' => 'aabbd8e80df919afe08ca70bd24578dc', 'AS_FIELD' => true, 'GET_TITLE_TOO' => $get_title_too, 'NAME' => $name, 'VALUE' => $default, 'PAGE_TYPE' => $page_type));

    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex, false, true);
}

/**
 * Get the Tempcode for a Comcode-enabled text input line.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?string $default The default value for this input field (null: blank)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_line_comcode($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    require_lang('comcode');

    if (is_null($default)) {
        $default = '';
    }

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_LINE', array('_GUID' => 'b47034df1d68c1465d045fca822071a1', 'MAXLENGTH' => get_field_restrict_property('maxlength', $name), 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => $default));
    return _form_input($name, $pretty_name, $description, $input, $required, true, $tabindex);
}

/**
 * Get the Tempcode for a DHTML input field that takes multiple lines. A new line is added when the prior one isn't blank.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The base parameter name which this input field is for (as this takes multiple parameters, they are named <name><x>). This name must end with '_'.
 * @param  array $default_array An array of lines to use as default (at least this many lines, filled by this array, will be presented by default)
 * @param  integer $num_required The minimum number of inputs allowed.
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  string $class CSS class for input.
 * @set    line email
 * @param  ?string $pattern Custom regex pattern, covers whole field value (null: none)
 * @param  ?string $pattern_error Custom regex pattern validation error (null: none)
 * @return Tempcode The input field
 */
function form_input_line_multi($pretty_name, $description, $name, $default_array, $num_required, $tabindex = null, $class = 'line', $pattern = null, $pattern_error = null)
{
    require_javascript('multi');

    if (substr($name, -1) != '_' && substr($name, -2) != '[]') {
        $name .= '_';
    }

    $tabindex = get_form_field_tabindex($tabindex);

    $default_array[0] = filter_form_field_default($name, array_key_exists(0, $default_array) ? $default_array[0] : '');
    if ($num_required == 0) {
        $required = filter_form_field_required($name, false);
        if ($required) {
            $num_required = 1;
        }
    }

    $input = new Tempcode();
    $i = 0;
    foreach ($default_array as $default) {
        $_required = ($i < $num_required) ? '_required' : '';
        $input->attach(do_template('FORM_SCREEN_INPUT_LINE_MULTI', array(
            '_GUID' => 'e2da34b7564cebfd83da2859e4abd020',
            'CLASS' => $class,
            'MAXLENGTH' => get_field_restrict_property('maxlength', $name),
            'PRETTY_NAME' => $pretty_name,
            'TABINDEX' => strval($tabindex),
            'NAME_STUB' => $name,
            'I' => strval($i),
            'REQUIRED' => $_required,
            'DEFAULT' => $default,
            'PATTERN' => $pattern,
        )));
        $i++;
    }
    $num_to_show_initially = has_js() ? max($num_required, count($default_array) + 1) : max($num_required, 10);
    for (; $i < $num_to_show_initially; $i++) {
        $input->attach(do_template('FORM_SCREEN_INPUT_LINE_MULTI', array(
            '_GUID' => '10fcbe72e80ea1be07c3dd1fd9e0719e',
            'CLASS' => $class,
            'MAXLENGTH' => get_field_restrict_property('maxlength', $name),
            'PRETTY_NAME' => $pretty_name,
            'TABINDEX' => strval($tabindex),
            'NAME_STUB' => $name,
            'I' => strval($i),
            'REQUIRED' => ($i >= $num_required) ? '' : '_required',
            'DEFAULT' => '',
            'PATTERN' => $pattern,
        )));
    }
    return _form_input(preg_replace('#\[\]$#', '', $name), $pretty_name, $description, $input, $num_required > 0, false, $tabindex, false, true, '', $pattern_error);
}

/**
 * Get the Tempcode for a DHTML input field that takes multiple textareas. A new textarea is added when the prior one isn't blank.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The base parameter name which this input field is for (as this takes multiple parameters, they are named <name><x>). This name must end with '_'.
 * @param  array $default_array An array of texts to use as default (at least this many textareas, filled by this array, will be presented by default)
 * @param  integer $num_required The minimum number of textareas allowed.
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  ?integer $maxlength The maximum length of the field (null: unlimited)
 * @return Tempcode The input field
 */
function form_input_text_multi($pretty_name, $description, $name, $default_array, $num_required, $tabindex = null, $maxlength = null)
{
    require_javascript('multi');

    if (substr($name, -1) != '_') {
        $name .= '_';
    }

    $tabindex = get_form_field_tabindex($tabindex);

    $default_array[0] = filter_form_field_default($name, array_key_exists(0, $default_array) ? $default_array[0] : '');
    if ($num_required == 0) {
        $required = filter_form_field_required($name, false);
        if ($required) {
            $num_required = 1;
        }
    }

    $input = new Tempcode();
    $i = 0;
    foreach ($default_array as $default) {
        $_required = ($i < $num_required) ? '_required' : '';
        $input->attach(do_template('FORM_SCREEN_INPUT_TEXT_MULTI', array('_GUID' => '0d9e3c073d09d1ce3725f47813375c28', 'PRETTY_NAME' => $pretty_name, 'TABINDEX' => strval($tabindex), 'NAME_STUB' => $name, 'I' => strval($i), 'REQUIRED' => $_required, 'DEFAULT' => $default, 'MAXLENGTH' => is_null($maxlength) ? null : strval($maxlength))));
        $i++;
    }
    if (!has_js()) {
        $num_required = max($num_required, 10);
    }
    for (; $i < $num_required; $i++) {
        $input->attach(do_template('FORM_SCREEN_INPUT_TEXT_MULTI', array('_GUID' => '2e816a71ef5a9ac9e1aac4bd1c13b5bd', 'PRETTY_NAME' => $pretty_name, 'TABINDEX' => strval($tabindex), 'NAME_STUB' => $name, 'I' => strval($i), 'REQUIRED' => '_required', 'DEFAULT' => '', 'MAXLENGTH' => is_null($maxlength) ? null : strval($maxlength))));
    }
    return _form_input($name, $pretty_name, $description, $input, $num_required > 0, false, $tabindex, false, true);
}

/**
 * Get the Tempcode for a username input line.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The base parameter name which this input field is for (as this takes multiple parameters, they are named <name><x>). This name must end with '_'.
 * @param  array $default_array An array of lines to use as default (at least this many lines, filled by this array, will be presented by default)
 * @param  integer $num_required The minimum number of inputs allowed
 * @param  boolean $needs_match Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_username_multi($pretty_name, $description, $name, $default_array, $num_required, $needs_match = true, $tabindex = null)
{
    if (substr($name, -1) != '_') {
        $name .= '_';
    }

    require_javascript('multi');
    require_javascript('ajax');
    require_javascript('ajax_people_lists');

    $tabindex = get_form_field_tabindex($tabindex);

    if ($num_required == 0) {
        $required = filter_form_field_required($name, false);
        if ($required) {
            $num_required = 1;
        }
    }

    $input = new Tempcode();
    $i = 0;
    foreach ($default_array as $default) {
        if (is_null($default)) {
            $default = '';
        }
        $default = filter_form_field_default($name, $default);

        $_required = ($i < $num_required) ? '_required' : '';
        $input->attach(do_template('FORM_SCREEN_INPUT_USERNAME_MULTI', array('_GUID' => 'f2adcb1464b13e339a0336db6d5228cb', 'PRETTY_NAME' => $pretty_name, 'TABINDEX' => strval($tabindex), 'NEEDS_MATCH' => $needs_match, 'NAME_STUB' => $name, 'I' => strval($i), 'REQUIRED' => $_required, 'DEFAULT' => $default)));
        $i++;
    }
    if (!has_js()) {
        $num_required = max($num_required, 10);
    }
    if ($num_required > $i) {
        $_num_required = $num_required;
    } else {
        $_num_required = $i + 1;
    }
    for (; $i < $_num_required; $i++) {
        $_required = ($i < $num_required) ? '_required' : '';

        $input->attach(do_template('FORM_SCREEN_INPUT_USERNAME_MULTI', array('_GUID' => '4bc8a187ee5fac91275f66f78478a3c6', 'PRETTY_NAME' => $pretty_name, 'TABINDEX' => strval($tabindex), 'NEEDS_MATCH' => $needs_match, 'NAME_STUB' => $name, 'I' => strval($i), 'REQUIRED' => $_required, 'DEFAULT' => '')));
    }

    return _form_input($name, $pretty_name, $description, $input, $num_required > 0, false, $tabindex);
}

/**
 * Get the Tempcode for a text input (textarea).
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  string $default The default value for this input field
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $scrolls Whether the field scrolls
 * @param  ?integer $maxlength The maximum length of the field (null: unlimited)
 * @param  ?integer $rows Number of rows for text input (null: default)
 * @return Tempcode The input field
 */
function form_input_text($pretty_name, $description, $name, $default, $required, $tabindex = null, $scrolls = false, $maxlength = null, $rows = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    check_suhosin_request_size(strlen($default));

    $_required = ($required) ? '_required' : '';

    $input = do_template('FORM_SCREEN_INPUT_TEXT', array(
        '_GUID' => '01626015c6ae36b1027e35e66a8b5d0b',
        'RAW' => true,
        'SCROLLS' => $scrolls,
        'ROWS' => is_null($rows) ? null : strval($rows),
        'TABINDEX' => strval($tabindex),
        'REQUIRED' => $_required,
        'NAME' => $name,
        'DEFAULT' => $default,
        'MAXLENGTH' => is_null($maxlength) ? null : strval($maxlength),
    ));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex, true);
}

/**
 * Get the Tempcode for a Comcode-enabled text input (textarea).
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  string $default The default value for this input field
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $force_non_wysiwyg Force non-WYSIWYG and non default-Comcode parsing
 * @param  mixed $description_side A secondary side description for this input field
 * @param  ?Tempcode $default_parsed The parsed Comcode. (null: calculate)
 * @param  boolean $scrolls Whether the field scrolls
 * @param  ?integer $rows Number of rows for text input (null: default)
 * @return Tempcode The input field
 */
function form_input_text_comcode($pretty_name, $description, $name, $default, $required, $tabindex = null, $force_non_wysiwyg = false, $description_side = '', $default_parsed = null, $scrolls = false, $rows = null)
{
    if ((browser_matches('wysiwyg', $default)) && (!$force_non_wysiwyg) && (strpos($default, '{$,page hint: no_wysiwyg}') === false)) {
        return form_input_huge_comcode($pretty_name, $description, $name, $default, $required, $tabindex, 10, $description_side, $default_parsed, $scrolls);
    }

    require_lang('comcode');

    require_javascript('ajax');

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $default_parsed = new Tempcode();

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    check_suhosin_request_size(strlen($default));

    if (!$force_non_wysiwyg) {
        attach_wysiwyg();

        $w = (has_js()) && (browser_matches('wysiwyg', $default) && (strpos($default, '{$,page hint: no_wysiwyg}') === false));
        if ($w) {
            $_required .= ' wysiwyg';
        }
        global $LAX_COMCODE;
        $temp = $LAX_COMCODE;
        $LAX_COMCODE = true;
        $GLOBALS['COMCODE_PARSE_URLS_CHECKED'] = 100; // Little hack to stop it checking any URLs
        /*Actually we reparse always to ensure it is done in semiparse mode if (is_null($default_parsed)) */
        $default_parsed = @comcode_to_tempcode($default, null, false, null, null, null, true);
        $LAX_COMCODE = $temp;
    } else {
        $w = false;
        $default_parsed = new Tempcode();
    }

    $input = do_template('FORM_SCREEN_INPUT_TEXT', array(
        '_GUID' => 'ff53196e943e7b19bc72fc3bbb3238b5',
        'SCROLLS' => $scrolls,
        'ROWS' => is_null($rows) ? (((is_object($description_side)) || ($description_side != '')) ? '16' : '8') : strval($rows),
        'TABINDEX' => strval($tabindex),
        'REQUIRED' => $_required,
        'NAME' => $name,
        'DEFAULT' => $default,
        'DEFAULT_PARSED' => $default_parsed,
    ));

    return _form_input($name, $pretty_name, $description, $input, $required, true, $tabindex, $w, false, $description_side);
}

/**
 * Get the Tempcode for a huge comcode-enabled text input (textarea). These need extra space to fit. This function is also used as an automatic replacement for form_input_text_comcode if WYSIWYG is available (as WYSIWYG needs more space too)
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  string $default The default value for this input field
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  integer $rows The number of rows to use
 * @param  mixed $description_side A secondary side description for this input field
 * @param  ?Tempcode $default_parsed The parsed Comcode. (null: calculate)
 * @param  boolean $scrolls Whether the field scrolls
 * @param  boolean $force_non_wysiwyg Force non-WYSIWYG and non default-Comcode parsing
 * @return Tempcode The input field
 */
function form_input_huge_comcode($pretty_name, $description, $name, $default, $required, $tabindex = null, $rows = 20, $description_side = '', $default_parsed = null, $scrolls = false, $force_non_wysiwyg = false)
{
    require_lang('comcode');

    require_javascript('ajax');

    $tabindex = get_form_field_tabindex($tabindex);

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    check_suhosin_request_size(strlen($default));

    $_required = ($required) ? '_required' : '';
    $default_parsed = new Tempcode();

    attach_wysiwyg();

    if (!$force_non_wysiwyg) {
        $w = (has_js()) && (browser_matches('wysiwyg', $default) && (strpos($default, '{$,page hint: no_wysiwyg}') === false));
        if ($w) {
            $_required .= ' wysiwyg';
        }
        global $LAX_COMCODE;
        $temp = $LAX_COMCODE;
        $LAX_COMCODE = true;
        $GLOBALS['COMCODE_PARSE_URLS_CHECKED'] = 100; // Little hack to stop it checking any URLs
        /*Actually we reparse always to ensure it is done in semiparse mode if (is_null($default_parsed)) */
        $default_parsed = @comcode_to_tempcode($default, null, false, null, null, null, true);
        $LAX_COMCODE = $temp;
    } else {
        $w = false;
        $default_parsed = new Tempcode();
    }

    $help_zone = get_comcode_zone('userguide_comcode', false);
    $_comcode = is_null($help_zone) ? new Tempcode() : do_template('COMCODE_MESSAGE', array('_GUID' => 'fbcf2413f754ca5829b9f4c908746843', 'NAME' => $name, 'W' => $w, 'URL' => build_url(array('page' => 'userguide_comcode'), $help_zone)));

    return do_template('FORM_SCREEN_INPUT_HUGE_COMCODE', array(
        '_GUID' => 'b8231827be2f4a00e12fcd8986119588',
        'SCROLLS' => $scrolls,
        'DESCRIPTION_SIDE' => $description_side,
        'REQUIRED' => $required,
        '_REQUIRED' => $_required,
        'TABINDEX' => strval($tabindex),
        'COMCODE' => $_comcode,
        'PRETTY_NAME' => $pretty_name,
        'DESCRIPTION' => $description,
        'NAME' => $name,
        'DEFAULT' => $default,
        'DEFAULT_PARSED' => $default_parsed,
        'ROWS' => strval($rows),
    ));
}

/**
 * Get the Tempcode for a huge text input (textarea).
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  string $default The default value for this input field
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  integer $rows The number of rows to use
 * @param  mixed $description_side A secondary side description for this input field
 * @param  boolean $scrolls Whether the field scrolls
 * @return Tempcode The input field
 */
function form_input_huge($pretty_name, $description, $name, $default, $required, $tabindex = null, $rows = 20, $description_side = '', $scrolls = false)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $default = filter_form_field_default($name, $default);
    $required = filter_form_field_required($name, $required);

    check_suhosin_request_size(strlen($default));

    $_required = ($required) ? '_required' : '';
    $default_parsed = new Tempcode();

    return do_template('FORM_SCREEN_INPUT_HUGE', array(
        '_GUID' => '9d51961cd53c3fcadb8f83b905b2bbea',
        'RAW' => true,
        'SCROLLS' => $scrolls,
        'DESCRIPTION_SIDE' => $description_side,
        'REQUIRED' => $required,
        '_REQUIRED' => $_required,
        'TABINDEX' => strval($tabindex),
        'PRETTY_NAME' => $pretty_name,
        'DESCRIPTION' => $description,
        'NAME' => $name,
        'DEFAULT' => $default,
        'ROWS' => strval($rows),
    ));
}

/**
 * Get the Tempcode for a password input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  string $default The default value for this input field
 * @return Tempcode The input field
 */
function form_input_password($pretty_name, $description, $name, $required, $tabindex = null, $default = '')
{
    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_PASSWORD', array('_GUID' => '12af7290441ebf5459feefaf9daa28c6', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'VALUE' => $default));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a checkbox input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  boolean $ticked Whether this is ticked by default
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  ID_TEXT $value The value the checkbox passes when ticked
 * @param  boolean $disabled Whether this is disabled
 * @return Tempcode The input field
 */
function form_input_tick($pretty_name, $description, $name, $ticked, $tabindex = null, $value = '1', $disabled = false)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $ticked = (filter_form_field_default($name, $ticked ? '1' : '0') == '1');

    $input = do_template('FORM_SCREEN_INPUT_TICK', array('_GUID' => '340a68c271b838d327f042d101df27eb', 'VALUE' => $value, 'CHECKED' => $ticked, 'TABINDEX' => strval($tabindex), 'NAME' => $name, 'DISABLED' => $disabled));
    return _form_input($name, $pretty_name, $description, $input, false, false, $tabindex);
}

/**
 * Get the Tempcode for a bank of tick boxes.
 *
 * @param  array $options A list of tuples: (prettyname, name, value, description)
 * @param  mixed $description A description for this input field
 * @param  ?integer $_tabindex The tab index of the field (null: not specified)
 * @param  mixed $_pretty_name A human intelligible name for this input field (blank: use default)
 * @param  boolean $simple_style Whether to place each tick on a new line
 * @param  ?ID_TEXT $custom_name Name for custom value to be entered to (null: no custom value allowed)
 * @param  ?mixed $custom_value Value for custom value, string (accept single value) or array (accept multiple values) (null: no custom value known)
 * @return Tempcode The input field
 */
function form_input_various_ticks($options, $description, $_tabindex = null, $_pretty_name = '', $simple_style = false, $custom_name = null, $custom_value = null)
{
    if (count($options) == 0) {
        return new Tempcode();
    }

    $options = array_values($options);

    if (is_null($_tabindex)) {
        $tabindex = get_form_field_tabindex(null);
    } else {
        $_tabindex++;
        $tabindex = $_tabindex;
    }

    if ((is_string($_pretty_name)) && ($_pretty_name == '')) {
        $_pretty_name = do_lang_tempcode('OPTIONS');
    }

    $input = new Tempcode();

    if (count($options[0]) != 3) {
        $options = array(array($options, null, new Tempcode()));
    }
    foreach ($options as $_option) {
        $out = array();
        foreach ($_option[0] as $option) {
            list($pretty_name, $name, $value, $_description) = $option;

            $value = (filter_form_field_default($name, $value ? '1' : '0') == '1');

            $out[] = array('DISABLED' => false, 'CHECKED' => $value, 'TABINDEX' => strval($tabindex), 'NAME' => $name, 'PRETTY_NAME' => $pretty_name, 'DESCRIPTION' => $_description);
        }

        if ($custom_value === array()) {
            $custom_value = array('');
        }

        $input->attach(do_template('FORM_SCREEN_INPUT_VARIOUS_TICKS', array(
            '_GUID' => 'a6212f61304a101fb2754e334a8b4212',
            'CUSTOM_ACCEPT_MULTIPLE' => is_array($custom_value),
            'CUSTOM_NAME' => $custom_name,
            'CUSTOM_VALUE' => is_null($custom_value) ? '' : $custom_value,
            'SECTION_TITLE' => $_option[2],
            'EXPANDED' => $_option[1],
            'SIMPLE_STYLE' => $simple_style,
            'BRETHREN_COUNT' => strval(count($out)),
            'OUT' => $out,
        )));
    }
    return _form_input('', $_pretty_name, $description, $input, false, false, $tabindex);
}

/**
 * Get the Tempcode for a multi-set upload, allowing selection from multiple different sources.
 * Mainly for images, used with post_param_image or post_param_multi_source_upload.
 *
 * @param  mixed $set_title A human intelligible name for this input field
 * @param  mixed $set_description A description for this input field
 * @param  Tempcode $hidden A reference to where we're putting hidden fields
 * @param  ID_TEXT $set_name The name which this input field is for (actually, this is a prefix put in front of different input types).
 * @param  ?ID_TEXT $theme_image_type The directory of theme images to provide selection from (null: do not support theme images)
 * @param  boolean $required Whether this is a required input field. Set this to false if you are using this field on an edit form and already have an uploaded file -- therefore you'd know no new file would mean not to replace the existing file
 * @param  ?string $default The default value for the field (null: none) (blank: none). Should only be passed if $required is false, because it creates a delete button for the existing file, implying that leaving it with no file is valid
 * @param  boolean $support_syndication Whether to syndicate the file off
 * @param  ?string $filter File type filter (null: autodetect)
 * @param  boolean $images_only Whether to accept images only
 * @return Tempcode The input field
 */
function form_input_upload_multi_source($set_title, $set_description, &$hidden, $set_name = 'image', $theme_image_type = null, $required = true, $default = null, $support_syndication = false, $filter = null, $images_only = true)
{
    $field_set = alternate_fields_set__start($set_name);

    require_code('images');

    // Remap theme image to URL if needed
    if ($images_only) {
        if ($theme_image_type !== null && get_option('allow_theme_image_selector') == '1') {
            require_code('themes2');
            $ids = get_all_image_ids_type($theme_image_type);

            if (in_array($default, $ids)) {
                $default = find_theme_image($default);
            }
        }
    }

    // ---

    // Upload
    // ------

    $field_file = $set_name . '__upload';

    if ($support_syndication) {
        require_code('upload_syndication');
        list($syndication_json,) = get_upload_syndication_json(CMS_UPLOAD_IMAGE);
    } else {
        $syndication_json = null;
    }
    if (is_null($filter)) {
        if ($images_only) {
            $filter = str_replace(' ', '', get_option('valid_images')); // We don't use the filter from get_upload_syndication_json because we're not restricted to what can syndicate
        } else {
            $filter = '';
        }
    }

    $field_set->attach(form_input_upload(do_lang_tempcode('UPLOAD'), do_lang_tempcode('DESCRIPTION_UPLOAD'), $field_file, $required, $default, null, true, $filter, $syndication_json));

    if ($images_only) {
        handle_max_file_size($hidden, 'image');
    }

    // URL
    // ---

    $field_url = $set_name . '__url';

    require_code('urls_simplifier'); // TODO: This should move into form_input_url in v11
    $coder_ob = new HarmlessURLCoder();
    $_default = ($default === null) ? '' : $coder_ob->decode($default);

    $field_set->attach(form_input_url(do_lang_tempcode('URL'), do_lang_tempcode('DESCRIPTION_ALTERNATE_URL'), $field_url, $_default, $required));

    // Filedump
    // --------

    if ((addon_installed('filedump')) && (has_actual_page_access(null, 'filedump'))) {
        require_code('files2');
        $full_path = get_custom_file_base() . '/uploads/filedump';
        $files = get_directory_contents($full_path, '', false, false);
        $has_image_or_dir = false;
        foreach ($files as $file) {
            if (is_image($file) || is_dir($full_path . '/' . $file)) {
                $has_image_or_dir = true;
                break;
            }
        }
        if ($has_image_or_dir) {
            $field_filedump = $set_name . '__filedump';

            require_lang('filedump');

            $filedump_url = build_url(array('page' => 'filedump'), get_module_zone('filedump'));
            if ($images_only) {
                $filedump_options = array('images_only' => '1');
            } else {
                $filedump_options = array();
            }
            $filedump_default = (($default !== null) && (preg_match('#^uploads/filedump/#', $default) != 0)) ? $default : '';
            $filedump_field_description = do_lang_tempcode('DESCRIPTION_ALTERNATE_URL_FILEDUMP', escape_html($filedump_url->evaluate()));
            $field_set->attach(form_input_tree_list(do_lang_tempcode('FILEDUMP'), $filedump_field_description, $field_filedump, '', 'choose_filedump_file', $filedump_options, $required, $filedump_default, false));
        }
    }

    // Theme image
    // -----------

    if ($images_only) {
        if ($theme_image_type !== null && get_option('allow_theme_image_selector') == '1') {
            if (count($ids) > 0) {
                $field_choose = $set_name . '__theme_image';

                $field_set->attach(form_input_theme_image(do_lang_tempcode('STOCK'), '', $field_choose, $ids, $default, null, null, $required));
            }
        }
    }

    // ---

    return alternate_fields_set__end($set_name, $set_title, $set_description, $field_set, $required);
}

/**
 * Make a preview URL absolute and return if it is an image.
 *
 * @param  URLPATH $url URL
 * @return array A pair: Modified URL, whether it is an image
 */
function make_previewable_url_absolute($url)
{
    $_url = $url;
    $is_image = false;

    if (($_url !== null) && ($_url != '')) {
        if (url_is_local($_url)) {
            $image_path = get_custom_file_base() . '/' . dirname(rawurldecode($_url));
            if (!is_file($image_path)) {
                $image_path = get_file_base() . '/' . dirname(rawurldecode($_url));
                $custom = false;
            } else {
                $custom = true;
            }

            $htaccess_path = $image_path . '/.htaccess';
            if ((is_file($htaccess_path)) && (stripos(file_get_contents($htaccess_path), 'deny from all') !== false) && (stripos(file_get_contents($htaccess_path), 'require not ip') !== false)) {
                return array($_url, $is_image);
            }

            require_code('images');
            $is_image = is_image($_url, true);
            $_url = ($custom ? get_custom_base_url() : get_base_url()) . '/' . $_url;
        }
    }

    return array($_url, $is_image);
}

/**
 * Get the Tempcode for a file upload input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  boolean $required Whether this is a required input field. Set this to false if you are using this field on an edit form and already have an uploaded file -- therefore you'd know no new file would mean not to replace the existing file
 * @param  ?string $default The default value for the field (null: none) (blank: none). Should only be passed if $required is false, because it creates a delete button for the existing file, implying that leaving it with no file is valid
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $plupload Whether plupload-style is preferred
 * @param  string $filter File-type filter to limit to, comma-separated file extensions (might not be supported)
 * @param  ?string $syndication_json JSON structure of what uploader syndications there will be (null: none)
 * @return Tempcode The input field
 */
function form_input_upload($pretty_name, $description, $name, $required, $default = null, $tabindex = null, $plupload = true, $filter = '', $syndication_json = null)
{
    if ($plupload) {
        require_javascript('plupload');
        require_css('widget_plupload');
    }

    if ($default === '') {
        $default = null;
    }

    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';

    list($existing_url, $is_image) = make_previewable_url_absolute($default);

    $input = do_template('FORM_SCREEN_INPUT_UPLOAD', array(
        '_GUID' => 'f493edcc5298bb32fff8635f2d316d21',
        'FILTER' => $filter,
        'PRETTY_NAME' => $pretty_name,
        'EXISTING_URL' => $existing_url,
        'IS_IMAGE' => $is_image,
        'PLUPLOAD' => $plupload,
        'EDIT' => ((!is_null($default)) && (!$required)),
        'TABINDEX' => strval($tabindex),
        'REQUIRED' => $_required,
        'NAME' => $name,
        'SYNDICATION_JSON' => $syndication_json,
    ));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a multiple file upload input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  string $name The base name which this input field is for
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  ?array $default The default value for the field (null: none)
 * @param  boolean $plupload Whether plupload-style is preferred
 * @param  string $filter File-type filter to limit to, comma-separated file extensions (might not be supported)
 * @param  ?string $syndication_json JSON structure of what uploader syndications there will be (null: none)
 * @return Tempcode The input field
 */
function form_input_upload_multi($pretty_name, $description, $name, $required, $tabindex = null, $default = null, $plupload = true, $filter = '', $syndication_json = null)
{
    if ($plupload) {
        require_javascript('plupload');
        require_css('widget_plupload');
    }
    require_javascript('multi');

    $tabindex = get_form_field_tabindex($tabindex);

    $edit = mixed();
    $existing_url = '';
    if ((!is_null($default)) && (count($default) > 0)) {
        list($edit, $is_image) = make_previewable_url_absolute($default[0]);
    } else {
        $edit = array();
    }

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_UPLOAD_MULTI', array(
        '_GUID' => 'e8712ede08591604738762ac03852ac1',
        'TABINDEX' => strval($tabindex),
        'EDIT' => $edit,
        'FILTER' => $filter,
        'REQUIRED' => $_required,
        'PLUPLOAD' => $plupload,
        'NAME' => $name,
        'I' => '1',
        'NAME_STUB' => $name,
        'SYNDICATION_JSON' => $syndication_json,
    ));
    return _form_input('', $pretty_name, $description, $input, $required, false, $tabindex, false, true);
}

/**
 * Get the Tempcode for a listbox.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  Tempcode $content The list entries for our list; you compose these via attaching together form_input_list_entry calls
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $inline_list Whether this is an inline displayed list as opposed to a dropdown
 * @param  boolean $required Whether this is required
 * @param  ?array $images List of theme images that $content is allowing selection of (so that we can show the images within the list, if JS is enabled) (null: none)
 * @param  integer $size How much space the list takes up (inline lists only)
 * @return Tempcode The input field
 */
function form_input_list($pretty_name, $description, $name, $content, $tabindex = null, $inline_list = false, $required = true, $images = null, $size = 5)
{
    $tabindex = get_form_field_tabindex($tabindex);

    require_css('widget_select2');
    require_javascript('jquery');
    require_javascript('select2');

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_LIST', array('_GUID' => '112dd79a8e0069aa21615594aec1e509', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'CONTENT' => $content, 'INLINE_LIST' => $inline_list, 'IMAGES' => $images, 'SIZE' => strval($size)));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a huge listbox.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  Tempcode $content The list entries for our list; you compose these via attaching together form_input_list_entry calls
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $inline_list Whether this is an inline displayed list as opposed to a dropdown
 * @param  boolean $required Whether this is required
 * @param  ?integer $size Size of list (null: default)
 * @return Tempcode The input field
 */
function form_input_huge_list($pretty_name, $description, $name, $content, $tabindex = null, $inline_list = false, $required = true, $size = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    require_css('widget_select2');
    require_javascript('jquery');
    require_javascript('select2');

    $_required = ($required) ? '_required' : '';

    return do_template('FORM_SCREEN_INPUT_HUGE_LIST', array(
        '_GUID' => 'b29dbbaf09bb5c36410e22feafa2f968',
        'TABINDEX' => strval($tabindex),
        'SIZE' => is_null($size) ? null : strval($size),
        'REQUIRED' => $_required,
        'PRETTY_NAME' => $pretty_name,
        'DESCRIPTION' => $description,
        'NAME' => $name,
        'CONTENT' => $content,
        'INLINE_LIST' => $inline_list,
    ));
}

/**
 * Get the Tempcode for a listbox with multiple selections.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  Tempcode $content The list entries for our list; you compose these via attaching together form_input_list_entry calls
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  integer $size How much space the list takes up
 * @param  boolean $required Whether at least one must be selected
 * @param  ?ID_TEXT $custom_name Name for custom value to be entered to (null: no custom value allowed)
 * @param  ?mixed $custom_value Value for custom value, string (accept single value) or array (accept multiple values) (null: no custom value known)
 * @return Tempcode The input field
 */
function form_input_multi_list($pretty_name, $description, $name, $content, $tabindex = null, $size = 5, $required = false, $custom_name = null, $custom_value = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    require_css('widget_select2');
    require_javascript('jquery');
    require_javascript('select2');

    $input = do_template('FORM_SCREEN_INPUT_MULTI_LIST', array(
        '_GUID' => 'ed0739205c0bf5039e1d4fe2ddfc06da',
        'TABINDEX' => strval($tabindex),
        'SIZE' => strval($size),
        'NAME' => $name,
        'CONTENT' => $content,
        'CUSTOM_ACCEPT_MULTIPLE' => is_array($custom_value),
        'CUSTOM_NAME' => $custom_name,
        'CUSTOM_VALUE' => is_null($custom_value) ? '' : $custom_value,
    ));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a combo-box (listbox with free text input). Works best if HTML5 is available.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  string $default Current selection
 * @param  Tempcode $options The list entries for our list; you compose these via attaching together form_input_list_entry calls
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $required Whether this is required
 * @return Tempcode The input field
 */
function form_input_combo($pretty_name, $description, $name, $default, $options, $tabindex = null, $required = true)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_COMBO', array('_GUID' => '4f4595c0b0dfcf09b004481e317d02a8', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'CONTENT' => $options, 'DEFAULT' => $default));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for an AJAX-powered tree listbox.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?ID_TEXT $root_id The ID to do under (null: root)
 * @param  string $hook The ajax tree-list hook that drives our data
 * @param  array $options A map of special options
 * @param  boolean $required Whether this is a required input field
 * @param  ?string $default The default value for the field (null: none)
 * @param  boolean $use_server_id Whether to use the server-ID in the list instead of the ID in the list
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $multi_select Whether multiple selections are allowed
 * @param  ?string $nice_label Label for default value (null: just use the literal)
 * @return Tempcode The input field
 */
function form_input_tree_list($pretty_name, $description, $name, $root_id, $hook, $options, $required, $default = null, $use_server_id = false, $tabindex = null, $multi_select = false, $nice_label = null)
{
    require_javascript('tree_list');

    require_code('hooks/systems/ajax_tree/' . $hook);
    $object = object_factory('Hook_' . $hook);

    if ((!has_js()) || (get_option('tree_lists') == '0')) {
        $simple_content = new Tempcode();
        $simple_content->attach(form_input_list_entry('', false, do_lang('NA')));
        $simple_content->attach($object->simple($root_id, $options, $default));

        if ($simple_content->is_empty()) {
            if ($required) {
                inform_exit(do_lang_tempcode('NO_OF_THIS', $pretty_name));
            }
            return new Tempcode();
        }
        if ($multi_select) {
            return form_input_multi_list($pretty_name, $description, $name, $simple_content, $tabindex, 10, $required);
        }

        return form_input_list($pretty_name, $description, $name, $simple_content, $tabindex, false, $required);
    }

    $tabindex = get_form_field_tabindex($tabindex);

    require_javascript('ajax');

    if (is_null($nice_label)) {
        $nice_label = $default;
    }

    require_code('content');
    $content_type = preg_replace('#^choose_#', '', $hook);
    if ($content_type != '') {
        $cma_ob = get_content_object($content_type);
        if (!is_null($cma_ob)) {
            $cma_info = $cma_ob->info();
            if ($cma_info === null) {
                fatal_exit(do_lang_tempcode('INTERNAL_ERROR'));
            }
            if (!is_null($cma_info['parent_category_meta_aware_type'])) {
                $content_type = $cma_info['parent_category_meta_aware_type'];
            }
        }
    }

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_TREE_LIST', array(
        '_GUID' => '21e9644eeac24356f38459ebe37f693a',
        'MULTI_SELECT' => $multi_select,
        'NICE_LABEL' => (is_null($nice_label) || $nice_label == '-1') ? '' : $nice_label,
        'USE_SERVER_ID' => $use_server_id,
        'TABINDEX' => strval($tabindex),
        'NAME' => $name,
        'REQUIRED' => $_required,
        '_REQUIRED' => $required,
        'DEFAULT' => is_null($default) ? '' : $default,
        'HOOK' => $hook,
        'ROOT_ID' => is_null($root_id) ? '' : $root_id,
        'OPTIONS' => serialize($options),
        'DESCRIPTION' => $description,
        'CONTENT_TYPE' => $content_type,
    ));
    return _form_input($name, $pretty_name, '', $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a complex input that chooses partials from a list ('all', 'all-except-these', or 'these').
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  string $base The base name which this input field is for
 * @param  Tempcode $list A list culmulation to select against
 * @param  string $type The current type of partial selection
 * @set    + - *
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_all_and_not($pretty_name, $description, $base, $list, $type = '+', $tabindex = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $type = filter_form_field_default($base, $type);

    $radios = new Tempcode();
    $radios->attach(form_input_radio_entry($base, '*', $type == '*', do_lang_tempcode('USE_ALL'), $tabindex));
    $radios->attach(form_input_radio_entry($base, '-', $type == '-', do_lang_tempcode('USE_ALL_EXCEPT_SELECTED'), $tabindex));
    $radios->attach(form_input_radio_entry($base, '+', $type == '+', do_lang_tempcode('USE_ALL_SELECTED'), $tabindex));
    $input = do_template('FORM_SCREEN_INPUT_ALL_AND_NOT', array('_GUID' => '32063ca0237a3b46e8fa08bb71a6e41c', 'TABINDEX' => strval($tabindex), 'BASE' => $base, 'RADIOS' => $radios, 'LIST' => $list));
    return _form_input($base . '_list', $pretty_name, $description, $input, false, false, $tabindex);
}

/**
 * Get the Tempcode for a radio group.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  Tempcode $content The radio buttons for our radio group; you compose these via attaching together form_input_radio_entry calls
 * @param  boolean $required Whether a radio selection is required
 * @param  boolean $picture_contents Whether this is a picture-based radio list
 * @param  string $selected_path Default value (only appropriate if has picture contents)
 * @return Tempcode The input field
 */
function form_input_radio($pretty_name, $description, $name, $content, $required = false, $picture_contents = false, $selected_path = '')
{
    $map = array('_GUID' => '26021f9ae8a0cd83b93874bfa80052ca', 'NAME' => $name, 'REQUIRED' => $required, 'CONTENT' => $content);
    if ($picture_contents) {
        $map = array_merge($map, array('CODE' => $selected_path,));
    }
    $input = do_template('FORM_SCREEN_INPUT_RADIO_LIST', $map);
    return _form_input(($GLOBALS['DOING_ALTERNATE_FIELDS_SET'] !== null) ? $name : '', $pretty_name, $description, $input, $required);
}

/**
 * Get the Tempcode for a radio input. (You would gather together the outputs of several of these functions, then put them in as the $content in a form_input_radio function call).
 *
 * @param  string $name The name of the radio button group this will be put in (i.e. the name the value presented here will be possibly matched against)
 * @param  string $value The value for this entry
 * @param  boolean $selected Whether this entry is selected by default or not
 * @param  mixed $text The text associated with this choice (blank: just use value for text)
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  mixed $description An additional long description (blank: no description)
 * @return Tempcode The input field
 */
function form_input_radio_entry($name, $value, $selected = false, $text = '', $tabindex = null, $description = '')
{
    $tabindex = get_form_field_tabindex($tabindex);

    if ((is_string($text)) && ($text == '')) {
        $text = $value;
    }

    $selected = (filter_form_field_default($name, $selected ? '1' : '') == '1');

    return do_template('FORM_SCREEN_INPUT_RADIO_LIST_ENTRY', array('_GUID' => 'e2fe4ba6e8b3f705651dba13ea27f61d', 'DESCRIPTION' => $description, 'CHECKED' => $selected, 'TABINDEX' => strval($tabindex), 'NAME' => $name, 'VALUE' => $value, 'TEXT' => $text));
}

/**
 * Get the Tempcode to choose a picture from the given list of images in the theme image system, with sub-categorisation.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  array $ids A list of image IDs (codes) we can choose from, probably found via get_all_image_ids_type
 * @param  ?URLPATH $selected_url The currently selected entry in our picture choosing, by URL (null: none)
 * @param  ?string $selected_code The currently selected entry in our picture choosing, by code (null: none)
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $allow_none Whether to allow the selection of 'no' picture
 * @param  ?object $db The database connection to the Conversr install we are choosing images from (null: site db)
 * @param  ?ID_TEXT $theme Theme to use (null: current theme)
 * @param  ?ID_TEXT $lang Language to use (null: current language)
 * @param  boolean $linear Whether choices are presented in a list (as opposed to a grid); useful when sizes are irregular
 * @param  boolean $direct_titles Whether to show direct codenames, rather than trying to prettify them into titles
 * @return Tempcode The input field
 */
function form_input_theme_image($pretty_name, $description, $name, $ids, $selected_url = null, $selected_code = null, $tabindex = null, $allow_none = false, $db = null, $theme = null, $lang = null, $linear = false, $direct_titles = false)
{
    if (is_null($db)) {
        $db = $GLOBALS['SITE_DB'];
    }

    if (count($ids) == 0) {
        return new Tempcode();
    }

    $tabindex = get_form_field_tabindex($tabindex);

    require_code('images');

    $selected_code = filter_form_field_default($name, is_null($selected_code) ? '' : $selected_code);
    if ($selected_code == '') {
        $selected_code = null;
    }

    // Split into lists of categories based on path division
    $current_path = ''; // Initialise type to string
    $current_path = null;
    $category = array();
    $categories = array();
    foreach ($ids as $id) {
        $slash_pos = strrpos($id, '/');
        if ($slash_pos === false) {
            $slash_pos = 0;
        }
        $new_path = substr($id, 0, $slash_pos);
        if ($new_path !== $current_path) {
            if (!is_null($current_path)) {
                if (!array_key_exists($current_path, $categories)) {
                    $categories[$current_path] = array();
                }
                $categories[$current_path] = array_merge($categories[$current_path], $category);
                $category = array();
            }
            $current_path = $new_path;
        }
        $category[] = $id;
    }
    if (!is_null($current_path)) {
        if (!array_key_exists($current_path, $categories)) {
            $categories[$current_path] = array();
        }
        $categories[$current_path] = array_merge($categories[$current_path], $category);
    }

    // Sorting but fudge it so 'cns_default_avatars/default_set' always comes first
    ksort($categories);
    $avatars = (array_key_exists(0, $category)) && (substr($category[0], 0, 20) == 'cns_default_avatars/');
    if ((array_key_exists('cns_default_avatars/default_set', $categories)) && ($avatars)) {
        $def = $categories['cns_default_avatars/default_set'];
        unset($categories['cns_default_avatars/default_set']);
        $categories = array_merge(array('cns_default_avatars/default_set' => $def), $categories);
    }
    // Add in the 'N/A' option
    if (($allow_none) && (!array_key_exists('', $categories))) {
        if (count($categories) == 1) {
            array_unshift($categories[$current_path], '');
        } else {
            $categories[do_lang('NA')] = array('');
        }
    }

    // Show each category
    $content = new Tempcode();
    foreach ($categories as $cat => $ids) {
        $cat = $direct_titles ? $cat : titleify($cat);

        if ($avatars) {
            $cut_pos = strpos($cat, '/');
            $cut_pos = ($cut_pos === false) ? ($avatars ? strlen($cat) : 0) : ($cut_pos + 1);
            $cat = titleify(substr($cat, $cut_pos)); // Make the category name a bit nicer
        }

        if ($cat == '') {
            $cat = do_lang($avatars ? 'GENERAL' : 'UNNAMED');
        }

        $_category = new Tempcode();
        $i = 0;
        $category_expanded = false;
        foreach ($ids as $id) {
            if ($selected_url !== null) {
                $pos = strpos($selected_url, '/' . $id);
                $selected = false;
                if ($id != '') {
                    $selected = (cms_rawurlrecode(find_theme_image($id, false, false, null, null, $db)) == cms_rawurlrecode($selected_url)) || (cms_rawurlrecode(find_theme_image($id, false, true, null, null, $db)) == cms_rawurlrecode($selected_url));
                }
                if ($selected) {
                    $selected_code = $id;
                }
            } else {
                $selected = ($selected_code == $id);
            }
            if ($selected) {
                $category_expanded = true;
            }

            if ($id == '') {
                if (is_null($selected_code)) {
                    $selected = true;
                }
                $url = find_theme_image('na', false, false, $theme, $lang, $db);
                $pretty = do_lang_tempcode('NA_EM');
            } else {
                $url = find_theme_image($id, $theme != 'default', false, $theme, $lang, $db);
                if ($url == '') {
                    $url = find_theme_image($id, false, false, 'default', $lang, $db);
                }
                $pretty = $direct_titles ? make_string_tempcode($id) : make_string_tempcode(titleify((strrpos($id, '/') === false) ? $id : substr($id, strrpos($id, '/') + 1)));
            }
            if ($url == '') {
                continue;
            }

            $file_path = convert_url_to_path($url);
            if (!is_null($file_path)) {
                $test = cms_getimagesize($file_path);
            } else {
                $test = false;
            }
            if ($test !== false) {
                list($width, $height) = $test;
            } else {
                $width = null;
                $height = null;
            }
            $temp = do_template('FORM_SCREEN_INPUT_THEME_IMAGE_ENTRY', array('_GUID' => '10005e2f08b44bfe17fce68685b4c884', 'LINEAR' => $linear, 'CHECKED' => $selected, 'PRETTY' => $pretty, 'NAME' => $name, 'CODE' => $id, 'URL' => $url, 'WIDTH' => is_null($width) ? '' : strval($width), 'HEIGHT' => is_null($height) ? '' : strval($height)));
            $_category->attach($temp);

            $i++;
        }

        $_category = do_template('FORM_SCREEN_INPUT_THEME_IMAGE_CATEGORY', array('_GUID' => 'c2f429315b73bcaacc3bff8db11c0056', 'DISPLAY' => $category_expanded ? 'block' : 'none', 'FIELD_NAME' => $name, 'CATEGORY' => $_category, 'CATEGORY_NAME' => (count($categories) == 1) ? '' : $cat));
        $content->attach($_category);
    }

    $input = do_template('FORM_SCREEN_INPUT_RADIO_LIST', array('_GUID' => '35fed772f022cf561f823543e56d63e8', 'REQUIRED' => !$allow_none, 'NAME' => $name, 'CODE' => is_null($selected_code) ? '' : $selected_code, 'TABINDEX' => strval($tabindex), 'CONTENT' => $content));

    return _form_input($GLOBALS['DOING_ALTERNATE_FIELDS_SET'] ? $name : '', $pretty_name, $description, $input, !$allow_none);
}

/**
 * Get the Tempcode for a date input, or if cron is not on, return blank.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The parameter name for this input field
 * @param  boolean $required Whether this is a required field
 * @param  boolean $null_default Whether this field is empty by default
 * @param  boolean $do_time Whether to input time for this field also
 * @param  ?mixed $default_time The default timestamp to use (either TIME or array of time components) (null: none) [ignored if $null_default is set]
 * @param  ?integer $total_years_to_show The number of years to allow selection from (pass a negative number for selection of past years instead of future years) (null: no limit)
 * @param  ?integer $year_start The year to start counting $total_years_to_show from (null: if $total_years_to_show<0 then this year minus $total_years_to_show else if $total_years_to_show is not null then the current year else no restriction)
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_date__scheduler($pretty_name, $description, $name, $required, $null_default, $do_time, $default_time = null, $total_years_to_show = 10, $year_start = null, $tabindex = null)
{
    if (cron_installed()) {
        return form_input_date($pretty_name, $description, $name, $required, $null_default, $do_time, $default_time, $total_years_to_show, $year_start, $tabindex);
    }
    return new Tempcode();
}

/**
 * Get the Tempcode for a date input. Use post_param_date to read the result.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The parameter name for this input field
 * @param  boolean $required Whether this is not a required field
 * @param  boolean $null_default Whether this field is empty by default
 * @param  boolean $do_time Whether to input time for this field also
 * @param  ?mixed $default_time The default timestamp to use (either TIME or array of time components) (null: none) [ignored if $null_default is set]
 * @param  ?integer $total_years_to_show The number of years to allow selection from (pass a negative number for selection of past years instead of future years) (null: no limit)
 * @param  ?integer $year_start The year to start counting $total_years_to_show from (null: if $total_years_to_show<0 then this year minus $total_years_to_show else if $total_years_to_show is not null then the current year else no restriction)
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $do_date Whether to input date for this field (if false, will just do time)
 * @param  ?ID_TEXT $timezone Timezone to input in (null: current user's timezone)
 * @param  boolean $handle_timezone Convert $default_time to $timezone
 * @return Tempcode The input field
 */
function form_input_date($pretty_name, $description, $name, $required, $null_default, $do_time, $default_time = null, $total_years_to_show = 10, $year_start = null, $tabindex = null, $do_date = true, $timezone = null, $handle_timezone = true)
{
    $input = _form_input_date($name, $required, $null_default, $do_time, $default_time, $total_years_to_show, $year_start, $tabindex, $do_date, $timezone, $handle_timezone);
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a date input, raw.
 *
 * @param  ID_TEXT $name The parameter name for this input field
 * @param  boolean $required Whether this is a required field
 * @param  boolean $null_default Whether this field is empty by default
 * @param  boolean $do_time Whether to input time for this field also
 * @param  ?mixed $default_time The default timestamp to use (either TIME or array of time components) (null: none) [ignored if $null_default is set]
 * @param  ?integer $total_years_to_show The number of years to allow selection from (pass a negative number for selection of past years instead of future years) (null: no limit)
 * @param  ?integer $year_start The year to start counting $total_years_to_show from (null: if $total_years_to_show<0 then this year minus $total_years_to_show else if $total_years_to_show is not null then the current year else no restriction)
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @param  boolean $do_date Whether to input date for this field (if false, will just do time)
 * @param  ?ID_TEXT $timezone Timezone to input in (null: current user's timezone)
 * @param  boolean $handle_timezone Convert $default_time to $timezone
 * @return Tempcode The input field
 *
 * @ignore
 */
function _form_input_date($name, $required, $null_default, $do_time, $default_time = null, $total_years_to_show = 10, $year_start = null, $tabindex = null, $do_date = true, $timezone = null, $handle_timezone = true)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $default_minute = mixed();
    $default_hour = mixed();
    $default_month = mixed();
    $default_day = mixed();
    $default_year = mixed();

    $required = filter_form_field_required($name, $required);

    if ((is_array($default_time)) && ($default_time[4] < 1970) && (@strftime('%Y', @mktime(0, 0, 0, 1, 1, 1963)) != '1963')) { // Some systems can't do negative timestamps. Actually the maximum negative integer size is also an issue
        list($default_minute, $default_hour, $default_month, $default_day, $default_year) = $default_time;
        if (is_null($default_minute)) {
            $default_minute = 0;
        }
        if (is_null($default_hour)) {
            $default_hour = 0;
        }
    } else {
        if (is_array($default_time)) {
            if (is_null($default_time[4])) {
                $default_time = null;
            } else {
                list($default_minute, $default_hour, $default_month, $default_day, $default_year) = $default_time;
                $default_time = mktime($default_hour, $default_minute, 0, $default_month, $default_day, $default_year);
            }
        }

        $_default_time = filter_form_field_default($name, is_null($default_time) ? '' : strval($default_time));
        $default_time = ($_default_time == '') ? null : intval($_default_time);

        if ((!is_null($default_time)) && ($handle_timezone)) {
            if (is_null($timezone)) {
                $timezone = get_users_timezone();
            }
            $default_time = tz_time($default_time, $timezone);
        }

        $default_minute = is_null($default_time) ? null : intval(date('i', $default_time));
        $default_hour = is_null($default_time) ? null : intval(date('H', $default_time));
        $default_day = is_null($default_time) ? null : intval(date('j', $default_time));
        $default_month = is_null($default_time) ? null : intval(date('n', $default_time));
        $default_year = is_null($default_time) ? null : intval(date('Y', $default_time));
    }

    if (($total_years_to_show !== null) && ($year_start === null)) {
        if ($total_years_to_show < 0) {
            $year_start = intval(date('Y')) + $total_years_to_show;
        } else {
            $year_start = intval(date('Y'));
        }
    }
    // Fix if out of range
    if ((!is_null($year_start)) && (!is_null($default_year)) && ($default_year < $year_start)) {
        $year_start = $default_year;
    }

    $year_end = mixed();
    if ((!is_null($total_years_to_show)) && (!is_null($year_start))) {
        if ($total_years_to_show >= 0) {
            $year_end = $year_start + $total_years_to_show;
        } else {
            $year_end = $year_start - $total_years_to_show;
        }
    }
    // Fix if out of range
    if ((!is_null($default_year)) && (!is_null($year_end)) && ($default_year > $year_end)) {
        $year_end = $default_year;
    }

    if ($null_default) {
        $default_minute = null;
        $default_hour = null;
        $default_day = null;
        $default_month = null;
        $default_year = null;
    }

    if ($do_date) {
        $type = $do_time ? 'datetime' : 'date';
    } else {
        $type = 'time';
    }

    return do_template($do_date ? 'FORM_SCREEN_INPUT_DATE' : 'FORM_SCREEN_INPUT_TIME', array(
        '_GUID' => '5ace58dd0f540f70fb3bd440fb02a430',
        'REQUIRED' => $required,
        'TABINDEX' => strval($tabindex),
        'NAME' => $name,
        'TYPE' => $type,

        'YEAR' => is_null($default_year) ? '' : strval($default_year),
        'MONTH' => is_null($default_month) ? '' : strval($default_month),
        'DAY' => is_null($default_day) ? '' : strval($default_day),
        'HOUR' => is_null($default_hour) ? '' : strval($default_hour),
        'MINUTE' => is_null($default_minute) ? '' : strval($default_minute),

        'MIN_DATE_DAY' => is_null($year_start) ? '' : '1',
        'MIN_DATE_MONTH' => is_null($year_start) ? '' : '1',
        'MIN_DATE_YEAR' => is_null($year_start) ? '' : strval($year_start),
        'MAX_DATE_DAY' => is_null($year_end) ? '' : '31',
        'MAX_DATE_MONTH' => is_null($year_end) ? '' : '12',
        'MAX_DATE_YEAR' => is_null($year_end) ? '' : strval($year_end),
    ));
}

/**
 * Get the Tempcode for an integer-only input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?integer $default The default value for this input field (null: no default)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_integer($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $_default = filter_form_field_default($name, is_null($default) ? '' : strval($default));
    $default = ($_default == '') ? null : intval($_default);

    $required = filter_form_field_required($name, $required);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_INTEGER', array('_GUID' => 'da09e21f329f300f71dd4dd518cb6242', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => is_null($default) ? '' : strval($default)));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for an width/height input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name_width The name which this input field is for: width part
 * @param  ID_TEXT $name_height The name which this input field is for: height part
 * @param  ?integer $default_width The default value for this input field: width part (null: no default)
 * @param  ?integer $default_height The default value for this input field: height part (null: no default)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_dimensions($pretty_name, $description, $name_width, $name_height, $default_width, $default_height, $required, $tabindex = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $_default_width = filter_form_field_default($name_width, is_null($default_width) ? '' : strval($default_width));
    $default_width = ($_default_width == '') ? null : intval($_default_width);
    $_default_height = filter_form_field_default($name_height, is_null($default_height) ? '' : strval($default_height));
    $default_height = ($_default_height == '') ? null : intval($_default_height);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_DIMENSIONS', array(
        '_GUID' => 'd8ccbe6813e4d1a0c41a25adb87d5c35',
        'TABINDEX' => strval($tabindex),
        'REQUIRED' => $_required,
        'NAME_WIDTH' => $name_width,
        'DEFAULT_WIDTH' => is_null($default_width) ? '' : strval($default_width),
        'NAME_HEIGHT' => $name_height,
        'DEFAULT_HEIGHT' => is_null($default_height) ? '' : strval($default_height),
    ));
    return _form_input($name_width, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Get the Tempcode for a float-only input.
 *
 * @param  mixed $pretty_name A human intelligible name for this input field
 * @param  mixed $description A description for this input field
 * @param  ID_TEXT $name The name which this input field is for
 * @param  ?float $default The default value for this input field (null: no default)
 * @param  boolean $required Whether this is a required input field
 * @param  ?integer $tabindex The tab index of the field (null: not specified)
 * @return Tempcode The input field
 */
function form_input_float($pretty_name, $description, $name, $default, $required, $tabindex = null)
{
    $tabindex = get_form_field_tabindex($tabindex);

    $_default = filter_form_field_default($name, is_null($default) ? '' : strval($default));
    $default = ($_default == '') ? null : floatval($_default);

    $required = filter_form_field_required($name, $required);

    $_required = ($required) ? '_required' : '';
    $input = do_template('FORM_SCREEN_INPUT_FLOAT', array('_GUID' => '6db802ae840bfe7e87881f95c79133c4', 'TABINDEX' => strval($tabindex), 'REQUIRED' => $_required, 'NAME' => $name, 'DEFAULT' => is_null($default) ? '' : float_to_raw_string($default, 10, true)));
    return _form_input($name, $pretty_name, $description, $input, $required, false, $tabindex);
}

/**
 * Start off a field set.
 *
 * IMPORTANT: Note that this function uses global state -- any fields generated between alternate_fields_set__start and alternate_fields_set__end will be rendered using field set templating.
 *
 * @param  ID_TEXT $set_name The codename for this field set
 * @return Tempcode Tempcode to start attaching the field set to
 */
function alternate_fields_set__start($set_name)
{
    global $DOING_ALTERNATE_FIELDS_SET;
    if (!is_null($DOING_ALTERNATE_FIELDS_SET)) {
        warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }
    $DOING_ALTERNATE_FIELDS_SET = $set_name;
    return new Tempcode();
}

/**
 * Show a field set that has just been finished off.
 *
 * @param  ID_TEXT $set_name The codename for this field set (blank: just collect raw fields, no actual field set)
 * @param  mixed $pretty_name The human-readable name for this field set
 * @param  mixed $description The human-readable description for this field set
 * @param  Tempcode $fields The field set Tempcode
 * @param  boolean $required Whether it is required that this field set be filled in
 * @param  ?URLPATH $existing_image_preview_url Image URL to show, of the existing selection for this field (null: N/A) (blank: N/A)
 * @param  boolean $raw Whether we just want the raw set contents, without any wrapper field row
 * @return Tempcode The field set
 */
function alternate_fields_set__end($set_name, $pretty_name, $description, $fields, $required, $existing_image_preview_url = null, $raw = false)
{
    global $DOING_ALTERNATE_FIELDS_SET;
    if ($DOING_ALTERNATE_FIELDS_SET === null) {
        return $fields; // Didn't actually start set, probably because some logic said not to - so just flow to append as normal
    }

    if ((!is_null($existing_image_preview_url)) && ($existing_image_preview_url != '')) {
        if (url_is_local($existing_image_preview_url)) {
            $existing_image_preview_url = get_custom_base_url() . '/' . $existing_image_preview_url;
        }
    }

    $set = do_template('FORM_SCREEN_FIELDS_SET', array('_GUID' => 'ae81cf68280aef067de1e8e71b2919a7', 'FIELDS' => $fields, 'PRETTY_NAME' => $pretty_name, 'SET_NAME' => $set_name, 'REQUIRED' => $required, 'EXISTING_IMAGE_PREVIEW_URL' => $existing_image_preview_url));

    if (is_null($DOING_ALTERNATE_FIELDS_SET)) {
        warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }
    $DOING_ALTERNATE_FIELDS_SET = null;

    if ($raw) {
        return $set;
    }

    return _form_input('', $pretty_name, $description, $set, $required);
}

/**
 * Start serving single field. This is used if you just need the raw widget, not the label (etc).
 */
function single_field__start()
{
    global $DOING_ALTERNATE_FIELDS_SET;
    $DOING_ALTERNATE_FIELDS_SET = '';
}

/**
 * Stop serving single field.
 */
function single_field__end()
{
    global $DOING_ALTERNATE_FIELDS_SET;
    $DOING_ALTERNATE_FIELDS_SET = null;
}

/**
 * Used to create a null option for field sets.
 *
 * @param  mixed $pretty_name The human-readable name for this field
 * @param  ?integer $tabindex The tab index (null: none specified)
 * @return Tempcode The field
 */
function form_input_na($pretty_name, $tabindex = null)
{
    return _form_input('', $pretty_name, new Tempcode(), new Tempcode(), false, false, null);
}

/**
 * Helper function to show an input field.
 *
 * @param  ID_TEXT $name The codename for this field (blank: N/A)
 * @param  mixed $pretty_name The human-readable name for this field
 * @param  mixed $description The human-readable description for this field
 * @param  Tempcode $input The actual raw input field
 * @param  boolean $required Whether it is required that this field be filled in
 * @param  boolean $comcode Whether this field may contain Comcode
 * @param  ?integer $tabindex The tab index (null: none specified)
 * @param  boolean $w Whether it is a textarea field
 * @param  boolean $skip_label Whether to skip displaying a label for the field
 * @param  mixed $description_side A secondary side description for this input field
 * @param  ?string $pattern_error Custom regex pattern validation error (null: none)
 * @return Tempcode The field
 *
 * @ignore
 */
function _form_input($name, $pretty_name, $description, $input, $required, $comcode = false, $tabindex = null, $w = false, $skip_label = false, $description_side = '', $pattern_error = null)
{
    check_suhosin_request_quantity(2, ($name == '') ? 20 : strlen($name));

    if (($GLOBALS['DEV_MODE']) && (user_lang() == fallback_lang())) {
        $_description = trim(strip_tags(is_object($description) ? $description->evaluate() : $description));
        if (($_description != '') && (substr($_description, -1) != '.') && (substr(is_object($description) ? $description->evaluate() : $description, -6) != '</kbd>') && (substr($_description, -1) != '!') && (substr($_description, -1) != '?') && (substr($_description, -1) != ']') && (substr($_description, -1) != ')') && (!$GLOBALS['NO_DEV_MODE_FULLSTOP_CHECK'])) {
            fatal_exit('Form field $description parameters should end in full stops [' . $_description . '].');
        }
    }

    $help_zone = get_comcode_zone('userguide_comcode', false);
    $_comcode = ((is_null($help_zone)) || (!$comcode)) ? new Tempcode() : do_template('COMCODE_MESSAGE', array('_GUID' => '7668b8365e34b2484be7c2c271f82e79', 'NAME' => $name, 'W' => $w, 'URL' => build_url(array('page' => 'userguide_comcode'), $help_zone)));

    global $DOING_ALTERNATE_FIELDS_SET, $SKIPPING_LABELS;
    if ($DOING_ALTERNATE_FIELDS_SET !== null) {
        if ($DOING_ALTERNATE_FIELDS_SET == '') {
            return $input;
        }

        $tpl = do_template('FORM_SCREEN_FIELDS_SET_ITEM', array(
            '_GUID' => '23f2e2df7fcacc01d9f5158dc635e73d',
            'SET_NAME' => $DOING_ALTERNATE_FIELDS_SET,
            'REQUIRED' => $required,
            'SKIP_LABEL' => $skip_label || $SKIPPING_LABELS,
            'NAME' => $name,
            'PRETTY_NAME' => $pretty_name,
            'DESCRIPTION' => $description,
            'DESCRIPTION_SIDE' => $description_side,
            'INPUT' => $input,
            'COMCODE' => $_comcode,
        ));
        return $tpl;
    }

    $tpl = do_template('FORM_SCREEN_FIELD', array('_GUID' => 'fa1402b7ad8319372f4bb5b152be7852', 'REQUIRED' => $required, 'SKIP_LABEL' => $skip_label || $SKIPPING_LABELS, 'NAME' => $name, 'PRETTY_NAME' => $pretty_name, 'DESCRIPTION' => $description, 'DESCRIPTION_SIDE' => $description_side, 'INPUT' => $input, 'COMCODE' => $_comcode, 'PATTERN_ERROR' => $pattern_error));
    return $tpl;
}

/**
 * Look for editing conflicts, and setup editing pinging.
 *
 * @param  ?ID_TEXT $id The ID we're editing (null: get from param, 'id')
 * @param  boolean $only_staff Whether to only care about staff conflicts
 * @return array A pair: warning details, ping url
 */
function handle_conflict_resolution($id = null, $only_staff = false)
{
    if (($only_staff) && (!$GLOBALS['FORUM_DRIVER']->is_staff(get_member()))) {
        return array(null, null);
    }

    if (get_param_integer('refreshing', 0) == 1) {
        return array(null, null);
    }

    if (is_null($id)) {
        $id = get_param_string('id', post_param_string('id', null), true);
        if ($id === null) {
            return array(null, null);
        }
    }

    require_javascript('ajax');
    $last_edit_screen_time = $GLOBALS['SITE_DB']->query('SELECT * FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'edit_pings WHERE ' . db_string_equal_to('the_page', cms_mb_substr(get_page_name(), 0, 80)) . ' AND ' . db_string_equal_to('the_type', cms_mb_substr(get_param_string('type', 'browse'), 0, 80)) . ' AND ' . db_string_equal_to('the_id', cms_mb_substr($id, 0, 80)) . ' AND the_member<>' . strval(get_member()) . ' ORDER BY the_time DESC', 1);
    if ((array_key_exists(0, $last_edit_screen_time)) && ($last_edit_screen_time[0]['the_time'] > time() - 20)) {
        $username = $GLOBALS['FORUM_DRIVER']->get_username($last_edit_screen_time[0]['the_member']);
        if (is_null($username)) {
            $username = '?';
        }
        $warning_details = do_template('WARNING_BOX', array('_GUID' => '10c4e7c0d16df68b38b66d162919c068', 'WARNING' => do_lang_tempcode('EDIT_CONFLICT_WARNING', escape_html($username))));
    } else {
        $warning_details = null;
    }
    $keep = symbol_tempcode('KEEP');
    $ping_url = find_script('edit_ping') . '?page=' . urlencode(get_page_name()) . '&type=' . urlencode(get_param_string('type', 'browse')) . '&id=' . urlencode($id) . $keep->evaluate();

    return array($warning_details, $ping_url);
}

/**
 * Helper function for tab-index linearisation (serves as a filter).
 *
 * @param  ?integer $tabindex Requested tab-index (null: no specific request)
 * @return integer Used tab-index
 */
function get_form_field_tabindex($tabindex = null)
{
    if ($tabindex === null) {
        global $TABINDEX;
        $tabindex = $TABINDEX;
        $TABINDEX++;
    }
    return $tabindex;
}
