Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wpforms-lite/includes/admin/builder/class-builder.php
+++ b/wpforms-lite/includes/admin/builder/class-builder.php
@@ -71,7 +71,7 @@
*
* @since 1.0.0
*
- * @var WP_Post
+ * @var WP_Post|null
*/
public $form;
@@ -119,7 +119,7 @@
*
* @since 1.0.0
*/
- public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks, Generic.Metrics.CyclomaticComplexity.TooHigh
+ public function init(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks, Generic.Metrics.CyclomaticComplexity.TooHigh
// Only load if we are actually on the builder.
if ( ! wpforms_is_admin_page( 'builder' ) ) {
@@ -135,11 +135,11 @@
}
if ( $form_id ) {
- // Default view for with an existing form is fields panel.
+ // The default view for with an existing form is the fields panel.
$this->view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'fields'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} else {
- // The default view for new form is the setup panel.
+ // The default view for the new form is the setup panel.
$this->view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'setup'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
@@ -153,7 +153,8 @@
}
// Fetch form.
- $this->form = wpforms()->obj( 'form' )->get( $form_id );
+ $form_obj = wpforms()->obj( 'form' );
+ $this->form = $form_obj ? $form_obj->get( $form_id ) : null;
if ( ! empty( $form_id ) && empty( $this->form ) ) {
$this->abort_message = esc_html__( 'It looks like the form you are trying to access is no longer available.', 'wpforms-lite' );
@@ -175,15 +176,15 @@
*
* @since 1.6.8
*
- * @param array $template Template data.
- * @param array $form_id Form ID.
+ * @param array $template Template data.
+ * @param WP_Post|false $form_id Form object.
*/
$this->template = apply_filters( 'wpforms_builder_template_active', [], $this->form );
// Load builder panels.
$this->load_panels();
- // Modify meta viewport tag if desktop view is forced.
+ // Modify meta-viewport tag if desktop view is forced.
add_filter( 'admin_viewport_meta', [ $this, 'viewport_meta' ] );
add_action( 'admin_head', [ $this, 'admin_head' ] );
@@ -217,7 +218,7 @@
*
* @since 1.6.8
*/
- public function deregister_common_wp_admin_styles() {
+ public function deregister_common_wp_admin_styles(): void {
if ( ! wpforms_is_admin_page( 'builder' ) ) {
return;
@@ -254,7 +255,7 @@
*
* @since 1.8.8
*/
- public function process_actions() {
+ public function process_actions(): void {
$form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0;
$action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false;
@@ -304,7 +305,7 @@
* @param int $form_id Form ID.
* @param string $action Action name.
*/
- private function process_action( int $form_id, string $action ) {
+ private function process_action( int $form_id, string $action ): void {
$form_handler = wpforms()->obj( 'form' );
@@ -380,7 +381,7 @@
*
* @since 1.0.0
*/
- public function load_panels() {
+ public function load_panels(): void {
// Base class and functions.
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/builder/panels/class-base.php';
@@ -424,7 +425,7 @@
*
* @since 1.4.6
*/
- public function admin_head() {
+ public function admin_head(): void {
// Force hide an admin side menu.
echo '<style>#adminmenumain { display: none !important }</style>';
@@ -445,7 +446,7 @@
* @since 1.0.0
* @since 1.6.8 All the panel's stylesheets restructured and moved here.
*/
- public function enqueues() {
+ public function enqueues(): void {
$this->suppress_conflicts();
@@ -637,6 +638,14 @@
);
wp_enqueue_script(
+ 'wpforms-oops',
+ WPFORMS_PLUGIN_URL . 'assets/lib/oops.min.js',
+ [],
+ '1.0.2',
+ false
+ );
+
+ wp_enqueue_script(
'wpforms-builder',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/admin-builder{$min}.js",
[
@@ -717,7 +726,7 @@
*
* @since 1.9.0
*/
- private function suppress_conflicts() {
+ private function suppress_conflicts(): void {
// Remove conflicting styles (e.g., WP JobSearch plugin).
wp_deregister_style( 'font-awesome' );
@@ -737,6 +746,18 @@
*/
private function get_localized_strings(): array {
+ /**
+ * It is a phpcs bug. This local variable is used below.
+ *
+ * @noinspection PhpUnusedLocalVariableInspection
+ */
+ $min = wpforms_get_min_suffix();
+
+ /**
+ * It is a phpcs bug. This local variable is used below.
+ *
+ * @noinspection PhpUnusedLocalVariableInspection
+ */
$image_extensions = wpforms_chain( get_allowed_mime_types() )
->map(
static function ( $mime ) {
@@ -763,15 +784,17 @@
'date_select_month' => 'MM',
'date_select_year' => 'YYYY',
'debug' => wpforms_debug(),
+ 'version' => WPFORMS_VERSION,
+ 'content_url' => content_url(), // Absolute URL to wp-content directory.
'dynamic_choices' => [
- 'limit_message' => sprintf( /* translators: %1$s - data source name (e.g. Categories, Posts), %2$s - data source type (e.g. post type, taxonomy), %3$s - display limit, %4$s - total number of items. */
+ 'limit_message' => sprintf( /* translators: %1$s - data source name (e.g., Categories, Posts), %2$s - data source type (e.g., post type, taxonomy), %3$s - display limit, %4$s - total number of items. */
esc_html__( 'The %1$s %2$s contains over %3$s items (%4$s). This may make the field difficult for your visitors to use and/or cause the form to be slow.', 'wpforms-lite' ),
'{source}',
'{type}',
'{limit}',
'{total}'
),
- 'empty_message' => sprintf( /* translators: %1$s - data source name (e.g. Categories, Posts), %2$s - data source type (e.g. post type, taxonomy). */
+ 'empty_message' => sprintf( /* translators: %1$s - data source name (e.g., Categories, Posts), %2$s - data source type (e.g., post type, taxonomy). */
esc_html__( 'This field will not be displayed in your form since there are no %2$s belonging to %1$s.', 'wpforms-lite' ),
'{source}',
'{type}'
@@ -832,8 +855,15 @@
'exit_url' => wpforms_current_user_can( 'view_forms' ) ? admin_url( 'admin.php?page=wpforms-overview' ) : admin_url(),
'exit_confirm' => esc_html__( 'Your form contains unsaved changes. Would you like to save your changes first.', 'wpforms-lite' ),
'delete_confirm' => esc_html__( 'Are you sure you want to delete this field?', 'wpforms-lite' ),
+ /* translators: %s - number of fields.*/
+ 'delete_confirm_multiple' => esc_html__( 'Are you sure you want to delete these %s fields?', 'wpforms-lite' ),
+ 'delete_confirm_multiple_title' => esc_html__( 'Delete Fields', 'wpforms-lite' ),
'delete_choice_confirm' => esc_html__( 'Are you sure you want to delete this choice?', 'wpforms-lite' ),
'duplicate_confirm' => esc_html__( 'Are you sure you want to duplicate this field?', 'wpforms-lite' ),
+ 'duplicate_confirm_title' => esc_html__( 'Duplicate Field', 'wpforms-lite' ),
+ /* translators: %s - number of fields. */
+ 'duplicate_confirm_multiple' => esc_html__( 'Are you sure you want to duplicate these %s fields?', 'wpforms-lite' ),
+ 'duplicate_confirm_multiple_title' => esc_html__( 'Duplicate Fields', 'wpforms-lite' ),
'duplicate_copy' => esc_html__( '(copy)', 'wpforms-lite' ),
'error_title' => esc_html__( 'Please enter a form name.', 'wpforms-lite' ),
'error_choice' => esc_html__( 'This item must contain at least one choice.', 'wpforms-lite' ),
@@ -911,7 +941,7 @@
'{to}'
),
'form_meta' => $this->form_data['meta'] ?? [],
- 'scrollbars_css_url' => WPFORMS_PLUGIN_URL . 'assets/css/builder/builder-scrollbars.css',
+ 'scrollbars_css_url' => WPFORMS_PLUGIN_URL . "assets/css/builder/builder-scrollbars$min.css",
'is_ai_disabled' => AIHelpers::is_disabled(),
'connection_label' => esc_html__( 'Connection', 'wpforms-lite' ),
'cl_reference' => sprintf( /* translators: %s - Integration name. */
@@ -924,7 +954,7 @@
$strings['disable_entries'] = sprintf(
wp_kses( /* translators: %s - link to the WPForms.com doc article. */
- __( 'Disabling entry storage for this form will completely prevent any new submissions from getting saved to your site. If you still intend to keep a record of entries through notification emails, then please <a href="%s" target="_blank" rel="noopener noreferrer">test your form</a> to ensure emails send reliably.', 'wpforms-lite' ),
+ __( 'Disabling entry storage for this form will completely prevent any new submissions from getting saved to your site. If you still intend to keep a record of entries through notification emails, then please <a href="%s" target="_blank" rel="noopener noreferrer">test your form</a> to ensure emails are sent reliably.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
@@ -944,7 +974,7 @@
$strings['akismet_not_installed'] = sprintf(
wp_kses( /* translators: %1$s - link to the plugin search page, %2$s - link to the WPForms.com doc article. */
- __( 'This feature cannot be used at this time because the Akismet plugin <a href="%1$s" target="_blank" rel="noopener noreferrer">has not been installed</a>. For information on how to use this feature please <a href="%2$s" target="_blank" rel="noopener noreferrer">refer to our documentation</a>.', 'wpforms-lite' ),
+ __( 'This feature cannot be used at this time because the Akismet plugin <a href="%1$s" target="_blank" rel="noopener noreferrer">has not been installed</a>. For information on how to use this feature, please <a href="%2$s" target="_blank" rel="noopener noreferrer">refer to our documentation</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
@@ -964,8 +994,8 @@
);
$strings['akismet_not_activated'] = sprintf(
- wp_kses( /* translators: %1$s - link to the plugins page, %2$s - link to the WPForms.com doc article. */
- __( 'This feature cannot be used at this time because the Akismet plugin <a href="%1$s" target="_blank" rel="noopener noreferrer">has not been activated</a>. For information on how to use this feature please <a href="%2$s" target="_blank" rel="noopener noreferrer">refer to our documentation</a>.', 'wpforms-lite' ),
+ wp_kses( /* translators: %1$s - link to the plugin page, %2$s - link to the WPForms.com doc article. */
+ __( 'This feature cannot be used at this time because the Akismet plugin <a href="%1$s" target="_blank" rel="noopener noreferrer">has not been activated</a>. For information on how to use this feature, please <a href="%2$s" target="_blank" rel="noopener noreferrer">refer to our documentation</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
@@ -986,7 +1016,7 @@
$strings['akismet_no_api_key'] = sprintf(
wp_kses( /* translators: %1$s - link to the Akismet settings page, %2$s - link to the WPForms.com doc article. */
- __( 'This feature cannot be used at this time because the Akismet plugin <a href="%1$s" target="_blank" rel="noopener noreferrer">has not been properly configured</a>. For information on how to use this feature please <a href="%2$s" target="_blank" rel="noopener noreferrer">refer to our documentation</a>.', 'wpforms-lite' ),
+ __( 'This feature cannot be used at this time because the Akismet plugin <a href="%1$s" target="_blank" rel="noopener noreferrer">has not been properly configured</a>. For information on how to use this feature, please <a href="%2$s" target="_blank" rel="noopener noreferrer">refer to our documentation</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
@@ -1019,6 +1049,8 @@
wpforms_utm_link( 'https://wpforms.com/docs/troubleshooting-403-forbidden-errors/', 'Builder - Settings', '403 Form Errors' )
);
+ $strings['js_modules'] = $this->get_js_modules();
+
/**
* Form Builder localized strings filter.
*
@@ -1042,11 +1074,72 @@
}
/**
+ * Get JS modules.
+ *
+ * Modules become available in the browser context as `WPForms.Admin.Builder.{key}`,
+ * for example, `WPForms.Admin.Builder.CopyPaste`.
+ *
+ * @since 1.9.9
+ *
+ * @return array List of JS modules.
+ */
+ public function get_js_modules(): array {
+
+ $min = wpforms_get_min_suffix();
+
+ $modules = [
+ 'KeyboardShortcuts' => "keyboard-shortcuts$min.js",
+ 'UndoRedoHelpers' => "undo-redo/helpers$min.js",
+ 'UndoRedoHelpersFields' => "undo-redo/helpers-fields$min.js",
+ 'UndoRedoInputCommandBase' => "undo-redo/input-command-base$min.js",
+ 'UndoRedoActionCommandBase' => "undo-redo/action-command-base$min.js",
+ 'UndoRedoInputSimple' => "undo-redo/input-simple$min.js",
+ 'UndoRedoInputToggle' => "undo-redo/input-toggle$min.js",
+ 'UndoRedoInputChoicesJS' => "undo-redo/input-choicesjs$min.js",
+ 'UndoRedoInputSmartTags' => "undo-redo/input-smart-tags$min.js",
+ 'UndoRedoInputCodeMirror' => "undo-redo/input-codemirror$min.js",
+ 'UndoRedoInputTinyMCE' => "undo-redo/input-tinymce$min.js",
+ 'UndoRedoChoicesList' => "undo-redo/choices-list$min.js",
+ 'UndoRedoFormThemes' => "undo-redo/form-themes$min.js",
+ 'UndoRedoDateTimePickers' => "undo-redo/date-time-pickers$min.js",
+ 'UndoRedoActionFieldAdd' => "undo-redo/action-field-add$min.js",
+ 'UndoRedoActionFieldDelete' => "undo-redo/action-field-delete$min.js",
+ 'UndoRedoActionFieldDuplicate' => "undo-redo/action-field-duplicate$min.js",
+ 'UndoRedoActionFieldMove' => "undo-redo/action-field-move$min.js",
+ 'UndoRedoActionMultiFieldDelete' => "undo-redo/action-multi-field-delete$min.js",
+ 'UndoRedoActionMultiFieldDuplicate' => "undo-redo/action-multi-field-duplicate$min.js",
+ 'UndoRedoActionMultiFieldPaste' => "undo-redo/action-multi-field-paste$min.js",
+ 'UndoRedoActionSettingsBlockAdd' => "undo-redo/action-settings-block-add$min.js",
+ 'UndoRedoActionSettingsBlockDelete' => "undo-redo/action-settings-block-delete$min.js",
+ 'UndoRedoActionItemsAddRemove' => "undo-redo/action-items-add-remove$min.js",
+ 'UndoRedoActionImageAddRemove' => "undo-redo/action-image-add-remove$min.js",
+ 'UndoRedo' => "undo-redo$min.js",
+ 'MultiSelectActions' => "multi-select/actions$min.js",
+ 'MultiSelect' => "multi-select/multi-select$min.js",
+ 'MultiSelectKeyboardShortcuts' => "multi-select/keyboard-shortcuts$min.js",
+ 'CopyPaste' => "copy-paste$min.js",
+ ];
+
+ /**
+ * Filters the list of Form Builder JS modules.
+ *
+ * Allows developers to add their own modules.
+ * The modules are loaded asynchronously.
+ * Custom module's path value should be absolute.
+ *
+ * @since 1.9.9
+ *
+ * @param array $modules List of JS modules.
+ */
+ return apply_filters( 'wpforms_builder_js_modules', $modules );
+ }
+
+ /**
* Footer JavaScript.
*
* @since 1.3.7
*/
- public function footer_scripts() {
+ public function footer_scripts(): void {
$countries = wpforms_countries();
$countries_postal = array_keys( $countries );
@@ -1084,9 +1177,9 @@
// phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
/**
- * Choices preset array filter.
+ * Choice preset array filter.
*
- * Allows developers to edit the choices preset used in all choices-based fields.
+ * Allows developers to edit the choice preset used in all choice-based fields.
*
* @since 1.3.7
*
@@ -1120,7 +1213,7 @@
*
* @since 1.0.0
*/
- public function output() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+ public function output(): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
if ( $this->abort ) {
return;
@@ -1139,7 +1232,8 @@
$form_id = $this->form ? absint( $this->form->ID ) : '';
$field_id = ! empty( $this->form_data['field_id'] ) ? $this->form_data['field_id'] : '';
- $revision = wpforms()->obj( 'revisions' )->get_revision();
+ $revisions_obj = wpforms()->obj( 'revisions' );
+ $revision = $revisions_obj ? $revisions_obj->get_revision() : null;
$preview_url = wpforms_get_form_preview_url( $form_id, true );
$allowed_caps = [ 'edit_posts', 'edit_other_posts', 'edit_private_posts', 'edit_published_posts', 'edit_pages', 'edit_other_pages', 'edit_published_pages', 'edit_private_pages' ];
$can_embed = array_filter( $allowed_caps, 'current_user_can' );
@@ -1167,7 +1261,7 @@
* @since 1.7.9
*
* @param array $classes List of classes.
- * @param array|bool $form_data Form data and settings or false when form isn't created.
+ * @param array|bool $form_data Form data and settings or false when the form isn't created.
*/
$builder_classes = (array) apply_filters( 'wpforms_builder_output_classes', $builder_classes, $this->form_data );
@@ -1176,7 +1270,7 @@
*
* @since 1.7.4
*
- * @param string $content Content before toolbar. Defaults to empty string.
+ * @param string $content Content before the toolbar. Defaults to empty string.
*/
$before_toolbar = apply_filters( 'wpforms_builder_output_before_toolbar', '' );
?>
@@ -1349,7 +1443,7 @@
*
* @since 1.7.3
*/
- public function display_abort_message() {
+ public function display_abort_message(): void {
if ( ! $this->abort ) {
return;
@@ -1366,8 +1460,7 @@
}
/**
- * Change default admin meta viewport tag upon request to force scrollable
- * desktop view on small screens.
+ * Change the default admin meta viewport tag upon request to force a scrollable desktop view on small screens.
*
* @since 1.7.8
*
@@ -1419,14 +1512,16 @@
*/
private function get_context_menu_args(): array {
- $args = [
+ $payment_obj = wpforms()->obj( 'payment' );
+ $args = [
'form_id' => $this->form->ID,
'is_form_template' => $this->form->post_type === 'wpforms-template',
- 'has_payments' => wpforms()->obj( 'payment' )->get_by( 'form_id', $this->form->ID ) !== null,
+ 'has_payments' => $payment_obj && $payment_obj->get_by( 'form_id', $this->form->ID ) !== null,
];
if ( wpforms()->is_pro() ) {
- $args['has_entries'] = wpforms()->obj( 'entry' )->get_entries( [ 'form_id' => $this->form->ID ], true ) > 0;
+ $entry_obj = wpforms()->obj( 'entry' );
+ $args['has_entries'] = $entry_obj && $entry_obj->get_entries( [ 'form_id' => $this->form->ID ], true ) > 0;
$args['can_duplicate'] = $this->can_duplicate();
}
--- a/wpforms-lite/includes/admin/builder/panels/class-fields.php
+++ b/wpforms-lite/includes/admin/builder/panels/class-fields.php
@@ -3,6 +3,8 @@
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
+use WPFormsFormsFieldsTraitsMultiFieldMenu as MultiFieldMenuTrait;
+
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
@@ -14,10 +16,14 @@
*/
class WPForms_Builder_Panel_Fields extends WPForms_Builder_Panel {
+ use MultiFieldMenuTrait;
+
/**
* All systems go.
*
* @since 1.0.0
+ *
+ * @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function init() {
@@ -48,6 +54,7 @@
add_action( 'wpforms_builder_fields', [ $this, 'fields' ] );
add_action( 'wpforms_builder_fields_options', [ $this, 'fields_options' ] );
add_action( 'wpforms_builder_preview', [ $this, 'preview' ] );
+ add_filter( 'wpforms_builder_strings', [ $this, 'multi_select_strings' ] );
// Template for form builder previews.
add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'field_preview_templates' ] );
@@ -60,6 +67,8 @@
*
* @since 1.0.0
* @since 1.6.8 All the builder stylesheets enqueues moved to the `WPForms_Builder::enqueues()`.
+ *
+ * @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function enqueues() {
@@ -83,9 +92,35 @@
}
/**
+ * Add Multi-Select Builder strings.
+ *
+ * @since 1.9.9
+ *
+ * @param array $strings Form Builder strings.
+ *
+ * @return array Form Builder strings.
+ */
+ public function multi_select_strings( array $strings ): array {
+
+ $strings['multi_select'] = [
+ 'repeater_tooltip' => esc_html__( "The Repeater field can't be selected with other fields.", 'wpforms-lite' ),
+ 'layout_tooltip' => esc_html__( "The Layout field can't be selected with other fields.", 'wpforms-lite' ),
+ 'general_tooltip' => esc_html__( 'This field can’t be selected alongside Layout or Repeater fields.', 'wpforms-lite' ),
+ /* translators: %s - Cmd or Ctrl key. */
+ 'copy_toast_single' => esc_html__( 'Field copied. Use %1$s + V to paste.', 'wpforms-lite' ),
+ /* translators: %d - number of fields, %s - Cmd or Ctrl key. */
+ 'copy_toast_multiple' => esc_html__( '%1$d fields copied. Use %2$s + V to paste.', 'wpforms-lite' ),
+ ];
+
+ return $strings;
+ }
+
+ /**
* Output the Field panel sidebar.
*
* @since 1.0.0
+ *
+ * @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function panel_sidebar() {
@@ -110,7 +145,7 @@
</ul>
- <div class="wpforms-add-fields wpforms-tab-content">
+ <div id="wpforms-add-fields-tab" class="wpforms-add-fields wpforms-tab-content">
<?php
/**
* Fires to add fields.
@@ -142,6 +177,8 @@
* Output the Field panel primary content.
*
* @since 1.0.0
+ *
+ * @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function panel_content() {
@@ -149,7 +186,7 @@
if ( ! $this->form ) {
echo '<div class="wpforms-alert wpforms-alert-info">';
echo wp_kses(
- __( 'You need to <a href="#" class="wpforms-panel-switch" data-panel="setup">setup your form</a> before you can manage the fields.', 'wpforms-lite' ),
+ __( 'You need to <a href="#" class="wpforms-panel-switch" data-panel="setup">set up your form</a> before you can manage the fields.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
@@ -316,7 +353,7 @@
echo '<button ' . wpforms_html_attributes( $atts['id'], $atts['class'], $atts['data'], $atts['atts'] ) . '>';
if ( $field['icon'] ) {
- echo '<i class="fa ' . esc_attr( $field['icon'] ) . '"></i> ';
+ echo '<i class="fa ' . esc_attr( $field['icon'] ) . '"></i> ';
}
echo esc_html( $field['name'] );
echo '</button>';
@@ -479,6 +516,9 @@
esc_attr__( 'Delete Field', 'wpforms-lite' )
);
+ // Multi-field actions menu.
+ echo $this->get_multi_field_menu_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+
if ( empty( $_COOKIE['wpforms_field_helper_hide'] ) ) {
printf(
'<div class="wpforms-field-helper">
--- a/wpforms-lite/includes/admin/class-about.php
+++ b/wpforms-lite/includes/admin/class-about.php
@@ -603,7 +603,7 @@
<ul class="list-features list-plain">
<li>
<i class="fa fa-check" aria-hidden="true"></i>
- <?php esc_html_e( '7000+ integrations with marketing and payment services', 'wpforms-lite' ); ?>
+ <?php esc_html_e( '9000+ integrations with marketing and payment services', 'wpforms-lite' ); ?>
</li>
<li>
<i class="fa fa-check" aria-hidden="true"></i>
@@ -1358,7 +1358,7 @@
),
'',
wp_kses(
- __( '<strong>Bonus:</strong> 7000+ integrations with Zapier.', 'wpforms-lite' ),
+ __( '<strong>Bonus:</strong> 9000+ integrations with Zapier.', 'wpforms-lite' ),
[
'strong' => [],
]
@@ -1394,7 +1394,7 @@
),
'',
wp_kses(
- __( '<strong>Bonus:</strong> 7000+ integrations with Zapier.', 'wpforms-lite' ),
+ __( '<strong>Bonus:</strong> 9000+ integrations with Zapier.', 'wpforms-lite' ),
[
'strong' => [],
]
@@ -1430,7 +1430,7 @@
),
'',
wp_kses(
- __( '<strong>Bonus:</strong> 7000+ integrations with Zapier.', 'wpforms-lite' ),
+ __( '<strong>Bonus:</strong> 9000+ integrations with Zapier.', 'wpforms-lite' ),
[
'strong' => [],
]
@@ -1466,7 +1466,7 @@
),
'',
wp_kses(
- __( '<strong>Bonus:</strong> 7000+ integrations with Zapier.', 'wpforms-lite' ),
+ __( '<strong>Bonus:</strong> 9000+ integrations with Zapier.', 'wpforms-lite' ),
[
'strong' => [],
]
@@ -1547,7 +1547,7 @@
'pro' => [
'status' => 'full',
'text' => [
- '<strong>' . esc_html__( 'Create interactive Surveys and Polls with beautiful reports', 'wpforms-lite' ) . '</strong>',
+ '<strong>' . esc_html__( 'Create interactive Surveys, Polls, and Quizzes with beautiful reports', 'wpforms-lite' ) . '</strong>',
],
],
],
@@ -1760,7 +1760,7 @@
'conditionals' => esc_html__( 'Smart Conditional Logic', 'wpforms-lite' ),
'marketing' => esc_html__( 'Marketing Integrations', 'wpforms-lite' ),
'payments' => esc_html__( 'Payment Forms', 'wpforms-lite' ),
- 'surveys' => esc_html__( 'Surveys & Polls', 'wpforms-lite' ),
+ 'surveys' => esc_html__( 'Surveys, Polls, and Quizzes', 'wpforms-lite' ),
'advanced' => esc_html__( 'Advanced Form Features', 'wpforms-lite' ),
'addons' => esc_html__( 'WPForms Addons', 'wpforms-lite' ),
'support' => esc_html__( 'Customer Support', 'wpforms-lite' ),
--- a/wpforms-lite/includes/class-form.php
+++ b/wpforms-lite/includes/class-form.php
@@ -211,7 +211,7 @@
* @param mixed $id Form ID.
* @param array $args Additional arguments array.
*
- * @return array|bool|null|WP_Post
+ * @return array|false|WP_Post
*/
public function get( $id = '', array $args = [] ) {
@@ -253,7 +253,7 @@
* @param string|int $id Form ID.
* @param array $args Additional arguments array.
*
- * @return array|bool|null|WP_Post
+ * @return array|false|WP_Post
*/
protected function get_single( $id = '', array $args = [] ) {
--- a/wpforms-lite/includes/class-process.php
+++ b/wpforms-lite/includes/class-process.php
@@ -813,7 +813,15 @@
return;
}
- $this->spam_errors[ $form_id ]['footer'] = $country_filter->get_error_message( $this->form_data );
+ $error_message = $country_filter->get_error_message( $this->form_data );
+
+ if ( $this->is_block_submission_by_spam_filtering_enabled() ) {
+ $this->errors[ $form_id ]['footer'] = $error_message;
+
+ return;
+ }
+
+ $this->spam_errors[ $form_id ]['footer'] = $error_message;
$this->spam_reason = 'Country Filter';
@@ -846,7 +854,15 @@
return;
}
- $this->spam_errors[ $form_id ]['footer'] = $keyword_filter->get_error_message( $this->form_data );
+ $error_message = $keyword_filter->get_error_message( $this->form_data );
+
+ if ( $this->is_block_submission_by_spam_filtering_enabled() ) {
+ $this->errors[ $form_id ]['footer'] = $error_message;
+
+ return;
+ }
+
+ $this->spam_errors[ $form_id ]['footer'] = $error_message;
$this->spam_reason = 'Keyword Filter';
@@ -1844,7 +1860,17 @@
$email['template'] = ! empty( $notification['template'] ) ? $notification['template'] : '';
if ( $is_carboncopy_enabled && ! empty( $notification['carboncopy'] ) ) {
- $email['carboncopy'] = explode( ',', wpforms_process_smart_tags( $notification['carboncopy'], $form_data, $fields, $this->entry_id, 'notification-carboncopy' ) );
+ $email['carboncopy'] = explode(
+ ',',
+ wpforms_process_smart_tags(
+ $notification['carboncopy'],
+ $form_data,
+ $fields,
+ $this->entry_id,
+ 'notification-carboncopy',
+ [ 'to_email' => $email['address'] ]
+ )
+ );
$email['carboncopy'] = array_filter( array_map( 'sanitize_email', $email['carboncopy'] ) );
}
@@ -2234,4 +2260,23 @@
return $this->email_handler;
}
+
+ /**
+ * Determines if blocking submissions by spam filtering is enabled.
+ *
+ * @since 1.9.9
+ *
+ * @return bool True if blocking submissions by spam filtering is enabled, false otherwise.
+ */
+ private function is_block_submission_by_spam_filtering_enabled(): bool {
+
+ /**
+ * Determines if blocking submissions by spam filtering should be forced.
+ *
+ * @since 1.9.9
+ *
+ * @param bool $enabled True if blocking submissions should be forced.
+ */
+ return (bool) apply_filters( 'wpforms_process_is_block_submission_by_spam_filtering_enabled', false );
+ }
}
--- a/wpforms-lite/includes/emails/class-emails.php
+++ b/wpforms-lite/includes/emails/class-emails.php
@@ -138,6 +138,15 @@
public $notification_id = '';
/**
+ * Context data to be passed to the tag.
+ *
+ * @since 1.9.9.2
+ *
+ * @var array|array[]
+ */
+ private $context_data = [];
+
+ /**
* Get things going.
*
* @since 1.1.3
@@ -396,6 +405,8 @@
return false;
}
+ $this->context_data = [ 'to_email' => (array) $to ];
+
// Hooks before email is sent.
do_action( 'wpforms_email_send_before', $this );
@@ -424,6 +435,9 @@
$this
);
+ // Update context data, as 'to' email address could be changed by the filter above.
+ $this->context_data = [ 'to_email' => (array) $data['to'] ];
+
$entry_obj = wpforms()->obj( 'entry' );
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
@@ -521,18 +535,18 @@
* Process a smart tag.
* Decodes entities and sanitized (keeping line breaks) by default.
*
- * @uses wpforms_decode_string()
+ * @uses wpforms_decode_string()
*
* @since 1.1.3
* @since 1.6.0 Deprecated 2 params: $sanitize, $linebreaks.
*
- * @param string $string String that may contain tags.
+ * @param string $content String that may contain tags.
*
- * @return string
+ * @return string|mixed
*/
- public function process_tag( $string = '' ) {
+ public function process_tag( $content = '' ) {
- return wpforms_process_smart_tags( $string, $this->form_data, $this->fields, $this->entry_id, 'email' );
+ return wpforms_process_smart_tags( $content, $this->form_data, $this->fields, $this->entry_id, 'email', $this->context_data );
}
/**
--- a/wpforms-lite/includes/fields/class-base.php
+++ b/wpforms-lite/includes/fields/class-base.php
@@ -7,6 +7,7 @@
use WPFormsFormsFieldsBaseFrontend as FrontendBase;
use WPFormsFormsFieldsHelpersRequirementsAlerts;
+use WPFormsFormsFieldsTraitsMultiFieldMenu as MultiFieldMenuTrait;
use WPFormsFormsFieldsTraitsReadOnlyField as ReadOnlyFieldTrait;
use WPFormsFormsIconChoices;
use WPFormsIntegrationsAIHelpers as AIHelpers;
@@ -18,6 +19,7 @@
*/
abstract class WPForms_Field {
+ use MultiFieldMenuTrait;
use ReadOnlyFieldTrait;
/**
@@ -115,7 +117,7 @@
*
* @since 1.1.1
*
- * @var int|bool
+ * @var int|false
*/
public $form_id;
@@ -1472,7 +1474,7 @@
case 'choices':
$values = ! empty( $field['choices'] ) ? $field['choices'] : $this->defaults;
$label = ! empty( $args['label'] ) ? esc_html( $args['label'] ) : esc_html__( 'Choices', 'wpforms-lite' );
- $class = [];
+ $class = [ 'wpforms-undo-redo-container' ];
$field_type = $this->type;
$inline_style = '';
@@ -1526,9 +1528,12 @@
false
);
+ $id = 'wpforms-field-option-' . wpforms_validate_field_id( $field['id'] ) . '-choices-list';
+
// Field contents.
$fld = sprintf(
- '<ul data-next-id="%s" class="choices-list %s" data-field-id="%s" data-field-type="%s" style="%s">',
+ '<ul id="%1$s" data-next-id="%2$s" class="choices-list %3$s" data-field-id="%4$s" data-field-type="%5$s" style="%6$s">',
+ esc_attr( $id ),
max( array_keys( $values ) ) + 1,
wpforms_sanitize_classes( $class, true ),
wpforms_validate_field_id( $field['id'] ),
@@ -1554,6 +1559,8 @@
$remove_class = $is_other_option ? 'remove wpforms-disabled' : 'remove';
$move_class = $is_other_option ? 'move wpforms-disabled' : 'move';
+ $fld .= sprintf( '<span class="%s"><i class="fa fa-grip-lines"></i></span>', esc_attr( $move_class ) );
+
$fld .= sprintf(
'<input type="%s" name="%s[default]" class="default" value="1" %s>',
$field_type === 'checkbox' ? 'checkbox' : 'radio',
@@ -1561,8 +1568,6 @@
checked( '1', $default, false )
);
- $fld .= sprintf( '<span class="%s"><i class="fa fa-bars"></i></span>', esc_attr( $move_class ) );
-
/**
* Fires before the field choice label.
*
@@ -1574,7 +1579,7 @@
* @param array $field Field settings.
* @param array $args Field options.
*/
- $fld .= (string) apply_filters( 'wpforms_field_option_choice_before_label', '', $key, $value, $field, $args );
+ $fld .= apply_filters( 'wpforms_field_option_choice_before_label', '', $key, $value, $field, $args );
$fld .= sprintf(
'<input type="text" name="%s[label]" value="%s" class="label">',
@@ -1593,9 +1598,23 @@
* @param array $field Field settings.
* @param array $args Field options.
*/
- $fld .= (string) apply_filters( 'wpforms_field_option_choice_after_label', '', $key, $value, $field, $args );
+ $fld .= apply_filters( 'wpforms_field_option_choice_after_label', '', $key, $value, $field, $args );
$fld .= sprintf( '<a class="%s" href="#"><i class="fa fa-plus-circle"></i></a><a class="%s" href="#"><i class="fa fa-minus-circle"></i></a>', esc_attr( $add_class ), esc_attr( $remove_class ) );
+
+ /**
+ * Fires after the field choice label.
+ *
+ * @since 1.9.9
+ *
+ * @param string $output Output string.
+ * @param int $key Choice key.
+ * @param array $value Choice value.
+ * @param array $field Field settings.
+ * @param array $args Field options.
+ */
+ $fld .= apply_filters( 'wpforms_field_option_choice_after_controls', '', $key, $value, $field, $args );
+
$fld .= sprintf(
'<input type="text" name="%s[value]" value="%s" class="value">',
esc_attr( $base ),
@@ -1701,7 +1720,7 @@
*/
case 'choices_payments':
$values = ! empty( $field['choices'] ) ? $field['choices'] : $this->defaults;
- $class = [];
+ $class = [ 'wpforms-undo-redo-container' ];
$input_type = in_array( $field['type'], [ 'payment-multiple', 'payment-select' ], true ) ? 'radio' : 'checkbox';
$inline_style = '';
@@ -1728,9 +1747,12 @@
false
);
+ $id = 'wpforms-field-option-' . wpforms_validate_field_id( $field['id'] ) . '-choices-list';
+
// Field contents.
$fld = sprintf(
- '<ul data-next-id="%s" class="choices-list %s" data-field-id="%s" data-field-type="%s" style="%s">',
+ '<ul id="%1$s" data-next-id="%2$s" class="choices-list %3$s" data-field-id="%4$s" data-field-type="%5$s" style="%6$s">',
+ esc_attr( $id ),
max( array_keys( $values ) ) + 1,
wpforms_sanitize_classes( $class, true ),
wpforms_validate_field_id( $field['id'] ),
@@ -1747,13 +1769,13 @@
$icon_style = ! empty( $value['icon_style'] ) ? $value['icon_style'] : IconChoices::DEFAULT_ICON_STYLE;
$fld .= '<li data-key="' . absint( $key ) . '">';
+ $fld .= '<span class="move"><i class="fa fa-grip-lines"></i></span>';
$fld .= sprintf(
'<input type="%s" name="%s[default]" class="default" value="1" %s>',
esc_attr( $input_type ),
esc_attr( $base ),
checked( '1', $default, false )
);
- $fld .= '<span class="move"><i class="fa fa-grip-lines"></i></span>';
$fld .= sprintf(
'<input type="text" name="%s[label]" value="%s" class="label">',
esc_attr( $base ),
@@ -1853,7 +1875,7 @@
break;
/*
- * Other Placeholder (for "Other" choice input field).
+ * Other Placeholder (for the "Other" choice input field).
*/
case 'other_placeholder':
$label = $this->field_element(
@@ -2806,8 +2828,10 @@
* @param object $this WPForms_Field object.
*/
do_action( "wpforms_field_options_before_{$option}", $field, $this );
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $output;
+
/**
* Fires after the field option output.
*
@@ -2822,10 +2846,26 @@
}
if ( $markup === 'open' ) {
+ /**
+ * Fires before the field option output.
+ *
+ * @since 1.0.2
+ *
+ * @param array $field Field data and settings.
+ * @param object $this WPForms_Field object.
+ */
do_action( "wpforms_field_options_before_{$option}", $field, $this );
}
if ( $markup === 'close' ) {
+ /**
+ * Fires at the bottom of the field option output.
+ *
+ * @since 1.0.2
+ *
+ * @param array $field Field data and settings.
+ * @param object $this WPForms_Field object.
+ */
do_action( "wpforms_field_options_bottom_{$option}", $field, $this );
}
@@ -2833,10 +2873,26 @@
echo $output;
if ( $markup === 'open' ) {
+ /**
+ * Fires at the top of the field option output.
+ *
+ * @since 1.0.2
+ *
+ * @param array $field Field data and settings.
+ * @param object $this WPForms_Field object.
+ */
do_action( "wpforms_field_options_top_{$option}", $field, $this );
}
if ( $markup === 'close' ) {
+ /**
+ * Fires after the field option output.
+ *
+ * @since 1.0.2
+ *
+ * @param array $field Field data and settings.
+ * @param object $this WPForms_Field object.
+ */
do_action( "wpforms_field_options_after_{$option}", $field, $this );
}
@@ -2844,7 +2900,7 @@
}
/**
- * Get choice images hide option field element.
+ * Get choice images hide an option field element.
*
* @since 1.9.8.3
*
@@ -2989,21 +3045,30 @@
$total_obj = wp_count_posts( $field['dynamic_post_type'] );
$total = isset( $total_obj->publish ) ? (int) $total_obj->publish : 0;
$values = [];
- $posts = wpforms_get_hierarchical_object(
- apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.Comments.SinceTagHooks.MissingSinceTag
- 'wpforms_dynamic_choice_post_type_args',
- [
- 'post_type' => $field['dynamic_post_type'],
- 'posts_per_page' => 20,
- 'orderby' => 'title',
- 'order' => 'ASC',
- ],
- $field,
- $this->form_id
- ),
- true
+
+ /**
+ * Filters dynamic choice taxonomy args.
+ *
+ * @since 1.5.0
+ *
+ * @param array $args Arguments.
+ * @param array $field Field.
+ * @param int|false $form_id Form ID.
+ */
+ $args = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.Comments.SinceTagHooks.MissingSinceTag
+ 'wpforms_dynamic_choice_post_type_args',
+ [
+ 'post_type' => $field['dynamic_post_type'],
+ 'posts_per_page' => 20,
+ 'orderby' => 'title',
+ 'order' => 'ASC',
+ ],
+ $field,
+ $this->form_id
);
+ $posts = wpforms_get_hierarchical_object( $args, true );
+
foreach ( $posts as $post ) {
$values[] = [
'label' => esc_html( wpforms_get_post_title( $post ) ),
@@ -3015,20 +3080,29 @@
// Taxonomy dynamic populating.
$total = (int) wp_count_terms( $field['dynamic_taxonomy'] );
$values = [];
- $terms = wpforms_get_hierarchical_object(
- apply_filters(
- 'wpforms_dynamic_choice_taxonomy_args',
- [
- 'taxonomy' => $field['dynamic_taxonomy'],
- 'hide_empty' => false,
- 'number' => 20,
- ],
- $field,
- $this->form_id
- ),
- true
+
+ /**
+ * Filters dynamic choice taxonomy args.
+ *
+ * @since 1.5.0
+ *
+ * @param array $args Arguments.
+ * @param array $field Field.
+ * @param int|false $form_id Form ID.
+ */
+ $args = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
+ 'wpforms_dynamic_choice_taxonomy_args',
+ [
+ 'taxonomy' => $field['dynamic_taxonomy'],
+ 'hide_empty' => false,
+ 'number' => 20,
+ ],
+ $field,
+ $this->form_id
);
+ $terms = wpforms_get_hierarchical_object( $args, true );
+
foreach ( $terms as $term ) {
$values[] = [
'label' => esc_html( wpforms_get_term_name( $term ) ),
@@ -3246,7 +3320,7 @@
$output .= '</ul>';
- // Multiple choice: Other option.
+ // Multiple choice: Another option.
if ( $type === 'radio' ) {
$placeholder = ! empty( $field['other_placeholder'] ) ? $field['other_placeholder'] : '';
@@ -3421,6 +3495,9 @@
$preview .= sprintf( '<a href="#" class="wpforms-field-delete" title="%s"><i class="fa fa-trash-o"></i></a>', esc_attr__( 'Delete Field', 'wpforms-lite' ) );
+ // Multi-field actions menu.
+ $preview .= $this->get_multi_field_menu_html();
+
if ( ! $field_helper_hide ) {
$preview .= sprintf(
'<div class="wpforms-field-helper">
@@ -3737,7 +3814,7 @@
*/
public function field_html_value_images( $filtering, string $context, array $field ): bool {
- // Bail if images are hidden and not in entry-preview context.
+ // Bail if images are hidden and not in the entry-preview context.
if ( ! empty( $field['choices_images_hide'] ) && $context !== 'entry-preview' ) {
return false;
}
@@ -3972,7 +4049,14 @@
*/
protected function is_choicesjs_search_enabled( $choices_count ) {
- // We should auto hide/remove search, if less than 8 choices.
+ /**
+ * Allow modifying the minimum number of choices to show the search area.
+ * We should auto hide/remove search, if less than 8 choices by default.
+ *
+ * @since 1.6.4
+ *
+ * @param int $min_choices Minimum number of choices to show the search area.
+ */
return $choices_count >= (int) apply_filters( 'wpforms_field_choicesjs_search_enabled_items_min', 8 );
}
--- a/wpforms-lite/includes/functions/forms.php
+++ b/wpforms-lite/includes/functions/forms.php
@@ -502,17 +502,19 @@
* Process smart tags.
*
* @since 1.7.1
- * @since 1.8.7 Added `$context` parameter.
+ * @since 1.8.7 Added `$context` parameter.
+ * @since 1.9.9.2 Added `$context` parameter.
*
- * @param string $content Content.
- * @param array $form_data Form data.
- * @param array $fields List of fields.
- * @param string $entry_id Entry ID.
- * @param string $context Context.
+ * @param string $content Content.
+ * @param array $form_data Form data.
+ * @param array $fields List of fields.
+ * @param string $entry_id Entry ID.
+ * @param string $context Context.
+ * @param array $context_data Context data.
*
- * @return string
+ * @return string|mixed
*/
-function wpforms_process_smart_tags( $content, $form_data, $fields = [], $entry_id = '', $context = '' ) {
+function wpforms_process_smart_tags( $content, $form_data, $fields = [], $entry_id = '', $context = '', array $context_data = [] ) {
// Skip it if variables have invalid format.
if ( ! is_string( $content ) || ! is_array( $form_data ) || ! is_array( $fields ) ) {
@@ -525,15 +527,16 @@
* @since 1.4.0
* @since 1.8.7 Added $context parameter.
*
- * @param string $content Content.
- * @param array $form_data Form data.
- * @param array $fields List of fields.
- * @param string $entry_id Entry ID.
- * @param string $context Context.
+ * @param string $content Content.
+ * @param array $form_data Form data.
+ * @param array $fields List of fields.
+ * @param string $entry_id Entry ID.
+ * @param string $context Context.
+ * @param array $context_data Context data.
*
* @return string
*/
- return apply_filters( 'wpforms_process_smart_tags', $content, $form_data, $fields, $entry_id, $context );
+ return (string) apply_filters( 'wpforms_process_smart_tags', $content, $form_data, $fields, $entry_id, $context, $context_data );
}
/**
--- a/wpforms-lite/includes/functions/utilities.php
+++ b/wpforms-lite/includes/functions/utilities.php
@@ -14,9 +14,24 @@
*
* @return string
*/
-function wpforms_get_min_suffix() {
+function wpforms_get_min_suffix(): string {
- return wpforms_debug() ? '' : '.min';
+ $script_debug = false;
+
+ if ( ( defined( 'WPFORMS_SCRIPT_DEBUG' ) && WPFORMS_SCRIPT_DEBUG ) && is_super_admin() ) {
+ $script_debug = true;
+ }
+
+ /**
+ * Filters wpforms script debug status.
+ *
+ * @since 1.9.9
+ *
+ * @param bool $script_debug WPForms script debug status.
+ */
+ $script_debug = (bool) apply_filters( 'wpforms_script_debug', $script_debug );
+
+ return $script_debug ? '' : '.min';
}
/**
--- a/wpforms-lite/includes/integrations.php
+++ b/wpforms-lite/includes/integrations.php
@@ -8,6 +8,8 @@
* Register and setup WPForms as a Visual Composer element.
*
* @since 1.3.0
+ *
+ * @noinspection PhpUndefinedFunctionInspection
*/
function wpforms_visual_composer_shortcode() {
@@ -15,7 +17,13 @@
return;
}
- $wpf = wpforms()->obj( 'form' )->get(
+ $form_obj = wpforms()->obj( 'form' );
+
+ if ( ! $form_obj ) {
+ return;
+ }
+
+ $wpf = $form_obj->get(
'',
[
'orderby' => 'title',
@@ -58,6 +66,7 @@
'heading' => esc_html__( 'Display Form Name', 'wpforms-lite' ),
'param_name' => 'title',
'value' => [
+ // phpcs:ignore WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
esc_html__( 'No', 'wpforms-lite' ) => 'false',
esc_html__( 'Yes', 'wpforms-lite' ) => 'true',
],
@@ -73,6 +82,7 @@
'heading' => esc_html__( 'Display Form Description', 'wpforms-lite' ),
'param_name' => 'description',
'value' => [
+ // phpcs:ignore WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
esc_html__( 'No', 'wpforms-lite' ) => 'false',
esc_html__( 'Yes', 'wpforms-lite' ) => 'true',
],
@@ -96,11 +106,13 @@
*/
function wpforms_visual_composer_shortcode_css() {
+ $min = wpforms_get_min_suffix();
+
// Load CSS per global setting.
if ( wpforms_setting( 'disable-css', '1' ) === '1' ) {
wp_enqueue_style(
'wpforms-full',
- WPFORMS_PLUGIN_URL . 'assets/css/frontend/classic/wpforms-full.css',
+ WPFORMS_PLUGIN_URL . "assets/css/frontend/classic/wpforms-full{$min}.css",
[],
WPFORMS_VERSION
);
@@ -109,7 +121,7 @@
if ( wpforms_setting( 'disable-css', '1' ) === '2' ) {
wp_enqueue_style(
'wpforms-base',
- WPFORMS_PLUGIN_URL . 'assets/css/frontend/classic/wpforms-base.css',
+ WPFORMS_PLUGIN_URL . "assets/css/frontend/classic/wpforms-base{$min}.css",
[],
WPFORMS_VERSION
);
--- a/wpforms-lite/includes/templates/class-base.php
+++ b/wpforms-lite/includes/templates/class-base.php
@@ -1,5 +1,8 @@
<?php
+// phpcs:ignore Generic.Commenting.DocComment.MissingShort
+/** @noinspection AutoloadingIssuesInspection */
+
/**
* Base form template.
*
@@ -8,7 +11,7 @@
abstract class WPForms_Template {
/**
- * Full name of the template, eg "Contact Form".
+ * Full name of the template, e.g. "Contact Form".
*
* @since 1.0.0
*
@@ -17,7 +20,7 @@
public $name;
/**
- * Slug of the template, eg "contact-form" - no spaces.
+ * Slug of the template, e.g. "contact-form" - no spaces.
*
* @since 1.0.0
*
@@ -44,7 +47,7 @@
public $categories;
/**
- * Short description the template.
+ * Short description of the template.
*
* @since 1.0.0
*
@@ -80,7 +83,7 @@
public $url = '';
/**
- * Form template thumbnail url.
+ * Form template thumbnail URL.
*
* @since 1.8.2
*
@@ -134,12 +137,8 @@
// Bootstrap.
$this->init();
- $type = $this->core ? '_core' : '';
-
- add_filter( "wpforms_form_templates{$type}", [ $this, 'template_details' ], $this->priority );
- add_filter( 'wpforms_create_form_args', [ $this, 'template_data' ], 10, 2 );
- add_filter( 'wpforms_save_form_args', [ $this, 'template_replace' ], 10, 3 );
- add_filter( 'wpforms_builder_template_active', [ $this, 'template_active' ], 10, 2 );
+ // Hooks.
+ $this->hooks();
}
/**
@@ -150,15 +149,32 @@
public function init() {}
/**
+ * Register hooks.
+ *
+ * @since 1.9.9
+ */
+ private function hooks(): void {
+
+ $type = $this->core ? '_core' : '';
+
+ add_filter( "wpforms_form_templates{$type}", [ $this, 'template_details' ], $this->priority );
+ add_filter( 'wpforms_create_form_args', [ $this, 'template_data' ], 10, 2 );
+ add_filter( 'wpforms_save_form_args', [ $this, 'template_replace' ], 10, 3 );
+ add_filter( 'wpforms_builder_template_active', [ $this, 'template_active' ], 10, 2 );
+ }
+
+ /**
* Add basic template details to the Add New Form admin screen.
*
* @since 1.0.0
*
- * @param array $templates Templates array.
+ * @param array|mixed $templates Templates array.
*
* @return array
*/
- public function template_details( $templates ) {
+ public function template_details( $templates ): array {
+
+ $templates = (array) $templates;
$templates[] = [
'name' => $this->name,
@@ -177,7 +193,7 @@
}
/**
- * Get the directory name of the plugin in which current template resides.
+ * Get the directory name of the plugin in which the current template resides.
*
* @since 1.6.9
*
@@ -197,7 +213,7 @@
}
/**
- * Add template data when form is created.
+ * Add template data when a form is created.
*
* @since 1.0.0
*
@@ -230,7 +246,7 @@
}
/**
- * Replace template on post update if triggered.
+ * Replace the template on post update if triggered.
*
* @since 1.0.0
*
@@ -239,6 +255,7 @@
* @param array $args Update form arguments.
*
* @return array
+ * @noinspection PhpMissingParamTypeInspection
*/
public function template_replace( $form, $data, $args ): array {
@@ -257,7 +274,7 @@
return $form;
}
- // Compile the new form data preserving needed data from the existing form.
+ // Compile the new form data while preserving the necessary data from the existing form.
$new = $this->data;
$new['id'] = $form_data['id'] ?? 0;
$new['settings'] = $form_data['settings'] ?? [];
@@ -266,7 +283,7 @@
$template_id = $this->data['meta']['template'] ?? '';
- // Preserve template ID `wpforms-user-template-{$form_id}` when overwriting it with core template.
+ // Preserve template ID `wpforms-user-template-{$form_id}` when overwriting it with the core template.
if ( wpforms_is_form_template( $form['ID'] ) ) {
$template_id = $form_data['meta']['template'] ?? '';
}
@@ -295,25 +312,27 @@
*
* @since 1.0.0
*
- * @param array $details Details.
- * @param object $form Form data.
+ * @param array|mixed $details Details.
+ * @param WP_Post|null|false $form Form data.
*
- * @return array|void
+ * @return array
*/
- public function template_active( $details, $form ) {
+ public function template_active( $details, $form ): array {
- if ( empty( $form ) ) {
- return;
+ $details = (array) $details;
+
+ if ( ! $form ) {
+ return [];
}
$form_data = wpforms_decode( $form->post_content );
if ( empty( $this->modal ) || empty( $form_data['meta']['template'] ) || $this->slug !== $form_data['meta']['template'] ) {
return $details;
- } else {
- $display = $this->template_modal_conditional( $form_data );
}
+ $display = $this->template_modal_conditional( $form_data );
+
return [
'name' => $this->name,
'slug' => $this->slug,
@@ -326,8 +345,8 @@
}
/**
- * Conditional to determine if the template informational modal screens
- * should display.
+ * Conditional logic to determine whether the template informational modal screens
+ * should be displayed.
*
* @since 1.0.0
*
--- a/wpforms-lite/lite/wpforms-lite.php
+++ b/wpforms-lite/lite/wpforms-lite.php
@@ -752,7 +752,7 @@
<li><?php esc_html_e( 'Accept user-submitted content with the Post Submissions addon', 'wpforms-lite' ); ?></li>
</ul>
<ul>
- <li><?php esc_html_e( '7000+ integrations with marketing and payment services', 'wpforms-lite' ); ?></li>
+ <li><?php esc_html_e( '9000+ integrations with marketing and payment services', 'wpforms-lite' ); ?></li>
<li><?php esc_html_e( 'Let users save & resume submissions to prevent abandonment', 'wpforms-lite' ); ?></li>
<li><?php esc_html_e( 'Take payments with Stripe, PayPal, Square, & Authorize.Net', 'wpforms-lite' ); ?></li>
<li><?php esc_html_e( 'Export entries to Google Sheets, Excel, and CSV', 'wpforms-lite' ); ?></li>
--- a/wpforms-lite/src/Admin/Builder/Addons.php
+++ b/wpforms-lite/src/Admin/Builder/Addons.php
@@ -34,6 +34,13 @@
'surveys-polls' => [
'survey',
],
+ 'quiz' => [
+ 'quiz_enabled',
+ 'choices' => [
+ 'quiz_personality',
+ 'quiz_weight',
+ ],
+ ],
];
/**
@@ -184,6 +191,43 @@
$new_field[ $setting_name ] = $setting_value;
}
}
+
+ if (
+ ! empty( $preserve_fields['choices'] ) &&
+ is_array( $preserve_fields['choices'] ) &&
+ ! empty( $new_field['choices'] ) &&
+ is_array( $new_field['choices'] )
+ ) {
+ $this->preserve_addon_field_choices_settings( $preserve_fields['choices'], $new_field, $previous_field );
+ }
+ }
+
+ /**
+ * Preserve addon field choices settings.
+ *
+ * @since 1.9.9
+ *
+ * @param array $choice_settings Choice settings.
+ * @param array $new_field Previous form fields settings.
+ * @param array $previous_field Form fields settings.
+ *
+ * @return void
+ */
+ private function preserve_addon_field_choices_settings( array $choice_settings, array &$new_field, array $previous_field ): void {
+
+ if ( ! isset( $previous_field['choices'] ) || ! is_array( $previous_field['choices'] ) ) {
+ return;
+ }
+
+ $previous_choices = $previous_field['choices'];
+
+ foreach ( $new_field['choices'] as $choice_id => $choice ) {
+ foreach ( $choice_settings as $setting_name ) {
+ if ( isset( $previous_choices[ $choice_id ][ $setting_name ] ) ) {
+ $new_field['choices'][ $choice_id ][ $setting_name ] = $previous_choices[ $choice_id ][ $setting_name ];
+ }
+ }
+ }
}
/**
--- a/wpforms-lite/src/Admin/Builder/Help.php
+++ b/wpforms-lite/src/Admin/Builder/Help.php
@@ -190,6 +190,7 @@
'settings/webhooks' => 'webhooks',
'settings/entry_automation' => 'entry automation',
'settings/pdf' => 'pdf',
+ 'settings/quiz' => 'quiz',
'providers' => '',
'providers/aweber' => 'aweber',
'providers/activecampaign' => 'activecampaign',
@@ -1284,6 +1285,9 @@
'airtable' => [
'/docs/airtable-addon/',
],
+ 'quiz' => [
+ '/docs/quiz-addon/',
+ ],
];
}
--- a/wpforms-lite/src/Admin/Builder/Settings/Themes.php
+++ b/wpforms-lite/src/Admin/Builder/Settings/Themes.php
@@ -153,7 +153,7 @@
wp_enqueue_style(
'wpforms-full',
- WPFORMS_PLUGIN_URL . 'assets/css/frontend/modern/wpforms-full.css',
+ WPFORMS_PLUGIN_URL . "assets/css/frontend/modern/wpforms-full{$min}.css",
[],
WPFORMS_VERSION
);
@@ -201,6 +201,7 @@
* @param string $slug Sidebar section slug.
*
* @return array
+ * @noinspection PhpUnusedParameterInspection
*/
public function add_pro_class( array $classes, string $name, string $slug ): array {
@@ -252,7 +253,7 @@
],
'permission_modal' => [
'title' => esc_html__( 'Insufficient Permissions', 'wpforms-lite' ),
- 'content' => esc_html__( 'Sorry, your user role doesn't have permission to access this feature.', 'wpforms-lite' ),
+ 'content' => esc_html__( "Sorry, your user role doesn't have permission to access this feature.", 'wpforms-lite' ),
'confirm' => esc_html__( 'OK', 'wpforms-lite' ),
],
],
--- a/wpforms-lite/src/Admin/Builder/Shortcuts.php
+++ b/wpforms-lite/src/Admin/Builder/Shortcuts.php
@@ -14,9 +14,9 @@
*
* @since 1.6.9
*/
- public function init() {
+ public function init(): void {
- // Terminate initialization if not in builder.
+ // Terminate initialization if not in the builder.
if ( ! wpforms_is_admin_page( 'builder' ) ) {
return;
}
@@ -29,20 +29,20 @@
*
* @since 1.6.9
*/
- private function hooks() {
+ private function hooks(): void {
- add_filter( 'wpforms_builder_strings', [ $this, 'builder_strings' ], 10, 2 );
+ add_filter( 'wpforms_builder_strings', [ $this, 'builder_strings' ] );
add_action( 'wpforms_admin_page', [ $this, 'output' ], 30 );
}
/**
- * Get shortcuts list.
+ * Get a shortcut list.
*
* @since 1.6.9
*
* @return array
*/
- private function get_list() {
+ private function get_list(): array {
return [
'left' => [
@@ -50,12 +50,18 @@
'ctrl p' => __( 'Preview Form', 'wpforms-lite' ),
'ctrl b' => __( 'Embed Form', 'wpforms-lite' ),
'ctrl f' => __( 'Search Fields', 'wpforms-lite' ),
+ 'ctrl c' => __( 'Copy Fields', 'wpforms-lite' ),
+ 'ctrl v' => __( 'Paste Fields', 'wpforms-lite' ),
+ 'd' => __( 'Duplicate Fields', 'wpforms-lite' ),
],
'right' => [
- 'ctrl h' => __( 'Open Help', 'wpforms-lite' ),
- 'ctrl t' => __( 'Toggle Sidebar', 'wpforms-lite' ), // It is 'alt s' on Windows/Linux, dynamically changed in the modal in admin-builder.js openKeyboardShortcutsModal().
- 'ctrl e' => __( 'View Entries', 'wpforms-lite' ),
- 'ctrl q' => __( 'Close Builder', 'wpforms-lite' ),
+ 'ctrl z' => __( 'Undo', 'wpforms-lite' ),
+ 'ctrl shift z' => __( 'Redo', 'wpforms-lite' ),
+ 'ctrl h' => __( 'Open Help', 'wpforms-lite' ),
+ 'ctrl t' => __( 'Toggle Sidebar', 'wpforms-lite' ), // It is 'alt s' on Windows/Linux, dynamically changed in the modal in admin-builder.js openKeyboardShortcutsModal().
+ 'ctrl e' => __( 'View Entries', 'wpforms-lite' ),
+ 'ctrl q' => __( 'Close Builder', 'wpforms-lite' ),
+ 'delete' => __( 'Delete Fields', 'wpforms-lite' ),
],
];
}
@@ -65,12 +71,13 @@
*
* @since 1.6.9
*
- * @param array $strings Form Builder strings.
- * @param WP_Post|bool $form Form object.
+ * @param array|mixed $strings Form Builder strings.
*
* @return array
*/
- public function builder_strings( $strings, $form ) {
+ public function builder_strings( $strings ): array {
+
+ $strings = (array) $strings;
$strings['shortcuts_modal_title'] = esc_html__( 'Keyboard Shortcuts', 'wpforms-lite' );
$strings['shortcuts_modal_msg'] = esc_html__( 'Handy shortcuts for common actions in the builder.', 'wpforms-lite' );
@@ -83,7 +90,7 @@
*
* @since 1.6.9
*/
- public function output() {
+ public function output(): void {
echo '
<script type="text/html" id="tmpl-wpforms-builder-keyboard-shortcuts">
@@ -95,19