# WORDPRESS插件開發 #–
WP2WPQAQ 插件是一個根據WordPress中的xmlrpc進行編寫的實現兩個WordPress站點進行文章同步的插件。目前該插件實現了文章的同步,媒體文件上傳(上傳到媒體庫),但是分類(category),標籤(Tag)和自定義字段(custom_fields)的同步功能未能實現。
該插件由於存在大部分不符合目前項目的具體需求,且因爲缺乏WordPress對應API詳細案例學習,導致開發不完善,沒能實現如下功能:
1.非媒體庫文件上傳到非媒體庫.
eg.從下面的ablog站點將order/目錄的image.jpeg圖片上傳到bblog的order/目錄中。
目前實現的上傳功能是這樣的,假設當前時間是2017年12月份,則ablog的order/image.jpeg圖片將會上傳到bblog的wp-content/uploads/2017/12/image.jpeg。
ablog—-
|– order/
|– image.jpeg
|–wp-content/
|–uploads
|–2017/
|–11/
|–12/
|–wp-includes/
|–wp-admin/
eg.想上傳到order/結果卻是上傳到wp-content/uploads/2017/12/目錄。
bblog—-
|– order/
|– images2.png
|–wp-content/
|–uploads
|–2017/
|–11/
|–12/
|–image.jpeg
|–wp-includes/
|–wp-admin/
2.custom_fields字段無法按照API中所述可以更新custom_fields字段,所以導致使用的插件ALL-IN-ONE-SEO-PACK的信息無法同步到目標站點。
3.分類同步,假設在ablog站點中,存在分類目錄叫A,而bblog中沒有A分類,則需要實現同步A分類到bblog中,目前是因爲不知道如何檢查bblog中是否存在A分類,如果直接使用API中的wp.newCategory是否會重複添加分類。
4.標籤同步,API中不提供標籤的新增,只存在wp.getTags的實現。
插件目錄結構:
WP2WPQAQ
|-asset/
|– js/
|– wp2wp-sync.js
|– css/
|– wp2wp-admin.css
|–lang/
|– wp2wpqaq.php —-插件主入口文件
|– wp2wp-options.php —-配置同步目標站點頁
|– wp2wp-ajax.php —-異步實現同步操作實現功能
|– uninstall.php —-卸載插件
wp2wpqaq.php 代碼如下:
<?php /** * Plugin Name: WP2WPQAQ * Plugin URI: # * Description: Synchronize articles between two WordPress sites. * Author: DHM(huimingdeng) * Author URI: # * Version: 0.0.1 */ // Nothing in this plugin works outside of the admin area, so don't bother loading it if we're not looking at the admin panel... if(is_admin()){ define( 'WP_OPTIONS_VERSION', 1 ); add_action( 'admin_menu', 'wp2wp_menu' );//Add Menu // add_action('admin_head','wp2wp_active_style'); add_action( 'admin_init', 'wp2wp_register_settings' ); add_action('add_meta_boxes','my_meta_box'); add_filter( 'plugin_action_links', 'wp2wp_settings_link', 10, 2 ); // add_action('add_meta_boxes', array('sync-details-container', 'wp2wp_view')); // Activate the style sheets and scripts. function wp2wp_active_style(){ $css = plugins_url('assets/css/',__FILE__).'wp2wp-admin.css'; $js = plugins_url('assets/js/',__FILE__).'wp2wp-sync.js';//echo $js; wp_register_script('wp2wp-sync',$js,array()); wp_register_style('wp2wp-admin',$css,array()); wp_enqueue_script('wp2wp-sync'); wp_enqueue_style('wp2wp-admin'); } // add a meta box... function my_meta_box(){ $screen = get_current_screen(); if('add' !== $screen->action){ add_meta_box('wp2wp_view','WP2WPQAQ-synchronize','wp2wp_view','post','side','high'); add_action('admin_enqueue_scripts','wp2wp_active_style'); } } // Create a template... function wp2wp_view(){ ?> <div id="sync-details-container"> <div class="sync-span-body"><span><?php _e('Push content to Target: <br>', 'wp2wpqaq');?><span title="配置頁設置的同步對象."><b><?php $server=get_option("wp2wp_server_option"); echo $server['host'];?></b></span></span></div> <div id="sync-content"> </div> <button type="button" class="btn button-primary" onclick="wp2wppush(<?php the_ID();?>)">Push to Target</button> <input type="hidden" name="ajaxurl" id="ajaxurl" value="<?php echo WP_PLUGIN_URL . '/' . dirname(plugin_basename(__FILE__)) . '/wp2wp-ajax.php'; ?>"> </div> <?php } // Add submenu... function wp2wp_menu() { add_submenu_page( 'options-general.php', 'WP2WPQAQ Settings', 'WP2WPQAQ', 'manage_options', 'wp2wpqaq', 'wp2wp_setting' ); } // Setting page... function wp2wp_setting(){ $css_uri=plugins_url('assets/css/wp2wp-admin.css',__FILE__); // wp_enqueue_style('wp2wp-admin.css',$css_uri); require_once( 'wp2wp-options.php' ); } // Register valid admin options... function wp2wp_register_settings(){ register_setting( "wp2wpqaq_options", "wp2wp_server_option" , "wp2wp_option_save"); } // Save option... function wp2wp_option_save($options){ if(!empty($options)){ $remoteServer = trim( $options['host'] ); if ( ( 'http://' != substr( $remoteServer, 0, 7 ) ) && ( 'https://' != substr( $remoteServer, 0, 8 ) ) ) { $remoteServer = 'http://'.$remoteServer; } if ( '/' != substr( $remoteServer, -1 ) ) { $remoteServer .= '/'; } if ( include_once( ABSPATH . WPINC . '/class-IXR.php' ) ) { if ( include_once( ABSPATH . WPINC . '/class-wp-http-ixr-client.php' ) ) { $xmlrpc = new WP_HTTP_IXR_CLIENT( $remoteServer.'xmlrpc.php' ); $xmlrpc->query( 'wp.getOptions', array( 0, $options['user'], $options['pass'], array( 'software_name', 'software_version', 'so_api' ) ) ); $xmlrpcResponse = $xmlrpc->getResponse(); if ( null == $xmlrpcResponse ) { if ( -32300 == $xmlrpc->getErrorCode() ) { $options['authenticated'] = false; $newOptions['group'][$groupId]['servers'][$serverKey]['api'] = 'API Unavailable'; } else { $options['authenticated'] = false; $options['api'] = 'Unknown'; } }else{ if ( isset( $xmlrpcResponse['faultString'] ) ) { $options['authenticated'] = false; $options['api'] = __( trim( $xmlrpcResponse['faultString'], ' .' ), 'WP2WPQAQ' ); }else{ $options['authenticated'] = true; if ( isset( $xmlrpcResponse['so_api'] ) ) { $options['api'] = sprintf( __( 'WP2WPQAQ Synchronize API v%s', 'WP2WPQAQ' ), $xmlrpcResponse['so_api']['value'] ); } else { $options['api'] = $xmlrpcResponse['software_name']['value'].' '.$xmlrpcResponse['software_version']['value']; } } } } } } return $options; } // Settings link on plugins page... function wp2wp_settings_link( $links, $file ) { if ( plugin_basename( __FILE__ ) == $file ) { array_push( $links, '<a href="options-general.php?page=wp2wpqaq">'.__( 'Settings', 'wp2wpqaq' ).'</a>' ); } return $links; } }
wp2wp-options.php 代碼如下:
<div class="wrap"> <h2>WP2WPQAQ Settings:</h2> <div> <form action="options.php" method="post"> <?php settings_fields( 'wp2wpqaq_options' ); ?> <?php $options=get_option("wp2wp_server_option");?> <table class="form-table"> <tbody> <tr> <th>Host Name of Target:</th> <td><input type="text" name="wp2wp_server_option[host]" id="wp2wp_server_option" value="<?php echo $options['host'];?>"></td> </tr> <tr> <th>Username on Target:</th> <td><input type="text" name="wp2wp_server_option[user]" id="wp2wp_server_option" autocomplete="off" value="<?php echo $options['user'];?>"></td> </tr> <tr> <th>Password on Target:</th> <td><input type="password" name="wp2wp_server_option[pass]" id="wp2wp_server_option" autocomplete="new-password" value="<?php echo $options['pass'];?>"><span class="dashicons dashicons-yes"><?php if(isset($options['authenticated'])){echo 'auth:'.$options['authenticated'];}else{echo 'auth:'.$options['api'];}?></span></td> </tr> </tbody> </table> <!-- <input type="submit" name="submit" class="button button-primary" value="保存更改"> --> <?php submit_button(); ?> </form> </div>
wp2wp-ajax.php 代碼如下:
<?php require_once(dirname(__FILE__)."/../../../wp-config.php"); include_once(ABSPATH . WPINC . '/class-IXR.php'); global $wpdb; $action = $_REQUEST['action']; if($action=='push'){ $postId = $_POST['postId']; // $postId=142;//116; // Test $server = get_host(); if($server['authenticated']){ $post_tab = $table_prefix."posts"; $sql = "SELECT * FROM $post_tab where ID=$postId"; $post=$wpdb->get_row($sql,ARRAY_A); $seo_tab = $table_prefix."postmeta"; $seo = "SELECT meta_key,meta_value FROM $seo_tab WHERE post_id = $postId AND (meta_key LIKE '_aioseop_%')"; $custom_fields = $wpdb->get_results($seo,ARRAY_A); $host = addslashes(site_url().'/'); //設置遠程站點信息 $target = $server['host']; if ( '/' != substr( $target, -1 ) ) { $target .= '/'; } $username = $server['user']; $password = $server['pass']; $result=sync_post($post,$username,$password,$target,$host,$custom_fields); }else{ $result=array('status'=>500,'msg'=>$server['api']); } echo json_encode($result); }else{ echo json_encode(array('status'=>404,'msg'=>'No operation.')); } // 替換源站點附件 function replace_host($string,$target,$host){ if('' == $target || '' == $host) return false; // 判斷連接是否爲圖片,PDF附件等 $allowedExts=array('jpeg','jpg','png','gif','txt','pdf'); $pattern = "(http://[a-zA-Z0-9\./]+)"; preg_match_all($pattern, $string, $urls); //如果源站點的附件等才替換,其他的不替換 if(!empty($urls)&&!empty($urls[0])){ foreach ($urls[0] as $v) { $ext = strtolower(pathinfo($v,PATHINFO_EXTENSION)); if(in_array($ext, $allowedExts)){ $tmp=preg_replace('('.$host.')',$target,$v); // echo $tmp."\r\n"; $string=str_replace($v,$tmp,$string);//替換掉附件的url域名,非附件不替換 // echo $v."\r\n"; // $string = $tmp; } } } // echo $string; return $string; //測試 // return preg_replace('('.$host.')',$target,$string); } /** * 遠程同步文章(A->B) * @param array $post 需要同步的文章信息 * @param string $user 同步站點的用戶(登錄用) * @param string $pass 同步站點的用戶的登陸密碼 * @param string $target 目標站點域名(同步站點) * @param string $host 來源站點域名(本站點) * @param array $custom_fields 需要同步的自定義字段 * @return array 返回消息記錄(成功200失敗400) */ function sync_post($post,$user,$pass,$target,$host,$custom_fields){ $xmlrpcurl=$target.'xmlrpc.php'; $blogid=get_current_blog_id(); $GLOBALS['xmlrpc_internalencoding'] = 'UTF-8'; $client = new IXR_Client($xmlrpcurl); //先獲取文章主體內容,用於上傳內容中的附件 $file=$post['post_content']; // $post['post_content']=replace_host($post['post_content'],$target,$host);//替換成目標站點url $post['guid']=''; if(!empty($custom_fields)){//如果用戶自定義非空,則添加到$post中 $cs_fields=array(); foreach ($custom_fields as $k => $v) { $tmp_fields['key']=$v['meta_key']; $tmp_fields['value']=$v['meta_value']; // $tmp_fields=array($v['meta_key'],$v['meta_value']); $cs_fields[]=$tmp_fields; } $post['custom_fields']=$cs_fields; // $post['custom_fields']=array(array('key' => '_aioseop_title', 'value' => '還是測試')); } // 新的文章則進行新增,否則修改 $target_id = get_option("target_postid_".$post['ID']); if(!$target_id){ $client->query("wp.newPost",$blogid,$user,$pass,$post); $response=$client->getResponse(); // 成功後設置 if ($response['faultCode']==0){ if( is_a( $client->message, '\IXR_Message' ) ){ // 需要設置 target_postid_{the_ID} => 返回的post_id if(!$client->message->message->params[0]){//如果無法獲取post_id,則根據返回的xml獲取 if(preg_match('/<string>(\d+)<\/string>/', $client->message->message,$rs)){ //如果正則匹配成功 if(!empty($rs)&&!empty($rs[1])){ update_option("target_postid_".$post['ID'],$rs[1]); /*if(!empty($custom_fields)){//同步SEO元素 foreach ($custom_fields as $k => $v) { } }*/ } } }else{ update_option("target_postid_".$post['ID'],$client->message->message->params[0]); } } $msg = "The article uploaded successfully."; $result=update_media($file,$target,$host,$user,$pass); $is_true=200; } else { $is_true=400;//文章同步失敗 $msg = 'Fail:' . $response['faultString']; } }else{//echo "yes"; $post['ID'] = intval($target_id);//換成目標網站的文章id $post['post_modified'] = $post['post_modified_gmt'] = date("Y-m-d H:i:s"); // 需要修改的字段 $content = array( 'post_status' => $post['post_status'],//文章狀態 'post_title' => $post['post_title'],//標題 'post_excerpt' => $post['post_excerpt'],//摘要 'post_content' => $post['post_content'],//正文 'post_name' => $post['post_name'], 'post_password' => $post['post_password'], 'post_thumbnail' => intval($post['post_thumbnail']), // 'custom_fields' => $post['custom_fields'], ); $client->query("wp.editPost",$blogid,$user,$pass,intval($target_id),$content); $response=$client->getResponse(); if($response['faultCode']==0){ $msg = "The article uploaded successfully."; $result=update_media($file,$target,$host,$user,$pass); $is_true=200;// /*if(!empty($custom_fields)){//同步SEO元素 foreach ($custom_fields as $k => $v) { } }*/ } else { $is_true=400;//文章同步失敗 $msg = 'Fail:' . $response['faultString']; } } // exit; return array("status"=>$is_true,"msg"=>$msg); // return $is_true; } // 獲取目標站點的域名 function get_host(){ $option = get_option('wp2wp_server_option');//獲取設置中保存的配置項 return $option; } // 附件處理,只能上傳到目標站點的/wp-content/uploads/{Year}/{Month} {Year},{Month}爲當前的年月 // 返回值中 status 的值大於1則成功 function update_media($postContent,$target,$host,$user,$pass){ $is_true=0; $xmlrpcurl=$target.'xmlrpc.php'; $client = new IXR_Client($xmlrpcurl); // Determine whether the connection is a picture, PDF attachment, etc $allowedExts=array('jpeg','jpg','png','gif','txt','pdf'); // reference:http://www.jb51.net/article/23511.htm $mimetype=array( 'jpg'=>'image/jpeg', 'gif'=>'image/gif', 'png'=>'image/png', 'bmp'=>'image/bmp', 'pdf'=>'application/pdf', 'txt'=>'text/plain' ); // Matching gets the url. $pattern = "(http://[a-zA-Z0-9\./]+)"; preg_match_all($pattern, $postContent, $urls); $msg = ""; // print_r($urls); // Gets the file suffix name for the url. pathinfo($fileInfo['name'],PATHINFO_EXTENSION) if(!empty($urls)&&!empty($urls[0])){ foreach ($urls[0] as $v) { $ext = strtolower(pathinfo($v,PATHINFO_EXTENSION)); if(in_array($ext, $allowedExts)){ $tmp=preg_replace('('.$host.')','',$v); // $attach[]=$tmp; $arr=explode('/',$v); $filename=end($arr); // echo $filename."\r\n"; //替換目錄,得到附件 $path = str_replace($filename,'',$tmp); // echo $path."\r\n"; $upload_file = $tmp; // echo $upload_file."\r\n"; $attachment = array( 'name' => $filename,//pathinfo($filename,PATHINFO_FILENAME), 'type' => $mimetype[$ext], 'bits' => new IXR_Base64( file_get_contents( ABSPATH.$path.$filename )), 'overwrite' => true ); $client->query('wp.uploadFile','',$user,$pass,$attachment); // $clinet->query('metaWeblog.newMediaObject','',$user,$pass,$attachment); $response=$client->getResponse(); // print_r($client); if($response['faultCode']==0){ $is_true +=1; $msg .= $filename." upload success. \r\n"; }else{ $is_true -= 1;//不成功則減一 $msg .= $filename." upload failured... \r\n"; } } } } // print_r($attachment); // echo $msg; return array("status"=>$is_true,"msg"=>$msg); }
wp2wp-sync.js 代碼如下:
function wp2wppush(postId){ var ajaxurl = $('#ajaxurl').val(); var data = {postId:postId,action:'push'}; $.ajax({ url:ajaxurl, data:data, type:'POST', dataType:'json', beforeSend:function(){ $("#sync-content").html("<span class='sync-loading'>Loding....</span>"); }, success:function(msg){ if(msg.status==200){ // if(msg==200){ // alert(msg.status); $("#sync-content").html("<span class='sync-success'>當前文章同步成功!</span>").css({"margin-bottom":"10px","border":"1px dashed gray"}); }else{ var html = "<span class='sync-error'>同步失敗!</span>"; html+="<div class='error-msg'>"+msg.msg+"</div>"; $("#sync-content").html(html).css("margin-bottom","10px"); } }, error:function(msg) { html="<span>Sorry,unknown error."; $("#sync-content").html(html); } }) // alert(postId); }
希望有WordPress的大神,能提供您寶貴的建議,幫助完善插件功能。