From 72770937f216f8e6bbf168ec3647b45b1e32b342 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 00:32:24 +0700 Subject: [PATCH 01/17] [ticket/17151] Make settings forms use macros PHPBB3-17151 --- phpBB/includes/acp/acp_board.php | 160 ++++++++++++++---- phpBB/includes/functions.php | 18 +- phpBB/includes/functions_acp.php | 55 +++--- phpBB/phpbb/template/twig/extension/forms.php | 13 +- .../all/template/macros/forms/select.twig | 11 +- .../all/template/macros/forms/textarea.twig | 3 +- .../template/ucp_prefs_personal.html | 4 +- 7 files changed, 187 insertions(+), 77 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d0ced6788f..1f8f632788 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -851,7 +851,7 @@ class acp_board /* @var $auth_providers \phpbb\auth\provider_collection */ $auth_providers = $phpbb_container->get('auth.provider_collection'); - $auth_plugins = array(); + $auth_plugins = []; foreach ($auth_providers as $key => $value) { @@ -864,14 +864,22 @@ class acp_board sort($auth_plugins); - $auth_select = ''; + $auth_select_options = []; foreach ($auth_plugins as $method) { - $selected = ($selected_method == $method) ? ' selected="selected"' : ''; - $auth_select .= "'; + $auth_select_options[] = [ + 'value' => $method, + 'selected' => $selected_method == $method, + 'label' => ucfirst($method), + 'data' => [ + 'toggle-setting' => "#auth_{$method}_settings", + ], + ]; } - return $auth_select; + return [ + 'options' => $auth_select_options, + ]; } /** @@ -881,15 +889,21 @@ class acp_board { global $user; - $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP'); - $s_smtp_auth_options = ''; + $auth_methods = ['PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP']; + $s_smtp_auth_options = []; foreach ($auth_methods as $method) { - $s_smtp_auth_options .= ''; + $s_smtp_auth_options[] = [ + 'value' => $method, + 'selected' => $selected_method == $method, + 'label' => $user->lang('SMTP_' . str_replace('-', '_', $method)), + ]; } - return $s_smtp_auth_options; + return [ + 'options' => $s_smtp_auth_options, + ]; } /** @@ -899,7 +913,22 @@ class acp_board { global $user; - return ''; + $full_folder_select_options = [ + 0 => [ + 'value' => 1, + 'selected' => $value == 1, + 'label' => $user->lang('DELETE_OLDEST_MESSAGES'), + ], + 1 => [ + 'value' => 2, + 'selected' => $value == 2, + 'label' => $user->lang('HOLD_NEW_MESSAGES_SHORT'), + ], + ]; + + return [ + 'options' => $full_folder_select_options, + ]; } /** @@ -907,7 +936,7 @@ class acp_board */ function select_ip_check($value, $key = '') { - $radio_ary = array(4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION'); + $radio_ary = [4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION']; return h_radio('config[ip_check]', $radio_ary, $value, $key); } @@ -917,7 +946,7 @@ class acp_board */ function select_ref_check($value, $key = '') { - $radio_ary = array(REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION'); + $radio_ary = [REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION']; return h_radio('config[referer_validation]', $radio_ary, $value, $key); } @@ -929,23 +958,28 @@ class acp_board { global $user, $config; - $act_ary = array( - 'ACC_DISABLE' => array(true, USER_ACTIVATION_DISABLE), - 'ACC_NONE' => array(true, USER_ACTIVATION_NONE), - 'ACC_USER' => array($config['email_enable'], USER_ACTIVATION_SELF), - 'ACC_ADMIN' => array($config['email_enable'], USER_ACTIVATION_ADMIN), - ); + $act_ary = [ + 'ACC_DISABLE' => [true, USER_ACTIVATION_DISABLE], + 'ACC_NONE' => [true, USER_ACTIVATION_NONE], + 'ACC_USER' => [$config['email_enable'], USER_ACTIVATION_SELF], + 'ACC_ADMIN' => [$config['email_enable'], USER_ACTIVATION_ADMIN], + ]; - $act_options = ''; + $act_options = []; foreach ($act_ary as $key => $data) { list($available, $value) = $data; - $selected = ($selected_value == $value) ? ' selected="selected"' : ''; - $class = (!$available) ? ' class="disabled-option"' : ''; - $act_options .= ''; + $act_options[] = [ + 'value' => $value, + 'selected' => $selected_value == $value, + 'label' => $user->lang($key), + 'disabled' => !$available, + ]; } - return $act_options; + return [ + 'options' => $act_options, + ]; } /** @@ -955,7 +989,27 @@ class acp_board { global $user; - return ' ' . $user->lang['MIN_CHARS'] . '   ' . $user->lang['MAX_CHARS']; + return [ + [ + 'tag' => 'input', + 'id' => $key, + 'type' => 'number', + 'name' => 'config[min_name_chars]', + 'min' => 1, + 'max' => 999, + 'value' => $value, + 'append' => $user->lang('MIN_CHARS') . '  ', + ], + [ + 'tag' => 'input', + 'type' => 'number', + 'name' => 'config[max_name_chars]', + 'min' => 8, + 'max' => 180, + 'value' => $this->new_config['max_name_chars'], + 'append' => $user->lang('MAX_CHARS'), + ], + ]; } /** @@ -965,15 +1019,20 @@ class acp_board { global $user; - $user_char_ary = array('USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII'); - $user_char_options = ''; + $user_char_ary = ['USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII']; + $user_char_options = []; foreach ($user_char_ary as $user_type) { - $selected = ($selected_value == $user_type) ? ' selected="selected"' : ''; - $user_char_options .= ''; + $user_char_options[] = [ + 'value' => $user_type, + 'selected' => $selected_value == $user_type, + 'label' => $user->lang($user_type), + ]; } - return $user_char_options; + return [ + 'options' => $user_char_options, + ]; } /** @@ -983,7 +1042,16 @@ class acp_board { global $user; - return ' ' . $user->lang['MIN_CHARS']; + return [ + [ + 'tag' => 'input', + 'id' => $key, + 'type' => 'number', + 'name' => 'config[min_pass_chars]', + 'value' => $value, + 'append' => $user->lang('MIN_CHARS'), + ], + ]; } /** @@ -994,14 +1062,20 @@ class acp_board global $user; $pass_type_ary = array('PASS_TYPE_ANY', 'PASS_TYPE_CASE', 'PASS_TYPE_ALPHA', 'PASS_TYPE_SYMBOL'); - $pass_char_options = ''; + $pass_char_options = []; foreach ($pass_type_ary as $pass_type) { - $selected = ($selected_value == $pass_type) ? ' selected="selected"' : ''; - $pass_char_options .= ''; + $pass_char_options[] = [ + 'tag' => 'select', + 'value' => $pass_type, + 'selected' => $selected_value == $pass_type, + 'label' => $user->lang[$pass_type], + ]; } - return $pass_char_options; + return [ + 'options' => $pass_char_options, + ]; } /** @@ -1359,8 +1433,22 @@ class acp_board { global $user; - return ' - '; + return [ + [ + 'tag' => 'input', + 'type' => 'submit', + 'name' => $key, + 'id' => $key, + 'class' => 'button2', + 'value' => $user->lang('SEND_TEST_EMAIL'), + ], + [ + 'tag' => 'textarea', + 'name' => $key . '_text', + 'id' => $key . '_text', + 'placeholder' => $user->lang('MESSAGE'), + ], + ]; } /** diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index bca9a97c48..0bdb36eb79 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -291,7 +291,9 @@ function phpbb_language_select(\phpbb\db\driver\driver_interface $db, string $de ]; } - return $lang_options; + return [ + 'options' => $lang_options + ]; } /** @@ -319,14 +321,20 @@ function style_select($default = '', $all = false, array $styledata = []) $db->sql_freeresult($result); } - $style_options = ''; + $style_options = []; foreach ($styledata as $row) { - $selected = ($row['style_id'] == $default) ? ' selected="selected"' : ''; - $style_options .= ''; + $style_options[] = [ + 'tag' => 'select', + 'value' => $row['style_id'], + 'selected' => $row['style_id'] == $default, + 'label' => $row['style_name'], + ]; } - return $style_options; + return [ + 'options' => $style_options, + ]; } /** diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index b8f007cdfd..6058b948fb 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -398,6 +398,32 @@ function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $conf $tpl['buttons'] = [$no_button, $yes_button]; } break; + + case 'select': + $tpl = [ + 'tag' => 'select', + 'class' => $tpl_type['class'] ?? false, + 'id' => $key, + 'data' => $tpl_type['data'] ?? [], + 'name' => $name, + 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), + 'options' => $tpl_type['options'], + 'group_only' => $tpl_type['group_only'] ?? false, + 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, + 'multiple' => $tpl_type['multiple'] ?? false, + ]; + break; + + case 'button': + $tpl = [ + 'tag' => 'input', + 'class' => $tpl_type['options']['class'], + 'id' => $key, + 'type' => $tpl_type['options']['type'], + 'name' => $tpl_type['options']['name'] ?? $name, + 'value' => $tpl_type['options']['value'], + ]; + break; } return $tpl; @@ -484,38 +510,15 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) $return = call_user_func_array($call, $args); - if ($tpl_type[0] == 'select') + if (in_array($tpl_type[0], ['select', 'button'])) { - $size = (isset($tpl_type[1])) ? (int) $tpl_type[1] : 1; - - if (is_string($return)) - { - $data_toggle = (!empty($tpl_type[2])) ? ' data-togglable-settings="true"' : ''; - - $tpl = ''; - } - else - { - $tpl = [ - 'tag' => 'select', - 'id' => $key, - 'name' => $name, - 'toggleable' => !empty($tpl_type[2]), - 'options' => $return, - ]; - - // Add size if it differs from default value of 1 - if ($size != 1) - { - $tpl['size'] = $size; - } - } + $tpl_type = array_merge($tpl_type, $return); + $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); } else { $tpl = $return; } - break; default: diff --git a/phpBB/phpbb/template/twig/extension/forms.php b/phpBB/phpbb/template/twig/extension/forms.php index 4d8de94b9d..ee47e19449 100644 --- a/phpBB/phpbb/template/twig/extension/forms.php +++ b/phpBB/phpbb/template/twig/extension/forms.php @@ -179,7 +179,7 @@ class forms extends AbstractExtension 'CLASS' => (string) ($form_data['class'] ?? ''), 'ID' => (string) ($form_data['id'] ?? ''), 'DATA' => $form_data['data'] ?? [], - 'NAME' => (string) $form_data['name'], + 'NAME' => (string) ($form_data['name'] ?? ''), 'TOGGLEABLE' => (bool) ($form_data['toggleable'] ?? false), 'OPTIONS' => $form_data['options'] ?? [], 'GROUP_ONLY' => (bool) ($form_data['group_only'] ?? false), @@ -206,13 +206,14 @@ class forms extends AbstractExtension try { return $environment->render('macros/forms/textarea.twig', [ - 'CLASS' => (string) ($form_data['class'] ?? ''), + 'CLASS' => (string) ($form_data['class'] ?? ''), 'ID' => (string) $form_data['id'], - 'DATA' => $form_data['data'] ?? [], + 'DATA' => $form_data['data'] ?? [], 'NAME' => (string) $form_data['name'], - 'ROWS' => (int) $form_data['rows'], - 'COLS' => (int) $form_data['cols'], - 'CONTENT' => (string) $form_data['content'], + 'ROWS' => (int) ($form_data['rows'] ?? ''), + 'COLS' => (int) ($form_data['cols'] ?? ''), + 'CONTENT' => (string) ($form_data['content'] ?? ''), + 'PLACEHOLDER' => (string) ($form_data['placeholder'] ?? ''), ]); } catch (\Twig\Error\Error $e) diff --git a/phpBB/styles/all/template/macros/forms/select.twig b/phpBB/styles/all/template/macros/forms/select.twig index 3cab1e7199..1ff690268f 100644 --- a/phpBB/styles/all/template/macros/forms/select.twig +++ b/phpBB/styles/all/template/macros/forms/select.twig @@ -21,11 +21,18 @@ label="{{ element.label }}"> {% endapply %} {% for option in element.options %} - + {% endfor %} {% else %} - + {% endif %} {% endfor %} diff --git a/phpBB/styles/all/template/macros/forms/textarea.twig b/phpBB/styles/all/template/macros/forms/textarea.twig index e4fada13f2..a81c5a09cf 100644 --- a/phpBB/styles/all/template/macros/forms/textarea.twig +++ b/phpBB/styles/all/template/macros/forms/textarea.twig @@ -7,4 +7,5 @@ {% endfor %} name="{{ NAME }}" rows="{{ ROWS }}" - cols="{{ COLS }}">{% endapply %}{{ CONTENT }} + cols="{{ COLS }}" + {% if PLACEHOLDER %}placeholder="{{ PLACEHOLDER }}"{% endif %}>{% endapply %}{{ CONTENT }} diff --git a/phpBB/styles/prosilver/template/ucp_prefs_personal.html b/phpBB/styles/prosilver/template/ucp_prefs_personal.html index 4e81b78fb3..fe1ea17c74 100644 --- a/phpBB/styles/prosilver/template/ucp_prefs_personal.html +++ b/phpBB/styles/prosilver/template/ucp_prefs_personal.html @@ -61,7 +61,9 @@
-
+
+ {{ FormsSelect(S_STYLE_OPTIONS) }} +
From 9350e82d71f19ce29a63f3eec7c5e9c5365086a7 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 10:23:08 +0700 Subject: [PATCH 02/17] [ticket/17151] Fix test error PHPBB3-17151 --- tests/functions_acp/build_cfg_template_test.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/functions_acp/build_cfg_template_test.php b/tests/functions_acp/build_cfg_template_test.php index 76558152ff..8153fdfabf 100644 --- a/tests/functions_acp/build_cfg_template_test.php +++ b/tests/functions_acp/build_cfg_template_test.php @@ -506,13 +506,14 @@ class phpbb_functions_acp_build_cfg_template_test extends phpbb_test_case public function select_helper() { - return build_select( - array( - '1' => 'First_Option', - '2' => 'Second_Option', - '3' => 'Third_Option', - ), - '2' - ); + return [ + 'options' => build_select( + [ + '1' => 'First_Option', + '2' => 'Second_Option', + '3' => 'Third_Option', + ], + '2'), + ]; } } From 52517a5efde8805328fc20f7a2c0964b3dae6888 Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 13:16:57 +0700 Subject: [PATCH 03/17] [ticket/17151] Fix registration test error PHPBB3-17151 --- phpBB/adm/style/acp_bots.html | 8 ++++++-- phpBB/adm/style/acp_users_prefs.html | 4 +++- phpBB/includes/acp/acp_board.php | 16 ++++++++++++++-- phpBB/includes/acp/acp_bots.php | 23 +++++++++++++++++------ phpBB/includes/acp/acp_users.php | 6 +++++- phpBB/includes/functions.php | 9 ++------- phpBB/includes/ucp/ucp_prefs.php | 10 +++++++--- 7 files changed, 54 insertions(+), 22 deletions(-) diff --git a/phpBB/adm/style/acp_bots.html b/phpBB/adm/style/acp_bots.html index b4f8ea5072..8332df7e2b 100644 --- a/phpBB/adm/style/acp_bots.html +++ b/phpBB/adm/style/acp_bots.html @@ -27,7 +27,9 @@

{L_BOT_STYLE_EXPLAIN}
-
+
+ {{ FormsSelect(S_STYLE_OPTIONS) }} +

{L_BOT_LANG_EXPLAIN}
@@ -37,7 +39,9 @@
-
+
+ {{ FormsSelect(S_ACTIVE_OPTIONS) }} +

{L_BOT_AGENT_EXPLAIN}
diff --git a/phpBB/adm/style/acp_users_prefs.html b/phpBB/adm/style/acp_users_prefs.html index 68420389b4..d485d1e113 100644 --- a/phpBB/adm/style/acp_users_prefs.html +++ b/phpBB/adm/style/acp_users_prefs.html @@ -48,7 +48,9 @@
-
+
+ {{ FormsSelect(S_STYLE_OPTIONS) }} +
diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 1f8f632788..1e82c0e6d5 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -86,8 +86,8 @@ class acp_board 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'custom', 'method' => 'timezone_select', 'explain' => true), 'legend2' => 'BOARD_STYLE', - 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), - 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), + 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'method' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), + 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'method' => 'style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), 'override_user_style' => array('lang' => 'OVERRIDE_STYLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'legend3' => 'WARNINGS', @@ -1127,6 +1127,18 @@ class acp_board return phpbb_language_select($db, $default, $langdata); } + /** + * Wrapper function for style_select() + * + * @return array + */ + public function style_select(): array + { + global $db; + + return ['options' => style_select()]; + } + /** * Board disable option and message */ diff --git a/phpBB/includes/acp/acp_bots.php b/phpBB/includes/acp/acp_bots.php index 323dd1baee..982a8967b7 100644 --- a/phpBB/includes/acp/acp_bots.php +++ b/phpBB/includes/acp/acp_bots.php @@ -321,12 +321,15 @@ class acp_bots unset($bot_row['user_lang'], $bot_row['user_style']); } - $s_active_options = ''; + $s_active_options = []; $_options = array('0' => 'NO', '1' => 'YES'); foreach ($_options as $value => $lang) { - $selected = ($bot_row['bot_active'] == $value) ? ' selected="selected"' : ''; - $s_active_options .= ''; + $s_active_options[] = [ + 'value' => $value, + 'selected' => $bot_row['bot_active'] == $value, + 'label' => $user->lang($lang), + ]; } $style_select = style_select($bot_row['bot_style'], true); @@ -345,14 +348,22 @@ class acp_bots 'BOT_AGENT' => $bot_row['bot_agent'], 'S_EDIT_BOT' => true, - 'S_ACTIVE_OPTIONS' => $s_active_options, - 'S_STYLE_OPTIONS' => $style_select, + 'S_ACTIVE_OPTIONS' => [ + 'id' => 'bot_active', + 'name' => 'bot_active', + 'options' => $s_active_options, + ], + 'S_STYLE_OPTIONS' => [ + 'id' => 'bot_style', + 'name' => 'bot_style', + 'options' => $style_select, + ], 'LANG_OPTIONS' => [ 'id' => 'bot_lang', 'name' => 'bot_lang', 'options' => $lang_options, ], - 'S_ERROR' => (count($error)) ? true : false, + 'S_ERROR' => (bool) count($error), )); return; diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 7a5036f931..32be512d81 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1838,7 +1838,11 @@ class acp_users 'name' => 'lang', 'options' => $lang_options, ], - 'S_STYLE_OPTIONS' => style_select($data['style']), + 'S_STYLE_OPTIONS' => [ + 'id' => 'style', + 'name' => 'style', + 'options' => style_select($data['style']) + ], 'TIMEZONE_OPTIONS' => [ 'tag' => 'select', 'name' => 'tz', diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 0bdb36eb79..68e4e75dda 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -291,9 +291,7 @@ function phpbb_language_select(\phpbb\db\driver\driver_interface $db, string $de ]; } - return [ - 'options' => $lang_options - ]; + return $lang_options; } /** @@ -325,16 +323,13 @@ function style_select($default = '', $all = false, array $styledata = []) foreach ($styledata as $row) { $style_options[] = [ - 'tag' => 'select', 'value' => $row['style_id'], 'selected' => $row['style_id'] == $default, 'label' => $row['style_name'], ]; } - return [ - 'options' => $style_options, - ]; + return $style_options; } /** diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index d2fc72621d..103f647ab0 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -205,14 +205,18 @@ class ucp_prefs 'name' => 'lang', 'options' => $lang_options, ], - 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['user_style'], false, $styles_row), + 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : [ + 'id' => 'user_style', + 'name' => 'user_style', + 'options' => style_select($data['user_style'], false, $styles_row) + ], 'TIMEZONE_OPTIONS' => [ 'tag' => 'select', 'name' => 'tz', 'options' => $timezone_select, ], - 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, - 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) + 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, + 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) ); break; From 7b04e411b62423ea4c640331b2805786c069f03b Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 15:30:52 +0700 Subject: [PATCH 04/17] [ticket/17151] Fix tests PHPBB3-17151 --- phpBB/includes/acp/acp_board.php | 8 +- .../all/template/macros/forms/select.twig | 4 +- tests/acp_board/select_auth_method_test.php | 32 ++++- .../fixtures/ext/foo/bar/acp/main_module.php | 24 +++- tests/functions/style_select_test.php | 121 ++++++++++++++++-- .../functions_acp/build_cfg_template_test.php | 15 ++- 6 files changed, 180 insertions(+), 24 deletions(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 1e82c0e6d5..1babdceb01 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -970,10 +970,10 @@ class acp_board { list($available, $value) = $data; $act_options[] = [ - 'value' => $value, - 'selected' => $selected_value == $value, - 'label' => $user->lang($key), - 'disabled' => !$available, + 'value' => $value, + 'selected' => $selected_value == $value, + 'label' => $user->lang($key), + 'class' => !$available ? 'disabled-option' : '', ]; } diff --git a/phpBB/styles/all/template/macros/forms/select.twig b/phpBB/styles/all/template/macros/forms/select.twig index 1ff690268f..052cf89f9b 100644 --- a/phpBB/styles/all/template/macros/forms/select.twig +++ b/phpBB/styles/all/template/macros/forms/select.twig @@ -24,14 +24,14 @@ + {% if option.disabled %} disabled="disabled"{% endif %}{% if option.class %} class="{{ option.class }}"{% endif %}>{{ option.label }} {% endfor %} {% else %} {% endif %} {% endfor %} diff --git a/tests/acp_board/select_auth_method_test.php b/tests/acp_board/select_auth_method_test.php index e06b5f845d..a9efdbc0ad 100644 --- a/tests/acp_board/select_auth_method_test.php +++ b/tests/acp_board/select_auth_method_test.php @@ -22,8 +22,36 @@ class phpbb_acp_board_select_auth_method_test extends phpbb_test_case public static function select_auth_method_data() { return [ - ['acp_board_valid', ''], - ['acp_board_invalid', ''], + [ + 'acp_board_valid', + [ + 'options' => [ + 0 => [ + 'value' => 'acp_board_valid', + 'label' => 'Acp_board_valid', + 'selected' => true, + 'data' => [ + 'toggle-setting' => '#auth_acp_board_valid_settings', + ], + ] + ], + ] + ], + [ + 'acp_board_invalid', + [ + 'options' => [ + 0 => [ + 'value' => 'acp_board_valid', + 'label' => 'Acp_board_valid', + 'selected' => false, + 'data' => [ + 'toggle-setting' => '#auth_acp_board_valid_settings', + ], + ] + ], + ] + ], ]; } diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php index 08cb73da07..660d7eb1c9 100644 --- a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php +++ b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php @@ -125,11 +125,25 @@ class main_module function create_select() { - return ' - - - - '; + return [ + 'options' => [ + [ + 'value' => 1, + 'selected' => true, + 'label' => 'Option 1', + ], + [ + 'value' => 2, + 'selected' => false, + 'label' => 'Option 2', + ], + [ + 'value' => 3, + 'selected' => false, + 'label' => 'Option 3', + ], + ] + ]; } function submit_button() diff --git a/tests/functions/style_select_test.php b/tests/functions/style_select_test.php index e36b799bde..aadacadb02 100644 --- a/tests/functions/style_select_test.php +++ b/tests/functions/style_select_test.php @@ -20,14 +20,119 @@ class phpbb_functions_style_select_test extends phpbb_database_test_case static public function style_select_data() { - return array( - array('', false, ''), - array('', true, ''), - array('1', false, ''), - array('1', true, ''), - array('3', false, ''), - array('3', true, ''), - ); + return [ + [ + '', + false, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '', + true, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => false, + 'label' => 'zoo', + ], + ] + ], + [ + '1', + false, + [ + [ + 'value' => '1', + 'selected' => true, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '1', + true, + [ + [ + 'value' => '1', + 'selected' => true, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => false, + 'label' => 'zoo', + ], + ] + ], + [ + '3', + false, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + ] + ], + [ + '3', + true, + [ + [ + 'value' => '1', + 'selected' => false, + 'label' => 'prosilver', + ], + [ + 'value' => '2', + 'selected' => false, + 'label' => 'subsilver2', + ], + [ + 'value' => '3', + 'selected' => true, + 'label' => 'zoo', + ], + ] + ], + ]; } /** diff --git a/tests/functions_acp/build_cfg_template_test.php b/tests/functions_acp/build_cfg_template_test.php index 8153fdfabf..53a1ef3045 100644 --- a/tests/functions_acp/build_cfg_template_test.php +++ b/tests/functions_acp/build_cfg_template_test.php @@ -431,8 +431,11 @@ class phpbb_functions_acp_build_cfg_template_test extends phpbb_test_case ['method' => 'select_helper'], [ 'tag' => 'select', + 'class' => false, 'id' => 'key_name', + 'data' => [], 'name' => 'config[config_key_name]', + 'toggleable' => false, 'options' => [ [ 'value' => 1, @@ -450,7 +453,9 @@ class phpbb_functions_acp_build_cfg_template_test extends phpbb_test_case 'selected' => false, ] ], - 'toggleable' => false, + 'group_only' => false, + 'size' => 1, + 'multiple' => false, ], ], [ @@ -461,9 +466,11 @@ class phpbb_functions_acp_build_cfg_template_test extends phpbb_test_case ['method' => 'select_helper'], [ 'tag' => 'select', + 'class' => false, 'id' => 'key_name', + 'data' => [], 'name' => 'config[config_key_name]', - 'size' => 8, + 'toggleable' => false, 'options' => [ [ 'value' => 1, @@ -481,7 +488,9 @@ class phpbb_functions_acp_build_cfg_template_test extends phpbb_test_case 'selected' => false, ] ], - 'toggleable' => false, + 'group_only' => false, + 'size' => 8, + 'multiple' => false, ], ], ]; From 7f365855ce3a848f365647f0a239bc40a8f28f5d Mon Sep 17 00:00:00 2001 From: rxu Date: Tue, 19 Sep 2023 22:34:18 +0700 Subject: [PATCH 05/17] [ticket/17151] Deduplicate code for building configuration options data PHPBB3-17151 --- phpBB/includes/functions_acp.php | 134 +++++++++++-------------------- 1 file changed, 46 insertions(+), 88 deletions(-) diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index 6058b948fb..277d992f3a 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -247,18 +247,19 @@ function h_radio($name, $input_ary, $input_default = false, $id = false, $key = } /** - * HTML-less version of build_cfg_template + * Build configuration data arrays or templates for configuration settings * - * @param array $tpl_type Template type - * @param string $key Config key - * @param $new_ary - * @param $config_key - * @param $vars - * @return array + * @param array $tpl_type Configuration setting type data + * @param string $key Configuration option name + * @param array $new_ary Updated configuration data + * @param string $config_key Configuration option name + * @param Array $vars Configuration setting data + * + * @return array|string */ -function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $config_key, $vars): array +function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) { - global $language; + global $language, $module, $phpbb_dispatcher; $tpl = []; $name = 'config[' . $config_key . ']'; @@ -399,78 +400,9 @@ function phpbb_build_cfg_template(array $tpl_type, string $key, &$new_ary, $conf } break; - case 'select': - $tpl = [ - 'tag' => 'select', - 'class' => $tpl_type['class'] ?? false, - 'id' => $key, - 'data' => $tpl_type['data'] ?? [], - 'name' => $name, - 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), - 'options' => $tpl_type['options'], - 'group_only' => $tpl_type['group_only'] ?? false, - 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, - 'multiple' => $tpl_type['multiple'] ?? false, - ]; - break; - case 'button': - $tpl = [ - 'tag' => 'input', - 'class' => $tpl_type['options']['class'], - 'id' => $key, - 'type' => $tpl_type['options']['type'], - 'name' => $tpl_type['options']['name'] ?? $name, - 'value' => $tpl_type['options']['value'], - ]; - break; - } - - return $tpl; -} - -/** -* Build configuration template for acp configuration pages -*/ -function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) -{ - global $module, $phpbb_dispatcher; - - $tpl = ''; - $name = 'config[' . $config_key . ']'; - - // Make sure there is no notice printed out for non-existent config options (we simply set them) - if (!isset($new_ary[$config_key])) - { - $new_ary[$config_key] = ''; - } - - switch ($tpl_type[0]) - { - case 'password': - case 'text': - case 'url': - case 'email': - case 'tel': - case 'search': - case 'color': - case 'datetime': - case 'datetime-local': - case 'month': - case 'week': - case 'date': - case 'time': - case 'number': - case 'range': - case 'dimension': - case 'textarea': - case 'radio': - $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); - break; - case 'select': case 'custom': - if (isset($vars['method'])) { $call = array($module->module, $vars['method']); @@ -513,7 +445,33 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) if (in_array($tpl_type[0], ['select', 'button'])) { $tpl_type = array_merge($tpl_type, $return); - $tpl = phpbb_build_cfg_template($tpl_type, $key, $new_ary, $config_key, $vars); + + if ($tpl_type[0] == 'select') + { + $tpl = [ + 'tag' => 'select', + 'class' => $tpl_type['class'] ?? false, + 'id' => $key, + 'data' => $tpl_type['data'] ?? [], + 'name' => $name, + 'toggleable' => !empty($tpl_type[2]) || !empty($tpl_type['toggleable']), + 'options' => $tpl_type['options'], + 'group_only' => $tpl_type['group_only'] ?? false, + 'size' => $tpl_type[1] ?? $tpl_type['size'] ?? 1, + 'multiple' => $tpl_type['multiple'] ?? false, + ]; + } + else + { + $tpl = [ + 'tag' => 'input', + 'class' => $tpl_type['options']['class'], + 'id' => $key, + 'type' => $tpl_type['options']['type'], + 'name' => $tpl_type['options']['name'] ?? $name, + 'value' => $tpl_type['options']['value'], + ]; + } } else { @@ -542,15 +500,15 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) * Overwrite the html code we display for the config value * * @event core.build_config_template - * @var array tpl_type Config type array: - * 0 => data type - * 1 [optional] => string: size, int: minimum - * 2 [optional] => string: max. length, int: maximum - * @var string key Should be used for the id attribute in html - * @var array new Array with the config values we display - * @var string name Should be used for the name attribute - * @var array vars Array with the options for the config - * @var string tpl The resulting html code we display + * @var array tpl_type Config type array: + * 0 => data type + * 1 [optional] => string: size, int: minimum + * 2 [optional] => string: max. length, int: maximum + * @var string key Should be used for the id attribute in html + * @var array new Array with the config values we display + * @var string name Should be used for the name attribute + * @var array vars Array with the options for the config + * @var array|string tpl The resulting html code we display * @since 3.1.0-a1 */ $vars = array('tpl_type', 'key', 'new', 'name', 'vars', 'tpl'); From 1cbe1d86da92ab652fc5a716a5d41486888b8d17 Mon Sep 17 00:00:00 2001 From: rxu Date: Wed, 20 Sep 2023 17:36:46 +0700 Subject: [PATCH 06/17] [ticket/17151] Improve radio input type handling - allow more than 2 buttons count - allow custom buttons order - allow custom button labels Implemented by allowing JSON data format, backward compatibility preserved. PHPBB3-17151 --- phpBB/includes/functions_acp.php | 60 +++++++++++-------- phpBB/phpbb/template/twig/extension/forms.php | 5 +- .../template/macros/forms/radio_buttons.twig | 5 +- tests/functional/extension_module_test.php | 10 ++++ .../fixtures/ext/foo/bar/acp/main_module.php | 1 + 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index 277d992f3a..cd7e8a382c 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -270,6 +270,12 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) $new_ary[$config_key] = ''; } + // For BC check if parameter is json format and if yes split $tpl_type in 2 parts only + if (isset($tpl_type[1]) && strpos($tpl_type[1], '{') !== false) + { + $tpl_type = explode(':', $vars['type'], 2); + } + switch ($tpl_type[0]) { case 'password': @@ -369,35 +375,37 @@ function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) break; case 'radio': - $tpl_type_cond = explode('_', $tpl_type[1]); - $type_no = $tpl_type_cond[0] != 'disabled' && $tpl_type_cond[0] != 'enabled'; - - $no_button = [ - 'type' => 'radio', - 'name' => $name, - 'value' => 0, - 'checked' => !$new_ary[$config_key], - 'label' => $type_no ? $language->lang('NO') : $language->lang('DISABLED'), - ]; - - $yes_button = [ - 'id' => $key, - 'type' => 'radio', - 'name' => $name, - 'value' => 1, - 'checked' => (bool) $new_ary[$config_key], - 'label' => $type_no ? $language->lang('YES') : $language->lang('ENABLED'), - ]; - - $tpl = ['tag' => 'radio']; - if ($tpl_type_cond[0] == 'yes' || $tpl_type_cond[0] == 'enabled') + // Convert 'old' radio button parameters to json encoded string for BC + if (in_array($tpl_type[1], ['yes_no', 'enabled_disabled'])) { - $tpl['buttons'] = [$yes_button, $no_button]; + $params = explode('_', $tpl_type[1]); + $tpl_type[1] = '{"' . $params[0] . '":1, "' . $params[1] . '":0}'; } - else + + $params = json_decode($tpl_type[1], true); + $id_assigned = false; + $buttons = []; + foreach ($params as $param => $value) { - $tpl['buttons'] = [$no_button, $yes_button]; - } + $buttons[] = [ + 'type' => 'radio', + 'name' => $name, + 'value' => $value, + 'checked' => $new_ary[$config_key] == $value, + 'label' => $language->lang(strtoupper($param)), + ]; + + // Only assign id to the one (1st) button in the list + if (!$id_assigned) + { + $buttons[key($buttons)]['id'] = $key; + } + }; + + $tpl = [ + 'tag' => 'radio', + 'buttons' => $buttons, + ]; break; case 'button': diff --git a/phpBB/phpbb/template/twig/extension/forms.php b/phpBB/phpbb/template/twig/extension/forms.php index ee47e19449..72be295e22 100644 --- a/phpBB/phpbb/template/twig/extension/forms.php +++ b/phpBB/phpbb/template/twig/extension/forms.php @@ -151,10 +151,7 @@ class forms extends AbstractExtension try { return $environment->render('macros/forms/radio_buttons.twig', [ - 'FIRST_BUTTON' => $form_data['buttons'][0], - 'FIRST_BUTTON_LABEL' => $form_data['buttons'][0]['label'], - 'SECOND_BUTTON' => $form_data['buttons'][1], - 'SECOND_BUTTON_LABEL' => $form_data['buttons'][1]['label'], + 'BUTTONS' => $form_data['buttons'], ]); } catch (\Twig\Error\Error $e) diff --git a/phpBB/styles/all/template/macros/forms/radio_buttons.twig b/phpBB/styles/all/template/macros/forms/radio_buttons.twig index 1ef7804c29..922eb1b432 100644 --- a/phpBB/styles/all/template/macros/forms/radio_buttons.twig +++ b/phpBB/styles/all/template/macros/forms/radio_buttons.twig @@ -1,2 +1,3 @@ - - +{% for button in BUTTONS %} + +{% endfor %} diff --git a/tests/functional/extension_module_test.php b/tests/functional/extension_module_test.php index 67dc35ae73..6894f5d644 100644 --- a/tests/functional/extension_module_test.php +++ b/tests/functional/extension_module_test.php @@ -101,6 +101,16 @@ class phpbb_functional_extension_module_test extends phpbb_functional_test_case $this->assertStringContainsString('SETTING_9', $crawler->filter('dl')->eq(9)->filter('dt > label[for="setting_9"]')->text()); $this->assertStringContainsString('SETTING_9_EXPLAIN', $crawler->filter('dl')->eq(9)->filter('dt > span')->text()); $this->assertEquals(2, $crawler->filter('dl')->eq(9)->filter('dd > label > input[type="radio"]')->count()); + + $this->assertStringContainsString('SETTING_10', $crawler->filter('dl')->eq(10)->filter('dt > label[for="setting_10"]')->text()); + $this->assertStringContainsString('SETTING_10_EXPLAIN', $crawler->filter('dl')->eq(10)->filter('dt > span')->text()); + $this->assertEquals(3, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->count()); + $this->assertEquals(1, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(0)->attr('value')); + $this->assertStringContainsString('LABEL_1', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(0)->text()); + $this->assertEquals(3, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(1)->attr('value')); + $this->assertStringContainsString('LABEL_3', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(1)->text()); + $this->assertEquals(2, $crawler->filter('dl')->eq(10)->filter('dd > label > input[type="radio"]')->eq(2)->attr('value')); + $this->assertStringContainsString('LABEL_2', $crawler->filter('dl')->eq(10)->filter('dd > label')->eq(2)->text()); } public function test_ucp() diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php index 660d7eb1c9..77174c95f6 100644 --- a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php +++ b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php @@ -49,6 +49,7 @@ class main_module 'setting_7' => ['lang' => 'SETTING_7', 'validate' => 'email', 'type' => 'email:0:100', 'explain' => true], 'setting_8' => ['lang' => 'SETTING_8', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true], 'setting_9' => ['lang' => 'SETTING_9', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true], + 'setting_10'=> ['lang' => 'SETTING_10', 'validate' => 'bool', 'type' => 'radio:{"LABEL_1":1, "LABEL_3":3, "LABEL_2":2}', 'explain' => true], ] ]; From 130366edfa7a1565007ff41e45a9e19ea37747e0 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 23 Sep 2023 01:27:00 +0700 Subject: [PATCH 07/17] [ticket/17151] Fix PHP warning on board settings page, add ACP pages test PHPBB3-17151 --- phpBB/includes/acp/acp_board.php | 2 +- tests/functional/acp_main_test.php | 38 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 1babdceb01..ff3dbba6d4 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -1124,7 +1124,7 @@ class acp_board { global $db; - return phpbb_language_select($db, $default, $langdata); + return ['options' => phpbb_language_select($db, $default, $langdata)]; } /** diff --git a/tests/functional/acp_main_test.php b/tests/functional/acp_main_test.php index d392102e1a..2caa3bbae2 100644 --- a/tests/functional/acp_main_test.php +++ b/tests/functional/acp_main_test.php @@ -28,4 +28,42 @@ class phpbb_functional_acp_main_test extends phpbb_functional_test_case $this->assertContainsLang('DATABASE_SIZE', $crawler->filter('tbody > tr')->eq(2)->filter('td[class="tabled"]')->eq(0)->text()); $this->assertNotContainsLang('NOT_AVAILABLE', $crawler->filter('tbody > tr')->eq(2)->filter('td[class="tabled"]')->eq(1)->text()); } + + public function test_all_acp_module_links() + { + $this->add_lang('common'); + $this->login(); + $this->admin_login(); + + // Browse ACP main page + $crawler = self::request('GET', 'index.php'); + $crawler = self::$client->click($crawler->selectLink($this->lang('ACP_SHORT'))->link()); + + // Get all ACP module URLs array + $acp_modules = $crawler->filter('.tabs a')->each( + function ($node, $i) + { + return $node->link(); + } + ); + + // Browse all ACP modules and get their mode URLs array + $acp_submodules = []; + foreach ($acp_modules as $module) + { + $crawler = self::$client->click($module); + $acp_submodules = array_merge($acp_submodules, $crawler->filter('.menu-block > ul a')->each( + function ($node, $i) + { + return $node->link(); + } + )); + } + + // Browse all ACP submodules' modes + foreach ($acp_submodules as $acp_submodule) + { + self::$client->click($acp_submodule); + } + } } From 830c1f3dc3e7375ad9b95833d59e110c203d0e51 Mon Sep 17 00:00:00 2001 From: rxu Date: Sat, 23 Sep 2023 10:21:07 +0700 Subject: [PATCH 08/17] [ticket/17151] Adjust template output formatting PHPBB3-17151 --- phpBB/styles/all/template/macros/forms/input.twig | 2 +- phpBB/styles/all/template/macros/forms/select.twig | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/phpBB/styles/all/template/macros/forms/input.twig b/phpBB/styles/all/template/macros/forms/input.twig index 1a2ff13fa3..dcfeddf369 100644 --- a/phpBB/styles/all/template/macros/forms/input.twig +++ b/phpBB/styles/all/template/macros/forms/input.twig @@ -1,4 +1,4 @@ -{% apply replace({"\n": ' ', "\t": ''}) %} +{% apply replace({"\n\t": ' ', "\t": '', "\n": ''}) %} -{% endapply %} + {% if TOGGLEABLE %}data-togglable-settings="true"{% endif %} + {% if MULTIPLE %}multiple="multiple"{% endif %} + {% if SIZE %}size="{{ SIZE }}"{% endif %}> {% for element in OPTIONS %} {% if not GROUP_ONLY and element.options %} - {% apply replace({"\n": ' ', '\t': ''}) %} - {% endapply %} {% for option in element.options %}