PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9-a.7
Jetpack – WP Security, Backup, Speed, & Growth v15.9-a.7
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-list-posts-v1-1-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 1 week ago class.wpcom-json-api-add-widget-endpoint.php 6 months ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 6 months ago class.wpcom-json-api-bulk-delete-post-endpoint.php 6 months ago class.wpcom-json-api-bulk-restore-post-endpoint.php 6 months ago class.wpcom-json-api-bulk-update-comments-endpoint.php 1 week ago class.wpcom-json-api-comment-endpoint.php 6 months ago class.wpcom-json-api-delete-media-endpoint.php 6 months ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 6 months ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 1 week ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 6 months ago class.wpcom-json-api-get-comment-counts-endpoint.php 6 months ago class.wpcom-json-api-get-comment-endpoint.php 6 months ago class.wpcom-json-api-get-comment-history-endpoint.php 6 months ago class.wpcom-json-api-get-comments-tree-endpoint.php 6 months ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 6 months ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 6 months ago class.wpcom-json-api-get-customcss.php 6 months ago class.wpcom-json-api-get-media-endpoint.php 6 months ago class.wpcom-json-api-get-media-v1-1-endpoint.php 6 months ago class.wpcom-json-api-get-media-v1-2-endpoint.php 6 months ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 6 months ago class.wpcom-json-api-get-post-endpoint.php 6 months ago class.wpcom-json-api-get-post-v1-1-endpoint.php 6 months ago class.wpcom-json-api-get-site-endpoint.php 4 weeks ago class.wpcom-json-api-get-site-v1-2-endpoint.php 3 months ago class.wpcom-json-api-get-taxonomies-endpoint.php 1 month ago class.wpcom-json-api-get-taxonomy-endpoint.php 6 months ago class.wpcom-json-api-get-term-endpoint.php 6 months ago class.wpcom-json-api-list-comments-endpoint.php 1 week ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 6 months ago class.wpcom-json-api-list-embeds-endpoint.php 6 months ago class.wpcom-json-api-list-media-endpoint.php 6 months ago class.wpcom-json-api-list-media-v1-1-endpoint.php 1 week ago class.wpcom-json-api-list-media-v1-2-endpoint.php 6 months ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 6 months ago class.wpcom-json-api-list-post-types-endpoint.php 6 months ago class.wpcom-json-api-list-posts-endpoint.php 1 week ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 1 week ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 1 week ago class.wpcom-json-api-list-roles-endpoint.php 6 months ago class.wpcom-json-api-list-shortcodes-endpoint.php 6 months ago class.wpcom-json-api-list-terms-endpoint.php 6 months ago class.wpcom-json-api-list-users-endpoint.php 6 months ago class.wpcom-json-api-menus-v1-1-endpoint.php 1 week ago class.wpcom-json-api-post-endpoint.php 6 months ago class.wpcom-json-api-post-v1-1-endpoint.php 1 month ago class.wpcom-json-api-render-embed-endpoint.php 6 months ago class.wpcom-json-api-render-embed-reversal-endpoint.php 6 months ago class.wpcom-json-api-render-endpoint.php 2 weeks ago class.wpcom-json-api-render-shortcode-endpoint.php 6 months ago class.wpcom-json-api-sharing-buttons-endpoint.php 1 week ago class.wpcom-json-api-site-settings-endpoint.php 2 months ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 6 months ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 6 months ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 2 months ago class.wpcom-json-api-site-user-endpoint.php 6 months ago class.wpcom-json-api-taxonomy-endpoint.php 6 months ago class.wpcom-json-api-update-comment-endpoint.php 4 months ago class.wpcom-json-api-update-customcss.php 6 months ago class.wpcom-json-api-update-media-endpoint.php 6 months ago class.wpcom-json-api-update-media-v1-1-endpoint.php 1 week ago class.wpcom-json-api-update-post-endpoint.php 1 week ago class.wpcom-json-api-update-post-v1-1-endpoint.php 1 week ago class.wpcom-json-api-update-post-v1-2-endpoint.php 1 week ago class.wpcom-json-api-update-site-homepage-endpoint.php 6 months ago class.wpcom-json-api-update-site-logo-endpoint.php 6 months ago class.wpcom-json-api-update-taxonomy-endpoint.php 5 months ago class.wpcom-json-api-update-term-endpoint.php 6 months ago class.wpcom-json-api-update-user-endpoint.php 6 months ago class.wpcom-json-api-upload-media-endpoint.php 6 months ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 6 months ago
class.wpcom-json-api-list-posts-v1-1-endpoint.php
646 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit( 0 );
5 }
6
7 /**
8 * List posts v1_1 endpoint.
9 */
10 new WPCOM_JSON_API_List_Posts_v1_1_Endpoint(
11 array(
12 'description' => 'Get a list of matching posts.',
13 'min_version' => '1.1',
14 'max_version' => '1.1',
15
16 'group' => 'posts',
17 'stat' => 'posts',
18
19 'method' => 'GET',
20 'path' => '/sites/%s/posts/',
21 'path_labels' => array(
22 '$site' => '(int|string) Site ID or domain',
23 ),
24 'rest_route' => '/posts',
25 'rest_min_jp_version' => '14.5-a.2',
26
27 'allow_fallback_to_jetpack_blog_token' => true,
28
29 'query_parameters' => array(
30 'number' => '(int=20) The number of posts to return. Limit: 100.',
31 'offset' => '(int=0) 0-indexed offset.',
32 'page' => '(int) Return the Nth 1-indexed page of posts. Takes precedence over the <code>offset</code> parameter.',
33 'page_handle' => '(string) A page handle, returned from a previous API call as a <code>meta.next_page</code> property. This is the most efficient way to fetch the next page of results.',
34 'order' => array(
35 'DESC' => 'Return posts in descending order. For dates, that means newest to oldest.',
36 'ASC' => 'Return posts in ascending order. For dates, that means oldest to newest.',
37 ),
38 'order_by' => array(
39 'date' => 'Order by the created time of each post.',
40 'modified' => 'Order by the modified time of each post.',
41 'title' => "Order lexicographically by the posts' titles.",
42 'comment_count' => 'Order by the number of comments for each post.',
43 'ID' => 'Order by post ID.',
44 ),
45 'after' => '(ISO 8601 datetime) Return posts dated after the specified datetime.',
46 'before' => '(ISO 8601 datetime) Return posts dated before the specified datetime.',
47 'modified_after' => '(ISO 8601 datetime) Return posts modified after the specified datetime.',
48 'modified_before' => '(ISO 8601 datetime) Return posts modified before the specified datetime.',
49 'tag' => '(string) Specify the tag name or slug.',
50 'category' => '(string) Specify the category name or slug.',
51 'term' => '(object:string) Specify comma-separated term slugs to search within, indexed by taxonomy slug.',
52 'type' => "(string) Specify the post type. Defaults to 'post', use 'any' to query for both posts and pages. Post types besides post and page need to be whitelisted using the <code>rest_api_allowed_post_types</code> filter.",
53 'parent_id' => '(int) Returns only posts which are children of the specified post. Applies only to hierarchical post types.',
54 'include' => '(array:int|int) Includes the specified post ID(s) in the response',
55 'exclude' => '(array:int|int) Excludes the specified post ID(s) from the response',
56 'exclude_tree' => '(int) Excludes the specified post and all of its descendants from the response. Applies only to hierarchical post types.',
57 'status' => '(string) Comma-separated list of statuses for which to query, including any of: "publish", "private", "draft", "pending", "future", and "trash", or simply "any". Defaults to "publish"',
58 'sticky' => array(
59 'include' => 'Sticky posts are not excluded from the list.',
60 'exclude' => 'Sticky posts are excluded from the list.',
61 'require' => 'Only include sticky posts',
62 ),
63 'author' => "(int) Author's user ID",
64 'search' => '(string) Search query',
65 'meta_key' => '(string) Metadata key that the post should contain',
66 'meta_value' => '(string) Metadata value that the post should contain. Will only be applied if a `meta_key` is also given',
67 ),
68
69 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/en.blog.wordpress.com/posts/?number=2',
70 )
71 );
72
73 /**
74 * List Posts v1_1 Endpoint class.
75 *
76 * /sites/%s/posts/ -> $blog_id
77 *
78 * @phan-constructor-used-for-side-effects
79 */
80 class WPCOM_JSON_API_List_Posts_v1_1_Endpoint extends WPCOM_JSON_API_Post_v1_1_Endpoint { // phpcs:ignore
81 /**
82 * Date range
83 *
84 * @var array
85 */
86 public $date_range = array();
87
88 /**
89 * Modified range
90 *
91 * @var array
92 */
93 public $modified_range = array();
94
95 /**
96 * Page handle
97 *
98 * @var array
99 */
100 public $page_handle = array();
101
102 /**
103 * Performed query
104 *
105 * @var array
106 */
107 public $performed_query = null;
108
109 /**
110 * Response format.
111 *
112 * @var array
113 */
114 public $response_format = array(
115 'found' => '(int) The total number of posts found that match the request (ignoring limits, offsets, and pagination).',
116 'posts' => '(array:post) An array of post objects.',
117 'meta' => '(object) Meta data',
118 );
119
120 /**
121 * API callback.
122 *
123 * @param string $path - the path.
124 * @param string $blog_id - the blog ID.
125 */
126 public function callback( $path = '', $blog_id = 0 ) {
127 $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
128 if ( is_wp_error( $blog_id ) ) {
129 return $blog_id;
130 }
131
132 $args = $this->query_args();
133 $is_eligible_for_page_handle = true;
134 $site = $this->get_platform()->get_site( $blog_id );
135
136 if ( $args['number'] < 1 ) {
137 $args['number'] = 20;
138 } elseif ( 100 < $args['number'] ) {
139 return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
140 }
141
142 if ( isset( $args['type'] ) &&
143 ! in_array( $args['type'], array( 'post', 'revision', 'page', 'any' ), true ) &&
144 defined( 'IS_WPCOM' ) && IS_WPCOM ) {
145 $this->load_theme_functions();
146 }
147
148 if ( isset( $args['type'] ) && ! $site->is_post_type_allowed( $args['type'] ) ) {
149 return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
150 }
151
152 // Normalize post_type.
153 if ( isset( $args['type'] ) && 'any' === $args['type'] ) {
154 if ( version_compare( $this->api->version, '1.1', '<' ) ) {
155 $args['type'] = array( 'post', 'page' );
156 } else { // 1.1+
157 $args['type'] = $site->get_whitelisted_post_types();
158 }
159 }
160
161 // determine statuses.
162 $status = ( ! empty( $args['status'] ) ) ? explode( ',', $args['status'] ) : array( 'publish' );
163 if ( is_user_logged_in() ) {
164 $statuses_whitelist = array(
165 'publish',
166 'pending',
167 'draft',
168 'future',
169 'private',
170 'trash',
171 'any',
172 );
173 $status = array_intersect( $status, $statuses_whitelist );
174 } else {
175 // logged-out users can see only published posts.
176 $statuses_whitelist = array( 'publish', 'any' );
177 $status = array_intersect( $status, $statuses_whitelist );
178
179 if ( empty( $status ) ) {
180 // requested only protected statuses? nothing for you here.
181 return array(
182 'found' => 0,
183 'posts' => array(),
184 );
185 }
186 // clear it (AKA published only) because "any" includes protected.
187 $status = array();
188 }
189
190 // let's be explicit about defaulting to 'post'.
191 $args['type'] = $args['type'] ?? 'post';
192
193 // make sure the user can read or edit the requested post type(s).
194 if ( is_array( $args['type'] ) ) {
195 $allowed_types = array();
196 foreach ( $args['type'] as $post_type ) {
197 if ( $site->current_user_can_access_post_type( $post_type, $args['context'] ) ) {
198 $allowed_types[] = $post_type;
199 }
200 }
201
202 if ( empty( $allowed_types ) ) {
203 return array(
204 'found' => 0,
205 'posts' => array(),
206 );
207 }
208 $args['type'] = $allowed_types;
209 } elseif ( ! $site->current_user_can_access_post_type( $args['type'], $args['context'] ) ) {
210 return array(
211 'found' => 0,
212 'posts' => array(),
213 );
214 }
215
216 $query = array(
217 'posts_per_page' => $args['number'],
218 'order' => $args['order'],
219 'orderby' => $args['order_by'],
220 'post_type' => $args['type'],
221 'post_status' => $status,
222 'post_parent' => $args['parent_id'] ?? null,
223 'author' => isset( $args['author'] ) && 0 < $args['author'] ? $args['author'] : null,
224 's' => isset( $args['search'] ) && '' !== $args['search'] ? $args['search'] : null,
225 'fields' => 'ids',
226 );
227
228 if ( ! is_user_logged_in() ) {
229 $query['has_password'] = false;
230 }
231
232 if ( isset( $args['include'] ) ) {
233 $query['post__in'] = is_array( $args['include'] ) ? $args['include'] : array( (int) $args['include'] );
234 }
235
236 if ( isset( $args['meta_key'] ) ) {
237 $show = false;
238 if ( WPCOM_JSON_API_Metadata::is_public( $args['meta_key'] ) ) {
239 $show = true;
240 }
241 if ( current_user_can( 'edit_post_meta', $query['post_type'], $args['meta_key'] ) ) {
242 $show = true;
243 }
244
245 if ( is_protected_meta( $args['meta_key'], 'post' ) && ! $show ) {
246 return new WP_Error( 'invalid_meta_key', 'Invalid meta key', 404 );
247 }
248
249 $meta = array( 'key' => $args['meta_key'] );
250 if ( isset( $args['meta_value'] ) ) {
251 $meta['value'] = $args['meta_value'];
252 }
253
254 $query['meta_query'] = array( $meta );
255 }
256
257 if ( 'include' === $args['sticky'] ) {
258 $query['ignore_sticky_posts'] = 1;
259 } elseif ( 'exclude' === $args['sticky'] ) {
260 $sticky = get_option( 'sticky_posts' );
261 if ( is_array( $sticky ) ) {
262 $query['post__not_in'] = $sticky;
263 }
264 } elseif ( 'require' === $args['sticky'] ) {
265 $sticky = get_option( 'sticky_posts' );
266 if ( is_array( $sticky ) && ! empty( $sticky ) ) {
267 $query['post__in'] = isset( $args['include'] ) ? array_merge( $query['post__in'], $sticky ) : $sticky;
268 } else {
269 // no sticky posts exist.
270 return array(
271 'found' => 0,
272 'posts' => array(),
273 );
274 }
275 }
276
277 if ( isset( $args['exclude'] ) ) {
278 $excluded_ids = (array) $args['exclude'];
279 $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $excluded_ids ) : $excluded_ids;
280 }
281
282 if ( isset( $args['exclude_tree'] ) && is_post_type_hierarchical( $args['type'] ) ) {
283 // get_page_children is a misnomer; it supports all hierarchical post types.
284 $page_args = array(
285 'child_of' => $args['exclude_tree'],
286 'post_type' => $args['type'],
287 // since we're looking for things to exclude, be aggressive.
288 'post_status' => 'publish,draft,pending,private,future,trash',
289 );
290 $post_descendants = get_pages( $page_args );
291
292 $exclude_tree = array( $args['exclude_tree'] );
293 foreach ( $post_descendants as $child ) {
294 $exclude_tree[] = $child->ID;
295 }
296
297 $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $exclude_tree ) : $exclude_tree;
298 }
299
300 if ( isset( $args['category'] ) ) {
301 $category = get_term_by( 'slug', $args['category'], 'category' );
302 if ( false === $category ) {
303 $query['category_name'] = $args['category'];
304 } else {
305 $query['cat'] = $category->term_id;
306 }
307 }
308
309 if ( isset( $args['tag'] ) ) {
310 $query['tag'] = $args['tag'];
311 }
312
313 if ( ! empty( $args['term'] ) ) {
314 $query['tax_query'] = array();
315 foreach ( $args['term'] as $taxonomy => $slug ) {
316 $taxonomy_object = get_taxonomy( $taxonomy );
317 if ( false === $taxonomy_object || ( ! $taxonomy_object->public &&
318 ! current_user_can( $taxonomy_object->cap->assign_terms ) ) ) {
319 continue;
320 }
321
322 $query['tax_query'][] = array(
323 'taxonomy' => $taxonomy,
324 'field' => 'slug',
325 'terms' => explode( ',', $slug ),
326 );
327 }
328 }
329
330 if ( isset( $args['page'] ) ) {
331 if ( $args['page'] < 1 ) {
332 $args['page'] = 1;
333 }
334
335 $query['paged'] = $args['page'];
336 if ( 1 !== $query['paged'] ) {
337 $is_eligible_for_page_handle = false;
338 }
339 } else {
340 if ( $args['offset'] < 0 ) {
341 $args['offset'] = 0;
342 }
343
344 $query['offset'] = $args['offset'];
345 if ( 0 !== $query['offset'] ) {
346 $is_eligible_for_page_handle = false;
347 }
348 }
349
350 if ( isset( $args['before_gmt'] ) ) {
351 $this->date_range['before'] = $args['before_gmt'];
352 }
353 if ( isset( $args['after_gmt'] ) ) {
354 $this->date_range['after'] = $args['after_gmt'];
355 }
356
357 if ( isset( $args['modified_before_gmt'] ) ) {
358 $this->modified_range['before'] = $args['modified_before_gmt'];
359 }
360 if ( isset( $args['modified_after_gmt'] ) ) {
361 $this->modified_range['after'] = $args['modified_after_gmt'];
362 }
363
364 if ( $this->date_range ) {
365 add_filter( 'posts_where', array( $this, 'handle_date_range' ) );
366 }
367
368 if ( $this->modified_range ) {
369 add_filter( 'posts_where', array( $this, 'handle_modified_range' ) );
370 }
371
372 if ( isset( $args['page_handle'] ) ) {
373 $page_handle = wp_parse_args( $args['page_handle'] );
374 if ( isset( $page_handle['value'] ) && isset( $page_handle['id'] ) ) {
375 // we have a valid looking page handle.
376 $this->page_handle = $page_handle;
377 add_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) );
378 }
379 }
380
381 /**
382 * 'column' necessary for the me/posts endpoint (which extends sites/$site/posts).
383 * Would need to be added to the sites/$site/posts definition if we ever want to
384 * use it there.
385 */
386 $column_whitelist = array( 'post_modified_gmt' );
387 if ( isset( $args['column'] ) && in_array( $args['column'], $column_whitelist, true ) ) {
388 $query['column'] = $args['column'];
389 }
390
391 $this->performed_query = $query;
392 add_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) );
393
394 $wp_query = new WP_Query( $query );
395
396 remove_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) );
397
398 if ( $this->date_range ) {
399 remove_filter( 'posts_where', array( $this, 'handle_date_range' ) );
400 $this->date_range = array();
401 }
402
403 if ( $this->modified_range ) {
404 remove_filter( 'posts_where', array( $this, 'handle_modified_range' ) );
405 $this->modified_range = array();
406 }
407
408 if ( $this->page_handle ) {
409 remove_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) );
410
411 }
412
413 $return = array();
414 $excluded_count = 0;
415 foreach ( array_keys( $this->response_format ) as $key ) {
416 switch ( $key ) {
417 case 'found':
418 $return[ $key ] = (int) $wp_query->found_posts;
419 break;
420 case 'posts':
421 $posts = array();
422 foreach ( $wp_query->posts as $post_ID ) {
423 $the_post = $this->get_post_by( 'ID', $post_ID, $args['context'] );
424 if ( $the_post && ! is_wp_error( $the_post ) ) {
425 $posts[] = $the_post;
426 } else {
427 ++$excluded_count;
428 }
429 }
430
431 if ( $posts ) {
432 /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
433 do_action( 'wpcom_json_api_objects', 'posts', count( $posts ) );
434 }
435
436 $return[ $key ] = $posts;
437 break;
438
439 case 'meta':
440 if ( ! is_array( $args['type'] ) ) {
441 $return[ $key ] = (object) array(
442 'links' => (object) array(
443 'counts' => (string) $this->links->get_site_link( $blog_id, 'post-counts/' . $args['type'] ),
444 ),
445 );
446 }
447
448 if ( $is_eligible_for_page_handle && $return['posts'] && is_array( $return['posts'] ) ) {
449 $last_post = end( $return['posts'] );
450 reset( $return['posts'] );
451 $post_count = is_countable( $return['posts'] ) ? count( $return['posts'] ) : 0;
452 if ( ( $return['found'] > $post_count ) && $last_post ) {
453 if ( ! isset( $return[ $key ] ) ) {
454 $return[ $key ] = (object) array();
455 }
456 $handle = $this->build_page_handle( $last_post, $query );
457 if ( $handle !== null ) {
458 $return[ $key ]->next_page = $handle;
459 }
460 }
461 }
462
463 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
464 if ( ! isset( $return[ $key ] ) ) {
465 $return[ $key ] = new stdClass();
466 }
467 $return[ $key ]->wpcom = true;
468 }
469
470 break;
471 }
472 }
473
474 $return['found'] -= $excluded_count;
475
476 return $return;
477 }
478
479 /**
480 * Build the page handle.
481 *
482 * @param array $post - the post.
483 * @param array $query - the query.
484 */
485 public function build_page_handle( $post, $query ) {
486 $column = $query['orderby'];
487 if ( ! $column ) {
488 $column = 'date';
489 }
490 if ( ! isset( $post['ID'] ) || ! isset( $post[ $column ] ) ) {
491 return null;
492 }
493 return build_query(
494 array(
495 'value' => rawurlencode( $post[ $column ] ),
496 'id' => $post['ID'],
497 )
498 );
499 }
500
501 /**
502 * Build the date range query.
503 *
504 * @param string $column - the database column.
505 * @param array $range - the date range.
506 * @param string $where - sql where clause.
507 */
508 public function build_date_range_query( $column, $range, $where ) {
509 global $wpdb;
510
511 switch ( count( $range ) ) {
512 case 2:
513 $where .= $wpdb->prepare(
514 " AND `$wpdb->posts`.$column >= CAST( %s AS DATETIME ) AND `$wpdb->posts`.$column < CAST( %s AS DATETIME ) ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
515 $range['after'],
516 $range['before']
517 );
518 break;
519 case 1:
520 if ( isset( $range['before'] ) ) {
521 $where .= $wpdb->prepare(
522 " AND `$wpdb->posts`.$column < CAST( %s AS DATETIME ) ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
523 $range['before']
524 );
525 } else {
526 $where .= $wpdb->prepare(
527 " AND `$wpdb->posts`.$column > CAST( %s AS DATETIME ) ", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
528 $range['after']
529 );
530 }
531 break;
532 }
533
534 return $where;
535 }
536
537 /**
538 * Handle date range.
539 *
540 * @param string $where - sql where clause.
541 */
542 public function handle_date_range( $where ) {
543 return $this->build_date_range_query( 'post_date_gmt', $this->date_range, $where );
544 }
545
546 /**
547 * Handle modified date range.
548 *
549 * @param string $where - sql where clause.
550 */
551 public function handle_modified_range( $where ) {
552 return $this->build_date_range_query( 'post_modified_gmt', $this->modified_range, $where );
553 }
554
555 /**
556 * Handle where clause for page handle.
557 *
558 * @param string $where - sql where clause.
559 */
560 public function handle_where_for_page_handle( $where ) {
561 global $wpdb;
562
563 $column = $this->performed_query['orderby'];
564 if ( ! $column ) {
565 $column = 'date';
566 }
567 $order = $this->performed_query['order'];
568 if ( ! $order ) {
569 $order = 'DESC';
570 }
571
572 if ( ! in_array( $column, array( 'ID', 'title', 'date', 'modified', 'comment_count' ), true ) ) {
573 return $where;
574 }
575
576 if ( ! in_array( $order, array( 'DESC', 'ASC' ), true ) ) {
577 return $where;
578 }
579
580 $db_column = '';
581 $db_value = '';
582 switch ( $column ) {
583 case 'ID':
584 $db_column = 'ID';
585 $db_value = '%d';
586 break;
587 case 'title':
588 $db_column = 'post_title';
589 $db_value = '%s';
590 break;
591 case 'date':
592 $db_column = 'post_date';
593 $db_value = 'CAST( %s as DATETIME )';
594 break;
595 case 'modified':
596 $db_column = 'post_modified';
597 $db_value = 'CAST( %s as DATETIME )';
598 break;
599 case 'comment_count':
600 $db_column = 'comment_count';
601 $db_value = '%d';
602 break;
603 }
604
605 if ( 'DESC' === $order ) {
606 $db_order = '<';
607 } else {
608 $db_order = '>';
609 }
610
611 // Add a clause that limits the results to items beyond the passed item, or equivalent to the passed item
612 // but with an ID beyond the passed item. When we're ordering by the ID already, we only ask for items
613 // beyond the passed item.
614 $where .= $wpdb->prepare( " AND ( ( `$wpdb->posts`.`$db_column` $db_order $db_value ) ", $this->page_handle['value'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
615 if ( 'ID' !== $db_column ) {
616 $where .= $wpdb->prepare( "OR ( `$wpdb->posts`.`$db_column` = $db_value AND `$wpdb->posts`.ID $db_order %d )", $this->page_handle['value'], $this->page_handle['id'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
617 }
618 $where .= ' )';
619
620 return $where;
621 }
622
623 /**
624 * Handle how the page handle is ordered.
625 *
626 * @param string $orderby - what we're ordering by.
627 */
628 public function handle_orderby_for_page_handle( $orderby ) {
629 global $wpdb;
630 if ( 'ID' === $this->performed_query['orderby'] ) {
631 // bail if we're already ordering by ID.
632 return $orderby;
633 }
634
635 if ( $orderby ) {
636 $orderby .= ' ,';
637 }
638 $order = $this->performed_query['order'];
639 if ( ! $order ) {
640 $order = 'DESC';
641 }
642 $orderby .= " `$wpdb->posts`.ID $order";
643 return $orderby;
644 }
645 }
646