WordPress Custom Plugin Architecture and Theme Structure

Posted by Pete Schuster on

Using WordPress as a CMS to build out a new site is very easy. One thing to keep in mind, however, is that when you’re creating your custom theme, you need to keep your content types separate. We can do this by creating a custom plugin that lists all the content types, meta boxes, theme options, etc. This keeps the sites content independant from the theme, and allows the user to retain their data if they feel the need to switch themes.

This is something I do on all of my projects, and I’ve been working on building a boilerplate plugin for faster and more efficient development. You can download the files shown below here.

The Code – Main Plugin File

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
<?php
/*
Plugin Name: Pete Schuster Movie Plugin
Plugin URI: http://peteschuster.com
Description: Establishes the movie content types, meta boxes, and taxonomy for a custom theme.
Version: 1.00
Author: Pete Schuster
Author URI: http://peteschuster.com
License: All Rights Reserved
*/
 
require_once( 'ps-content-types.php' );
require_once( 'ps-meta-boxes.php' );
 
function ps_init() {
 
	ps_movie_type();
	ps_movie_genre_tax();
 
}
 
add_action( 'init', 'ps_init' );
 
add_action( 'add_meta_boxes', 'ps_movie_add_metas' );
add_action( 'save_post', 'ps_movie_save_metas' );
 
 
//script actions with page detection
add_action( 'admin_print_scripts-post.php', 'ps_image_admin_scripts' );
add_action( 'admin_print_scripts-post-new.php', 'ps_image_admin_scripts' );
 
function ps_image_admin_scripts() {
 
	wp_enqueue_script( 'imageUpload', plugins_url( '/ps-movie-plugin/js/image_upload.js' ), array( 'jquery','media-upload','thickbox' ) );
 
}
 
//style actions with page detection
add_action( 'admin_print_styles-post.php', 'ps_image_admin_styles' );
add_action( 'admin_print_styles-post-new.php', 'ps_image_admin_styles' );
 
function tmf_image_admin_styles() {
 
	wp_enqueue_style( 'thickbox' );
 
}
 
 
//Custom Admin CSS
function ps_admin_css() {
       echo '<link rel="stylesheet" type="text/css" href="' . plugins_url( 'css/wp_admin.css', __FILE__ ) . '">';
}
 
add_action( 'admin_head', 'ps_admin_css' );
 
?>

The code above is the first type in the plugin. It establishes the plugins name, author, function, etc. This is how WordPress recognizes the plugin. After that it includes other files, and calls some initialization functions from those files. It also includes a custom JS script and CSS file. I used to include all functions in one file, but once you get to 2+ content types with meta boxes, it starts to get crowded. In the above code I separate all my custom post type and custom taxonomy registrations and custom meta boxes.

Custom Content Type

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
<?php
 
function ps_movie_type() {
 
	register_post_type( 'ps_movie', array(
		'capability_type' => 'page',
		'public' => true,
		'supports' => array(
			'title',
			'thumbnail',
			'editor'
		),
		'query_var' => 'ps_movie',
		'rewrite' => array(
			'slug' => "movie",
			'with_front' => false
		),
		'labels' => array(
			'name' => "Movies",
			'singular_name' => "Movie",
			'add_new' => "Add New Movie",
			'add_new_item' => "Add New Movie",
			'edit_item' => "Edit Movie",
			'new_item' => "New Movie",
			'view_item' => "View Moviee",
			'search_items' => "Search Movie",
			'not_found' => "No Movies were found",
			'not_found_in_trash' => "No Movies Found in Trash"
		)
	) );
 
}
 
function ps_movie_genre_tax() {
 
	register_taxonomy( 'ps_movie_genre', 'ps_movie', array(
		'public' => true,
		'hierarchical' => true,
		'query_var' => "ps_movie_genre",
		'rewrite' => array(
			'slug' => "movie-genre",
			'with_front' => false,
			'hierarchical' => true
		),
		'show_tagcloud' => false,
		'labels' => array(
			'name' => "Genres",
			'singular_name' => "Genre",
			'add_new_item' => "Add New Genre",
			'new_item' => "New Genre",
			'edit_item' => "Edit Genre",
			'update_item' => "Update Genre",
			'all_items' => "All Genres",
			'search_items' => "Search Genres",
			'popular_items' => "Popular Genres",
			'separate_items_with_commas' => "Separate genres with commas",
			'add_or_remove_items' => "Add or remove genres",
			'choose_from_most_used' => "Choose from the most used genres"
		),
	) );
 
}
 
?>

The above code is where I would put all of my custom content typing. You can see through this and all the files I maintain a namespace of “ps_.” This is really important in WordPress, and any programming language, because your “movie” or “event” content type might conflict with another plugin. So be sure you’re namespacing all of your functions and content types!

Custom Meta Boxes

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
109
110
<?php
 
function ps_movie_meta_boxes( $post ) {
 
	$post_id = $post->ID;
 
	$ps_movie_director 		= get_post_meta( $post_id, 'ps_movie_director', true );
	$ps_movie_release_year 	= get_post_meta( $post_id, 'ps_movie_release_year', true );
 
	$ps_movie_box_image 	= get_post_meta( $post_id, 'ps_movie_box_image', true );
	$ps_movie_content_image = get_post_meta( $post_id, 'ps_movie_content_image', true );
 
	$ps_movie_summary = get_post_meta( $post_id, 'ps_movie_summary', true );
	$ps_movie_sidebar_content = get_post_meta( $post_id, 'ps_movie_sidebar_content', true );
 
	$defaultImage = get_template_directory_uri() . '/images/ui/apple-touch-icon-72x72-precomposed.png';
 
	if ( $ps_movie_box_image ) { 
 
		$box_thumbnail = wp_get_attachment_image_src( $ps_movie_box_image, 'medium' ); $box_thumbnail = $box_thumbnail[0];
 
	} else {
 
		$box_thumbnail = $defaultImage;
 
	}
 
	if ( $ps_movie_content_image ) { 
 
		$content_thumbnail = wp_get_attachment_image_src( $ps_movie_content_image, 'medium' ); $content_thumbnail = $content_thumbnail[0];
 
	} else {
 
		$content_thumbnail = $defaultImage;
 
	}
 
	echo '
		<h3>Movie Details</h3>
		<table class="form-table">
			<tr>
				<td style="text-align: right;">Director</td>
				<td><input type="text" class="widefat" name="ps_movie_director" value="' . esc_attr( $ps_movie_director ) . '" /></td>
			</tr>
			<tr>
				<td style="text-align: right;">Release Year</td>
				<td><input type="text" class="widefat" name="ps_movie_release_year" value="' . esc_attr( $ps_movie_release_year ) . '" /></td>
			</tr>
			<tr>
				<td style="text-align: right;">DVD Box Image</td>
				<td><input name="ps_movie_box_image" type="hidden" class="custom_upload_image" value="' . esc_attr( $ps_movie_box_image ) . '" /> 
				<span class="custom_default_image" style="display:none">' . $defaultImage . '</span>
				<img src="' . $box_thumbnail . '" class="custom_preview_image" alt="logo" /><br />
				<input class="custom_upload_image_button button" type="button" value="Choose Image" /> 
				<small><a href="#" class="custom_clear_image_button">Remove Image</a></small></td>
			</tr>
			<tr>
				<td style="text-align: right;">Content Image</td>
				<td><input name="ps_movie_content_image" type="hidden" class="custom_upload_image" value="' . esc_attr( $ps_movie_content_image ) . '" /> 
				<span class="custom_default_image" style="display:none">' . $defaultImage . '</span>
				<img src="' . $content_thumbnail . '" class="custom_preview_image" alt="logo" /><br />
				<input class="custom_upload_image_button button" type="button" value="Choose Image" /> 
				<small><a href="#" class="custom_clear_image_button">Remove Image</a></small></td>
			</tr>
			<tr>
				<td style="text-align: right;">Quick Summary</td><td>';
				wp_editor( $ps_movie_summary, 'ps_movie_summary' );
			echo '</td></tr>
			<tr>
				<td style="text-align: right;">Sidebar Content</td><td>';
				wp_editor( $ps_movie_sidebar_content, 'ps_movie_sidebar_content' );
			echo '</td></tr>
		</table>
	';
 
}
 
function ps_movie_add_metas() {
 
	add_meta_box(
		'ps_movie-attributes',
		"Movie Info",
		'ps_movie_meta_boxes',
		'ps_movie',
		'normal',
		'high'
	);
 
}
 
function ps_movie_save_metas( $post_id ) {
 
	// Stop WP from clearing custom fields on autosave
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
        return;
 
    // Prevent quick edit from clearing custom fields
    if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
        return;
 
	if( isset( $_POST[ 'ps_movie_director' ] ) ) { update_post_meta( $post_id, "ps_movie_director", $_POST[ 'ps_movie_director' ] ); }
	if( isset( $_POST[ 'ps_movie_release_year' ] ) ) { update_post_meta( $post_id, "ps_movie_release_year", $_POST[ 'ps_movie_release_year' ] ); }
	if( isset( $_POST[ 'ps_movie_box_image' ] ) ) { update_post_meta( $post_id, "ps_movie_box_image", $_POST[ 'ps_movie_box_image' ] ); }
	if( isset( $_POST[ 'ps_movie_content_image' ] ) ) { update_post_meta( $post_id, "ps_movie_content_image", $_POST[ 'ps_movie_content_image' ] ); }
	if( isset( $_POST[ 'ps_movie_summary' ] ) ) { update_post_meta( $post_id, "ps_movie_summary", $_POST[ 'ps_movie_summary' ] ); }
	if( isset( $_POST[ 'ps_movie_sidebar_content' ] ) ) { update_post_meta( $post_id, "ps_movie_sidebar_content", $_POST[ 'ps_movie_sidebar_content' ] ); }
 
}
 
?>

The above code is all the custom meta boxes created for the “ps_movie” post type. I included three different varieties of fields you can add. The first is basic input. You can use this and change around the “type” attribute to change it into a checkbox or radio button, etc. The next is a image uploader that will fire the media manager. It also includes a default image from your theme, this is just for looks. Finally there is a WYSIWYG, that you can use as another content area in your page.

Template File page-movies.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
<?php get_header(); ?>
 
<div class="content clearfix">
	<section class="content_main" role="main">
 
		<?php 
 
		while ( have_posts() ) : the_post();
 
			echo "<header><h1>" . get_the_title() . "</h1></header>";
 
			the_content();
 
		endwhile;
 
		edit_post_link( 'Edit this entry.', '<p>', '</p>' );
		wp_link_pages(); 
 
		//List Movies
		$args = array(
 
			'post_type' => 'ps_movie',
			'posts_per_page' => '-1'
 
		);
 
		$the_query = new WP_Query( $args );
 
		if ( $the_query->have_posts() ) :
 
			echo '<ul class="list_styled list_movies">';
 
			while( $the_query->have_posts() ) : $the_query->the_post();
 
				echo '<li><a href="' . get_permalink() . '" title="' . get_the_title() . '">' . get_the_title() . '</a></li>';
 
			endwhile;
 
			echo '</ul>';
 
		endif;
		wp_reset_postdata();
 
		?>
 
	</section><!--/end .content_main-->
	<?php get_sidebar(); ?>
</div><!--/end .content-->
<?php get_footer(); ?>

Next we need to display all this data on our page. We can achieve this by creating two new template files. The first will display all the movies we’ve added to our site. This will appear on a normal page that has a title of “Movies.” Notice that the file is called page-movies.php which corresponds to the slug of the page. This gets around using “Template Name” that the client might turn off by mistake.

Template File single-ps_movie.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
<?php get_header(); ?>
 
<div class="content clearfix">
	<article <?php post_class( 'content_main' ); ?> role="main">
 
	<?php 
 
	while ( have_posts() ) : the_post();
 
	$post_id = $post->ID;
 
	$director 			= get_post_meta( $post_id, 'ps_movie_director', true );
	$release_year 		= get_post_meta( $post_id, 'ps_movie_release_year', true );
	$box_image 			= get_post_meta( $post_id, 'ps_movie_box_image', true );
	$content_image 		= get_post_meta( $post_id, 'ps_movie_content_image', true );
	$summary 			= get_post_meta( $post_id, 'ps_movie_summary', true );
	$sidebar_content 	= get_post_meta( $post_id, 'ps_movie_sidebar_content', true );
 
	$box_image = wp_get_attachment_image_src( $box_image, 'full' );
	$content_image = wp_get_attachment_image_src( $content_image, 'full' );
 
	?>
 
		<header>
 
			<h1><?php the_title(); ?> <span>( <?php echo $release_year; ?> )</span></h1>
 
			<p>Directed by <cite><?php echo $director; ?></cite></p>
 
		</header>
 
		<img src="<?php echo $box_image[0]; ?>" width="<?php echo $box_image[1]; ?>" height="<?php echo $box_image[2]; ?>" alt="<?php the_title(); ?> DVD Box" />
 
		<img src="<?php echo $content_image[0]; ?>" width="<?php echo $content_image[1]; ?>" height="<?php echo $content_image[2]; ?>" alt="<?php the_title(); ?> Screen Grab" />
 
		<div class="movie_content">
 
			<?php the_content(); ?>
 
		</div><!--/end .movie_content-->
 
		<div class="movie_summary">
 
			<?php echo $summary; ?>
 
		</div><!--/end .movie_content-->
 
	<?php endwhile; ?>
 
	</article><!--/end .content-main-->
 
	<aside class="sidebar" role="complementary">
 
		<?php echo $sidebar_content; ?>
 
	</aside><!--/end .sidebar-->
 
</div><!--/end .content-->
<?php get_footer(); ?>

Finally our last template file we need is the detail page for our movie that includes all the information we added to the entry. Again this file is called single-ps_movie.php which corresponds to the ps_movie post type we registered in our plugin. Once we have this file in place we have access to all the data in that post type with just the normal template tags we would use in single.php or page.php.

This is the general structure the themes I create take. First create some content types, create some meta boxes, create some template files, and get all the data on the page and printing our properly. After I get that working well, I start to add more HTML and then begin styling with CSS.

And that’s that. Once you get the basics down of how to setup the backend, the next time you do it is mainly duplicating what you’ve already done. This plugin can easily port over to whatever content type you need to add, just change the namespaces, and be sure your adding and new meta boxes, custom post types, or custom taxonomies, to your main plugin file under the init function or what have you.

Good luck! Now, I’m off to a two week vacation/honeymoon in Italy :)

Leave a Comment

Your email address will not be published. Required fields are marked *