web-dev-qa-db-fra.com

Développement d'un formulaire de post-édition sécurisé

Je souhaite ajouter une fonctionnalité de post-édition frontale à l'un de mes sites Wordpress. J'ai trouvé quelques plugins qui font cela, mais ils ne répondent pas à tous mes besoins, alors j'ai décidé d'adapter une solution existante pour développer mon propre plugin qui renverra avec un shortcode un formulaire d’édition frontal. Mon plugin fonctionne, je peux éditer et sauvegarder des posts, mais je ne peux pas résoudre un avertissement et je n'ai pas (encore) suffisamment de connaissances pour rendre ce plugin plus sécurisé . Aucune suggestion?

L'avertissement:

Avertissement: impossible de modifier les informations d'en-tête - en-têtes déjà envoyés par (sortie démarrée à .../wp-content/plugins/front-post-edit.php: 139) dans .../wp-includes/pluggable.php à la ligne 1228

P.S. La ligne 139 est la dernière ligne de mon plugin (une ligne sans code).

Mon code de plugin:

<?php
/*
 * Plugin Name: Front Post Editor
 * 
 */

add_shortcode( 'front_post_edit', 'post_shortcode' );

function post_shortcode() {
    return getForm();
}

function getForm() {

    if ( !is_user_logged_in()) {
        echo '<p class="alert-box notice">You must be <a href="' . esc_url( wp_login_url( get_permalink() ) ) . '" title="Login">logged in</a>!';
    } else {

        if( 'POST' == $_SERVER['REQUEST_METHOD'] && !empty( $_POST['action'] ) &&  $_POST['action'] == "edit_post" && isset($_POST['postid'])) {
            $post_to_edit = array();
            $post_to_edit = get_post($_POST['postid']);

            /* these are the fields that we are editing in the form below */
            $title = $_POST['item_title'];
            $description = $_POST['item_description'];
            $category = $_POST['item_category'];
            $location = $_POST['item_location'];
            $location2 = $_POST['item_location2'];

            /* this code will save the title and description into the post_to_edit array */
            $post_to_edit->post_title = $title;
            $post_to_edit->post_content = $description;

            /* this code is a must */
            $pid = wp_update_post($post_to_edit);

            /* save taxonomies: post ID, form field name, taxonomy name, if it appends(true) or rewrite(false) */
            wp_set_post_terms($pid, array($_POST['item_category']),'category',false);
            wp_set_post_terms($pid, array($_POST['item_location']),'location',false);

            /* update custom fields with the new info */
            update_post_meta($pid, 'item_location2', $location2);

            /* redirect user after done editing */
            wp_redirect( home_url( '/myposts' ) );

        }

        /* get post to edit */
        $post_to_edit = get_post($_POST['postid']);

        /* get this post's category taxonomy term id */
        $term_name = strip_tags( get_the_term_list( $post_to_edit->ID, 'category', '', ', ', '' ) );
        $term_obj = get_term_by('name', $term_name, 'category');
        $term_id = $term_obj->term_id;

        /* array for wp_dropdown_category to display with the current post category selected by default */
        $args_cat = array(
            'selected'           => $term_id,
            'name'               => 'item_category',
            'class'              => 'postform',
            'tab_index'          => 10,
            'depth'              => 2,
            'hierarchical'       => 1,
            'taxonomy'           => 'category',
            'hide_empty'      => false );

        /* get this post's location taxonomy term id */
        $term_name2 = strip_tags( get_the_term_list( $post_to_edit->ID, 'location', '', ', ', '' ) );
        $term_obj2 = get_term_by('name', $term_name2, 'location');
        $term_id2 = $term_obj2->term_id;

        $args_loc = array(
            'selected'           => $term_id2,
            'name'               => 'item_location',
            'class'              => 'postform',
            'tab_index'          => 10,
            'depth'              => 2,
            'hierarchical'       => 1,
            'taxonomy'           => 'location',
            'hide_empty'      => false ); 

    ?>

<!-- EDIT FORM -->

    <form id="edit_post" name="edit_post" method="post" action="" enctype="multipart/form-data">

        <!-- post name -->
        <fieldset name="item_title">
            <label for="item_title">Item title:</label><br />
            <input type="text" id="item_title" value="<?php echo $post_to_edit->post_title; ?>" tabindex="5" name="item_title" />
        </fieldset>

        <!-- post Content -->
        <fieldset class="item_description">
            <label for="item_description">Item description:</label><br />
            <textarea id="item_description"  tabindex="15" name="item_description"><?php echo $post_to_edit->post_content; ?></textarea>
        </fieldset>

        <!-- post Category -->
        <fieldset id="item_category">
            <label for="item_category">Item category:</label>
            <?php wp_dropdown_categories( $args_cat ); ?>
        </fieldset>

        <!-- post Location -->
        <fieldset id="item_location">
            <label for="item_location">Item location:</label>
            <?php wp_dropdown_categories( $args_loc ); ?>
        </fieldset>

        <!-- custom fields -->

        <fieldset class="item_location2">
            <label for="item_location2">Location 2:</label><br />
            <input type="text" value="<?php echo get_post_meta($post_to_edit->ID,'item_location2', true); ?>" id="item_location2" tabindex="20" name="item_location2" />
        </fieldset>

        <!-- submit button -->
        <fieldset class="submit">
            <input type="submit" value="Save Post" tabindex="40" id="submit" name="submit" />
        </fieldset>

        <input type="hidden" name="postid" value="<?php echo $post_to_edit->ID; ?>" /> <!-- DONT REMOVE OR CHANGE -->
        <input type="hidden" name="action" value="edit_post" />                           <!-- DONT REMOVE OR CHANGE -->
        <input type="hidden" name="change_cat" value="" />                                <!-- DONT REMOVE OR CHANGE -->
    <?php // wp_nonce_field( 'new-post' ); ?>
    </form>
    <!-- END OF FORM -->

<?php } ?><!-- user is logged in -->
<?php } ?><!-- getForm -->

Un bouton Éditer a été ajouté au pied de page de chaque entrée générée par une boucle de la forme suivante:

<form class="edit-post" action="<?php echo home_url( '/edit'); ?>" method="post">
    <input type="hidden" name="postid" value="<?php the_ID(); ?>" />
    <input type="submit"  value="Edit" />
</form>
1
Iurie Malai

D'après le code, il semble que votre avertissement provient d'une redirection trop tardive. En règle générale, les redirections doivent être effectuées au plus tard après l'action init. Et après la redirection, vous devriez mourir () (je ne pense pas que le wp_redirect le fasse pour vous)

En ce qui concerne la sécurité, il ne suffit pas de vérifier que l'utilisateur est connecté, vous devez également vérifier s'il a la capacité de modifier la publication, quelque chose comme if current_user_can('edit_post',$post_id). Vous devez le vérifier du côté de l'interface utilisateur et du côté du serveur. ce n’est pas parce que vous ne montrez pas la capacité du pirate informatique qu’il ne créera pas de requête HTTP spéciale pour modifier la publication si vous n’avez pas de protection côté serveur.

1
Mark Kaplun

Après quelques études et recherches, j’ai abandonné l’approche décrite dans ma question. En effet, j’ai trouvé comment ajouter la fonctionnalité de montage frontal à l’aide de la solution @TheDeadMedic . Maintenant je n'ai que deux petites questions :

1) Existe-t-il une meilleure façon d'utiliser/de définir l'ID et la date de publication? J'ai utilisé pour cela deux entrées cachées: foo_id et foo_date.

2) Quel est le degré de sécurité de la communication entre les formulaires?

C'est le formulaire qui obtient l'ID de l'article dans la variable "postid" et le transmet à la page "post-formulaire":

<form action="<?php echo home_url( '/post-form'); ?>" method="post">
    <input type="hidden" name="postid" value="<?php the_ID(); ?>" /> 
    <input type="submit"  value="Edit" />
</form>

<?php

Il s’agit du formulaire frontal permettant de publier des modifications (le code ajouté par moi est marqué avec // MY CODE):

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(
            // MY CODE
            'foo_id'      => FILTER_DEFAULT,
            'foo_date'    => FILTER_DEFAULT,
            // END MY CODE
            'postTitle'   => FILTER_DEFAULT,
            'postContent' => FILTER_DEFAULT,
            'location2'   => 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['postContent'] = wp_check_invalid_utf8( $data['postContent'] );
        $data['location2']   = sanitize_text_field( $data['location2'] );

        $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(
                // MY CODE
                'ID'           => $data['foo_id'],
                'post_date'    => $data['foo_date'],
                // END MY CODE
                '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() {

    // MY CODE
    if( 'POST' == $_SERVER['REQUEST_METHOD'] && isset( $_POST['postid'] ) ) {
        $post_to_edit                   = array();
        $post_to_edit                   = get_post( $_POST['postid'] );
        $this->data['foo_id']           = $post_to_edit->ID;
        $this->data['foo_date']         = $post_to_edit->post_date;
        $this->data['item_name']        = $post_to_edit->post_title;
        $this->data['item_description'] = $post_to_edit->post_content;
    }
    // END MY CODE

        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>
        <label for="location2">Location 2</label>
        <input type="text" name="location2" id="location2" title="Location 2 (opțional)" value="<?php

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

        ?>" />
        </fieldset>

        <fieldset>
            // MY CODE
            <input type="hidden" name="foo_id" id="foo_id" value="<?php

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

        ?>" />

            <input type="hidden" name="foo_date" id="foo_date" value="<?php

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

        ?>" />
            // END MY CODE
            <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;
0
Iurie Malai