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