{"id":66,"date":"2005-07-26T21:41:38","date_gmt":"2005-07-26T11:41:38","guid":{"rendered":"http:\/\/www.thunderguy.com\/semicolon\/?p=66"},"modified":"2005-07-27T09:43:22","modified_gmt":"2005-07-26T23:43:22","slug":"wordpress-pingback-and-trackback","status":"publish","type":"post","link":"https:\/\/thunderguy.com\/semicolon\/2005\/07\/26\/wordpress-pingback-and-trackback\/","title":{"rendered":"WordPress pingback and trackback bug"},"content":{"rendered":"<p>There is a bug in the way WordPress 1.5.1 handles draft posts and Advanced Editing mode. One effect of this is that pingbacks and trackbacks are sometimes sent with the wrong URL. This problem has been around for a while, but it looked a bit complicated so I figured that somebody would fix it one day. But as I was trying to write a useful new plugin, the bug hit me. So I have investigated the bug and found out what it&#8217;s all about. I have also written a plugin that fixes the problem.<\/p>\n<h2>Background<\/h2>\n<p>Basic (unfancy) WordPress permalinks look like this:<br \/>\n<code>www.blog.com\/index.php?p=42<\/code><\/p>\n<p>If you have fancy permalinks enabled, you&#8217;ll have fancy permalinks that look like this:<br \/>\n<code>www.blog.com\/2005\/07\/11\/ecky-thump\/<\/code><\/p>\n<p>If you publish a post and send pingbacks or trackbacks to other blogs, your permalink URL is sent as part of the ping\/trackback.<\/p>\n<h2>The Problem<\/h2>\n<p>If you create a post and save it as a draft, and then publish it, then WordPress will send pingbacks and trackbacks with the unfancy URL instead of the fancy one. The same thing happens if you create a post and click &#8220;Advanced Editing&#8221; before publishing it. (Behind the scenes, WordPress creates a draft when you do this.)<\/p>\n<p>This bug is in the WordPress bug tracking systems as <a href=\"http:\/\/trac.wordpress.org\/ticket\/1257\">bug #1257<\/a>.<\/p>\n<h2>The Quick Solution<\/h2>\n<p>I have written a plugin that fixes this. The <a href=\"http:\/\/www.thunderguy.com\/semicolon\/wordpress\/fixback-wordpress-plugin\/\">FixBack WordPress plugin<\/a> page has details and download.<\/p>\n<h2>Analysis: The Cause<\/h2>\n<p>The problem comes from the <code>get_post()<\/code> function. This function maintains a cache of posts. However, it never updates cache entries. In cases where WordPress is simply reading from the database to display posts, this is fine. But when publishing a post there can be problems. The problem occurs when we publish a post that was originally a draft.<\/p>\n<p>In this case, <code>get_post()<\/code> populates its cache with the draft version of the post (which has status set to &#8216;draft&#8217;). When the trackback\/pingback code calls <code>get_permalink()<\/code>, <code>get_permalink()<\/code> gets the post data from <code>get_post()<\/code> and thinks the post is still a draft &#8212; and <code>get_permalink()<\/code> returns unfancy permalinks for draft posts.<\/p>\n<h2>Analysis: The Solution<\/h2>\n<p>The solution I have used in my plugin is to update the cache after publishing the post, but before doing the trackbacks and pingbacks. Fortunately, there is a convenient action hook in just the right place. (By an amazing coincidence, I am <a href=\"http:\/\/trac.wordpress.org\/ticket\/1347\">partly responsible<\/a> for the hook being where it is &#8212; what a stroke of luck.)<\/p>\n<p>The action hook is &#8216;edit_post&#8217;. FixBack simply hooks into this, and updates the post cache by re-reading from the database. That&#8217;s all!<\/p>\n<h2>Further thoughts<\/h2>\n<p>This problem may be widespread in WordPress. I know many functions in WordPress maintain internal caches that could suffer from similar problems. The FixBack plugin only fixes this one case. This functionality should be added to the core. The simplest way is to add a function that invalidates the cache for a particular post ID, and to call this function immediately after publishing the post.<\/p>\n<p>It would be even nicer to implement a layer (a class perhaps) that mediates all post data access &#8212; this could then maintain its cache without worrying that somebody will bypass it. Following this sort of approach throughout would be quite a big effort &#8212; WordPress 2.0?<\/p>\n<h2>Gory details<\/h2>\n<p>This took me quite some time to figure out. Here I have annotated some excerpts from the WordPress source files. This may illuminate or obscure the foregoing explanations. The two colours of annotation describe the problem and the plugin solution respectively.<\/p>\n<p><strong>File: <code>wp-admin\/post.php<\/code><\/strong><\/p>\n<pre class=\"code\"><code><span class=\"hilite\">Line 271<\/span>\r\ncase 'editpost':\r\n\t<span class=\"hilite\">...\r\n\tThe next line calls user_can_edit_post(), which calls\r\n\tget_post(), which reads the draft post from the database\r\n\tinto $post_cache. The post status is 'draft'.<\/span>\r\n\tif (!user_can_edit_post($user_ID, $post_ID, $blog_ID))\r\n\t\tdie( __('You are not allowed to edit this post.') );\r\n\t<span class=\"hilite\">...<\/span>\r\n\t$post_status = $_POST['post_status'];\r\n\t<span class=\"hilite\">...<\/span>\r\n<span class=\"hilite\">Line 364<\/span>\r\n\t<span class=\"hilite\">The next line updates the post in the database. This\r\n\tupdates the post slug and sets the status to 'publish'.<\/span>\r\n\t$result = $wpdb-&gt;query(\"\r\n\t\tUPDATE $wpdb-&gt;posts SET\r\n\t\t\t<span class=\"hilite\">...<\/span>\r\n\t<span class=\"hilite\">Now the post is published, so its slug should be set\r\n\tand its status should be set to 'publish'.<\/span>\r\n<span class=\"hilite\">Line 430<\/span>\r\n\tif ($prev_status != 'publish' &amp;&amp; $post_status == 'publish')\r\n\t\t<span class=\"hilite-2\">We could use this hook to refresh the cache, and\r\n\t\tthis would fix the problem with publishing drafts.\r\n\t\tBut the stale cache may have other effects, so\r\n\t\tlet's delay the refresh until the next line...<\/span>\r\n\t\tdo_action('private_to_published', $post_ID);\r\n\r\n\t<span class=\"hilite-2\">Here is where we install a plugin that refreshes\r\n\t$post_cache from the database. Then in the next few\r\n\tlines, pingbacks and trackbacks will use the correct \r\n\tpermalink URL. Also, any plugins using the 'publish_post'\r\n\thook will be able to call get_permalink() with\r\n\tno problems.<\/span>\r\n\tdo_action('edit_post', $post_ID);\r\n\r\n\tif ($post_status == 'publish') {\r\n\t\tdo_action('publish_post', $post_ID);\r\n\t\t<span class=\"hilite\">Here is the problem. The trackback and pingback\r\n\t\troutines call get_permalink(), which returns the\r\n\t\tunfancy permalink.<\/span>\r\n\t\tdo_trackbacks($post_ID);\r\n\t\tdo_enclose( $content, $post_ID );\r\n\t\tif ( get_option('default_pingback_flag') )\r\n\t\t\tpingback($content, $post_ID);\r\n\t}<\/code><\/pre>\n<p><strong>File: <code>wp-includes\/template-functions-links.php<\/code><\/strong><\/p>\n<pre class=\"code\"><code><span class=\"hilite\">Line 25<\/span>\r\nfunction get_permalink($id = 0) {\r\n\t<span class=\"hilite\">...\r\n\tNext line gets the post from $post_cache. When the cache was\r\n\tfilled (see above) the post slug was empty and the status was\r\n\t'draft'. Even though both are now updated in the database,\r\n\tthe cache is stale.<\/span>\r\n\t$post = &amp; get_post($id);\r\n\t<span class=\"hilite\">...<\/span>\r\n\tif ('' != $permalink &amp;&amp; 'draft' != $post-&gt;post_status) {\r\n\t\t<span class=\"hilite\">... (do the fancy permalink)<\/span>\r\n\t} else { \/\/ if they're not using the fancy permalink option\r\n\t\t<span class=\"hilite\">We end up here because the cached post status is\r\n\t\t'draft', though it should be 'publish'.<\/span>\r\n\t\t$permalink = get_settings('home') . '\/?p=' . $post-&gt;ID;\r\n\t\treturn apply_filters('post_link', $permalink, $post);\r\n\t}\r\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>There is a bug in the way WordPress 1.5.1 handles draft posts and Advanced Editing mode. One effect of this is that pingbacks and trackbacks are sometimes sent with the wrong URL. This problem has been around for a while, but it looked a bit complicated so I figured that somebody would fix it one [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13],"tags":[83],"class_list":["post-66","post","type-post","status-publish","format-standard","hentry","category-wordpress","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/posts\/66","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/comments?post=66"}],"version-history":[{"count":0,"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/posts\/66\/revisions"}],"wp:attachment":[{"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/media?parent=66"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/categories?post=66"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thunderguy.com\/semicolon\/wp-json\/wp\/v2\/tags?post=66"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}