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