Themes Versus Plugins
Where to Place Code When Developing Apps
Use one main plugin to store the core app code, and one theme to manage the frontend code.
Any modular functionality that could be useful on other projects or potentially replaced by another plugin should be coded as a separate plugin.
Never hack the core!1
- Plugins = models
All of your code-defining data structures, business logic, and Ajax services should go into the core plugin. Things like definitions for CPTs and taxonomies, form processing, and class wrappers for the
Post
andUser
classes should also go into your core plugin.- Themes = views
All of your templating code and frontend logic should go in your theme. The frame of your website, header, footer, menu, and sidebars should be coded in your theme. Simple logic like
if(is_user_logged_in()) { //show menu } else { //show login }
should go into your theme.
When Developing Plugins
Where to Place Code When Developing Themes
The Template Hierarchy
404.php
author.php
archive.php
attachment.php
category.php
comments.php
date.php
footer.php
front-page.php
functions.php
header.php
home.php
image.php
index.php
page.php
search.php
sidebar.php
single.php
single-(post-type).php
style.css
tag.php
taxonomy.php
Page Templates
Sample Page Template
Example 4-1. Sample page template
<?
php
/*
Template Name: Page - Contact Form
*/
//get values possibly submitted by form
=
sanitize_email
(
$_POST
[
'email'
]
);
$cname
=
sanitize_text_field
(
$_POST
[
'cname'
]
);
$phone
=
sanitize_text_field
(
$_POST
[
'phone'
]
);
$message
=
sanitize_text_field
(
$_POST
[
'message'
]
);
$sendemail
=
!
empty
(
$_POST
[
'sendemail'
]
);
// form submitted?
if
(
!
empty
(
$sendemail
)
&&
!
empty
(
$cname
)
&&
!
empty
(
)
&&
empty
(
$lname
)
)
{
$mailto
=
get_bloginfo
(
'admin_email'
);
$mailsubj
=
"Contact Form Submission from "
.
get_bloginfo
(
'name'
);
$mailhead
=
"From: "
.
$cname
.
" <"
.
.
">
\n
"
;
$mailbody
=
"Name: "
.
$cname
.
"
\n\n
"
;
$mailbody
.=
"Email:
$email\n\n
"
;
$mailbody
.=
"Phone:
$phone\n\n
"
;
$mailbody
.=
"Message:
\n
"
.
$message
;
// send email to us
wp_mail
(
$mailto
,
$mailsubj
,
$mailbody
,
$mailhead
);
// set message for this page and clear vars
$msg
=
"Your message has been sent."
;
=
""
;
$cname
=
""
;
$phone
=
""
;
$message
=
""
;
}
elseif
(
!
empty
(
$sendemail
)
&&
!
is_email
(
)
)
$msg
=
"Please enter a valid email address."
;
elseif
(
!
empty
(
$lname
)
)
$msg
=
"Are you a spammer?"
;
elseif
(
!
empty
(
$sendemail
)
&&
empty
(
$cname
)
)
$msg
=
"Please enter your name."
;
elseif
(
!
empty
(
$sendemail
)
&&
!
empty
(
$cname
)
&&
empty
(
)
)
$msg
=
"Please enter your email address."
;
// get the header
get_header
();
?>
<div id="wrapper">
<div id="content">
<?php
if
(
have_posts
()
)
:
while
(
have_posts
()
)
:
the_post
();
?>
<h1>
<?php
the_title
();
?>
</h1>
<?php
if
(
!
empty
(
$msg
)
)
{
?>
<div class="message">
<?php
echo
$msg
?>
</div>
<?php
}
?>
<form class="general" action="
<?php
the_permalink
();
?>
" method="post">
<div class="form-row">
<label for="cname">Name</label>
<input type="text" name="cname" value="
<?php
echo
esc_attr
(
$cname
);
?>
"/>
<small class="red">* Required</small>
</div>
<div class="hidden">
<label for="lname">Last Name</label>
<input type="text" name="lname" value="
<?php
echo
esc_attr
(
$lname
);
?>
"/>
<small class="red">LEAVE THIS FIELD BLANK</small>
</div>
<div class="form-row">
<label for="email">Email</label>
<input type="text" name="email" value="
<?php
echo
esc_attr
(
);
?>
"/>
<small class="red">* Required</small>
</div>
<div class="form-row">
<label for="phone">Phone</label>
<input type="text" name="phone" value="
<?php
echo
esc_attr
(
$phone
);
?>
"/>
</div>
<div class="form-row">
<label for="message">Question or Comment</label>
<textarea class="textarea" id="message" name="message" rows="4" cols="55">
<?php
echo
esc_textarea
(
$message
)
?>
</textarea>
</div>
<div class="form-row">
<label for="sendemail"> </label>
<input type="submit" id="sendemail" name="sendemail" value="Submit"/>
</div>
</form>
<?php
endwhile
;
endif
;
?>
</div>
</div>
<?php
// get the footer
get_footer
();
?>
Using Hooks to Copy Templates
Example 4-2. Hooking template
<?
php
/*
Template Name: Hooking Template Example
*/
//use the default page template
require_once
(
dirname
(
__FILE__
)
.
"/page.php"
);
//now add content using a function called during the the_content hook
function
template_content
(
$content
)
{
//get the current post in this loop
global
$post
;
//get the post object for the current page
$queried_object
=
get_queried_object
();
//we don't want to filter posts that aren't the main post
if
(
empty
(
$queried_object
)
||
$queried_object
->
ID
!=
$post
->
ID
)
return
$content
;
//capture output
ob_start
();
?>
<p>This content will show up under the page content.</p>
<?php
$temp_content
=
ob_get_contents
();
ob_end_clean
();
//append and return template content
return
$content
.
$temp_content
;
}
add_action
(
"the_content"
,
"template_content"
);
When Should You Use a Theme Template?
Theme-Related WordPress Functions
<?
php
/* Start the Loop */
?>
<?php
while
(
have_posts
()
)
:
the_post
();
?>
<?php
get_template_part
(
'content'
,
get_post_format
()
);
?>
<?php
endwhile
;
?>
get_template_part
(
'templates/content'
,
'page'
);
<?
php
function
load_template
(
$_template_file
,
$require_once
=
true
)
{
global
$posts
,
$post
,
$wp_did_header
,
$wp_query
,
$wp_rewrite
;
global
$wpdb
,
$wp_version
,
$wp
,
$id
,
$comment
,
$user_ID
;
if
(
is_array
(
$wp_query
->
query_vars
)
)
extract
(
$wp_query
->
query_vars
,
EXTR_SKIP
);
if
(
isset
(
$s
)
)
$s
=
esc_attr
(
$s
);
if
(
$require_once
)
require_once
(
$_template_file
);
else
require
(
$_template_file
);
}
?>
Using locate_template in Your Plugins
//schoolpress/templates/invite-students.php
?>
<p>Enter</p>
<form action="" method="post">
<label for="email">Email:</label>
<input type="text" id="email" name="email" value="" />
<input type="submit" name="invite" value="Invite Student" />
</form>
//schoolpress/shortcodes/invite-students.php
function
sp_invite_students_shortcode
(
$atts
,
$content
=
null
,
$code
=
""
)
{
//start output buffering
ob_start
();
//look for an invite-students template part in the active theme
$template
=
locate_template
(
'schoolpress/templates/invite-students.php'
);
//if not found, use the default
if
(
empty
(
$template
))
$template
=
dirname
(
__FILE__
)
.
'/../templates/invite-students.php'
;
//load the template
load_template
(
$template
);
//get content from buffer and output it
$temp_content
=
ob_get_contents
();
ob_end_clean
();
return
$temp_content
;
}
add_shortcode
(
'invite-students'
,
'sp_invite_students_shortcode'
);
Style.css
/*
Theme Name: Twenty Nineteen
Theme URI: https://wordpress.org/themes/twentynineteen/
Author: the WordPress team
Author URI: https://wordpress.org/
Description: Our 2019 default theme is designed to show off the power of the
block editor. It features custom styles for all the default blocks, and is
built so that what you see in the editor looks like what you'll see on your
website. Twenty Nineteen is designed to be adaptable to a wide range of
websites, whether you’re running a photo blog, launching a new business, or
supporting a non-profit. Featuring ample whitespace and modern sans-serif
headlines paired with classic serif body text, it's built to be beautiful on
all screen sizes.
Requires at least: WordPress 4.9.6
Version: 1.4
License: GNU General Public License v2 or later
License URI: LICENSE
Text Domain: twentynineteen
Tags: one-column, flexible-header, accessibility-ready,
custom-colors, custom-menu, custom-logo, editor-style,
featured-images, footer-widgets, rtl-language-support,
sticky-post, threaded-comments, translation-ready
This theme, like WordPress, is licensed under the GPL.
Use it to make something cool, have fun, and share what
you've learned with others.
Twenty Nineteen is based on Underscores https://underscores.me/,
(C) 2012-2018 Automattic, Inc.
Underscores is distributed under the terms of the GNU GPL v2 or later.
Normalizing styles have been helped along thanks to the fine work of
Nicolas Gallagher and Jonathan Neal https://necolas.github.io/normalize.css/
*/
Versioning Your Theme’s CSS Files
<link
rel=
'stylesheet'
id=
'twentynineteen-style-css'
href=
'.../wp-content/themes/twentynineteen/style.css?ver=1.4'
type=
'text/css'
media=
'all'
/>
/*
Theme Name: SchoolPress
Version: 1.0
That's it! All CSS can be found in the "css" folder of the theme.
*/
<?
php
define
(
'SCHOOLPRESS_VERSION'
,
'1.0'
);
function
sp_enqueue_theme_styles
()
{
if
(
!
is_admin
()
)
{
wp_enqueue_style
(
'schoolpress-theme'
,
get_stylesheet_directory_uri
()
.
'/css/main.css'
,
NULL
,
SCHOOLPRESS_VERSION
);
}
}
add_action
(
'init'
,
'sp_enqueue_theme_styles'
);
?>
define
(
'SCHOOLPRESS_VERSION'
,
'1.0'
);
function
sp_wp_default_styles
(
$styles
)
{
//use release version for stylesheets
$styles
->
default_version
=
SCHOOLPRESS_VERSION
;
}
add_action
(
"wp_default_styles"
,
"sp_wp_default_styles"
);
functions.php
- /includes/functions.php
Where you really place helper functions.
- /includes/settings.php
For code related to theme settings and options.
- /includes/sidebars.php
To define sidebars/widget areas.
Themes and CPTs
Popular Theme Frameworks
WordPress Theme Frameworks
_s (underscores)
Memberlite
Genesis
Non-WordPress Theme Frameworks
Creating a Child Theme for Memberlite
Create a new folder in your wp-content/themes folder. Then, give it the name memberlite-child.
Create a style.css file in the memberlite-child folder.
Paste the following into your style.css file:
/*
THEME NAME: Memberlite Child
THEME URI: http://bwawwp.com/wp-content/themes/memberlite-child/
DESCRIPTION: Memberlite Child Theme
VERSION: 0.1
AUTHOR: Jason Coleman
AUTHOR Uri: http://bwawwp.com
TAGS: memberlite, child, tag
TEMPLATE: memberlite
*/
@import url("../memberlite/style.css");
The key field in the comment is the
TEMPLATE
field, which needs to match the folder of the parent theme, in this casememberlite
. The only required file for a child theme is style.css. So at this point, you’ve created a child theme.You can either copy all the CSS from the parent theme’s style.css into the child theme’s style.css and edit what you want to, or use
@import_url
like we’ve done here to import the rules from the parent theme’s stylesheet and add more rules below to override the parent theme’s styles.To enqueue the bootstrap files, you will also need a functions.php file.
Create an empty functions.php file in the memberlite-child folder for now.
Including Bootstrap in Your App’s Theme
memberlite-child
bootstrap
css
js
functions.php
style.css
<?
php
function
memberlite_child_init
()
{
wp_enqueue_style
(
'bootstrap'
,
get_stylesheet_directory_uri
()
.
'/bootstrap/css/bootstrap.min.css'
,
'style'
,
'3.0'
);
wp_enqueue_script
(
'bootstrap'
,
get_stylesheet_directory_uri
()
.
'/bootstrap/js/bootstrap.min.js'
,
'jquery'
,
'3.0'
);
}
add_action
(
'init'
,
'memberlite_child_init'
);
?>
Menus
Navigation Menus
register_nav_menu
(
'main'
,
'Main Menu'
);
register_nav_menus
(
array
(
'main'
=>
'Main'
,
'logged-in'
=>
'Logged-In'
));
wp_nav_menu
(
array
(
'theme_location'
=>
'main'
));
Dynamic Menus
if
(
is_user_logged_in
()
)
{
wp_nav_menu
(
array
(
'theme_location'
=>
'logged-in-menu'
)
);
}
else
{
wp_nav_menu
(
array
(
'theme_location'
=>
'logged-out-menu'
)
);
}
function
remove_login_link
(
$classes
,
$item
)
{
if
(
is_page
(
'login'
)
&&
$item
->
title
==
'Login'
)
$classes
[]
=
'hide'
;
//hide this item
return
$classes
;
}
add_filter
(
'nav_menu_css_class'
,
'sp_nav_menu_css_class'
,
10
,
2
);
Responsive Design
Device and Display Detection in CSS
<
link
rel
=
"stylesheet"
media
=
"print"
href
=
"example.css"
/>
<
style
>
@
media
{
.
hide
-
from
-
{
display
:
none
;}
.
show
-
when
-
printing
{
display
:
auto
;}
}
</
style
>
<?
php
wp_enqueue_style
(
'example'
,
'example.css'
,
NULL
,
'1.0'
,
'print'
);
?>
@media
(
min-width
:
768px
)
and
(
max-width
:
979px
)
{
.hidden-desktop
{
display
:
inherit
!important
;
}
.visible-desktop
{
display
:
none
!important
;
}
.visible-tablet
{
display
:
inherit
!important
;
}
.hidden-tablet
{
display
:
none
!important
;
}
}
@media
(
-o-
min
-
device
-
pixel
-
ratio
:
5
/
4
)
,
/* Opera */
(
-webkit-
min
-
device
-
pixel
-
ratio
:
1
.
25
)
,
/* Webkit */
(
min
-
resolution
:
120dpi
)
{
/* Others */
/* add your high res CSS here */
}
Device and Feature Detection in JavaScript
Detecting the screen and window size with JavaScript and jQuery
//bind an event to run when the window is resized
jQuery
(
window
).
resize
(
function
()
{
width
=
jQuery
(
window
).
width
();
height
=
jQuery
(
window
).
height
();
//update your layout, etc
});
Feature detection in JavaScript
Check whether a certain property exists on a global object (such as
window
ornavigator
).Create an element, then check whether a certain property exists on that element.
Create an element, check if a certain method exists on that element, and then call the method and check the value it returns.
Create an element, set a property to a certain value, and then check whether the property has retained its value.
<?
php
function
sp_wp_footer_modernizr
()
{
wp_enqueue_script
(
'modernizr'
,
get_stylesheet_directory_uri
()
.
'/js/modernizr.min.js'
);
?>
<script>
//change search inputs to text if unsupported
if(!Modernizr.inputtypes.search)
jQuery('input[type=search]').attr('type', 'text');
</script>
<?php
}
add_action
(
'wp_footer'
,
'sp_wp_footer_modernizr'
);
?>
jQuery
(
document
).
ready
(
function
()
{
//only load AJAX code if AJAX is available
if
(
jQuery
.
support
.
ajax
)
{
//AJAX code goes here
}
});
Device Detection in PHP
<?
php
echo
$_SERVER
[
'HTTP_USER_AGENT'
];
/*
Outputs something like:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
*/
?>
And then Google built Chrome, and Chrome used Webkit, and it was like Safari, and wanted pages built for Safari, and so pretended to be Safari. And thus Chrome used WebKit, and pretended to be Safari, and WebKit pretended to be KHTML, and KHTML pretended to be Gecko, and all browsers pretended to be Mozilla, and Chrome called itself Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13, and the user agent string was a complete mess, and near useless, and everyone pretended to be everyone else, and confusion abounded.
Aaron Anderson
Browser detection in WordPress core
$is_lynx
$is_gecko
$is_winIE
$is_macIE
$is_opera
$is_NS4
$is_safari
$is_chrome
$is_iphone
$is_IE
$is_apache
$is_IIS
$is_iis7
<?
php
function
sp_init_browser_hacks
()
{
global
$is_IE
;
if
(
$is_IE
)
{
//check version and load CSS
$user_agent
=
strtolower
(
$_SERVER
[
'HTTP_USER_AGENT'
]
);
if
(
strpos
(
'msie 6.'
,
$user_agent
)
!==
false
&&
strpos
(
'opera'
,
$user_agent
)
===
false
)
{
wp_enqueue_style
(
'ie6-hacks'
,
get_stylesheet_directory_uri
()
.
'/css/ie6.css'
);
}
}
if
(
wp_is_mobile
()
)
{
//load our mobile CSS and JS
wp_enqueue_style
(
'sp-mobile'
,
get_stylesheet_directory_uri
()
.
'/css/mobile.css'
);
wp_enqueue_script
(
'sp-mobile'
,
get_stylesheet_directory_uri
()
.
'/js/mobile.js'
);
}
}
add_action
(
'init'
,
'sp_init_browser_hacks'
);
?>
Browser detection with PHP’s get_browser()
<?
php
$browser
=
get_browser
();
print_r
(
$browser
);
/*
Would produce output like:
stdClass Object (
[browser_name_regex] => §^mozilla/5\.0 \(.*intel mac os x.*\)
applewebkit/.* \(khtml, like gecko\).*chrome/28\..*safari/.*$§
[browser_name_pattern] => Mozilla/5.0 (*Intel Mac OS X*)
AppleWebKit/* (KHTML, like Gecko)*Chrome/28.*Safari/*
[parent] => Chrome 28.0
[platform] => MacOSX
[win32] =>
[comment] => Chrome 28.0
[browser] => Chrome
[version] => 28.0
[majorver] => 28
[minorver] => 0
[frames] => 1
[iframes] => 1
[tables] => 1
[cookies] => 1
[javascript] => 1
[javaapplets] => 1
[cssversion] => 3
[platform_version] => unknown
[alpha] =>
[beta] =>
[win16] =>
[win64] =>
[backgroundsounds] =>
[vbscript] =>
[activexcontrols] =>
[ismobiledevice] =>
[issyndicationreader] =>
[crawler] =>
[aolversion] => 0
)
*/
[browscap] browscap = /etc/lite_php_browscap.ini
Final Note on Browser Detection
1 If you find that you must hack the core files to get something to work, first reconsider whether you really need to do this. If you do need to change a core WordPress file, add hooks instead, and submit those hooks as a patch to the next version of WordPress.
2 We added a line break and removed some curly braces so it would fit better in print.
3 You could check $_SERVER['PHP_SELF']
to see whether you’re on the wp-login.php page. In this example, we assume our login is on a WordPress page with the slug login
.
4 Retina is Apple’s brand name for their high-resolution displays. However, you may find the term “retina” used in code comments and documentation to refer to any high-resolution display.