*/ class Disqus_Rest_Api { const REST_NAMESPACE = 'disqus/v1'; const DISQUS_API_BASE = 'https://disqus.com/api/3.0/'; /** * Instance of the Disqus API service. * * @since 3.0 * @access private * @var Disqus_Api_Service $api_service Instance of the Disqus API service. */ private $api_service; /** * The current version of the plugin. * * @since 3.0 * @access protected * @var string $version The current version of the plugin. */ protected $version; /** * Initialize the class and set its properties. * * @since 3.0 * @param Disqus_Api_Service $api_service Instance of the Disqus API service. * @param string $version The version of this plugin. */ public function __construct( $api_service, $version ) { $this->api_service = $api_service; $this->version = $version; } /** * When added as a filter, allows customizing the response. We use this to stop WordPress from * JSON-encoding the webhook verification response. * See: https://github.com/WordPress/WordPress/blob/27aa0664df927f610e42f9a694eb452cee49e245/wp-includes/rest-api/class-wp-rest-server.php#L376 * * @since 3.0 * @param boolean $served Whether this filter has already been applied. * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. * @param WP_REST_Request $request Request used to generate the response. * @param WP_REST_Server $server Server instance. * @return boolean Whether we've handled the response or not. */ public function filter_rest_pre_serve_request( $served, $result, $request, $server ) { if ( '/' . Disqus_Rest_Api::REST_NAMESPACE . '/sync/webhook' === $request->get_route() ) { // The sync/webhook endpoint should never return anything except the challenge in // plain text when prompted. header( 'Content-Type: text/plain; charset=UTF-8' ); $json_data = $request->get_json_params(); echo 'verify' === $json_data['verb'] ? $json_data['challenge'] : ''; $served = true; } return $served; } /** * Callback to ensure user has manage_options permissions. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return boolean|WP_Error Whether the user has permission to the admin REST API. */ public function rest_admin_only_permission_callback( WP_REST_Request $request ) { // Regular cookie-based authentication. if ( current_user_can( 'manage_options' ) ) { return true; } // Shared secret authentication. $hub_signature = $request->get_header( 'X-Hub-Signature' ); $sync_token = get_option( 'disqus_sync_token' ); $body = $request->get_body(); if ( $hub_signature && $sync_token && $body && ( 'sha512=' . hash_hmac( 'sha512', $body, $sync_token ) ) === $hub_signature ) { return true; } return $this->rest_get_error( 'You must be logged in and have admin permissions for this resource.', 401 ); } /** * Registers Disqus plugin WordPress REST API endpoints. * * @since 3.0 */ public function register_endpoints() { register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'sync/webhook', array( 'methods' => 'POST', 'callback' => array( $this, 'rest_sync_webhook' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ) ); // Alias route for `sync/webhook` to get around plugins/services disabling "abused" routes. register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'sync/comment', array( 'methods' => 'POST', 'callback' => array( $this, 'rest_sync_webhook' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ) ); register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'settings', array( array( 'methods' => array( 'GET', 'POST' ), 'callback' => array( $this, 'rest_settings' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ), 'schema' => array( $this, 'dsq_get_settings_schema' ), ) ); register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'sync/status', array( array( 'methods' => 'GET', 'callback' => array( $this, 'rest_sync_status' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ), ) ); register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'sync/enable', array( array( 'methods' => 'POST', 'callback' => array( $this, 'rest_sync_enable' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ), ) ); register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'sync/disable', array( array( 'methods' => 'POST', 'callback' => array( $this, 'rest_sync_disable' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ), ) ); register_rest_route( Disqus_Rest_Api::REST_NAMESPACE, 'export/post', array( array( 'methods' => 'POST', 'callback' => array( $this, 'rest_export_post' ), 'permission_callback' => array( $this, 'rest_admin_only_permission_callback' ), ), ) ); } /** * Utility function to format REST API responses. * * @since 3.0 * @access private * @param array $data The request data to be returned. * @return WP_REST_Response The API response object. */ private function rest_get_response( array $data ) { return new WP_REST_Response( array( 'code' => 'OK', 'message' => 'Request completed successfully', 'data' => $data, ), 200 ); } /** * Utility function to format REST API errors, and to optionally log them. * * @since 3.0 * @access private * @param string $message The error message to be returned. * @param int $status_code The http status code of the error. * @return WP_Error The API error object. */ private function rest_get_error( $message, $status_code = 500 ) { return new WP_Error( $status_code, $message, array( 'status' => $status_code, ) ); } /** * Endpoint callback for sync/webhook. Takes a Disqus comment and saves * it to the local comments database. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return WP_REST_Response|WP_Error The API response object. */ public function rest_sync_webhook( WP_REST_Request $request ) { $json_data = $request->get_json_params(); if ( ! isset( $json_data['verb'] ) ) { return new WP_Error( 400, 'Missing required property: verb.' ); } // Check for and respond to verification request before anything else. if ( 'verify' === $json_data['verb'] ) { // The X-Hub-Signature header was already validated, so we only need to return the challenge. $this->log_sync_message( 'Syncing established with Disqus servers' ); return new WP_REST_Response( $json_data['challenge'], 200 ); } // Don't attempt to sync anything that isn't a comment. if ( 'post' !== $json_data['object_type'] ) { return new WP_REST_Response( '', 204 ); } try { switch ( $json_data['verb'] ) { case 'force_sync': $comment_id = $this->create_or_update_comment_from_post( $json_data['transformed_data'] ); $this->log_sync_message( 'Manually synced comment "' . $json_data['transformed_data']['id'] . '" from Disqus' ); return new WP_REST_Response( (string) $comment_id, 200 ); case 'create': $new_comment_id = $this->create_or_update_comment_from_post( $json_data['transformed_data'] ); $this->log_sync_message( 'Synced new comment "' . $json_data['transformed_data']['id'] . '" from Disqus' ); return new WP_REST_Response( (string) $new_comment_id, 201 ); case 'update': $updated_comment_id = $this->create_or_update_comment_from_post( $json_data['transformed_data'] ); $this->log_sync_message( 'Updated synced comment "' . $json_data['transformed_data']['id'] . '" from Disqus' ); return new WP_REST_Response( (string) $updated_comment_id, 200 ); default: return new WP_REST_Response( '', 204 ); } } catch ( Exception $e ) { $this->log_sync_message( 'Error occurred during sync request from Disqus' ); return new WP_Error( 500, (string) $e ); } } /** * Endpoint callback for admin settings. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return WP_REST_Response|WP_Error The API response object. */ public function rest_settings( WP_REST_Request $request ) { $should_update = 'POST' === $request->get_method(); $new_settings = $should_update ? $this->get_request_data( $request ) : null; // Validate sync token if set. if ( $should_update && isset( $new_settings['disqus_sync_token'] ) ) { if ( strlen( $new_settings['disqus_sync_token'] ) < 32 ) { return $this->rest_get_error( 'The site secret key should be at least 32 characters.' ); } } $updated_settings = $this->get_or_update_settings( $new_settings ); return $this->rest_get_response( $updated_settings ); } /** * Endpoint callback for fetching automatic syncing status. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return WP_REST_Response|WP_Error The API response object. */ public function rest_sync_status( WP_REST_Request $request ) { try { $status = $this->get_sync_status(); return $this->rest_get_response( $status ); } catch ( Exception $e ) { return $this->rest_get_error( 'There was an error fetching sync status.' ); } } /** * Endpoint callback for enabling automatic syncing. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return WP_REST_Response|WP_Error The API response object. */ public function rest_sync_enable( WP_REST_Request $request ) { try { $status = $this->enable_sync(); return $this->rest_get_response( $status ); } catch ( Exception $e ) { return $this->rest_get_error( 'There was an error attempting to enable syncing.' ); } } /** * Endpoint callback for disabling automatic syncing. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return WP_REST_Response|WP_Error The API response object. */ public function rest_sync_disable( WP_REST_Request $request ) { try { $status = $this->disable_sync(); return $this->rest_get_response( $status ); } catch ( Exception $e ) { return $this->rest_get_error( 'There was an error attempting to disable syncing.' ); } } /** * Endpoint callback for exporting comments from a single WordPress post to Disqus. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return WP_REST_Response|WP_Error The API response object. */ public function rest_export_post( WP_REST_Request $request ) { $json_data = $request->get_json_params(); $postId = $json_data['postId']; if ( ! isset( $json_data['postId'] ) ) { return $this->rest_get_error( 'Missing required property: postId.', 400 ); } $post = get_post( $postId ); // First get comments that are approved, and sort them by smaller IDs first, so that the parent/child dependency // of replies are maintained. $comments = get_comments( array( 'post_id' => $postId, 'status' => 'approve', 'orderby' => 'comment_ID', 'order' => 'ASC', ) ); // Filter out pingbacks/trackings and comments that have been created by Disqus via syncing. $filtered_comments = array_filter( $comments, array( $this, 'is_pingback_or_disqus_comment' ) ); $response_data = array( 'comments' => $filtered_comments, ); if ( ! empty( $filtered_comments ) ) { // Generate a WXR (XML) file that Disqus will be able to read. $wxr = $this->generate_export_wxr( $post, $filtered_comments ); $filename = (string) $post->ID . '.wxr'; $response_data['wxr'] = array( 'filename' => $filename, 'xmlContent' => $wxr, ); } return $this->rest_get_response( $response_data ); } /** * Returns the schema for the Disqus admin settings REST endpoint. * * @since 3.0 * @return array The REST schema. */ public function dsq_get_settings_schema() { return array( // This tells the spec of JSON Schema we are using which is draft 4. '$schema' => 'http://json-schema.org/draft-04/schema#', // The title property marks the identity of the resource. 'title' => 'settings', 'type' => 'object', // In JSON Schema you can specify object properties in the properties attribute. 'properties' => array( 'disqus_forum_url' => array( 'description' => 'Your site\'s unique identifier', 'type' => 'string', 'readonly' => false, ), 'disqus_sso_enabled' => array( 'description' => 'This will enable Single Sign-on for this site, if already enabled for your Disqus organization.', 'type' => 'boolean', 'readonly' => false, ), 'disqus_public_key' => array( 'description' => 'The public key of your application.', 'type' => 'string', 'readonly' => false, ), 'disqus_secret_key' => array( 'description' => 'The secret key of your application.', 'type' => 'string', 'readonly' => false, ), 'disqus_admin_access_token' => array( 'description' => 'The primary admin\'s access token for your application.', 'type' => 'string', 'readonly' => false, ), 'disqus_sso_button' => array( 'description' => 'A link to a .png, .gif, or .jpg image to show as a button in Disqus.', 'type' => 'string', 'readonly' => false, ), 'disqus_sync_token' => array( 'description' => 'The shared secret token for data sync between Disqus and the plugin.', 'type' => 'string', 'readonly' => false, ), 'disqus_installed' => array( 'description' => 'The shared secret token for data sync between Disqus and the plugin.', 'type' => 'boolean', 'readonly' => true, ), 'disqus_render_js' => array( 'description' => 'When true, the Disqus embed javascript is output directly into markup rather than being enqueued in a separate file.', 'type' => 'boolean', 'readonly' => false, ), ), ); } /** * Checks a comment state to determine if it's valid for syncing. * * @since 3.0.11 * @param array $comment The WordPress comment instance. * @return boolean Whether the comment is valid for syncing. */ private function is_pingback_or_disqus_comment( $comment ) { return empty( $comment->comment_type ) && strpos( $comment->comment_agent, 'Disqus' ) === false; } /** * Parses and returns body content for either form-url-encoded or json data. * * @since 3.0 * @param WP_REST_Request $request The request object. * @return array Array of parsed request data. */ private function get_request_data( WP_REST_Request $request ) { $content_type = $request->get_content_type(); switch ( $content_type['value'] ) { case 'application/json': return $request->get_json_params(); default: return $request->get_body_params(); } } /** * Fetches all available plugin options and updates any values if passed, and returns updated array. * * @since 3.0 * @param array $new_settings Any options to be updated. * @access private * @return array The current settings array. */ private function get_or_update_settings( $new_settings = null ) { $settings = array(); $schema = $this->dsq_get_settings_schema(); $should_update = is_array( $new_settings ); // Loops through properties in our schema to check the value and update if needed. foreach ( $schema['properties'] as $key => $schema_value ) { $should_update_param = $should_update && isset( $new_settings[ $key ] ) && false === $schema_value['readonly']; if ( $should_update_param ) { update_option( $key, $new_settings[ $key ] ); } $settings[ $key ] = get_option( $key, null ); // Escape only values that have been set, otherwise esc_attr() will change null to an empty string. if ( null !== $settings[ $key ] ) { $settings[ $key ] = esc_attr( $settings[ $key ] ); } } // Add additional non-database options here. $settings['disqus_installed'] = trim( $settings['disqus_forum_url'] ) !== ''; return $settings; } /** * Determines if subscription information matches information about this WordPress site. * * @since 3.0 * @param object $subscription The Disqus webhook subscription array. * @return boolean Whether the subscription information belongs to this WordPress site. */ private function validate_subscription( $subscription ) { return rest_url( Disqus_Rest_Api::REST_NAMESPACE . '/sync/webhook' ) === $subscription->url; } /** * Fetches and returns the syncing status from the Disqus servers. * * @since 3.0 * @return array The syncing status array. * @throws Exception An exception if the Disqus API doesn't return with code 0 (status: 200). */ private function get_sync_status() { $is_subscribed = false; $is_enabled = false; $current_subscription = null; $requires_update = false; if ( get_option( 'disqus_secret_key' ) && get_option( 'disqus_admin_access_token' ) ) { $api_data = $this->api_service->api_get( 'forums/webhooks/list', array( 'forum' => get_option( 'disqus_forum_url' ), )); if ( 0 === $api_data->code ) { // Loop through each subscription, looking for the first match. foreach ( $api_data->response as $subscription ) { if ( $this->validate_subscription( $subscription ) ) { $current_subscription = $subscription; $is_enabled = $current_subscription->enableSending; $is_subscribed = true; $requires_update = get_option( 'disqus_sync_token' ) !== $current_subscription->secret; break; } } } else { throw new Exception( $api_data->response ); } } return array( 'subscribed' => $is_subscribed, 'enabled' => $is_enabled, 'requires_update' => $requires_update, 'subscription' => $current_subscription, 'last_message' => get_option( 'disqus_last_sync_message', '' ), ); } /** * Enables automatic syncing from Disqus if disabled. * * @since 3.0 * @return array The syncing status array. * @throws Exception An exception if the Disqus API doesn't return with code 0 (status: 200). */ private function enable_sync() { $sync_status = $this->get_sync_status(); $subscription = $sync_status['subscription']; $endpoint = null; $params = array( 'url' => rest_url( Disqus_Rest_Api::REST_NAMESPACE . '/sync/webhook' ), 'secret' => get_option( 'disqus_sync_token' ), 'enableSending' => '1', ); if ( ! $sync_status['subscribed'] ) { $endpoint = 'forums/webhooks/create'; $params['forum'] = get_option( 'disqus_forum_url' ); } elseif ( ! $sync_status['enabled'] || $sync_status['requires_update'] ) { $endpoint = 'forums/webhooks/update'; $params['subscription'] = $subscription->id; } if ( null !== $endpoint ) { $api_data = $this->api_service->api_post( $endpoint, $params ); if ( 0 === $api_data->code ) { $sync_status = array( 'subscribed' => true, 'enabled' => true, 'requires_update' => false, 'subscription' => $api_data->response, 'last_message' => $sync_status['last_message'], ); } else { $this->log_sync_message( 'Error enabling syncing: ' . $api_data->response ); throw new Exception( $api_data->response ); } } return $sync_status; } /** * Disables automatic syncing from Disqus if enabled. * * @since 3.0 * @return array The syncing status array. * @throws Exception An exception if the Disqus API doesn't return with code 0 (status: 200). */ private function disable_sync() { $sync_status = $this->get_sync_status(); $subscription = $sync_status['subscription']; if ( $sync_status['enabled'] ) { $params = array( 'subscription' => $subscription->id, 'enableSending' => '0', ); $api_data = $this->api_service->api_post( 'forums/webhooks/update', $params ); if ( 0 === $api_data->code ) { $sync_status = array( 'subscribed' => true, 'enabled' => false, 'requires_update' => false, 'subscription' => $api_data->response, 'last_message' => $sync_status['last_message'], ); } else { throw new Exception( $api_data->response ); } } return $sync_status; } /** * Queries the WordPress database for existing comment by dsq_post_id. Creates or updates if comment found * in the WordPress database given a Disqus post. * * @since 3.0.17 * @param array $post The Disqus post object. * @return int The created or updated comment ID. * @throws Exception An exception if comment can't be saved from post data. */ private function create_or_update_comment_from_post( $post ) { $this->validate_disqus_post_data( $post ); // Check for existing comment. $comment_query = new WP_Comment_Query( array( 'meta_key' => 'dsq_post_id', 'meta_value' => $post['id'], 'number' => 1, ) ); $comments = $comment_query->comments; if ( ! empty( $comments ) ) { return $this->update_comment_from_post( $post, $comments ); } return $this->create_comment_from_post( $post ); } /** * Creates a comment in the WordPress database given a Disqus post. * * @since 3.0 * @param array $post The Disqus post object. * @return int The newly created comment ID. * @throws Exception An exception if comment can't be saved from post data. */ private function create_comment_from_post( $post ) { $comment_data = $this->comment_data_from_post( $post ); $new_comment_id = wp_insert_comment( $comment_data ); return $new_comment_id; } /** * Updates a comment in the WordPress database given a Disqus post. * * @since 3.0 * @param array $post The Disqus post object. * @param array $comments The comments found matching the dsq_post_id. * @return int The newly created comment ID. * @throws Exception An exception if comment can't be saved from post data. */ private function update_comment_from_post( $post, $comments ) { foreach ( $comments as $comment ) { $updated_comment_id = $comment->comment_ID; } $comment_data = $this->comment_data_from_post( $post ); $comment_data['comment_ID'] = $updated_comment_id; // Remove non-updating fields. unset( $comment_data['comment_meta'] ); unset( $comment_data['comment_agent'] ); unset( $comment_data['comment_parent'] ); unset( $comment_data['comment_type'] ); unset( $comment_data['comment_date_gmt'] ); unset( $comment_data['comment_post_ID'] ); $updated = wp_update_comment( $comment_data ); return 1 === $updated ? $updated_comment_id : 0; } /** * Creates a comment in the WordPress database given a Disqus post. * * @since 3.0 * @param array $post The Disqus post object. * @throws Exception An exception if comment is invalid. */ private function validate_disqus_post_data( $post ) { if ( ! $post || get_option( 'disqus_forum_url' ) !== $post['forum'] ) { throw new Exception( 'The comment\'s forum does not match the installed forum. Was "' . $post['forum'] . '", expected "' . get_option( 'disqus_forum_url' ) . '"' ); } } /** * Checks the state of a Disqus post (comment) and translates to the WordPress comment_approved format. * * @since 3.0 * @param array $post The Disqus post object. * @return array The translated comment data to be inserted/updated. * @throws Exception An exception if comment can't be saved from post data. */ private function comment_data_from_post( $post ) { $thread = array_key_exists( 'threadData', $post ) ? $post['threadData'] : $post['thread']; $author = $post['author']; $wp_post_id = null; // Look up posts with the Disqus thread ID meta field. $post_query = new WP_Query( array( 'meta_key' => 'dsq_thread_id', 'meta_value' => $thread['id'], 'ignore_sticky_posts' => true, ) ); if ( $post_query->have_posts() ) { $wp_post_id = $post_query->post->ID; wp_reset_postdata(); } // If that doesn't exist, get the and update the matching post metadata. if ( null === $wp_post_id || false === $wp_post_id ) { $identifiers = $thread['identifiers']; $first_identifier = count( $identifiers ) > 0 ? $identifiers[0] : null; if ( null !== $first_identifier ) { $ident_parts = explode( ' ', $first_identifier, 2 ); $wp_post_id = reset( $ident_parts ); } // Keep the post's thread ID meta up to date. update_post_meta( $wp_post_id, 'dsq_thread_id', $thread['id'] ); } if ( null === $wp_post_id || false == $wp_post_id ) { throw new Exception( 'No post found associated with the thread.' ); } // Find the parent comment, if any. $parent = 0; if ( null !== $post['parent'] ) { $parent_comment_query = new WP_Comment_Query( array( 'meta_key' => 'dsq_post_id', 'meta_value' => (string) $post['parent'], 'number' => 1, ) ); $parent_comments = $parent_comment_query->comments; if ( empty( $parent_comments ) ) { throw new Exception( 'This comment\'s parent has not been synced yet.' ); } else { $parent = $parent_comments[0]->comment_ID; } } // Email is a special permission for Disqus API applications and won't be present // if the user has not set the permission for their API application. $author_email = null; if ( isset( $author['email'] ) ) { $author_email = $author['email']; } elseif ( $author['isAnonymous'] ) { $author_email = 'anonymized-' . md5( $author['name'] ) . '@disqus.com'; } else { $author_email = 'user-' . $author['id'] . '@disqus.com'; } // Translate the comment approval state. $comment_approved = 1; if ( $post['isApproved'] && ! $post['isDeleted'] ) { $comment_approved = 1; } elseif ( $post['isDeleted'] ) { $comment_approved = 0; // Deleted is not a state in WordPress, so we'll keep them in pending. } elseif ( ! $post['isDeleted'] && ! $post['isSpam'] && ! $post['isApproved'] ) { $comment_approved = 0; } elseif ( $post['isSpam'] && ! $post['isApproved'] ) { $comment_approved = 'spam'; } return array( 'comment_post_ID' => (int) $wp_post_id, 'comment_author' => $author['name'], 'comment_author_email' => $author_email, 'comment_author_IP' => $post['ipAddress'], 'comment_author_url' => isset( $author['url'] ) ? $author['url'] : '', 'comment_content' => $post['raw_message'], 'comment_date' => $post['createdAt'], 'comment_date_gmt' => $post['createdAt'], 'comment_type' => '', // Leave blank for a regular comment. 'comment_parent' => $parent, 'comment_agent' => 'Disqus Sync Host', 'comment_approved' => $comment_approved, 'comment_meta' => array( 'dsq_post_id' => $post['id'], ), ); } /** * Stores the last sync log message with date/time appended. * * @since 3.0 * @access private * @param string $message The base message to store. */ private function log_sync_message( $message ) { update_option( 'disqus_last_sync_message', $message . ': ' . date( 'Y-m-d g:i a', time() ) ); } /** * Outputs a list of comments to a WXR file for uploading to the Disqus importer. * * @since 3.0 * @access private * @param WP_Post $post The post details the comments belong to. * @param array $comments The base message to store. * @return string The WXR document as a string. */ private function generate_export_wxr( $post, $comments ) { $post_author = get_userdata( $post->post_author ); $xml = new DOMDocument( '1.0', get_bloginfo( 'charset' ) ); $rss = $xml->createElement( 'rss' ); $rss->setAttribute( 'version', '2.0' ); $rss->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:excerpt', 'http://wordpress.org/export/1.0/excerpt/' ); $rss->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:content', 'http://purl.org/rss/1.0/modules/content/' ); $rss->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:dsq', 'https://disqus.com/' ); $rss->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:wfw', 'http://wellformedweb.org/CommentAPI/' ); $rss->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/' ); $rss->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:wp', 'http://wordpress.org/export/1.0/' ); $channel = $xml->createElement( 'channel' ); $channel->appendChild( $xml->createElement( 'title', get_bloginfo_rss( 'name' ) ) ); $channel->appendChild( $xml->createElement( 'link', get_bloginfo_rss( 'url' ) ) ); $channel->appendChild( $xml->createElement( 'pubDate', mysql2date( 'D, d M Y H:i:s +0000', get_lastpostmodified( 'GMT' ), false ) ) ); $channel->appendChild( $xml->createElement( 'generator', 'WordPress ' . get_bloginfo_rss( 'version' ) . '; Disqus ' . $this->version ) ); // Generate the item (the post). $item = $xml->createElement( 'item' ); $item->appendChild( $xml->createElement( 'title', apply_filters( 'the_title_rss', $post->post_title ) ) ); $item->appendChild( $xml->createElement( 'link', esc_url( apply_filters( 'the_permalink_rss', get_permalink( $post->ID ) ) ) ) ); $item->appendChild( $xml->createElement( 'pubDate', mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true, $post ), false ) ) ); $author_name_cdata = $xml->createCDATASection( $post_author->display_name ); $author_name_element = $xml->createElement( 'dc:creator' ); $author_name_element->appendChild( $author_name_cdata ); $item->appendChild( $author_name_element ); $guid = $xml->createElement( 'guid', $post->guid ); $guid->setAttribute( 'isPermalink', 'false' ); $item->appendChild( $guid ); $post_content_cdata = $xml->createCDATASection( apply_filters( 'the_content_export', $post->post_content ) ); $post_content_element = $xml->createElement( 'content:encoded' ); $post_content_element->appendChild( $post_content_cdata ); $item->appendChild( $post_content_element ); $identifier_cdata = $xml->createCDATASection( $post->ID . ' ' . $post->guid ); $identifier_element = $xml->createElement( 'dsq:thread_identifier' ); $identifier_element->appendChild( $identifier_cdata ); $item->appendChild( $identifier_element ); $item->appendChild( $xml->createElement( 'wp:post_id', $post->ID ) ); $item->appendChild( $xml->createElement( 'wp:post_date_gmt', $post->post_date_gmt ) ); $item->appendChild( $xml->createElement( 'wp:comment_status', $post->comment_status ) ); foreach ( $comments as $c ) { $wpcomment = $xml->createElement( 'wp:comment' ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_id', $c->comment_ID ) ); $comment_author_name_cdata = $xml->createCDATASection( $c->comment_author ); $comment_author_name_element = $xml->createElement( 'wp:comment_author' ); $comment_author_name_element->appendChild( $comment_author_name_cdata ); $wpcomment->appendChild( $comment_author_name_element ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_author_email', $c->comment_author_email ) ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_author_url', $c->comment_author_url ) ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_author_IP', $c->comment_author_IP ) ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_date', $c->comment_date ) ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_date_gmt', $c->comment_date_gmt ) ); $comment_content_cdata = $xml->createCDATASection( $c->comment_content ); $comment_content_element = $xml->createElement( 'wp:comment_content' ); $comment_content_element->appendChild( $comment_content_cdata ); $wpcomment->appendChild( $comment_content_element ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_approved', $c->comment_approved ) ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_type', $c->comment_type ) ); $wpcomment->appendChild( $xml->createElement( 'wp:comment_parent', $c->comment_parent ) ); $item->appendChild( $wpcomment ); } // Append the post item to the channel. $channel->appendChild( $item ); // Append the root channel to the RSS element. $rss->appendChild( $channel ); // Finally append the root RSS element to the XML document. $xml->appendChild( $rss ); $wxr = $xml->saveXML(); return $wxr; } }