/class.jetpack-xmlrpc-server.php

  1. <?php 
  2.  
  3. /** 
  4. * Just a sack of functions. Not actually an IXR_Server 
  5. */ 
  6. class Jetpack_XMLRPC_Server { 
  7. /** 
  8. * The current error object 
  9. */ 
  10. public $error = null; 
  11.  
  12. /** 
  13. * Whitelist of the XML-RPC methods available to the Jetpack Server. If the 
  14. * user is not authenticated (->login()) then the methods are never added,  
  15. * so they will get a "does not exist" error. 
  16. */ 
  17. function xmlrpc_methods( $core_methods ) { 
  18. $jetpack_methods = array( 
  19. 'jetpack.jsonAPI' => array( $this, 'json_api' ),  
  20. 'jetpack.verifyAction' => array( $this, 'verify_action' ),  
  21. ); 
  22.  
  23. $user = $this->login(); 
  24.  
  25. if ( $user ) { 
  26. $jetpack_methods = array_merge( $jetpack_methods, array( 
  27. 'jetpack.testConnection' => array( $this, 'test_connection' ),  
  28. 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),  
  29. 'jetpack.featuresAvailable' => array( $this, 'features_available' ),  
  30. 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ),  
  31. 'jetpack.getPost' => array( $this, 'get_post' ),  
  32. 'jetpack.getPosts' => array( $this, 'get_posts' ),  
  33. 'jetpack.getComment' => array( $this, 'get_comment' ),  
  34. 'jetpack.getComments' => array( $this, 'get_comments' ),  
  35. 'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ),  
  36. 'jetpack.unlinkUser' => array( $this, 'unlink_user' ),  
  37. ) ); 
  38.  
  39. if ( isset( $core_methods['metaWeblog.editPost'] ) ) { 
  40. $jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject']; 
  41. $jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' ); 
  42.  
  43. /** 
  44. * Filters the XML-RPC methods available to Jetpack for authenticated users. 
  45. * 
  46. * @since 1.1.0 
  47. * 
  48. * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server. 
  49. * @param array $core_methods Available core XML-RPC methods. 
  50. * @param WP_User $user Information about a given WordPress user. 
  51. */ 
  52. $jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $user ); 
  53.  
  54. /** 
  55. * Filters the XML-RPC methods available to Jetpack for unauthenticated users. 
  56. * 
  57. * @since 3.0.0 
  58. * 
  59. * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server. 
  60. * @param array $core_methods Available core XML-RPC methods. 
  61. */ 
  62. return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods ); 
  63.  
  64. /** 
  65. * Whitelist of the bootstrap XML-RPC methods 
  66. */ 
  67. function bootstrap_xmlrpc_methods() { 
  68. return array( 
  69. 'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),  
  70. ); 
  71.  
  72. /** 
  73. * Verifies that Jetpack.WordPress.com received a registration request from this site 
  74. */ 
  75. function verify_registration( $verify_secret ) { 
  76. return $this->verify_action( array( 'register', $verify_secret ) ); 
  77.  
  78. /** 
  79. * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure 
  80. * 
  81. * Possible error_codes: 
  82. * 
  83. * verify_secret_1_missing 
  84. * verify_secret_1_malformed 
  85. * verify_secrets_missing: No longer have verification secrets stored 
  86. * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com 
  87. */ 
  88. function verify_action( $params ) { 
  89. $action = $params[0]; 
  90. $verify_secret = $params[1]; 
  91.  
  92. if ( empty( $verify_secret ) ) { 
  93. return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ) ); 
  94. } else if ( !is_string( $verify_secret ) ) { 
  95. return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ) ); 
  96.  
  97. $secrets = Jetpack_Options::get_option( $action ); 
  98. if ( !$secrets || is_wp_error( $secrets ) ) { 
  99. Jetpack_Options::delete_option( $action ); 
  100. return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) ); 
  101.  
  102. @list( $secret_1, $secret_2, $secret_eol ) = explode( ':', $secrets ); 
  103. if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) { 
  104. Jetpack_Options::delete_option( $action ); 
  105. return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) ); 
  106.  
  107. if ( $verify_secret !== $secret_1 ) { 
  108. Jetpack_Options::delete_option( $action ); 
  109. return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ) ); 
  110.  
  111. Jetpack_Options::delete_option( $action ); 
  112.  
  113. return $secret_2; 
  114.  
  115. /** 
  116. * Wrapper for wp_authenticate( $username, $password ); 
  117. * 
  118. * @return WP_User|IXR_Error 
  119. */ 
  120. function login() { 
  121. Jetpack::init()->require_jetpack_authentication(); 
  122. $user = wp_authenticate( 'username', 'password' ); 
  123. if ( is_wp_error( $user ) ) { 
  124. if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything. 
  125. $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 ); 
  126. } else { 
  127. $this->error = $user; 
  128. return false; 
  129. } else if ( !$user ) { // Shouldn't happen. 
  130. $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 ); 
  131. return false; 
  132.  
  133. return $user; 
  134.  
  135. /** 
  136. * Returns the current error as an IXR_Error 
  137. * 
  138. * @return null|IXR_Error 
  139. */ 
  140. function error( $error = null ) { 
  141. if ( !is_null( $error ) ) { 
  142. $this->error = $error; 
  143.  
  144. if ( is_wp_error( $this->error ) ) { 
  145. $code = $this->error->get_error_data(); 
  146. if ( !$code ) { 
  147. $code = -10520; 
  148. $message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() ); 
  149. return new IXR_Error( $code, $message ); 
  150. } else if ( is_a( $this->error, 'IXR_Error' ) ) { 
  151. return $this->error; 
  152.  
  153. return false; 
  154.  
  155. /** API Methods */ 
  156.  
  157. /** 
  158. * Just authenticates with the given Jetpack credentials. 
  159. * 
  160. * @return bool|IXR_Error 
  161. */ 
  162. function test_connection() { 
  163. return JETPACK__VERSION; 
  164.  
  165. function test_api_user_code( $args ) { 
  166. $client_id = (int) $args[0]; 
  167. $user_id = (int) $args[1]; 
  168. $nonce = (string) $args[2]; 
  169. $verify = (string) $args[3]; 
  170.  
  171. if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) { 
  172. return false; 
  173.  
  174. $user = get_user_by( 'id', $user_id ); 
  175. if ( !$user || is_wp_error( $user ) ) { 
  176. return false; 
  177.  
  178. /** debugging 
  179. error_log( "CLIENT: $client_id" ); 
  180. error_log( "USER: $user_id" ); 
  181. error_log( "NONCE: $nonce" ); 
  182. error_log( "VERIFY: $verify" ); 
  183. */ 
  184.  
  185. $jetpack_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER ); 
  186.  
  187. $api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true ); 
  188. if ( !$api_user_code ) { 
  189. return false; 
  190.  
  191. $hmac = hash_hmac( 'md5', json_encode( (object) array( 
  192. 'client_id' => (int) $client_id,  
  193. 'user_id' => (int) $user_id,  
  194. 'nonce' => (string) $nonce,  
  195. 'code' => (string) $api_user_code,  
  196. ) ), $jetpack_token->secret ); 
  197.  
  198. if ( $hmac !== $verify ) { 
  199. return false; 
  200.  
  201. return $user_id; 
  202.  
  203. /** 
  204. * Disconnect this blog from the connected wordpress.com account 
  205. * @return boolean 
  206. */ 
  207. function disconnect_blog() { 
  208. Jetpack::log( 'disconnect' ); 
  209. Jetpack::disconnect(); 
  210.  
  211. return true; 
  212.  
  213. /** 
  214. * Unlink a user from WordPress.com 
  215. * 
  216. * This will fail if called by the Master User. 
  217. */ 
  218. function unlink_user() { 
  219. Jetpack::log( 'unlink' ); 
  220. return Jetpack::unlink_user(); 
  221.  
  222. /** 
  223. * Returns what features are available. Uses the slug of the module files. 
  224. * 
  225. * @return array|IXR_Error 
  226. */ 
  227. function features_available() { 
  228. $raw_modules = Jetpack::get_available_modules(); 
  229. $modules = array(); 
  230. foreach ( $raw_modules as $module ) { 
  231. $modules[] = Jetpack::get_module_slug( $module ); 
  232.  
  233. return $modules; 
  234.  
  235. /** 
  236. * Returns what features are enabled. Uses the slug of the modules files. 
  237. * 
  238. * @return array|IXR_Error 
  239. */ 
  240. function features_enabled() { 
  241. $raw_modules = Jetpack::get_active_modules(); 
  242. $modules = array(); 
  243. foreach ( $raw_modules as $module ) { 
  244. $modules[] = Jetpack::get_module_slug( $module ); 
  245.  
  246. return $modules; 
  247.  
  248. function get_post( $id ) { 
  249. if ( !$id = (int) $id ) { 
  250. return false; 
  251.  
  252. $jetpack = Jetpack::init(); 
  253.  
  254. $post = $jetpack->sync->get_post( $id ); 
  255. return $post; 
  256.  
  257. function get_posts( $args ) { 
  258. list( $post_ids ) = $args; 
  259. $post_ids = array_map( 'intval', (array) $post_ids ); 
  260. $jp = Jetpack::init(); 
  261. $sync_data = $jp->sync->get_content( array( 'posts' => $post_ids ) ); 
  262.  
  263. return $sync_data; 
  264.  
  265. function get_comment( $id ) { 
  266. if ( !$id = (int) $id ) { 
  267. return false; 
  268.  
  269. $jetpack = Jetpack::init(); 
  270.  
  271. $comment = $jetpack->sync->get_comment( $id ); 
  272. if ( !is_array( $comment ) ) 
  273. return false; 
  274.  
  275. $post = $jetpack->sync->get_post( $comment['comment_post_ID'] ); 
  276. if ( !$post ) { 
  277. return false; 
  278.  
  279. return $comment; 
  280.  
  281. function get_comments( $args ) { 
  282. list( $comment_ids ) = $args; 
  283. $comment_ids = array_map( 'intval', (array) $comment_ids ); 
  284. $jp = Jetpack::init(); 
  285. $sync_data = $jp->sync->get_content( array( 'comments' => $comment_ids ) ); 
  286.  
  287. return $sync_data; 
  288.  
  289. function update_attachment_parent( $args ) { 
  290. $attachment_id = (int) $args[0]; 
  291. $parent_id = (int) $args[1]; 
  292.  
  293. return wp_update_post( array( 
  294. 'ID' => $attachment_id,  
  295. 'post_parent' => $parent_id,  
  296. ) ); 
  297.  
  298. function json_api( $args = array() ) { 
  299. $json_api_args = $args[0]; 
  300. $verify_api_user_args = $args[1]; 
  301.  
  302. $method = (string) $json_api_args[0]; 
  303. $url = (string) $json_api_args[1]; 
  304. $post_body = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2]; 
  305. $user_details = (array) $json_api_args[4]; 
  306. $locale = (string) $json_api_args[5]; 
  307.  
  308. if ( !$verify_api_user_args ) { 
  309. $user_id = 0; 
  310. } elseif ( 'internal' === $verify_api_user_args[0] ) { 
  311. $user_id = (int) $verify_api_user_args[1]; 
  312. if ( $user_id ) { 
  313. $user = get_user_by( 'id', $user_id ); 
  314. if ( !$user || is_wp_error( $user ) ) { 
  315. return false; 
  316. } else { 
  317. $user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args ); 
  318. if ( !$user_id ) { 
  319. return false; 
  320.  
  321. /** debugging 
  322. error_log( "-- begin json api via jetpack debugging -- " ); 
  323. error_log( "METHOD: $method" ); 
  324. error_log( "URL: $url" ); 
  325. error_log( "POST BODY: $post_body" ); 
  326. error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) ); 
  327. error_log( "VERIFIED USER_ID: " . (int) $user_id ); 
  328. error_log( "-- end json api via jetpack debugging -- " ); 
  329. */ 
  330.  
  331. if ( 'en' !== $locale ) { 
  332. // .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them. 
  333. $new_locale = $locale; 
  334. if ( strpos( $locale, '-' ) !== false ) { 
  335. $pieces = explode( '-', $locale ); 
  336. $new_locale = $locale_pieces[0]; 
  337. $new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : ''; 
  338. } else { 
  339. // .com might pass 'fr' because thats what our language files are named as, where core seems 
  340. // to do fr_FR - so try that if we don't think we can load the file. 
  341. if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) { 
  342. $new_locale = $locale . '_' . strtoupper( $locale ); 
  343.  
  344. if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) { 
  345. unload_textdomain( 'default' ); 
  346. load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' ); 
  347.  
  348. $old_user = wp_get_current_user(); 
  349. wp_set_current_user( $user_id ); 
  350.  
  351. $token = Jetpack_Data::get_access_token( get_current_user_id() ); 
  352. if ( !$token || is_wp_error( $token ) ) { 
  353. return false; 
  354.  
  355. define( 'REST_API_REQUEST', true ); 
  356. define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); 
  357.  
  358. // needed? 
  359. require_once ABSPATH . 'wp-admin/includes/admin.php'; 
  360.  
  361. require_once JETPACK__PLUGIN_DIR . 'class.json-api.php'; 
  362. $api = WPCOM_JSON_API::init( $method, $url, $post_body ); 
  363. $api->token_details['user'] = $user_details; 
  364. require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; 
  365.  
  366. $display_errors = ini_set( 'display_errors', 0 ); 
  367. ob_start(); 
  368. $content_type = $api->serve( false ); 
  369. $output = ob_get_clean(); 
  370. ini_set( 'display_errors', $display_errors ); 
  371.  
  372. $nonce = wp_generate_password( 10, false ); 
  373. $hmac = hash_hmac( 'md5', $nonce . $output, $token->secret ); 
  374.  
  375. wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 ); 
  376.  
  377. return array( 
  378. (string) $output,  
  379. (string) $nonce,  
  380. (string) $hmac,  
  381. ); 
.