WPCOM_JSON_API_List_Posts_v1_1_Endpoint

The Jetpack by WordPress.com WPCOM JSON API List Posts v1 1 Endpoint class.

Defined (1)

The class is defined in the following location(s).

/json-endpoints/class.wpcom-json-api-list-posts-v1-1-endpoint.php  
  1. class WPCOM_JSON_API_List_Posts_v1_1_Endpoint extends WPCOM_JSON_API_Post_v1_1_Endpoint { 
  2. public $date_range = array(); 
  3. public $modified_range = array(); 
  4. public $page_handle = array(); 
  5. public $performed_query = null; 
  6.  
  7. public $response_format = array( 
  8. 'found' => '(int) The total number of posts found that match the request (ignoring limits, offsets, and pagination).',  
  9. 'posts' => '(array:post) An array of post objects.',  
  10. 'meta' => '(object) Meta data',  
  11. ); 
  12.  
  13. // /sites/%s/posts/ -> $blog_id 
  14. function callback( $path = '', $blog_id = 0 ) { 
  15. $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) ); 
  16. if ( is_wp_error( $blog_id ) ) { 
  17. return $blog_id; 
  18.  
  19. $args = $this->query_args(); 
  20. $is_eligible_for_page_handle = true; 
  21.  
  22. if ( $args['number'] < 1 ) { 
  23. $args['number'] = 20; 
  24. } elseif ( 100 < $args['number'] ) { 
  25. return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 ); 
  26.  
  27. if ( isset( $args['type'] ) && ! $this->is_post_type_allowed( $args['type'] ) ) { 
  28. return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 ); 
  29.  
  30. // Normalize post_type 
  31. if ( isset( $args['type'] ) && 'any' == $args['type'] ) { 
  32. if ( version_compare( $this->api->version, '1.1', '<' ) ) { 
  33. $args['type'] = array( 'post', 'page' ); 
  34. } else { // 1.1+ 
  35. $args['type'] = $this->_get_whitelisted_post_types(); 
  36.  
  37. // determine statuses 
  38. $status = $args['status']; 
  39. $status = ( $status ) ? explode( ', ', $status ) : array( 'publish' ); 
  40. if ( is_user_logged_in() ) { 
  41. $statuses_whitelist = array( 
  42. 'publish',  
  43. 'pending',  
  44. 'draft',  
  45. 'future',  
  46. 'private',  
  47. 'trash',  
  48. 'any',  
  49. ); 
  50. $status = array_intersect( $status, $statuses_whitelist ); 
  51. } else { 
  52. // logged-out users can see only published posts 
  53. $statuses_whitelist = array( 'publish', 'any' ); 
  54. $status = array_intersect( $status, $statuses_whitelist ); 
  55.  
  56. if ( empty( $status ) ) { 
  57. // requested only protected statuses? nothing for you here 
  58. return array( 'found' => 0, 'posts' => array() ); 
  59. // clear it (AKA published only) because "any" includes protected 
  60. $status = array(); 
  61.  
  62. if ( isset( $args['type'] ) && 
  63. ! in_array( $args['type'], array( 'post', 'page', 'revision', 'any' ) ) && 
  64. defined( 'IS_WPCOM' ) && IS_WPCOM ) { 
  65. $this->load_theme_functions(); 
  66.  
  67. // let's be explicit about defaulting to 'post' 
  68. $args['type'] = isset( $args['type'] ) ? $args['type'] : 'post'; 
  69.  
  70. // make sure the user can read or edit the requested post type(s) 
  71. if ( is_array( $args['type'] ) ) { 
  72. $allowed_types = array(); 
  73. foreach ( $args['type'] as $post_type ) { 
  74. if ( $this->current_user_can_access_post_type( $post_type, $args['context'] ) ) { 
  75. $allowed_types[] = $post_type; 
  76.  
  77. if ( empty( $allowed_types ) ) { 
  78. return array( 'found' => 0, 'posts' => array() ); 
  79. $args['type'] = $allowed_types; 
  80. else { 
  81. if ( ! $this->current_user_can_access_post_type( $args['type'], $args['context'] ) ) { 
  82. return array( 'found' => 0, 'posts' => array() ); 
  83.  
  84.  
  85. $query = array( 
  86. 'posts_per_page' => $args['number'],  
  87. 'order' => $args['order'],  
  88. 'orderby' => $args['order_by'],  
  89. 'post_type' => $args['type'],  
  90. 'post_status' => $status,  
  91. 'post_parent' => isset( $args['parent_id'] ) ? $args['parent_id'] : null,  
  92. 'author' => isset( $args['author'] ) && 0 < $args['author'] ? $args['author'] : null,  
  93. 's' => isset( $args['search'] ) ? $args['search'] : null,  
  94. 'fields' => 'ids',  
  95. ); 
  96.  
  97. if ( ! is_user_logged_in () ) { 
  98. $query['has_password'] = false; 
  99.  
  100. if ( isset( $args['meta_key'] ) ) { 
  101. $show = false; 
  102. if ( $this->is_metadata_public( $args['meta_key'] ) ) 
  103. $show = true; 
  104. if ( current_user_can( 'edit_post_meta', $query['post_type'], $args['meta_key'] ) ) 
  105. $show = true; 
  106.  
  107. if ( is_protected_meta( $args['meta_key'], 'post' ) && ! $show ) 
  108. return new WP_Error( 'invalid_meta_key', 'Invalid meta key', 404 ); 
  109.  
  110. $meta = array( 'key' => $args['meta_key'] ); 
  111. if ( isset( $args['meta_value'] ) ) 
  112. $meta['value'] = $args['meta_value']; 
  113.  
  114. $query['meta_query'] = array( $meta ); 
  115.  
  116. if ( $args['sticky'] === 'include' ) { 
  117. $query['ignore_sticky_posts'] = 1; 
  118. } else if ( $args['sticky'] === 'exclude' ) { 
  119. $sticky = get_option( 'sticky_posts' ); 
  120. if ( is_array( $sticky ) ) { 
  121. $query['post__not_in'] = $sticky; 
  122. } else if ( $args['sticky'] === 'require' ) { 
  123. $sticky = get_option( 'sticky_posts' ); 
  124. if ( is_array( $sticky ) && ! empty( $sticky ) ) { 
  125. $query['post__in'] = $sticky; 
  126. } else { 
  127. // no sticky posts exist 
  128. return array( 'found' => 0, 'posts' => array() ); 
  129.  
  130. if ( isset( $args['exclude'] ) ) { 
  131. $excluded_ids = (array) $args['exclude']; 
  132. $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $excluded_ids ) : $excluded_ids; 
  133.  
  134. if ( isset( $args['exclude_tree'] ) && is_post_type_hierarchical( $args['type'] ) ) { 
  135. // get_page_children is a misnomer; it supports all hierarchical post types 
  136. $page_args = array( 
  137. 'child_of' => $args['exclude_tree'],  
  138. 'post_type' => $args['type'],  
  139. // since we're looking for things to exclude, be aggressive 
  140. 'post_status' => 'publish, draft, pending, private, future, trash',  
  141. ); 
  142. $post_descendants = get_pages( $page_args ); 
  143.  
  144. $exclude_tree = array( $args['exclude_tree'] ); 
  145. foreach ( $post_descendants as $child ) { 
  146. $exclude_tree[] = $child->ID; 
  147.  
  148. $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $exclude_tree ) : $exclude_tree; 
  149.  
  150. if ( isset( $args['category'] ) ) { 
  151. $category = get_term_by( 'slug', $args['category'], 'category' ); 
  152. if ( $category === false) { 
  153. $query['category_name'] = $args['category']; 
  154. } else { 
  155. $query['cat'] = $category->term_id; 
  156.  
  157. if ( isset( $args['tag'] ) ) { 
  158. $query['tag'] = $args['tag']; 
  159.  
  160. if ( isset( $args['page'] ) ) { 
  161. if ( $args['page'] < 1 ) { 
  162. $args['page'] = 1; 
  163.  
  164. $query['paged'] = $args['page']; 
  165. if ( $query['paged'] !== 1 ) { 
  166. $is_eligible_for_page_handle = false; 
  167. } else { 
  168. if ( $args['offset'] < 0 ) { 
  169. $args['offset'] = 0; 
  170.  
  171. $query['offset'] = $args['offset']; 
  172. if ( $query['offset'] !== 0 ) { 
  173. $is_eligible_for_page_handle = false; 
  174.  
  175. if ( isset( $args['before'] ) ) { 
  176. $this->date_range['before'] = $args['before']; 
  177. if ( isset( $args['after'] ) ) { 
  178. $this->date_range['after'] = $args['after']; 
  179.  
  180. if ( isset( $args['modified_before_gmt'] ) ) { 
  181. $this->modified_range['before'] = $args['modified_before_gmt']; 
  182. if ( isset( $args['modified_after_gmt'] ) ) { 
  183. $this->modified_range['after'] = $args['modified_after_gmt']; 
  184.  
  185. if ( $this->date_range ) { 
  186. add_filter( 'posts_where', array( $this, 'handle_date_range' ) ); 
  187.  
  188. if ( $this->modified_range ) { 
  189. add_filter( 'posts_where', array( $this, 'handle_modified_range' ) ); 
  190.  
  191. if ( isset( $args['page_handle'] ) ) { 
  192. $page_handle = wp_parse_args( $args['page_handle'] ); 
  193. if ( isset( $page_handle['value'] ) && isset( $page_handle['id'] ) ) { 
  194. // we have a valid looking page handle 
  195. $this->page_handle = $page_handle; 
  196. add_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) ); 
  197.  
  198. /** 
  199. * 'column' necessary for the me/posts endpoint (which extends sites/$site/posts). 
  200. * Would need to be added to the sites/$site/posts definition if we ever want to 
  201. * use it there. 
  202. */ 
  203. $column_whitelist = array( 'post_modified_gmt' ); 
  204. if ( isset( $args['column'] ) && in_array( $args['column'], $column_whitelist ) ) { 
  205. $query['column'] = $args['column']; 
  206.  
  207. $this->performed_query = $query; 
  208. add_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) ); 
  209.  
  210. $wp_query = new WP_Query( $query ); 
  211.  
  212. remove_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) ); 
  213.  
  214. if ( $this->date_range ) { 
  215. remove_filter( 'posts_where', array( $this, 'handle_date_range' ) ); 
  216. $this->date_range = array(); 
  217.  
  218. if ( $this->modified_range ) { 
  219. remove_filter( 'posts_where', array( $this, 'handle_modified_range' ) ); 
  220. $this->modified_range = array(); 
  221.  
  222. if ( $this->page_handle ) { 
  223. remove_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) ); 
  224.  
  225.  
  226. $return = array(); 
  227. $excluded_count = 0; 
  228. foreach ( array_keys( $this->response_format ) as $key ) { 
  229. switch ( $key ) { 
  230. case 'found' : 
  231. $return[$key] = (int) $wp_query->found_posts; 
  232. break; 
  233. case 'posts' : 
  234. $posts = array(); 
  235. foreach ( $wp_query->posts as $post_ID ) { 
  236. $the_post = $this->get_post_by( 'ID', $post_ID, $args['context'] ); 
  237. if ( $the_post && ! is_wp_error( $the_post ) ) { 
  238. $posts[] = $the_post; 
  239. } else { 
  240. $excluded_count++; 
  241.  
  242. if ( $posts ) { 
  243. /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */ 
  244. do_action( 'wpcom_json_api_objects', 'posts', count( $posts ) ); 
  245.  
  246. $return[$key] = $posts; 
  247. break; 
  248.  
  249. case 'meta' : 
  250. if ( ! is_array( $args['type'] ) ) { 
  251. $return[$key] = (object) array( 
  252. 'links' => (object) array( 
  253. 'counts' => (string) $this->get_site_link( $blog_id, 'post-counts/' . $args['type'] ),  
  254. ); 
  255.  
  256. if ( $is_eligible_for_page_handle && $return['posts'] ) { 
  257. $last_post = end( $return['posts'] ); 
  258. reset( $return['posts'] ); 
  259. if ( ( $return['found'] > count( $return['posts'] ) ) && $last_post ) { 
  260. if ( ! isset( $return[$key] ) ) { 
  261. $return[$key] = (object) array(); 
  262. $return[$key]->next_page = $this->build_page_handle( $last_post, $query ); 
  263. break; 
  264.  
  265. $return['found'] -= $excluded_count; 
  266.  
  267. return $return; 
  268.  
  269. function build_page_handle( $post, $query ) { 
  270. $column = $query['orderby']; 
  271. if ( ! $column ) { 
  272. $column = 'date'; 
  273. return build_query( array( 'value' => urlencode($post[$column]), 'id' => $post['ID'] ) ); 
  274.  
  275. function _build_date_range_query( $column, $range, $where ) { 
  276. global $wpdb; 
  277.  
  278. switch ( count( $range ) ) { 
  279. case 2 : 
  280. $where .= $wpdb->prepare( 
  281. " AND `$wpdb->posts`.$column >= CAST( %s AS DATETIME ) AND `$wpdb->posts`.$column < CAST( %s AS DATETIME ) ",  
  282. $range['after'],  
  283. $range['before'] 
  284. ); 
  285. break; 
  286. case 1 : 
  287. if ( isset( $range['before'] ) ) { 
  288. $where .= $wpdb->prepare( 
  289. " AND `$wpdb->posts`.$column < CAST( %s AS DATETIME ) ",  
  290. $range['before'] 
  291. ); 
  292. } else { 
  293. $where .= $wpdb->prepare( 
  294. " AND `$wpdb->posts`.$column > CAST( %s AS DATETIME ) ",  
  295. $range['after'] 
  296. ); 
  297. break; 
  298.  
  299. return $where; 
  300.  
  301. function handle_date_range( $where ) { 
  302. return $this->_build_date_range_query( 'post_date', $this->date_range, $where ); 
  303.  
  304. function handle_modified_range( $where ) { 
  305. return $this->_build_date_range_query( 'post_modified_gmt', $this->modified_range, $where ); 
  306.  
  307. function handle_where_for_page_handle( $where ) { 
  308. global $wpdb; 
  309.  
  310. $column = $this->performed_query['orderby']; 
  311. if ( ! $column ) { 
  312. $column = 'date'; 
  313. $order = $this->performed_query['order']; 
  314. if ( ! $order ) { 
  315. $order = 'DESC'; 
  316.  
  317. if ( ! in_array( $column, array( 'ID', 'title', 'date', 'modified', 'comment_count' ) ) ) { 
  318. return $where; 
  319.  
  320. if ( ! in_array( $order, array( 'DESC', 'ASC' ) ) ) { 
  321. return $where; 
  322.  
  323. $db_column = ''; 
  324. $db_value = ''; 
  325. switch( $column ) { 
  326. case 'ID': 
  327. $db_column = 'ID'; 
  328. $db_value = '%d'; 
  329. break; 
  330. case 'title': 
  331. $db_column = 'post_title'; 
  332. $db_value = '%s'; 
  333. break; 
  334. case 'date': 
  335. $db_column = 'post_date'; 
  336. $db_value = 'CAST( %s as DATETIME )'; 
  337. break; 
  338. case 'modified': 
  339. $db_column = 'post_modified'; 
  340. $db_value = 'CAST( %s as DATETIME )'; 
  341. break; 
  342. case 'comment_count': 
  343. $db_column = 'comment_count'; 
  344. $db_value = '%d'; 
  345. break; 
  346.  
  347. if ( 'DESC'=== $order ) { 
  348. $db_order = '<'; 
  349. } else { 
  350. $db_order = '>'; 
  351.  
  352. // Add a clause that limits the results to items beyond the passed item, or equivalent to the passed item 
  353. // but with an ID beyond the passed item. When we're ordering by the ID already, we only ask for items 
  354. // beyond the passed item. 
  355. $where .= $wpdb->prepare( " AND ( ( `$wpdb->posts`.`$db_column` $db_order $db_value ) ", $this->page_handle['value'] ); 
  356. if ( $db_column !== 'ID' ) { 
  357. $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'] ); 
  358. $where .= ' )'; 
  359.  
  360. return $where; 
  361.  
  362. function handle_orderby_for_page_handle( $orderby ) { 
  363. global $wpdb; 
  364. if ( $this->performed_query['orderby'] === 'ID' ) { 
  365. // bail if we're already ordering by ID 
  366. return $orderby; 
  367.  
  368. if ( $orderby ) { 
  369. $orderby .= ' , '; 
  370. $order = $this->performed_query['order']; 
  371. if ( ! $order ) { 
  372. $order = 'DESC'; 
  373. $orderby .= " `$wpdb->posts`.ID $order"; 
  374. return $orderby;