plugin development - 403 Error when text is pasted in Custom Metabox Textarea

admin2025-06-02  0

I hope everyone is having a great day!

I am creating this question as I have been stuck on this issue all day and I need some friendly advice.

I have created a repeater custom metabox item for a custom post type called Proposal. The repeater works fine and is saving the content and displaying it how I would like. However, when I paste content into the textarea it throws a 403 error page when you update or publish the post and have MORE than 1 repeated option.

The weird part is when you just add text to the textarea and don't paste the content everything works fine. Obviously, this is an issue I need to fix before I release the plugin to the world. Here is the code I have. If you notice anything wrong, please let me know so I can improve it as I am fairly new to backend development.

<div id="wpp_meta_inner">
<?php
//get the saved meta as an array
$service = get_post_meta($post->ID,'service',false);
$service_title = get_post_meta($post->ID, 'wpp_service_title', true);
?>
    <div>

          <div class="wpp-input-container">
              <label><?php _e('Title') ?></label>
              <input type="text" name="wpp_service_title" id="wpp_service_title" value="<?php echo $service_title; ?>" />
              <p><?php _e('Will display default "Scope of Services" if empty.') ?></p>
          </div>

          <div class="wpp-input-container">
              <label class="wpp-label-repeater"><?php _e('Service Items') ?></label>
              <?php
              wp_nonce_field ( 'c_nonce_field', 'c_wpnonce');
                  $c = 0;
                  if ( count( $service ) > 0 ) {
                      if(!empty($service)) {
                          foreach( $service as $service_item_val ) {
                              foreach( $service_item_val as $service_item ) {
                                  if ( isset( $service_item['title'] ) || isset( $service_item['service_item'] ) ) {
                                      printf( '<div class="wpp-repeater-wrapper service">Title: <input class="wpp-repeater-input service" type="text" name="service[%1$s][title]" value="%2$s" />Description: <textarea class="wpp-repeater-input service" name="service[%1$s][service_item]" data-gramm_editor="false" value="">%3$s</textarea><span class="wpp-item-remove service">%4$s</span></div>', $c, $service_item['title'], $service_item['service_item'], __( 'Remove' ) );
                                      $c = $c +1;
                                  }
                              }
                          }
                      }
                  }
              ?>
              <span id="services_here"></span>
              <div class="wpp-item-add services" style="visibility: hidden; margin-bottom: -20px;"><?php _e('Add Item'); ?></div>
              <div class="wpp-item-add services add-button"><?php _e('Add Service'); ?></div>
              </div>

    </div>

    <script>
    var $ =jQuery.noConflict();
    $(document).ready(function() {
        var count = <?php echo $c; ?>;
        $(".wpp-item-add.services").click(function() {
            count = count + 1;
            $('#services_here').append('<div class="wpp-repeater-wrapper service">Title: <input class="wpp-repeater-input service" type="text" name="service['+count+'][title]" value="" placeholder="" />Description: <textarea class="wpp-repeater-input service" name="service['+count+'][service_item]" data-gramm_editor="false" value="" placeholder=""></textarea><span class="wpp-item-remove service">Remove</span></div>' );
            return false;
        });
        $(".wpp-item-remove.service").live('click', function() {
            $(this).parent().remove();
        });
    });
    </script>

</div>

<?php }
/* When the post is saved, saves our data */
function wpp_save_services_data( $post_id ) {
if(defined("DOING_AJAX") AND DOING_AJAX)
  return;
  if(!current_user_can('edit_post', $post_id ))
  return;
  if(!isset($_POST['c_wpnonce']) || !wp_verify_nonce( $_POST['c_wpnonce'], 'c_nonce_field'))
  return;
  $allowed_html = array(
          'a' => array(
              'href' => array()
          ));
  $service = $_POST['service'];
  update_post_meta ($post_id, 'wpp_service_title', wp_kses( $_POST['wpp_service_title'], $allowed_html));
  update_post_meta ($post_id,'service',$service);
 }

I hope everyone is having a great day!

I am creating this question as I have been stuck on this issue all day and I need some friendly advice.

I have created a repeater custom metabox item for a custom post type called Proposal. The repeater works fine and is saving the content and displaying it how I would like. However, when I paste content into the textarea it throws a 403 error page when you update or publish the post and have MORE than 1 repeated option.

The weird part is when you just add text to the textarea and don't paste the content everything works fine. Obviously, this is an issue I need to fix before I release the plugin to the world. Here is the code I have. If you notice anything wrong, please let me know so I can improve it as I am fairly new to backend development.

<div id="wpp_meta_inner">
<?php
//get the saved meta as an array
$service = get_post_meta($post->ID,'service',false);
$service_title = get_post_meta($post->ID, 'wpp_service_title', true);
?>
    <div>

          <div class="wpp-input-container">
              <label><?php _e('Title') ?></label>
              <input type="text" name="wpp_service_title" id="wpp_service_title" value="<?php echo $service_title; ?>" />
              <p><?php _e('Will display default "Scope of Services" if empty.') ?></p>
          </div>

          <div class="wpp-input-container">
              <label class="wpp-label-repeater"><?php _e('Service Items') ?></label>
              <?php
              wp_nonce_field ( 'c_nonce_field', 'c_wpnonce');
                  $c = 0;
                  if ( count( $service ) > 0 ) {
                      if(!empty($service)) {
                          foreach( $service as $service_item_val ) {
                              foreach( $service_item_val as $service_item ) {
                                  if ( isset( $service_item['title'] ) || isset( $service_item['service_item'] ) ) {
                                      printf( '<div class="wpp-repeater-wrapper service">Title: <input class="wpp-repeater-input service" type="text" name="service[%1$s][title]" value="%2$s" />Description: <textarea class="wpp-repeater-input service" name="service[%1$s][service_item]" data-gramm_editor="false" value="">%3$s</textarea><span class="wpp-item-remove service">%4$s</span></div>', $c, $service_item['title'], $service_item['service_item'], __( 'Remove' ) );
                                      $c = $c +1;
                                  }
                              }
                          }
                      }
                  }
              ?>
              <span id="services_here"></span>
              <div class="wpp-item-add services" style="visibility: hidden; margin-bottom: -20px;"><?php _e('Add Item'); ?></div>
              <div class="wpp-item-add services add-button"><?php _e('Add Service'); ?></div>
              </div>

    </div>

    <script>
    var $ =jQuery.noConflict();
    $(document).ready(function() {
        var count = <?php echo $c; ?>;
        $(".wpp-item-add.services").click(function() {
            count = count + 1;
            $('#services_here').append('<div class="wpp-repeater-wrapper service">Title: <input class="wpp-repeater-input service" type="text" name="service['+count+'][title]" value="" placeholder="" />Description: <textarea class="wpp-repeater-input service" name="service['+count+'][service_item]" data-gramm_editor="false" value="" placeholder=""></textarea><span class="wpp-item-remove service">Remove</span></div>' );
            return false;
        });
        $(".wpp-item-remove.service").live('click', function() {
            $(this).parent().remove();
        });
    });
    </script>

</div>

<?php }
/* When the post is saved, saves our data */
function wpp_save_services_data( $post_id ) {
if(defined("DOING_AJAX") AND DOING_AJAX)
  return;
  if(!current_user_can('edit_post', $post_id ))
  return;
  if(!isset($_POST['c_wpnonce']) || !wp_verify_nonce( $_POST['c_wpnonce'], 'c_nonce_field'))
  return;
  $allowed_html = array(
          'a' => array(
              'href' => array()
          ));
  $service = $_POST['service'];
  update_post_meta ($post_id, 'wpp_service_title', wp_kses( $_POST['wpp_service_title'], $allowed_html));
  update_post_meta ($post_id,'service',$service);
 }
Share Improve this question asked Dec 12, 2018 at 19:29 Kevin W.Kevin W. 439 bronze badges 3
  • I tested your code and could not duplicate the issue. Maybe there is some other code somewhere causing the issue. – Dave Romsey Commented Dec 13, 2018 at 6:10
  • Hey Dave, thanks for commenting back. I have finally debugged the issue and determined it was a mod_security rule that was getting triggered from the hosting company. The reason why it would trigger only when pasting content is because the content I was pasting containged the text (CMS). For some reason anytime the textarea contained () it would trigger mod_security. Do you happen to have a solution that would trip those values from the textarea before it gets saved or on blur? – Kevin W. Commented Dec 13, 2018 at 15:07
  • You got it! I posted an answer with some updated code. Not 100% sure it will fix the issue (let me know), but I was easily able to break the input fields with certain data before, and with the updated code, things are working as expected. – Dave Romsey Commented Dec 13, 2018 at 19:24
Add a comment  | 

1 Answer 1

Reset to default 1

I gave your code a look over, and updated it to include proper escaping and sanitation. I suspect that the problem could have stemmed from the lack of escaping. I also applied some formatting and WP best practices, but otherwise the code is the same.

/**
 * Meta box display callback.
 *
 * @param WP_Post $post Current post object.
 */
function wpse_metabox_callback( $post ) {
    $service_title = get_post_meta( $post->ID, 'wpp_service_title', true );
    $service       = get_post_meta( $post->ID, 'service', false ); // get as an array ?>

    <div id="wpp_meta_inner">
    <div>
            <div class="wpp-input-container">
                <label><?php esc_html_e( 'Title', 'textdomain' ); ?></label>
                <input type="text" name="wpp_service_title" id="wpp_service_title" value="<?php echo esc_attr( $service_title ); ?>" />
                <p><?php esc_html_e( 'Will display default "Scope of Services" if empty.', 'textdomain' ); ?></p>
            </div>

            <div class="wpp-input-container">
                <label class="wpp-label-repeater"><?php _e( 'Service Items', 'textdomain' ); ?></label>
                <?php
                    wp_nonce_field( 'c_nonce_field', 'c_wpnonce' );
                    $c = 0;
                    if ( count( $service ) > 0 ) {
                        if ( ! empty( $service ) ) {
                            foreach( $service as $service_item_val ) {
                                foreach( $service_item_val as $service_item ) {
                                    if ( isset( $service_item['title'] ) || isset( $service_item['service_item'] ) ) {
                                        printf(
                                            '<div class="wpp-repeater-wrapper service">' .
                                                'Title: <input class="wpp-repeater-input service" type="text" name="service[%1$s][title]" value="%2$s" />' .
                                                'Description: <textarea class="wpp-repeater-input service" name="service[%1$s][service_item]" data-gramm_editor="false" value="">%3$s</textarea><span class="wpp-item-remove service">%4$s</span>' . 
                                            '</div>',
                                            esc_attr( $c ),
                                            esc_html( $service_item['title'] ),
                                            esc_textarea( $service_item['service_item'] ),
                                            esc_html( __( 'Remove', 'textdomain' ) )
                                        );
                                        $c += 1;
                                    }
                                }
                            }
                        }
                    }
                ?>
                <span id="services_here"></span>
                <div class="wpp-item-add services" style="visibility: hidden; margin-bottom: -20px;"><?php esc_html_e( 'Add Item', 'textdomain' ); ?></div>
                <div class="wpp-item-add services add-button"><?php esc_html_e( 'Add Service', 'textdomain' ); ?></div>
            </div>
        </div>

        <script>
            var $ =jQuery.noConflict();
            $(document).ready(function() {
                    var count = <?php echo esc_js( $c ); ?>;
                    $(".wpp-item-add.services").click(function() {
                            count = count + 1;
                            $('#services_here').append('<div class="wpp-repeater-wrapper service">Title: <input class="wpp-repeater-input service" type="text" name="service['+count+'][title]" value="" placeholder="" />Description: <textarea class="wpp-repeater-input service" name="service['+count+'][service_item]" data-gramm_editor="false" value="" placeholder=""></textarea><span class="wpp-item-remove service">Remove</span></div>' );
                            return false;
                    });
                    $(".wpp-item-remove.service").live('click', function() {
                            $(this).parent().remove();
                    });
            });
        </script>
    </div><?php
}

When saving the metabox data, wp_kses_post() will be applied to each element of the $services array:

/**
 * Sanitize and save metabox data.
 */
add_action( 'save_post', 'wpse_save_meta_box' );
function wpse_save_meta_box( $post_id ) {
    if ( defined( 'DOING_AJAX' ) AND DOING_AJAX ) {
        return;
    }

    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    if ( ! isset( $_POST['c_wpnonce'] ) || ! wp_verify_nonce( $_POST['c_wpnonce'], 'c_nonce_field' ) ) {
        return;
    }

    $allowed_html = array (
        'a' => array(
            'href' => array()
    ) );

    $service = isset( $_POST['service'] ) && ! empty( $_POST['service'] ) ? array_map( 'wp_kses_post', $_POST['service'] ) : array();

    update_post_meta( $post_id, 'wpp_service_title', wp_kses( $_POST['wpp_service_title'], $allowed_html ) );
    update_post_meta( $post_id, 'service', $service );
}

For the sake of completeness, here is the code that registers the proposal post type:

/**
 * Register proposal post type.
 */
add_action( 'init', 'wpse_register_post_type_proposal' );
function wpse_register_post_type_proposal() {
    $args = [
            'label'             => __( 'Proposals', 'textdomain' ),
            'public'             => true,
            'publicly_queryable' => true,
            'show_ui'            => true,
            'show_in_menu'       => true,
            'query_var'          => true,
            'rewrite'            => [ 'slug' => 'proposal' ],
            'capability_type'    => 'post',
            'has_archive'        => true,
            'hierarchical'       => false,
            'menu_position'      => null,
            'supports'           => [ 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' ],
    ];
    register_post_type( 'proposal', $args );
}

and the meta box:

/**
 * Register meta box
 */
function wpse_register_meta_boxes() {
    add_meta_box( 'meta-box-id', __( 'My Meta Box', 'textdomain' ), 'wpse_metabox_callback', 'proposal' );
}
add_action( 'add_meta_boxes', 'wpse_register_meta_boxes' );
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1748823486a314020.html

最新回复(0)