I want to add a front end posting functionality to one of my WordPress sites. For more control and for more understanding of how this works (I am not a professional programmer) I choosed to use as a base for future development a solution from the wpkb.com site (see the bellow code). This solution works, but the question is how it is protected against security issues/malicious attacks?

Secondly, when a new post is submitted, it is saved, but also produces a notice, and I don’t understand why:

Notice: Undefined variable: hasError in …/submit-from-front.php on
line 106

This is the 106 line:

//Check if any error was detected in validation.
if($hasError == true) {

The full code:

class WPSubmitFromFront {

    protected $pluginPath;  
    protected $pluginUrl;  

    public function __construct() {  

        // Set Plugin Path  
        $this->pluginPath = dirname(__FILE__);  
        // Set Plugin URL  
        $this->pluginUrl = WP_PLUGIN_URL . '/submitfromfront';

         //Add CSS for the form.
        add_action('wp_enqueue_scripts', array($this, 'addStyles'));

        //Add the short code
        add_shortcode('post_from_front', array($this, 'handleFrontEndForm'));  

    }
 function handleFrontEndForm() {
        //Check if the user has permission to publish the post.
        if ( !current_user_can('publish_posts') ) {
            echo "<h2>Please Login to post links.</h2>";
            return;
        }

        if($this->isFormSubmitted() && $this->isNonceSet()) {
            if($this->isFormValid()) {
                $this->createPost();
            } else {
                $this->displayForm();
            }
        } else {
            $this->displayForm();
        }

    }

    //This function displays the HTML form.
    public function displayForm() {
        ?>
        <div id ="frontpostform">
            <form action="" id="formpost" method="POST" enctype="multipart/form-data">

                <fieldset>
                    <label for="postTitle">Post Title</label>

                    <input type="text" name="postTitle" id="postTitle" />
                </fieldset>

                <fieldset>
                    <label for="postContent">Content</label>

                    <textarea name="postContent" id="postContent" rows="10" cols="35" ></textarea>
                </fieldset>

                <fieldset>
                    <button type="submit" name="submitForm" >Create Post</button>
                </fieldset>

                <?php wp_nonce_field( 'front_end_new_post' , 'nonce_field_for_front_end_new_post'); ?>

            </form>
        </div>
        <?php
    }

    function addStyles() {
        // Register the style for the form
        wp_register_style( 'submitform-style', plugins_url( 'submitfromfront/submitfromfront.css'));
        wp_enqueue_style( 'submitform-style' );
    }

    function isFormSubmitted() {
        if( isset( $_POST['submitForm'] ) ) return true;
        else return false;
    }

    function isNonceSet() {
        if( isset( $_POST['nonce_field_for_front_end_new_post'] )  &&
          wp_verify_nonce( $_POST['nonce_field_for_front_end_new_post'], 'front_end_new_post' ) ) return true;
        else return false;
    }

    function isFormValid() {
        //Check all mandatory fields are present.
        if ( trim( $_POST['postTitle'] ) === '' ) {
            $error="Please enter a title.";
            $hasError = true;
        } else if ( trim( $_POST['postContent'] ) === '' ) {
            $error="Please enter the content.";
            $hasError = true;
        } 

        //Check if any error was detected in validation.
        if($hasError == true) {
            echo $error;
            return false;
        }
        return true;
    }

   function createPost() {

        //Get the ID of currently logged in user to set as post author
        $current_user = wp_get_current_user();
        $currentuserid = $current_user->ID;

        //Get the details from the form which was posted
        $postTitle = $_POST['postTitle'];
        $contentOfPost = $_POST['postContent'] ;
        $postSatus="publish"; // 'pending' - in case you want to manually aprove all posts;

        //Create the post in WordPress
        $post_id = wp_insert_post( array(
                        'post_title'        => $postTitle,
                        'post_content'      => $contentOfPost,
                        'post_status'       => $postSatus , 
                        'post_author'       => $currentuserid

                    ));

    }
}

$wpSubmitFromFEObj = new WPSubmitFromFront();

1 Answer
1

Hopefully the code will be sufficient to describe the key points, but please do comment if you have any further questions:

<?php

class WPSE_Submit_From_Front {
    const NONCE_VALUE = 'front_end_new_post';
    const NONCE_FIELD = 'fenp_nonce';

    protected $pluginPath;
    protected $pluginUrl;
    protected $errors = array();
    protected $data = array();

    function __construct() {
        $this->pluginPath = plugin_dir_path( __file__ );
        $this->pluginUrl  = plugins_url( '', __file__ );

        add_action( 'wp_enqueue_scripts', array( $this, 'addStyles' ) );
        add_shortcode( 'post_from_front', array( $this, 'shortcode' ) );

        // Listen for the form submit & process before headers output
        add_action( 'template_redirect',  array( $this, 'handleForm' ) );
    }

    function addStyles() {
        wp_enqueue_style( 'submitform-style', "$this->pluginUrl/submitfromfront.css" );
    }

    /**
     * Shortcodes should return data, NOT echo it.
     *
     * @return string
     */
    function shortcode() {
        if ( ! current_user_can( 'publish_posts' ) )
            return sprintf( '<p>Please <a href="https://wordpress.stackexchange.com/questions/210648/%s">login</a> to post links.</p>', esc_url( wp_login_url(  get_permalink() ) ) );
        elseif ( $this->isFormSuccess() )
            return '<p class="success">Nice one, post created.</p>';
        else
            return $this->getForm();
    }

    /**
     * Process the form and redirect if sucessful.
     */
    function handleForm() {
        if ( ! $this->isFormSubmitted() )
            return false;

        // http://php.net/manual/en/function.filter-input-array.php
        $data = filter_input_array( INPUT_POST, array(
            'postTitle'   => FILTER_DEFAULT,
            'location2'   => FILTER_DEFAULT,
            'postContent' => FILTER_DEFAULT,
        ));

        $data = wp_unslash( $data );
        $data = array_map( 'trim', $data );

        // You might also want to more aggressively sanitize these fields
        // By default WordPress will handle it pretty well, based on the current user's "unfiltered_html" capability

        $data['postTitle']   = sanitize_text_field( $data['postTitle'] );
        $data['location2']   = sanitize_text_field( $data['location2'] );
        $data['postContent'] = wp_check_invalid_utf8( $data['postContent'] );

        $this->data = $data;

        if ( ! $this->isNonceValid() )
            $this->errors[] = 'Security check failed, please try again.';

        if ( ! $data['postTitle'] )
            $this->errors[] = 'Please enter a title.';

        if ( ! $data['postContent'] )
            $this->errors[] = 'Please enter the content.';

        if ( ! $this->errors ) {
            $post_id = wp_insert_post( array(
                'post_title'   => $data['postTitle'],
                'post_content' => $data['postContent'],
                'post_status'  => 'publish',
            ));

            if ( $post_id ) {
                add_post_meta( $post_id, 'location2', $data['location2'] );

                // Redirect to avoid duplicate form submissions
                wp_redirect( add_query_arg( 'success', 'true' ) );
                exit;

            } else {
                $this->errors[] = 'Whoops, please try again.';
            }
        }
    }

    /**
     * Use output buffering to *return* the form HTML, not echo it.
     *
     * @return string
     */
    function getForm() {
        ob_start();
        ?>

<div id ="frontpostform">
    <?php foreach ( $this->errors as $error ) : ?>

        <p class="error"><?php echo $error ?></p>

    <?php endforeach ?>

    <form id="formpost" method="post">
        <fieldset>
            <label for="postTitle">Post Title</label>
            <input type="text" name="postTitle" id="postTitle" value="<?php

                // "Sticky" field, will keep value from last POST if there were errors
                if ( isset( $this->data['postTitle'] ) )
                    echo esc_attr( $this->data['postTitle'] );

            ?>" />
        </fieldset>

        <fieldset>
            <label for="postContent">Content</label>
            <textarea name="postContent" id="postContent" rows="10" cols="35" ><?php

                if ( isset( $this->data['postContent'] ) )
                    echo esc_textarea( $this->data['postContent'] );

            ?></textarea>
        </fieldset>

        <fieldset>
            <button type="submit" name="submitForm" >Create Post</button>
        </fieldset>

        <?php wp_nonce_field( self::NONCE_VALUE , self::NONCE_FIELD ) ?>
    </form>
</div>

        <?php
        return ob_get_clean();
    }

    /**
     * Has the form been submitted?
     *
     * @return bool
     */
    function isFormSubmitted() {
        return isset( $_POST['submitForm'] );
    }

    /**
     * Has the form been successfully processed?
     *
     * @return bool
     */
    function isFormSuccess() {
        return filter_input( INPUT_GET, 'success' ) === 'true';
    }

    /**
     * Is the nonce field valid?
     *
     * @return bool
     */
    function isNonceValid() {
        return isset( $_POST[ self::NONCE_FIELD ] ) && wp_verify_nonce( $_POST[ self::NONCE_FIELD ], self::NONCE_VALUE );
    }
}

new WPSE_Submit_From_Front;

Leave a Reply

Your email address will not be published. Required fields are marked *