2

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
  • 1
    I'll be blunt - that code sucks. So much so that I'll post a revision shortly. Dec 3, 2015 at 12:35

1 Answer 1

3

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="%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;
10
  • 1) I added an add_post_meta($post_id, 'location2', $data['postLocation2']); line just before the if statement with the 'Whoops, please try again.' message, but I am not sure if this is the right place/moment to save the post meta-data. 2) From the php.net/manual/en/function.filter-input.php: "If (filter parameter) omitted, FILTER_DEFAULT will be used, which is equivalent to FILTER_UNSAFE_RAW. This will result in no filtering taking place by default." This means that I must to replace the FILTER_DEFAULT with some specific filters? Can you answer, please?
    – Yurié
    Dec 6, 2015 at 17:22
  • 1
    You want to place it in handleForm - see revised answer: Dec 7, 2015 at 15:18
  • Today I discovered this issue: if the post is created successfully and the message Nice one, post created. is displayed, when the page with this message is reloaded a second post with the same title and same content is created and so on. How to avoid this?
    – Yurié
    Dec 9, 2015 at 20:55
  • 1
    See revised answer - I've slightly refactored the code so that the form handling happens before headers are sent (i.e. before the shortcode is rendered). This way we can redirect on successful submission, a process known as Post/Redirect/Get Dec 9, 2015 at 20:57
  • This form works great! Now I want to add to my site the front end post editing functionality. What will be the optimal solution: to add this functionality to the above posting form or will be better to make a separate form for editing? Any suggestions? I can add a new question if is needed.
    – Yurié
    Dec 10, 2015 at 9:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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