PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
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-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 1 week ago class.wpcom-json-api-add-widget-endpoint.php 7 months ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 7 months ago class.wpcom-json-api-bulk-delete-post-endpoint.php 7 months ago class.wpcom-json-api-bulk-restore-post-endpoint.php 7 months ago class.wpcom-json-api-bulk-update-comments-endpoint.php 1 month ago class.wpcom-json-api-comment-endpoint.php 7 months ago class.wpcom-json-api-delete-media-endpoint.php 7 months ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 7 months ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 1 month ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-comment-counts-endpoint.php 7 months ago class.wpcom-json-api-get-comment-endpoint.php 7 months ago class.wpcom-json-api-get-comment-history-endpoint.php 7 months ago class.wpcom-json-api-get-comments-tree-endpoint.php 7 months ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 7 months ago class.wpcom-json-api-get-customcss.php 7 months ago class.wpcom-json-api-get-media-endpoint.php 7 months ago class.wpcom-json-api-get-media-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-media-v1-2-endpoint.php 7 months ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 1 week ago class.wpcom-json-api-get-post-endpoint.php 7 months ago class.wpcom-json-api-get-post-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-site-endpoint.php 1 week ago class.wpcom-json-api-get-site-v1-2-endpoint.php 1 week ago class.wpcom-json-api-get-taxonomies-endpoint.php 2 months ago class.wpcom-json-api-get-taxonomy-endpoint.php 7 months ago class.wpcom-json-api-get-term-endpoint.php 7 months ago class.wpcom-json-api-list-comments-endpoint.php 1 week ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 7 months ago class.wpcom-json-api-list-embeds-endpoint.php 7 months ago class.wpcom-json-api-list-media-endpoint.php 7 months ago class.wpcom-json-api-list-media-v1-1-endpoint.php 1 month ago class.wpcom-json-api-list-media-v1-2-endpoint.php 7 months ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 7 months ago class.wpcom-json-api-list-post-types-endpoint.php 7 months ago class.wpcom-json-api-list-posts-endpoint.php 1 week ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 1 week ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 1 week ago class.wpcom-json-api-list-roles-endpoint.php 7 months ago class.wpcom-json-api-list-shortcodes-endpoint.php 7 months ago class.wpcom-json-api-list-terms-endpoint.php 7 months ago class.wpcom-json-api-list-users-endpoint.php 1 week ago class.wpcom-json-api-menus-v1-1-endpoint.php 1 month ago class.wpcom-json-api-post-endpoint.php 7 months ago class.wpcom-json-api-post-v1-1-endpoint.php 2 months ago class.wpcom-json-api-render-embed-endpoint.php 7 months ago class.wpcom-json-api-render-embed-reversal-endpoint.php 7 months ago class.wpcom-json-api-render-endpoint.php 1 month ago class.wpcom-json-api-render-shortcode-endpoint.php 7 months ago class.wpcom-json-api-sharing-buttons-endpoint.php 1 month ago class.wpcom-json-api-site-settings-endpoint.php 1 week ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 7 months ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 7 months ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 1 week ago class.wpcom-json-api-site-user-endpoint.php 7 months ago class.wpcom-json-api-taxonomy-endpoint.php 7 months ago class.wpcom-json-api-update-comment-endpoint.php 5 months ago class.wpcom-json-api-update-customcss.php 7 months ago class.wpcom-json-api-update-media-endpoint.php 7 months ago class.wpcom-json-api-update-media-v1-1-endpoint.php 1 month ago class.wpcom-json-api-update-post-endpoint.php 1 month ago class.wpcom-json-api-update-post-v1-1-endpoint.php 1 month ago class.wpcom-json-api-update-post-v1-2-endpoint.php 1 month ago class.wpcom-json-api-update-site-homepage-endpoint.php 7 months ago class.wpcom-json-api-update-site-logo-endpoint.php 7 months ago class.wpcom-json-api-update-taxonomy-endpoint.php 6 months ago class.wpcom-json-api-update-term-endpoint.php 7 months ago class.wpcom-json-api-update-user-endpoint.php 7 months ago class.wpcom-json-api-upload-media-endpoint.php 7 months ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 7 months ago
class.wpcom-json-api-update-post-endpoint.php
1016 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Update post endpoint.
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 if ( ! defined( 'ABSPATH' ) ) {
13 exit( 0 );
14 }
15
16 new WPCOM_JSON_API_Update_Post_Endpoint(
17 array(
18 'description' => 'Create a post.',
19 'group' => 'posts',
20 'stat' => 'posts:new',
21 'new_version' => '1.2',
22 'max_version' => '1',
23 'method' => 'POST',
24 'path' => '/sites/%s/posts/new',
25 'path_labels' => array(
26 '$site' => '(int|string) Site ID or domain',
27 ),
28
29 'request_format' => array(
30 // explicitly document all input.
31 'date' => "(ISO 8601 datetime) The post's creation time.",
32 'title' => '(HTML) The post title.',
33 'content' => '(HTML) The post content.',
34 'excerpt' => '(HTML) An optional post excerpt.',
35 'slug' => '(string) The name (slug) for the post, used in URLs.',
36 'author' => '(string) The username or ID for the user to assign the post to.',
37 '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.',
38 'publicize_message' => '(string) Custom message to be shared to external services.',
39 'status' => array(
40 'publish' => 'Publish the post.',
41 'private' => 'Privately publish the post.',
42 'draft' => 'Save the post as a draft.',
43 'pending' => 'Mark the post as pending editorial approval.',
44 'auto-draft' => 'Save a placeholder for a newly created post, with no content.',
45 ),
46 'sticky' => array(
47 'false' => 'Post is not marked as sticky.',
48 'true' => 'Stick the post to the front page.',
49 ),
50 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
51 'parent' => "(int) The post ID of the new post's parent.",
52 '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.",
53 'categories' => '(array|string) Comma-separated list or array of categories (name or id)',
54 'tags' => '(array|string) Comma-separated list or array of tags (name or id)',
55 'format' => array_merge( array( 'default' => 'Use default post format' ), get_post_format_strings() ),
56 '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.',
57 '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. <br /><br /><strong>Example</strong>:<br />' .
58 "<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>",
59 'media_urls' => '(array) An array of URLs for images to attach to a post. Sideloads the media in for a post.',
60 '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.',
61 'comments_open' => "(bool) Should the post be open to comments? Defaults to the blog's preference.",
62 'pings_open' => "(bool) Should the post be open to comments? Defaults to the blog's preference.",
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 ),
67
68 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/82974409/posts/new/',
69
70 'example_request_data' => array(
71 'headers' => array(
72 'authorization' => 'Bearer YOUR_API_TOKEN',
73 ),
74
75 'body' => array(
76 'title' => 'Hello World',
77 'content' => 'Hello. I am a test post. I was created by the API',
78 'tags' => 'tests',
79 'categories' => 'API',
80 ),
81 ),
82 )
83 );
84
85 new WPCOM_JSON_API_Update_Post_Endpoint(
86 array(
87 'description' => 'Edit a post.',
88 'group' => 'posts',
89 'stat' => 'posts:1:POST',
90 'new_version' => '1.2',
91 'max_version' => '1',
92 'method' => 'POST',
93 'path' => '/sites/%s/posts/%d',
94 'path_labels' => array(
95 '$site' => '(int|string) Site ID or domain',
96 '$post_ID' => '(int) The post ID',
97 ),
98
99 'request_format' => array(
100 'date' => "(ISO 8601 datetime) The post's creation time.",
101 'title' => '(HTML) The post title.',
102 'content' => '(HTML) The post content.',
103 'excerpt' => '(HTML) An optional post excerpt.',
104 'slug' => '(string) The name (slug) for the post, used in URLs.',
105 'author' => '(string) The username or ID for the user to assign the post to.',
106 '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.',
107 'publicize_message' => '(string) Custom message to be shared to external services.',
108 'status' => array(
109 'publish' => 'Publish the post.',
110 'private' => 'Privately publish the post.',
111 'draft' => 'Save the post as a draft.',
112 'pending' => 'Mark the post as pending editorial approval.',
113 'trash' => 'Set the post as trashed.',
114 ),
115 'sticky' => array(
116 'false' => 'Post is not marked as sticky.',
117 'true' => 'Stick the post to the front page.',
118 ),
119 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
120 'parent' => "(int) The post ID of the new post's parent.",
121 'categories' => '(array|string) Comma-separated list or array of categories (name or id)',
122 'tags' => '(array|string) Comma-separated list or array of tags (name or id)',
123 'format' => array_merge( array( 'default' => 'Use default post format' ), get_post_format_strings() ),
124 'comments_open' => '(bool) Should the post be open to comments?',
125 'pings_open' => '(bool) Should the post be open to comments?',
126 'likes_enabled' => '(bool) Should the post be open to likes?',
127 'menu_order' => '(int) (Pages Only) the order pages should appear in. Use 0 to maintain alphabetical order.',
128 'sharing_enabled' => '(bool) Should sharing buttons show on this post?',
129 '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.',
130 '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 />' .
131 "<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>",
132 'media_urls' => '(array) An array of URLs for images to attach to a post. Sideloads the media in for a post.',
133 '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.',
134 ),
135
136 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/82974409/posts/881',
137
138 'example_request_data' => array(
139 'headers' => array(
140 'authorization' => 'Bearer YOUR_API_TOKEN',
141 ),
142
143 'body' => array(
144 'title' => 'Hello World (Again)',
145 'content' => 'Hello. I am an edited post. I was edited by the API',
146 'tags' => 'tests',
147 'categories' => 'API',
148 ),
149 ),
150 )
151 );
152
153 new WPCOM_JSON_API_Update_Post_Endpoint(
154 array(
155 '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.',
156 'group' => 'posts',
157 'stat' => 'posts:1:delete',
158 'new_version' => '1.1',
159 'max_version' => '1',
160 'method' => 'POST',
161 'path' => '/sites/%s/posts/%d/delete',
162 'path_labels' => array(
163 '$site' => '(int|string) Site ID or domain',
164 '$post_ID' => '(int) The post ID',
165 ),
166
167 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/82974409/posts/$post_ID/delete/',
168
169 'example_request_data' => array(
170 'headers' => array(
171 'authorization' => 'Bearer YOUR_API_TOKEN',
172 ),
173 ),
174 )
175 );
176
177 new WPCOM_JSON_API_Update_Post_Endpoint(
178 array(
179 'description' => 'Restore a post or page from the trash to its previous status.',
180 'group' => 'posts',
181 'stat' => 'posts:1:restore',
182
183 'method' => 'POST',
184 'new_version' => '1.1',
185 'max_version' => '1',
186 'path' => '/sites/%s/posts/%d/restore',
187 'path_labels' => array(
188 '$site' => '(int|string) Site ID or domain',
189 '$post_ID' => '(int) The post ID',
190 ),
191
192 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/82974409/posts/$post_ID/restore/',
193
194 'example_request_data' => array(
195 'headers' => array(
196 'authorization' => 'Bearer YOUR_API_TOKEN',
197 ),
198 ),
199 )
200 );
201
202 /**
203 * Update post endpoint class.
204 *
205 * @phan-constructor-used-for-side-effects
206 */
207 class WPCOM_JSON_API_Update_Post_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
208 /**
209 * WPCOM_JSON_API_Update_Post_Endpoint constructor.
210 *
211 * @param array $args Args.
212 */
213 public function __construct( $args ) {
214 parent::__construct( $args );
215 if ( $this->api->ends_with( $this->path, '/delete' ) ) {
216 $this->post_object_format['status']['deleted'] = 'The post has been deleted permanently.';
217 }
218 }
219
220 /**
221 * Update post API callback.
222 *
223 * /sites/%s/posts/new -> $blog_id
224 * /sites/%s/posts/%d -> $blog_id, $post_id
225 * /sites/%s/posts/%d/delete -> $blog_id, $post_id
226 * /sites/%s/posts/%d/restore -> $blog_id, $post_id
227 *
228 * @param string $path API path.
229 * @param int $blog_id Blog ID.
230 * @param int $post_id Post ID.
231 *
232 * @return array|bool|WP_Error
233 */
234 public function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
235 $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
236 if ( is_wp_error( $blog_id ) ) {
237 return $blog_id;
238 }
239
240 if ( $this->api->ends_with( $path, '/delete' ) ) {
241 return $this->delete_post( $path, $blog_id, $post_id );
242 } elseif ( $this->api->ends_with( $path, '/restore' ) ) {
243 return $this->restore_post( $path, $blog_id, $post_id );
244 } else {
245 return $this->write_post( $path, $blog_id, $post_id );
246 }
247 }
248
249 /**
250 * Create or update a post.
251 *
252 * /sites/%s/posts/new -> $blog_id
253 * /sites/%s/posts/%d -> $blog_id, $post_id
254 *
255 * @param string $path API path.
256 * @param int $blog_id Blog ID.
257 * @param int $post_id Post ID.
258 */
259 public function write_post( $path, $blog_id, $post_id ) {
260 $delete_featured_image = null;
261 $new = $this->api->ends_with( $path, '/new' );
262 $args = $this->query_args();
263
264 // unhook publicize, it's hooked again later -- without this, skipping services is impossible.
265 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
266 remove_action( 'save_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ), 100 );
267 add_action( 'rest_api_inserted_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ) );
268 }
269
270 if ( $new ) {
271 $input = $this->input( true );
272
273 // default to post.
274 if ( empty( $input['type'] ) ) {
275 $input['type'] = 'post';
276 }
277
278 if ( 'revision' === $input['type'] ) {
279 if ( ! isset( $input['parent'] ) ) {
280 return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
281 }
282 $input['status'] = 'inherit'; // force inherit for revision type.
283 $input['slug'] = $input['parent'] . '-autosave-v1';
284 } elseif ( ! isset( $input['title'] ) && ! isset( $input['content'] ) && ! isset( $input['excerpt'] ) ) {
285 return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
286 }
287
288 $post_type = get_post_type_object( $input['type'] );
289
290 if ( ! $this->is_post_type_allowed( $input['type'] ) ) {
291 return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
292 }
293
294 if ( ! empty( $input['author'] ) ) {
295 $author_id = $this->parse_and_set_author( $input['author'], $input['type'] );
296 unset( $input['author'] );
297 if ( is_wp_error( $author_id ) ) {
298 return $author_id;
299 }
300 }
301
302 if ( 'publish' === $input['status'] ) {
303 if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
304 if ( current_user_can( $post_type->cap->edit_posts ) ) {
305 $input['status'] = 'pending';
306 } else {
307 return new WP_Error( 'unauthorized', 'User cannot publish posts', 403 );
308 }
309 }
310 } elseif ( ! current_user_can( $post_type->cap->edit_posts ) ) {
311 return new WP_Error( 'unauthorized', 'User cannot edit posts', 403 );
312 }
313 } else {
314 $input = $this->input( false );
315
316 if ( ! is_array( $input ) || ! $input ) {
317 return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
318 }
319
320 if ( isset( $input['status'] ) && 'trash' === $input['status'] && ! current_user_can( 'delete_post', $post_id ) ) {
321 return new WP_Error( 'unauthorized', 'User cannot delete post', 403 );
322 }
323
324 $post = get_post( $post_id );
325 $_post_type = ( ! empty( $input['type'] ) ) ? $input['type'] : $post->post_type;
326 $post_type = get_post_type_object( $_post_type );
327 if ( ! $post || is_wp_error( $post ) ) {
328 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
329 }
330
331 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
332 return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
333 }
334
335 if ( ! empty( $input['author'] ) ) {
336 $author_id = $this->parse_and_set_author( $input['author'], $_post_type );
337 unset( $input['author'] );
338 if ( is_wp_error( $author_id ) ) {
339 return $author_id;
340 }
341 }
342
343 if ( ( isset( $input['status'] ) && 'publish' === $input['status'] ) && 'publish' !== $post->post_status && ! current_user_can( 'publish_post', $post->ID ) ) {
344 $input['status'] = 'pending';
345 }
346 $last_status = $post->post_status;
347 $new_status = $input['status'] ?? $last_status;
348
349 // Make sure that drafts get the current date when transitioning to publish if not supplied in the post.
350 $date_in_past = ( strtotime( $post->post_date_gmt ) < time() );
351 if ( 'publish' === $new_status && 'draft' === $last_status && ! isset( $input['date_gmt'] ) && $date_in_past ) {
352 $input['date_gmt'] = gmdate( 'Y-m-d H:i:s' );
353 }
354
355 // Untrash a post so that the proper hooks get called as well as the comments get untrashed.
356 if ( 'trash' === $last_status && 'trash' !== $new_status && isset( $post->ID ) ) {
357 wp_untrash_post( $post->ID );
358 $untashed_post = get_post( $post->ID );
359 // Lets make sure that we use the revert the slug.
360 if ( isset( $untashed_post->post_name ) && $untashed_post->post_name . '__trashed' === $input['slug'] ) {
361 unset( $input['slug'] );
362 }
363 }
364 }
365
366 if ( function_exists( 'wpcom_switch_to_locale' ) ) {
367 // fixes calypso-pre-oss #12476: respect blog locale when creating the post slug.
368 wpcom_switch_to_locale( get_blog_lang_code( $blog_id ) );
369 }
370
371 // If date was set, $this->input will set date_gmt, date still needs to be adjusted for the blog's offset.
372 if ( isset( $input['date_gmt'] ) ) {
373 $gmt_offset = get_option( 'gmt_offset' );
374 $time_with_offset = strtotime( $input['date_gmt'] ) + $gmt_offset * HOUR_IN_SECONDS;
375 $input['date'] = gmdate( 'Y-m-d H:i:s', $time_with_offset );
376 }
377
378 if ( ! empty( $author_id ) && get_current_user_id() !== $author_id ) {
379 if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
380 return new WP_Error( 'unauthorized', "User is not allowed to publish others' posts.", 403 );
381 } elseif ( ! user_can( $author_id, $post_type->cap->edit_posts ) ) {
382 return new WP_Error( 'unauthorized', 'Assigned author cannot publish post.', 403 );
383 }
384 }
385
386 if ( ! is_post_type_hierarchical( $post_type->name ) && 'revision' !== $post_type->name ) {
387 unset( $input['parent'] );
388 }
389
390 $tax_input = array();
391
392 foreach ( array(
393 'categories' => 'category',
394 'tags' => 'post_tag',
395 ) as $key => $taxonomy ) {
396 if ( ! isset( $input[ $key ] ) ) {
397 continue;
398 }
399
400 $tax_input[ $taxonomy ] = array();
401
402 $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
403
404 if ( is_array( $input[ $key ] ) ) {
405 $terms = $input[ $key ];
406 } else {
407 $terms = explode( ',', $input[ $key ] );
408 }
409
410 foreach ( $terms as $term ) {
411 /**
412 * `curl --data 'category[]=123'` should be interpreted as a category ID,
413 * not a category whose name is '123'.
414 *
415 * Consequence: To add a category/tag whose name is '123', the client must
416 * first look up its ID.
417 */
418 $term = (string) $term; // ctype_digit compat.
419 if ( ctype_digit( $term ) ) {
420 $term = (int) $term;
421 }
422
423 $term_info = term_exists( $term, $taxonomy );
424
425 if ( ! $term_info ) {
426 // A term ID that doesn't already exist. Ignore it: we don't know what name to give it.
427 if ( is_int( $term ) ) {
428 continue;
429 }
430 // only add a new tag/cat if the user has access to.
431 $tax = get_taxonomy( $taxonomy );
432
433 // see https://core.trac.wordpress.org/ticket/26409 .
434 if ( 'category' === $taxonomy && ! current_user_can( $tax->cap->edit_terms ) ) {
435 continue;
436 } elseif ( ! current_user_can( $tax->cap->assign_terms ) ) {
437 continue;
438 }
439
440 $term_info = wp_insert_term( $term, $taxonomy );
441 }
442
443 if ( ! is_wp_error( $term_info ) ) {
444 if ( $is_hierarchical ) {
445 // Categories must be added by ID.
446 $tax_input[ $taxonomy ][] = (int) $term_info['term_id'];
447 } elseif ( is_int( $term ) ) { // Tags must be added by name.
448 $term = get_term( $term, $taxonomy );
449 $tax_input[ $taxonomy ][] = $term->name;
450 } else {
451 $tax_input[ $taxonomy ][] = $term;
452 }
453 }
454 }
455 }
456
457 if ( isset( $input['categories'] ) && empty( $tax_input['category'] ) && 'revision' !== $post_type->name ) {
458 $tax_input['category'][] = get_option( 'default_category' );
459 }
460
461 unset( $input['tags'], $input['categories'] );
462
463 $insert = array();
464
465 if ( ! empty( $input['slug'] ) ) {
466 $insert['post_name'] = $input['slug'];
467 unset( $input['slug'] );
468 }
469
470 if ( isset( $input['comments_open'] ) ) {
471 $insert['comment_status'] = ( true === $input['comments_open'] ) ? 'open' : 'closed';
472 }
473
474 if ( isset( $input['pings_open'] ) ) {
475 $insert['ping_status'] = ( true === $input['pings_open'] ) ? 'open' : 'closed';
476 }
477
478 unset( $input['comments_open'], $input['pings_open'] );
479
480 if ( isset( $input['menu_order'] ) ) {
481 $insert['menu_order'] = $input['menu_order'];
482 unset( $input['menu_order'] );
483 }
484
485 $publicize = $input['publicize'] ?? null;
486 unset( $input['publicize'] );
487
488 $publicize_custom_message = $input['publicize_message'] ?? null;
489 unset( $input['publicize_message'] );
490
491 if ( isset( $input['featured_image'] ) ) {
492 $featured_image = trim( $input['featured_image'] );
493 $delete_featured_image = empty( $featured_image );
494 unset( $input['featured_image'] );
495 }
496
497 $metadata = $input['metadata'] ?? null;
498 unset( $input['metadata'] );
499
500 $likes = $input['likes_enabled'] ?? null;
501 unset( $input['likes_enabled'] );
502
503 $sharing = $input['sharing_enabled'] ?? null;
504 unset( $input['sharing_enabled'] );
505
506 $sticky = $input['sticky'] ?? null;
507 unset( $input['sticky'] );
508
509 foreach ( $input as $key => $value ) {
510 $insert[ "post_$key" ] = $value;
511 }
512
513 if ( ! empty( $author_id ) ) {
514 $insert['post_author'] = absint( $author_id );
515 }
516
517 if ( ! empty( $tax_input ) ) {
518 $insert['tax_input'] = $tax_input;
519 }
520
521 $has_media = isset( $input['media'] ) && $input['media'] ? count( $input['media'] ) : false;
522 $has_media_by_url = isset( $input['media_urls'] ) && $input['media_urls'] ? count( $input['media_urls'] ) : false;
523
524 if ( $new ) {
525
526 if ( isset( $input['content'] ) && ! has_shortcode( $input['content'], 'gallery' ) && ( $has_media || $has_media_by_url ) ) {
527 switch ( ( $has_media + $has_media_by_url ) ) {
528 case 0:
529 // No images - do nothing.
530 break;
531 case 1:
532 // 1 image - make it big.
533 $input['content'] = "[gallery size=full columns=1]\n\n" . $input['content'];
534 $insert['post_content'] = $input['content'];
535 break;
536 default:
537 // Several images - 3 column gallery.
538 $input['content'] = "[gallery]\n\n" . $input['content'];
539 $insert['post_content'] = $input['content'];
540 break;
541 }
542 }
543
544 $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
545 } else {
546 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable -- $post is set and validated several blocks earlier if $new (only set once) is falsy.
547 $insert['ID'] = $post->ID;
548
549 // wp_update_post ignores date unless edit_date is set
550 // See: https://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts
551 // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302 .
552 if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
553 $insert['edit_date'] = true;
554 }
555
556 // this two-step process ensures any changes submitted along with status=trash get saved before trashing.
557 if ( isset( $input['status'] ) && 'trash' === $input['status'] ) {
558 // if we insert it with status='trash', it will get double-trashed, so insert it as a draft first.
559 unset( $insert['status'] );
560 $post_id = wp_update_post( (object) $insert );
561 // now call wp_trash_post so post_meta gets set and any filters get called.
562 wp_trash_post( $post_id );
563 } else {
564 $post_id = wp_update_post( (object) $insert );
565 }
566 }
567
568 if ( ! $post_id || is_wp_error( $post_id ) ) {
569 return $post_id;
570 }
571
572 // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint).
573 $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
574 if ( is_wp_error( $post_check ) ) {
575 return $post_check;
576 }
577
578 if ( $has_media ) {
579 $this->api->trap_wp_die( 'upload_error' );
580 foreach ( $input['media'] as $media_item ) {
581 $_FILES['.api.media.item.'] = $media_item;
582 // check for WP_Error if we ever actually need $media_id .
583 $media_id = media_handle_upload( '.api.media.item.', $post_id ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
584 }
585 $this->api->trap_wp_die( null );
586
587 unset( $_FILES['.api.media.item.'] );
588 }
589
590 if ( $has_media_by_url ) {
591 foreach ( $input['media_urls'] as $url ) {
592 $this->handle_media_sideload( $url, $post_id );
593 }
594 }
595
596 // Set like status for the post.
597 /** This filter is documented in modules/likes.php */
598 $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
599 if ( $new ) {
600 if ( $sitewide_likes_enabled ) {
601 if ( false === $likes ) {
602 update_post_meta( $post_id, 'switch_like_status', 0 );
603 } else {
604 delete_post_meta( $post_id, 'switch_like_status' );
605 }
606 } elseif ( $likes ) {
607 update_post_meta( $post_id, 'switch_like_status', 1 );
608 } else {
609 delete_post_meta( $post_id, 'switch_like_status' );
610 }
611 } elseif ( isset( $likes ) ) {
612 if ( $sitewide_likes_enabled ) {
613 if ( false === $likes ) {
614 update_post_meta( $post_id, 'switch_like_status', 0 );
615 } else {
616 delete_post_meta( $post_id, 'switch_like_status' );
617 }
618 } elseif ( true === $likes ) {
619 update_post_meta( $post_id, 'switch_like_status', 1 );
620 } else {
621 delete_post_meta( $post_id, 'switch_like_status' );
622 }
623 }
624
625 // Set sharing status of the post.
626 if ( $new ) {
627 $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
628 if ( ! $sharing_enabled ) {
629 update_post_meta( $post_id, 'sharing_disabled', 1 );
630 }
631 } elseif ( isset( $sharing ) && true === $sharing ) {
632 delete_post_meta( $post_id, 'sharing_disabled' );
633 } elseif ( isset( $sharing ) && false == $sharing ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
634 update_post_meta( $post_id, 'sharing_disabled', 1 );
635 }
636
637 if ( isset( $sticky ) ) {
638 if ( true === $sticky ) {
639 stick_post( $post_id );
640 } else {
641 unstick_post( $post_id );
642 }
643 }
644
645 // WPCOM Specific (Jetpack's will get bumped elsewhere
646 // Tracks how many posts are published and sets meta
647 // so we can track some other cool stats (like likes & comments on posts published).
648 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
649 if (
650 ( $new && 'publish' === $input['status'] )
651 || (
652 ! $new && isset( $last_status )
653 && 'publish' !== $last_status
654 && isset( $new_status )
655 && 'publish' === $new_status
656 )
657 ) {
658 /** This action is documented in modules/widgets/social-media-icons.php */
659 do_action( 'jetpack_bump_stats_extras', 'api-insights-posts', $this->api->token_details['client_id'] );
660 update_post_meta( $post_id, '_rest_api_published', 1 );
661 update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
662 }
663 }
664
665 // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
666 // to instead flag the ones we don't want to be skipped. proceed with said logic.
667 // any posts coming from Path (client ID 25952) should also not publicize.
668 if ( false === $publicize || ( isset( $this->api->token_details['client_id'] ) && 25952 === (int) $this->api->token_details['client_id'] ) ) {
669 // No publicize at all, skip all by ID.
670 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
671 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
672 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
673 if ( ! $service_connections ) {
674 continue;
675 }
676 foreach ( $service_connections as $service_connection ) {
677 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
678 }
679 }
680 } elseif ( is_array( $publicize ) && ( $publicize !== array() ) ) {
681 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
682 /*
683 * We support both indexed and associative arrays:
684 * * indexed are to pass entire services
685 * * associative are to pass specific connections per service
686 *
687 * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
688 *
689 * EG: array( 'linkedin', 'facebook') will only publicize to those, ignoring the other available services
690 * Form data: publicize[]=linkedin&publicize[]=facebook
691 * 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.
692 * Form data: publicize[linkedin]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
693 * 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
694 * Form data: publicize[]=linkedin&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
695 */
696
697 // Delete any stale SKIP value for the service by name. We'll add it back by ID.
698 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
699
700 // Get the user's connections.
701 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
702
703 // if the user doesn't have any connections for this service, move on.
704 if ( ! $service_connections ) {
705 continue;
706 }
707
708 if ( ! in_array( $name, $publicize, true ) && ! array_key_exists( $name, $publicize ) ) {
709 // Skip the whole service by adding each connection ID.
710 foreach ( $service_connections as $service_connection ) {
711 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
712 }
713 } elseif ( ! empty( $publicize[ $name ] ) ) {
714 // Seems we're being asked to only push to [a] specific connection[s].
715 // Explode the list on commas, which will also support a single passed ID.
716 $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
717 // Flag the connections we can't match with the requested list to be skipped.
718 foreach ( $service_connections as $service_connection ) {
719 if ( ! in_array( $service_connection->meta['connection_data']->id, $requested_connections, true ) ) {
720 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
721 } else {
722 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
723 }
724 }
725 } else {
726 // delete all SKIP values; it's okay to publish to all connected IDs for this service.
727 foreach ( $service_connections as $service_connection ) {
728 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
729 }
730 }
731 }
732 }
733
734 if ( $publicize_custom_message !== null ) {
735 if ( empty( $publicize_custom_message ) ) {
736 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS );
737 } else {
738 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
739 }
740 }
741
742 if ( ! empty( $insert['post_format'] ) ) {
743 if ( 'default' !== strtolower( $insert['post_format'] ) ) {
744 set_post_format( $post_id, $insert['post_format'] );
745 } else {
746 set_post_format( $post_id, get_option( 'default_post_format' ) );
747 }
748 }
749
750 if ( isset( $featured_image ) ) {
751 $this->parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
752 }
753
754 if ( ! empty( $metadata ) ) {
755 foreach ( (array) $metadata as $meta ) {
756
757 $meta = (object) $meta;
758
759 if (
760 in_array( $meta->key ?? null, Jetpack_SEO_Posts::POST_META_KEYS_ARRAY, true ) &&
761 ! Jetpack_SEO_Utils::is_enabled_jetpack_seo()
762 ) {
763 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
764 }
765
766 $existing_meta_item = new stdClass();
767
768 if ( empty( $meta->operation ) ) {
769 $meta->operation = 'update';
770 }
771
772 if ( ! empty( $meta->value ) ) {
773 if ( 'true' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
774 $meta->value = true;
775 }
776 if ( 'false' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
777 $meta->value = false;
778 }
779 }
780
781 if ( ! empty( $meta->id ) ) {
782 $meta->id = absint( $meta->id );
783 $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
784 if ( $post_id !== (int) $existing_meta_item->post_id ) {
785 // Only allow updates for metadata on this post.
786 continue;
787 }
788 }
789
790 $unslashed_meta_key = isset( $meta->key ) ? wp_unslash( $meta->key ) : null; // should match what the final key will be.
791 $meta->key = isset( $meta->key ) ? wp_slash( $meta->key ) : null;
792 $unslashed_existing_meta_key = isset( $existing_meta_item->meta_key ) ? wp_unslash( $existing_meta_item->meta_key ) : null;
793 $existing_meta_item->meta_key = isset( $existing_meta_item->meta_key ) ? wp_slash( $existing_meta_item->meta_key ) : null;
794
795 // make sure that the meta id passed matches the existing meta key.
796 if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
797 $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
798 if ( $meta_by_id->meta_key !== $meta->key ) {
799 continue; // skip this meta.
800 }
801 }
802
803 switch ( $meta->operation ) {
804 case 'delete':
805 if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
806 delete_metadata_by_mid( 'post', $meta->id );
807 } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
808 delete_post_meta( $post_id, $meta->key, $meta->previous_value );
809 } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
810 delete_post_meta( $post_id, $meta->key );
811 }
812
813 break;
814 case 'add':
815 if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
816 break;
817 } 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 ) ) {
818 add_post_meta( $post_id, $meta->key, $meta->value );
819 }
820
821 break;
822 case 'update':
823 if ( ! isset( $meta->value ) ) {
824 break;
825 } 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 ) ) ) {
826 update_metadata_by_mid( 'post', $meta->id, $meta->value );
827 } 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 ) ) ) {
828 update_post_meta( $post_id, $meta->key, $meta->value, $meta->previous_value );
829 } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) ) {
830 update_post_meta( $post_id, $meta->key, $meta->value );
831 }
832
833 break;
834 }
835 }
836 }
837
838 /**
839 * Fires when a post is created via the REST API.
840 *
841 * @module json-api
842 *
843 * @since 2.3.0
844 *
845 * @param int $post_id Post ID.
846 * @param array $insert Data used to build the post.
847 * @param string $new New post URL suffix.
848 */
849 do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
850
851 $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
852 if ( ! $return || is_wp_error( $return ) ) {
853 return $return;
854 }
855
856 if ( isset( $input['type'] ) && 'revision' === $input['type'] ) {
857 $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
858 }
859
860 if ( isset( $sticky ) ) {
861 // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above.
862 $return['sticky'] = ( true === $sticky );
863 }
864
865 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
866 do_action( 'wpcom_json_api_objects', 'posts' );
867
868 return $return;
869 }
870
871 /**
872 * Delete a post.
873 *
874 * /sites/%s/posts/%d/delete -> $blog_id, $post_id
875 *
876 * @param string $path API path.
877 * @param array $blog_id Blog ID.
878 * @param array $post_id Post ID.
879 *
880 * @return array|WP_Error
881 */
882 public function delete_post( $path, $blog_id, $post_id ) {
883 $post = get_post( $post_id );
884 if ( ! $post || is_wp_error( $post ) ) {
885 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
886 }
887
888 if ( ! $this->is_post_type_allowed( $post->post_type ) ) {
889 return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
890 }
891
892 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
893 return new WP_Error( 'unauthorized', 'User cannot delete posts', 403 );
894 }
895
896 $args = $this->query_args();
897 $return = $this->get_post_by( 'ID', $post->ID, $args['context'] );
898 if ( ! $return || is_wp_error( $return ) ) {
899 return $return;
900 }
901
902 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
903 do_action( 'wpcom_json_api_objects', 'posts' );
904
905 // we need to call wp_trash_post so that untrash will work correctly for all post types.
906 if ( 'trash' === $post->post_status ) {
907 wp_delete_post( $post->ID );
908 } else {
909 wp_trash_post( $post->ID );
910 }
911
912 $status = get_post_status( $post->ID );
913 if ( false === $status ) {
914 $return['status'] = 'deleted';
915 return $return;
916 }
917
918 return $this->get_post_by( 'ID', $post->ID, $args['context'] );
919 }
920
921 /**
922 * Restore a post.
923 *
924 * /sites/%s/posts/%d/restore -> $blog_id, $post_id
925 *
926 * @param string $path API path.
927 * @param int $blog_id Blog ID.
928 * @param int $post_id Post ID.
929 *
930 * @return array|WP_Error
931 */
932 public function restore_post( $path, $blog_id, $post_id ) {
933 $args = $this->query_args();
934 $post = get_post( $post_id );
935
936 if ( ! $post || is_wp_error( $post ) ) {
937 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
938 }
939
940 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
941 return new WP_Error( 'unauthorized', 'User cannot restore trashed posts', 403 );
942 }
943
944 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
945 do_action( 'wpcom_json_api_objects', 'posts' );
946
947 wp_untrash_post( $post->ID );
948
949 return $this->get_post_by( 'ID', $post->ID, $args['context'] );
950 }
951
952 /**
953 * Set or delete a post's featured image.
954 *
955 * @param int $post_id Post ID.
956 * @param bool $delete_featured_image Whether to delete the featured image.
957 * @param int $featured_image Thumbnail ID to attach.
958 *
959 * @return null|int|bool
960 */
961 private function parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image ) {
962 if ( $delete_featured_image ) {
963 delete_post_thumbnail( $post_id );
964 return;
965 }
966
967 $featured_image = (string) $featured_image;
968
969 // if we got a post ID, we can just set it as the thumbnail.
970 if ( ctype_digit( $featured_image ) && 'attachment' === get_post_type( $featured_image ) ) {
971 set_post_thumbnail( $post_id, $featured_image );
972 return $featured_image;
973 }
974
975 $featured_image_id = $this->handle_media_sideload( $featured_image, $post_id, 'image' );
976
977 if ( empty( $featured_image_id ) || ! is_int( $featured_image_id ) ) {
978 return false;
979 }
980
981 set_post_thumbnail( $post_id, $featured_image_id );
982 return $featured_image_id;
983 }
984
985 /**
986 * Get the Author ID for a post.
987 *
988 * @param int|string $author Author ID.
989 * @param string $post_type Post type.
990 *
991 * @return int|WP_Error
992 */
993 private function parse_and_set_author( $author = null, $post_type = 'post' ) {
994 if ( empty( $author ) || ! post_type_supports( $post_type, 'author' ) ) {
995 return get_current_user_id();
996 }
997
998 $author = (string) $author;
999 if ( ctype_digit( $author ) ) {
1000 $_user = get_user_by( 'id', $author );
1001 if ( ! $_user || is_wp_error( $_user ) ) {
1002 return new WP_Error( 'invalid_author', 'Invalid author provided' );
1003 }
1004
1005 return $_user->ID;
1006 }
1007
1008 $_user = get_user_by( 'login', $author );
1009 if ( ! $_user || is_wp_error( $_user ) ) {
1010 return new WP_Error( 'invalid_author', 'Invalid author provided' );
1011 }
1012
1013 return $_user->ID;
1014 }
1015 }
1016