Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Core Competency

Alex King · alexking.org

  • Original contributor to WordPress core
  • Hosted first theme competitions for WordPress (2004, 2005)
  • Released dozens of plugins/themes
  • Architect of Carrington Core,
    Carrington Build & RAMP
  • Founder of Crowd Favorite

the_content()

is “Special”

wp_posts table in database

global $post in templates

Database columns → Object properties

Data

$post->ID

Return

get_the_ID()

Echo

the_ID()

Data

$post->post_title

Return

get_the_title()

apply_filters('the_title' …

Echo

the_title()

Data

?

Return

get_the_content()

apply_filters('the_content' …

Echo

the_content()

Developers love patterns.

Developers assume consistency.

the_content()

NEVER references

$post->post_content

The data does come from

$post->post_content

just not like you might expect.


Let's follow the rabbit hole…

1 of 1

the_content()

wp-includes/post-template.php, line 164

function the_content($more_link_text = null, $stripteaser = false) {
	$content = get_the_content($more_link_text, $stripteaser);
	$content = apply_filters('the_content', $content);
	$content = str_replace(']]>', ']]>', $content);
	echo $content;
}

get_the_content()

wp-includes/post-template.php, line 180

function get_the_content($more_link_text = null, $stripteaser = false) {
	global $post, $more, $page, $pages, $multipage, $preview;

	if ( null === $more_link_text )
		$more_link_text = __( '(more...)' );

	$output = '';
	$hasTeaser = false;

	// If post password required and it doesn't match the cookie.
	if ( post_password_required($post) )
		return get_the_password_form();

	if ( $page > count($pages) ) // if the requested page doesn't exist
		$page = count($pages); // give them the highest numbered page that DOES exist

	$content = $pages[$page-1];
	if ( preg_match('//', $content, $matches) ) {
		$content = explode($matches[0], $content, 2);
		if ( !empty($matches[1]) && !empty($more_link_text) )
			$more_link_text = strip_tags(wp_kses_no_null(trim($matches[1])));

		$hasTeaser = true;
	} else {
		$content = array($content);
	}
	if ( (false !== strpos($post->post_content, '') && ((!$multipage) || ($page==1))) )
		$stripteaser = true;
	$teaser = $content[0];
	if ( $more && $stripteaser && $hasTeaser )
		$teaser = '';
	$output .= $teaser;
	if ( count($content) > 1 ) {
		if ( $more ) {
			$output .= '' . $content[1];
		} else {
			if ( ! empty($more_link_text) )
				$output .= apply_filters( 'the_content_more_link', ' ID}\" class=\"more-link\">$more_link_text", $more_link_text );
			$output = force_balance_tags($output);
		}
	}
	if ( $preview ) // preview fix for javascript bug with foreign languages
		$output =	preg_replace_callback('/\%u([0-9A-F]{4})/', '_convert_urlencoded_to_entities', $output);

	return $output;
}

So get_the_content() returns $output

Which was created from $content

Which was pulled from global $pages

1 of 7

Where is global $pages set?

You'll recognize this...

while ( have_posts() ) : the_post();
// ...
endwhile;

The loop...

while ( have_posts() ) : the_post();
// ...
endwhile;

the_post()

wp-includes/query.php, line 784

function the_post() {
	global $wp_query;

	$wp_query->the_post();
}

$wp_query->the_post()

wp-includes/query.php, line 2815


class WP_Query {

// ...

	function the_post() {
		global $post;
		$this->in_the_loop = true;

		if ( $this->current_post == -1 ) // loop has just started
			do_action_ref_array('loop_start', array(&$this));

		$post = $this->next_post();
		setup_postdata($post);
	}

setup_postdata()

wp-includes/query.php, line 3589

function setup_postdata($post) {
	global $id, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, $numpages;

	$id = (int) $post->ID;

	$authordata = get_userdata($post->post_author);

	$currentday = mysql2date('d.m.y', $post->post_date, false);
	$currentmonth = mysql2date('m', $post->post_date, false);
	$numpages = 1;
	$page = get_query_var('page');
	if ( !$page )
		$page = 1;
	if ( is_single() || is_page() || is_feed() )
		$more = 1;
	$content = $post->post_content;
	if ( strpos( $content, '' ) ) {
		if ( $page > 1 )
			$more = 1;
		$multipage = 1;
		$content = str_replace("\n\n", '', $content);
		$content = str_replace("\n", '', $content);
		$content = str_replace("\n", '', $content);
		$pages = explode('', $content);
		$numpages = count($pages);
	} else {
		$pages = array( $post->post_content );
		$multipage = 0;
	}

	do_action_ref_array('the_post', array(&$post));

	return true;
}

the_content()

get_the_content()

global $pages

the_post()

$wp_query->the_post()

setup_postdata()

$post->post_content

Why does
this matter?

  1. Documentation tells you what the code is supposed to do.
    Reading the code tells what it does.
  2. Understanding the code let's you choose the right tool for the job.

Examples

1 of 7

the_content filter is commonly used to prepend and append features to posts/pages.

<!--nextpage-->

page 1 and page 2 example

WP_Query, get_posts()

wp-includes/query.php, line 2756

if ( !$q['suppress_filters'] )
	$this->posts = apply_filters_ref_array('the_posts', array( $this->posts, &$this ) );

$this->post_count = count($this->posts);

// Always sanitize
foreach ( $this->posts as $i => $post ) {
	$this->posts[$i] = sanitize_post( $post, 'raw' );
}

if ( $q['cache_results'] )
	update_post_caches($this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache']);

if ( $this->post_count > 0 ) {
	$this->post = $this->posts[0];
}

return $this->posts;
function my_content_first($posts, $query) {
	foreach ($posts as &$post) {
		$post->post_content = '<div class="banner">FIRST!</div>'
			.$post->post_content;
	}
	return $posts;
}
add_filter('the_posts', 'my_content_first', 10, 2);

the_excerpt()

  • Can be set manually.
  • Auto-generated from
    $post->post_content
  • Truncates by words (default 55), not by characters.

wp_trim_excerpt()

wp-includes/formatting.php, line 2111

function wp_trim_excerpt($text = '') {
	$raw_excerpt = $text;
	if ( '' == $text ) {
		$text = get_the_content('');

		$text = strip_shortcodes( $text );

		$text = apply_filters('the_content', $text);
		$text = str_replace(']]>', ']]>', $text);
		$excerpt_length = apply_filters('excerpt_length', 55);
		$excerpt_more = apply_filters('excerpt_more', ' ' . '[...]');
		$text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
	}
	return apply_filters('wp_trim_excerpt', $text, $raw_excerpt);
}

1 of 1

What do you think happens if you put something like this at the beginning of a post?

a:2:{s:7:"twitter";a:1:{i:101143;a:1:{i:195423878724599809;a:3:{s:7:"message";s:2584:"eyJjb
29yZGluYXRlcyI6bnVsbCwicmV0d2VldF9jb3VudCI6MCwiaWRfc3RyIjoiMTk1NDIzODc4NzI0NTk5ODA5IiwiaW5f
cmVwbHlfdG9fdXNlcl9pZCI6bnVsbCwiaW5fcmVwbHlfdG9fc3RhdHVzX2lkX3N0ciI6bnVsbCwiZmF2b3JpdGVkIjp
mYWxzZSwiaW5fcmVwbHlfdG9fdXNlcl9pZF9zdHIiOm51bGwsInBsYWNlIjpudWxsLCJ1c2VyIjp7ImlzX3RyYW5zbG
F0b3IiOmZhbHNlLCJwcm9maWxlX3RleHRfY29sb3IiOiIwMDAwMDAiLCJzdGF0dXNlc19jb3VudCI6NDA1OCwiaWQiO
iIxMDExNDMiLCJwcm9maWxlX2ltYWdlX3VybF9odHRwcyI6Imh0dHBzOlwvXC9zaTAudHdpbWcuY29tXC9wcm9maWxl
X2ltYWdlc1wvMTcwODQyODJcL2FsZXhfa2luZ19ub3JtYWwuanBnIiwicHJvZmlsZV9iYWNrZ3JvdW5kX2ltYWdlX3V
ybF9odHRwcyI6Imh0dHBzOlwvXC9zaTAudHdpbWcuY29tXC9pbWFnZXNcL3RoZW1lc1wvdGhlbWUxXC9iZy5wbmciLC
JmcmllbmRzX2NvdW50IjoxMzEsIm5vdGlmaWNhdGlvbnMiOmZhbHNlLCJkZWZhdWx0X3Byb2ZpbGVfaW1hZ2UiOmZhb
HNlLCJjb250cmlidXRvcnNfZW5hYmxlZCI6ZmFsc2UsInByb2ZpbGVfc2lkZWJhcl9ib3JkZXJfY29sb3IiOiI1NTY2
OTkiLCJpZF9zdHIiOiIxMDExNDMiLCJnZW9fZW5hYmxlZCI6ZmFsc2UsImZhdm91cml0ZXNfY291bnQiOjE3MDgsImx
hbmciOiJlbiIsInV0Y19vZmZzZXQiOi0yNTIwMCwicHJvZmlsZV9pbWFnZV91cmwiOiJodHRwOlwvXC9hMC50d2ltZy
5jb21cL3Byb2ZpbGVfaW1hZ2VzXC8xNzA4NDI4MlwvYWxleF9raW5nX25vcm1hbC5qcGciLCJ1cmwiOiJodHRwOlwvX
C9hbGV4a2luZy5vcmciLCJjcmVhdGVkX2F0IjoiVGh1IERlYyAyMSAwODoxOTozOCArMDAwMCAyMDA2IiwibmFtZSI6
IkFsZXggS2luZyIsImZvbGxvd2Vyc19jb3VudCI6NTExNSwicHJvZmlsZV9iYWNrZ3JvdW5kX3RpbGUiOmZhbHNlLCJ
zaG93X2FsbF9pbmxpbmVfbWVkaWEiOmZhbHNlLCJwcm9maWxlX3NpZGViYXJfZmlsbF9jb2xvciI6IkVBRUFGQyIsIm
ZvbGxvd19yZXF1ZXN0X3NlbnQiOmZhbHNlLCJwcm90ZWN0ZWQiOmZhbHNlLCJsb2NhdGlvbiI6IkRlbnZlciwgQ08iL
CJkZWZhdWx0X3Byb2ZpbGUiOmZhbHNlLCJ2ZXJpZmllZCI6ZmFsc2UsImZvbGxvd2luZyI6ZmFsc2UsImRlc2NyaXB0
aW9uIjoiV2ViIGRldmVsb3BlciBhbmQgZm91bmRlciBvZiBDcm93ZCBGYXZvcml0ZS4iLCJwcm9maWxlX2JhY2tncm9
1bmRfY29sb3IiOiI4ODk5Y2MiLCJwcm9maWxlX2JhY2tncm91bmRfaW1hZ2VfdXJsIjoiaHR0cDpcL1wvYTAudHdpbW
cuY29tXC9pbWFnZXNcL3RoZW1lc1wvdGhlbWUxXC9iZy5wbmciLCJwcm9maWxlX2xpbmtfY29sb3IiOiIwMDAwRkYiL
CJsaXN0ZWRfY291bnQiOjQyNSwic2NyZWVuX25hbWUiOiJhbGV4a2luZ29yZyIsInRpbWVfem9uZSI6Ik1vdW50YWlu
IFRpbWUgKFVTICYgQ2FuYWRhKSIsInByb2ZpbGVfdXNlX2JhY2tncm91bmRfaW1hZ2UiOmZhbHNlfSwiaW5fcmVwbHl
fdG9fc3RhdHVzX2lkIjpudWxsLCJjcmVhdGVkX2F0IjoiVGh1IEFwciAyNiAwODowNzozOCArMDAwMCAyMDEyIiwidH
J1bmNhdGVkIjpmYWxzZSwiZ2VvIjpudWxsLCJpbl9yZXBseV90b19zY3JlZW5fbmFtZSI6bnVsbCwic291cmNlIjoiP
GEgaHJlZj1cImh0dHBzOlwvXC9zb3ByZXN0by5tYWlsY2hpbXAuY29tXCIgcmVsPVwibm9mb2xsb3dcIj5Tb2NpYWwg
UHJveHkgYnkgTWFpbGNoaW1wPFwvYT4iLCJpZCI6IjE5NTQyMzg3ODcyNDU5OTgwOSIsImNvbnRyaWJ1dG9ycyI6bnV
sbCwicmV0d2VldGVkIjpmYWxzZSwidGV4dCI6IkFsbCBmZWF0dXJlcyBhcmUgXCJkZXZlbG9wbWVudCBjb21wbGV0ZV
wiIGZvciB0aGUgbmV4dCB2ZXJzaW9uIG9mIFNvY2lhbC4gSSB3aWxsIHNsZWVwIHdlbGwgKHRob3VnaCBub3QgbG9uZ
ykgdG9uaWdodC4gI3dvcmRwcmVzcyJ9";s:4:"urls";a:2:{i:0;s:41:"http://alexking.org/blog/2012/04
/26/13316";i:1;s:28:"http://alexking.org/?p=13316";}s:7:"account";O:8:"stdClass":1:{s:4:"us
er";O:8:"stdClass":39:{s:20:"contributors_enabled";s:0:"";s:14:"statuses_count";s:4:"3676";
s:13:"notifications";s:0:"";s:18:"profile_link_color";s:6:"0000FF";s:9:"protected";s:0:"";s
:23:"profile_image_url_https";s:66:"https://si0.twimg.com/profile_images/17084282/alex_king
_normal.jpg";s:16:"favourites_count";s:3:"877";s:28:"profile_sidebar_border_color";s:6:"556
699";s:15:"followers_count";s:4:"4380";s:4:"name";s:9:"Alex King";s:12:"listed_count";s:3:"
398";s:9:"time_zone";s:27:"Mountain Time (US & Canada)";s:8:"verified";s:0:"";s:28:"pr
ofile_use_background_image";s:0:"";s:10:"utc_offset";s:6:"-25200";s:17:"profile_image_url";
s:64:"http://a1.twimg.com/profile_images/17084282/alex_king_normal.jpg";s:11:"description"
;s:44:"Web developer and founder of Crowd Favorite.";s:24:"profile_background_color";s:6:"8
899cc";s:4:"lang";s:2:"en";s:28:"profile_background_image_url";s:47:"http://a0.twimg.com/im
ages/themes/theme1/bg.png";s:11:"screen_name";s:11:"alexkingorg";s:6:"status";O:8:"stdClass
":18:{s:19:"in_reply_to_user_id";s:0:"";s:9:"truncated";s:0:"";s:21:"in_reply_to_status_id"
;s:0:"";s:9:"favorited";s:0:"";s:25:"in_reply_to_status_id_str";s:0:"";s:5:"place";s:0:"";s
:3:"geo";s:0:"";s:11:"coordinates";s:0:"";s:23:"in_reply_to_screen_name";s:0:"";s:23:"in_re
ply_to_user_id_str";s:0:"";s:6:"source";s:101:"Twitter for Mac";s:6:"id_str";s:18:"1013087
[...]

Easy solution via filter.

function excerpt_sanity($excerpt) {
	if (strlen($excerpt) > 550) {

// take care of it.

	}
	return $excerpt;
}
add_filter('get_the_excerpt', 'excerpt_sanity', 50);

Slashes

1. Validation / Sanitization

2. (stuff)

3. Encoding / Escaping

Push $_POST directly into functions that save.

  • WordPress forces slashes on $_POST data
    wp_magic_quotes() defined wp-includes/load.php, line 531
    called in wp-settings.php, line 218
  • stripslashes_deep()
  • add_magic_quotes()

1 of 1

You may have seen this in plugins/themes, etc.

update_post_meta($post->ID, '_my_meta_key', $_POST['_my_meta_key']);

update_metadata()

wp-includes/meta.php, line 101

function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') {

// [...]

	// expected_slashed ($meta_key)
	$meta_key = stripslashes($meta_key);
	$passed_value = $meta_value;
	$meta_value = stripslashes_deep($meta_value);
	$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );

// [...]

}

1. Make API call to Twitter, Facebook, etc.

2. Store data in post meta.

$my_api_data = get_twitter_data();
update_post_meta($post->ID, '_my_meta_key', $my_api_data);

Stripping slashes on JSON data is problematic.

// before
{"profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/17084282\/alex_king_normal.jpg"}

// after
{"profile_image_url":"http://a0.twimg.com/profile_images/17084282/alex_king_normal.jpg"}

We're gonna need more slashes.

$my_api_data = get_twitter_data();
update_post_meta($post->ID, '_my_meta_key', add_magic_quotes($my_api_data));

1 of 1

wp_insert_post()

wp-includes/post.php, line 2460

function wp_insert_post($postarr, $wp_error = false) {

// [...]

	// expected_slashed (everything!)
	$data = compact( array( 'post_author', 'post_date', 'post_date_gmt', 'post_content',
		'post_content_filtered', 'post_title', 'post_excerpt', 'post_status', [...] ) );
	$data = apply_filters('wp_insert_post_data', $data, $postarr);
	$data = stripslashes_deep( $data );

// [...]

}
$post_data = array(
	'post_status' => 'publish',
	'post_type' => 'post',
	'post_title' => 'This is my title',
	'post_content' => 'Crazy with the backslashes \\\\\\\\',
);

// wrong

wp_insert_post($post_data);

// right (as of WP 3.4.1)

wp_insert_post(add_magic_quotes($post_data));

wp_insert_user()

wp-includes/user.php, line 1251

function wp_insert_user($userdata) {

// [...]

	$data = compact( 'user_pass', 'user_email', 'user_url', 'user_nicename', 'display_name',
		'user_registered' );
	$data = stripslashes_deep( $data );

// [...]

}

Working on a patch for WordPress 3.5

(Core ticket #21767 — it's a big change)

JavaScript
Debugging

Add this to your wp-config.php file.

define('SCRIPT_DEBUG', true);

Database Queries

  • Most common performance bottleneck
  • Easiest way to improve page
    generation time
  • Every database query should be intentional

1 of 1

Get the two most recent posts.

$example = new WP_Query(array(
	'posts_per_page' => 2,
	'orderby' => 'date',
	'order' => 'DESC',
));

This actually generates five database queries.

Query #1: find IDs of posts matching query args

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'post'
AND (
	wp_posts.post_status = 'publish'
	OR wp_posts.post_status = 'future'
	OR wp_posts.post_status = 'draft'
	OR wp_posts.post_status = 'pending'
	OR wp_posts.post_status = 'private'
)
ORDER BY wp_posts.post_date DESC
LIMIT 0, 2

Query #2: check total number of results

SELECT FOUND_ROWS()

Query #3: get full post data

SELECT wp_posts.*
FROM wp_posts
WHERE ID IN (10134,10263)

Query #4: find taxonomy terms for selected posts

SELECT t.*, tt.*, tr.object_id
FROM wp_terms AS t
INNER JOIN wp_term_taxonomy AS tt
	ON tt.term_id = t.term_id
INNER JOIN wp_term_relationships AS tr
	ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tt.taxonomy IN ('category', 'post_tag', 'post_format')
AND tr.object_id IN (10134, 10263)
ORDER BY t.name ASC

Query #5: find postmeta for selected posts

SELECT post_id, meta_key, meta_value
FROM wp_postmeta
WHERE post_id IN (10134,10263)

1 of 1

Get the two most recent posts in a category.

$example = new WP_Query(array(
	'category_name' => 'uncategorized',
	'posts_per_page' => 2,
	'orderby' => 'date',
	'order' => 'DESC',
));

This actually generates eight database queries.

Query #1: find term ID

SELECT wp_term_taxonomy.term_id
FROM wp_term_taxonomy
INNER JOIN wp_terms USING (term_id)
WHERE taxonomy = 'category'
AND wp_terms.slug IN ('uncategorized')

Query #2: find term taxonomy ID

SELECT term_taxonomy_id
FROM wp_term_taxonomy
WHERE taxonomy = 'category'
AND term_id IN (1)

Query #3: get detailed term info

SELECT t.*, tt.*
FROM wp_terms AS t
INNER JOIN wp_term_taxonomy AS tt
	ON t.term_id = tt.term_id
WHERE tt.taxonomy = 'category'
AND t.slug = 'uncategorized'
LIMIT 1

Query #4: find post IDs that match query

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships
	ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (1) )
AND wp_posts.post_type = 'post'
AND (
	wp_posts.post_status = 'publish'
	OR wp_posts.post_status = 'future'
	OR wp_posts.post_status = 'draft'
	OR wp_posts.post_status = 'pending'
	OR wp_posts.post_status = 'private'
)
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 2

Query #5: check total number of results

SELECT FOUND_ROWS()

Query #6: get full post data

SELECT wp_posts.*
FROM wp_posts
WHERE ID IN (10134,10263)

Query #7: find taxonomy terms for selected posts

SELECT t.*, tt.*, tr.object_id
FROM wp_terms AS t
INNER JOIN wp_term_taxonomy AS tt
	ON tt.term_id = t.term_id
INNER JOIN wp_term_relationships AS tr
	ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tt.taxonomy IN ('category', 'post_tag', 'post_format')
AND tr.object_id IN (10134, 10263)
ORDER BY t.name ASC

Query #8: find postmeta for selected posts

SELECT post_id, meta_key, meta_value
FROM wp_postmeta
WHERE post_id IN (10134,10263)

Put WP_Query on a diet.

$example = new WP_Query(array(
	'posts_per_page' => 2,
	'orderby' => 'date',
	'order' => 'DESC',
	'no_found_rows' => true,
	'update_post_term_cache' => false,
	'update_post_meta_cache' => false,
));

Brings it down to two queries.

get_post()

wp-includes/post.php, line 380

function &get_post(&$post, $output = OBJECT, $filter = 'raw') {
// [...]
	if ( ! $_post = wp_cache_get($post_id, 'posts') ) {
		$_post = $wpdb->get_row($wpdb->prepare("
			SELECT * FROM $wpdb->posts
			WHERE ID = %d LIMIT 1
		", $post_id));
		if ( ! $_post )
			return $null;
		_get_post_ancestors($_post);
		$_post = sanitize_post($_post, 'raw');
		wp_cache_add($_post->ID, $_post, 'posts');
	}
// [...]
}

Context matters.

1 of 1

Tools

Roll your own

define('SAVE_QUERIES', true);

global $wpdb;
print_r($wpdb->queries);

Debug Bar — http://wordpress.org/extend/plugins/debug-bar/

debug bar

Bonus tip: is_main_query()

wp-includes/query.php

// line 724
function is_main_query() {
	global $wp_query;
	return $wp_query->is_main_query();
}

// line 3514
class WP_Query {
	function is_main_query() {
		global $wp_the_query;
		return $wp_the_query === $this;
	}
}

Post Meta vs.
Taxonomies

  • Both can be used to set attributes on a post/page/custom post type.
  • Relatively easy to present a custom UI for both (in the admin).
  • Context matters.
  • Use a taxonomy when you need to query by the attribute.
  • Use post meta when you don't.

Delete Post

1 of 1

wp_delete_post()

wp-includes/post.php, line 2022

function wp_delete_post( $postid = 0, $force_delete = false ) {

// [...]

	do_action('before_delete_post', $postid);

// [...]

	wp_delete_object_term_relationships($postid, get_object_taxonomies($post->post_type));

// [...]

	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ));
	foreach ( $comment_ids as $comment_id )
		wp_delete_comment( $comment_id, true );

	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ));
	foreach ( $post_meta_ids as $mid )
		delete_metadata_by_mid( 'post', $mid );

	do_action( 'delete_post', $postid );
	do_action( 'delete_post', $postid );
	$wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
	do_action( 'deleted_post', $postid );

// [...]

	do_action('after_delete_post', $postid);

	return $post;
}

Post GUID

Globally unique identifier

http://en.wikipedia.org/wiki/Globally_unique_identifier

Page GUIDs were not unique prior to WordPress 3.1

They were all the site URL: http://example.com/

Trac ticket #15041

This data has not been "cleaned up" yet

Trac ticket #18286

Plugin to fix it: https://github.com/crowdfavorite/wp-guid-fix

$my_guid = $service_name.'-'.$service_item_id;
// 'twitter-256996758964563968'

wp_insert_post(array(
	'post_title' => $my_title,
	'post_content' => $my_content,
	'guid' => $my_guid
));

The GUID must begin with http://

$my_guid = 'http://'.$service_name.'-'.$service_item_id;
// 'http://twitter-256996758964563968'

wp_insert_post(array(
	'post_title' => $my_title,
	'post_content' => $my_content,
	'guid' => $my_guid
));

Security

Always upgrade immediately.

Connect

  • alexking.org | @alexkingorg
  • crowdfavorite.com | @crowdfavorite

Slides

  • alexking.org/speaking
  • Built with the excellent Impress.js
    code: github.com/alexkingorg/core-competency
  • Slide styling by Jared Cornwall (@jaredkc)
  • Code examples are from WordPress 3.4.1