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