WPCOM_JSON_API

The Jetpack by WordPress.com WPCOM JSON API class.

Defined (1)

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

/class.json-api.php  
  1. class WPCOM_JSON_API { 
  2. static $self = null; 
  3.  
  4. public $endpoints = array(); 
  5.  
  6. public $token_details = array(); 
  7.  
  8. public $method = ''; 
  9. public $url = ''; 
  10. public $path = ''; 
  11. public $version = null; 
  12. public $query = array(); 
  13. public $post_body = null; 
  14. public $files = null; 
  15. public $content_type = null; 
  16. public $accept = ''; 
  17.  
  18. public $_server_https; 
  19. public $exit = true; 
  20. public $public_api_scheme = 'https'; 
  21.  
  22. public $output_status_code = 200; 
  23.  
  24. public $trapped_error = null; 
  25. public $did_output = false; 
  26.  
  27. /** 
  28. * @return WPCOM_JSON_API instance 
  29. */ 
  30. static function init( $method = null, $url = null, $post_body = null ) { 
  31. if ( !self::$self ) { 
  32. $class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__; 
  33. self::$self = new $class( $method, $url, $post_body ); 
  34. return self::$self; 
  35.  
  36. function add( WPCOM_JSON_API_Endpoint $endpoint ) { 
  37. $path_versions = serialize( array ( 
  38. $endpoint->path,  
  39. $endpoint->min_version,  
  40. $endpoint->max_version,  
  41. ) ); 
  42. if ( !isset( $this->endpoints[$path_versions] ) ) { 
  43. $this->endpoints[$path_versions] = array(); 
  44. $this->endpoints[$path_versions][$endpoint->method] = $endpoint; 
  45.  
  46. static function is_truthy( $value ) { 
  47. switch ( strtolower( (string) $value ) ) { 
  48. case '1' : 
  49. case 't' : 
  50. case 'true' : 
  51. return true; 
  52.  
  53. return false; 
  54.  
  55. static function is_falsy( $value ) { 
  56. switch ( strtolower( (string) $value ) ) { 
  57. case '0' : 
  58. case 'f' : 
  59. case 'false' : 
  60. return true; 
  61.  
  62. return false; 
  63.  
  64. function __construct() { 
  65. $args = func_get_args(); 
  66. call_user_func_array( array( $this, 'setup_inputs' ), $args ); 
  67.  
  68. function setup_inputs( $method = null, $url = null, $post_body = null ) { 
  69. if ( is_null( $method ) ) { 
  70. $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] ); 
  71. } else { 
  72. $this->method = strtoupper( $method ); 
  73. if ( is_null( $url ) ) { 
  74. $this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 
  75. } else { 
  76. $this->url = $url; 
  77.  
  78. $parsed = parse_url( $this->url ); 
  79. $this->path = $parsed['path']; 
  80.  
  81. if ( !empty( $parsed['query'] ) ) { 
  82. wp_parse_str( $parsed['query'], $this->query ); 
  83.  
  84. if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) { 
  85. $this->accept = $_SERVER['HTTP_ACCEPT']; 
  86.  
  87. if ( 'POST' === $this->method ) { 
  88. if ( is_null( $post_body ) ) { 
  89. $this->post_body = file_get_contents( 'php://input' ); 
  90.  
  91. if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) { 
  92. $this->content_type = $_SERVER['HTTP_CONTENT_TYPE']; 
  93. } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) { 
  94. $this->content_type = $_SERVER['CONTENT_TYPE'] ; 
  95. } elseif ( '{' === $this->post_body[0] ) { 
  96. $this->content_type = 'application/json'; 
  97. } else { 
  98. $this->content_type = 'application/x-www-form-urlencoded'; 
  99.  
  100. if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) { 
  101. $this->post_body = http_build_query( stripslashes_deep( $_POST ) ); 
  102. $this->files = $_FILES; 
  103. $this->content_type = 'multipart/form-data'; 
  104. } else { 
  105. $this->post_body = $post_body; 
  106. $this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded'; 
  107. } else { 
  108. $this->post_body = null; 
  109. $this->content_type = null; 
  110.  
  111. $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--'; 
  112.  
  113. function initialize() { 
  114. $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' ); 
  115.  
  116. function serve( $exit = true ) { 
  117. ini_set( 'display_errors', false ); 
  118.  
  119. $this->exit = (bool) $exit; 
  120.  
  121. // This was causing problems with Jetpack, but is necessary for wpcom 
  122. // @see https://github.com/Automattic/jetpack/pull/2603 
  123. // @see r124548-wpcom 
  124. if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { 
  125. add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 ); 
  126.  
  127. add_filter( 'user_can_richedit', '__return_true' ); 
  128.  
  129. add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) ); 
  130.  
  131. $initialization = $this->initialize(); 
  132. if ( 'OPTIONS' == $this->method ) { 
  133. /** 
  134. * Fires before the page output. 
  135. * Can be used to specify custom header options. 
  136. * @module json-api 
  137. * @since 3.1.0 
  138. */ 
  139. do_action( 'wpcom_json_api_options' ); 
  140. return $this->output( 200, '', 'plain/text' ); 
  141.  
  142. if ( is_wp_error( $initialization ) ) { 
  143. $this->output_error( $initialization ); 
  144. return; 
  145.  
  146. // Normalize path and extract API version 
  147. $this->path = untrailingslashit( $this->path ); 
  148. preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches ); 
  149.  
  150. // HACK Alert! 
  151. // In order to workaround a bug in the iOS 5.6 release we need to handle /rest/sites/new as if it was 
  152. // /rest/v1.1/sites/new 
  153. if ( $this->path === '/rest/sites/new' ) { 
  154. $this->version = '1.1'; 
  155. $this->path = '/sites/new'; 
  156. } else if ( $this->path === '/rest/users/new' ) { 
  157. $this->version = '1.1'; 
  158. $this->path = '/users/new'; 
  159. } else { 
  160. $this->path = substr( $this->path, strlen( $matches[0] ) ); 
  161. $this->version = $matches[1]; 
  162.  
  163. $allowed_methods = array( 'GET', 'POST' ); 
  164. $four_oh_five = false; 
  165.  
  166. $is_help = preg_match( '#/help/?$#i', $this->path ); 
  167. $matching_endpoints = array(); 
  168.  
  169. if ( $is_help ) { 
  170. $origin = get_http_origin(); 
  171.  
  172. if ( !empty( $origin ) && 'GET' == $this->method ) { 
  173. header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) ); 
  174.  
  175. $this->path = substr( rtrim( $this->path, '/' ), 0, -5 ); 
  176. // Show help for all matching endpoints regardless of method 
  177. $methods = $allowed_methods; 
  178. $find_all_matching_endpoints = true; 
  179. // How deep to truncate each endpoint's path to see if it matches this help request 
  180. $depth = substr_count( $this->path, '/' ) + 1; 
  181. if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) { 
  182. $help_content_type = 'json'; 
  183. } else { 
  184. $help_content_type = 'html'; 
  185. } else { 
  186. if ( in_array( $this->method, $allowed_methods ) ) { 
  187. // Only serve requested method 
  188. $methods = array( $this->method ); 
  189. $find_all_matching_endpoints = false; 
  190. } else { 
  191. // We don't allow this requested method - find matching endpoints and send 405 
  192. $methods = $allowed_methods; 
  193. $find_all_matching_endpoints = true; 
  194. $four_oh_five = true; 
  195.  
  196. // Find which endpoint to serve 
  197. $found = false; 
  198. foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) { 
  199. $endpoint_path_versions = unserialize( $endpoint_path_versions ); 
  200. $endpoint_path = $endpoint_path_versions[0]; 
  201. $endpoint_min_version = $endpoint_path_versions[1]; 
  202. $endpoint_max_version = $endpoint_path_versions[2]; 
  203.  
  204. // Make sure max_version is not less than min_version 
  205. if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) { 
  206. $endpoint_max_version = $endpoint_min_version; 
  207.  
  208. foreach ( $methods as $method ) { 
  209. if ( !isset( $endpoints_by_method[$method] ) ) { 
  210. continue; 
  211.  
  212. // Normalize 
  213. $endpoint_path = untrailingslashit( $endpoint_path ); 
  214. if ( $is_help ) { 
  215. // Truncate path at help depth 
  216. $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) ); 
  217.  
  218. // Generate regular expression from sprintf() 
  219. $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path ); 
  220.  
  221. if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) { 
  222. // This endpoint does not match the requested path. 
  223. continue; 
  224.  
  225. if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) { 
  226. // This endpoint does not match the requested version. 
  227. continue; 
  228.  
  229. $found = true; 
  230.  
  231. if ( $find_all_matching_endpoints ) { 
  232. $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces ); 
  233. } else { 
  234. // The method parameters are now in $path_pieces 
  235. $endpoint = $endpoints_by_method[$method]; 
  236. break 2; 
  237.  
  238. if ( !$found ) { 
  239. return $this->output( 404, '', 'text/plain' ); 
  240.  
  241. if ( $four_oh_five ) { 
  242. $allowed_methods = array(); 
  243. foreach ( $matching_endpoints as $matching_endpoint ) { 
  244. $allowed_methods[] = $matching_endpoint[0]->method; 
  245.  
  246. header( 'Allow: ' . strtoupper( join( ', ', array_unique( $allowed_methods ) ) ) ); 
  247. return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) ); 
  248.  
  249. if ( $is_help ) { 
  250. /** 
  251. * Fires before the API output. 
  252. * @since 1.9.0 
  253. * @param string help. 
  254. */ 
  255. do_action( 'wpcom_json_api_output', 'help' ); 
  256. if ( 'json' === $help_content_type ) { 
  257. $docs = array(); 
  258. foreach ( $matching_endpoints as $matching_endpoint ) { 
  259. if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG ) 
  260. $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) ); 
  261. return $this->output( 200, $docs ); 
  262. } else { 
  263. status_header( 200 ); 
  264. foreach ( $matching_endpoints as $matching_endpoint ) { 
  265. if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG ) 
  266. call_user_func( array( $matching_endpoint[0], 'document' ) ); 
  267. exit; 
  268.  
  269. if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) { 
  270. return $this->output( 404, '', 'text/plain' ); 
  271.  
  272. /** This action is documented in class.json-api.php */ 
  273. do_action( 'wpcom_json_api_output', $endpoint->stat ); 
  274.  
  275. $response = $this->process_request( $endpoint, $path_pieces ); 
  276.  
  277. if ( !$response && !is_array( $response ) ) { 
  278. return $this->output( 500, '', 'text/plain' ); 
  279. } elseif ( is_wp_error( $response ) ) { 
  280. return $this->output_error( $response ); 
  281.  
  282. $output_status_code = $this->output_status_code; 
  283. $this->set_output_status_code(); 
  284.  
  285. return $this->output( $output_status_code, $response ); 
  286.  
  287. function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) { 
  288. $this->endpoint = $endpoint; 
  289. return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces ); 
  290.  
  291. function output_early( $status_code, $response = null, $content_type = 'application/json' ) { 
  292. $exit = $this->exit; 
  293. $this->exit = false; 
  294. if ( is_wp_error( $response ) ) 
  295. $this->output_error( $response ); 
  296. else 
  297. $this->output( $status_code, $response, $content_type ); 
  298. $this->exit = $exit; 
  299. if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) { 
  300. $this->finish_request(); 
  301.  
  302. function set_output_status_code( $code = 200 ) { 
  303. $this->output_status_code = $code; 
  304.  
  305. function output( $status_code, $response = null, $content_type = 'application/json' ) { 
  306. // In case output() was called before the callback returned 
  307. if ( $this->did_output ) { 
  308. if ( $this->exit ) 
  309. exit; 
  310. return $content_type; 
  311. $this->did_output = true; 
  312.  
  313. // 400s and 404s are allowed for all origins 
  314. if ( 404 == $status_code || 400 == $status_code ) 
  315. header( 'Access-Control-Allow-Origin: *' ); 
  316.  
  317. if ( is_null( $response ) ) { 
  318. $response = new stdClass; 
  319.  
  320. if ( 'text/plain' === $content_type ) { 
  321. status_header( (int) $status_code ); 
  322. header( 'Content-Type: text/plain' ); 
  323. echo $response; 
  324. if ( $this->exit ) { 
  325. exit; 
  326.  
  327. return $content_type; 
  328.  
  329. $response = $this->filter_fields( $response ); 
  330.  
  331. if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) { 
  332. $response = array( 
  333. 'code' => (int) $status_code,  
  334. 'headers' => array( 
  335. array( 
  336. 'name' => 'Content-Type',  
  337. 'value' => $content_type,  
  338. ),  
  339. ),  
  340. 'body' => $response,  
  341. ); 
  342. $status_code = 200; 
  343. $content_type = 'application/json'; 
  344.  
  345. status_header( (int) $status_code ); 
  346. header( "Content-Type: $content_type" ); 
  347. if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) { 
  348. $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] ); 
  349. } else { 
  350. $callback = false; 
  351.  
  352. if ( $callback ) { 
  353. // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header 
  354. // and by prepending the JSONP response with a JS comment. 
  355. // [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ 
  356. echo "/**/$callback("; 
  357.  
  358. echo $this->json_encode( $response ); 
  359. if ( $callback ) { 
  360. echo ");"; 
  361.  
  362. if ( $this->exit ) { 
  363. exit; 
  364.  
  365. return $content_type; 
  366.  
  367. public static function serializable_error ( $error ) { 
  368.  
  369. $status_code = $error->get_error_data(); 
  370.  
  371. if ( is_array( $status_code ) ) 
  372. $status_code = $status_code['status_code']; 
  373.  
  374. if ( !$status_code ) { 
  375. $status_code = 400; 
  376. $response = array( 
  377. 'error' => $error->get_error_code(),  
  378. 'message' => $error->get_error_message(),  
  379. ); 
  380. return array( 
  381. 'status_code' => $status_code,  
  382. 'errors' => $response 
  383. ); 
  384.  
  385. function output_error( $error ) { 
  386. if ( function_exists( 'bump_stats_extra' ) ) { 
  387. $client_id = ! empty( $this->token_details['client_id'] ) ? $this->token_details['client_id'] : 0; 
  388. bump_stats_extra( 'rest-api-errors', $client_id ); 
  389.  
  390. $error_response = $this->serializable_error( $error ); 
  391.  
  392. return $this->output( $error_response[ 'status_code'], $error_response['errors'] ); 
  393.  
  394. function filter_fields( $response ) { 
  395. if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) ) 
  396. return $response; 
  397.  
  398. $fields = array_map( 'trim', explode( ', ', $this->query['fields'] ) ); 
  399.  
  400. if ( is_object( $response ) ) { 
  401. $response = (array) $response; 
  402.  
  403. $has_filtered = false; 
  404. if ( is_array( $response ) && empty( $response['ID'] ) ) { 
  405. $keys_to_filter = array( 
  406. 'categories',  
  407. 'comments',  
  408. 'connections',  
  409. 'domains',  
  410. 'groups',  
  411. 'likes',  
  412. 'media',  
  413. 'notes',  
  414. 'posts',  
  415. 'services',  
  416. 'sites',  
  417. 'suggestions',  
  418. 'tags',  
  419. 'themes',  
  420. 'topics',  
  421. 'users',  
  422. ); 
  423.  
  424. foreach ( $keys_to_filter as $key_to_filter ) { 
  425. if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered ) 
  426. continue; 
  427.  
  428. foreach ( $response[ $key_to_filter ] as $key => $values ) { 
  429. if ( is_object( $values ) ) { 
  430. $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( (array) $values, array_flip( $fields ) ); 
  431. } elseif ( is_array( $values ) ) { 
  432. $response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) ); 
  433.  
  434. $has_filtered = true; 
  435.  
  436. if ( ! $has_filtered ) { 
  437. if ( is_object( $response ) ) { 
  438. $response = (object) array_intersect_key( (array) $response, array_flip( $fields ) ); 
  439. } else if ( is_array( $response ) ) { 
  440. $response = array_intersect_key( $response, array_flip( $fields ) ); 
  441.  
  442. return $response; 
  443.  
  444. function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) { 
  445. if ( $original_scheme ) { 
  446. return $url; 
  447.  
  448. return preg_replace( '#^https:#', 'http:', $url ); 
  449.  
  450. function comment_edit_pre( $comment_content ) { 
  451. return htmlspecialchars_decode( $comment_content, ENT_QUOTES ); 
  452.  
  453. function json_encode( $data ) { 
  454. return json_encode( $data ); 
  455.  
  456. function ends_with( $haystack, $needle ) { 
  457. return $needle === substr( $haystack, -strlen( $needle ) ); 
  458.  
  459. // Returns the site's blog_id in the WP.com ecosystem 
  460. function get_blog_id_for_output() { 
  461. return $this->token_details['blog_id']; 
  462.  
  463. // Returns the site's local blog_id 
  464. function get_blog_id( $blog_id ) { 
  465. return $GLOBALS['blog_id']; 
  466.  
  467. function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) { 
  468. if ( $this->is_restricted_blog( $blog_id ) ) { 
  469. return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 ); 
  470.  
  471. if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) { 
  472. return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 ); 
  473.  
  474. return $blog_id; 
  475.  
  476. // Returns true if the specified blog ID is a restricted blog 
  477. function is_restricted_blog( $blog_id ) { 
  478. /** 
  479. * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs. 
  480. * @module json-api 
  481. * @since 3.4.0 
  482. * @param array $array Array of Blog IDs. 
  483. */ 
  484. $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() ); 
  485. return true === in_array( $blog_id, $restricted_blog_ids ); 
  486.  
  487. function post_like_count( $blog_id, $post_id ) { 
  488. return 0; 
  489.  
  490. function is_liked( $blog_id, $post_id ) { 
  491. return false; 
  492.  
  493. function is_reblogged( $blog_id, $post_id ) { 
  494. return false; 
  495.  
  496. function is_following( $blog_id ) { 
  497. return false; 
  498.  
  499. function add_global_ID( $blog_id, $post_id ) { 
  500. return ''; 
  501.  
  502. function get_avatar_url( $email, $avatar_size = 96 ) { 
  503. add_filter( 'pre_option_show_avatars', '__return_true', 999 ); 
  504. $_SERVER['HTTPS'] = 'off'; 
  505.  
  506. $avatar_img_element = get_avatar( $email, $avatar_size, '' ); 
  507.  
  508. if ( !$avatar_img_element || is_wp_error( $avatar_img_element ) ) { 
  509. $return = ''; 
  510. } elseif ( !preg_match( '#src=([\'"])?(.*?)(?(1)\\1|\s)#', $avatar_img_element, $matches ) ) { 
  511. $return = ''; 
  512. } else { 
  513. $return = esc_url_raw( htmlspecialchars_decode( $matches[2] ) ); 
  514.  
  515. remove_filter( 'pre_option_show_avatars', '__return_true', 999 ); 
  516. if ( '--UNset--' === $this->_server_https ) { 
  517. unset( $_SERVER['HTTPS'] ); 
  518. } else { 
  519. $_SERVER['HTTPS'] = $this->_server_https; 
  520.  
  521. return $return; 
  522.  
  523. /** 
  524. * Traps `wp_die()` calls and outputs a JSON response instead. 
  525. * The result is always output, never returned. 
  526. * @param string|null $error_code. Call with string to start the trapping. Call with null to stop. 
  527. */ 
  528. function trap_wp_die( $error_code = null ) { 
  529. // Stop trapping 
  530. if ( is_null( $error_code ) ) { 
  531. $this->trapped_error = null; 
  532. remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) ); 
  533. return; 
  534.  
  535. // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die(). 
  536. if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { 
  537. if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) { 
  538. return; 
  539. } else { 
  540. if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) { 
  541. return; 
  542.  
  543. // Start trapping 
  544. $this->trapped_error = array( 
  545. 'status' => 500,  
  546. 'code' => $error_code,  
  547. 'message' => '',  
  548. ); 
  549.  
  550. add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) ); 
  551.  
  552. function wp_die_handler_callback() { 
  553. return array( $this, 'wp_die_handler' ); 
  554.  
  555. function wp_die_handler( $message, $title = '', $args = array() ) { 
  556. $args = wp_parse_args( $args, array( 
  557. 'response' => 500,  
  558. ) ); 
  559.  
  560. if ( $title ) { 
  561. $message = "$title: $message"; 
  562.  
  563. switch ( $this->trapped_error['code'] ) { 
  564. case 'comment_failure' : 
  565. if ( did_action( 'comment_duplicate_trigger' ) ) { 
  566. $this->trapped_error['code'] = 'comment_duplicate'; 
  567. } else if ( did_action( 'comment_flood_trigger' ) ) { 
  568. $this->trapped_error['code'] = 'comment_flood'; 
  569. break; 
  570.  
  571. $this->trapped_error['status'] = $args['response']; 
  572. $this->trapped_error['message'] = wp_kses( $message, array() ); 
  573.  
  574. // We still want to exit so that code execution stops where it should. 
  575. // Attach the JSON output to WordPress' shutdown handler 
  576. add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 ); 
  577. exit; 
  578.  
  579. function output_trapped_error() { 
  580. $this->exit = false; // We're already exiting once. Don't do it twice. 
  581. $this->output( $this->trapped_error['status'], (object) array( 
  582. 'error' => $this->trapped_error['code'],  
  583. 'message' => $this->trapped_error['message'],  
  584. ) ); 
  585.  
  586. function finish_request() { 
  587. if ( function_exists( 'fastcgi_finish_request' ) ) 
  588. return fastcgi_finish_request();