2
\$\begingroup\$

I'm wanting to step into plugin development so I can eventually push to WordPress.org so I wanted to go through and really learn how to properly develop a Plugin's settings page. I went through several pieces of documentation but I still think the WordPress Plugin Handbook is very lacking so I created a boilerplate of the Settings page as a menu main level item. I've seen tabs and I'm working on learning the development but for now I wanted to know if my plugin form is secure:

function plugin_prefix_add_admin_menu() { 
    add_menu_page( 
        'Plugin Name', 
        'Plugin Name', 
        'manage_options', 
        'plugin_name', 
        'plugin_prefix_options_page', // $menu_slug
        'dashicons-admin-settings', // https://developer.wordpress.org/resource/dashicons/#products
        1 
    );
}
add_action( 'admin_menu', 'plugin_prefix_add_admin_menu' );


// function plugin_prefix_add_sub_menu(  ) { 
//  add_submenu_page( 
//      'plugin_prefix_options_page', // https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
//      'Plugin Name', 
//      'Plugin Name', 
//      'manage_options', 
//      'plugin_name', 
//      'plugin_prefix_options_page' );
// }
// add_action( 'admin_menu', 'plugin_prefix_add_sub_menu' );


function plugin_prefix_settings_init() { 
    register_setting( 'pluginPage', 'plugin_prefix_settings' );
    add_settings_section(
        'plugin_prefix_pluginPage_section', 
        __( 'Your section description', 'text_domain' ), 
        'plugin_prefix_settings_section_callback', 
        'pluginPage'
    );
    add_settings_field( 
        'plugin_prefix_text_field_0', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_text_field_0_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_text_field_1', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_text_field_1_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_text_field_2', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_text_field_2_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_checkbox_field_3', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_checkbox_field_3_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_checkbox_field_4', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_checkbox_field_4_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_checkbox_field_5', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_checkbox_field_5_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_radio_field_6', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_radio_field_6_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_radio_field_7', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_radio_field_7_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_select_field_9', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_select_field_9_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_select_field_10', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_select_field_10_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_select_field_11', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_select_field_11_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_textarea_field_12', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_textarea_field_12_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
    add_settings_field( 
        'plugin_prefix_textarea_field_13', 
        __( 'Settings field description', 'text_domain' ), 
        'plugin_prefix_textarea_field_13_render', 
        'pluginPage', 
        'plugin_prefix_pluginPage_section' 
    );
}
add_action( 'admin_init', 'plugin_prefix_settings_init' );


function plugin_prefix_text_field_0_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <input type='email' name='plugin_prefix_settings[plugin_prefix_text_field_0]' value='<?php echo $options['plugin_prefix_text_field_0']; ?>' placeholder='Enter email'>
    <?php
}


function plugin_prefix_text_field_1_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <input style="width: 300px;" type='text' name='plugin_prefix_settings[plugin_prefix_text_field_1]' value='<?php echo $options['plugin_prefix_text_field_1']; ?>' placeholder='enter something'>
    <?php
}


function plugin_prefix_text_field_2_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <input type='text' name='plugin_prefix_settings[plugin_prefix_text_field_2]' value='<?php echo $options['plugin_prefix_text_field_2']; ?>' placeholder='enter somethng'>
    <?php
}


function plugin_prefix_checkbox_field_3_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <input type='checkbox' name='plugin_prefix_settings[plugin_prefix_checkbox_field_3]' <?php if (!empty($options['plugin_prefix_checkbox_field_3'])) echo checked( $options['plugin_prefix_checkbox_field_3'], 1 ); ?> value='1'></input>
<?php if (!empty($twitterhandle)) echo $twitterhandle; ?>
    Foobar
    <?php
}


function plugin_prefix_checkbox_field_4_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <input type='checkbox' name='plugin_prefix_settings[plugin_prefix_checkbox_field_4]' <?php if (!empty($options['plugin_prefix_checkbox_field_4'])) echo checked( $options['plugin_prefix_checkbox_field_4'], 1 ); ?> value='1'></input>
    Foobar
    <?php
}


function plugin_prefix_checkbox_field_5_render() { 

    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <input type='checkbox' name='plugin_prefix_settings[plugin_prefix_checkbox_field_5]' <?php if (!empty($options['plugin_prefix_checkbox_field_5'])) echo checked( $options['plugin_prefix_checkbox_field_5'], 1 ); ?> value='1'></input>
    Foobar
    <?php
}


function plugin_prefix_radio_field_6_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <label>
        <input type='radio' name='plugin_prefix_settings[plugin_prefix_radio_field_6]' <?php if (!empty($options['plugin_prefix_radio_field_6'])) echo checked( $options['plugin_prefix_radio_field_6'], 1 ); ?> value='1'></input>
        <span>Foo</span>
    </label>
    <label>
        <input type='radio' name='plugin_prefix_settings[plugin_prefix_radio_field_6]' <?php if (!empty($options['plugin_prefix_radio_field_6'])) echo checked( $options['plugin_prefix_radio_field_6'], 2 ); ?> value='2'></input>
        <span>Bar</span>
    </label>
    <p class="description">Bacon ipsum dolor amet ball tip shoulder pork, short loin sirloin sausage doner jerky chuck landjaeger tenderloin t-bone beef shankle picanha.</p>
    <?php
}


function plugin_prefix_radio_field_7_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <label>
        <input type='radio' name='plugin_prefix_settings[plugin_prefix_radio_field_7]' <?php if (!empty($options['plugin_prefix_radio_field_7'])) echo checked( $options['plugin_prefix_radio_field_7'], 1 ); ?> value='1'></input>
        <span>Foo</span>
    </label>
    <br />
    <label>
        <input type='radio' name='plugin_prefix_settings[plugin_prefix_radio_field_7]' <?php if (!empty($options['plugin_prefix_radio_field_7'])) echo checked( $options['plugin_prefix_radio_field_7'], 2 ); ?> value='2'></input>
        <span>Bar</span>
    </label>
    <br />
    <label>
        <input type='radio' name='plugin_prefix_settings[plugin_prefix_radio_field_7]' <?php if (!empty($options['plugin_prefix_radio_field_7'])) echo checked( $options['plugin_prefix_radio_field_7'], 3 ); ?> value='3'></input>
        <span>luu</span>
    </label>
    <?php
}


function plugin_prefix_select_field_9_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <select name='plugin_prefix_settings[plugin_prefix_select_field_9]'>
        <option value='1' <?php selected( $options['plugin_prefix_select_field_9'], 1 ); ?>>Option 1</option>
        <option value='2' <?php selected( $options['plugin_prefix_select_field_9'], 2 ); ?>>Option 2</option>
        <option value='3' <?php selected( $options['plugin_prefix_select_field_9'], 3 ); ?>>Option 3</option>
        <option value='4' <?php selected( $options['plugin_prefix_select_field_9'], 4 ); ?>>Option 4</option>
    </select>
    <?php
}


function plugin_prefix_select_field_10_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <select name='plugin_prefix_settings[plugin_prefix_select_field_10]'>
        <option value='1' <?php selected( $options['plugin_prefix_select_field_10'], 1 ); ?>>Option 1</option>
        <option value='2' <?php selected( $options['plugin_prefix_select_field_10'], 2 ); ?>>Option 2</option>
    </select>
    <?php
}


function plugin_prefix_select_field_11_render() { 

    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <select name='plugin_prefix_settings[plugin_prefix_select_field_11]'>
        <option value='1' <?php selected( $options['plugin_prefix_select_field_11'], 1 ); ?>>Option 1</option>
        <option value='2' <?php selected( $options['plugin_prefix_select_field_11'], 2 ); ?>>Option 2</option>
    </select>
    <?php
}


function plugin_prefix_textarea_field_12_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <textarea cols='40' rows='5' name='plugin_prefix_settings[plugin_prefix_textarea_field_12]'> 
        <?php echo $options['plugin_prefix_textarea_field_12']; ?>
    </textarea>
    <?php
}


function plugin_prefix_textarea_field_13_render() { 
    $options = get_option( 'plugin_prefix_settings' );
    ?>
    <textarea cols='40' rows='10' name='plugin_prefix_settings[plugin_prefix_textarea_field_13]'> 
        <?php echo $options['plugin_prefix_textarea_field_13']; ?>
    </textarea>
    <?php
}


function plugin_prefix_settings_section_callback() { 
    ?>
    <p> 
        <?= esc_html__('Bacon ipsum dolor amet beef ribs pastrami shoulder capicola chuck short loin venison salami pork loin. Shoulder swine pastrami spare ribs brisket rump. Ball tip capicola pork chop ribeye ham hock, landjaeger boudin short loin tail corned beef picanha. Prosciutto spare ribs sirloin chicken, turkey pancetta tongue leberkas jowl.', 
        'text_domain'); ?>
    </p>
    <?php
}


function plugin_prefix_options_page() { 
    if (!current_user_can('manage_options')) {
        return;
    }
    if (isset($_GET['settings-updated'])) {
        add_settings_error(
            'manage_options_messages', 
            'manage_options_code', 
            __('The settings were saved', 'theme-panel'), 
            'updated'
        );
    }
    ?>
    <form action='options.php' method='post'>
        <h2>Plugin Name</h2>
        <?php
        settings_fields('pluginPage');
        do_settings_sections('pluginPage');
        submit_button('Save changes to this plugin page');
        ?>
    </form>
    <?php
}

I'm looking for input on any security issues and how I can better develop. If I'm missing anything security wise or can suggest any documentation to further expand my knowledge it would be great.

\$\endgroup\$
2
\$\begingroup\$

A cursory glance suggests the main issue is a lack of escaping on the HTML esc_attr esc_html esc_textarea and esc_url will improve the security.

This is to prevent the values you entered becoming executable HTML in the UI. E.g. enter the following into your text area and save:

</textarea><script>alert('hello there');</script><textarea>

I couldn't see anything from the settings API usage I disagreed with but I didn't look super close

I wasn't a big fan of the <?= syntax, and in a lot of cases it was unnecessary, e.g.

<?= esc_html__

could have been

<?php esc_html__e(

I'm not so keen either on multiple fields using the same storage, e.g.:

$options = get_option( 'plugin_prefix_settings' );
?>
<input type='checkbox' name='plugin_prefix_settings[plugin_prefix_checkbox_field_5]' <?php if (!empty($options['plugin_prefix_checkbox_field_5'])) echo checked( $options['plugin_prefix_checkbox_field_5'], 1 ); ?> value='1'></input>

My preference would have been for every field to have an independent option. I also see some room for abstraction/encapsulation here via an object. From that point on your fields could be defined in an array, which is then looped over to create a field object, with different kinds of object for different UI's of field.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.