Нет описания

class.wpcom-json-api-list-comments-endpoint.php 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <?php
  2. class WPCOM_JSON_API_List_Comments_Walker extends Walker {
  3. public $tree_type = 'comment';
  4. public $db_fields = array(
  5. 'parent' => 'comment_parent',
  6. 'id' => 'comment_ID'
  7. );
  8. public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
  9. if ( ! is_array( $output ) ) {
  10. $output = array();
  11. }
  12. $output[] = $object->comment_ID;
  13. }
  14. /**
  15. * Taken from WordPress's Walker_Comment::display_element()
  16. *
  17. * This function is designed to enhance Walker::display_element() to
  18. * display children of higher nesting levels than selected inline on
  19. * the highest depth level displayed. This prevents them being orphaned
  20. * at the end of the comment list.
  21. *
  22. * Example: max_depth = 2, with 5 levels of nested content.
  23. * 1
  24. * 1.1
  25. * 1.1.1
  26. * 1.1.1.1
  27. * 1.1.1.1.1
  28. * 1.1.2
  29. * 1.1.2.1
  30. * 2
  31. * 2.2
  32. *
  33. * @see Walker_Comment::display_element()
  34. * @see Walker::display_element()
  35. * @see wp_list_comments()
  36. */
  37. public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
  38. if ( !$element )
  39. return;
  40. $id_field = $this->db_fields['id'];
  41. $id = $element->$id_field;
  42. parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
  43. // If we're at the max depth, and the current element still has children, loop over those and display them at this level
  44. // This is to prevent them being orphaned to the end of the list.
  45. if ( $max_depth <= $depth + 1 && isset( $children_elements[$id]) ) {
  46. foreach ( $children_elements[ $id ] as $child )
  47. $this->display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
  48. unset( $children_elements[ $id ] );
  49. }
  50. }
  51. }
  52. new WPCOM_JSON_API_List_Comments_Endpoint( array(
  53. 'description' => 'Get a list of recent comments.',
  54. 'group' => 'comments',
  55. 'stat' => 'comments',
  56. 'method' => 'GET',
  57. 'path' => '/sites/%s/comments/',
  58. 'path_labels' => array(
  59. '$site' => '(int|string) Site ID or domain',
  60. ),
  61. 'allow_fallback_to_jetpack_blog_token' => true,
  62. 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/comments/?number=2',
  63. ) );
  64. new WPCOM_JSON_API_List_Comments_Endpoint( array(
  65. 'description' => 'Get a list of recent comments on a post.',
  66. 'group' => 'comments',
  67. 'stat' => 'posts:1:replies',
  68. 'method' => 'GET',
  69. 'path' => '/sites/%s/posts/%d/replies/',
  70. 'path_labels' => array(
  71. '$site' => '(int|string) Site ID or domain',
  72. '$post_ID' => '(int) The post ID',
  73. ),
  74. 'allow_fallback_to_jetpack_blog_token' => true,
  75. 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/7/replies/?number=2',
  76. ) );
  77. // @todo permissions
  78. class WPCOM_JSON_API_List_Comments_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
  79. public $response_format = array(
  80. 'found' => '(int) The total number of comments found that match the request (ignoring limits, offsets, and pagination).',
  81. 'site_ID' => '(int) The site ID',
  82. 'comments' => '(array:comment) An array of comment objects.',
  83. );
  84. function __construct( $args ) {
  85. parent::__construct( $args );
  86. $this->query = array_merge( $this->query, array(
  87. 'number' => '(int=20) The number of comments to return. Limit: 100. When using hierarchical=1, number refers to the number of top-level comments returned.',
  88. 'offset' => '(int=0) 0-indexed offset. Not available if using hierarchical=1.',
  89. 'page' => '(int) Return the Nth 1-indexed page of comments. Takes precedence over the <code>offset</code> parameter. When using hierarchical=1, pagination is a bit different. See the note on the number parameter.',
  90. 'order' => array(
  91. 'DESC' => 'Return comments in descending order from newest to oldest.',
  92. 'ASC' => 'Return comments in ascending order from oldest to newest.',
  93. ),
  94. 'hierarchical' => array(
  95. 'false' => '',
  96. 'true' => '(BETA) Order the comment list hierarchically.',
  97. ),
  98. 'after' => '(ISO 8601 datetime) Return comments dated on or after the specified datetime. Not available if using hierarchical=1.',
  99. 'before' => '(ISO 8601 datetime) Return comments dated on or before the specified datetime. Not available if using hierarchical=1.',
  100. 'type' => array(
  101. 'any' => 'Return all comments regardless of type.',
  102. 'comment' => 'Return only regular comments.',
  103. 'trackback' => 'Return only trackbacks.',
  104. 'pingback' => 'Return only pingbacks.',
  105. 'pings' => 'Return both trackbacks and pingbacks.',
  106. ),
  107. 'status' => array(
  108. 'approved' => 'Return only approved comments.',
  109. 'unapproved' => 'Return only comments in the moderation queue.',
  110. 'spam' => 'Return only comments marked as spam.',
  111. 'trash' => 'Return only comments in the trash.',
  112. 'all' => 'Return comments of all statuses.',
  113. ),
  114. ) );
  115. }
  116. // /sites/%s/comments/ -> $blog_id
  117. // /sites/%s/posts/%d/replies/ -> $blog_id, $post_id
  118. // /sites/%s/comments/%d/replies/ -> $blog_id, $comment_id
  119. function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
  120. $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
  121. if ( is_wp_error( $blog_id ) ) {
  122. return $blog_id;
  123. }
  124. $args = $this->query_args();
  125. if ( $args['number'] < 1 ) {
  126. $args['number'] = 20;
  127. } elseif ( 100 < $args['number'] ) {
  128. return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
  129. }
  130. if ( false !== strpos( $path, '/posts/' ) ) {
  131. // We're looking for comments of a particular post
  132. $post_id = $object_id;
  133. $comment_id = 0;
  134. } else {
  135. // We're looking for comments for the whole blog, or replies to a single comment
  136. $comment_id = $object_id;
  137. $post_id = 0;
  138. }
  139. // We can't efficiently get the number of replies to a single comment
  140. $count = false;
  141. $found = -1;
  142. if ( !$comment_id ) {
  143. // We can get comment counts for the whole site or for a single post, but only for certain queries
  144. if ( 'any' === $args['type'] && !isset( $args['after'] ) && !isset( $args['before'] ) ) {
  145. $count = $this->api->wp_count_comments( $post_id );
  146. }
  147. }
  148. switch ( $args['status'] ) {
  149. case 'approved' :
  150. $status = 'approve';
  151. if ( $count ) {
  152. $found = $count->approved;
  153. }
  154. break;
  155. default :
  156. if ( ! current_user_can( 'edit_posts' ) ) {
  157. return new WP_Error( 'unauthorized', 'User cannot read non-approved comments', 403 );
  158. }
  159. if ( 'unapproved' === $args['status'] ) {
  160. $status = 'hold';
  161. $count_status = 'moderated';
  162. } elseif ( 'all' === $args['status'] ) {
  163. $status = 'all';
  164. $count_status = 'total_comments';
  165. } else {
  166. $status = $count_status = $args['status'];
  167. }
  168. if ( $count ) {
  169. $found = $count->$count_status;
  170. }
  171. }
  172. /** This filter is documented in class.json-api.php */
  173. $exclude = apply_filters( 'jetpack_api_exclude_comment_types',
  174. array( 'order_note', 'webhook_delivery', 'review', 'action_log' )
  175. );
  176. $query = array(
  177. 'order' => $args['order'],
  178. 'type' => 'any' === $args['type'] ? false : $args['type'],
  179. 'status' => $status,
  180. 'type__not_in' => $exclude,
  181. );
  182. if ( isset( $args['page'] ) ) {
  183. if ( $args['page'] < 1 ) {
  184. $args['page'] = 1;
  185. }
  186. } else {
  187. if ( $args['offset'] < 0 ) {
  188. $args['offset'] = 0;
  189. }
  190. }
  191. if ( ! $args['hierarchical'] ) {
  192. $query['number'] = $args['number'];
  193. if ( isset( $args['page'] ) ) {
  194. $query['offset'] = ( $args['page'] - 1 ) * $args['number'];
  195. } else {
  196. $query['offset'] = $args['offset'];
  197. }
  198. $is_before = isset( $args['before_gmt'] );
  199. $is_after = isset( $args['after_gmt'] );
  200. if ( $is_before || $is_after ) {
  201. $query['date_query'] = array(
  202. 'column' => 'comment_date_gmt',
  203. 'inclusive' => true,
  204. );
  205. if ( $is_before ) {
  206. $query['date_query']['before'] = $args['before_gmt'];
  207. }
  208. if ( $is_after ) {
  209. $query['date_query']['after'] = $args['after_gmt'];
  210. }
  211. }
  212. }
  213. if ( $post_id ) {
  214. $post = get_post( $post_id );
  215. if ( !$post || is_wp_error( $post ) ) {
  216. return new WP_Error( 'unknown_post', 'Unknown post', 404 );
  217. }
  218. $query['post_id'] = $post->ID;
  219. if ( $this->api->ends_with( $this->path, '/replies' ) ) {
  220. $query['parent'] = 0;
  221. }
  222. } elseif ( $comment_id ) {
  223. $comment = get_comment( $comment_id );
  224. if ( !$comment || is_wp_error( $comment ) ) {
  225. return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
  226. }
  227. $query['parent'] = $comment_id;
  228. }
  229. $comments = get_comments( $query );
  230. update_comment_cache( $comments );
  231. if ( $args['hierarchical'] ) {
  232. $walker = new WPCOM_JSON_API_List_Comments_Walker;
  233. $comment_ids = $walker->paged_walk( $comments, get_option( 'thread_comments_depth', -1 ), isset( $args['page'] ) ? $args['page'] : 1 , $args['number'] );
  234. if ( ! empty( $comment_ids ) ) {
  235. $comments = array_map( 'get_comment', $comment_ids );
  236. }
  237. }
  238. $return = array();
  239. foreach ( array_keys( $this->response_format ) as $key ) {
  240. switch ( $key ) {
  241. case 'found' :
  242. $return[ $key ] = (int) $found;
  243. break;
  244. case 'site_ID' :
  245. $return[ $key ] = (int) $blog_id;
  246. break;
  247. case 'comments' :
  248. $return_comments = array();
  249. if ( ! empty( $comments ) ) {
  250. foreach ( $comments as $comment ) {
  251. $the_comment = $this->get_comment( $comment->comment_ID, $args['context'] );
  252. if ( $the_comment && !is_wp_error( $the_comment ) ) {
  253. $return_comments[] = $the_comment;
  254. }
  255. }
  256. }
  257. if ( $return_comments ) {
  258. /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
  259. do_action( 'wpcom_json_api_objects', 'comments', count( $return_comments ) );
  260. }
  261. $return[ $key ] = $return_comments;
  262. break;
  263. }
  264. }
  265. return $return;
  266. }
  267. }