2021-09-07 21:40:05 +02:00
< ? php
/**
* The REST API - specific functionality of the plugin .
*
* @ link https :// disqus . com
* @ since 3.0
*
* @ package Disqus
* @ subpackage Disqus / rest - api
*/
/**
* Defines the REST API endpoints for the plugin
*
* @ package Disqus
* @ subpackage Disqus / rest - api
* @ author Ryan Valentin < ryan @ disqus . com >
*/
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 / 27 aa0664df927f610e42f9a694eb452cee49e245 / 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 ) {
2024-02-16 11:03:01 +01:00
return $this -> rest_get_error ( 'There was an error fetching sync status. Please go to Disqus.com and check that you are logged into a Disqus account with moderator privileges on this site, and then reload this page. For further troubleshooting, here is a 150 character error message preview: ' . substr ( $e , 0 , 150 ) );
2021-09-07 21:40:05 +02:00
}
}
/**
* 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 ) {
2024-02-16 11:03:01 +01:00
return $this -> rest_get_error ( 'There was an error attempting to enable automatic syncing. Please go to Disqus.com and check that you are logged into a Disqus account with moderator privileges on this site, and then reload this page. For further troubleshooting, here is a 150 character error message preview: ' . substr ( $e , 0 , 150 ) );
2021-09-07 21:40:05 +02:00
}
}
/**
* 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 ) {
2024-02-16 11:03:01 +01:00
return $this -> rest_get_error ( 'There was an error attempting to disable automatic syncing. Please go to Disqus.com and check that you are logged into a Disqus account with moderator privileges on this site, and then reload this page. For further troubleshooting, here is a 150 character error message preview: ' . substr ( $e , 0 , 150 ) );
2021-09-07 21:40:05 +02:00
}
}
/**
* 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.
2023-04-26 17:39:43 +02:00
$filtered_comments = array_filter ( $comments , array ( $this , 'is_comment_exportable' ) );
2021-09-07 21:40:05 +02:00
$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 ,
),
),
);
}
/**
2023-04-26 17:39:43 +02:00
* Checks a comment state to determine if it ' s valid for exporting into Disqus .
2021-09-07 21:40:05 +02:00
*
* @ since 3.0 . 11
* @ param array $comment The WordPress comment instance .
2023-04-26 17:39:43 +02:00
* @ return boolean Whether the comment is valid for exporting into Disqus .
2021-09-07 21:40:05 +02:00
*/
2023-04-26 17:39:43 +02:00
private function is_comment_exportable ( $comment ) {
$is_comment = ( empty ( $comment -> comment_type ) || 'comment' === $comment -> comment_type );
$is_disqus_comment = strpos ( $comment -> comment_agent , 'Disqus' ) !== false ;
return $is_comment && ! $is_disqus_comment ;
2021-09-07 21:40:05 +02:00
}
/**
* 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 ();
2023-04-26 17:39:43 +02:00
if ( $content_type && $content_type [ 'value' ] ) {
$content_type_val = $content_type [ 'value' ];
} else {
$content_type_val = null ;
}
2021-09-07 21:40:05 +02:00
2023-04-26 17:39:43 +02:00
switch ( $content_type_val ) {
2021-09-07 21:40:05 +02:00
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_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' ] );
}
// Find the parent comment, if any.
2024-02-16 11:03:01 +01:00
// To simplify our syncing process and prevent syncing errors,
// still sync the comment even if we don't have its parent comment synced.
2021-09-07 21:40:05 +02:00
$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 ;
2024-02-16 11:03:01 +01:00
if ( $parent_comments ) {
2021-09-07 21:40:05 +02:00
$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 ;
}
}