: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Ajax actions used in by admin.
if ( ! defined( 'ABSPATH' ) ) {
function wpforms_save_form() {
if ( ! check_ajax_referer( 'wpforms-builder', 'nonce', false ) ) {
wp_send_json_error( esc_html__( 'Your session expired. Please reload the builder.', 'wpforms-lite' ) );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) );
if ( empty( $_POST['data'] ) ) {
wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms-lite' ) );
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$form_post = json_decode( wp_unslash( $_POST['data'] ) );
foreach ( $form_post as $post_input_data ) {
// For input names that are arrays (e.g. `menu-item-db-id[3][4][5]`),
// derive the array path keys via regex and set the value in $_POST.
preg_match( '#([^\[]*)(\[(.+)\])?#', $post_input_data->name, $matches );
$array_bits = [ $matches[1] ];
if ( isset( $matches[3] ) ) {
$array_bits = array_merge( $array_bits, explode( '][', $matches[3] ) );
// Build the new array value from leaf to trunk.
for ( $i = count( $array_bits ) - 1; $i >= 0; $i -- ) {
if ( $i === count( $array_bits ) - 1 ) {
$new_post_data[ $array_bits[ $i ] ] = wp_slash( $post_input_data->value );
$array_bits[ $i ] => $new_post_data,
$data = array_replace_recursive( $data, $new_post_data );
$form_tags = isset( $data['settings']['form_tags_json'] ) ? json_decode( wp_unslash( $data['settings']['form_tags_json'] ), true ) : [];
// Clear not needed data.
unset( $data['settings']['form_tags_json'] );
// Store tags labels in the form settings.
$data['settings']['form_tags'] = wp_list_pluck( $form_tags, 'label' );
$form_id = wpforms()->get( 'form' )->update( $data['id'], $data, [ 'context' => 'save_form' ] );
* Fires after updating form data.
* @param int $form_id Form ID.
* @param array $data Form data.
do_action( 'wpforms_builder_save_form', $form_id, $data );
wp_send_json_error( esc_html__( 'Something went wrong while saving the form.', 'wpforms-lite' ) );
wpforms()->get( 'forms_tags_ajax' )->get_processed_tags( $form_tags ),
WPForms_Form_Handler::TAGS_TAXONOMY
'form_name' => esc_html( $data['settings']['form_title'] ),
'form_desc' => $data['settings']['form_desc'],
'redirect' => admin_url( 'admin.php?page=wpforms-overview' ),
* Allows filtering ajax response data after form was saved.
* @param array $response_data The data to be sent in the response.
* @param int $form_id Form ID.
* @param array $data Form data.
$response_data = apply_filters(
'wpforms_builder_save_form_response_data',
wp_send_json_success( $response_data );
add_action( 'wp_ajax_wpforms_save_form', 'wpforms_save_form' );
function wpforms_new_form() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Prevent second form creating if user has no licence set.
// Redirect will lead to the warning page.
if ( wpforms()->is_pro() && empty( wpforms_get_license_type() ) && wp_count_posts( 'wpforms' )->publish >= 1 ) {
wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=wpforms-builder&view=setup' ) ] );
if ( empty( $_POST['title'] ) ) {
'error_type' => 'missing_form_title',
'message' => esc_html__( 'No Form Name Provided', 'wpforms-lite' ),
$form_title = sanitize_text_field( wp_unslash( $_POST['title'] ) );
$form_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
$category = empty( $_POST['category'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['category'] ) );
$subcategory = empty( $_POST['subcategory'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['subcategory'] ) );
if ( ! wpforms()->get( 'builder_templates' )->is_valid_template( $form_template ) ) {
'error_type' => 'invalid_template',
'message' => esc_html__( 'The template you selected is currently not available, but you can try again later. If you continue to have trouble, please reach out to support.', 'wpforms-lite' ),
$title_query = new WP_Query(
'post_type' => 'wpforms',
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
$title_exists = $title_query->post_count > 0;
$form_id = wpforms()->get( 'form' )->add(
'template' => $form_template,
'subcategory' => $subcategory,
// Skip creating a revision for this action.
remove_action( 'post_updated', 'wp_save_post_revision' );
'post_title' => $form_title . ' (ID #' . $form_id . ')',
// Restore the initial revisions state.
add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
'error_type' => 'cant_create_form',
'message' => esc_html__( 'Error Creating Form', 'wpforms-lite' ),
if ( wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
'redirect' => add_query_arg(
admin_url( 'admin.php?page=wpforms-builder' )
if ( wpforms_current_user_can( 'view_forms' ) ) {
wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=wpforms-overview' ) ] );
wp_send_json_success( [ 'redirect' => admin_url() ] );
add_action( 'wp_ajax_wpforms_new_form', 'wpforms_new_form' );
function wpforms_update_form_template() {
check_ajax_referer( 'wpforms-builder', 'nonce' );
if ( empty( $_POST['form_id'] ) ) {
'error_type' => 'invalid_form_id',
'message' => esc_html__( 'No Form ID Provided', 'wpforms-lite' ),
// Set initial variables.
$form_id = absint( $_POST['form_id'] );
$form_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
$category = empty( $_POST['category'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['category'] ) );
$subcategory = empty( $_POST['subcategory'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['subcategory'] ) );
// Check for valid template.
if ( ! wpforms()->get( 'builder_templates' )->is_valid_template( $form_template ) ) {
'error_type' => 'invalid_template',
'message' => esc_html__( 'The template you selected is currently not available, but you can try again later. If you continue to have trouble, please reach out to support.', 'wpforms-lite' ),
// Get current form data.
$data = wpforms()->get( 'form' )->get(
// Get the cached data from the form template JSON.
$template_data = wpforms()->get( 'builder_templates' )->get_template( $form_template );
// If the template title is set, use it. Otherwise, clear the form title.
$template_title = ! empty( $template_data['name'] ) ? $template_data['name'] : '';
// If the form title is set, use it. Otherwise, use the template title.
$form_title = ! empty( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : $template_title;
// Check if the current form title is equal to the previous template name.
// If so, set the form title equal to the new template name.
$prev_template_slug = $data['meta']['template'] ?? '';
$prev_template = wpforms()->get( 'builder_templates' )->get_template( $prev_template_slug );
$form_title = isset( $prev_template['name'] ) && $prev_template['name'] === $form_title ? $template_title : $form_title;
// If the these template titles are empty, use the form title.
$form_pages_title = $template_title ? $template_title : $form_title;
$form_conversational_title = ! empty( $template_data['data']['settings']['conversational_forms_title'] ) ? $template_data['data']['settings']['conversational_forms_title'] : $form_title;
// If these template slugs are empty, use the form title.
$form_conversational_slug = ! empty( $template_data['data']['settings']['conversational_forms_page_slug'] ) ? $template_data['data']['settings']['conversational_forms_page_slug'] : $form_title;
$form_pages_slug = ! empty( $template_data['data']['settings']['form_pages_page_slug'] ) ? $template_data['data']['settings']['form_pages_page_slug'] : $form_title;
// Loop over notifications.
$notifications = isset( $template_data['data']['settings']['notifications'] ) ? $template_data['data']['settings']['notifications'] : [];
foreach ( $notifications as $key => $notification ) {
// If the subject is empty, set it to an empty string.
$notification_subject = ! empty( $notification['subject'] ) ? sanitize_text_field( $notification['subject'] ) : '';
$data['settings']['notifications'][ $key ]['subject'] = $notification_subject;
// Loop over confirmations.
$confirmations = isset( $template_data['data']['settings']['confirmations'] ) ? $template_data['data']['settings']['confirmations'] : [];
foreach ( $confirmations as $key => $confirmation ) {
// If the message is empty, set it to an empty string.
$confirmation_message = ! empty( $confirmation['message'] ) ? wp_kses_post( $confirmation['message'] ) : '';
$data['settings']['confirmations'][ $key ]['message'] = $confirmation_message;
// Set updated form titles.
$data['settings']['form_title'] = sanitize_text_field( $form_title );
$data['settings']['form_pages_title'] = sanitize_text_field( $form_pages_title );
$data['settings']['conversational_forms_title'] = sanitize_text_field( $form_conversational_title );
// Set updated form slugs.
$data['settings']['form_pages_page_slug'] = sanitize_title( $form_pages_slug );
$data['settings']['conversational_forms_page_slug'] = sanitize_title( $form_conversational_slug );
// Try to update the form.
$updated = (bool) wpforms()->get( 'form' )->update(
'template' => $form_template,
'subcategory' => $subcategory,
// If the form was updated, return the form ID and redirect to the form builder.
'redirect' => add_query_arg(
admin_url( 'admin.php?page=wpforms-builder' )
// Otherwise, return an error.
'error_type' => 'cant_update',
'message' => esc_html__( 'Error Updating Template', 'wpforms-lite' ),
add_action( 'wp_ajax_wpforms_update_form_template', 'wpforms_update_form_template' );
* Form Builder update next field ID.
function wpforms_builder_increase_next_field_id() {
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
// Check for required items.
if ( empty( $_POST['form_id'] ) ) {
// In the case of duplicating the Layout field that contains a bunch of fields,
// we need to set the next `field_id` to the desired value which is passed via POST argument.
if ( ! empty( $_POST['field_id'] ) ) {
$args['field_id'] = sanitize_text_field( wp_unslash( $_POST['field_id'] ) );
wpforms()->get( 'form' )->next_field_id( absint( $_POST['form_id'] ), $args );
add_action( 'wp_ajax_wpforms_builder_increase_next_field_id', 'wpforms_builder_increase_next_field_id' );
* Form Builder Dynamic Choices option toggle.
* This can be triggered with select/radio/checkbox fields.
function wpforms_builder_dynamic_choices() {
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
// Check for valid/required items.
if ( ! isset( $_POST['field_id'] ) || empty( $_POST['type'] ) || ! in_array( $_POST['type'], [ 'post_type', 'taxonomy' ], true ) ) {
$type = sanitize_key( $_POST['type'] );
$id = sanitize_text_field( wp_unslash( $_POST['field_id'] ) );
// Fetch the option row HTML to be returned to the builder.
$field = new WPForms_Field_Select( false );
'dynamic_choices' => $type,
$option_row = $field->field_option( 'dynamic_choices_source', $field_args, [], false );
add_action( 'wp_ajax_wpforms_builder_dynamic_choices', 'wpforms_builder_dynamic_choices' );
* Form Builder Dynamic Choices Source option toggle.
* This can be triggered with select/radio/checkbox fields.
function wpforms_builder_dynamic_source() {
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
// Check for required items.
if ( ! isset( $_POST['field_id'] ) || empty( $_POST['form_id'] ) || empty( $_POST['type'] ) || empty( $_POST['source'] ) ) {
$type = sanitize_key( $_POST['type'] );
$source = sanitize_key( $_POST['source'] );
$id = sanitize_text_field( wp_unslash( $_POST['field_id'] ) );
$form_id = absint( $_POST['form_id'] );
if ( $type === 'post_type' ) {
$type_name = esc_html__( 'post type', 'wpforms-lite' );
$posts = wpforms_get_hierarchical_object(
'wpforms_dynamic_choice_post_type_args',