PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.83
51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 All 39 releases
← All changes | includes/widgets/Form_Builder/Form_Builder.php +424 -25 51.1.7651.1.83 View file →
@@ -85,9 +85,11 @@
85 85 'email' => 'Email',
86 86 'redirect' => 'Redirect',
87 87 'pro-sb' => 'Submission (Pro)',
88 88 'pro-mch' => 'Mailchimp (Pro)',
89 - 'pro-wh' => 'Webhook (Pro)'
89 + 'pro-wh' => 'Webhook (Pro)',
90 + 'integrations' => 'Integrations (Pro)',
91 + 'payment' => 'Payment (Pro)'
90 92 ];
91 93 }
92 94
93 95 public function register_settings_section_submissions($widget)
@@ -155,8 +157,235 @@
155 157
156 158 $widget->end_controls_section();
157 159 }
158 160
161 + /**
162 + * Settings for the Integrations action.
163 + *
164 + * Everything here is stored per form and read back on the server when a
165 + * submission arrives; nothing travels through the browser.
166 + *
167 + * @param \Elementor\Widget_Base $widget Widget the controls belong to.
168 + *
169 + * @return void
170 + */
171 + public function register_settings_section_integrations($widget)
172 + {
173 + if (!class_exists('King_Addons\\Form_Integrations')) {
174 + return;
175 + }
176 +
177 + $widget->start_controls_section(
178 + $this->get_control_id('section_integrations'),
179 + [
180 + 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Integrations', 'king-addons'),
181 + 'tab' => Controls_Manager::TAB_CONTENT,
182 + 'condition' => [
183 + 'submit_actions' => 'integrations',
184 + ],
185 + ]
186 + );
187 +
188 + $widget->add_control(
189 + 'integration_services',
190 + [
191 + 'label' => esc_html__('Send to', 'king-addons'),
192 + 'type' => Controls_Manager::SELECT2,
193 + 'multiple' => true,
194 + 'label_block' => true,
195 + 'options' => Form_Integrations::services(),
196 + 'render_type' => 'none',
197 + ]
198 + );
199 +
200 + $widget->add_control(
201 + 'integration_email_field',
202 + [
203 + 'label' => esc_html__('Email field ID', 'king-addons'),
204 + 'type' => Controls_Manager::TEXT,
205 + 'description' => esc_html__('Which field holds the address. Left empty, the first value that looks like an email is used.', 'king-addons'),
206 + 'render_type' => 'none',
207 + ]
208 + );
209 +
210 + $widget->add_control(
211 + 'integration_name_field',
212 + [
213 + 'label' => esc_html__('Name field ID', 'king-addons'),
214 + 'type' => Controls_Manager::TEXT,
215 + 'render_type' => 'none',
216 + ]
217 + );
218 +
219 + $text_controls = [
220 + ['slack_webhook_url', esc_html__('Slack webhook URL', 'king-addons'), 'slack', 'https://hooks.slack.com/services/…'],
221 + ['slack_title', esc_html__('Slack message title', 'king-addons'), 'slack', ''],
222 + ['telegram_bot_token', esc_html__('Telegram bot token', 'king-addons'), 'telegram', ''],
223 + ['telegram_chat_id', esc_html__('Telegram chat ID', 'king-addons'), 'telegram', ''],
224 + ['brevo_api_key', esc_html__('Brevo API key', 'king-addons'), 'brevo', ''],
225 + ['brevo_list_id', esc_html__('Brevo list ID', 'king-addons'), 'brevo', ''],
226 + ['convertkit_api_key', esc_html__('ConvertKit API key', 'king-addons'), 'convertkit', ''],
227 + ['convertkit_form_id', esc_html__('ConvertKit form ID', 'king-addons'), 'convertkit', ''],
228 + ['activecampaign_url', esc_html__('ActiveCampaign API URL', 'king-addons'), 'activecampaign', 'https://your-account.api-us1.com'],
229 + ['activecampaign_api_key', esc_html__('ActiveCampaign API key', 'king-addons'), 'activecampaign', ''],
230 + ['activecampaign_list_id', esc_html__('ActiveCampaign list ID', 'king-addons'), 'activecampaign', ''],
231 + ['hubspot_token', esc_html__('HubSpot private app token', 'king-addons'), 'hubspot', ''],
232 + ['klaviyo_api_key', esc_html__('Klaviyo private API key', 'king-addons'), 'klaviyo', ''],
233 + ['klaviyo_list_id', esc_html__('Klaviyo list ID', 'king-addons'), 'klaviyo', ''],
234 + ['google_sheets_url', esc_html__('Apps Script web app URL', 'king-addons'), 'google_sheets', 'https://script.google.com/macros/s/…/exec'],
235 + ['google_sheets_secret', esc_html__('Shared secret', 'king-addons'), 'google_sheets', ''],
236 + ];
237 +
238 + foreach ($text_controls as $control) {
239 + list($name, $label, $service, $placeholder) = $control;
240 +
241 + $widget->add_control(
242 + $name,
243 + [
244 + 'label' => $label,
245 + 'type' => Controls_Manager::TEXT,
246 + 'label_block' => true,
247 + 'placeholder' => $placeholder,
248 + 'render_type' => 'none',
249 + 'ai' => ['active' => false],
250 + 'condition' => ['integration_services' => $service],
251 + ]
252 + );
253 + }
254 +
255 + $widget->add_control(
256 + 'google_sheets_notice',
257 + [
258 + 'type' => Controls_Manager::RAW_HTML,
259 + 'raw' => esc_html__('Google Sheets is reached through a Google Apps Script web app bound to your sheet: Google\'s own API needs an OAuth flow a form widget cannot run.', 'king-addons'),
260 + 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
261 + 'condition' => ['integration_services' => 'google_sheets'],
262 + ]
263 + );
264 +
265 + $widget->end_controls_section();
266 + }
267 +
268 + /**
269 + * Settings for the Payment action.
270 + *
271 + * @param \Elementor\Widget_Base $widget Widget the controls belong to.
272 + *
273 + * @return void
274 + */
275 + public function register_settings_section_payment($widget)
276 + {
277 + if (!class_exists('King_Addons\\Form_Payments')) {
278 + return;
279 + }
280 +
281 + $widget->start_controls_section(
282 + $this->get_control_id('section_payment'),
283 + [
284 + 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Payment', 'king-addons'),
285 + 'tab' => Controls_Manager::TAB_CONTENT,
286 + 'condition' => [
287 + 'submit_actions' => 'payment',
288 + ],
289 + ]
290 + );
291 +
292 + $widget->add_control(
293 + 'payment_provider',
294 + [
295 + 'label' => esc_html__('Provider', 'king-addons'),
296 + 'type' => Controls_Manager::SELECT,
297 + 'options' => Form_Payments::providers(),
298 + 'default' => 'none',
299 + 'render_type' => 'none',
300 + 'description' => esc_html__('Keys live in King Addons settings. The visitor pays on the provider\'s own page, so no card details reach this site.', 'king-addons'),
301 + ]
302 + );
303 +
304 + $widget->add_control(
305 + 'payment_currency',
306 + [
307 + 'label' => esc_html__('Currency', 'king-addons'),
308 + 'type' => Controls_Manager::TEXT,
309 + 'default' => 'USD',
310 + 'render_type' => 'none',
311 + 'condition' => ['payment_provider!' => 'none'],
312 + ]
313 + );
314 +
315 + $widget->add_control(
316 + 'payment_fixed_amount',
317 + [
318 + 'label' => esc_html__('Fixed amount', 'king-addons'),
319 + 'type' => Controls_Manager::NUMBER,
320 + 'min' => 0,
321 + 'step' => 0.01,
322 + 'render_type' => 'none',
323 + 'condition' => ['payment_provider!' => 'none'],
324 + ]
325 + );
326 +
327 + $widget->add_control(
328 + 'payment_amount_field',
329 + [
330 + 'label' => esc_html__('Or a calculation field ID', 'king-addons'),
331 + 'type' => Controls_Manager::TEXT,
332 + 'render_type' => 'none',
333 + 'description' => esc_html__('The server works the total out again from that field\'s formula; the number the browser shows is never trusted.', 'king-addons'),
334 + 'condition' => ['payment_provider!' => 'none'],
335 + ]
336 + );
337 +
338 + $widget->add_control(
339 + 'payment_max_amount',
340 + [
341 + 'label' => esc_html__('Refuse above', 'king-addons'),
342 + 'type' => Controls_Manager::NUMBER,
343 + 'min' => 0,
344 + 'step' => 0.01,
345 + 'render_type' => 'none',
346 + 'description' => esc_html__('A ceiling for calculated totals. 0 means no ceiling.', 'king-addons'),
347 + 'condition' => ['payment_provider!' => 'none'],
348 + ]
349 + );
350 +
351 + $widget->add_control(
352 + 'payment_description',
353 + [
354 + 'label' => esc_html__('What is being paid for', 'king-addons'),
355 + 'type' => Controls_Manager::TEXT,
356 + 'label_block' => true,
357 + 'render_type' => 'none',
358 + 'condition' => ['payment_provider!' => 'none'],
359 + ]
360 + );
361 +
362 + $widget->add_control(
363 + 'payment_success_url',
364 + [
365 + 'label' => esc_html__('After paying', 'king-addons'),
366 + 'type' => Controls_Manager::TEXT,
367 + 'label_block' => true,
368 + 'render_type' => 'none',
369 + 'description' => esc_html__('A page on this site. Anything else is ignored.', 'king-addons'),
370 + 'condition' => ['payment_provider!' => 'none'],
371 + ]
372 + );
373 +
374 + $widget->add_control(
375 + 'payment_cancel_url',
376 + [
377 + 'label' => esc_html__('If they cancel', 'king-addons'),
378 + 'type' => Controls_Manager::TEXT,
379 + 'label_block' => true,
380 + 'render_type' => 'none',
381 + 'condition' => ['payment_provider!' => 'none'],
382 + ]
383 + );
384 +
385 + $widget->end_controls_section();
386 + }
387 +
159 388 public function register_settings_section_email($widget)
160 389 {
161 390 $widget->start_controls_section(
162 391 $this->get_control_id('section_email'),
@@ -515,8 +744,10 @@
515 744 }
516 745
517 746 public $last_prev_btn_text;
518 747
748 + public $last_next_btn_text;
749 +
519 750 protected function register_controls()
520 751 {
521 752
522 753 $this->start_controls_section(
@@ -548,8 +779,14 @@
548 779 'hidden' => esc_html__('Hidden Field', 'king-addons'),
549 780 'king-addons-fb-step' => esc_html__('Step', 'king-addons'),
550 781 ];
551 782
783 + // Range, rating, signature and acceptance: controls and markup live in
784 + // helpers/Extra_Fields.php.
785 + if (class_exists('King_Addons\\Form_Extra_Fields')) {
786 + $field_types = array_merge($field_types, Form_Extra_Fields::field_types());
787 + }
788 +
552 789 $repeater->add_control(
553 790 'field_type',
554 791 [
555 792 'label' => esc_html__('Type', 'king-addons'),
@@ -570,8 +807,16 @@
570 807 ]
571 808 ]
572 809 );
573 810
811 + if (class_exists('King_Addons\\Form_Extra_Fields')) {
812 + Form_Extra_Fields::register_controls($repeater);
813 + }
814 +
815 + if (class_exists('King_Addons\\Form_Conditional_Logic')) {
816 + Form_Conditional_Logic::register_controls($repeater);
817 + }
818 +
574 819 if ('' == get_option('king_addons_recaptcha_v3_site_key')) {
575 820 $repeater->add_control(
576 821 'recaptcha_key_notice',
577 822 [
@@ -1408,8 +1653,35 @@
1408 1653 ]
1409 1654 );
1410 1655 }
1411 1656
1657 + if (class_exists('King_Addons\\Form_Partial_Entries')) {
1658 + $this->add_control(
1659 + 'partial_entries',
1660 + [
1661 + 'label' => esc_html__('Save partial entries', 'king-addons'),
1662 + 'type' => Controls_Manager::SWITCHER,
1663 + 'return_value' => 'yes',
1664 + 'separator' => 'before',
1665 + 'description' => esc_html__('Keeps what a visitor typed even if they never press submit. Drafts appear alongside submissions and are removed after 30 days.', 'king-addons'),
1666 + ]
1667 + );
1668 + }
1669 +
1670 + if (class_exists('King_Addons\\Form_Spam_Protection')) {
1671 + $this->add_control(
1672 + 'spam_protection',
1673 + [
1674 + 'label' => esc_html__('Spam protection', 'king-addons'),
1675 + 'type' => Controls_Manager::SELECT,
1676 + 'options' => Form_Spam_Protection::modes(),
1677 + 'default' => 'none',
1678 + 'separator' => 'before',
1679 + 'description' => esc_html__('Honeypot needs no account. Turnstile and hCaptcha need their keys in King Addons settings.', 'king-addons'),
1680 + ]
1681 + );
1682 + }
1683 +
1412 1684 $this->end_controls_section();
1413 1685
1414 1686 $this->register_settings_section_submissions($this);
1415 1687
@@ -1416,8 +1688,12 @@
1416 1688 $this->register_settings_section_email($this);
1417 1689
1418 1690 $this->register_settings_section_webhook($this);
1419 1691
1692 + $this->register_settings_section_integrations($this);
1693 +
1694 + $this->register_settings_section_payment($this);
1695 +
1420 1696 $this->register_settings_section_redirect($this);
1421 1697
1422 1698 $this->register_settings_section_mailchimp();
1423 1699
@@ -3277,16 +3553,39 @@
3277 3553 }
3278 3554
3279 3555 public function get_attribute_name($item)
3280 3556 {
3281 - return "form_fields[{$item['field_id']}]";
3557 + return 'form_fields[' . self::resolve_field_key($item) . ']';
3282 3558 }
3283 3559
3284 3560 public function get_attribute_id($item)
3285 3561 {
3562 + return 'form-field-' . self::resolve_field_key($item);
3563 + }
3286 3564
3287 - $id_suffix = !empty($item['field_id']) ? $item['field_id'] : $item['_id'];
3288 - return 'form-field-' . $id_suffix;
3565 + /**
3566 + * Field ID used as the submitted name/key.
3567 + *
3568 + * An empty Field ID used to produce name="form_fields[]", which PHP cannot
3569 + * map back onto the field. Fall back to Elementor's repeater _id so imported
3570 + * and programmatic forms still keep their values.
3571 + *
3572 + * @param array<string,mixed> $item Repeater row.
3573 + *
3574 + * @return string
3575 + */
3576 + public static function resolve_field_key($item): string
3577 + {
3578 + if (!is_array($item)) {
3579 + return '';
3580 + }
3581 +
3582 + $id = isset($item['field_id']) ? trim((string) $item['field_id']) : '';
3583 + if ('' !== $id) {
3584 + return $id;
3585 + }
3586 +
3587 + return isset($item['_id']) ? trim((string) $item['_id']) : '';
3289 3588 }
3290 3589
3291 3590 protected function make_textarea_field($item, $item_index)
3292 3591 {
@@ -3366,9 +3665,10 @@
3366 3665 <!--suppress HtmlFormInputWithoutLabel -->
3367 3666 <select <?php $this->print_render_attribute_string('select' . $i); ?>>
3368 3667 <?php
3369 3668 foreach ($options as $key => $option) :
3370 - $option_id = $item['field_id'] . $key;
3669 + $field_key = self::resolve_field_key($item);
3670 + $option_id = $field_key . $key;
3371 3671 $option_value = esc_attr($option);
3372 3672 $option_label = esc_html($option);
3373 3673
3374 3674 if (false !== strpos($option, '|')) {
@@ -3404,9 +3704,10 @@
3404 3704 $html = '';
3405 3705 if ($options) {
3406 3706 $html .= '<div class="king-addons-field-sub-group ' . esc_attr($item['css_classes']) . ' ' . esc_attr($item['inline_list']) . '">';
3407 3707 foreach ($options as $key => $option) {
3408 - $element_id = ($item['field_id'] ? esc_attr($item['field_id']) : $item['field_type']) . $key;
3708 + $field_key = self::resolve_field_key($item);
3709 + $element_id = ($field_key ? esc_attr($field_key) : $item['field_type']) . $key;
3409 3710 $html_id = $this->get_attribute_id($item) . '-' . $key;
3410 3711 $option_label = $option;
3411 3712 $option_value = $option;
3412 3713
@@ -3431,9 +3732,9 @@
3431 3732 if ($item['required'] && ('radio' === $type || 'checkbox' === $type)) {
3432 3733 $this->add_required_attribute($element_id);
3433 3734 }
3434 3735
3435 - $html .= '<span class="king-addons-form-field-option" data-key="form-field-' . esc_attr($item['field_id']) . '"><input ' . $this->get_render_attribute_string($element_id) . '> <label for="' . esc_attr($html_id) . '">' . esc_html($option_label) . '</label></span>';
3736 + $html .= '<span class="king-addons-form-field-option" data-key="form-field-' . esc_attr(self::resolve_field_key($item)) . '"><input ' . $this->get_render_attribute_string($element_id) . '> <label for="' . esc_attr($html_id) . '">' . esc_html($option_label) . '</label></span>';
3436 3737 }
3437 3738 $html .= '</div>';
3438 3739 }
3439 3740
@@ -3448,9 +3749,9 @@
3448 3749 'class' => [
3449 3750 'king-addons-form-field-type-' . $item['field_type'],
3450 3751 'king-addons-field-group',
3451 3752 'king-addons-column',
3452 - 'king-addons-field-group-' . esc_attr($item['field_id']),
3753 + 'king-addons-field-group-' . esc_attr(self::resolve_field_key($item)),
3453 3754 ],
3454 3755 ],
3455 3756 'input' . $i => [
3456 3757 'type' => ('acceptance' === $item['field_type']) ? 'checkbox' : (('upload' === $item['field_type']) ? 'file' : $item['field_type']),
@@ -3479,9 +3780,18 @@
3479 3780
3480 3781
3481 3782 $this->add_render_attribute('field-group' . $i, 'class', 'elementor-repeater-item-' . esc_attr($item['_id']));
3482 3783
3784 + // Conditional logic travels to the browser as one data attribute; the
3785 + // script evaluates it on every change in the form.
3786 + if (class_exists('King_Addons\\Form_Conditional_Logic')) {
3787 + $condition = Form_Conditional_Logic::build($item);
3788 + if (null !== $condition) {
3789 + $this->add_render_attribute('field-group' . $i, 'data-ka-cond', wp_json_encode($condition));
3790 + }
3791 + }
3483 3792
3793 +
3484 3794 if ('true' == $instance['show_placeholders'] && !Utils::is_empty($item['placeholder'])) {
3485 3795 $this->add_render_attribute('input' . $i, 'placeholder', $item['placeholder']);
3486 3796 }
3487 3797
@@ -3505,10 +3815,10 @@
3505 3815
3506 3816 private function render_form_icon($settings)
3507 3817 { ?>
3508 3818 <span <?php echo $this->get_render_attribute_string('icon-align'); ?>>
3509 - <?php Icons_Manager::render_icon($settings['selected_button_icon'], ['aria-hidden' => 'true']); ?>
3510 - <?php if (empty($instance['button_text'])) : ?>
3819 + <?php Icons_Manager::render_icon($settings['selected_button_icon'], ['aria-hidden' => 'true']); ?>
3820 + <?php if (empty($settings['button_text'])) : ?>
3511 3821 <span class="king-addons-hidden-element"><?php echo esc_html__('Submit', 'king-addons'); ?></span>
3512 3822 <?php endif; ?>
3513 3823 </span>
3514 3824 <?php }
@@ -3557,8 +3867,41 @@
3557 3867 update_option('king_addons_referrer_' . $this->get_id(), home_url($_SERVER['REQUEST_URI']));
3558 3868 update_option('king_addons_referrer_title_' . $this->get_id(), get_the_title($post->ID));
3559 3869 update_option('king_addons_webhook_url_' . $this->get_id(), $instance['webhook_url']);
3560 3870
3871 + if (class_exists('King_Addons\\Form_Integrations')) {
3872 + Form_Integrations::save_settings($this->get_id(), $instance);
3873 + }
3874 +
3875 + if (class_exists('King_Addons\\Form_Payments')) {
3876 + Form_Payments::save_settings(
3877 + $this->get_id(),
3878 + $instance,
3879 + isset($post->ID) ? (int) $post->ID : (int) get_the_ID()
3880 + );
3881 + }
3882 +
3883 + $partial_entries = false;
3884 + if (class_exists('King_Addons\\Form_Partial_Entries')) {
3885 + $partial_entries = 'yes' === ($instance['partial_entries'] ?? '')
3886 + && king_addons_freemius()->can_use_premium_code__premium_only();
3887 + Form_Partial_Entries::save_setting($this->get_id(), $partial_entries);
3888 + }
3889 +
3890 + // The submit actions are separate AJAX endpoints, so the challenge has
3891 + // to be checked there; they read it back by form id.
3892 + $spam_mode = 'none';
3893 + if (class_exists('King_Addons\\Form_Spam_Protection')) {
3894 + $spam_mode = (string) ($instance['spam_protection'] ?? 'none');
3895 + Form_Spam_Protection::save_mode($this->get_id(), $spam_mode);
3896 + $spam_mode = Form_Spam_Protection::get_mode($this->get_id());
3897 +
3898 + $spam_script = Form_Spam_Protection::script($spam_mode);
3899 + if (null !== $spam_script) {
3900 + wp_enqueue_script($spam_script['handle'], $spam_script['src'], [], null, true);
3901 + }
3902 + }
3903 +
3561 3904 $emailField = isset($instance['email_field']) ? $instance['email_field'] : '';
3562 3905 $firstNameField = isset($instance['first_name_field']) ? $instance['first_name_field'] : '';
3563 3906 $lastNameField = isset($instance['last_name_field']) ? $instance['last_name_field'] : '';
3564 3907 $addressField = isset($instance['address_field']) ? $instance['address_field'] : '';
@@ -3575,10 +3918,18 @@
3575 3918 'birthday_field' => $birthdayField,
3576 3919 'group_id' => $groupId
3577 3920 ];
3578 3921
3579 - $submit_actions = array_filter($instance['submit_actions'], function ($value) {
3580 - return $value !== 'pro-sb' && $value !== 'pro-mch' && $value !== 'pro-wh';
3922 + // The three original Pro actions are placeholders in the free build;
3923 + // Integrations is a real action, so it is only dropped when Pro is off.
3924 + $stripped_actions = ['pro-sb', 'pro-mch', 'pro-wh'];
3925 + if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
3926 + $stripped_actions[] = 'integrations';
3927 + $stripped_actions[] = 'payment';
3928 + }
3929 +
3930 + $submit_actions = array_filter($instance['submit_actions'], function ($value) use ($stripped_actions) {
3931 + return !in_array($value, $stripped_actions, true);
3581 3932 });
3582 3933 $submit_actions = array_values($submit_actions);
3583 3934
3584 3935 $this->add_render_attribute(
@@ -3601,8 +3952,11 @@
3601 3952 ],
3602 3953 'data-redirect-url' => [
3603 3954 in_array('redirect', $submit_actions) ? esc_url($instance['redirect_to']) : ''
3604 3955 ],
3956 + 'data-partial-entries' => [
3957 + $partial_entries ? '1' : ''
3958 + ],
3605 3959 'data-mailchimp-fields' => [
3606 3960 json_encode($fieldsArray)
3607 3961 ],
3608 3962 'data-list-id' => [
@@ -3738,22 +4092,24 @@
3738 4092
3739 4093 $print_label = !in_array($item['field_type'], ['hidden', 'html', 'king-addons-fb-step'], true);
3740 4094
3741 4095 if ('king-addons-fb-step' === $item['field_type']) {
3742 - if (isset($item['previous_button_text'])) {
3743 - $this->last_prev_btn_text = $item['previous_button_text'];
3744 - }
3745 -
3746 4096 if (0 === $step_count) {
3747 4097 echo '<div class="king-addons-fb-step-tab king-addons-fb-step-tab-hidden">';
3748 4098 } else {
3749 4099 echo '<div class="king-addons-step-buttons-wrap">';
3750 - echo '<button type="button" class="king-addons-fb-step-prev">' . esc_html($item['previous_button_text']) . '</button>';
3751 - echo '<button type="button" class="king-addons-fb-step-next">' . esc_html($item['next_button_text']) . '</button>';
4100 + echo '<button type="button" class="king-addons-fb-step-prev">' . esc_html((string) $this->last_prev_btn_text) . '</button>';
4101 + echo '<button type="button" class="king-addons-fb-step-next">' . esc_html((string) $this->last_next_btn_text) . '</button>';
3752 4102 echo '</div>';
3753 4103 echo '</div>';
3754 4104 echo '<div class="king-addons-fb-step-tab king-addons-fb-step-tab-hidden">';
3755 4105 }
4106 + $this->last_next_btn_text = (isset($item['next_button_text']) && '' !== $item['next_button_text'])
4107 + ? $item['next_button_text']
4108 + : esc_html__('Next', 'king-addons');
4109 + $this->last_prev_btn_text = (isset($item['previous_button_text']) && '' !== $item['previous_button_text'])
4110 + ? $item['previous_button_text']
4111 + : esc_html__('Previous', 'king-addons');
3756 4112 $step_count++;
3757 4113 }
3758 4114
3759 4115 ?>
@@ -3787,9 +4143,23 @@
3787 4143
3788 4144 echo $this->make_radio_checkbox_field($item, $item_index, $item['field_type']);
3789 4145 break;
3790 4146 case 'recaptcha-v3':
3791 - echo '<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" data-site-key="' . esc_attr(get_option('king_addons_recaptcha_v3_site_key')) . '" />';
4147 + $recaptcha_v3_site_key = (string) get_option('king_addons_recaptcha_v3_site_key', '');
4148 + if ('' === $recaptcha_v3_site_key) {
4149 + if (current_user_can('manage_options')) {
4150 + echo '<p class="king-addons-form-captcha-notice">' . sprintf(
4151 + /* translators: 1: provider name, 2: settings URL. */
4152 + esc_html__('%1$s is selected but its keys are missing. Add them under %2$s.', 'king-addons'),
4153 + esc_html__('reCAPTCHA v3', 'king-addons'),
4154 + '<a href="' . esc_url(admin_url('admin.php?page=king-addons-settings')) . '">'
4155 + . esc_html__('King Addons settings', 'king-addons') . '</a>'
4156 + ) . '</p>';
4157 + }
4158 + } else {
4159 + echo '<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" data-site-key="' . esc_attr($recaptcha_v3_site_key) . '" />';
4160 + }
4161 + break;
3792 4162 case 'text':
3793 4163 case 'email':
3794 4164 case 'url':
3795 4165 case 'tel':
@@ -3827,14 +4197,13 @@
3827 4197
3828 4198 if (!empty($non_whitelisted)) {
3829 4199 $item['file_types'] = 'jpg,jpeg,png,gif,pdf,doc,docx,ppt,pptx,odt,avi,ogg,m4a,mov,mp3,mp4,mpg,wav,wmv,txt';
3830 4200 if (is_admin()) {
3831 - echo '<br>';
3832 - echo '<ul class="king-addons-file-type-error">';
3833 - echo esc_html__('Please remove unsupported file type(s):', 'king-addons');
4201 + echo '<p class="king-addons-file-type-error">' . esc_html__('Please remove unsupported file type(s):', 'king-addons') . '</p>';
4202 + echo '<ul class="king-addons-file-type-error-list">';
3834 4203 foreach ($non_whitelisted as $type) {
3835 4204 if (!empty($type)) {
3836 - echo '<li>' . esc_html($type) . ' <li/>';
4205 + echo '<li>' . esc_html($type) . '</li>';
3837 4206 }
3838 4207 }
3839 4208 echo '</ul>';
3840 4209 }
@@ -3849,10 +4218,36 @@
3849 4218 }
3850 4219
3851 4220 echo '<input size="1 "' . $this->get_render_attribute_string('input' . $item_index) . '>';
3852 4221 break;
4222 + case 'range':
4223 + $this->add_render_attribute('input' . $item_index, [
4224 + 'class' => 'king-addons-form-field-range',
4225 + 'min' => isset($item['range_min']) && '' !== $item['range_min'] ? (string) (float) $item['range_min'] : '0',
4226 + 'max' => isset($item['range_max']) && '' !== $item['range_max'] ? (string) (float) $item['range_max'] : '100',
4227 + 'step' => isset($item['range_step']) && '' !== $item['range_step'] && (float) $item['range_step'] > 0 ? (string) (float) $item['range_step'] : '1',
4228 + ]);
4229 + echo '<div class="king-addons-form-range">';
4230 + echo '<input ' . $this->get_render_attribute_string('input' . $item_index) . '>';
4231 + if ('yes' === ($item['range_show_value'] ?? 'yes')) {
4232 + echo '<output class="king-addons-form-range__value"></output>';
4233 + }
4234 + echo '</div>';
4235 + break;
4236 + case 'rating':
4237 + case 'signature':
4238 + case 'acceptance':
4239 + case 'calculation':
4240 + if (class_exists('King_Addons\\Form_Extra_Fields')) {
4241 + echo Form_Extra_Fields::render(
4242 + $item,
4243 + $this->get_attribute_name($item),
4244 + $this->get_attribute_id($item)
4245 + );
4246 + }
4247 + break;
3853 4248 case 'king-addons-fb-step':
3854 - echo '<input type="hidden" class="king-addons-fb-step-input" id="form-field-' . esc_attr($item['field_id']) . '" value="' . esc_attr($item['field_label']) . '">';
4249 + echo '<input type="hidden" class="king-addons-fb-step-input" id="form-field-' . esc_attr(self::resolve_field_key($item)) . '" value="' . esc_attr($item['field_label']) . '">';
3855 4250 break;
3856 4251 default:
3857 4252 $field_type = $item['field_type'];
3858 4253 endswitch;
@@ -3860,12 +4255,16 @@
3860 4255 </div>
3861 4256 <?php
3862 4257 endforeach;
3863 4258
4259 + if ('none' !== $spam_mode && class_exists('King_Addons\\Form_Spam_Protection')) {
4260 + echo Form_Spam_Protection::render($spam_mode); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
4261 + }
4262 +
3864 4263 echo '<div ' . $this->get_render_attribute_string('submit-group') . '>';
3865 4264 if ('exists' === $step_exists) {
3866 4265 if (2 <= $step_count) {
3867 - echo '<button type="button" class="king-addons-fb-step-prev">' . $this->last_prev_btn_text . '</button>';
4266 + echo '<button type="button" class="king-addons-fb-step-prev">' . esc_html((string) $this->last_prev_btn_text) . '</button>';
3868 4267 }
3869 4268
3870 4269 $this->render_submit_button($instance);
3871 4270