1

Edit WordPress Posts from the Front End

Posted (Updated ) in PHP

I have a project coming up involving editing WordPress posts from the front end of the site. There are a bunch of plugins that let you do this the coolest of which seems to be Front-end Editor but I wanted to come up with my own solution. Luckily it turned out to be surprisingly quick and painless!

For this tutorial I’ll be editing the cafe custom post type from my last post.

 

The Frontend

I want to add a modal to my cafe pages that lets visitors edit the cafe they’re looking at.  I like Twitter Bootstrap so I’m using it for theming but you can make it look however you want.

Drop the following code into the bottom of content-cafe.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<?php
//Admin functions required to show taxonomy lists
require_once(ABSPATH.'wp-admin/includes/template.php');
 
$custom = get_post_custom( get_the_ID() );
$address = @$custom["address"][0];
$website = @$custom["website"][0];
$phone = @$custom["phone"][0];
?>
 
<!-- Lazily add resources here - this is bad, do it the right way! -->
<link rel="stylesheet" type="text/css" media="all" href="<?php bloginfo('template_url'); ?>/packages/bootstrap/css/bootstrap.min.css" />
<style>
	.modal {width:900px;margin-left:-440px;}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="<?php bloginfo('template_url'); ?>/packages/bootstrap/js/bootstrap.min.js"></script>
 
<!-- Modal -->
<form method='post' action="" enctype="multipart/form-data">
	<div id="editModal">
		<div>
			<button type="button" data-dismiss="modal">×</button>
			<h3>Edit Cafe</h3>
		</div>
		<div>
			<input type='hidden' name='frontend' value="true" />
			<input type='hidden' name='ID' value="<?= get_the_ID() ?>" />
			<input type='hidden' name='post_type' value="<?= $post->post_type ?>" />
 
			<div>
				<label for="inputTitle">Title</label>
				<div>
					<input type="text" id="inputTitle" name='post_title' placeholder="Title" value="<?= get_the_title() ?>" />
				</div>
			</div>
			<div>
				<label for="inputURL">URL</label>
				<div>
					<input type="text" id="inputURL" name='website' placeholder="http://your-cafe.com" value="<?= $website ?>" />
				</div>
			</div>
			<div>
				<label for="inputPhone">Phone</label>
				<div>
					<input type="text" id="inputPhone" name='phone' placeholder="(xx) xxxx xxxx" value="<?= $phone ?>" />
				</div>
			</div>
			<div>
				<label for="inputAddress">Address</label>
				<div>
					<textarea id="inputAddress" name='address' rows="5"><?= $address ?></textarea>
				</div>
			</div>
			<div>
				<label for="inputCountries">Countries</label>
				<div>
					<?php wp_terms_checklist(get_the_ID(), array(
						'taxonomy' => 'countries',
					)); ?>
				</div>
			</div>
			<div>
				<label for="inputContent">Description</label>
				<div>
					<?php wp_editor( get_the_content(), 'post_content', array(
						'media_buttons' => false,
					)); ?>
				</div>
			</div>
			<div>
				<label for="inputImages">Images</label>
				<div>
					<?php
						$attachments = get_posts(array(
							'post_type' => 'attachment',
							'numberposts' => -1,
							'post_status' => null,
							'post_parent' => $post->ID
						));
						if ( !$attachments )
							echo "No images";
						else
						{
							?>
							<ul>
								<?php foreach ( $attachments as $attachment ) { ?>
									<li>
										<div>
											<?php the_attachment_link( $attachment->ID , false ); ?>
											<h3><?php echo apply_filters( 'the_title' , $attachment->post_title ); ?></h3>
										</div>
									</li>
								<?php } ?>
							</ul>
							<?php
						}
					?>
					<input type="file" name="image" />
				</div>
			</div>
		</div>
		<div>
			<button data-dismiss="modal">Close</button>
			<button>Save changes</button>
		</div>
	</div>
</form>

We also need a button to open the modal. Drop the following code wherever you want that to be:

1
<a href="#editModal" role="button" data-toggle="modal">Edit Cafe</a>

 

Saving the Post

Here’s the full code I used to handle the frontend form submission (dropped into functions.php):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
 * Frontend post editing
 */
function frontend_update_cafe()
{
	if ( empty($_POST['frontend']) || empty($_POST['ID']) || empty($_POST['post_type']) || $_POST['post_type'] != 'cafes' )
		return;
 
	// $post global is required so save_cafe_custom_fields() doesn't error out
	global $post;
	$post = get_post($_POST['ID']);
 
	/*
	 * Format taxonomies properly for saving.
	 *
	 * Convert from
	 * $_POST[tax_input] => Array (
	 * 		[countries] => Array (0, 4, ...)
	 * )
	 * to
	 * $_POST[tax_input] => Array (
	 * 		[countries] => Australia,New Zealand
	 * )
	 */
	global $wpdb;
	if ( !empty($_POST['tax_input']) )
		foreach ( $_POST['tax_input'] as $taxonomy_slug=>$ids )
			$_POST['tax_input'][$taxonomy_slug] = implode(',', $wpdb->get_col("
				SELECT name
				FROM $wpdb->terms
				WHERE term_id IN (".implode(",", $ids).")
			"));
 
	$post_id = wp_update_post( $_POST );
 
	//upload images
	foreach ( $_FILES as $file => $details )
	{
		$wp_filetype = wp_check_filetype_and_ext( $details['tmp_name'], $details['name'] );
 
		//Only accept image uploads
		if ( !in_array($wp_filetype['ext'], array('png', 'jpg')) )
			continue;
 
		flynsarmy_add_media($file, $post_id, true);
	}
 
}
add_action('init', 'frontend_update_cafe');
 
// Upload media attachments
// See http://voodoopress.com/including-images-as-attachments-or-featured-image-in-post-from-front-end-form/
function flynsarmy_add_media($file_handler, $post_id, $setthumb='false') {
 
	// check to make sure its a successful upload
	if ( $_FILES[$file_handler]['error'] !== UPLOAD_ERR_OK )
		return false;
 
	require_once(ABSPATH . "wp-admin/includes/image.php");
	require_once(ABSPATH . "wp-admin/includes/file.php");
	require_once(ABSPATH . "wp-admin/includes/media.php");
 
	$attach_id = media_handle_upload( $file_handler, $post_id );
 
	if ( $setthumb )
		update_post_meta($post_id, '_thumbnail_id', $attach_id);
 
	return $attach_id;
}

 

Fancy Form Elements

For the most part on the frontend I just created basic form <input> and <textarea> fields but there are a couple of more advanced form elements that WP provides functions for.

Adding a WYSIWYG

For editing large text fields such as the post description WordPress provides a function wp_editor() that displays its TinyMCE editor:

1
2
3
<?php wp_editor( get_the_content(), 'post_content', array(
	'media_buttons' => false,
)); ?>
Edit fields on the frontend of your site with WordPress's WYSIWYG editor

Edit fields on the frontend of your site with WordPress’s WYSIWYG editor

 

Adding a Taxonomy Checkbox List

I didn’t have time to figure out how to get the whole AJAX input selection stuff from the backend onto the frontend however I did manage to get a checkbox list that works nicely but it’ll depend how many taxonomy entries you have.

1
2
3
<?php wp_terms_checklist(get_the_ID(), array(
	'taxonomy' => 'countries',
)); ?>
Edit taxonomy terms on the frontend of your site with a checkboxlist

Edit taxonomy terms on the frontend of your site with a checkboxlist

 

Unfortunately when we submit it’ll send the data like so:

$_POST[tax_input] => Array (
	[countries] => Array (0, 4, ...)
)

but we want

$_POST[tax_input] => Array (
	[countries] => Australia,New Zealand
)

so in our form submit function we’ll have to write a little bit of code to grab the term names from their respective IDs. Here’s how I did it:

1
2
3
4
5
6
7
8
global $wpdb;
if ( !empty($_POST['tax_input']) )
	foreach ( $_POST['tax_input'] as $taxonomy_slug=>$ids )
		$_POST['tax_input'][$taxonomy_slug] = implode(',', $wpdb->get_col("
			SELECT name
			FROM $wpdb->terms
			WHERE term_id IN (".implode(",", $ids).")
		"));

Media Attachments

On the frontend I displayed my gallery of media attachments with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
	$attachments = get_posts(array(
		'post_type' => 'attachment',
		'numberposts' => -1,
		'post_status' => null,
		'post_parent' => $post->ID
	));
	if ( !$attachments )
		echo "No images";
	else
	{
		?>
		<ul class="thumbnails">
			<?php foreach ( $attachments as $attachment ) { ?>
				<li class="span4">
					<div class="thumbnail">
						<?php the_attachment_link( $attachment->ID , false ); ?>
						<h3><?php echo apply_filters( 'the_title' , $attachment->post_title ); ?></h3>
					</div>
				</li>
			<?php } ?>
		</ul>
		<?php
	}
?>

And to upload attachments when processing the post I used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Upload media attachments
// See http://voodoopress.com/including-images-as-attachments-or-featured-image-in-post-from-front-end-form/
function flynsarmy_add_media($file_handler, $post_id, $setthumb='false') {
 
	// check to make sure its a successful upload
	if ( $_FILES[$file_handler]['error'] !== UPLOAD_ERR_OK )
		return false;
 
	require_once(ABSPATH . "wp-admin/includes/image.php");
	require_once(ABSPATH . "wp-admin/includes/file.php");
	require_once(ABSPATH . "wp-admin/includes/media.php");
 
	$attach_id = media_handle_upload( $file_handler, $post_id );
 
	if ( $setthumb )
		update_post_meta($post_id, '_thumbnail_id', $attach_id);
 
	return $attach_id;
}

Call it with

1
flynsarmy_add_media($file, $post_id, true);

Remember to prefix the function with your templatename to avoid function name clashes!

 

Conclusion

It seems like alot of code but really there’s not much there. The brunt of the work is in the functions.php code above and everything on the frontend is up to you.

If you spot any issues or find any other cool functions like wp_editor() or wp_terms_checklist() that bring alot of functionality to the frontend easily be sure to leave it in the comments below.