最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

actions - How to validate XML-RPC post creation and cancel when needed?

programmeradmin4浏览0评论

I have an IFTTT recipe that creates posts for me on some occasion, but for some weird reason it creates three, sometimes four posts of the same content.

I would like to add an add_action hook/callback to validate what will be a new post and, if it already exists, cancel the post, or move it to trash or something like it.

I found the the xmlrpc_prepare_post but I don't think I can cancel it from there. Unless I can update some attribute and set it to trash?

Update.

I tried the following and it only ever gets into the xmlrpc_call, but never ever inside xmlrpc_wp_insert_post_meta. I even added a hard-coded add_filter call (not just in case of if newPost) and my logs never show such logging message.

Here's the code:

function hueman_xmlrpc_call( $method )
{
    error_log("XMLRPC | hueman_xmlrpc_call Method = $method \n" , 3, '/home/.../debug.log');
    if( 'wp.newPost' === $method || 'metaWeblog.newPost' === $method )
    {
        error_log("XMLRPC | hueman_xmlrpc_call  method = " . $method . " \n" , 3, '/home/.../debug.log');
        add_filter( 'xmlrpc_wp_insert_post_data', 'hueman_xmlrpc_wp_insert_post_data' );
    }
}
add_action('xmlrpc_call', 'hueman_xmlrpc_call', 1 );


add_filter( 'xmlrpc_wp_insert_post_data', 'hueman_xmlrpc_wp_insert_post_data' );

function hueman_xmlrpc_wp_insert_post_data( $post_data )
{
    error_log("XMLRPC | hueman_xmlrpc_wp_insert_post_data  \n" , 3, '/home/.../debug.log');
    // Check if the post title exists:
    $tmp = get_page_by_title( 
        $post_data['post_title'], 
        OBJECT, 
        $post_data['post_type'] 
    );

    if( is_object ( $tmp ) )
    {
        // Go from 'insert' to 'update' mode within wp_insert_post():
        //$post_data['ID'] = $tmp->ID; 

        $post_data['post_status'] = 'trash';
        error_log("XMLRPC | hueman_xmlrpc_wp_insert_post_data I TRASHED IT! \n" , 3, '/home/.../debug.log');
    }

    return $post_data;  
}

In the logs, I have this kind of log statements:

XMLRPC | hueman_xmlrpc_call Method = mt.supportedMethods 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getRecentPosts 
XMLRPC | hueman_xmlrpc_call Method = mt.supportedMethods 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getRecentPosts 
XMLRPC | hueman_xmlrpc_call Method = mt.supportedMethods 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getRecentPosts 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getCategories 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.newPost 
XMLRPC | hueman_xmlrpc_call  method = metaWeblog.newPost 

Then, I see other filters I added when an article is created. I know I will probably end-up fixing the articles there but I'm sure this XMLRPC filters/actions should work.

From the logs, I see it go inside the if newPost and adding the filter 'xmlrpc_wp_insert_post_data' but it never executes the hueman_xmlrpc_wp_insert_post_data function... :(

I have an IFTTT recipe that creates posts for me on some occasion, but for some weird reason it creates three, sometimes four posts of the same content.

I would like to add an add_action hook/callback to validate what will be a new post and, if it already exists, cancel the post, or move it to trash or something like it.

I found the the xmlrpc_prepare_post but I don't think I can cancel it from there. Unless I can update some attribute and set it to trash?

Update.

I tried the following and it only ever gets into the xmlrpc_call, but never ever inside xmlrpc_wp_insert_post_meta. I even added a hard-coded add_filter call (not just in case of if newPost) and my logs never show such logging message.

Here's the code:

function hueman_xmlrpc_call( $method )
{
    error_log("XMLRPC | hueman_xmlrpc_call Method = $method \n" , 3, '/home/.../debug.log');
    if( 'wp.newPost' === $method || 'metaWeblog.newPost' === $method )
    {
        error_log("XMLRPC | hueman_xmlrpc_call  method = " . $method . " \n" , 3, '/home/.../debug.log');
        add_filter( 'xmlrpc_wp_insert_post_data', 'hueman_xmlrpc_wp_insert_post_data' );
    }
}
add_action('xmlrpc_call', 'hueman_xmlrpc_call', 1 );


add_filter( 'xmlrpc_wp_insert_post_data', 'hueman_xmlrpc_wp_insert_post_data' );

function hueman_xmlrpc_wp_insert_post_data( $post_data )
{
    error_log("XMLRPC | hueman_xmlrpc_wp_insert_post_data  \n" , 3, '/home/.../debug.log');
    // Check if the post title exists:
    $tmp = get_page_by_title( 
        $post_data['post_title'], 
        OBJECT, 
        $post_data['post_type'] 
    );

    if( is_object ( $tmp ) )
    {
        // Go from 'insert' to 'update' mode within wp_insert_post():
        //$post_data['ID'] = $tmp->ID; 

        $post_data['post_status'] = 'trash';
        error_log("XMLRPC | hueman_xmlrpc_wp_insert_post_data I TRASHED IT! \n" , 3, '/home/.../debug.log');
    }

    return $post_data;  
}

In the logs, I have this kind of log statements:

XMLRPC | hueman_xmlrpc_call Method = mt.supportedMethods 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getRecentPosts 
XMLRPC | hueman_xmlrpc_call Method = mt.supportedMethods 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getRecentPosts 
XMLRPC | hueman_xmlrpc_call Method = mt.supportedMethods 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getRecentPosts 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.getCategories 
XMLRPC | hueman_xmlrpc_call Method = metaWeblog.newPost 
XMLRPC | hueman_xmlrpc_call  method = metaWeblog.newPost 

Then, I see other filters I added when an article is created. I know I will probably end-up fixing the articles there but I'm sure this XMLRPC filters/actions should work.

From the logs, I see it go inside the if newPost and adding the filter 'xmlrpc_wp_insert_post_data' but it never executes the hueman_xmlrpc_wp_insert_post_data function... :(

Share Improve this question edited Nov 4, 2014 at 20:09 Vallieres asked Aug 9, 2014 at 0:32 VallieresVallieres 1857 bronze badges 2
  • 4 A very good (and interesting) question. Might you please file an edit and explain how IFTTT works together with WP, how you set it up and how you point to the XML-RPLC interface? Please be as verbose as possible as this has the potential to become a very epic question. Thanks. – kaiser Commented Aug 9, 2014 at 8:04
  • It's actually very simple. I used IFTTT with the RSS 2 Wordpress recipe. Plugged in my author RSS feed from the external site. In the Wordpress I logged in user my regular Wordpress user. here's the channel: ifttt/wordpress and the recipe: ifttt/recipes/19382-rss-feed-to-wordpress-blog – Vallieres Commented Nov 4, 2014 at 19:23
Add a comment  | 

3 Answers 3

Reset to default 7

It looks like the xmlrpc_prepare_post filter is only applied to the output of the wp_getPost and wp_getRevision methods of the wp_xmlrpc_server class.

It would be great if this code line:

do_action( 'xmlrpc_call', 'wp.newPost' );

would be replaced with extra input arguments, for example:

do_action( 'xmlrpc_call', 'wp.newPost', ..., $content_struct );

but that's not going to happen according to this ticket.

So we need to find another way around this.

Possible workarounds:

Here are some untested ideas using the xmlrpc_call and the xmlrpc_wp_insert_post_data filters.

Modify input data before it's inserted with wp_insert_posts():

/**
 * Prevent duplicate posts when doing wp.newPost via XML-RPC
 *
 * @see http://wordpress.stackexchange/a/157261/26350
 */

add_action( 'xmlrpc_call', 'wpse_xmlrpc_call' );

function wpse_xmlrpc_call( $method )
{
    if( 'wp.newPost' === $method )
        add_filter( 'xmlrpc_wp_insert_post_data', 'wpse_xmlrpc_wp_insert_post_data' );
}

function wpse_xmlrpc_wp_insert_post_data( $post_data )
{
    // Check if the post title exists:
    $tmp = get_page_by_title( 
        $post_data['post_title'], 
        OBJECT, 
        $post_data['post_type'] 
    );

    // Go from 'insert' to 'update' mode within wp_insert_post():
    if( is_object ( $tmp ) )
        $post_data['ID'] = $tmp->ID; 

    return $post_data;  
}

Here we try to find an existing post with the same title, during wp.newPost calls. If we find one, we add it's ID to the $post_data array, so it will be updated instead.

Notice that we could also have modified the post_status instead with:

    $post_data['post_status'] = 'trash'; 

so all extra inserts are directed to the trash.

You could also try to create your own insert method via the xmlrpc_methods filter.

I hope you can modify this to your needs, assuming this will work ;-)

Update: I've now tested this idea and it works, I can both trash the duplicated posts or update it directly.

Thanks to @DavidPeterson for noticing my silly PHP syntax errors from when I edited the code within the WPSE editor ;-)

In the wp-includes/class-wp-xmlrpc-server.php file

Inside the function mw_newPost() after these lines:

$post_title = isset( $content_struct['title'] ) ? $content_struct['title'] : null;
$post_content = isset( $content_struct['description'] ) ? $content_struct['description'] : null;

Add:

global $wpdb;
$some_post = $wpdb->get_row("
    SELECT ID
    FROM {$wpdb->posts}
    WHERE post_title = '{$post_title}'
");

And create a statement:

if (!empty($some_post->ID) and $some_post->ID > 0) {
    return 0;
} else {
    // the rest of the code that already exists in the function
    /*
    $post_status = $publish ? 'publish' : 'draft';
    ...
    return strval($post_ID);
    */
}

None of the previous solutions were working for me (maybe because I´m posting using metaWeblog.newPost)

So I made my own:

1-Make a copy of xmlrpc.php and rename to xmlrpc2.php to stay safe from WordPress updates.

2-Paste the code below this part:

/** Include the bootstrap for setting up WordPress environment */
require_once __DIR__ . '/wp-load.php';

Paste this code to prevent duplicate titles:

$mixml = file_get_contents('php://input');
$mixml2 = simplexml_load_string($mixml);
$json = json_encode($mixml2);

if ( ! is_admin() ) {
    require_once( ABSPATH . 'wp-admin/includes/post.php' );
}

$jsonIterator = new RecursiveIteratorIterator(
    new RecursiveArrayIterator(json_decode($json, TRUE)),
    RecursiveIteratorIterator::SELF_FIRST);

$nextok=false;
//Sorry for the noob parsing :)
foreach ($jsonIterator as $key => $val) {   
    if ($val=="title") $nextok=true;    
    if (($key=="string")&&($nextok)) { 
        $nextok=false;      
        $post_id = post_exists($val);
        //already exists, so I break the posting with exit
        if ($post_id) exit;                             
    }                   
}

Explanation:

  1. Read the XMLRPC input.
  2. Include post.php so we can use post_exists later.
  3. Get the post title. Sorry for the dirty parsing :) you will probably want to improve it.
  4. If the title already exists (even in recycle bin) we use exit so nothing is posted.

You can modify the original xmlrpc.php file, but you may lose the changes in future updates.

Renaming xmlrpc.php file is recommended since some servers block that file path and some brute force attacks target it.

发布评论

评论列表(0)

  1. 暂无评论