Advertisement

Creating Client Testimonials With Custom Post Types

by

If you're running any type of business online, it's a good idea to get your clients' opinions on what they thought about the services you provided.

First of all, this can benefit you by giving you feedback on how you can improve aspects of your business, but most of all, it can give you great testimonials, which can help to persuade potential clients to use your services.

The easiest way to add this functionality to your site would be to include it as a plugin. I've put together all the necessary files and included a download link for the Client Testimonials plugin above.


Testimonial Custom Post Type

Custom Post Types are great for separating your content according to different needs. Especially if your custom content doesn't need all the bells and whistles of a straight-up post.

For this tutorial, I'm going to show you how you can quickly create a Custom Post Type for your testimonials that only requires the text editor and three custom meta boxes.

add_action( 'init', 'testimonials_post_type' );
function testimonials_post_type() {
	$labels = array(
		'name' => 'Testimonials',
		'singular_name' => 'Testimonial',
		'add_new' => 'Add New',
		'add_new_item' => 'Add New Testimonial',
		'edit_item' => 'Edit Testimonial',
		'new_item' => 'New Testimonial',
		'view_item' => 'View Testimonial',
		'search_items' => 'Search Testimonials',
		'not_found' =>  'No Testimonials found',
		'not_found_in_trash' => 'No Testimonials in the trash',
		'parent_item_colon' => '',
	);

	register_post_type( 'testimonials', array(
		'labels' => $labels,
		'public' => true,
		'publicly_queryable' => true,
		'show_ui' => true,
		'exclude_from_search' => true,
		'query_var' => true,
		'rewrite' => true,
		'capability_type' => 'post',
		'has_archive' => true,
		'hierarchical' => false,
		'menu_position' => 10,
		'supports' => array( 'editor' ),
		'register_meta_box_cb' => 'testimonials_meta_boxes', // Callback function for custom metaboxes
	) );
}

Adding a Metabox

Now that a Custom Post Type for your testimonials has been created and you've established a callback for the custom metaboxes, you need to set up how those metaboxes will be displayed. So next up you need to use the add_meta_box() function to do just that.

function testimonials_meta_boxes() {
	add_meta_box( 'testimonials_form', 'Testimonial Details', 'testimonials_form', 'testimonials', 'normal', 'high' );
}

function testimonials_form() {
	$post_id = get_the_ID();
	$testimonial_data = get_post_meta( $post_id, '_testimonial', true );
	$client_name = ( empty( $testimonial_data['client_name'] ) ) ? '' : $testimonial_data['client_name'];
	$source = ( empty( $testimonial_data['source'] ) ) ? '' : $testimonial_data['source'];
	$link = ( empty( $testimonial_data['link'] ) ) ? '' : $testimonial_data['link'];

	wp_nonce_field( 'testimonials', 'testimonials' );
	?>
	<p>
		<label>Client's Name (optional)</label><br />
		<input type="text" value="<?php echo $client_name; ?>" name="testimonial[client_name]" size="40" />
	</p>
	<p>
		<label>Business/Site Name (optional)</label><br />
		<input type="text" value="<?php echo $source; ?>" name="testimonial[source]" size="40" />
	</p>
	<p>
		<label>Link (optional)</label><br />
		<input type="text" value="<?php echo $link; ?>" name="testimonial[link]" size="40" />
	</p>
	<?php
}

There are three fields you should include when setting up the data for your testimonial: the client's name, their business and a link to their site. Sometimes, you might not have all three but the least amount of information you should require is the client's name.

Tip: Whenever you add a metabox, be sure to use a nonce to secure the form. It's a must. Read more about nonces in the WordPress codex.

Saving the Custom Meta

Since you've added a custom metabox, you'll need to make sure that all the data is validated and saved. You need to hook into the save_post action and set up a callback function.

add_action( 'save_post', 'testimonials_save_post' );
function testimonials_save_post( $post_id ) {
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
		return;

	if ( ! empty( $_POST['testimonials'] ) && ! wp_verify_nonce( $_POST['testimonials'], 'testimonials' ) )
		return;

	if ( ! empty( $_POST['post_type'] ) && 'page' == $_POST['post_type'] ) {
		if ( ! current_user_can( 'edit_page', $post_id ) )
			return;
	} else {
		if ( ! current_user_can( 'edit_post', $post_id ) )
			return;
	}

	if ( ! wp_is_post_revision( $post_id ) && 'testimonials' == get_post_type( $post_id ) ) {
		remove_action( 'save_post', 'testimonials_save_post' );

		wp_update_post( array(
			'ID' => $post_id,
			'post_title' => 'Testimonial - ' . $post_id
		) );

		add_action( 'save_post', 'testimonials_save_post' );
	}

	if ( ! empty( $_POST['testimonial'] ) ) {
		$testimonial_data['client_name'] = ( empty( $_POST['testimonial']['client_name'] ) ) ? '' : sanitize_text_field( $_POST['testimonial']['client_name'] );
		$testimonial_data['source'] = ( empty( $_POST['testimonial']['source'] ) ) ? '' : sanitize_text_field( $_POST['testimonial']['source'] );
		$testimonial_data['link'] = ( empty( $_POST['testimonial']['link'] ) ) ? '' : esc_url( $_POST['testimonial']['link'] );

		update_post_meta( $post_id, '_testimonial', $testimonial_data );
	} else {
		delete_post_meta( $post_id, '_testimonial' );
	}
}

Customizing the List View

After you've created your first testimonial, you'll see it appear in the list view of your Custom Post Type; however, you won't see any of the custom meta data.

That's an easy fix: You just need to add a couple more functions to customize the list view columns so that all the info you want to see will appear.

add_filter( 'manage_edit-testimonials_columns', 'testimonials_edit_columns' );
function testimonials_edit_columns( $columns ) {
	$columns = array(
		'cb' => '<input type="checkbox" />',
		'title' => 'Title',
		'testimonial' => 'Testimonial',
		'testimonial-client-name' => 'Client\'s Name',
		'testimonial-source' => 'Business/Site',
		'testimonial-link' => 'Link',
		'author' => 'Posted by',
		'date' => 'Date'
	);

	return $columns;
}

add_action( 'manage_posts_custom_column', 'testimonials_columns', 10, 2 );
function testimonials_columns( $column, $post_id ) {
	$testimonial_data = get_post_meta( $post_id, '_testimonial', true );
	switch ( $column ) {
		case 'testimonial':
			the_excerpt();
			break;
		case 'testimonial-client-name':
			if ( ! empty( $testimonial_data['client_name'] ) )
				echo $testimonial_data['client_name'];
			break;
		case 'testimonial-source':
			if ( ! empty( $testimonial_data['source'] ) )
				echo $testimonial_data['source'];
			break;
		case 'testimonial-link':
			if ( ! empty( $testimonial_data['link'] ) )
				echo $testimonial_data['link'];
			break;
	}
}

That's pretty much everything you need to set up testimonials in the WordPress admin. But what about displaying them on the front end? Let's look at a few different ways to display your testimonials.


Display Testimonials

If you'd like to display a testimonial somewhere in one of your theme's page templates, you'll need to create a function to do so. Here's a quick one that'll allow you to display client testimonials. You can use the parameters to select one specific testimonial using an ID, or even display a random one by passing an 'orderby' value.

/**
 * Display a testimonial
 *
 * @param  int $post_per_page  The number of testimonials you want to display
 * @param  string $orderby  The order by setting  https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters
 * @param  array $testimonial_id  The ID or IDs of the testimonial(s), comma separated
 *
 * @return  string  Formatted HTML
 */
function get_testimonial( $posts_per_page = 1, $orderby = 'none', $testimonial_id = null ) {
	$args = array(
		'posts_per_page' => (int) $posts_per_page,
		'post_type' => 'testimonials',
		'orderby' => $orderby,
		'no_found_rows' => true,
	);
	if ( $testimonial_id )
		$args['post__in'] = array( $testimonial_id );

	$query = new WP_Query( $args  );

	$testimonials = '';
	if ( $query->have_posts() ) {
		while ( $query->have_posts() ) : $query->the_post();
			$post_id = get_the_ID();
			$testimonial_data = get_post_meta( $post_id, '_testimonial', true );
			$client_name = ( empty( $testimonial_data['client_name'] ) ) ? '' : $testimonial_data['client_name'];
			$source = ( empty( $testimonial_data['source'] ) ) ? '' : ' - ' . $testimonial_data['source'];
			$link = ( empty( $testimonial_data['link'] ) ) ? '' : $testimonial_data['link'];
			$cite = ( $link ) ? '<a href="' . esc_url( $link ) . '" target="_blank">' . $client_name . $source . '</a>' : $client_name . $source;

			$testimonials .= '<aside class="testimonial">';
			$testimonials .= '<span class="quote">&ldquo;</span>';
			$testimonials .= '<div class="entry-content">';
			$testimonials .= '<p class="testimonial-text">' . get_the_content() . '<span></span></p>';
			$testimonials .= '<p class="testimonial-client-name"><cite>' . $cite . '</cite>';
			$testimonials .= '</div>';
			$testimonials .= '</aside>';

		endwhile;
		wp_reset_postdata();
	}

	return $testimonials;
}

Here's the CSS that I use to style the testimonials.

.testimonial {
	padding-left: 60px;
	position: relative;
	z-index: 0;
	font-size: 16px;
	}

	aside.testimonial {

		}

	.testimonial .quote {
		position: absolute;
		left: 0;
		top: -25px;
		font-size: 300px;
		font-family: Georgia, serif;
		color: #f2f2f2;
		z-index: -1;
		line-height: 1;
		}

	.testimonial-text {
		font-style: italic;
		}

	.testimonial-client-name {
		text-align: right;
		font-size: 14px;
		}

		.testimonial-client-name cite {
			font-style: normal;
			}

Testimonials Shortcode

You might also want to display testimonials within your post or page content. That's not a problem. All you need to do is hook into the WordPress Shortcode API.

add_shortcode( 'testimonial', 'testimonial_shortcode' );
/**
 * Shortcode to display testimonials
 *
 * [testimonial posts_per_page="1" orderby="none" testimonial_id=""]
 */
function testimonial_shortcode( $atts ) {
	extract( shortcode_atts( array(
		'posts_per_page' => '1',
		'orderby' => 'none',
		'testimonial_id' => '',
	), $atts ) );

	return get_testimonial( $posts_per_page, $orderby, $testimonial_id );
}

Testimonials Widget

Widgets are great. They're easy to use and can add so much functionality to your site. So let's set up a simple testimonials widget so you can display your client's testimonials in any of your theme's widgetized areas.

/**
 * Testimonials Widget
 */
class Testimonial_Widget extends WP_Widget {
	public function __construct() {
		$widget_ops = array( 'classname' => 'testimonial_widget', 'description' => 'Display testimonial post type' );
		parent::__construct( 'testimonial_widget', 'Testimonials', $widget_ops );
	}

	public function widget( $args, $instance ) {
		extract( $args );
		$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base );
		$posts_per_page = (int) $instance['posts_per_page'];
		$orderby = strip_tags( $instance['orderby'] );
		$testimonial_id = ( null == $instance['testimonial_id'] ) ? '' : strip_tags( $instance['testimonial_id'] );

		echo $before_widget;

		if ( ! empty( $title ) )
			echo $before_title . $title . $after_title;

		echo get_testimonial( $posts_per_page, $orderby, $testimonial_id );

		echo $after_widget;
	}

	public function update( $new_instance, $old_instance ) {
		$instance = $old_instance;
		$instance['title'] = strip_tags( $new_instance['title'] );
		$instance['posts_per_page'] = (int) $new_instance['posts_per_page'];
		$instance['orderby'] = strip_tags( $new_instance['orderby'] );
		$instance['testimonial_id'] = ( null == $new_instance['testimonial_id'] ) ? '' : strip_tags( $new_instance['testimonial_id'] );

		return $instance;
	}

	public function form( $instance ) {
		$instance = wp_parse_args( (array) $instance, array( 'title' => '', 'posts_per_page' => '1', 'orderby' => 'none', 'testimonial_id' => null ) );
		$title = strip_tags( $instance['title'] );
		$posts_per_page = (int) $instance['posts_per_page'];
		$orderby = strip_tags( $instance['orderby'] );
		$testimonial_id = ( null == $instance['testimonial_id'] ) ? '' : strip_tags( $instance['testimonial_id'] );
		?>
		<p><label for="<?php echo $this->get_field_id( 'title' ); ?>">Title:</label>
		<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /></p>

		<p><label for="<?php echo $this->get_field_id( 'posts_per_page' ); ?>">Number of Testimonials: </label>
		<input class="widefat" id="<?php echo $this->get_field_id( 'posts_per_page' ); ?>" name="<?php echo $this->get_field_name( 'posts_per_page' ); ?>" type="text" value="<?php echo esc_attr( $posts_per_page ); ?>" />
		</p>

		<p><label for="<?php echo $this->get_field_id( 'orderby' ); ?>">Order By</label>
		<select id="<?php echo $this->get_field_id( 'orderby' ); ?>" name="<?php echo $this->get_field_name( 'orderby' ); ?>">
			<option value="none" <?php selected( $orderby, 'none' ); ?>>None</option>
			<option value="ID" <?php selected( $orderby, 'ID' ); ?>>ID</option>
			<option value="date" <?php selected( $orderby, 'date' ); ?>>Date</option>
			<option value="modified" <?php selected( $orderby, 'modified' ); ?>>Modified</option>
			<option value="rand" <?php selected( $orderby, 'rand' ); ?>>Random</option>
		</select></p>

		<p><label for="<?php echo $this->get_field_id( 'testimonial_id' ); ?>">Testimonial ID</label>
		<input class="widefat" id="<?php echo $this->get_field_id( 'testimonial_id' ); ?>" name="<?php echo $this->get_field_name( 'testimonial_id' ); ?>" type="text" value="<?php echo $testimonial_id; ?>" /></p>
		<?php
	}
}

add_action( 'widgets_init', 'register_testimonials_widget' );
/**
 * Register widget
 *
 * This functions is attached to the 'widgets_init' action hook.
 */
function register_testimonials_widget() {
	register_widget( 'Testimonial_Widget' );
}

Testimonials Archive Page Template

Since testimonials require custom meta, you can't rely on the default archive page template to display them correctly. In order to set up a custom archive page, you need to create a file called archive-testimonials.php and add it to your theme's main folder.

<?php
/**
 * Archive template for client testimonials
 */

get_header(); ?>

	<section id="primary" class="site-content">

		<div id="content" role="main">
			<header class="archive-header">
				<h1 class="archive-title">Testimonials</h1>
			</header><!-- #archive-header -->

			<?php while ( have_posts() ) : the_post();
				$testimonial_data = get_post_meta( get_the_ID(), '_testimonial', true );
				$client_name = ( empty( $testimonial_data['client_name'] ) ) ? '' : $testimonial_data['client_name'];
				$source = ( empty( $testimonial_data['source'] ) ) ? '' : ' - ' . $testimonial_data['source'];
				$link = ( empty( $testimonial_data['link'] ) ) ? '' : $testimonial_data['link'];
				$cite = ( $link ) ? '<a href="' . esc_url( $link ) . '" target="_blank">' . $client_name . $source . '</a>' : $client_name . $source;
				?>

				<article id="post-<?php the_ID(); ?>" <?php post_class( 'testimonial' ); ?>>
					<span class="quote">&ldquo;</span>
					<div class="entry-content">
						<p class="testimonial-text"><?php echo get_the_content(); ?><span></span></p>
						<p class="testimonial-client-name"><cite><?php echo $cite; ?></cite></p>
					</div>
				</article>

			<?php endwhile; ?>

			<?php
			global $wp_query;

			if (  1 < $wp_query->max_num_pages ) : ?>
				<nav class="archive-navigation" role="navigation">
					<div class="nav-previous alignleft"><?php next_posts_link( '<span class="meta-nav">&larr;</span> Older posts' ); ?></div>
					<div class="nav-next alignright"><?php previous_posts_link( 'Newer posts <span class="meta-nav">&rarr;</span>' ); ?></div>
				</nav><!-- .archive-navigation -->
				<?php
			endif;
			?>
		</div>

	</section><!-- #primary -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

Conclusion

Hopefully you won't feel too overwhelmed by the amount of code above. You might not actually have to use it all since it really depends on what your needs will be. You might only need the shortcode or just the archive template. Either way, going through this tutorial should prepare you for many situations that you might encounter when adding client testimonials to your site.

If you have any comments or feedback on anything you read above, please feel free to discuss it below.