PageRenderTime 71ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

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

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

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