PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.2.4
Jetpack – WP Security, Backup, Speed, & Growth v13.2.4
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / json-endpoints / class.wpcom-json-api-update-post-v1-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 4 years ago class.wpcom-json-api-bulk-update-comments-endpoint.php 3 years ago class.wpcom-json-api-comment-endpoint.php 2 years ago class.wpcom-json-api-delete-media-endpoint.php 4 years ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 5 years ago class.wpcom-json-api-get-comment-counts-endpoint.php 4 years ago class.wpcom-json-api-get-comment-endpoint.php 4 years ago class.wpcom-json-api-get-comment-history-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 4 years ago class.wpcom-json-api-get-customcss.php 2 years ago class.wpcom-json-api-get-media-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 4 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 2 years ago class.wpcom-json-api-get-site-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-taxonomies-endpoint.php 2 years ago class.wpcom-json-api-get-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-get-term-endpoint.php 4 years ago class.wpcom-json-api-list-comments-endpoint.php 2 years ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 3 years ago class.wpcom-json-api-list-embeds-endpoint.php 4 years ago class.wpcom-json-api-list-media-endpoint.php 2 years ago class.wpcom-json-api-list-media-v1-1-endpoint.php 2 years ago class.wpcom-json-api-list-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 4 years ago class.wpcom-json-api-list-post-types-endpoint.php 3 years ago class.wpcom-json-api-list-posts-endpoint.php 3 years ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 3 years ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 3 years ago class.wpcom-json-api-list-roles-endpoint.php 2 years ago class.wpcom-json-api-list-shortcodes-endpoint.php 4 years ago class.wpcom-json-api-list-terms-endpoint.php 2 years ago class.wpcom-json-api-list-users-endpoint.php 2 years ago class.wpcom-json-api-menus-v1-1-endpoint.php 2 years ago class.wpcom-json-api-post-endpoint.php 3 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 2 years ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 2 years ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 2 years ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 2 years 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 3 years ago class.wpcom-json-api-update-post-v1-2-endpoint.php 2 years ago class.wpcom-json-api-update-site-homepage-endpoint.php 4 years ago class.wpcom-json-api-update-site-logo-endpoint.php 2 years ago class.wpcom-json-api-update-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-update-term-endpoint.php 4 years ago class.wpcom-json-api-update-user-endpoint.php 3 years ago class.wpcom-json-api-upload-media-endpoint.php 3 years ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 2 years ago
class.wpcom-json-api-update-post-v1-2-endpoint.php
1055 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 if ( isset( $input['status'] ) && 'trash' === $input['status'] && ! current_user_can( 'delete_post', $post_id ) ) {
262 return new WP_Error( 'unauthorized', 'User cannot delete post', 403 );
263 }
264
265 // 'future' is an alias for 'publish' for now
266 if ( isset( $input['status'] ) && 'future' === $input['status'] ) {
267 $input['status'] = 'publish';
268 }
269
270 $post = get_post( $post_id );
271 if ( ! $post || is_wp_error( $post ) ) {
272 return new WP_Error( 'unknown_post', 'Unknown post', 404 );
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
539 $media_id_string = '';
540 if ( $has_media || $has_media_by_url ) {
541 $media_files = ! empty( $input['media'] ) ? $input['media'] : array();
542 $media_urls = ! empty( $input['media_urls'] ) ? $input['media_urls'] : array();
543 $media_attrs = ! empty( $input['media_attrs'] ) ? $input['media_attrs'] : array();
544 $media_results = $this->handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs );
545 $media_id_string = implode( ',', array_filter( array_map( 'absint', $media_results['media_ids'] ) ) );
546 }
547
548 $is_dtp_fb_post = false;
549 if ( in_array( '_dtp_fb', wp_list_pluck( (array) $metadata, 'key' ), true ) ) {
550 $is_dtp_fb_post = true;
551 add_filter( 'rest_api_allowed_public_metadata', array( $this, 'dtp_fb_allowed_metadata' ) );
552 }
553
554 /**
555 * Log Media details for a Post creation request.
556 * Temporary logging for media data.
557 *
558 * @see p1709028174665519-slack-CDLH4C1UZ
559 *
560 * @since 13.2
561 *
562 * @param bool $is_dtp_fb_post Is this for a Facebook import?
563 * @param int $blog_id Blog ID.
564 * @param array $input Whole input.
565 * @param array $media_files File upload data.
566 * @param array $media_urls URLs to fetch.
567 * @param array $media_attrs Attributes corresponding to each entry in `$media_files`/`$media_urls`.
568 * @param array $media_results
569 * - media_ids: IDs created, by index in `$media_files`/`$media_urls`.
570 * - errors: Errors encountered, by index in `$media_files`/`$media_urls`.
571 */
572 do_action( 'jetpack_dtp_fb_media', $is_dtp_fb_post, $blog_id, $input, $media_files, $media_urls, $media_attrs, $media_results );
573
574 if ( $new ) {
575 if ( isset( $input['content'] ) && ! has_shortcode( $input['content'], 'gallery' ) && ( $has_media || $has_media_by_url ) ) {
576 switch ( ( $has_media + $has_media_by_url ) ) {
577 case 0:
578 // No images - do nothing.
579 break;
580 case 1:
581 // 1 image - make it big.
582 $input['content'] = sprintf(
583 "[gallery size=full ids='%s' columns=1]\n\n",
584 $media_id_string
585 ) . $input['content'];
586 $insert['post_content'] = $input['content'];
587 break;
588 default:
589 // Several images - 3 column gallery.
590 $input['content'] = sprintf(
591 "[gallery ids='%s']\n\n",
592 $media_id_string
593 ) . $input['content'];
594 $insert['post_content'] = $input['content'];
595 break;
596 }
597 }
598
599 $insert['post_date'] = isset( $insert['post_date'] ) ? $insert['post_date'] : '';
600
601 if ( $is_dtp_fb_post ) {
602 $insert = $this->dtp_fb_preprocess_post( $insert, $metadata );
603 }
604
605 $post_id = $this->post_exists( $insert['post_title'], $insert['post_content'], $insert['post_date'], $post_type->name );
606 if ( 0 === $post_id ) {
607 $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
608 }
609 } else {
610 $insert['ID'] = $post->ID;
611
612 // wp_update_post ignores date unless edit_date is set
613 // See: https://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts
614 // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302 .
615 if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
616 $insert['edit_date'] = true;
617 }
618
619 // this two-step process ensures any changes submitted along with status=trash get saved before trashing.
620 if ( isset( $input['status'] ) && 'trash' === $input['status'] ) {
621 // if we insert it with status='trash', it will get double-trashed, so insert it as a draft first.
622 unset( $insert['status'] );
623 $post_id = wp_update_post( (object) $insert );
624 // now call wp_trash_post so post_meta gets set and any filters get called.
625 wp_trash_post( $post_id );
626 } else {
627 $post_id = wp_update_post( (object) $insert );
628 }
629 }
630
631 if ( ! $post_id || is_wp_error( $post_id ) ) {
632 return $post_id;
633 }
634
635 // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint).
636 $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
637 if ( is_wp_error( $post_check ) ) {
638 return $post_check;
639 }
640
641 if ( $media_id_string ) {
642 // Yes - this is really how wp-admin does it.
643 $wpdb->query(
644 $wpdb->prepare(
645 "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.
646 $post_id
647 )
648 );
649 foreach ( $media_results['media_ids'] as $media_id ) {
650 clean_attachment_cache( $media_id );
651 }
652 clean_post_cache( $post_id );
653 }
654
655 // set page template for this post.
656 if ( isset( $input['page_template'] ) && 'page' === $post_type->name ) {
657 $page_template = $input['page_template'];
658 $page_templates = wp_get_theme()->get_page_templates( get_post( $post_id ) );
659 if ( empty( $page_template ) || 'default' === $page_template || isset( $page_templates[ $page_template ] ) ) {
660 update_post_meta( $post_id, '_wp_page_template', $page_template );
661 }
662 }
663
664 // Set like status for the post.
665 /** This filter is documented in modules/likes.php */
666 $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
667 if ( $new ) {
668 if ( $sitewide_likes_enabled ) {
669 if ( false === $likes ) {
670 update_post_meta( $post_id, 'switch_like_status', 0 );
671 } else {
672 delete_post_meta( $post_id, 'switch_like_status' );
673 }
674 } elseif ( $likes ) {
675 update_post_meta( $post_id, 'switch_like_status', 1 );
676 } else {
677 delete_post_meta( $post_id, 'switch_like_status' );
678 }
679 } elseif ( isset( $likes ) ) {
680 if ( $sitewide_likes_enabled ) {
681 if ( false === $likes ) {
682 update_post_meta( $post_id, 'switch_like_status', 0 );
683 } else {
684 delete_post_meta( $post_id, 'switch_like_status' );
685 }
686 } elseif ( true === $likes ) {
687 update_post_meta( $post_id, 'switch_like_status', 1 );
688 } else {
689 delete_post_meta( $post_id, 'switch_like_status' );
690 }
691 }
692
693 // Set sharing status of the post.
694 if ( $new ) {
695 $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
696 if ( false === $sharing_enabled ) {
697 update_post_meta( $post_id, 'sharing_disabled', 1 );
698 }
699 } elseif ( isset( $sharing ) && true === $sharing ) {
700 delete_post_meta( $post_id, 'sharing_disabled' );
701 } elseif ( isset( $sharing ) && false == $sharing ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
702 update_post_meta( $post_id, 'sharing_disabled', 1 );
703 }
704
705 if ( isset( $sticky ) ) {
706 if ( true === $sticky ) {
707 stick_post( $post_id );
708 } else {
709 unstick_post( $post_id );
710 }
711 }
712
713 // WPCOM Specific (Jetpack's will get bumped elsewhere
714 // Tracks how many posts are published and sets meta
715 // so we can track some other cool stats (like likes & comments on posts published).
716 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
717 if (
718 ( $new && 'publish' === $input['status'] )
719 || (
720 ! $new && isset( $last_status )
721 && 'publish' !== $last_status
722 && isset( $new_status )
723 && 'publish' === $new_status
724 )
725 ) {
726 /** This action is documented in modules/widgets/social-media-icons.php */
727 do_action( 'jetpack_bump_stats_extras', 'api-insights-posts', $this->api->token_details['client_id'] );
728 update_post_meta( $post_id, '_rest_api_published', 1 );
729 update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
730 }
731 }
732
733 // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
734 // to instead flag the ones we don't want to be skipped. proceed with said logic.
735 // any posts coming from Path (client ID 25952) should also not publicize.
736 if ( false === $publicize || ( isset( $this->api->token_details['client_id'] ) && 25952 === (int) $this->api->token_details['client_id'] ) ) {
737 // No publicize at all, skip all by ID.
738 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
739 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
740 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
741 if ( ! $service_connections ) {
742 continue;
743 }
744 foreach ( $service_connections as $service_connection ) {
745 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
746 }
747 }
748 } elseif ( is_array( $publicize ) && ( count( $publicize ) > 0 ) ) {
749 foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
750 /*
751 * We support both indexed and associative arrays:
752 * * indexed are to pass entire services
753 * * associative are to pass specific connections per service
754 *
755 * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
756 *
757 * EG: array( 'linkedin', 'facebook') will only publicize to those, ignoring the other available services
758 * Form data: publicize[]=linkedin&publicize[]=facebook
759 * 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.
760 * Form data: publicize[linkedin]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
761 * 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
762 * Form data: publicize[]=linkedin&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
763 */
764
765 // Delete any stale SKIP value for the service by name. We'll add it back by ID.
766 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
767
768 // Get the user's connections.
769 $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
770
771 // if the user doesn't have any connections for this service, move on.
772 if ( ! $service_connections ) {
773 continue;
774 }
775
776 if ( ! in_array( $name, $publicize, true ) && ! array_key_exists( $name, $publicize ) ) {
777 // Skip the whole service by adding each connection ID.
778 foreach ( $service_connections as $service_connection ) {
779 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
780 }
781 } elseif ( ! empty( $publicize[ $name ] ) ) {
782 // Seems we're being asked to only push to [a] specific connection[s].
783 // Explode the list on commas, which will also support a single passed ID.
784 $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
785
786 // Flag the connections we can't match with the requested list to be skipped.
787 foreach ( $service_connections as $service_connection ) {
788 if ( ! in_array( $service_connection->meta['connection_data']->id, $requested_connections, true ) ) {
789 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
790 } else {
791 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
792 }
793 }
794 } else {
795 // delete all SKIP values; it's okay to publish to all connected IDs for this service.
796 foreach ( $service_connections as $service_connection ) {
797 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
798 }
799 }
800 }
801 }
802
803 if ( $publicize_custom_message !== null ) {
804 if ( empty( $publicize_custom_message ) ) {
805 delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS );
806 } else {
807 update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
808 }
809 }
810
811 if ( ! empty( $insert['post_format'] ) ) {
812 if ( 'default' !== strtolower( $insert['post_format'] ) ) {
813 set_post_format( $post_id, $insert['post_format'] );
814 } else {
815 set_post_format( $post_id, get_option( 'default_post_format' ) );
816 }
817 }
818
819 if ( isset( $featured_image ) ) {
820 parent::parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
821 }
822
823 if ( ! empty( $metadata ) ) {
824 foreach ( (array) $metadata as $meta ) {
825
826 $meta = (object) $meta;
827
828 if (
829 in_array( $meta->key, Jetpack_SEO_Posts::POST_META_KEYS_ARRAY, true ) &&
830 ! Jetpack_SEO_Utils::is_enabled_jetpack_seo()
831 ) {
832 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
833 }
834
835 $existing_meta_item = new stdClass();
836
837 if ( empty( $meta->operation ) ) {
838 $meta->operation = 'update';
839 }
840
841 if ( ! empty( $meta->value ) ) {
842 if ( 'true' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
843 $meta->value = true;
844 }
845 if ( 'false' == $meta->value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
846 $meta->value = false;
847 }
848 }
849
850 if ( ! empty( $meta->id ) ) {
851 $meta->id = absint( $meta->id );
852 $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
853 if ( $post_id !== (int) $existing_meta_item->post_id ) {
854 // Only allow updates for metadata on this post.
855 continue;
856 }
857 }
858
859 $unslashed_meta_key = wp_unslash( $meta->key ); // should match what the final key will be.
860 $meta->key = wp_slash( $meta->key );
861 $unslashed_existing_meta_key = isset( $existing_meta_item->meta_key ) ? wp_unslash( $existing_meta_item->meta_key ) : '';
862 $existing_meta_item->meta_key = isset( $existing_meta_item->meta_key ) ? wp_slash( $existing_meta_item->meta_key ) : '';
863
864 // make sure that the meta id passed matches the existing meta key.
865 if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
866 $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
867 if ( $meta_by_id->meta_key !== $meta->key ) {
868 continue; // skip this meta.
869 }
870 }
871
872 switch ( $meta->operation ) {
873 case 'delete':
874 if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
875 delete_metadata_by_mid( 'post', $meta->id );
876 } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
877 delete_post_meta( $post_id, $meta->key, $meta->previous_value );
878 } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
879 delete_post_meta( $post_id, $meta->key );
880 }
881
882 break;
883 case 'add':
884 if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
885 break;
886 } 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 ) ) {
887 add_post_meta( $post_id, $meta->key, $meta->value );
888 }
889
890 break;
891 case 'update':
892 if ( ! isset( $meta->value ) ) {
893 break;
894 } 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 ) ) ) {
895 update_metadata_by_mid( 'post', $meta->id, $meta->value );
896 } 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 ) ) ) {
897 update_post_meta( $post_id, $meta->key, $meta->value, $meta->previous_value );
898 } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || WPCOM_JSON_API_Metadata::is_public( $meta->key ) ) ) {
899 update_post_meta( $post_id, $meta->key, $meta->value );
900 }
901
902 break;
903 }
904 }
905 }
906
907 /** This action is documented in json-endpoints/class.wpcom-json-api-update-post-endpoint.php */
908 do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
909
910 $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
911 if ( ! $return || is_wp_error( $return ) ) {
912 return $return;
913 }
914
915 if ( isset( $input['type'] ) && 'revision' === $input['type'] ) {
916 $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
917 }
918
919 if ( isset( $sticky ) ) {
920 // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above.
921 $return['sticky'] = ( true === $sticky );
922 }
923
924 if ( ! empty( $media_results['errors'] ) ) {
925 /*
926 * Depending on whether the errors array keys are sequential or not
927 * json_encode would transform this into either an array or an object
928 * see https://www.php.net/manual/en/function.json-encode.php#example-3967
929 * We use array_values to always return an array
930 */
931 $return['media_errors'] = array_values( $media_results['errors'] );
932 }
933
934 if ( 'publish' !== $return['status'] && isset( $input['title'] ) ) {
935 $sal_site = $this->get_sal_post_by( 'ID', $post_id, $args['context'] );
936 $return['other_URLs'] = (object) $sal_site->get_permalink_suggestions( $input['title'] );
937 }
938
939 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
940 do_action( 'wpcom_json_api_objects', 'posts' );
941
942 return $return;
943 }
944
945 /**
946 * Determine if a theme's functions.php file should be loaded.
947 *
948 * @param int $post_id Post ID.
949 *
950 * @return bool
951 */
952 protected function should_load_theme_functions( $post_id = null ) {
953 if ( empty( $post_id ) ) {
954 $input = $this->input( true );
955 $type = $input['type'];
956 } else {
957 $type = get_post_type( $post_id );
958 }
959
960 return ! empty( $type ) && ! in_array( $type, array( 'post', 'revision' ), true );
961 }
962
963 /**
964 * Filter for rest_api_allowed_public_metadata.
965 * Adds FB's DTP specific metadata.
966 *
967 * @param array $keys Array of metadata that is accessible by the REST API.
968 *
969 * @return array
970 */
971 public function dtp_fb_allowed_metadata( $keys ) {
972 return array_merge( $keys, array( '_dtp_fb', '_dtp_fb_geo_points', '_dtp_fb_post_link' ) );
973 }
974
975 /**
976 * Pre-process FB DTP posts before inserting.
977 * Here we can improve the DTP content for the following issues:
978 * - Render the map block based on provided coordinates in metadata
979 * - [TODO] Improve the title
980 *
981 * @param array $post Post to be inserted.
982 * @param array $metadata Metadata for the post.
983 *
984 * @return mixed
985 */
986 private function dtp_fb_preprocess_post( $post, $metadata ) {
987 $geo_points_metadata = wp_filter_object_list( $metadata, array( 'key' => '_dtp_fb_geo_points' ), 'and', 'value' );
988 if ( ! empty( $geo_points_metadata ) ) {
989 $fb_points = reset( $geo_points_metadata );
990 $geo_points = array();
991
992 // Prepare Geo Points so that they match the format expected by the map block.
993 foreach ( $fb_points as $fb_point ) {
994 $geo_points[] = array(
995 'coordinates' => array(
996 'longitude' => $fb_point['longitude'],
997 'latitude' => $fb_point['latitude'],
998 ),
999 'title' => $fb_point['name'],
1000 );
1001 }
1002 $map_block = map_block_from_geo_points( $geo_points );
1003
1004 $post['post_content'] = $map_block . $post['post_content'];
1005 }
1006
1007 $post['post_format'] = 'aside';
1008
1009 return $post;
1010 }
1011
1012 /**
1013 * Determines if a post exists based on title, content, date, and type,
1014 * But excluding IDs in gallery shortcodes.
1015 * This will prevent duplication of posts created through the API.
1016 *
1017 * @param string $title Post title.
1018 * @param string $content Post content.
1019 * @param string $post_date Post date.
1020 * @param string $type Optional post type.
1021 * @return int Post ID if post exists, 0 otherwise.
1022 */
1023 private function post_exists( $title, $content, $post_date, $type = '' ) {
1024 $date = date_create( $post_date );
1025
1026 $posts = get_posts(
1027 array(
1028 'year' => date_format( $date, 'Y' ),
1029 'monthnum' => date_format( $date, 'n' ),
1030 'day' => date_format( $date, 'j' ),
1031 'hour' => date_format( $date, 'G' ),
1032 'minute' => date_format( $date, 'i' ),
1033 'second' => date_format( $date, 's' ),
1034 'post_type' => $type,
1035 'title' => $title,
1036 'numberposts' => -1,
1037 'suppress_filters' => false,
1038 )
1039 );
1040
1041 foreach ( $posts as $post ) {
1042 $gallery_ids_pattern = "/(\[gallery[^\]]*)(\sids='[\d,]+')([^\]]*\])/";
1043
1044 $post->post_content = preg_replace( $gallery_ids_pattern, '$1$3', $post->post_content );
1045 $content = preg_replace( $gallery_ids_pattern, '$1$3', $content );
1046
1047 if ( $content === $post->post_content ) {
1048 return $post->ID;
1049 }
1050 }
1051
1052 return 0;
1053 }
1054 }
1055