PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.9.2
Jetpack – WP Security, Backup, Speed, & Growth v13.9.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / json-endpoints / class.wpcom-json-api-update-post-v1-1-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 2 years ago class.wpcom-json-api-add-widget-endpoint.php 2 years ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 5 years ago class.wpcom-json-api-bulk-delete-post-endpoint.php 4 years ago class.wpcom-json-api-bulk-restore-post-endpoint.php 2 years ago class.wpcom-json-api-bulk-update-comments-endpoint.php 3 years ago class.wpcom-json-api-comment-endpoint.php 2 years ago class.wpcom-json-api-delete-media-endpoint.php 4 years ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 5 years ago class.wpcom-json-api-get-comment-counts-endpoint.php 4 years ago class.wpcom-json-api-get-comment-endpoint.php 4 years ago class.wpcom-json-api-get-comment-history-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 4 years ago class.wpcom-json-api-get-customcss.php 2 years ago class.wpcom-json-api-get-media-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 2 years ago class.wpcom-json-api-get-post-endpoint.php 2 years ago class.wpcom-json-api-get-post-v1-1-endpoint.php 2 years ago class.wpcom-json-api-get-site-endpoint.php 1 year ago class.wpcom-json-api-get-site-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-taxonomies-endpoint.php 2 years ago class.wpcom-json-api-get-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-get-term-endpoint.php 4 years ago class.wpcom-json-api-list-comments-endpoint.php 2 years ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 3 years ago class.wpcom-json-api-list-embeds-endpoint.php 4 years ago class.wpcom-json-api-list-media-endpoint.php 2 years ago class.wpcom-json-api-list-media-v1-1-endpoint.php 2 years ago class.wpcom-json-api-list-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 4 years ago class.wpcom-json-api-list-post-types-endpoint.php 3 years ago class.wpcom-json-api-list-posts-endpoint.php 2 years ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 3 years ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 3 years ago class.wpcom-json-api-list-roles-endpoint.php 2 years ago class.wpcom-json-api-list-shortcodes-endpoint.php 4 years ago class.wpcom-json-api-list-terms-endpoint.php 2 years ago class.wpcom-json-api-list-users-endpoint.php 2 years ago class.wpcom-json-api-menus-v1-1-endpoint.php 2 years ago class.wpcom-json-api-post-endpoint.php 2 years ago class.wpcom-json-api-post-v1-1-endpoint.php 2 years ago class.wpcom-json-api-render-embed-endpoint.php 2 years ago class.wpcom-json-api-render-embed-reversal-endpoint.php 2 years ago class.wpcom-json-api-render-endpoint.php 3 years ago class.wpcom-json-api-render-shortcode-endpoint.php 3 years ago class.wpcom-json-api-sharing-buttons-endpoint.php 2 years ago class.wpcom-json-api-site-settings-endpoint.php 1 year ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 2 years ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 2 years ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 1 year ago class.wpcom-json-api-site-user-endpoint.php 2 years ago class.wpcom-json-api-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-update-comment-endpoint.php 2 years ago class.wpcom-json-api-update-customcss.php 2 years ago class.wpcom-json-api-update-media-endpoint.php 4 years ago class.wpcom-json-api-update-media-v1-1-endpoint.php 2 years ago class.wpcom-json-api-update-post-endpoint.php 3 years ago class.wpcom-json-api-update-post-v1-1-endpoint.php 2 years ago class.wpcom-json-api-update-post-v1-2-endpoint.php 2 years ago class.wpcom-json-api-update-site-homepage-endpoint.php 4 years ago class.wpcom-json-api-update-site-logo-endpoint.php 2 years ago class.wpcom-json-api-update-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-update-term-endpoint.php 4 years ago class.wpcom-json-api-update-user-endpoint.php 3 years ago class.wpcom-json-api-upload-media-endpoint.php 3 years ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 2 years ago
class.wpcom-json-api-update-post-v1-1-endpoint.php
1134 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Update post endpoint v1.1
4 *
5 * Endpoints:
6 * Create a post: /sites/%s/posts/new
7 * Update a post: /sites/%s/posts/%d
8 * Delete a post: /sites/%s/posts/%d/delete
9 * Restore a post: /sites/%s/posts/%d/restore
10 */
11
12 new WPCOM_JSON_API_Update_Post_v1_1_Endpoint(
13 array(
14 'description' => 'Create a post.',
15 'group' => 'posts',
16 'stat' => 'posts:new',
17 'new_version' => '1.2',
18 'min_version' => '1.1',
19 'max_version' => '1.1',
20 'method' => 'POST',
21 'path' => '/sites/%s/posts/new',
22 'path_labels' => array(
23 '$site' => '(int|string) Site ID or domain',
24 ),
25
26 'request_format' => array(
27 // explicitly document all input.
28 'date' => "(ISO 8601 datetime) The post's creation time.",
29 'title' => '(HTML) The post title.',
30 'content' => '(HTML) The post content.',
31 'excerpt' => '(HTML) An optional post excerpt.',
32 'slug' => '(string) The name (slug) for the post, used in URLs.',
33 'author' => '(string) The username or ID for the user to assign the post to.',
34 'publicize' => '(array|bool) True or false if the post be shared to external services. An array of services if we only want to share to a select few. Defaults to true.',
35 'publicize_message' => '(string) Custom message to be shared to external services.',
36 'status' => array(
37 'publish' => 'Publish the post.',
38 'private' => 'Privately publish the post.',
39 'draft' => 'Save the post as a draft.',
40 'pending' => 'Mark the post as pending editorial approval.',
41 'future' => 'Schedule the post (alias for publish; you must also set a future date).',
42 'auto-draft' => 'Save a placeholder for a newly created post, with no content.',
43 ),
44 'sticky' => array(
45 'false' => 'Post is not marked as sticky.',
46 'true' => 'Stick the post to the front page.',
47 ),
48 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
49 'parent' => "(int) The post ID of the new post's parent.",
50 'type' => "(string) The post type. Defaults to 'post'. Post types besides post and page need to be whitelisted using the <code>rest_api_allowed_post_types</code> filter.",
51 'terms' => '(object) Mapping of taxonomy to comma-separated list or array of terms (name or id)',
52 'categories' => '(array|string) Comma-separated list or array of categories (name or id)',
53 'tags' => '(array|string) Comma-separated list or array of tags (name or id)',
54 'format' => array_merge( array( 'default' => 'Use default post format' ), get_post_format_strings() ),
55 'featured_image' => '(string) The post ID of an existing attachment to set as the featured image. Pass an empty string to delete the existing image.',
56 'media' => '(media) An array of files to attach to the post. To upload media, the entire request should be multipart/form-data encoded. Multiple media items will be displayed in a gallery. Accepts jpg, jpeg, png, gif, pdf, doc, ppt, odt, pptx, docx, pps, ppsx, xls, xlsx, key. Audio and Video may also be available. See <code>allowed_file_types</code> in the options response of the site endpoint. Errors produced by media uploads, if any, will be in `media_errors` in the response. <br /><br /><strong>Example</strong>:<br />' .
57 "<code>curl \<br />--form 'title=Image Post' \<br />--form 'media[0]=@/path/to/file.jpg' \<br />--form 'media_attrs[0][caption]=My Great Photo' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'</code>",
58 'media_urls' => '(array) An array of URLs for images to attach to a post. Sideloads the media in for a post. Errors produced by media sideloading, if any, will be in `media_errors` in the response.',
59 'media_attrs' => '(array) An array of attributes (`title`, `description` and `caption`) are supported to assign to the media uploaded via the `media` or `media_urls` properties. You must use a numeric index for the keys of `media_attrs` which follow the same sequence as `media` and `media_urls`. <br /><br /><strong>Example</strong>:<br />' .
60 "<code>curl \<br />--form 'title=Gallery Post' \<br />--form 'media[]=@/path/to/file1.jpg' \<br />--form 'media_urls[]=http://exapmple.com/file2.jpg' \<br /> \<br />--form 'media_attrs[0][caption]=This will be the caption for file1.jpg' \<br />--form 'media_attrs[1][title]=This will be the title for file2.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'</code>",
61 'metadata' => '(array) Array of metadata objects containing the following properties: `key` (metadata key), `id` (meta ID), `previous_value` (if set, the action will only occur for the provided previous value), `value` (the new value to set the meta to), `operation` (the operation to perform: `update` or `add`; defaults to `update`). All unprotected meta keys are available by default for read requests. Both unprotected and protected meta keys are avaiable for authenticated requests with proper capabilities. Protected meta keys can be made available with the <code>rest_api_allowed_public_metadata</code> filter.',
62 'discussion' => '(object) A hash containing one or more of the following boolean values, which default to the blog\'s discussion preferences: `comments_open`, `pings_open`',
63 'likes_enabled' => "(bool) Should the post be open to likes? Defaults to the blog's preference.",
64 'sharing_enabled' => '(bool) Should sharing buttons show on this post? Defaults to true.',
65 'menu_order' => '(int) (Pages Only) the order pages should appear in. Use 0 to maintain alphabetical order.',
66 'page_template' => '(string) (Pages Only) The page template this page should use.',
67 ),
68
69 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/posts/new/',
70
71 'example_request_data' => array(
72 'headers' => array(
73 'authorization' => 'Bearer YOUR_API_TOKEN',
74 ),
75
76 'body' => array(
77 'title' => 'Hello World',
78 'content' => 'Hello. I am a test post. I was created by the API',
79 'tags' => 'tests',
80 'categories' => 'API',
81 ),
82 ),
83 )
84 );
85
86 new WPCOM_JSON_API_Update_Post_v1_1_Endpoint(
87 array(
88 'description' => 'Edit a post.',
89 'group' => 'posts',
90 'stat' => 'posts:1:POST',
91 'new_version' => '1.2',
92 'min_version' => '1.1',
93 'max_version' => '1.1',
94 'method' => 'POST',
95 'path' => '/sites/%s/posts/%d',
96 'path_labels' => array(
97 '$site' => '(int|string) Site ID or domain',
98 '$post_ID' => '(int) The post ID',
99 ),
100
101 'request_format' => array(
102 'date' => "(ISO 8601 datetime) The post's creation time.",
103 'title' => '(HTML) The post title.',
104 'content' => '(HTML) The post content.',
105 'excerpt' => '(HTML) An optional post excerpt.',
106 'slug' => '(string) The name (slug) for the post, used in URLs.',
107 'author' => '(string) The username or ID for the user to assign the post to.',
108 'publicize' => '(array|bool) True or false if the post be shared to external services. An array of services if we only want to share to a select few. Defaults to true.',
109 'publicize_message' => '(string) Custom message to be shared to external services.',
110 'status' => array(
111 'publish' => 'Publish the post.',
112 'private' => 'Privately publish the post.',
113 'draft' => 'Save the post as a draft.',
114 'future' => 'Schedule the post (alias for publish; you must also set a future date).',
115 'pending' => 'Mark the post as pending editorial approval.',
116 'trash' => 'Set the post as trashed.',
117 ),
118 'sticky' => array(
119 'false' => 'Post is not marked as sticky.',
120 'true' => 'Stick the post to the front page.',
121 ),
122 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
123 'parent' => "(int) The post ID of the new post's parent.",
124 'terms' => '(object) Mapping of taxonomy to comma-separated list or array of terms (name or id)',
125 'categories' => '(array|string) Comma-separated list or array of categories (name or id)',
126 'tags' => '(array|string) Comma-separated list or array of tags (name or id)',
127 'format' => array_merge( array( 'default' => 'Use default post format' ), get_post_format_strings() ),
128 'discussion' => '(object) A hash containing one or more of the following boolean values, which default to the blog\'s discussion preferences: `comments_open`, `pings_open`',
129 'likes_enabled' => '(bool) Should the post be open to likes?',
130 'menu_order' => '(int) (Pages only) the order pages should appear in. Use 0 to maintain alphabetical order.',
131 'page_template' => '(string) (Pages Only) The page template this page should use.',
132 'sharing_enabled' => '(bool) Should sharing buttons show on this post?',
133 'featured_image' => '(string) The post ID of an existing attachment to set as the featured image. Pass an empty string to delete the existing image.',
134 'media' => '(media) An array of files to attach to the post. To upload media, the entire request should be multipart/form-data encoded. Multiple media items will be displayed in a gallery. Accepts jpg, jpeg, png, gif, pdf, doc, ppt, odt, pptx, docx, pps, ppsx, xls, xlsx, key. Audio and Video may also be available. See <code>allowed_file_types</code> in the options resposne of the site endpoint. <br /><br /><strong>Example</strong>:<br />' .
135 "<code>curl \<br />--form 'title=Image' \<br />--form 'media[]=@/path/to/file.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'</code>",
136 'media_urls' => '(array) An array of URLs for images to attach to a post. Sideloads the media in for a post.',
137 'metadata' => '(array) Array of metadata objects containing the following properties: `key` (metadata key), `id` (meta ID), `previous_value` (if set, the action will only occur for the provided previous value), `value` (the new value to set the meta to), `operation` (the operation to perform: `update` or `add`; defaults to `update`). All unprotected meta keys are available by default for read requests. Both unprotected and protected meta keys are available for authenticated requests with proper capabilities. Protected meta keys can be made available with the <code>rest_api_allowed_public_metadata</code> filter.',
138 ),
139
140 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/posts/881',
141
142 'example_request_data' => array(
143 'headers' => array(
144 'authorization' => 'Bearer YOUR_API_TOKEN',
145 ),
146
147 'body' => array(
148 'title' => 'Hello World (Again)',
149 'content' => 'Hello. I am an edited post. I was edited by the API',
150 'tags' => 'tests',
151 'categories' => 'API',
152 ),
153 ),
154 )
155 );
156
157 new WPCOM_JSON_API_Update_Post_v1_1_Endpoint(
158 array(
159 'description' => 'Delete a post. Note: If the trash is enabled, this request will send the post to the trash. A second request will permanently delete the post.',
160 'group' => 'posts',
161 'stat' => 'posts:1:delete',
162 'min_version' => '1.1',
163 'max_version' => '1.1',
164 'method' => 'POST',
165 'path' => '/sites/%s/posts/%d/delete',
166 'path_labels' => array(
167 '$site' => '(int|string) Site ID or domain',
168 '$post_ID' => '(int) The post ID',
169 ),
170
171 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/posts/$post_ID/delete/',
172
173 'example_request_data' => array(
174 'headers' => array(
175 'authorization' => 'Bearer YOUR_API_TOKEN',
176 ),
177 ),
178 )
179 );
180
181 new WPCOM_JSON_API_Update_Post_v1_1_Endpoint(
182 array(
183 'description' => 'Restore a post or page from the trash to its previous status.',
184 'group' => 'posts',
185 'stat' => 'posts:1:restore',
186 'min_version' => '1.1',
187 'max_version' => '1.1',
188 'method' => 'POST',
189 'path' => '/sites/%s/posts/%d/restore',
190 'path_labels' => array(
191 '$site' => '(int|string) Site ID or domain',
192 '$post_ID' => '(int) The post ID',
193 ),
194
195 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/posts/$post_ID/restore/',
196
197 'example_request_data' => array(
198 'headers' => array(
199 'authorization' => 'Bearer YOUR_API_TOKEN',
200 ),
201 ),
202 )
203 );
204
205 // phpcs:disable PEAR.NamingConventions.ValidClassName.Invalid
206 /**
207 * Update post v1.1 endpoint class.
208 */
209 class WPCOM_JSON_API_Update_Post_v1_1_Endpoint extends WPCOM_JSON_API_Post_v1_1_Endpoint {
210 /**
211 * WPCOM_JSON_API_Update_Post_v1_1_Endpoint constructor.
212 *
213 * @param array $args Args.
214 */
215 public function __construct( $args ) {
216 parent::__construct( $args );
217 if ( $this->api->ends_with( $this->path, '/delete' ) ) {
218 $this->post_object_format['status']['deleted'] = 'The post has been deleted permanently.';
219 }
220 }
221
222 /**
223 * Update post API v1.1 callback.
224 *
225 * /sites/%s/posts/new -> $blog_id
226 * /sites/%s/posts/%d -> $blog_id, $post_id
227 * /sites/%s/posts/%d/delete -> $blog_id, $post_id
228 * /sites/%s/posts/%d/restore -> $blog_id, $post_id
229 *
230 * @param string $path API path.
231 * @param int $blog_id Blog ID.
232 * @param int $post_id Post ID.
233 *
234 * @return array|bool|WP_Error
235 */
236 public function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
237 $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
238 if ( is_wp_error( $blog_id ) ) {
239 return $blog_id;
240 }
241
242 if ( $this->api->ends_with( $path, '/delete' ) ) {
243 return $this->delete_post( $path, $blog_id, $post_id );
244 } elseif ( $this->api->ends_with( $path, '/restore' ) ) {
245 return $this->restore_post( $path, $blog_id, $post_id );
246 } else {
247 return $this->write_post( $path, $blog_id, $post_id );
248 }
249 }
250
251 /**
252 * Create or update a post.
253 *
254 * /sites/%s/posts/new -> $blog_id
255 * /sites/%s/posts/%d -> $blog_id, $post_id
256 *
257 * @param string $path API path.
258 * @param int $blog_id Blog ID.
259 * @param int $post_id Post ID.
260 */
261 public function write_post( $path, $blog_id, $post_id ) {
262 $delete_featured_image = null;
263 $media_results = array();
264 $post = null;
265 global $wpdb;
266
267 $new = $this->api->ends_with( $path, '/new' );
268 $args = $this->query_args();
269
270 // unhook publicize, it's hooked again later -- without this, skipping services is impossible.
271 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
272 remove_action( 'save_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ), 100, 2 );
273 add_action( 'rest_api_inserted_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ) );
274
275 if ( $this->should_load_theme_functions( $post_id ) ) {
276 $this->load_theme_functions();
277 }
278 }
279
280 if ( $new ) {
281 $input = $this->input( true );
282
283 // 'future' is an alias for 'publish' for now
284 if ( 'future' === $input['status'] ) {
285 $input['status'] = 'publish';
286 }
287
288 // default to post.
289 if ( empty( $input['type'] ) ) {
290 $input['type'] = 'post';
291 }
292
293 if ( 'revision' === $input['type'] ) {
294 if ( ! isset( $input['parent'] ) ) {
295 return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
296 }
297 $input['status'] = 'inherit'; // force inherit for revision type.
298 $input['slug'] = $input['parent'] . '-autosave-v1';
299 } elseif ( ! isset( $input['title'] ) && ! isset( $input['content'] ) && ! isset( $input['excerpt'] ) ) {
300 return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
301 }
302
303 $post_type = get_post_type_object( $input['type'] );
304
305 if ( ! $this->is_post_type_allowed( $input['type'] ) ) {
306 return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
307 }
308
309 if ( ! empty( $input['author'] ) ) {
310 $author_id = $this->parse_and_set_author( $input['author'], $input['type'] );
311 unset( $input['author'] );
312 if ( is_wp_error( $author_id ) ) {
313 return $author_id;
314 }
315 }
316
317 if ( 'publish' === $input['status'] ) {
318 if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
319 if ( current_user_can( $post_type->cap->edit_posts ) ) {
320 $input['status'] = 'pending';
321 } else {
322 return new WP_Error( 'unauthorized', 'User cannot publish posts', 403 );
323 }
324 }
325 } elseif ( ! current_user_can( $post_type->cap->edit_posts ) ) {
326 return new WP_Error( 'unauthorized', 'User cannot edit posts', 403 );
327 }
328 } else {
329 $input = $this->input( false );
330
331 if ( ! is_array( $input ) || ! $input ) {
332 return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
333 }
334
335 $post = get_post( $post_id );
336 if ( ! $post || is_wp_error( $post ) ) {
337 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
338 }
339
340 if ( isset( $input['status'] ) && 'trash' === $input['status'] && ! current_user_can( 'delete_post', $post_id ) ) {
341 return new WP_Error( 'unauthorized', 'User cannot delete post', 403 );
342 }
343
344 // 'future' is an alias for 'publish' for now
345 if ( isset( $input['status'] ) && 'future' === $input['status'] ) {
346 $input['status'] = 'publish';
347 }
348
349 $_post_type = ( ! empty( $input['type'] ) ) ? $input['type'] : $post->post_type;
350 $post_type = get_post_type_object( $_post_type );
351
352 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
353 return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
354 }
355
356 if ( ! empty( $input['author'] ) ) {
357 $author_id = $this->parse_and_set_author( $input['author'], $_post_type );
358 unset( $input['author'] );
359 if ( is_wp_error( $author_id ) ) {
360 return $author_id;
361 }
362 }
363
364 if ( ( isset( $input['status'] ) && 'publish' === $input['status'] ) && 'publish' !== $post->post_status && ! current_user_can( 'publish_post', $post->ID ) ) {
365 $input['status'] = 'pending';
366 }
367 $last_status = $post->post_status;
368 $new_status = isset( $input['status'] ) ? $input['status'] : $last_status;
369
370 // Make sure that drafts get the current date when transitioning to publish if not supplied in the post.
371 // Similarly, scheduled posts that are manually published before their scheduled date should have the date reset.
372 $date_in_past = ( strtotime( $post->post_date_gmt ) < time() );
373 $reset_draft_date = 'publish' === $new_status && 'draft' === $last_status && ! isset( $input['date_gmt'] ) && $date_in_past;
374 $reset_scheduled_date = 'publish' === $new_status && 'future' === $last_status && ! isset( $input['date_gmt'] ) && ! $date_in_past;
375
376 if ( $reset_draft_date || $reset_scheduled_date ) {
377 $input['date_gmt'] = gmdate( 'Y-m-d H:i:s' );
378 }
379
380 // Untrash a post so that the proper hooks get called as well as the comments get untrashed.
381 if ( $this->should_untrash_post( $last_status, $new_status, $post ) ) {
382 $input = $this->untrash_post( $post, $input );
383 }
384 }
385
386 if ( function_exists( 'wpcom_switch_to_blog_locale' ) ) {
387 // fixes calypso-pre-oss #12476: respect blog locale when creating the post slug.
388 wpcom_switch_to_blog_locale( $blog_id );
389 }
390
391 // If date was set, $this->input will set date_gmt, date still needs to be adjusted for the blog's offset.
392 if ( isset( $input['date_gmt'] ) ) {
393 $gmt_offset = get_option( 'gmt_offset' );
394 $time_with_offset = strtotime( $input['date_gmt'] ) + $gmt_offset * HOUR_IN_SECONDS;
395 $input['date'] = gmdate( 'Y-m-d H:i:s', $time_with_offset );
396 }
397
398 if ( ! empty( $author_id ) && get_current_user_id() !== $author_id ) {
399 if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
400 return new WP_Error( 'unauthorized', "User is not allowed to publish others' posts.", 403 );
401 } elseif ( ! user_can( $author_id, $post_type->cap->edit_posts ) ) {
402 return new WP_Error( 'unauthorized', 'Assigned author cannot publish post.', 403 );
403 }
404 }
405
406 if ( ! is_post_type_hierarchical( $post_type->name ) && 'revision' !== $post_type->name ) {
407 unset( $input['parent'] );
408 }
409
410 $input['terms'] = isset( $input['terms'] ) ? (array) $input['terms'] : array();
411
412 // Convert comma-separated terms to array before attempting to
413 // merge with hardcoded taxonomies.
414 foreach ( $input['terms'] as $taxonomy => $terms ) {
415 if ( is_string( $terms ) ) {
416 $input['terms'][ $taxonomy ] = explode( ',', $terms );
417 } elseif ( ! is_array( $terms ) ) {
418 $input['terms'][ $taxonomy ] = array();
419 }
420 }
421
422 // For each hard-coded taxonomy, merge into terms object.
423 foreach ( array(
424 'categories' => 'category',
425 'tags' => 'post_tag',
426 ) as $taxonomy_key => $taxonomy ) {
427 if ( ! isset( $input[ $taxonomy_key ] ) ) {
428 continue;
429 }
430
431 if ( ! isset( $input['terms'][ $taxonomy ] ) ) {
432 $input['terms'][ $taxonomy ] = array();
433 }
434
435 $terms = $input[ $taxonomy_key ];
436 if ( is_string( $terms ) ) {
437 $terms = explode( ',', $terms );
438 } elseif ( ! is_array( $terms ) ) {
439 continue;
440 }
441
442 $input['terms'][ $taxonomy ] = array_merge(
443 $input['terms'][ $taxonomy ],
444 $terms
445 );
446 }
447
448 $tax_input = array();
449
450 foreach ( $input['terms'] as $taxonomy => $terms ) {
451 $tax_input[ $taxonomy ] = array();
452 $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
453
454 foreach ( $terms as $term ) {
455 /**
456 * `curl --data 'terms[category][]=123'` should be interpreted as a category ID,
457 * not a category whose name is '123'.
458 *
459 * Consequence: To add a category/tag whose name is '123', the client must
460 * first look up its ID.
461 */
462 $term = (string) $term; // ctype_digit compat.
463 if ( ctype_digit( $term ) ) {
464 $term = (int) $term;
465 }
466
467 $term_info = term_exists( $term, $taxonomy );
468
469 if ( ! $term_info ) {
470 // A term ID that doesn't already exist. Ignore it: we don't know what name to give it.
471 if ( is_int( $term ) ) {
472 continue;
473 }
474 // only add a new tag/cat if the user has access to.
475 $tax = get_taxonomy( $taxonomy );
476
477 // see https://core.trac.wordpress.org/ticket/26409 .
478 if ( $is_hierarchical && ! current_user_can( $tax->cap->edit_terms ) ) {
479 continue;
480 } elseif ( ! current_user_can( $tax->cap->assign_terms ) ) {
481 continue;
482 }
483
484 $term_info = wp_insert_term( $term, $taxonomy );
485 }
486
487 if ( ! is_wp_error( $term_info ) ) {
488 if ( $is_hierarchical ) {
489 // Hierarchical terms must be added by ID.
490 $tax_input[ $taxonomy ][] = (int) $term_info['term_id'];
491 } elseif ( is_int( $term ) ) { // Non-hierarchical terms must be added by name.
492 $term = get_term( $term, $taxonomy );
493 $tax_input[ $taxonomy ][] = $term->name;
494 } else {
495 $tax_input[ $taxonomy ][] = $term;
496 }
497 }
498 }
499 }
500
501 if ( isset( $input['terms']['category'] ) && empty( $tax_input['category'] ) && 'revision' !== $post_type->name ) {
502 $tax_input['category'][] = get_option( 'default_category' );
503 }
504
505 unset( $input['terms'], $input['tags'], $input['categories'] );
506
507 $insert = array();
508
509 if ( ! empty( $input['slug'] ) ) {
510 $insert['post_name'] = $input['slug'];
511 unset( $input['slug'] );
512 }
513
514 if ( isset( $input['discussion'] ) ) {
515 $discussion = (array) $input['discussion'];
516 foreach ( array( 'comment', 'ping' ) as $discussion_type ) {
517 $discussion_open = sprintf( '%ss_open', $discussion_type );
518 $discussion_status = sprintf( '%s_status', $discussion_type );
519
520 if ( isset( $discussion[ $discussion_open ] ) ) {
521 $is_open = WPCOM_JSON_API::is_truthy( $discussion[ $discussion_open ] );
522 $discussion[ $discussion_status ] = $is_open ? 'open' : 'closed';
523 }
524
525 if ( in_array( $discussion[ $discussion_status ], array( 'open', 'closed' ), true ) ) {
526 $insert[ $discussion_status ] = $discussion[ $discussion_status ];
527 }
528 }
529 }
530
531 unset( $input['discussion'] );
532
533 if ( isset( $input['menu_order'] ) ) {
534 $insert['menu_order'] = $input['menu_order'];
535 unset( $input['menu_order'] );
536 }
537
538 $publicize = isset( $input['publicize'] ) ? $input['publicize'] : null;
539 unset( $input['publicize'] );
540
541 $publicize_custom_message = isset( $input['publicize_message'] ) ? $input['publicize_message'] : null;
542 unset( $input['publicize_message'] );
543
544 if ( isset( $input['featured_image'] ) ) {
545 $featured_image = trim( $input['featured_image'] );
546 $delete_featured_image = empty( $featured_image );
547 unset( $input['featured_image'] );
548 }
549
550 $metadata = isset( $input['metadata'] ) ? $input['metadata'] : null;
551 unset( $input['metadata'] );
552
553 $likes = isset( $input['likes_enabled'] ) ? $input['likes_enabled'] : null;
554 unset( $input['likes_enabled'] );
555
556 $sharing = isset( $input['sharing_enabled'] ) ? $input['sharing_enabled'] : null;
557 unset( $input['sharing_enabled'] );
558
559 $sticky = isset( $input['sticky'] ) ? $input['sticky'] : null;
560 unset( $input['sticky'] );
561
562 foreach ( $input as $key => $value ) {
563 $insert[ "post_$key" ] = $value;
564 }
565
566 if ( ! empty( $author_id ) ) {
567 $insert['post_author'] = absint( $author_id );
568 }
569
570 if ( ! empty( $tax_input ) ) {
571 $insert['tax_input'] = $tax_input;
572 }
573
574 $has_media = ! empty( $input['media'] ) ? count( $input['media'] ) : false;
575 $has_media_by_url = ! empty( $input['media_urls'] ) ? count( $input['media_urls'] ) : false;
576
577 $media_id_string = '';
578 if ( $has_media || $has_media_by_url ) {
579 $media_files = ! empty( $input['media'] ) ? $input['media'] : array();
580 $media_urls = ! empty( $input['media_urls'] ) ? $input['media_urls'] : array();
581 $media_attrs = ! empty( $input['media_attrs'] ) ? $input['media_attrs'] : array();
582 $media_results = $this->handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs );
583 $media_id_string = implode( ',', array_filter( array_map( 'absint', $media_results['media_ids'] ) ) );
584 }
585
586 if ( $new ) {
587 if ( isset( $input['content'] ) && ! has_shortcode( $input['content'], 'gallery' ) && ( $has_media || $has_media_by_url ) ) {
588 switch ( ( $has_media + $has_media_by_url ) ) {
589 case 0:
590 // No images - do nothing.
591 break;
592 case 1:
593 // 1 image - make it big
594 $input['content'] = sprintf(
595 "[gallery size=full ids='%s' columns=1]\n\n",
596 $media_id_string
597 ) . $input['content'];
598 $insert['post_content'] = $input['content'];
599 break;
600 default:
601 // Several images - 3 column gallery.
602 $input['content'] = sprintf(
603 "[gallery ids='%s']\n\n",
604 $media_id_string
605 ) . $input['content'];
606 $insert['post_content'] = $input['content'];
607 break;
608 }
609 }
610
611 $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
612 } else {
613 $insert['ID'] = $post->ID;
614
615 // wp_update_post ignores date unless edit_date is set
616 // See: https://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts .
617 // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302 .
618 if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
619 $insert['edit_date'] = true;
620 }
621
622 // this two-step process ensures any changes submitted along with status=trash get saved before trashing.
623 if ( isset( $input['status'] ) && 'trash' === $input['status'] ) {
624 // if we insert it with status='trash', it will get double-trashed, so insert it as a draft first.
625 unset( $insert['status'] );
626 $post_id = wp_update_post( (object) $insert );
627 // now call wp_trash_post so post_meta gets set and any filters get called.
628 wp_trash_post( $post_id );
629 } else {
630 $post_id = wp_update_post( (object) $insert );
631 }
632 }
633
634 if ( ! $post_id || is_wp_error( $post_id ) ) {
635 return $post_id;
636 }
637
638 // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint).
639 $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
640 if ( is_wp_error( $post_check ) ) {
641 return $post_check;
642 }
643
644 if ( $media_id_string ) {
645 // Yes - this is really how wp-admin does it.
646 $wpdb->query(
647 $wpdb->prepare(
648 "UPDATE $wpdb->posts SET post_parent = %d WHERE post_type = 'attachment' AND ID IN ( $media_id_string )", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- IDs are filtered to absint above.
649 $post_id
650 )
651 );
652 foreach ( $media_results['media_ids'] as $media_id ) {
653 clean_attachment_cache( $media_id );
654 }
655 clean_post_cache( $post_id );
656 }
657
658 // set page template for this post.
659 if ( isset( $input['page_template'] ) && 'page' === $post_type->name ) {
660 $page_template = $input['page_template'];
661 $page_templates = wp_get_theme()->get_page_templates( get_post( $post_id ) );
662 if ( empty( $page_template ) || 'default' === $page_template || isset( $page_templates[ $page_template ] ) ) {
663 update_post_meta( $post_id, '_wp_page_template', $page_template );
664 }
665 }
666
667 // Set like status for the post.
668 /** This filter is documented in modules/likes.php */
669 $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
670 if ( $new ) {
671 if ( $sitewide_likes_enabled ) {
672 if ( false === $likes ) {
673 update_post_meta( $post_id, 'switch_like_status', 0 );
674 } else {
675 delete_post_meta( $post_id, 'switch_like_status' );
676 }
677 } elseif ( $likes ) {
678 update_post_meta( $post_id, 'switch_like_status', 1 );
679 } else {
680 delete_post_meta( $post_id, 'switch_like_status' );
681 }
682 } elseif ( isset( $likes ) ) {
683 if ( $sitewide_likes_enabled ) {
684 if ( false === $likes ) {
685 update_post_meta( $post_id, 'switch_like_status', 0 );
686 } else {
687 delete_post_meta( $post_id, 'switch_like_status' );
688 }
689 } elseif ( true === $likes ) {
690 update_post_meta( $post_id, 'switch_like_status', 1 );
691 } else {
692 delete_post_meta( $post_id, 'switch_like_status' );
693 }
694 }
695
696 // Set sharing status of the post.
697 if ( $new ) {
698 $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
699 if ( false === $sharing_enabled ) {
700 update_post_meta( $post_id, 'sharing_disabled', 1 );
701 }
702 } elseif ( isset( $sharing ) && true === $sharing ) {
703 delete_post_meta( $post_id, 'sharing_disabled' );
704 } elseif ( isset( $sharing ) && false == $sharing ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
705 update_post_meta( $post_id, 'sharing_disabled', 1 );
706 }
707
708 if ( isset( $sticky ) ) {
709 if ( true === $sticky ) {
710 stick_post( $post_id );
711 } else {
712 unstick_post( $post_id );
713 }
714 }
715
716 // WPCOM Specific (Jetpack's will get bumped elsewhere
717 // Tracks how many posts are published and sets meta
718 // so we can track some other cool stats (like likes & comments on posts published).
719 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
720 if (
721 ( $new && 'publish' === $input['status'] )
722 || (
723 ! $new && isset( $last_status )
724 && 'publish' !== $last_status
725 && isset( $new_status )
726 && 'publish' === $new_status
727 )
728 ) {
729 /** This action is documented in modules/widgets/social-media-icons.php */
730 do_action( 'jetpack_bump_stats_extras', 'api-insights-posts', $this->api->token_details['client_id'] );
731 update_post_meta( $post_id, '_rest_api_published', 1 );
732 update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
733 }
734 }
735
736 // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
737 // to instead flag the ones we don't want to be skipped. proceed with said logic.
738 // Any posts coming from Path (client ID 25952) should also not publicize.
739 if ( false === $publicize || ( isset( $this->api->token_details['client_id'] ) && 25952 === (int) $this->api->token_details['client_id'] ) ) {
740 // No publicize at all, skip all by ID.
741 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
742 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
743 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
744 if ( ! $service_connections ) {
745 continue;
746 }
747 foreach ( $service_connections as $service_connection ) {
748 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
749 }
750 }
751 } elseif ( is_array( $publicize ) && ( $publicize !== array() ) ) {
752 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
753 /*
754 * We support both indexed and associative arrays:
755 * * indexed are to pass entire services
756 * * associative are to pass specific connections per service
757 *
758 * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
759 *
760 * EG: array( 'linkedin', 'facebook') will only publicize to those, ignoring the other available services
761 * Form data: publicize[]=linkedin&publicize[]=facebook
762 * EG: array( 'linkedin' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3', 'facebook' => (int) $pub_conn_id_7 ) will publicize to two LinkedIn accounts, and one Facebook connection, of potentially many.
763 * Form data: publicize[linkedin]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
764 * EG: array( 'linkedin', 'facebook' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3' ) will publicize to all available LinkedIn accounts, but only 2 of potentially many Facebook connections
765 * Form data: publicize[]=linkedin&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
766 */
767
768 // Delete any stale SKIP value for the service by name. We'll add it back by ID.
769 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
770
771 // Get the user's connections.
772 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
773
774 // if the user doesn't have any connections for this service, move on.
775 if ( ! $service_connections ) {
776 continue;
777 }
778
779 if ( ! in_array( $name, $publicize, true ) && ! array_key_exists( $name, $publicize ) ) {
780 // Skip the whole service by adding each connection ID.
781 foreach ( $service_connections as $service_connection ) {
782 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
783 }
784 } elseif ( ! empty( $publicize[ $name ] ) ) {
785 // Seems we're being asked to only push to [a] specific connection[s].
786 // Explode the list on commas, which will also support a single passed ID.
787 $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
788
789 // Flag the connections we can't match with the requested list to be skipped.
790 foreach ( $service_connections as $service_connection ) {
791 if ( ! in_array( $service_connection->meta['connection_data']->id, $requested_connections, true ) ) {
792 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
793 } else {
794 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
795 }
796 }
797 } else {
798 // delete all SKIP values; it's okay to publish to all connected IDs for this service.
799 foreach ( $service_connections as $service_connection ) {
800 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
801 }
802 }
803 }
804 }
805
806 if ( $publicize_custom_message !== null ) {
807 if ( empty( $publicize_custom_message ) ) {
808 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS );
809 } else {
810 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
811 }
812 }
813
814 if ( ! empty( $insert['post_format'] ) ) {
815 if ( 'default' !== strtolower( $insert['post_format'] ) ) {
816 set_post_format( $post_id, $insert['post_format'] );
817 } else {
818 set_post_format( $post_id, get_option( 'default_post_format' ) );
819 }
820 }
821
822 if ( isset( $featured_image ) ) {
823 $this->parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
824 }
825
826 if ( ! empty( $metadata ) ) {
827 foreach ( (array) $metadata as $meta ) {
828
829 $meta = (object) $meta;
830
831 if (
832 in_array( $meta->key, Jetpack_SEO_Posts::POST_META_KEYS_ARRAY, true ) &&
833 ! Jetpack_SEO_Utils::is_enabled_jetpack_seo()
834 ) {
835 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
836 }
837
838 $existing_meta_item = new stdClass();
839
840 if ( empty( $meta->operation ) ) {
841 $meta->operation = 'update';
842 }
843
844 if ( ! empty( $meta->value ) ) {
845 if ( 'true' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
846 $meta->value = true;
847 }
848 if ( 'false' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
849 $meta->value = false;
850 }
851 }
852
853 if ( ! empty( $meta->id ) ) {
854 $meta->id = absint( $meta->id );
855 $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
856 if ( $post_id !== (int) $existing_meta_item->post_id ) {
857 // Only allow updates for metadata on this post.
858 continue;
859 }
860 }
861
862 $unslashed_meta_key = wp_unslash( $meta->key ); // should match what the final key will be.
863 $meta->key = wp_slash( $meta->key );
864 $unslashed_existing_meta_key = wp_unslash( $existing_meta_item->meta_key );
865 $existing_meta_item->meta_key = wp_slash( $existing_meta_item->meta_key );
866
867 // make sure that the meta id passed matches the existing meta key.
868 if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
869 $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
870 if ( $meta_by_id->meta_key !== $meta->key ) {
871 continue; // skip this meta.
872 }
873 }
874
875 switch ( $meta->operation ) {
876 case 'delete':
877 if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
878 delete_metadata_by_mid( 'post', $meta->id );
879 } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
880 delete_post_meta( $post_id, $meta->key, $meta->previous_value );
881 } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
882 delete_post_meta( $post_id, $meta->key );
883 }
884
885 break;
886 case 'add':
887 if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
888 break;
889 } elseif ( ! empty( $meta->key ) && ! empty( $meta->value ) && ( current_user_can( 'add_post_meta', $post_id, $unslashed_meta_key ) ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) {
890 add_post_meta( $post_id, $meta->key, $meta->value );
891 }
892
893 break;
894 case 'update':
895 if ( ! isset( $meta->value ) ) {
896 break;
897 } elseif ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_existing_meta_key ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) ) {
898 update_metadata_by_mid( 'post', $meta->id, $meta->value );
899 } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) ) {
900 update_post_meta( $post_id, $meta->key, $meta->value, $meta->previous_value );
901 } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) ) {
902 update_post_meta( $post_id, $meta->key, $meta->value );
903 }
904
905 break;
906 }
907 }
908 }
909
910 /** This action is documented in json-endpoints/class.wpcom-json-api-update-post-endpoint.php */
911 do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
912
913 $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
914 if ( ! $return || is_wp_error( $return ) ) {
915 return $return;
916 }
917
918 if ( isset( $input['type'] ) && 'revision' === $input['type'] ) {
919 $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
920 }
921
922 if ( isset( $sticky ) ) {
923 // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above.
924 $return['sticky'] = ( true === $sticky );
925 }
926
927 if ( ! empty( $media_results['errors'] ) ) {
928 $return['media_errors'] = $media_results['errors'];
929 }
930
931 if ( 'publish' !== $post->post_status ) {
932 $sal_site = $this->get_sal_post_by( 'ID', $post_id, $args['context'] );
933 $return['other_URLs'] = (object) $sal_site->get_permalink_suggestions( $input['title'] );
934 }
935
936 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
937 do_action( 'wpcom_json_api_objects', 'posts' );
938
939 return $return;
940 }
941
942 /**
943 * Delete a post.
944 *
945 * /sites/%s/posts/%d/delete -> $blog_id, $post_id
946 *
947 * @param string $path API path.
948 * @param array $blog_id Blog ID.
949 * @param array $post_id Post ID.
950 *
951 * @return array|WP_Error
952 */
953 public function delete_post( $path, $blog_id, $post_id ) {
954 $post = get_post( $post_id );
955 if ( ! $post || is_wp_error( $post ) ) {
956 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
957 }
958
959 if ( ! $this->is_post_type_allowed( $post->post_type ) ) {
960 return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
961 }
962
963 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
964 return new WP_Error( 'unauthorized', 'User cannot delete posts', 403 );
965 }
966
967 $args = $this->query_args();
968 $return = $this->get_post_by( 'ID', $post->ID, $args['context'] );
969 if ( ! $return || is_wp_error( $return ) ) {
970 return $return;
971 }
972
973 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
974 do_action( 'wpcom_json_api_objects', 'posts' );
975
976 // we need to call wp_trash_post so that untrash will work correctly for all post types.
977 if ( 'trash' === $post->post_status ) {
978 wp_delete_post( $post->ID );
979 } else {
980 wp_trash_post( $post->ID );
981 }
982
983 $status = get_post_status( $post->ID );
984 if ( false === $status ) {
985 $return['status'] = 'deleted';
986 return $return;
987 }
988
989 return $this->get_post_by( 'ID', $post->ID, $args['context'] );
990 }
991
992 /**
993 * Restore a post.
994 *
995 * /sites/%s/posts/%d/restore -> $blog_id, $post_id
996 *
997 * @param string $path API path.
998 * @param int $blog_id Blog ID.
999 * @param int $post_id Post ID.
1000 *
1001 * @return array|WP_Error
1002 */
1003 public function restore_post( $path, $blog_id, $post_id ) {
1004 $args = $this->query_args();
1005 $post = get_post( $post_id );
1006
1007 if ( ! $post || is_wp_error( $post ) ) {
1008 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
1009 }
1010
1011 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
1012 return new WP_Error( 'unauthorized', 'User cannot restore trashed posts', 403 );
1013 }
1014
1015 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
1016 do_action( 'wpcom_json_api_objects', 'posts' );
1017
1018 wp_untrash_post( $post->ID );
1019
1020 return $this->get_post_by( 'ID', $post->ID, $args['context'] );
1021 }
1022
1023 /**
1024 * Set or delete a post's featured image.
1025 *
1026 * @param int $post_id Post ID.
1027 * @param bool $delete_featured_image Whether to delete the featured image.
1028 * @param int $featured_image Thumbnail ID to attach.
1029 *
1030 * @return null|int|bool
1031 */
1032 protected function parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image ) {
1033 if ( $delete_featured_image ) {
1034 delete_post_thumbnail( $post_id );
1035 return;
1036 }
1037
1038 $featured_image = (string) $featured_image;
1039
1040 // if we got a post ID, we can just set it as the thumbnail.
1041 if ( ctype_digit( $featured_image ) && 'attachment' === get_post_type( $featured_image ) ) {
1042 set_post_thumbnail( $post_id, $featured_image );
1043 return $featured_image;
1044 }
1045
1046 $featured_image_id = $this->handle_media_sideload( $featured_image, $post_id, 'image' );
1047
1048 if ( empty( $featured_image_id ) || ! is_int( $featured_image_id ) ) {
1049 return false;
1050 }
1051
1052 set_post_thumbnail( $post_id, $featured_image_id );
1053 return $featured_image_id;
1054 }
1055
1056 /**
1057 * Get the Author ID for a post.
1058 *
1059 * @param int|string $author Author ID.
1060 * @param string $post_type Post type.
1061 *
1062 * @return int|WP_Error
1063 */
1064 protected function parse_and_set_author( $author = null, $post_type = 'post' ) {
1065 if ( empty( $author ) || ! post_type_supports( $post_type, 'author' ) ) {
1066 return get_current_user_id();
1067 }
1068
1069 $author = (string) $author;
1070 if ( ctype_digit( $author ) ) {
1071 $_user = get_user_by( 'id', $author );
1072 if ( ! $_user || is_wp_error( $_user ) ) {
1073 return new WP_Error( 'invalid_author', 'Invalid author provided' );
1074 }
1075
1076 return $_user->ID;
1077 }
1078
1079 $_user = get_user_by( 'login', $author );
1080 if ( ! $_user || is_wp_error( $_user ) ) {
1081 return new WP_Error( 'invalid_author', 'Invalid author provided' );
1082 }
1083
1084 return $_user->ID;
1085 }
1086
1087 /**
1088 * Determine if a post can be untrashed.
1089 *
1090 * @param string $last_status Last post status.
1091 * @param string $new_status New post status.
1092 * @param WP_Post $post Post.
1093 *
1094 * @return bool
1095 */
1096 protected function should_untrash_post( $last_status, $new_status, $post ) {
1097 return 'trash' === $last_status && 'trash' !== $new_status && isset( $post->ID );
1098 }
1099
1100 /**
1101 * Untrash a post.
1102 *
1103 * @param WP_Post $post Post to untrash.
1104 * @param array $input POST body data.
1105 */
1106 protected function untrash_post( $post, $input ) {
1107 wp_untrash_post( $post->ID );
1108 $untrashed_post = get_post( $post->ID );
1109 // Lets make sure that we use the reverted the slug.
1110 if ( isset( $untrashed_post->post_name ) && $untrashed_post->post_name . '__trashed' === $input['slug'] ) {
1111 unset( $input['slug'] );
1112 }
1113 return $input;
1114 }
1115
1116 /**
1117 * Determine if a theme's functions.php file should be loaded.
1118 *
1119 * @param int $post_id Post ID.
1120 *
1121 * @return bool
1122 */
1123 protected function should_load_theme_functions( $post_id = null ) {
1124 if ( empty( $post_id ) ) {
1125 $input = $this->input( true );
1126 $type = $input['type'];
1127 } else {
1128 $type = get_post_type( $post_id );
1129 }
1130
1131 return ! empty( $type ) && ! in_array( $type, array( 'post', 'revision' ), true );
1132 }
1133 }
1134