To begin…
This post is published to relate recent experiments with xmlrpc protocol now fully available in WordPress (version >3.1) source code (server and client like desktop clients listed in codex).
Researchs on other tests on web were not fully successful because some posts are incomplete or too old according recent adds in WP code and specially about processes to sent media and attach it to remote post.
The context
One master (local) WP site and a target (remote) WP site where to send a post (or a part of this) when published locally.
Technical context
- WordPress 3.1.3 on both sites and error.log available on both servers…
- One website named local containing a plugin working as a xmlrpc client : the purpose of this article.
- Another target site named remote accepting xmlrpc input (and login/pass with editing capabilities).
- TIPS : to test use the error_log ( ‘message’) php function (and not echo or print_r) !
The testing process
We will create a small plugin without admin UI. When a local post is published, a remote post is created through xmlrpc with his attachment copied to target site. On local post a custom field is added and contains the ID of the post on the remote site.
This process is simple and can fully improved with, by example, cron tasks with sub-selection.
By traveling inside the source code, you will discover some tips and tricks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?php /* Plugin Name: xili-xmlrpc Plugin URI: http://dev.xiligroup.com/ Description: xili-xmlrpc is a tool to post from one site to another. Author: dev.xiligroup.com - MS Version: 0.5.0 Author URI: http://dev.xiligroup.com */ define('XILIXMLRPC_VER','0.5.0'); /* used in admin UI */ include_once(ABSPATH . WPINC . '/class-IXR.php'); /* not included in wp-settings */ include_once(ABSPATH . WPINC . '/class-wp-http-ixr-client.php'); /* not included in wp-settings */ /** * Class without admin UI * */ class xili_xmlrpc { var $test_url = 'http://subdomain.domain.tld/xmlrpc.php'; var $remote_login = 'adlog'; var $remote_passwd = 'thepasswd'; var $remote_server = 'remoteslug'; // shortname used in custom field var $currentrequest = null; var $currentdate = true; // option to update target/remote date with now ! /** * hook when post is published - minimum for test * */ function xili_xmlrpc () { add_action( 'publish_post', array( &$this,'send_post_to') ); } |
The start of the source code is easy readable.
The first function is the hook when a post is published and now the function fired :
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | /** * function fired when a post is published * * @param post ID * */ function send_post_to ( $post_id ) { $to_post_id = get_post_meta ( $post_id , $this->remote_server, true ); $this -> create_remote_categories_if ( $post_id ); if ( $to_post_id > 0 ) { // mise à jour $remote_id = $this->edit_post_to ( $post_id ); /* not used now */ } else { $guids = $this->send_attachments ( $post_id ); $remote_id = $this->new_post_to ( $post_id ) ; if ( $remote_id ) { update_post_meta ( $post_id , $this->remote_server, $this->currentrequest ) ; } // update content after changing attachment url if ( $guids ) { //error_log ( '- array -'); $remote_id = $this->edit_post_to ( $post_id, $guids ); // create attachments link to parent in remote } // get attached medias $medias = $this->get_attached_medias ( $post_id , 'image') ; $remote_images = array(); if ( $medias ) { //print_r( $medias ); // create remote links of medias images foreach ( $medias as $oneimage ) { $remote_images[] = array ( 'link' => $oneimage['link'], 'file' => $oneimage['metadata']['file'], 'thumbnail' => array ( 'link' => $oneimage['thumbnail'], 'file' => $oneimage['metadata']['sizes']['thumbnail']['file'] ), 'medium' => array ('file' => $oneimage['metadata']['sizes']['medium']['file'] ), 'post-thumbnail' => array('file' => $oneimage['metadata']['sizes']['post-thumbnail']['file'] ) ); } // synchronize $search = array(); //local remaining link $replace = array(); foreach ( $guids as $one_id => $one_attachment ) { foreach ( $remote_images as $oneimage ) { if ( $one_attachment['remote'] == $oneimage['link'] ) { //error_log ( '----> '.$oneimage['link'] ); $remote_folder = str_replace ($oneimage['thumbnail']['file'], "", $oneimage['thumbnail']['link'] ); $replace[] = $remote_folder.$oneimage['medium']['file']; //error_log ( '---med replace-> '.$remote_folder.$oneimage['medium']['file'] ); $src_m = wp_get_attachment_image_src($one_id, 'medium'); $search[] = $src_m[0]; // error_log ( '---med search-> '.$src_m[0] ); $replace[] = $oneimage['thumbnail']['link']; $src = wp_get_attachment_image_src($one_id, 'thumbnail'); $search[] = $src[0]; } } } if ( $replace ) { // error_log ( '- array replace -'); $remote_id = $this->edit_post_to ( $post_id, $guids, array ('search' => $search, 'replace' => $replace) ); } } } // send the attachments } |
In this main function, the step by step tips when creating a new remote one is :
Send the attachment BEFORE the new post containing link in content to attachment. (if the content don’t contain media links, attachments (child to parent) are impossible.
Create the new post,
Save the remote ID inside custom field of the local post,
Attach medias links in the content of the remote post.
After doing a fitting table of links inside contents (local and remote): updating remote content of post.
…
Other functions used by the main presented above :
A new post is created on remote with datas from local one (the links (src or…) inside content are not yet updated and link to local wp site:
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | /** * Create a new remote post from a local one * * @param local post ID * */ function new_post_to ( $post_id ) { $rpc = new WP_HTTP_IXR_Client( $this->test_url ); $original_post = & get_post ($id = $post_id); $post = array( 'title' => $original_post->post_title, 'categories' => wp_get_object_terms($post_id, 'category', array('fields'=>'names') ), 'mt_keywords' => wp_get_object_terms($post_id, 'post_tag', array('fields'=>'names') ), 'description' => $original_post->post_content, 'wp_slug' => $original_post->post_name ); $params = array( 0, $this->remote_login, $this->remote_passwd, $post, 'publish' ); $status = $rpc->query( 'metaWeblog.newPost', $params ); $this->currentrequest = $rpc->getResponse(); return $status; // ID or false } |
Edit and update the remote post according optional 2nd and 3rd parameters:
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | /** * edit the local post clone content before to edit the remote one * * @param local ID * @param guids of local images or attachments * @param array of local - remote src or url inside content to proceed search and replace */ function edit_post_to ( $post_id , $guids = array(), $search_replace = array() ) { $rpc = new WP_HTTP_IXR_Client( $this->test_url ); $original_post = & get_post ($id = $post_id); if ( $guids ) { $post_content = $original_post->post_content ; foreach ( $guids as $one_id => $one ) { $post_content = str_replace ( $one['local'], $one['remote'], $post_content ); } if ( isset( $search_replace ['search']) ) $post_content = str_replace ($search_replace ['search'], $search_replace ['replace'], $post_content); } else { $post_content = $original_post->post_content ; } $post = array( 'title' => $original_post->post_title, 'categories' => wp_get_object_terms($post_id, 'category', array('fields'=>'names') ), 'mt_keywords' => wp_get_object_terms($post_id, 'post_tag', array('fields'=>'names') ), 'description' => $post_content, 'wp_slug' => $original_post->post_name ); if ( $this->currentdate ) { $newDate = new IXR_Date(strtotime('now')); $post['dateCreated'] = $newDate; } $to_post_id = get_post_meta ( $post_id , $this->remote_server, true ); if ( $to_post_id > 0 ) { $params = array( $to_post_id, $this->remote_login, $this->remote_passwd, $post, 'publish' ); $status = $rpc->query( 'metaWeblog.editPost', $params ); /* if(!$status) { echo 'Error [' . $rpc->getErrorCode() . ']: ' . $rpc->getErrorMessage(); exit(); } */ $request = $rpc->getResponse(); return $status; // ID or false } else { return false ; } } |
This function must be launched BEFORE a new post containing attachment is created.
Why ?
Because the xmlrpc server process analyze the content of the post (links inside) and yet don’t use the parent/child way.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | /** * create a remote attachment * use metaWeblog.newMediaObject */ function send_attachments ( $post_id ) { // get_attachments $attachments = & get_children( array( 'post_parent' => $post_id, 'post_type' => 'attachment', ) ); if ( $attachments != array() ) { $rpc = new WP_HTTP_IXR_Client( $this->test_url ); $to_post_id = get_post_meta ( $post_id , $this->remote_server, true ); // $guids = array(); foreach ( $attachments as $attachment_id => $attachment ) { $params = array( 0, $this->remote_login, $this->remote_passwd, array( 'name' => basename( get_attached_file( $attachment_id ) ), //$attachment->post_title, 'type' => $attachment->post_mime_type, 'bits' => new IXR_Base64 ( file_get_contents ( get_attached_file( $attachment_id ) ) ) // get_attached_file( $attachment_id) ) ); //error_log ('***'.$params[3]['bits']->getXml() ); $status = $rpc->query( 'metaWeblog.newMediaObject', $params ); if(!$status) { error_log ( 'Error [' . $rpc->getErrorCode() . ']: ' . $rpc->getErrorMessage() ); } if ( $status ) { $request = $rpc->getResponse(); //error_log( print_r( $request ) ); $guids[$attachment_id] = array( 'local' => $attachment->guid, 'remote' => $request['url'] ); error_log ( $request['url'] ); /* */ } } return $guids; } return array(); } |
Used to collect remote infos about attachments sent to target WP site.
/** * get attached medias * * * */ function get_attached_medias ( $post_id = 0, $mime_type = '' ) { $rpc = new WP_HTTP_IXR_Client( $this->test_url ); $remote_post_id = get_post_meta ( $post_id , $this->remote_server, true ); // get recent $params = array( 0, $this->remote_login, $this->remote_passwd, array( 'mime_type' => $mime_type, 'parent_id' => $remote_post_id ) ); $status = $rpc->query( 'wp.getMediaLibrary', $params ); if ( $status ) { $requestattach = $rpc->getResponse(); //print_r($requestattach); return $requestattach ; //$guids[$attachment_id] = array( 'local' => $attachment->guid, 'remote' => $requestattach[0]['link'] ); error_log ( $requestattach[0]['link'] ); } if(!$status) { error_log ( 'Error [' . $rpc->getErrorCode() . ']: ' . $rpc->getErrorMessage() ); } return array(); } |
Before ending the class of the plugin, a function that create categories on remote site if they not exists. Here in the example, we copy without choice the categories. but on a final release or for the special way of publishing, it is forecast to sub-select or to use a translation table.
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | /** * If remote category don't exist, create it * */ function create_remote_categories_if ( $post_id ) { $thecats = wp_get_object_terms($post_id, 'category', array('fields'=>'names') ) ; $rpc = new WP_HTTP_IXR_Client( $this->test_url ); $params = array( 0, $this->remote_login, $this->remote_passwd ); $status = $rpc->query( 'metaWeblog.getCategories', $params ); if ( $status ) { $request = $rpc->getResponse(); //print_r( $request ); $remotecatsnames = array(); foreach ( $request as $remotecategory ) { $remotecatsnames[] = $remotecategory ['categoryName'] ; } foreach ( $thecats as $onecatname ) { // error_log ( $onecatname ) ; if ( !in_array ( $onecatname, $remotecatsnames ) ) { //error_log ( $onecatname ) ; $params = array( 0, $this->remote_login, $this->remote_passwd, array("name" => $onecatname) ); $status = $rpc->query( 'wp.newCategory', $params ); if(!$status) { error_log ( 'Error [' . $rpc->getErrorCode() . ']: ' . $rpc->getErrorMessage() ); } if ( $status ) error_log ( $onecatname ) ; } } } } } // end class /** * plugin class instantiation * */ $xili_xmlrpc = new xili_xmlrpc (); |
When the plugin is activated, and when a post is published in the local WP containing the plugin with right parameters, a post is created on the remote WP.
And for annexe, a function to get a remote post not yet used because, in the test, we only create a new post but don’t modify it after the first creating process including attachements (if exists).
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /** * get_post from remote with his remote ID * */ function get_post_from ( $post_id ) { // $rpc = new WP_HTTP_IXR_Client( $this->test_url ); $params = array( $post_id, $this->remote_login, $this->remote_passwd, ); $status = $rpc->query( 'metaWeblog.getPost', $params ); if(!$status) { //echo 'Error [' . $rpc->getErrorCode() . ']: ' . $rpc->getErrorMessage(); //exit(); return false; } else { $request = $rpc->getResponse(); return $request ; } //print_r($request); } |
Infos for readers :
Around one day to go deep in xmlrpc processes and functions in WP Source Code.
Around two hours to write this post.
Enjoy it !
Michel of dev.xiligroup team
This plugin doesn’t seem to work! I edited the login info but i am unclear as to what the remote slug’s purpose is and how to use it could this be the problem why i am not seeing post on my remote blog?
remoteslug value is used as key to save in local post’s custom post field the value of ID of the remote post… see in functions where attachments are transfered…
This example is a small example. To test on local and remote server use tracing by inserting error_log line.
Salut,
Ton article est parfait tu est le seul qui montre une solution pour poster de wordpress à wordpress.
Par contre il y a deux trucs que je comprends pas :
var $remote_server = ‘remoteslug’; // shortname used in custom field
Sa correspond à quoi?
Et surtout comment tu récupère l’id du post qui se trouve sur le blog ou tu as posté l’article afin de l’éditer
Tu utilise get_post_meta ( 0, $remote_server, true );
Mais du coup comme je comprend pas le $remote_server je ne peux pas l’utiliser!
Cet article est un exemple simplifié. Le $remote_server est un slug qui identifie le serveur cible. Cela permet, utilisé en tant que clé post_meta, sur le post local de remplir/conserver un champ personnalisé avec l’ID du post sur le WP distant.
Ainsi, c’est le cas dans une utilisation pro et non publiée émettant plusieurs flux, il est possible de conserver pour un post local les numéros des posts dans plusieurs wp distants chacun étant identité par ce slug.
Bon travail.
C’est exactement ce dont j’ai besoin.
Mais ce que je comprends pas c’est ou tu le définis?
Par ce que dans ma fonction qui post sur le blog je fais :
$blog_server = ‘blogslug’;
$to_post_id = get_post_meta ( $post_ID, $blog_server, true );
echo (‘id du post envoyer : ‘.$to_post_id);
Et sa m’affiche rien. Donc je sais pas ou tu définis le nom de tom slug
Ce plugin est l’instantiation d’une classe. Donc ici la valeur remote_server est défini à sa déclaration (ligne 26) . Dans la classe on y accède via $this->remote_server (ex ligne 48). En dehors $xili_xmlrpc->remote_server… Comme il s’agit d’un exemple, vous avez libre choix pour les adaptations.
Ok c’est bon en faite j’ai trouver.
Merci beaucoup.