7

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... :(

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.com/wordpress and the recipe: ifttt.com/recipes/19382-rss-feed-to-wordpress-blog
    – Vallieres
    Commented Nov 4, 2014 at 19:23

3 Answers 3

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.com/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 ;-)

7
  • This is exactly what I was looking for! I implemented your solution, hopefully it will work properly and place all duplicate in the trash. I'll know next time the XMLRPC gets triggered!
    – Vallieres
    Commented Aug 14, 2014 at 20:33
  • Hello, it did not work as intended. See below what I did. :(
    – Vallieres
    Commented Sep 21, 2014 at 14:59
  • Hi @vallieres, thanks for reporting back. Hopefully I can find the problem.
    – birgire
    Commented Sep 22, 2014 at 8:56
  • Thanks @birgire, it really seems to be area where documentation and/or examples are not to be found. You might think I'm not the only one that wants to filter XMLRPC articles...
    – Vallieres
    Commented Sep 22, 2014 at 13:46
  • were you able to make it work? On my end, it does not seem to work at all :(
    – Vallieres
    Commented Oct 15, 2014 at 1:05
1

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);
    */
}
2
  • 1
    Hi, ingenious solution but that would be easily overriden by any Wordpress update following the change. Any way to make it work from a plugin or functions.php ?
    – Vallieres
    Commented Aug 9, 2015 at 21:11
  • 1
    @Vallieres, Yes, you're right, then I've disabled WordPress updates. I wanna create a plugin soon. Commented Aug 10, 2015 at 3:37
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.