How to Replace the All in One SEO Pack for WordPress
Nearly every client who’s having SEO issues comes to me with a statement similar to this one:
My SEO is broken for [this or that reason]. I don’t understand! I’ve installed the All in One SEO Plugin and it’s still not helping!
When queried further, it turns out that their issues arrived shortly after installing the plugin. I’m not saying that it doesn’t work for some folks, or that it doesn’t work at all. But it’s overkill, it fiddles with things it doesn’t need to, and it relies on the idea that the person utilizing the plugin understands SEO.
Canonical URLs, meta tags, title tags…am I stuffing my posts with keywords, are my meta descriptions accurate? These are the questions most folks don’t know about or care to research, and from an even more technical viewpoint, why load a bulky plugin which adds a ton of markup to your website’s HTML when you only really need three lines in your header to ensure your WordPress site is up to snuff in this area of search engine optimization.
So here’s a quick walk through of how you can ditch that plugin altogether, and then reinstate it’s most useful features.
Title Tag Control
One of the more important aspects of your site’s markup which, in most themes anyway, you don’t have any control over, is the <title>
tag. If you’re not familiar with it, this tag:
- Controls the text that appears at the top of most browsers (or partially appears in Chrome tabs).
- Is typically what Google and the search engines use to display the title of a webpage in their search results.
- Can be a somewhat heavy factor in your search engine placement, but can be a huge factor in whether or not real humans want to click the link back to your site.
Let’s see how we can add this field to the Post and Page Editor in WordPress. I like to use the code below, which has been floating around forever but now comes with various customizations I’ve made as necessary. Note that this code completely replaces the Custo Fields box in the WordPress Editor screens, so if you use custom fields a lot, you’ll either need to enter them directly into the code here. Just copy lines 16-23 as many times as needed. I typically assign specific custom fields to my various post types and don’t want to accidentally create new ones via the somewhat lacking default WP interface.
But moving on, the code below should be placed in your functions.php file.
if ( !class_exists('myCustomFields') ) {
class myCustomFields {
/**
* @var string $prefix The prefix for storing custom fields in the postmeta table
*/
var $prefix = 'cn_';
/**
* @var array $postTypes An array of public custom post types, plus the standard "post" and "page" - add the custom types you want to include here
*/
var $postTypes = array( "page", "post" );
/**
* @var array $customFields Defines the custom fields available
*/
var $customFields = array(
array(
"name" => "title",
"title" => "Title Tag",
"description" => "For SEO, this should be a poignant title for what this page is actually about.",
"type" => "text",
"scope" => array( "post", "page" ),
"capability" => "edit_pages"
),
array(
"name" => "description",
"title" => "Meta Description",
"description" => "For SEO, this won't help get you better search results, but will sometimes be shown to users on search results pages.",
"type" => "textarea",
"scope" => array( "post", "page" ),
"capability" => "edit_posts"
)
);
/**
* PHP 4 Compatible Constructor
*/
function myCustomFields() { $this->__construct(); }
/**
* PHP 5 Constructor
*/
function __construct() {
add_action( 'admin_menu', array( &$this, 'createCustomFields' ) );
add_action( 'save_post', array( &$this, 'saveCustomFields' ), 1, 2 );
// Comment this line out if you want to keep default custom fields meta box
add_action( 'do_meta_boxes', array( &$this, 'removeDefaultCustomFields' ), 10, 3 );
}
/**
* Remove the default Custom Fields meta box
*/
function removeDefaultCustomFields( $type, $context, $post ) {
foreach ( array( 'normal', 'advanced', 'side' ) as $context ) {
foreach ( $this->postTypes as $postType ) {
remove_meta_box( 'postcustom', $postType, $context );
}
}
}
/**
* Create the new Custom Fields meta box
*/
function createCustomFields() {
if ( function_exists( 'add_meta_box' ) ) {
foreach ( $this->postTypes as $postType ) {
add_meta_box( 'my-custom-fields', 'Custom Fields', array( &$this, 'displayCustomFields' ), $postType, 'normal', 'high' );
}
}
}
/**
* Display the new Custom Fields meta box
*/
function displayCustomFields() {
global $post;
?>
<div class="form-wrap">
<?php
wp_nonce_field( 'my-custom-fields', 'my-custom-fields_wpnonce', false, true );
foreach ( $this->customFields as $customField ) {
// Check scope
$scope = $customField[ 'scope' ];
$output = false;
foreach ( $scope as $scopeItem ) {
switch ( $scopeItem ) {
default: {
if ( $post->post_type == $scopeItem )
$output = true;
break;
}
}
if ( $output ) break;
}
// Check capability
if ( !current_user_can( $customField['capability'], $post->ID ) )
$output = false;
// Output if allowed
if ( $output ) { ?>
div class="form-field form-required" id="ll-<?php echo $customField[ 'name' ]; ?>">
<?php
switch ( $customField[ 'type' ] ) {
case "checkbox": {
// Checkbox
echo '<label for="' . $this->prefix . $customField[ 'name' ] .'" style="display:inline;"><b>' . $customField[ 'title' ] . '</b></label> ';
echo '<input type="checkbox" name="' . $this->prefix . $customField['name'] . '" id="' . $this->prefix . $customField['name'] . '" value="yes"';
if ( get_post_meta( $post->ID, $this->prefix . $customField['name'], true ) == "yes" )
echo ' checked="checked"';
echo '" style="width: auto;" />';
break;
}
case "textarea":
case "wysiwyg": {
// Text area
echo '<label for="' . $this->prefix . $customField[ 'name' ] .'"><b>' . $customField[ 'title' ] . '</b></label>';
echo '<textarea name="' . $this->prefix . $customField[ 'name' ] . '" id="' . $this->prefix . $customField[ 'name' ] . '" columns="30" rows="3">' . htmlspecialchars( get_post_meta( $post->ID, $this->prefix . $customField[ 'name' ], true ) ) . '</textarea>';
// WYSIWYG
if ( $customField[ 'type' ] == "wysiwyg" ) { ?>
<script type="text/javascript">
jQuery( document ).ready( function() {
jQuery( "<?php echo $this->prefix . $customField[ 'name' ]; ?>" ).addClass( "mceEditor" );
if ( typeof( tinyMCE ) == "object" && typeof( tinyMCE.execCommand ) == "function" ) {
tinyMCE.execCommand( "mceAddControl", false, "<?php echo $this->prefix . $customField[ 'name' ]; ?>" );
}
});
</script>
<?php }
break;
}
default: {
// Plain text field
echo '<label for="' . $this->prefix . $customField[ 'name' ] .'"><b>' . $customField[ 'title' ] . '</b></label>';
echo '<input type="text" name="' . $this->prefix . $customField[ 'name' ] . '" id="' . $this->prefix . $customField[ 'name' ] . '" value="' . htmlspecialchars( get_post_meta( $post->ID, $this->prefix . $customField[ 'name' ], true ) ) . '" />';
break;
}
}
?>
<?php if ( $customField[ 'description' ] ) echo '<p>' . $customField[ 'description' ] . '</p>'; ?>
</div>
<?php
}
} ?>
</div>
<?php
}
/**
* Save the new Custom Fields values
*/
function saveCustomFields( $post_id, $post ) {
if ( !isset( $_POST[ 'my-custom-fields_wpnonce' ] ) || !wp_verify_nonce( $_POST[ 'my-custom-fields_wpnonce' ], 'my-custom-fields' ) )
return;
if ( !current_user_can( 'edit_post', $post_id ) )
return;
if ( ! in_array( $post->post_type, $this->postTypes ) )
return;
foreach ( $this->customFields as $customField ) {
if ( current_user_can( $customField['capability'], $post_id ) ) {
if ( isset( $_POST[ $this->prefix . $customField['name'] ] ) && trim( $_POST[ $this->prefix . $customField['name'] ] ) ) {
$value = $_POST[ $this->prefix . $customField['name'] ];
// Auto-paragraphs for any WYSIWYG
if ( $customField['type'] == "wysiwyg" ) $value = wpautop( $value );
update_post_meta( $post_id, $this->prefix . $customField[ 'name' ], $value );
} else {
delete_post_meta( $post_id, $this->prefix . $customField[ 'name' ] );
}
}
}
}
} // End Class
} // End if class exists statement
// Instantiate the class
if ( class_exists('myCustomFields') ) {
$myCustomFields_var = new myCustomFields();
}
Once you’ve done this, return to the Post Editor and you should see something like the screenshot below.
You can now enter your custom Title tag into the first field and customize that piece of every page on a page-by-page basis. Not sure what to enter into this field? I recommend you read through my Free SEO Advice section and How to Write a Great Title Tag articles.
Meta Description
You’ve probably noticed that the Title Tag wasn’t the only new field added to your site…the Meta Description is now editable on a post by post basis as well. The All in One SEO plugin also adds a Meta Keywords field, which I don’t recommend, as no current search engine uses it to rank results (except Bing, but in a very minor way) and it’s been shown to actually hurt your results with some search engines.
For more info on writing Meta Description Tags, check out this post on The Meta Description Tag, Truths, Myths and Bullet Points.
Adding This All to Your Site
So we’ve got these fields, but they’re not actually being added to our WordPress theme yet. Let’s make that happen.
- Open your theme’s header.php file
- Find the opening
<title>
– depending on your theme this may have any amount of information contained in it. Let’s completely replace it with:
<?php $click_title = get_post_meta($post->ID, 'cn_title', true);
$click_description = get_post_meta($post->ID, 'cn_description', true);
if ($click_description == '') { $click_description = get_the_excerpt(); } ?><title><?php if (is_singular()) { if ($click_title != '') { echo $click_title; } else { bloginfo('name'); wp_title( '|', true, 'left' ); } } else { bloginfo('name'); echo ' | '; bloginfo('description'); } ?></title>
<?php if ($click_description != '') { ?><meta name="description" content="<?php echo $click_description; ?>" /><?php } ?>
With that code, we first get the values of the two custom fields: cn_title
, which is our Title Tag and cn_description
, which is our Meta Description, then we say “if the title tag is empty, write out Your Blog Name | Title of the Page, and if the description is empty, grab any custom excerpts you may have created, and if that’s empty too, then just show the first several words of the post.”
Canonical URLs
WordPress has gotten pretty good with making sure your www.website.com and website.com are in line. It’s pretty smart about sending users to a page that exists instead of a 404 page when it can, but there are still instances when multiple pages on your WP website will have the same content. This is fairly simple to rectify. Just add the following code to your header.php file, above the closing </head> tag.
Update: the following is no longer recommended, see my latest post on WordPress canonical URLs.
<link rel="canonical" href="< the_permalink(); ?>"/>
Voila, easy as pie.
Of course, there are plenty of other things you can do to help with SEO, but as for All in One SEO Pack, these three things cover the bulk of what that plugin does, without the bulk of what that plugin requires.
Up Next: WordPress is_tree Functions for Custom Post Types & Ancestors