<?php include_once( ABSPATH . 'wp-includes/class-IXR.php' ); include_once( ABSPATH . 'wp-includes/class-wp-http-ixr-client.php' ); include_once( dirname(__FILE__) . '/interface-syndication-client.php' ); include_once( dirname( __FILE__ ) . '/push-syndicate-encryption.php' ); class Syndication_WP_XMLRPC_Client extends WP_HTTP_IXR_Client implements Syndication_Client { private $username; private $password; private $site_ID; function __construct( $site_ID ) { // @TODO check port, timeout etc $server = untrailingslashit( get_post_meta( $site_ID, 'syn_site_url', true ) ); if ( false === strpos( $server, 'xmlrpc.php' ) ) $server = esc_url_raw( trailingslashit( $server ) . 'xmlrpc.php' ); else $server = esc_url_raw( $server ); $this->username = get_post_meta( $site_ID, 'syn_site_username', true); $this->password = push_syndicate_decrypt( get_post_meta( $site_ID, 'syn_site_password', true) ); $this->site_ID = $site_ID; parent::__construct( $server ); if ( true === apply_filters( 'syn_xmlrpc_push_send_thumbnail', true, $site_ID, $this ) ) { add_action( 'syn_xmlrpc_push_new_post_success', array( $this, 'post_push_send_thumbnail' ), 10, 6 ); add_action( 'syn_xmlrpc_push_edit_post_success', array( $this, 'post_push_send_thumbnail' ), 10, 6 ); // TODO: on delete post, delete thumbnail } } private function get_thumbnail_meta_keys( $post_id ) { // Support for non-core images, like from the Multiple Post Thumbnail plugin return apply_filters( 'syn_xmlrpc_push_thumbnail_metas', array( '_thumbnail_id' ), $post_id ); } function post_push_send_thumbnail( $remote_post_id, $post_id ) { $thumbnail_meta_keys = $this->get_thumbnail_meta_keys( $post_id ); foreach ( $thumbnail_meta_keys as $thumbnail_meta ) { $thumbnail_id = get_post_meta( $post_id, $thumbnail_meta, true ); $syn_local_meta_key = '_syn_push_thumb_' . $thumbnail_meta; $syndicated_thumbnails_by_site = get_post_meta( $post_id, $syn_local_meta_key, true ); if ( ! is_array( $syndicated_thumbnails_by_site ) ) $syndicated_thumbnails_by_site = array(); $syndicated_thumbnail_id = isset( $syndicated_thumbnails_by_site[ $this->site_ID ] ) ? $syndicated_thumbnails_by_site[ $this->site_ID ] : false; if ( ! $thumbnail_id ) { if ( $syndicated_thumbnail_id ) { $result = $this->query( 'syndication.deleteThumbnail', '1', $this->username, $this->password, $remote_post_id, $thumbnail_meta ); unset( $syndicated_thumbnails_by_site[ $this->site_ID ] ); update_post_meta( $post_id, $syn_local_meta_key, $syndicated_thumbnails_by_site ); } continue; } if ( $syndicated_thumbnail_id == $thumbnail_id ) continue; list( $thumbnail_url ) = wp_get_attachment_image_src( $thumbnail_id, 'full' ); //pass thumbnail data and meta into the addThumnail to sync caption, description and alt-text //has to be this way since $thumbnail_post_data = get_post($thumbnail_id); $thumbnail_alt_text = trim(strip_tags(get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true))); $result = $this->query( 'syndication.addThumbnail', '1', $this->username, $this->password, $remote_post_id, $thumbnail_url, $thumbnail_meta, $thumbnail_post_data, $thumbnail_alt_text ); if ( $result ) { $syndicated_thumbnails_by_site[ $this->site_ID ] = $thumbnail_id; update_post_meta( $post_id, $syn_local_meta_key, $syndicated_thumbnails_by_site ); } } } public static function get_client_data() { return array( 'id' => 'WP_XMLRPC', 'modes' => array( 'push' ), 'name' => 'WordPress XMLRPC' ); } public function new_post( $post_ID ) { $post = (array)get_post( $post_ID ); // This filter can be used to exclude or alter posts during a content push $post = apply_filters( 'syn_xmlrpc_push_filter_new_post', $post, $post_ID ); if ( false === $post ) return true; // rearranging arguments $args = array(); $args['post_title'] = $post['post_title']; $args['post_content'] = $post['post_content']; $args['post_excerpt'] = $post['post_excerpt']; $args['post_status'] = $post['post_status']; $args['post_type'] = $post['post_type']; $args['wp_password'] = $post['post_password']; $args['post_date_gmt'] = $this->convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ); $args['terms_names'] = $this->_get_post_terms( $post_ID ); $args['custom_fields'] = $this->_get_custom_fields( $post_ID ); $args = apply_filters( 'syn_xmlrpc_push_new_post_args', $args, $post ); $result = $this->query( 'wp.newPost', '1', $this->username, $this->password, $args ); if ( ! $result ) { return new WP_Error( $this->getErrorCode(), $this->getErrorMessage() ); } $remote_post_id = (int) $this->getResponse(); do_action( 'syn_xmlrpc_push_new_post_success', $remote_post_id, $post_ID ); return $remote_post_id; } public function edit_post( $post_ID, $remote_post_id ) { $args = array(); $post = (array)get_post( $post_ID ); // This filter can be used to exclude or alter posts during a content push $post = apply_filters( 'syn_xmlrpc_push_filter_edit_post', $post, $post_ID ); if ( false === $post ) return true; $remote_post = $this->get_remote_post( $remote_post_id ); if ( ! $remote_post ) { return new WP_Error( 'syn-remote-post-not-found', __( 'Remote post doesn\'t exist.', 'syndication' ) ); } // Delete existing metadata to avoid duplicates $args['custom_fields'] = array(); foreach ( $remote_post['custom_fields'] as $custom_field ) { $args['custom_fields'][] = array( 'id' => $custom_field['id'] ); } // rearranging arguments $args['post_title'] = $post['post_title']; $args['post_content'] = $post['post_content']; $args['post_excerpt'] = $post['post_excerpt']; $args['post_status'] = $post['post_status']; $args['post_type'] = $post['post_type']; $args['wp_password'] = $post['post_password']; $args['post_date_gmt'] = $this->convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ); $args['terms_names'] = $this->_get_post_terms( $post_ID ); $args['custom_fields'] = array_merge( $args['custom_fields'], $this->_get_custom_fields( $post_ID ) ); $args = apply_filters( 'syn_xmlrpc_push_edit_post_args', $args, $post ); $result = $this->query( 'wp.editPost', '1', $this->username, $this->password, $remote_post_id, $args ); if ( ! $result ) { return new WP_Error( $this->getErrorCode(), $this->getErrorMessage() ); } do_action( 'syn_xmlrpc_push_edit_post_success', $remote_post_id, $post_ID ); return $remote_post_id; } public function delete_post( $remote_post_id ) { $result = $this->query( 'wp.deletePost', '1', $this->username, $this->password, $remote_post_id ); if ( ! $result ) { return new WP_Error( $this->getErrorCode(), $this->getErrorMessage() ); } return true; } private function _get_custom_fields( $post_id ) { $post = get_post( $post_id ); $custom_fields = array(); $all_post_meta = get_post_custom( $post_id ); $blacklisted_meta = $this->_get_meta_blacklist( $post_id ); foreach ( (array) $all_post_meta as $post_meta_key => $post_meta_values ) { if ( in_array( $post_meta_key, $blacklisted_meta ) || preg_match( '/^_?syn/i', $post_meta_key ) ) continue; foreach ( $post_meta_values as $post_meta_value ) { $post_meta_value = maybe_unserialize( $post_meta_value ); // get_post_custom returns serialized data $custom_fields[] = array( 'key' => $post_meta_key, 'value' => $post_meta_value, ); } } $custom_fields[] = array( 'key' => 'syn_source_url', 'value' => $post->guid, ); return $custom_fields; } private function _get_meta_blacklist( $post_id ) { $blacklist = array( '_edit_last', '_edit_lock' /** TODO: add more **/ ); $thumbnail_meta_keys = $this->get_thumbnail_meta_keys( $post_id ); $blacklist = array_merge( $blacklist, $thumbnail_meta_keys ); return apply_filters( 'syn_ignored_meta_fields', $blacklist, $post_id ); } private function _get_post_terms( $post_id ) { $terms_names = array(); $post = get_post( $post_id ); if ( is_object_in_taxonomy( $post->post_type, 'category' ) ) $terms_names['category'] = wp_get_object_terms( $post_id, 'category', array( 'fields' => 'names' ) ); if ( is_object_in_taxonomy( $post->post_type, 'post_tag' ) ) $terms_names['post_tag'] = wp_get_object_terms( $post_id, 'post_tag', array( 'fields' => 'names' ) ); // TODO: custom taxonomy return $terms_names; } public function test_connection() { $result = $this->query( 'wp.getPostTypes', // @TODO find a better suitable function '1', $this->username, $this->password ); if( !$result ) { $error_code = absint($this->getErrorCode()); switch( $error_code ) { case 32301: add_filter('redirect_post_location', create_function( '$location', 'return add_query_arg("message", 305, $location);' ) ); break; case 401: add_filter('redirect_post_location', create_function( '$location', 'return add_query_arg("message", 302, $location);' ) ); break; case 403: add_filter('redirect_post_location', create_function( '$location', 'return add_query_arg("message", 303, $location);' ) ); break; case 405: add_filter('redirect_post_location', create_function( '$location', 'return add_query_arg("message", 304, $location);' ) ); break; default: add_filter('redirect_post_location', create_function( '$location', 'return add_query_arg("message", 306, $location);' ) ); break; } return false; } return true; } function get_remote_post( $remote_post_id ) { $result = $this->query( 'wp.getPost', '1', $this->username, $this->password, $remote_post_id ); if( !$result ) return false; return $this->getResponse(); } public function is_post_exists( $remote_post_id ) { $remote_post = $this->get_remote_post( $remote_post_id ); if( ! $remote_post || $remote_post_id != $remote_post['post_id'] ) { return false; } return true; } protected function convert_date_gmt( $date_gmt, $date ) { if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) { return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) ); } return $this->convert_date( $date_gmt ); } protected function convert_date( $date ) { if ( $date === '0000-00-00 00:00:00' ) { return new IXR_Date( '00000000T00:00:00Z' ); } return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) ); } public static function display_settings( $site ) { $site_url = get_post_meta( $site->ID, 'syn_site_url', true); $site_username = get_post_meta( $site->ID, 'syn_site_username', true); $site_password = push_syndicate_decrypt( get_post_meta( $site->ID, 'syn_site_password', true) ); ?> <p> <label for=site_url><?php echo esc_html__( 'Enter a valid site URL', 'push-syndication' ); ?></label> </p> <p> <input type="text" class="widefat" name="site_url" id="site_url" size="100" value="<?php echo esc_html( $site_url ); ?>" /> </p> <p> <label for="site_username"><?php echo esc_html__( 'Enter Username', 'push-syndication' ); ?></label> </p> <p> <input type="text" class="widefat" name="site_username" id="site_username" size="100" value="<?php echo esc_attr( $site_username ); ?>" /> </p> <p> <label><?php echo esc_html__( 'Enter Password', 'push-syndication' ); ?></label> </p> <p> <input type="password" class="widefat" name="site_password" id="site_password" size="100" autocomplete="off" value="<?php echo esc_attr( $site_password ); ?>" /> </p> <?php } public static function save_settings( $site_ID ) { $_POST['site_url'] = str_replace( '/xmlrpc.php', '', $_POST['site_url'] ); update_post_meta( $site_ID, 'syn_site_url', esc_url_raw( $_POST['site_url'] ) ); update_post_meta( $site_ID, 'syn_site_username', sanitize_text_field( $_POST['site_username'] ) ); update_post_meta( $site_ID, 'syn_site_password', push_syndicate_encrypt( sanitize_text_field( $_POST['site_password'] ) ) ); if( !filter_var( $_POST['site_url'], FILTER_VALIDATE_URL ) ) { add_filter('redirect_post_location', create_function( '$location', 'return add_query_arg("message", 301, $location);' ) ); return false; } return true; } public function get_post( $ext_ID ) { // TODO: Implement get_post() method. } public function get_posts( $args = array() ) { // TODO: Implement get_posts() method. } } class Syndication_WP_XMLRPC_Client_Extensions { public static function init() { add_filter( 'xmlrpc_methods' , array( __CLASS__, 'push_syndicate_methods' ) ); } public static function push_syndicate_methods( $methods ) { $methods['syndication.addThumbnail'] = array( __CLASS__, 'xmlrpc_add_thumbnail' ); $methods['syndication.addThumbnailData'] = array( __CLASS__, 'xmlrpc_add_thumbnail_data' ); $methods['syndication.deleteThumbnail'] = array( __CLASS__, 'xmlrpc_delete_thumbnail' ); return $methods; } public static function xmlrpc_add_thumbnail( $args ) { global $wp_xmlrpc_server, $wpdb; $wp_xmlrpc_server->escape( $args ); $blog_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $post_ID = (int)$args[3]; $thumbnail_url = esc_url_raw( $args[4] ); $meta_key = ! empty( $args[5] ) ? sanitize_text_field( $args[5] ) : '_thumbnail_id'; $thumbnail_post_data = $args[6]; $thumbnail_alt_text = $args[7]; if ( ! $post_ID ) return new IXR_Error( 500, __( 'Please specify a valid post_ID.', 'syndication' ) ); $thumbnail_raw = wp_remote_retrieve_body( wp_remote_get( $thumbnail_url ) ); if ( ! $thumbnail_raw ) return new IXR_Error( 500, __( 'Sorry, the image URL provided was incorrect.', 'syndication' ) ); $thumbnail_filename = basename( $thumbnail_url ); $thumbnail_type = wp_check_filetype( $thumbnail_filename ); $args = array( $blog_id, $username, $password, array( 'name' => $thumbnail_filename, 'type' => $thumbnail_type['type'], 'bits' => $thumbnail_raw, 'overwrite' => false, ), ); // Note: Leting mw_newMediaObject handle our auth and cap checks $image = $wp_xmlrpc_server->mw_newMediaObject( $args ); if ( ! is_array( $image ) || empty( $image['url'] ) ) return $image; $thumbnail_id = (int) $image['id']; if( empty( $thumbnail_id ) ) return new IXR_Error( 500, __( 'Sorry, looks like the image upload failed.', 'syndication' ) ); if ( '_thumbnail_id' == $meta_key ) $thumbnail_set = set_post_thumbnail( $post_ID, $thumbnail_id ); else $thumbnail_set = update_post_meta( $post_ID, $meta_key, $thumbnail_id ); if ( ! $thumbnail_set ) return new IXR_Error( 403, __( 'Could not attach post thumbnail.' ) ); $args = array( $blog_id, $username, $password, $thumbnail_id, array( 'post_title' => $thumbnail_post_data['post_title'], 'post_content' => $thumbnail_post_data['post_content'], 'post_excerpt' => $thumbnail_post_data['post_excerpt'], ), ); //update caption and description of the image $result = $wp_xmlrpc_server->wp_editPost($args); if ($result !== true) { //failed to update atatchment post details //handle it th way you want it (log it, message it) } //update alt text of the image update_post_meta($thumbnail_id, '_wp_attachment_image_alt', $thumbnail_alt_text); return $thumbnail_id; } public static function xmlrpc_delete_thumbnail( $args ) { global $wp_xmlrpc_server; $wp_xmlrpc_server->escape( $args ); $blog_id = (int) $args[0]; $username = $args[1]; $password = $args[2]; $post_ID = (int)$args[3]; $meta_key = ! empty( $args[4] ) ? sanitize_text_field( $args[4] ) : '_thumbnail_id'; if ( !$user = $wp_xmlrpc_server->login( $username, $password ) ) return $wp_xmlrpc_server->error; if ( ! current_user_can( 'edit_post', $post_ID ) ) return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) ); if ( '_thumbnail_id' == $meta_key ) $result = delete_post_thumbnail( $post_ID ); else $result = delete_post_meta( $post_ID, $meta_key ); if ( ! $result ) return new IXR_Error( 403, __( 'Could not remove post thumbnail.' ) ); return true; } } Syndication_WP_XMLRPC_Client_Extensions::init();