PageRenderTime 64ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/class-wp-xmlrpc-server.php

https://bitbucket.org/abnopanda/wordpress
PHP | 5560 lines | 3409 code | 908 blank | 1243 comment | 608 complexity | a5f03f63cc98eb2e47c69b5f9b5e3a3c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * XML-RPC protocol support for WordPress
  4. *
  5. * @package WordPress
  6. */
  7. /**
  8. * WordPress XMLRPC server implementation.
  9. *
  10. * Implements compatibility for Blogger API, MetaWeblog API, MovableType, and
  11. * pingback. Additional WordPress API for managing comments, pages, posts,
  12. * options, etc.
  13. *
  14. * Since WordPress 2.6.0, WordPress XMLRPC server can be disabled in the
  15. * administration panels.
  16. *
  17. * @package WordPress
  18. * @subpackage Publishing
  19. * @since 1.5.0
  20. */
  21. class wp_xmlrpc_server extends IXR_Server {
  22. /**
  23. * Register all of the XMLRPC methods that XMLRPC server understands.
  24. *
  25. * Sets up server and method property. Passes XMLRPC
  26. * methods through the 'xmlrpc_methods' filter to allow plugins to extend
  27. * or replace XMLRPC methods.
  28. *
  29. * @since 1.5.0
  30. *
  31. * @return wp_xmlrpc_server
  32. */
  33. function __construct() {
  34. $this->methods = array(
  35. // WordPress API
  36. 'wp.getUsersBlogs' => 'this:wp_getUsersBlogs',
  37. 'wp.newPost' => 'this:wp_newPost',
  38. 'wp.editPost' => 'this:wp_editPost',
  39. 'wp.deletePost' => 'this:wp_deletePost',
  40. 'wp.getPost' => 'this:wp_getPost',
  41. 'wp.getPosts' => 'this:wp_getPosts',
  42. 'wp.newTerm' => 'this:wp_newTerm',
  43. 'wp.editTerm' => 'this:wp_editTerm',
  44. 'wp.deleteTerm' => 'this:wp_deleteTerm',
  45. 'wp.getTerm' => 'this:wp_getTerm',
  46. 'wp.getTerms' => 'this:wp_getTerms',
  47. 'wp.getTaxonomy' => 'this:wp_getTaxonomy',
  48. 'wp.getTaxonomies' => 'this:wp_getTaxonomies',
  49. 'wp.getUser' => 'this:wp_getUser',
  50. 'wp.getUsers' => 'this:wp_getUsers',
  51. 'wp.getProfile' => 'this:wp_getProfile',
  52. 'wp.editProfile' => 'this:wp_editProfile',
  53. 'wp.getPage' => 'this:wp_getPage',
  54. 'wp.getPages' => 'this:wp_getPages',
  55. 'wp.newPage' => 'this:wp_newPage',
  56. 'wp.deletePage' => 'this:wp_deletePage',
  57. 'wp.editPage' => 'this:wp_editPage',
  58. 'wp.getPageList' => 'this:wp_getPageList',
  59. 'wp.getAuthors' => 'this:wp_getAuthors',
  60. 'wp.getCategories' => 'this:mw_getCategories', // Alias
  61. 'wp.getTags' => 'this:wp_getTags',
  62. 'wp.newCategory' => 'this:wp_newCategory',
  63. 'wp.deleteCategory' => 'this:wp_deleteCategory',
  64. 'wp.suggestCategories' => 'this:wp_suggestCategories',
  65. 'wp.uploadFile' => 'this:mw_newMediaObject', // Alias
  66. 'wp.getCommentCount' => 'this:wp_getCommentCount',
  67. 'wp.getPostStatusList' => 'this:wp_getPostStatusList',
  68. 'wp.getPageStatusList' => 'this:wp_getPageStatusList',
  69. 'wp.getPageTemplates' => 'this:wp_getPageTemplates',
  70. 'wp.getOptions' => 'this:wp_getOptions',
  71. 'wp.setOptions' => 'this:wp_setOptions',
  72. 'wp.getComment' => 'this:wp_getComment',
  73. 'wp.getComments' => 'this:wp_getComments',
  74. 'wp.deleteComment' => 'this:wp_deleteComment',
  75. 'wp.editComment' => 'this:wp_editComment',
  76. 'wp.newComment' => 'this:wp_newComment',
  77. 'wp.getCommentStatusList' => 'this:wp_getCommentStatusList',
  78. 'wp.getMediaItem' => 'this:wp_getMediaItem',
  79. 'wp.getMediaLibrary' => 'this:wp_getMediaLibrary',
  80. 'wp.getPostFormats' => 'this:wp_getPostFormats',
  81. 'wp.getPostType' => 'this:wp_getPostType',
  82. 'wp.getPostTypes' => 'this:wp_getPostTypes',
  83. 'wp.getRevisions' => 'this:wp_getRevisions',
  84. 'wp.restoreRevision' => 'this:wp_restoreRevision',
  85. // Blogger API
  86. 'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
  87. 'blogger.getUserInfo' => 'this:blogger_getUserInfo',
  88. 'blogger.getPost' => 'this:blogger_getPost',
  89. 'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
  90. 'blogger.getTemplate' => 'this:blogger_getTemplate',
  91. 'blogger.setTemplate' => 'this:blogger_setTemplate',
  92. 'blogger.newPost' => 'this:blogger_newPost',
  93. 'blogger.editPost' => 'this:blogger_editPost',
  94. 'blogger.deletePost' => 'this:blogger_deletePost',
  95. // MetaWeblog API (with MT extensions to structs)
  96. 'metaWeblog.newPost' => 'this:mw_newPost',
  97. 'metaWeblog.editPost' => 'this:mw_editPost',
  98. 'metaWeblog.getPost' => 'this:mw_getPost',
  99. 'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
  100. 'metaWeblog.getCategories' => 'this:mw_getCategories',
  101. 'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
  102. // MetaWeblog API aliases for Blogger API
  103. // see http://www.xmlrpc.com/stories/storyReader$2460
  104. 'metaWeblog.deletePost' => 'this:blogger_deletePost',
  105. 'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
  106. 'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
  107. 'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
  108. // MovableType API
  109. 'mt.getCategoryList' => 'this:mt_getCategoryList',
  110. 'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
  111. 'mt.getPostCategories' => 'this:mt_getPostCategories',
  112. 'mt.setPostCategories' => 'this:mt_setPostCategories',
  113. 'mt.supportedMethods' => 'this:mt_supportedMethods',
  114. 'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
  115. 'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
  116. 'mt.publishPost' => 'this:mt_publishPost',
  117. // PingBack
  118. 'pingback.ping' => 'this:pingback_ping',
  119. 'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
  120. 'demo.sayHello' => 'this:sayHello',
  121. 'demo.addTwoNumbers' => 'this:addTwoNumbers'
  122. );
  123. $this->initialise_blog_option_info();
  124. $this->methods = apply_filters('xmlrpc_methods', $this->methods);
  125. }
  126. function serve_request() {
  127. $this->IXR_Server($this->methods);
  128. }
  129. /**
  130. * Test XMLRPC API by saying, "Hello!" to client.
  131. *
  132. * @since 1.5.0
  133. *
  134. * @param array $args Method Parameters.
  135. * @return string
  136. */
  137. function sayHello($args) {
  138. return 'Hello!';
  139. }
  140. /**
  141. * Test XMLRPC API by adding two numbers for client.
  142. *
  143. * @since 1.5.0
  144. *
  145. * @param array $args Method Parameters.
  146. * @return int
  147. */
  148. function addTwoNumbers($args) {
  149. $number1 = $args[0];
  150. $number2 = $args[1];
  151. return $number1 + $number2;
  152. }
  153. /**
  154. * Log user in.
  155. *
  156. * @since 2.8.0
  157. *
  158. * @param string $username User's username.
  159. * @param string $password User's password.
  160. * @return mixed WP_User object if authentication passed, false otherwise
  161. */
  162. function login( $username, $password ) {
  163. // Respect any old filters against get_option() for 'enable_xmlrpc'.
  164. $enabled = apply_filters( 'pre_option_enable_xmlrpc', false ); // Deprecated
  165. if ( false === $enabled )
  166. $enabled = apply_filters( 'option_enable_xmlrpc', true ); // Deprecated
  167. // Proper filter for turning off XML-RPC. It is on by default.
  168. $enabled = apply_filters( 'xmlrpc_enabled', $enabled );
  169. if ( ! $enabled ) {
  170. $this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site.' ) ) );
  171. return false;
  172. }
  173. $user = wp_authenticate($username, $password);
  174. if (is_wp_error($user)) {
  175. $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
  176. $this->error = apply_filters( 'xmlrpc_login_error', $this->error, $user );
  177. return false;
  178. }
  179. wp_set_current_user( $user->ID );
  180. return $user;
  181. }
  182. /**
  183. * Check user's credentials. Deprecated.
  184. *
  185. * @since 1.5.0
  186. * @deprecated 2.8.0
  187. * @deprecated use wp_xmlrpc_server::login
  188. * @see wp_xmlrpc_server::login
  189. *
  190. * @param string $username User's username.
  191. * @param string $password User's password.
  192. * @return bool Whether authentication passed.
  193. */
  194. function login_pass_ok( $username, $password ) {
  195. return (bool) $this->login( $username, $password );
  196. }
  197. /**
  198. * Sanitize string or array of strings for database.
  199. *
  200. * @since 1.5.2
  201. *
  202. * @param string|array $array Sanitize single string or array of strings.
  203. * @return string|array Type matches $array and sanitized for the database.
  204. */
  205. function escape(&$array) {
  206. global $wpdb;
  207. if (!is_array($array)) {
  208. return($wpdb->escape($array));
  209. } else {
  210. foreach ( (array) $array as $k => $v ) {
  211. if ( is_array($v) ) {
  212. $this->escape($array[$k]);
  213. } else if ( is_object($v) ) {
  214. //skip
  215. } else {
  216. $array[$k] = $wpdb->escape($v);
  217. }
  218. }
  219. }
  220. }
  221. /**
  222. * Retrieve custom fields for post.
  223. *
  224. * @since 2.5.0
  225. *
  226. * @param int $post_id Post ID.
  227. * @return array Custom fields, if exist.
  228. */
  229. function get_custom_fields($post_id) {
  230. $post_id = (int) $post_id;
  231. $custom_fields = array();
  232. foreach ( (array) has_meta($post_id) as $meta ) {
  233. // Don't expose protected fields.
  234. if ( ! current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
  235. continue;
  236. $custom_fields[] = array(
  237. "id" => $meta['meta_id'],
  238. "key" => $meta['meta_key'],
  239. "value" => $meta['meta_value']
  240. );
  241. }
  242. return $custom_fields;
  243. }
  244. /**
  245. * Set custom fields for post.
  246. *
  247. * @since 2.5.0
  248. *
  249. * @param int $post_id Post ID.
  250. * @param array $fields Custom fields.
  251. */
  252. function set_custom_fields($post_id, $fields) {
  253. $post_id = (int) $post_id;
  254. foreach ( (array) $fields as $meta ) {
  255. if ( isset($meta['id']) ) {
  256. $meta['id'] = (int) $meta['id'];
  257. $pmeta = get_metadata_by_mid( 'post', $meta['id'] );
  258. if ( isset($meta['key']) ) {
  259. $meta['key'] = stripslashes( $meta['key'] );
  260. if ( $meta['key'] != $pmeta->meta_key )
  261. continue;
  262. $meta['value'] = stripslashes_deep( $meta['value'] );
  263. if ( current_user_can( 'edit_post_meta', $post_id, $meta['key'] ) )
  264. update_metadata_by_mid( 'post', $meta['id'], $meta['value'] );
  265. } elseif ( current_user_can( 'delete_post_meta', $post_id, $pmeta->meta_key ) ) {
  266. delete_metadata_by_mid( 'post', $meta['id'] );
  267. }
  268. } elseif ( current_user_can( 'add_post_meta', $post_id, stripslashes( $meta['key'] ) ) ) {
  269. add_post_meta( $post_id, $meta['key'], $meta['value'] );
  270. }
  271. }
  272. }
  273. /**
  274. * Set up blog options property.
  275. *
  276. * Passes property through 'xmlrpc_blog_options' filter.
  277. *
  278. * @since 2.6.0
  279. */
  280. function initialise_blog_option_info() {
  281. global $wp_version;
  282. $this->blog_options = array(
  283. // Read only options
  284. 'software_name' => array(
  285. 'desc' => __( 'Software Name' ),
  286. 'readonly' => true,
  287. 'value' => 'WordPress'
  288. ),
  289. 'software_version' => array(
  290. 'desc' => __( 'Software Version' ),
  291. 'readonly' => true,
  292. 'value' => $wp_version
  293. ),
  294. 'blog_url' => array(
  295. 'desc' => __( 'Site URL' ),
  296. 'readonly' => true,
  297. 'option' => 'siteurl'
  298. ),
  299. 'home_url' => array(
  300. 'desc' => __( 'Home URL' ),
  301. 'readonly' => true,
  302. 'option' => 'home'
  303. ),
  304. 'image_default_link_type' => array(
  305. 'desc' => __( 'Image default link type' ),
  306. 'readonly' => true,
  307. 'option' => 'image_default_link_type'
  308. ),
  309. 'image_default_size' => array(
  310. 'desc' => __( 'Image default size' ),
  311. 'readonly' => true,
  312. 'option' => 'image_default_size'
  313. ),
  314. 'image_default_align' => array(
  315. 'desc' => __( 'Image default align' ),
  316. 'readonly' => true,
  317. 'option' => 'image_default_align'
  318. ),
  319. 'template' => array(
  320. 'desc' => __( 'Template' ),
  321. 'readonly' => true,
  322. 'option' => 'template'
  323. ),
  324. 'stylesheet' => array(
  325. 'desc' => __( 'Stylesheet' ),
  326. 'readonly' => true,
  327. 'option' => 'stylesheet'
  328. ),
  329. 'post_thumbnail' => array(
  330. 'desc' => __('Post Thumbnail'),
  331. 'readonly' => true,
  332. 'value' => current_theme_supports( 'post-thumbnails' )
  333. ),
  334. // Updatable options
  335. 'time_zone' => array(
  336. 'desc' => __( 'Time Zone' ),
  337. 'readonly' => false,
  338. 'option' => 'gmt_offset'
  339. ),
  340. 'blog_title' => array(
  341. 'desc' => __( 'Site Title' ),
  342. 'readonly' => false,
  343. 'option' => 'blogname'
  344. ),
  345. 'blog_tagline' => array(
  346. 'desc' => __( 'Site Tagline' ),
  347. 'readonly' => false,
  348. 'option' => 'blogdescription'
  349. ),
  350. 'date_format' => array(
  351. 'desc' => __( 'Date Format' ),
  352. 'readonly' => false,
  353. 'option' => 'date_format'
  354. ),
  355. 'time_format' => array(
  356. 'desc' => __( 'Time Format' ),
  357. 'readonly' => false,
  358. 'option' => 'time_format'
  359. ),
  360. 'users_can_register' => array(
  361. 'desc' => __( 'Allow new users to sign up' ),
  362. 'readonly' => false,
  363. 'option' => 'users_can_register'
  364. ),
  365. 'thumbnail_size_w' => array(
  366. 'desc' => __( 'Thumbnail Width' ),
  367. 'readonly' => false,
  368. 'option' => 'thumbnail_size_w'
  369. ),
  370. 'thumbnail_size_h' => array(
  371. 'desc' => __( 'Thumbnail Height' ),
  372. 'readonly' => false,
  373. 'option' => 'thumbnail_size_h'
  374. ),
  375. 'thumbnail_crop' => array(
  376. 'desc' => __( 'Crop thumbnail to exact dimensions' ),
  377. 'readonly' => false,
  378. 'option' => 'thumbnail_crop'
  379. ),
  380. 'medium_size_w' => array(
  381. 'desc' => __( 'Medium size image width' ),
  382. 'readonly' => false,
  383. 'option' => 'medium_size_w'
  384. ),
  385. 'medium_size_h' => array(
  386. 'desc' => __( 'Medium size image height' ),
  387. 'readonly' => false,
  388. 'option' => 'medium_size_h'
  389. ),
  390. 'large_size_w' => array(
  391. 'desc' => __( 'Large size image width' ),
  392. 'readonly' => false,
  393. 'option' => 'large_size_w'
  394. ),
  395. 'large_size_h' => array(
  396. 'desc' => __( 'Large size image height' ),
  397. 'readonly' => false,
  398. 'option' => 'large_size_h'
  399. ),
  400. 'default_comment_status' => array(
  401. 'desc' => __( 'Allow people to post comments on new articles' ),
  402. 'readonly' => false,
  403. 'option' => 'default_comment_status'
  404. ),
  405. 'default_ping_status' => array(
  406. 'desc' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks)' ),
  407. 'readonly' => false,
  408. 'option' => 'default_ping_status'
  409. )
  410. );
  411. $this->blog_options = apply_filters( 'xmlrpc_blog_options', $this->blog_options );
  412. }
  413. /**
  414. * Retrieve the blogs of the user.
  415. *
  416. * @since 2.6.0
  417. *
  418. * @param array $args Method parameters. Contains:
  419. * - username
  420. * - password
  421. * @return array. Contains:
  422. * - 'isAdmin'
  423. * - 'url'
  424. * - 'blogid'
  425. * - 'blogName'
  426. * - 'xmlrpc' - url of xmlrpc endpoint
  427. */
  428. function wp_getUsersBlogs( $args ) {
  429. global $current_site;
  430. // If this isn't on WPMU then just use blogger_getUsersBlogs
  431. if ( !is_multisite() ) {
  432. array_unshift( $args, 1 );
  433. return $this->blogger_getUsersBlogs( $args );
  434. }
  435. $this->escape( $args );
  436. $username = $args[0];
  437. $password = $args[1];
  438. if ( !$user = $this->login($username, $password) )
  439. return $this->error;
  440. do_action( 'xmlrpc_call', 'wp.getUsersBlogs' );
  441. $blogs = (array) get_blogs_of_user( $user->ID );
  442. $struct = array();
  443. foreach ( $blogs as $blog ) {
  444. // Don't include blogs that aren't hosted at this site
  445. if ( $blog->site_id != $current_site->id )
  446. continue;
  447. $blog_id = $blog->userblog_id;
  448. switch_to_blog( $blog_id );
  449. $is_admin = current_user_can( 'manage_options' );
  450. $struct[] = array(
  451. 'isAdmin' => $is_admin,
  452. 'url' => home_url( '/' ),
  453. 'blogid' => (string) $blog_id,
  454. 'blogName' => get_option( 'blogname' ),
  455. 'xmlrpc' => site_url( 'xmlrpc.php', 'rpc' ),
  456. );
  457. restore_current_blog();
  458. }
  459. return $struct;
  460. }
  461. /**
  462. * Checks if the method received at least the minimum number of arguments.
  463. *
  464. * @since 3.4.0
  465. *
  466. * @param string|array $args Sanitize single string or array of strings.
  467. * @param int $count Minimum number of arguments.
  468. * @return boolean if $args contains at least $count arguments.
  469. */
  470. protected function minimum_args( $args, $count ) {
  471. if ( count( $args ) < $count ) {
  472. $this->error = new IXR_Error( 400, __( 'Insufficient arguments passed to this XML-RPC method.' ) );
  473. return false;
  474. }
  475. return true;
  476. }
  477. /**
  478. * Prepares taxonomy data for return in an XML-RPC object.
  479. *
  480. * @access protected
  481. *
  482. * @param object $taxonomy The unprepared taxonomy data
  483. * @param array $fields The subset of taxonomy fields to return
  484. * @return array The prepared taxonomy data
  485. */
  486. protected function _prepare_taxonomy( $taxonomy, $fields ) {
  487. $_taxonomy = array(
  488. 'name' => $taxonomy->name,
  489. 'label' => $taxonomy->label,
  490. 'hierarchical' => (bool) $taxonomy->hierarchical,
  491. 'public' => (bool) $taxonomy->public,
  492. 'show_ui' => (bool) $taxonomy->show_ui,
  493. '_builtin' => (bool) $taxonomy->_builtin,
  494. );
  495. if ( in_array( 'labels', $fields ) )
  496. $_taxonomy['labels'] = (array) $taxonomy->labels;
  497. if ( in_array( 'cap', $fields ) )
  498. $_taxonomy['cap'] = (array) $taxonomy->cap;
  499. if ( in_array( 'object_type', $fields ) )
  500. $_taxonomy['object_type'] = array_unique( (array) $taxonomy->object_type );
  501. return apply_filters( 'xmlrpc_prepare_taxonomy', $_taxonomy, $taxonomy, $fields );
  502. }
  503. /**
  504. * Prepares term data for return in an XML-RPC object.
  505. *
  506. * @access protected
  507. *
  508. * @param array|object $term The unprepared term data
  509. * @return array The prepared term data
  510. */
  511. protected function _prepare_term( $term ) {
  512. $_term = $term;
  513. if ( ! is_array( $_term) )
  514. $_term = get_object_vars( $_term );
  515. // For Intergers which may be largeer than XMLRPC supports ensure we return strings.
  516. $_term['term_id'] = strval( $_term['term_id'] );
  517. $_term['term_group'] = strval( $_term['term_group'] );
  518. $_term['term_taxonomy_id'] = strval( $_term['term_taxonomy_id'] );
  519. $_term['parent'] = strval( $_term['parent'] );
  520. // Count we are happy to return as an Integer because people really shouldn't use Terms that much.
  521. $_term['count'] = intval( $_term['count'] );
  522. return apply_filters( 'xmlrpc_prepare_term', $_term, $term );
  523. }
  524. /**
  525. * Convert a WordPress date string to an IXR_Date object.
  526. *
  527. * @access protected
  528. *
  529. * @param string $date
  530. * @return IXR_Date
  531. */
  532. protected function _convert_date( $date ) {
  533. if ( $date === '0000-00-00 00:00:00' ) {
  534. return new IXR_Date( '00000000T00:00:00Z' );
  535. }
  536. return new IXR_Date( mysql2date( 'Ymd\TH:i:s', $date, false ) );
  537. }
  538. /**
  539. * Convert a WordPress GMT date string to an IXR_Date object.
  540. *
  541. * @access protected
  542. *
  543. * @param string $date_gmt
  544. * @param string $date
  545. * @return IXR_Date
  546. */
  547. protected function _convert_date_gmt( $date_gmt, $date ) {
  548. if ( $date !== '0000-00-00 00:00:00' && $date_gmt === '0000-00-00 00:00:00' ) {
  549. return new IXR_Date( get_gmt_from_date( mysql2date( 'Y-m-d H:i:s', $date, false ), 'Ymd\TH:i:s' ) );
  550. }
  551. return $this->_convert_date( $date_gmt );
  552. }
  553. /**
  554. * Prepares post data for return in an XML-RPC object.
  555. *
  556. * @access protected
  557. *
  558. * @param array $post The unprepared post data
  559. * @param array $fields The subset of post type fields to return
  560. * @return array The prepared post data
  561. */
  562. protected function _prepare_post( $post, $fields ) {
  563. // holds the data for this post. built up based on $fields
  564. $_post = array( 'post_id' => strval( $post['ID'] ) );
  565. // prepare common post fields
  566. $post_fields = array(
  567. 'post_title' => $post['post_title'],
  568. 'post_date' => $this->_convert_date( $post['post_date'] ),
  569. 'post_date_gmt' => $this->_convert_date_gmt( $post['post_date_gmt'], $post['post_date'] ),
  570. 'post_modified' => $this->_convert_date( $post['post_modified'] ),
  571. 'post_modified_gmt' => $this->_convert_date_gmt( $post['post_modified_gmt'], $post['post_modified'] ),
  572. 'post_status' => $post['post_status'],
  573. 'post_type' => $post['post_type'],
  574. 'post_name' => $post['post_name'],
  575. 'post_author' => $post['post_author'],
  576. 'post_password' => $post['post_password'],
  577. 'post_excerpt' => $post['post_excerpt'],
  578. 'post_content' => $post['post_content'],
  579. 'post_parent' => strval( $post['post_parent'] ),
  580. 'post_mime_type' => $post['post_mime_type'],
  581. 'link' => post_permalink( $post['ID'] ),
  582. 'guid' => $post['guid'],
  583. 'menu_order' => intval( $post['menu_order'] ),
  584. 'comment_status' => $post['comment_status'],
  585. 'ping_status' => $post['ping_status'],
  586. 'sticky' => ( $post['post_type'] === 'post' && is_sticky( $post['ID'] ) ),
  587. );
  588. // Thumbnail
  589. $post_fields['post_thumbnail'] = array();
  590. $thumbnail_id = get_post_thumbnail_id( $post['ID'] );
  591. if ( $thumbnail_id ) {
  592. $thumbnail_size = current_theme_supports('post-thumbnail') ? 'post-thumbnail' : 'thumbnail';
  593. $post_fields['post_thumbnail'] = $this->_prepare_media_item( get_post( $thumbnail_id ), $thumbnail_size );
  594. }
  595. // Consider future posts as published
  596. if ( $post_fields['post_status'] === 'future' )
  597. $post_fields['post_status'] = 'publish';
  598. // Fill in blank post format
  599. $post_fields['post_format'] = get_post_format( $post['ID'] );
  600. if ( empty( $post_fields['post_format'] ) )
  601. $post_fields['post_format'] = 'standard';
  602. // Merge requested $post_fields fields into $_post
  603. if ( in_array( 'post', $fields ) ) {
  604. $_post = array_merge( $_post, $post_fields );
  605. } else {
  606. $requested_fields = array_intersect_key( $post_fields, array_flip( $fields ) );
  607. $_post = array_merge( $_post, $requested_fields );
  608. }
  609. $all_taxonomy_fields = in_array( 'taxonomies', $fields );
  610. if ( $all_taxonomy_fields || in_array( 'terms', $fields ) ) {
  611. $post_type_taxonomies = get_object_taxonomies( $post['post_type'], 'names' );
  612. $terms = wp_get_object_terms( $post['ID'], $post_type_taxonomies );
  613. $_post['terms'] = array();
  614. foreach ( $terms as $term ) {
  615. $_post['terms'][] = $this->_prepare_term( $term );
  616. }
  617. }
  618. if ( in_array( 'custom_fields', $fields ) )
  619. $_post['custom_fields'] = $this->get_custom_fields( $post['ID'] );
  620. if ( in_array( 'enclosure', $fields ) ) {
  621. $_post['enclosure'] = array();
  622. $enclosures = (array) get_post_meta( $post['ID'], 'enclosure' );
  623. if ( ! empty( $enclosures ) ) {
  624. $encdata = explode( "\n", $enclosures[0] );
  625. $_post['enclosure']['url'] = trim( htmlspecialchars( $encdata[0] ) );
  626. $_post['enclosure']['length'] = (int) trim( $encdata[1] );
  627. $_post['enclosure']['type'] = trim( $encdata[2] );
  628. }
  629. }
  630. return apply_filters( 'xmlrpc_prepare_post', $_post, $post, $fields );
  631. }
  632. /**
  633. * Prepares post data for return in an XML-RPC object.
  634. *
  635. * @access protected
  636. *
  637. * @param object $post_type Post type object
  638. * @param array $fields The subset of post fields to return
  639. * @return array The prepared post type data
  640. */
  641. protected function _prepare_post_type( $post_type, $fields ) {
  642. $_post_type = array(
  643. 'name' => $post_type->name,
  644. 'label' => $post_type->label,
  645. 'hierarchical' => (bool) $post_type->hierarchical,
  646. 'public' => (bool) $post_type->public,
  647. 'show_ui' => (bool) $post_type->show_ui,
  648. '_builtin' => (bool) $post_type->_builtin,
  649. 'has_archive' => (bool) $post_type->has_archive,
  650. 'supports' => get_all_post_type_supports( $post_type->name ),
  651. );
  652. if ( in_array( 'labels', $fields ) ) {
  653. $_post_type['labels'] = (array) $post_type->labels;
  654. }
  655. if ( in_array( 'cap', $fields ) ) {
  656. $_post_type['cap'] = (array) $post_type->cap;
  657. $_post_type['map_meta_cap'] = (bool) $post_type->map_meta_cap;
  658. }
  659. if ( in_array( 'menu', $fields ) ) {
  660. $_post_type['menu_position'] = (int) $post_type->menu_position;
  661. $_post_type['menu_icon'] = $post_type->menu_icon;
  662. $_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
  663. }
  664. if ( in_array( 'taxonomies', $fields ) )
  665. $_post_type['taxonomies'] = get_object_taxonomies( $post_type->name, 'names' );
  666. return apply_filters( 'xmlrpc_prepare_post_type', $_post_type, $post_type );
  667. }
  668. /**
  669. * Prepares media item data for return in an XML-RPC object.
  670. *
  671. * @access protected
  672. *
  673. * @param object $media_item The unprepared media item data
  674. * @param string $thumbnail_size The image size to use for the thumbnail URL
  675. * @return array The prepared media item data
  676. */
  677. protected function _prepare_media_item( $media_item, $thumbnail_size = 'thumbnail' ) {
  678. $_media_item = array(
  679. 'attachment_id' => strval( $media_item->ID ),
  680. 'date_created_gmt' => $this->_convert_date_gmt( $media_item->post_date_gmt, $media_item->post_date ),
  681. 'parent' => $media_item->post_parent,
  682. 'link' => wp_get_attachment_url( $media_item->ID ),
  683. 'title' => $media_item->post_title,
  684. 'caption' => $media_item->post_excerpt,
  685. 'description' => $media_item->post_content,
  686. 'metadata' => wp_get_attachment_metadata( $media_item->ID ),
  687. );
  688. $thumbnail_src = image_downsize( $media_item->ID, $thumbnail_size );
  689. if ( $thumbnail_src )
  690. $_media_item['thumbnail'] = $thumbnail_src[0];
  691. else
  692. $_media_item['thumbnail'] = $_media_item['link'];
  693. return apply_filters( 'xmlrpc_prepare_media_item', $_media_item, $media_item, $thumbnail_size );
  694. }
  695. /**
  696. * Prepares page data for return in an XML-RPC object.
  697. *
  698. * @access protected
  699. *
  700. * @param object $page The unprepared page data
  701. * @return array The prepared page data
  702. */
  703. protected function _prepare_page( $page ) {
  704. // Get all of the page content and link.
  705. $full_page = get_extended( $page->post_content );
  706. $link = post_permalink( $page->ID );
  707. // Get info the page parent if there is one.
  708. $parent_title = "";
  709. if ( ! empty( $page->post_parent ) ) {
  710. $parent = get_post( $page->post_parent );
  711. $parent_title = $parent->post_title;
  712. }
  713. // Determine comment and ping settings.
  714. $allow_comments = comments_open( $page->ID ) ? 1 : 0;
  715. $allow_pings = pings_open( $page->ID ) ? 1 : 0;
  716. // Format page date.
  717. $page_date = $this->_convert_date( $page->post_date );
  718. $page_date_gmt = $this->_convert_date_gmt( $page->post_date_gmt, $page->post_date );
  719. // Pull the categories info together.
  720. $categories = array();
  721. foreach ( wp_get_post_categories( $page->ID ) as $cat_id ) {
  722. $categories[] = get_cat_name( $cat_id );
  723. }
  724. // Get the author info.
  725. $author = get_userdata( $page->post_author );
  726. $page_template = get_page_template_slug( $page->ID );
  727. if ( empty( $page_template ) )
  728. $page_template = 'default';
  729. $_page = array(
  730. 'dateCreated' => $page_date,
  731. 'userid' => $page->post_author,
  732. 'page_id' => $page->ID,
  733. 'page_status' => $page->post_status,
  734. 'description' => $full_page['main'],
  735. 'title' => $page->post_title,
  736. 'link' => $link,
  737. 'permaLink' => $link,
  738. 'categories' => $categories,
  739. 'excerpt' => $page->post_excerpt,
  740. 'text_more' => $full_page['extended'],
  741. 'mt_allow_comments' => $allow_comments,
  742. 'mt_allow_pings' => $allow_pings,
  743. 'wp_slug' => $page->post_name,
  744. 'wp_password' => $page->post_password,
  745. 'wp_author' => $author->display_name,
  746. 'wp_page_parent_id' => $page->post_parent,
  747. 'wp_page_parent_title' => $parent_title,
  748. 'wp_page_order' => $page->menu_order,
  749. 'wp_author_id' => (string) $author->ID,
  750. 'wp_author_display_name' => $author->display_name,
  751. 'date_created_gmt' => $page_date_gmt,
  752. 'custom_fields' => $this->get_custom_fields( $page->ID ),
  753. 'wp_page_template' => $page_template
  754. );
  755. return apply_filters( 'xmlrpc_prepare_page', $_page, $page );
  756. }
  757. /**
  758. * Prepares comment data for return in an XML-RPC object.
  759. *
  760. * @access protected
  761. *
  762. * @param object $comment The unprepared comment data
  763. * @return array The prepared comment data
  764. */
  765. protected function _prepare_comment( $comment ) {
  766. // Format page date.
  767. $comment_date = $this->_convert_date( $comment->comment_date );
  768. $comment_date_gmt = $this->_convert_date_gmt( $comment->comment_date_gmt, $comment->comment_date );
  769. if ( '0' == $comment->comment_approved )
  770. $comment_status = 'hold';
  771. else if ( 'spam' == $comment->comment_approved )
  772. $comment_status = 'spam';
  773. else if ( '1' == $comment->comment_approved )
  774. $comment_status = 'approve';
  775. else
  776. $comment_status = $comment->comment_approved;
  777. $_comment = array(
  778. 'date_created_gmt' => $comment_date_gmt,
  779. 'user_id' => $comment->user_id,
  780. 'comment_id' => $comment->comment_ID,
  781. 'parent' => $comment->comment_parent,
  782. 'status' => $comment_status,
  783. 'content' => $comment->comment_content,
  784. 'link' => get_comment_link($comment),
  785. 'post_id' => $comment->comment_post_ID,
  786. 'post_title' => get_the_title($comment->comment_post_ID),
  787. 'author' => $comment->comment_author,
  788. 'author_url' => $comment->comment_author_url,
  789. 'author_email' => $comment->comment_author_email,
  790. 'author_ip' => $comment->comment_author_IP,
  791. 'type' => $comment->comment_type,
  792. );
  793. return apply_filters( 'xmlrpc_prepare_comment', $_comment, $comment );
  794. }
  795. /**
  796. * Prepares user data for return in an XML-RPC object.
  797. *
  798. * @access protected
  799. *
  800. * @param WP_User $user The unprepared user object
  801. * @param array $fields The subset of user fields to return
  802. * @return array The prepared user data
  803. */
  804. protected function _prepare_user( $user, $fields ) {
  805. $_user = array( 'user_id' => strval( $user->ID ) );
  806. $user_fields = array(
  807. 'username' => $user->user_login,
  808. 'first_name' => $user->user_firstname,
  809. 'last_name' => $user->user_lastname,
  810. 'registered' => $this->_convert_date( $user->user_registered ),
  811. 'bio' => $user->user_description,
  812. 'email' => $user->user_email,
  813. 'nickname' => $user->nickname,
  814. 'nicename' => $user->user_nicename,
  815. 'url' => $user->user_url,
  816. 'display_name' => $user->display_name,
  817. 'roles' => $user->roles,
  818. );
  819. if ( in_array( 'all', $fields ) ) {
  820. $_user = array_merge( $_user, $user_fields );
  821. } else {
  822. if ( in_array( 'basic', $fields ) ) {
  823. $basic_fields = array( 'username', 'email', 'registered', 'display_name', 'nicename' );
  824. $fields = array_merge( $fields, $basic_fields );
  825. }
  826. $requested_fields = array_intersect_key( $user_fields, array_flip( $fields ) );
  827. $_user = array_merge( $_user, $requested_fields );
  828. }
  829. return apply_filters( 'xmlrpc_prepare_user', $_user, $user, $fields );
  830. }
  831. /**
  832. * Create a new post for any registered post type.
  833. *
  834. * @since 3.4.0
  835. *
  836. * @param array $args Method parameters. Contains:
  837. * - int $blog_id
  838. * - string $username
  839. * - string $password
  840. * - array $content_struct
  841. * $content_struct can contain:
  842. * - post_type (default: 'post')
  843. * - post_status (default: 'draft')
  844. * - post_title
  845. * - post_author
  846. * - post_excerpt
  847. * - post_content
  848. * - post_date_gmt | post_date
  849. * - post_format
  850. * - post_password
  851. * - comment_status - can be 'open' | 'closed'
  852. * - ping_status - can be 'open' | 'closed'
  853. * - sticky
  854. * - post_thumbnail - ID of a media item to use as the post thumbnail/featured image
  855. * - custom_fields - array, with each element containing 'key' and 'value'
  856. * - terms - array, with taxonomy names as keys and arrays of term IDs as values
  857. * - terms_names - array, with taxonomy names as keys and arrays of term names as values
  858. * - enclosure
  859. * - any other fields supported by wp_insert_post()
  860. * @return string post_id
  861. */
  862. function wp_newPost( $args ) {
  863. if ( ! $this->minimum_args( $args, 4 ) )
  864. return $this->error;
  865. $this->escape( $args );
  866. $blog_id = (int) $args[0];
  867. $username = $args[1];
  868. $password = $args[2];
  869. $content_struct = $args[3];
  870. if ( ! $user = $this->login( $username, $password ) )
  871. return $this->error;
  872. do_action( 'xmlrpc_call', 'wp.newPost' );
  873. unset( $content_struct['ID'] );
  874. return $this->_insert_post( $user, $content_struct );
  875. }
  876. /**
  877. * Helper method for filtering out elements from an array.
  878. *
  879. * @since 3.4.0
  880. *
  881. * @param int $count Number to compare to one.
  882. */
  883. private function _is_greater_than_one( $count ) {
  884. return $count > 1;
  885. }
  886. /**
  887. * Helper method for wp_newPost and wp_editPost, containing shared logic.
  888. *
  889. * @since 3.4.0
  890. * @uses wp_insert_post()
  891. *
  892. * @param WP_User $user The post author if post_author isn't set in $content_struct.
  893. * @param array $content_struct Post data to insert.
  894. */
  895. protected function _insert_post( $user, $content_struct ) {
  896. $defaults = array( 'post_status' => 'draft', 'post_type' => 'post', 'post_author' => 0,
  897. 'post_password' => '', 'post_excerpt' => '', 'post_content' => '', 'post_title' => '' );
  898. $post_data = wp_parse_args( $content_struct, $defaults );
  899. $post_type = get_post_type_object( $post_data['post_type'] );
  900. if ( ! $post_type )
  901. return new IXR_Error( 403, __( 'Invalid post type' ) );
  902. $update = ! empty( $post_data['ID'] );
  903. if ( $update ) {
  904. if ( ! get_post( $post_data['ID'] ) )
  905. return new IXR_Error( 401, __( 'Invalid post ID.' ) );
  906. if ( ! current_user_can( $post_type->cap->edit_post, $post_data['ID'] ) )
  907. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit this post.' ) );
  908. if ( $post_data['post_type'] != get_post_type( $post_data['ID'] ) )
  909. return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
  910. } else {
  911. if ( ! current_user_can( $post_type->cap->edit_posts ) )
  912. return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
  913. }
  914. switch ( $post_data['post_status'] ) {
  915. case 'draft':
  916. case 'pending':
  917. break;
  918. case 'private':
  919. if ( ! current_user_can( $post_type->cap->publish_posts ) )
  920. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create private posts in this post type' ) );
  921. break;
  922. case 'publish':
  923. case 'future':
  924. if ( ! current_user_can( $post_type->cap->publish_posts ) )
  925. return new IXR_Error( 401, __( 'Sorry, you are not allowed to publish posts in this post type' ) );
  926. break;
  927. default:
  928. if ( ! get_post_status_object( $post_data['post_status'] ) )
  929. $post_data['post_status'] = 'draft';
  930. break;
  931. }
  932. if ( ! empty( $post_data['post_password'] ) && ! current_user_can( $post_type->cap->publish_posts ) )
  933. return new IXR_Error( 401, __( 'Sorry, you are not allowed to create password protected posts in this post type' ) );
  934. $post_data['post_author'] = absint( $post_data['post_author'] );
  935. if ( ! empty( $post_data['post_author'] ) && $post_data['post_author'] != $user->ID ) {
  936. if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
  937. return new IXR_Error( 401, __( 'You are not allowed to create posts as this user.' ) );
  938. $author = get_userdata( $post_data['post_author'] );
  939. if ( ! $author )
  940. return new IXR_Error( 404, __( 'Invalid author ID.' ) );
  941. } else {
  942. $post_data['post_author'] = $user->ID;
  943. }
  944. if ( isset( $post_data['comment_status'] ) && $post_data['comment_status'] != 'open' && $post_data['comment_status'] != 'closed' )
  945. unset( $post_data['comment_status'] );
  946. if ( isset( $post_data['ping_status'] ) && $post_data['ping_status'] != 'open' && $post_data['ping_status'] != 'closed' )
  947. unset( $post_data['ping_status'] );
  948. // Do some timestamp voodoo
  949. if ( ! empty( $post_data['post_date_gmt'] ) ) {
  950. // We know this is supposed to be GMT, so we're going to slap that Z on there by force
  951. $dateCreated = rtrim( $post_data['post_date_gmt']->getIso(), 'Z' ) . 'Z';
  952. } elseif ( ! empty( $post_data['post_date'] ) ) {
  953. $dateCreated = $post_data['post_date']->getIso();
  954. }
  955. if ( ! empty( $dateCreated ) ) {
  956. $post_data['post_date'] = get_date_from_gmt( iso8601_to_datetime( $dateCreated ) );
  957. $post_data['post_date_gmt'] = iso8601_to_datetime( $dateCreated, 'GMT' );
  958. }
  959. if ( ! isset( $post_data['ID'] ) )
  960. $post_data['ID'] = get_default_post_to_edit( $post_data['post_type'], true )->ID;
  961. $post_ID = $post_data['ID'];
  962. if ( $post_data['post_type'] == 'post' ) {
  963. // Private and password-protected posts cannot be stickied.
  964. if ( $post_data['post_status'] == 'private' || ! empty( $post_data['post_password'] ) ) {
  965. // Error if the client tried to stick the post, otherwise, silently unstick.
  966. if ( ! empty( $post_data['sticky'] ) )
  967. return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
  968. if ( $update )
  969. unstick_post( $post_ID );
  970. } elseif ( isset( $post_data['sticky'] ) ) {
  971. if ( ! current_user_can( $post_type->cap->edit_others_posts ) )
  972. return new IXR_Error( 401, __( 'Sorry, you are not allowed to stick this post.' ) );
  973. if ( $post_data['sticky'] )
  974. stick_post( $post_ID );
  975. else
  976. unstick_post( $post_ID );
  977. }
  978. }
  979. if ( isset( $post_data['post_thumbnail'] ) ) {
  980. // empty value deletes, non-empty value adds/updates
  981. if ( ! $post_data['post_thumbnail'] )
  982. delete_post_thumbnail( $post_ID );
  983. elseif ( ! get_post( absint( $post_data['post_thumbnail'] ) ) )
  984. return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  985. set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] );
  986. unset( $content_struct['post_thumbnail'] );
  987. }
  988. if ( isset( $post_data['custom_fields'] ) )
  989. $this->set_custom_fields( $post_ID, $post_data['custom_fields'] );
  990. if ( isset( $post_data['terms'] ) || isset( $post_data['terms_names'] ) ) {
  991. $post_type_taxonomies = get_object_taxonomies( $post_data['post_type'], 'objects' );
  992. // accumulate term IDs from terms and terms_names
  993. $terms = array();
  994. // first validate the terms specified by ID
  995. if ( isset( $post_data['terms'] ) && is_array( $post_data['terms'] ) ) {
  996. $taxonomies = array_keys( $post_data['terms'] );
  997. // validating term ids
  998. foreach ( $taxonomies as $taxonomy ) {
  999. if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
  1000. return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
  1001. if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
  1002. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
  1003. $term_ids = $post_data['terms'][$taxonomy];
  1004. foreach ( $term_ids as $term_id ) {
  1005. $term = get_term_by( 'id', $term_id, $taxonomy );
  1006. if ( ! $term )
  1007. return new IXR_Error( 403, __( 'Invalid term ID' ) );
  1008. $terms[$taxonomy][] = (int) $term_id;
  1009. }
  1010. }
  1011. }
  1012. // now validate terms specified by name
  1013. if ( isset( $post_data['terms_names'] ) && is_array( $post_data['terms_names'] ) ) {
  1014. $taxonomies = array_keys( $post_data['terms_names'] );
  1015. foreach ( $taxonomies as $taxonomy ) {
  1016. if ( ! array_key_exists( $taxonomy , $post_type_taxonomies ) )
  1017. return new IXR_Error( 401, __( 'Sorry, one of the given taxonomies is not supported by the post type.' ) );
  1018. if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->assign_terms ) )
  1019. return new IXR_Error( 401, __( 'Sorry, you are not allowed to assign a term to one of the given taxonomies.' ) );
  1020. // for hierarchical taxonomies, we can't assign a term when multiple terms in the hierarchy share the same name
  1021. $ambiguous_terms = array();
  1022. if ( is_taxonomy_hierarchical( $taxonomy ) ) {
  1023. $tax_term_names = get_terms( $taxonomy, array( 'fields' => 'names', 'hide_empty' => false ) );
  1024. // count the number of terms with the same name
  1025. $tax_term_names_count = array_count_values( $tax_term_names );
  1026. // filter out non-ambiguous term names
  1027. $ambiguous_tax_term_counts = array_filter( $tax_term_names_count, array( $this, '_is_greater_than_one') );
  1028. $ambiguous_terms = array_keys( $ambiguous_tax_term_counts );
  1029. }
  1030. $term_names = $post_data['terms_names'][$taxonomy];
  1031. foreach ( $term_names as $term_name ) {
  1032. if ( in_array( $term_name, $ambiguous_terms ) )
  1033. return new IXR_Error( 401, __( 'Ambiguous term name used in a hierarchical taxonomy. Please use term ID instead.' ) );
  1034. $term = get_term_by( 'name', $term_name, $taxonomy );
  1035. if ( ! $term ) {
  1036. // term doesn't exist, so check that the user is allowed to create new terms
  1037. if ( ! current_user_can( $post_type_taxonomies[$taxonomy]->cap->edit_terms ) )
  1038. return new IXR_Error( 401, __( 'Sorry, you are not allowed to add a term to one of the given taxonomies.' ) );
  1039. // create the new term
  1040. $term_info = wp_insert_term( $term_name, $taxonomy );
  1041. if ( is_wp_error( $term_info ) )
  1042. return new IXR_Error( 500, $term_info->get_error_message() );
  1043. $terms[$taxonomy][] = (int) $term_info['term_id'];
  1044. } else {
  1045. $terms[$taxonomy][] = (int) $term->term_id;
  1046. }
  1047. }
  1048. }
  1049. }
  1050. $post_data['tax_input'] = $terms;
  1051. unset( $post_data['terms'], $post_data['terms_names'] );
  1052. } else {
  1053. // do not allow direct submission of 'tax_input', clients must use 'terms' and/or 'terms_names'
  1054. unset( $post_data['tax_input'], $post_data['post_category'], $post_data['tags_input'] );
  1055. }
  1056. if ( isset( $post_data['post_format'] ) ) {
  1057. $format = set_post_format( $post_ID, $post_data['post_format'] );
  1058. if ( is_wp_error( $format ) )
  1059. return new IXR_Error( 500, $format->get_error_message() );
  1060. unset( $post_data['post_format'] );
  1061. }
  1062. // Handle enclosures
  1063. $enclosure = isset( $post_data['enclosure'] ) ? $post_data['enclosure'] : null;
  1064. $this->add_enclosure_if_new( $post_ID, $enclosure );
  1065. $this->attach_uploads( $post_ID, $post_data['post_content'] );
  1066. $post_data = apply_filters( 'xmlrpc_wp_insert_post_data', $post_data, $content_struct );
  1067. $post_ID = $update ? wp_update_post( $post_data, true ) : wp_insert_post( $post_data, true );
  1068. if ( is_wp_error( $post_ID ) )
  1069. return new IXR_Error( 500, $post_ID->get_error_message() );
  1070. if ( ! $post_ID )
  1071. return new IXR_Error( 401, __( 'Sorry, your entry could not be posted. Something wrong happened.' ) );
  1072. return strval( $post_ID );
  1073. }
  1074. /**
  1075. * Edit a post for any registered post type.
  1076. *
  1077. * The $content_struct parameter only needs to contain fields that
  1078. * should be changed. All other fields will retain their existing values.
  1079. *
  1080. * @since 3.4.0
  1081. *
  1082. * @param array $args Method parameters. Contains:
  1083. * - int $blog_id
  1084. * - string $username
  1085. * - string $password
  1086. * - int $post_id
  1087. * - array $content_struct
  1088. * @return true on success
  1089. */
  1090. function wp_editPost( $args ) {
  1091. if ( ! $this->minimum_args( $args, 5 ) )
  1092. return $this->error;
  1093. $this->escape( $args );
  1094. $blog_id = (int) $args[0];
  1095. $username = $args[1];
  1096. $password = $args[2];
  1097. $post_id = (int) $args[3];
  1098. $content_struct = $args[4];
  1099. if ( ! $user = $this->login( $username, $password ) )
  1100. return $this->error;
  1101. do_action( 'xmlrpc_call', 'wp.editPost' );
  1102. $post = get_post( $post_id, ARRAY_A );
  1103. if ( empty( $post['ID'] ) )
  1104. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  1105. if ( isset( $content_struct['if_not_modified_since'] ) ) {
  1106. // If the post has been modified since the date provided, return an error.
  1107. if ( mysql2date( 'U', $post['post_modified_gmt'] ) > $content_struct['if_not_modified_since']->getTimestamp() ) {
  1108. return new IXR_Error( 409, __( 'There is a revision of this post that is more recent.' ) );
  1109. }
  1110. }
  1111. // convert the date field back to IXR form
  1112. $post['post_date'] = $this->_convert_date( $post['post_date'] );
  1113. // ignore the existing GMT date if it is empty or a non-GMT date was supplied in $content_struct,
  1114. // since _insert_post will ignore the non-GMT date if the GMT date is set
  1115. if ( $post['post_date_gmt'] == '0000-00-00 00:00:00' || isset( $content_struct['post_date'] ) )
  1116. unset( $post['post_date_gmt'] );
  1117. else
  1118. $post['post_date_gmt'] = $this->_convert_date( $post['post_date_gmt'] );
  1119. $this->escape( $post );
  1120. $merged_content_struct = array_merge( $post, $content_struct );
  1121. $retval = $this->_insert_post( $user, $merged_content_struct );
  1122. if ( $retval instanceof IXR_Error )
  1123. return $retval;
  1124. return true;
  1125. }
  1126. /**
  1127. * Delete a post for any registered post type.
  1128. *
  1129. * @since 3.4.0
  1130. *
  1131. * @uses wp_delete_post()
  1132. * @param array $args Method parameters. Contains:
  1133. * - int $blog_id
  1134. * - string $username
  1135. * - string $password
  1136. * - int $post_id
  1137. * @return true on success
  1138. */
  1139. function wp_deletePost( $args ) {
  1140. if ( ! $this->minimum_args( $args, 4 ) )
  1141. return $this->error;
  1142. $this->escape( $args );
  1143. $blog_id = (int) $args[0];
  1144. $username = $args[1];
  1145. $password = $args[2];
  1146. $post_id = (int) $args[3];
  1147. if ( ! $user = $this->login( $username, $password ) )
  1148. return $this->error;
  1149. do_action( 'xmlrpc_call', 'wp.deletePost' );
  1150. $post = get_post( $post_id, ARRAY_A );
  1151. if ( empty( $post['ID'] ) )
  1152. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  1153. $post_type = get_post_type_object( $post['post_type'] );
  1154. if ( ! current_user_can( $post_type->cap->delete_post, $post_id ) )
  1155. return new IXR_Error( 401, __( 'Sorry, you are not allowed to delete this post.' ) );
  1156. $result = wp_delete_post( $post_id );
  1157. if ( ! $result )
  1158. return new IXR_Error( 500, __( 'The post cannot be deleted.' ) );
  1159. return true;
  1160. }
  1161. /**
  1162. * Retrieve a post.
  1163. *
  1164. * @since 3.4.0
  1165. *
  1166. * The optional $fields parameter specifies what fields will be included
  1167. * in the response array. This should be a list of field names. 'post_id' will
  1168. * always be included in the response regardless of the value of $fields.
  1169. *
  1170. * Instead of, or in addition to, individual field names, conceptual group
  1171. * names can be used to specify multiple fields. The available conceptual
  1172. * groups are 'post' (all basic fields), 'taxonomies', 'custom_fields',
  1173. * and 'enclosure'.
  1174. *
  1175. * @uses get_post()
  1176. * @param array $args Method parameters. Contains:
  1177. * - int $post_id
  1178. * - string $username
  1179. * - string $password
  1180. * - array $fields optional
  1181. * @return array contains (based on $fields parameter):
  1182. * - 'post_id'
  1183. * - 'post_title'
  1184. * - 'post_date'
  1185. * - 'post_date_gmt'
  1186. * - 'post_modified'
  1187. * - 'post_modified_gmt'
  1188. * - 'post_status'
  1189. * - 'post_type'
  1190. * - 'post_name'
  1191. * - 'post_author'
  1192. * - 'post_password'
  1193. * - 'post_excerpt'
  1194. * - 'post_content'
  1195. * - 'link'
  1196. * - 'comment_status'
  1197. * - 'ping_status'
  1198. * - 'sticky'
  1199. * - 'custom_fields'
  1200. * - 'terms'
  1201. * - 'categories'
  1202. * - 'tags'
  1203. * - 'enclosure'
  1204. */
  1205. function wp_getPost( $args ) {
  1206. if ( ! $this->minimum_args( $args, 4 ) )
  1207. return $this->error;
  1208. $this->escape( $args );
  1209. $blog_id = (int) $args[0];
  1210. $username = $args[1];
  1211. $password = $args[2];
  1212. $post_id = (int) $args[3];
  1213. if ( isset( $args[4] ) )
  1214. $fields = $args[4];
  1215. else
  1216. $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPost' );
  1217. if ( ! $user = $this->login( $username, $password ) )
  1218. return $this->error;
  1219. do_action( 'xmlrpc_call', 'wp.getPost' );
  1220. $post = get_post( $post_id, ARRAY_A );
  1221. if ( empty( $post['ID'] ) )
  1222. return new IXR_Error( 404, __( 'Invalid post ID.' ) );
  1223. $post_type = get_post_type_object( $post['post_type'] );
  1224. if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) )
  1225. return new IXR_Error( 401, __( 'Sorry, you cannot edit this post.' ) );
  1226. return $this->_prepare_post( $post, $fields );
  1227. }
  1228. /**
  1229. * Retrieve posts.
  1230. *
  1231. * @since 3.4.0
  1232. *
  1233. * The optional $filter parameter modifies the query used to retrieve posts.
  1234. * Accepted keys are 'post_type', 'post_status', 'number', 'offset',
  1235. * 'orderby', and 'order'.
  1236. *
  1237. * The optional $fields parameter specifies what fields will be included
  1238. * in the response array.
  1239. *
  1240. * @uses wp_get_recent_posts()
  1241. * @see wp_getPost() for more on $fields
  1242. * @see get_posts() for more on $filter values
  1243. *
  1244. * @param array $args Method parameters. Contains:
  1245. * - int $blog_id
  1246. * - string $username
  1247. * - string $password
  1248. * - array $filter optional
  1249. * - array $fields optional
  1250. * @return array contains a collection of posts.
  1251. */
  1252. function wp_getPosts( $args ) {
  1253. if ( ! $this->minimum_args( $args, 3 ) )
  1254. return $this->error;
  1255. $this->escape( $args );
  1256. $blog_id = (int) $args[0];
  1257. $username = $args[1];
  1258. $password = $args[2];
  1259. $filter = isset( $args[3] ) ? $args[3] : array();
  1260. if ( isset( $args[4] ) )
  1261. $fields = $args[4];
  1262. else
  1263. $fields = apply_filters( 'xmlrpc_default_post_fields', array( 'post', 'terms', 'custom_fields' ), 'wp.getPosts' );
  1264. if ( ! $user = $this->login( $username, $password ) )
  1265. return $this->error;
  1266. do_action( 'xmlrpc_call', 'wp.getPosts' );
  1267. $query = array();
  1268. if ( isset( $filter['post_type'] ) ) {
  1269. $post_type = get_post_type_object( $filter['post_type'] );
  1270. if ( ! ( (bool) $post_type ) )
  1271. return new IXR_Error( 403, __( 'The post type specified is not valid' ) );
  1272. } else {
  1273. $post_type = get_post_type_object( 'post' );
  1274. }
  1275. if ( ! current_user_can( $post_type->cap->edit_posts ) )
  1276. return new IXR_Error( 401, __( 'Sorry, you are not allowed to edit posts in this post type' ));
  1277. $query['post_type'] = $post_type->name;
  1278. if ( isset( $filter['post_status'] ) )
  1279. $query['post_status'] = $filter['post_status'];
  1280. if ( isset( $filter['number'] ) )
  1281. $query['numberposts'] = absint( $filter['number'] );
  1282. if ( isset( $filter['offset'] ) )
  1283. $query['offset'] = absint( $filter['offset'] );
  1284. if ( isset( $filter['orderby'] ) ) {
  1285. $query['orderby'] = $filter['orderby'];
  1286. if ( isset( $filter['order'] ) )
  1287. $query['order'] = $filter['order'];
  1288. }
  1289. if ( isset( $filter['s'] ) ) {
  1290. $query['s'] = $filter['s'];
  1291. }
  1292. $posts_list = wp_get_recent_posts( $query );
  1293. if ( ! $posts_list )
  1294. return array();
  1295. // holds all the posts data
  1296. $struct = array();
  1297. foreach ( $posts_list as $post ) {
  1298. $post_type = get_post_type_object( $post['post_type'] );
  1299. if ( ! current_user_can( $post_type->cap->edit_post, $post['ID']

Large files files are truncated, but you can click here to view the full file