PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.2.1
Jetpack – WP Security, Backup, Speed, & Growth v15.2.1
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-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 10 months ago class.wpcom-json-api-add-widget-endpoint.php 1 year ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 1 year ago class.wpcom-json-api-bulk-delete-post-endpoint.php 1 year ago class.wpcom-json-api-bulk-restore-post-endpoint.php 1 year ago class.wpcom-json-api-bulk-update-comments-endpoint.php 1 year ago class.wpcom-json-api-comment-endpoint.php 1 year ago class.wpcom-json-api-delete-media-endpoint.php 1 year ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 1 year ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 1 year ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 1 year ago class.wpcom-json-api-get-comment-counts-endpoint.php 1 year ago class.wpcom-json-api-get-comment-endpoint.php 1 year ago class.wpcom-json-api-get-comment-history-endpoint.php 1 year ago class.wpcom-json-api-get-comments-tree-endpoint.php 1 year ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 1 year ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 1 year ago class.wpcom-json-api-get-customcss.php 1 year ago class.wpcom-json-api-get-media-endpoint.php 1 year ago class.wpcom-json-api-get-media-v1-1-endpoint.php 1 year ago class.wpcom-json-api-get-media-v1-2-endpoint.php 1 year ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 10 months ago class.wpcom-json-api-get-post-endpoint.php 1 year ago class.wpcom-json-api-get-post-v1-1-endpoint.php 1 year ago class.wpcom-json-api-get-site-endpoint.php 10 months ago class.wpcom-json-api-get-site-v1-2-endpoint.php 1 year ago class.wpcom-json-api-get-taxonomies-endpoint.php 1 year ago class.wpcom-json-api-get-taxonomy-endpoint.php 1 year ago class.wpcom-json-api-get-term-endpoint.php 1 year ago class.wpcom-json-api-list-comments-endpoint.php 1 year ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 1 year ago class.wpcom-json-api-list-embeds-endpoint.php 1 year ago class.wpcom-json-api-list-media-endpoint.php 1 year ago class.wpcom-json-api-list-media-v1-1-endpoint.php 1 year ago class.wpcom-json-api-list-media-v1-2-endpoint.php 1 year ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 1 year ago class.wpcom-json-api-list-post-types-endpoint.php 1 year ago class.wpcom-json-api-list-posts-endpoint.php 1 year ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 11 months ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 11 months ago class.wpcom-json-api-list-roles-endpoint.php 1 year ago class.wpcom-json-api-list-shortcodes-endpoint.php 1 year ago class.wpcom-json-api-list-terms-endpoint.php 1 year ago class.wpcom-json-api-list-users-endpoint.php 10 months ago class.wpcom-json-api-menus-v1-1-endpoint.php 11 months ago class.wpcom-json-api-post-endpoint.php 1 year ago class.wpcom-json-api-post-v1-1-endpoint.php 1 year ago class.wpcom-json-api-render-embed-endpoint.php 1 year ago class.wpcom-json-api-render-embed-reversal-endpoint.php 1 year ago class.wpcom-json-api-render-endpoint.php 1 year ago class.wpcom-json-api-render-shortcode-endpoint.php 1 year ago class.wpcom-json-api-sharing-buttons-endpoint.php 1 year ago class.wpcom-json-api-site-settings-endpoint.php 10 months ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 1 year ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 1 year ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 1 year ago class.wpcom-json-api-site-user-endpoint.php 11 months ago class.wpcom-json-api-taxonomy-endpoint.php 1 year ago class.wpcom-json-api-update-comment-endpoint.php 1 year ago class.wpcom-json-api-update-customcss.php 1 year ago class.wpcom-json-api-update-media-endpoint.php 1 year ago class.wpcom-json-api-update-media-v1-1-endpoint.php 1 year ago class.wpcom-json-api-update-post-endpoint.php 1 year ago class.wpcom-json-api-update-post-v1-1-endpoint.php 1 year ago class.wpcom-json-api-update-post-v1-2-endpoint.php 1 year ago class.wpcom-json-api-update-site-homepage-endpoint.php 1 year ago class.wpcom-json-api-update-site-logo-endpoint.php 1 year ago class.wpcom-json-api-update-taxonomy-endpoint.php 1 year ago class.wpcom-json-api-update-term-endpoint.php 1 year ago class.wpcom-json-api-update-user-endpoint.php 11 months ago class.wpcom-json-api-upload-media-endpoint.php 1 year ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 10 months ago
class.wpcom-json-api-update-post-endpoint.php
1015 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 = isset( $input['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 = isset( $input['publicize'] ) ? $input['publicize'] : null;
486 unset( $input['publicize'] );
487
488 $publicize_custom_message = isset( $input['publicize_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 = isset( $input['metadata'] ) ? $input['metadata'] : null;
498 unset( $input['metadata'] );
499
500 $likes = isset( $input['likes_enabled'] ) ? $input['likes_enabled'] : null;
501 unset( $input['likes_enabled'] );
502
503 $sharing = isset( $input['sharing_enabled'] ) ? $input['sharing_enabled'] : null;
504 unset( $input['sharing_enabled'] );
505
506 $sticky = isset( $input['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 $insert['ID'] = $post->ID;
547
548 // wp_update_post ignores date unless edit_date is set
549 // See: https://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts
550 // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302 .
551 if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
552 $insert['edit_date'] = true;
553 }
554
555 // this two-step process ensures any changes submitted along with status=trash get saved before trashing.
556 if ( isset( $input['status'] ) && 'trash' === $input['status'] ) {
557 // if we insert it with status='trash', it will get double-trashed, so insert it as a draft first.
558 unset( $insert['status'] );
559 $post_id = wp_update_post( (object) $insert );
560 // now call wp_trash_post so post_meta gets set and any filters get called.
561 wp_trash_post( $post_id );
562 } else {
563 $post_id = wp_update_post( (object) $insert );
564 }
565 }
566
567 if ( ! $post_id || is_wp_error( $post_id ) ) {
568 return $post_id;
569 }
570
571 // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint).
572 $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
573 if ( is_wp_error( $post_check ) ) {
574 return $post_check;
575 }
576
577 if ( $has_media ) {
578 $this->api->trap_wp_die( 'upload_error' );
579 foreach ( $input['media'] as $media_item ) {
580 $_FILES['.api.media.item.'] = $media_item;
581 // check for WP_Error if we ever actually need $media_id .
582 $media_id = media_handle_upload( '.api.media.item.', $post_id ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
583 }
584 $this->api->trap_wp_die( null );
585
586 unset( $_FILES['.api.media.item.'] );
587 }
588
589 if ( $has_media_by_url ) {
590 foreach ( $input['media_urls'] as $url ) {
591 $this->handle_media_sideload( $url, $post_id );
592 }
593 }
594
595 // Set like status for the post.
596 /** This filter is documented in modules/likes.php */
597 $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
598 if ( $new ) {
599 if ( $sitewide_likes_enabled ) {
600 if ( false === $likes ) {
601 update_post_meta( $post_id, 'switch_like_status', 0 );
602 } else {
603 delete_post_meta( $post_id, 'switch_like_status' );
604 }
605 } elseif ( $likes ) {
606 update_post_meta( $post_id, 'switch_like_status', 1 );
607 } else {
608 delete_post_meta( $post_id, 'switch_like_status' );
609 }
610 } elseif ( isset( $likes ) ) {
611 if ( $sitewide_likes_enabled ) {
612 if ( false === $likes ) {
613 update_post_meta( $post_id, 'switch_like_status', 0 );
614 } else {
615 delete_post_meta( $post_id, 'switch_like_status' );
616 }
617 } elseif ( true === $likes ) {
618 update_post_meta( $post_id, 'switch_like_status', 1 );
619 } else {
620 delete_post_meta( $post_id, 'switch_like_status' );
621 }
622 }
623
624 // Set sharing status of the post.
625 if ( $new ) {
626 $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
627 if ( false === $sharing_enabled ) {
628 update_post_meta( $post_id, 'sharing_disabled', 1 );
629 }
630 } elseif ( isset( $sharing ) && true === $sharing ) {
631 delete_post_meta( $post_id, 'sharing_disabled' );
632 } elseif ( isset( $sharing ) && false == $sharing ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
633 update_post_meta( $post_id, 'sharing_disabled', 1 );
634 }
635
636 if ( isset( $sticky ) ) {
637 if ( true === $sticky ) {
638 stick_post( $post_id );
639 } else {
640 unstick_post( $post_id );
641 }
642 }
643
644 // WPCOM Specific (Jetpack's will get bumped elsewhere
645 // Tracks how many posts are published and sets meta
646 // so we can track some other cool stats (like likes & comments on posts published).
647 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
648 if (
649 ( $new && 'publish' === $input['status'] )
650 || (
651 ! $new && isset( $last_status )
652 && 'publish' !== $last_status
653 && isset( $new_status )
654 && 'publish' === $new_status
655 )
656 ) {
657 /** This action is documented in modules/widgets/social-media-icons.php */
658 do_action( 'jetpack_bump_stats_extras', 'api-insights-posts', $this->api->token_details['client_id'] );
659 update_post_meta( $post_id, '_rest_api_published', 1 );
660 update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
661 }
662 }
663
664 // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
665 // to instead flag the ones we don't want to be skipped. proceed with said logic.
666 // any posts coming from Path (client ID 25952) should also not publicize.
667 if ( false === $publicize || ( isset( $this->api->token_details['client_id'] ) && 25952 === (int) $this->api->token_details['client_id'] ) ) {
668 // No publicize at all, skip all by ID.
669 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
670 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
671 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
672 if ( ! $service_connections ) {
673 continue;
674 }
675 foreach ( $service_connections as $service_connection ) {
676 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
677 }
678 }
679 } elseif ( is_array( $publicize ) && ( $publicize !== array() ) ) {
680 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
681 /*
682 * We support both indexed and associative arrays:
683 * * indexed are to pass entire services
684 * * associative are to pass specific connections per service
685 *
686 * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
687 *
688 * EG: array( 'linkedin', 'facebook') will only publicize to those, ignoring the other available services
689 * Form data: publicize[]=linkedin&publicize[]=facebook
690 * 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.
691 * Form data: publicize[linkedin]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
692 * 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
693 * Form data: publicize[]=linkedin&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
694 */
695
696 // Delete any stale SKIP value for the service by name. We'll add it back by ID.
697 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
698
699 // Get the user's connections.
700 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
701
702 // if the user doesn't have any connections for this service, move on.
703 if ( ! $service_connections ) {
704 continue;
705 }
706
707 if ( ! in_array( $name, $publicize, true ) && ! array_key_exists( $name, $publicize ) ) {
708 // Skip the whole service by adding each connection ID.
709 foreach ( $service_connections as $service_connection ) {
710 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
711 }
712 } elseif ( ! empty( $publicize[ $name ] ) ) {
713 // Seems we're being asked to only push to [a] specific connection[s].
714 // Explode the list on commas, which will also support a single passed ID.
715 $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
716 // Flag the connections we can't match with the requested list to be skipped.
717 foreach ( $service_connections as $service_connection ) {
718 if ( ! in_array( $service_connection->meta['connection_data']->id, $requested_connections, true ) ) {
719 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
720 } else {
721 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
722 }
723 }
724 } else {
725 // delete all SKIP values; it's okay to publish to all connected IDs for this service.
726 foreach ( $service_connections as $service_connection ) {
727 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
728 }
729 }
730 }
731 }
732
733 if ( $publicize_custom_message !== null ) {
734 if ( empty( $publicize_custom_message ) ) {
735 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS );
736 } else {
737 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
738 }
739 }
740
741 if ( ! empty( $insert['post_format'] ) ) {
742 if ( 'default' !== strtolower( $insert['post_format'] ) ) {
743 set_post_format( $post_id, $insert['post_format'] );
744 } else {
745 set_post_format( $post_id, get_option( 'default_post_format' ) );
746 }
747 }
748
749 if ( isset( $featured_image ) ) {
750 $this->parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
751 }
752
753 if ( ! empty( $metadata ) ) {
754 foreach ( (array) $metadata as $meta ) {
755
756 $meta = (object) $meta;
757
758 if (
759 in_array( isset( $meta->key ) ? $meta->key : null, Jetpack_SEO_Posts::POST_META_KEYS_ARRAY, true ) &&
760 ! Jetpack_SEO_Utils::is_enabled_jetpack_seo()
761 ) {
762 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
763 }
764
765 $existing_meta_item = new stdClass();
766
767 if ( empty( $meta->operation ) ) {
768 $meta->operation = 'update';
769 }
770
771 if ( ! empty( $meta->value ) ) {
772 if ( 'true' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
773 $meta->value = true;
774 }
775 if ( 'false' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
776 $meta->value = false;
777 }
778 }
779
780 if ( ! empty( $meta->id ) ) {
781 $meta->id = absint( $meta->id );
782 $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
783 if ( $post_id !== (int) $existing_meta_item->post_id ) {
784 // Only allow updates for metadata on this post.
785 continue;
786 }
787 }
788
789 $unslashed_meta_key = isset( $meta->key ) ? wp_unslash( $meta->key ) : null; // should match what the final key will be.
790 $meta->key = isset( $meta->key ) ? wp_slash( $meta->key ) : null;
791 $unslashed_existing_meta_key = isset( $existing_meta_item->meta_key ) ? wp_unslash( $existing_meta_item->meta_key ) : null;
792 $existing_meta_item->meta_key = isset( $existing_meta_item->meta_key ) ? wp_slash( $existing_meta_item->meta_key ) : null;
793
794 // make sure that the meta id passed matches the existing meta key.
795 if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
796 $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
797 if ( $meta_by_id->meta_key !== $meta->key ) {
798 continue; // skip this meta.
799 }
800 }
801
802 switch ( $meta->operation ) {
803 case 'delete':
804 if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
805 delete_metadata_by_mid( 'post', $meta->id );
806 } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
807 delete_post_meta( $post_id, $meta->key, $meta->previous_value );
808 } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
809 delete_post_meta( $post_id, $meta->key );
810 }
811
812 break;
813 case 'add':
814 if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
815 break;
816 } 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 ) ) {
817 add_post_meta( $post_id, $meta->key, $meta->value );
818 }
819
820 break;
821 case 'update':
822 if ( ! isset( $meta->value ) ) {
823 break;
824 } 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 ) ) ) {
825 update_metadata_by_mid( 'post', $meta->id, $meta->value );
826 } 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 ) ) ) {
827 update_post_meta( $post_id, $meta->key, $meta->value, $meta->previous_value );
828 } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) ) {
829 update_post_meta( $post_id, $meta->key, $meta->value );
830 }
831
832 break;
833 }
834 }
835 }
836
837 /**
838 * Fires when a post is created via the REST API.
839 *
840 * @module json-api
841 *
842 * @since 2.3.0
843 *
844 * @param int $post_id Post ID.
845 * @param array $insert Data used to build the post.
846 * @param string $new New post URL suffix.
847 */
848 do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
849
850 $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
851 if ( ! $return || is_wp_error( $return ) ) {
852 return $return;
853 }
854
855 if ( isset( $input['type'] ) && 'revision' === $input['type'] ) {
856 $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
857 }
858
859 if ( isset( $sticky ) ) {
860 // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above.
861 $return['sticky'] = ( true === $sticky );
862 }
863
864 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
865 do_action( 'wpcom_json_api_objects', 'posts' );
866
867 return $return;
868 }
869
870 /**
871 * Delete a post.
872 *
873 * /sites/%s/posts/%d/delete -> $blog_id, $post_id
874 *
875 * @param string $path API path.
876 * @param array $blog_id Blog ID.
877 * @param array $post_id Post ID.
878 *
879 * @return array|WP_Error
880 */
881 public function delete_post( $path, $blog_id, $post_id ) {
882 $post = get_post( $post_id );
883 if ( ! $post || is_wp_error( $post ) ) {
884 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
885 }
886
887 if ( ! $this->is_post_type_allowed( $post->post_type ) ) {
888 return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
889 }
890
891 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
892 return new WP_Error( 'unauthorized', 'User cannot delete posts', 403 );
893 }
894
895 $args = $this->query_args();
896 $return = $this->get_post_by( 'ID', $post->ID, $args['context'] );
897 if ( ! $return || is_wp_error( $return ) ) {
898 return $return;
899 }
900
901 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
902 do_action( 'wpcom_json_api_objects', 'posts' );
903
904 // we need to call wp_trash_post so that untrash will work correctly for all post types.
905 if ( 'trash' === $post->post_status ) {
906 wp_delete_post( $post->ID );
907 } else {
908 wp_trash_post( $post->ID );
909 }
910
911 $status = get_post_status( $post->ID );
912 if ( false === $status ) {
913 $return['status'] = 'deleted';
914 return $return;
915 }
916
917 return $this->get_post_by( 'ID', $post->ID, $args['context'] );
918 }
919
920 /**
921 * Restore a post.
922 *
923 * /sites/%s/posts/%d/restore -> $blog_id, $post_id
924 *
925 * @param string $path API path.
926 * @param int $blog_id Blog ID.
927 * @param int $post_id Post ID.
928 *
929 * @return array|WP_Error
930 */
931 public function restore_post( $path, $blog_id, $post_id ) {
932 $args = $this->query_args();
933 $post = get_post( $post_id );
934
935 if ( ! $post || is_wp_error( $post ) ) {
936 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
937 }
938
939 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
940 return new WP_Error( 'unauthorized', 'User cannot restore trashed posts', 403 );
941 }
942
943 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
944 do_action( 'wpcom_json_api_objects', 'posts' );
945
946 wp_untrash_post( $post->ID );
947
948 return $this->get_post_by( 'ID', $post->ID, $args['context'] );
949 }
950
951 /**
952 * Set or delete a post's featured image.
953 *
954 * @param int $post_id Post ID.
955 * @param bool $delete_featured_image Whether to delete the featured image.
956 * @param int $featured_image Thumbnail ID to attach.
957 *
958 * @return null|int|bool
959 */
960 private function parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image ) {
961 if ( $delete_featured_image ) {
962 delete_post_thumbnail( $post_id );
963 return;
964 }
965
966 $featured_image = (string) $featured_image;
967
968 // if we got a post ID, we can just set it as the thumbnail.
969 if ( ctype_digit( $featured_image ) && 'attachment' === get_post_type( $featured_image ) ) {
970 set_post_thumbnail( $post_id, $featured_image );
971 return $featured_image;
972 }
973
974 $featured_image_id = $this->handle_media_sideload( $featured_image, $post_id, 'image' );
975
976 if ( empty( $featured_image_id ) || ! is_int( $featured_image_id ) ) {
977 return false;
978 }
979
980 set_post_thumbnail( $post_id, $featured_image_id );
981 return $featured_image_id;
982 }
983
984 /**
985 * Get the Author ID for a post.
986 *
987 * @param int|string $author Author ID.
988 * @param string $post_type Post type.
989 *
990 * @return int|WP_Error
991 */
992 private function parse_and_set_author( $author = null, $post_type = 'post' ) {
993 if ( empty( $author ) || ! post_type_supports( $post_type, 'author' ) ) {
994 return get_current_user_id();
995 }
996
997 $author = (string) $author;
998 if ( ctype_digit( $author ) ) {
999 $_user = get_user_by( 'id', $author );
1000 if ( ! $_user || is_wp_error( $_user ) ) {
1001 return new WP_Error( 'invalid_author', 'Invalid author provided' );
1002 }
1003
1004 return $_user->ID;
1005 }
1006
1007 $_user = get_user_by( 'login', $author );
1008 if ( ! $_user || is_wp_error( $_user ) ) {
1009 return new WP_Error( 'invalid_author', 'Invalid author provided' );
1010 }
1011
1012 return $_user->ID;
1013 }
1014 }
1015