Allow Users To Submit To Your WordPress Site: A Quotes Plugin

Allow Users To Submit To Your WordPress Site: A Quotes Plugin

Tutorial Details
  • Program: WordPress
  • Version: 3.0+
  • Difficulty: Beginner to Intermediate
  • Estimated Completion Time: 30 minutes

In this tutorial, you’ll learn how to create a plugin that allows users to submit form data. You’ll also learn about security by using nonces.


What You’ll Learn

  • How to display and process a form using a shortcode
  • Use nonces to secure user submissions

It’s simpler than you think

Shortcodes are often used to display simple data, but because they are actually a way to branch out of a page or post and execute code, they can be used for quite complex tasks, such as displaying and processing forms.

We’ll construct a plugin that will allow logged in users to:

  • submit quotes for moderation and publication
  • view their unpublished quotes
  • delete their unpublished quotes

Here’s what we’re aiming for:

All code is available in the plugin source at the top of this tutorial.


Step 1 Set-Up the Plugin

The WordPress plugin folder is located in your WordPress installation folder at wp-content/plugins. Create a folder inside the plugins folder. Let’s call it submit-user-quotes. Now, create the plugin file itself. Let’s call it submit_user_quotes.php. The path to your plugin file should now be: wp-content/plugins/submit-user-quotes/submit_user_quotes.php

Every WordPress plugin needs some header information so WordPress can identify it and make it available on your dashboard plugin page.

<?php
/*
Plugin Name: Submit User Quotes
Plugin URI: http://wp.rosselliot.co.nz/user-quotes/
Description: Allows registered users to submit quotes.
Version: 1.0
License: GPLv2
Author: Ross Elliot
Author URI: http://wp.rosselliot.co.nz
*/

You can edit this info as per your own requirements.

You’ll see the plugin listed like this:


Step 2 Plugin Initialization Function

We’re going to create a custom post type named Quotes to hold our quotes and a custom taxonomy named quote_category. This will enable cleaner administration of the quotes than simply assigning them to normal posts and categories.

The Init Hook and Function

We’ll use the following initialization code to create our custom post type and custom taxonomy:

add_action('init', 'suq_plugin_init');

function suq_plugin_init(){

  $quote_type_labels = array(
    'name' => _x('Quotes', 'post type general name'),
    'singular_name' => _x('Quote', 'post type singular name'),
    'add_new' => _x('Add New Quote', 'quote'),
    'add_new_item' => __('Add New Quote'),
    'edit_item' => __('Edit Quote'),
    'new_item' => __('Add New Quote'),
    'all_items' => __('View Quotes'),
    'view_item' => __('View Quote'),
    'search_items' => __('Search Quotes'),
    'not_found' =>  __('No Quotes found'),
    'not_found_in_trash' => __('No Quotes found in Trash'), 
    'parent_item_colon' => '',
    'menu_name' => 'Quotes'
  );
  
  $quote_type_args = array(
    'labels' => $quote_type_labels,
    'public' => true,
    'query_var' => true,
    'rewrite' => true,
    'capability_type' => 'post',
    'has_archive' => true, 
    'hierarchical' => false,
    'menu_position' => null,
    'supports' => array('title', 'editor', 'author')
  ); 
  
  register_post_type('quotes', $quote_type_args);

  $quote_category_labels = array(
    'name' => _x( 'Quote Categories', 'taxonomy general name' ),
    'singular_name' => _x( 'Quote', 'taxonomy singular name' ),
    'search_items' =>  __( 'Search Quote Categories' ),
    'all_items' => __( 'All Quote Categories' ),
    'parent_item' => __( 'Parent Quote Category' ),
    'parent_item_colon' => __( 'Parent Quote Category:' ),
    'edit_item' => __( 'Edit Quote Category' ), 
    'update_item' => __( 'Update Quote Category' ),
    'add_new_item' => __( 'Add New Quote Category' ),
    'new_item_name' => __( 'New Quote Name' ),
    'menu_name' => __( 'Quote Categories' ),
  ); 	

  $quote_category_args = array(
    'hierarchical' => true,
    'labels' => $quote_category_labels,
    'show_ui' => true,
    'query_var' => true,
    'rewrite' => array( 'slug' => 'quote_category' ),
  );
  
  register_taxonomy('quote_category', array('quotes'), $quote_category_args);
  
  $default_quote_cats = array('humor', 'politics', 'sport', 'philosophy');
  
  foreach($default_quote_cats as $cat){
  
    if(!term_exists($cat, 'quote_category')) wp_insert_term($cat, 'quote_category');
    
  }
    
}

What this code does:

We will now have a Quotes menu in our admin dashboard and a way to administer quotes and their categories.


Step 3 Define A Shortcode

Next, we’ll define a shortcode that will allow us to display (and process) the user quotes submission form in a post or page:

add_shortcode('suq_form', 'suq_form_shortcode');

Here we use the WordPress add_shortcode function to define a shortcode named suq_form and a function named suq_form_shortcode that will be called whenever WordPress encounters the shortcode [suq_form] in a post or a page.

Before we look at the form display and processing functions, let’s talk a little about…


Security

Because our plugin accepts data from the user, we implement the following security mechanisms:

  • only logged-in users have access to the post submission form
  • we use nonces to verify that the forms were generated by our plugin
  • quotes are submitted using wp_insert_post which sanitizes the data before saving it to the database
  • users can only view their own quotes, and nonces prevent them from deleting any other user’s quotes

Nonces

A nonce is a number used once. We use them to verify that the data coming back to us is actually from the forms we’ve created.

Here we generate a nonce field using wp_nonce_field that will be included in our form as a hidden field:

wp_nonce_field('suq_form_create_quote', 'suq_form_create_quote_submitted');

Because it’s now a hidden field in our form, it’ll come back to us when the form is submitted. We can then check that the nonce is valid using wp_verify_nonce:

wp_verify_nonce($_POST['suq_form_create_quote_submitted'], 'suq_form_create_quote') )

That will return true if the nonce verifies.


Step 4 The Main Function

This is the function called by our shortcode. It displays and processes the quote submission form and the quote listing/deletion form. We’ll take it in bite-sized pieces and in Step 5 we’ll look at the helper functions.

function suq_form_shortcode(){

  if(!is_user_logged_in()){
  
    return '<p>You need to be logged in to post a quote.</p>';

  }

  global $current_user;
  • check to see if the user is logged in
  • grab the WordPress $current_user variable which we’ll need to get our user ID
  if (isset( $_POST['suq_form_create_quote_submitted'] ) && wp_verify_nonce($_POST['suq_form_create_quote_submitted'], 'suq_form_create_quote') ){
  
    $suq_quote_author = trim($_POST['suq_quote_author']);
    $suq_quote_text = trim($_POST['suq_quote_text']);  
  
    if($suq_quote_author != '' && $suq_quote_text != ''){
   
      $quote_data = array(
      	'post_title' => $suq_quote_author,
      	'post_content' => $suq_quote_text,
        'post_status' => 'pending',
        'post_author' => $current_user->ID,
        'post_type' => 'quotes'     
      );
      
  
      if($quote_id = wp_insert_post($quote_data)){
      
        wp_set_object_terms( $quote_id, (int)$_POST['suq_quote_category'], 'quote_category');
        
        echo '<p>Quote created and awaiting moderation!</p>';
        
      }
      
    }else{//author or text field is empty
    
      echo '<p>Quote NOT saved! Who said it? and Quote must not be empty.</p>';    
    
    }
  }
  • if the quote creation form has been submitted, there’ll be a suq_form_create_quote_submitted field which was generated by our wp_nonce_field function. We can then verify the nonce and proceed to process the submitted quote
  • do some basic validation by making sure the quote author and quote text fields both have something in them, if not, display error message
  • construct an array setting the post status to pending (the admin will now have to approve it for publication), setting the post type to quotes (our custom post type), and setting the author of the quote to the currently logged-in user
  • if the quote was successfully inserted, set the category for the quote and display a success message
  if (isset( $_POST['suq_form_delete_submitted'] ) && wp_verify_nonce($_POST['suq_form_delete_submitted'], 'suq_form_delete')){

    if(isset($_POST['suq_delete_id'])){
    
      if($quotes_deleted = suq_delete_quotes($_POST['suq_delete_id'])){        
      
        echo '<p>' . $quotes_deleted . ' quote(s) deleted!</p>';
        
      }
    }
  }
  • if the quote delete form has been submitted, there’ll be a suq_form_delete_submitted field which was generated by our wp_nonce_field function. We can then verify the nonce and proceed to process the array of quotes checked for deletion
  • we check that we actually have some quotes checked for deletion by testing $_POST['suq_delete_id']. If so, we hand them off to the suq_delete_quotes function (see Step 5)
  • if quotes were deleted, we display a success message
  echo suq_get_create_quote_form($suq_quote_author, $suq_quote_text, $suq_quote_category);

  if($quotes_table = suq_get_user_quotes($current_user->ID)){
  
    echo $quotes_table;
    
  }
  • we output the quote creation form
  • finally, we output the quote listing/deletion form by passing the user ID to the suq_get_user_quotes function (see Step 5)

Step 5 Helper Functions

Here we’ll look at the functions that generate the forms and the function that deletes the selected quotes.

function suq_get_create_quote_form($suq_quote_author = '', $suq_quote_text = '', $suq_quote_category = 0){

  $out .= '<form id="create_quote_form" method="post" action="">';

  $out .= wp_nonce_field('suq_form_create_quote', 'suq_form_create_quote_submitted');

  $out .= '<label for="suq_quote_author">Who said it?</label><br/>';
  $out .= '<input type="text" id="suq_quote_author" name="suq_quote_author" value="' . $suq_quote_author . '"/><br/>';
  $out .= '<label for="suq_quote_category">Category</label><br/>';  
  $out .= suq_get_quote_categories_dropdown('quote_category', $suq_quote_category) . '<br/>';  
  $out .= '<label for="suq_quote_text">Quote</label><br/>';          
  $out .= '<textarea id="suq_quote_text" name="suq_quote_text" />' . $suq_quote_text . '</textarea><br/><br/>';          
  $out .= '<input type="submit" id="suq_submit" name="suq_submit" value="Submit Quote For Publication">';

  $out .= '</form>';

  return $out;
  
}
  • the function accepts 3 optional arguments for repopulating the form fields. This is a convenience for the user.
  • a nonce field is output which we check when the form is submitted
  • we output a dropdown for the quote categories by calling suq_get_quote_categories_dropdown (see next function)
function suq_get_quote_categories_dropdown($taxonomy, $selected){

  return wp_dropdown_categories(array('taxonomy' => $taxonomy, 'name' => 'suq_quote_category', 'selected' => $selected, 'hide_empty' => 0, 'echo' => 0));

}
  • the function accepts 2 arguments including the element ID of the currently selected category
  • we use the WordPress wp_dropdown_categories function to create a dropdown that lists the quote categories from the quote_category taxonomy (our custom taxonomy)
function suq_get_user_quotes($user_id){

  $args = array(
    'author' => $user_id,
    'post_type' => 'quotes',
    'post_status' => 'pending'    
  );
  
  $posts = new WP_Query($args);

  if(!$posts->post_count) return 0;
  
  $out .= '<p>Your Unpublished Quotes</p>';
  
  $out .= '<form method="post" action="">';
  
  $out .= wp_nonce_field('suq_form_delete', 'suq_form_delete_submitted');  
  
  $out .= '<table id="quotes">';
  $out .= '<thead><th>Said By</th><th>Quote</th><th>Category</th><th>Delete</th></thead>';
    
  foreach($posts->posts as $post){

    $quote_cats = get_the_terms($post->ID, 'quote_category');
    
    foreach($quote_cats as $cat){
    
      $quote_cat = $cat->name;
    
    }

    $out .= wp_nonce_field('suq_post_delete_' . $post->ID, 'suq_post_delete_id_' . $post->ID, false); 
       
    $out .= '<tr>';
    $out .= '<td>' . $post->post_title . '</td>';
    $out .= '<td>' . $post->post_content . '</td>';
    $out .= '<td>' . $quote_cat . '</td>';    
    $out .= '<td><input type="checkbox" name="suq_delete_id[]" value="' . $post->ID . '" /></td>';          
    $out .= '</tr>';
    
  }

  $out .= '</table>';
    
  $out .= '<input type="submit" name="suq_delete" value="Delete Selected Quotes!">';

  $out .= '</form>';  
  
  return $out;

}
  • accept the user ID because we need to get a listing of quotes for the current user only
  • create $args to specify our user, the post type of quotes and quotes that are pending (not yet published by the admin)
  • execute a custom query using WP_Query
  • return false if our query returns no quotes
  • start a form and generate a nonce for the form
  • loop through the quotes making sure we also grab the category of the quote
  • generate a nonce for the quote delete checkbox, assigning a unique name for the nonce by concatenating the post ID
  • output a table row containing the quote info as well as a delete checkbox

Why add a nonce for each quote?

Forms can be manipulated in the browser to post back unexpected data. In our case, each delete checkbox is assigned the value of a post. But what if a malicious user altered that value and caused our delete function to remove a post that was not actually listed?

One way to avoid this, is to use nonces for each row of post data, ensuring that the nonces are uniquely named with the post value to be deleted. We then verify the nonce upon form submission to make sure it’s a genuine return value.

function suq_delete_quotes($quotes_to_delete){

  $quotes_deleted = 0;

  foreach($quotes_to_delete as $quote){

    if (isset($_POST['suq_post_delete_id_' . $quote]) && wp_verify_nonce($_POST['suq_post_delete_id_' . $quote], 'suq_post_delete_' . $quote)){  

      wp_trash_post($quote);
      
      $quotes_deleted ++;

    }
  }

  return $quotes_deleted;
  
}
  • the function accepts an array of quote IDs to delete
  • each quote ID is checked to see if a nonce was generated for it
  • if the nonce verifies, we trash the quote using the WordPress function wp_trash_post

Step 6 Some Styling

Just drop this style info into the style.css file in your theme folder:

#suq_quote_author{
  width:300px;
}
#suq_quote_text{
  width:400px;
  height:100px;
}
#quotes{
  font-size:12px;
}
#quotes th{
  text-align:left;
}

Step 7 Try It Out

Activate the plugin, pop the shortcode onto a page, log into your site, and test it out.

The full plugin code source and a demo site link is listed at the top of this tutorial.

The source folder also contains a WordPress page template with a custom loop that displays published quotes for all users.


Final Thoughts

  • the quotes plugin could be improved by offering an edit option. As it is, users can only delete their quotes
  • you could also include an image upload option to brighten things up
  • perhaps add some custom fields to the quotes post type for quote meta info

Useful Links

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://webania.net Elvin

    I also have such solution based on Quick Post Widget plugin. But it doesn’t create new page for moderating quotes, it just saves new posts with publish_status=draft, then moderator can approove them with changing status to “publish”.
    But this tutorial is also interesting.

  • http://iamzachreed.com/ Zach Reed

    This is a really good tutorial. Could we possible get a “part 2″ with more in depth options. You mention “you could also include an image upload option to brighten things up” but could you possible make a more in-depth version of this tutorial that actually explains that. Also, maybe things like tags, etc.

    Either way, great article/tut! :)

    • http://wp.rosselliot.co.nz Ross Elliot
      Author

      Hi, Zach

      Sure, if there’s enough interest I can put your suggestion to my editor and maybe come up with a second part for this tutorial.

      Ross

      • Shawn

        Love it! I would really like to see a part 2 to this article too. Image uploading would be the exact feature I’m looking for with something like this.

        • http://www.jacorre.com Josh

          Yes definitely would love to see a follow-up to this tutorial for allowing users to upload images which would then be stored as the post thumbnail. Thanks!

    • http://hockleys.org Paul

      +1 for a second part with user image upload. Specially with the new WordPress uploader that comes with wp 3.3 :-) Thanks for a great tutorial

    • http://kartik7.wordpress.com/ Kartik

      I would love to see Part 2 with image uploading features. !!! ….. Great tutorial ….Thanx..!!

  • http://blog.kedarsapkota.com.np Kedar Sapkota

    I like this site. I knew this through themeforest.net.Thanks

  • http://contempographicdesign.com Chris Robinson

    Nice tut, would also be interested in a second part with more advanced features.

  • http://www.simplyhire.me Karim

    Nice Tut, this could be a start of creating a basic web directory :-)

  • Dappy

    I can’t seem to get this to work (probably more a problem with my theme than anything).

    However, what I really want to do is remove the requirement for the user to be logged in. I’m trying to adapt this in order to accomodate submitting testimonials that would still require approval. Is that possible even?

    • http://wp.rosselliot.co.nz Ross Elliot
      Author

      Dappy, you could simply remove the test for is_logged_in().

      But, you still need to assign the quote to a user. So, if the user is not logged in, who will you assign it to? The administrator? Sure, you could do that, but then it makes administration of the quotes problematic as the admin will not know which user (as they are anonymous) has made the quote. You, could, I guess, assign each quote to a dummy user already created by the admin.

  • Paul

    Is there an easy way to add tags from a custom taxonomy too? That would make it even more awesome! Thanks a lot!

  • Pingback: Allow Users To Submit Images Your Site | Wptuts+

  • Pingback: My Stream » Allow Users To Submit Images Your WordPress Site

  • Pingback: Allow To Submit Images To Your WordPress Site To The Users | Web Help 101

  • rm

    I would also love to be able to tag these quotes. Is there a custom taxonomy for that as well? Thanks for the great code!

  • Flick

    +1 for how to edit! :)

    • http://www.danieltomas.com Daniel

      I’m also looking forward for the second part of the tutorials. How to update and upload an image would be great!!!!

  • http://www.magdyblass.com.br Magdy Blass

    Awesome tutorial!! I tried to follow your Final Thoughts on implementing meta boxes, as I’m not a WP expert, I fall into some difficulties. Folowing the WordPress Codex and some tutorials I learned how to add the meta boxes, insert labels and inputs…, but the submitted info of the meta box does not appear on the post editor. I think that I’m not able to pass the variable or possibly don’t know how to pass.

    Reviewing this tutorial I find that you pass $quote_data as an array with all the data such as post_title, post_content, etc. So I thought that I should pass the meta box data here, but I don’t know what can be added. I searched WordPress Codex, tutorials, but couldn’t realize what I’m doing wrong…

    Any Thoughts on this?
    Also, I would realy appreciate an in-depth tutorial on how to add meta boxes to this.
    Thanks again for this great tutorial!
    Keep on with the good work.

  • http://magdyblass.com.br Magdy

    Hi Ross Elliot,

    I finally got the time and manage a way to add meta boxes to this front end submit form.
    But I’m facing a notice message all the time when debug is true. This message appear in all posts, pages and custom post type posts when editing a post or page.

    Notice: Undefined index: mt_nonce in /Applications/MAMP/htdocs/…/themes/MyTheme/mt-usq.php on line 486

    This nonce is specifically related to the meta boxes.
    If I tried to edit posts or pages, I end up in an error page with the following message:

    Notice: Undefined index: mt_nonce in /Applications/MAMP/htdocs/…/themes/MyTheme/mt-usq.php on line 486

    Notice: Undefined index: mt_nonce in /Applications/MAMP/htdocs/…/themes/MyTheme/mt-usq.php on line 486

    Warning: Cannot modify header information – headers already sent by (output started at /Applications/MAMP/htdocs/…/themes/MyTheme/mt-usq.php:486) in /Applications/MAMP/htdocs/…/wp-includes/pluggable.php on line 934

    Any idea on how to solve this issue?
    Thanks!

    • http://luisdiego.com Luis Diego

      I fixed this error: Undefined index,
      by adding: if(isset($yourpostvar) )

  • Pingback: most Popular wordpress tutorial part:2 | Write For Share

  • Pingback: Popular WordPress Tips for Beginners

  • http://cortneyrobinson.com im_cr

    could you integrate this with twitter api and require the user to submit their twitter name for post approval

  • tom sawyer

    why dont you finish and explain the whole thing, specially about “pop” the shortcode.
    this and the other ‘pictures’ plugin dont work but i think is because you are not specific.
    too bad!

  • http://www.facebook.com/mittul.chauhan Mittul Chauhan

    for extra stuff, we can create and add README.txt file as well for user friendly .. if someone only downloaded your source.zip file instead of reading the whole article .. it could be really time saving for them and very useful as well .. like how to use and etc ..

    nyway .. thanks for this though .. its really informative..