diff --git a/assets/js/press-sync.js b/assets/js/press-sync.js
index 080e435..896bcbd 100644
--- a/assets/js/press-sync.js
+++ b/assets/js/press-sync.js
@@ -1,17 +1,83 @@
window.PressSync = ( function( window, document, $ ) {
- var app = {};
-
- app.PAGE_SIZE = 1;
+ var app = {
+ PAGE_SIZE: 1,
+ LOG_LIMIT: 20, // Limit how many log entries to show, previous logs will be discarded.
+ log_index: 0, // Count how many logs have processed.
+ times: [], // Array of times used to calculate time remaining average more accurately.
+ elCache: {},
+ logFileURL: null
+ };
+ /**
+ * Initialize the JS app for Press Sync.
+ *
+ * Handles registering elements to the element Cache and binding inital listeners.
+ *
+ * @since 0.1.0
+ */
app.init = function() {
- $(document).on( 'click', '.press-sync-button', app.pressSyncButton );
+ app.elCache.syncButton = $('#press-sync-button');
+ app.elCache.cancelButton = $('#press-sync-cancel-button');
+ app.elCache.status = $('.progress-stats');
+ app.elCache.bulkSettings = $('#press-sync-bulk-settings');
+ app.elCache.logView = $('#press-sync-log-view' );
+ app.elCache.logs = $('#press-sync-logs');
+ app.elCache.downloadLog = $('#press-sync-download-log');
+
+ app.elCache.syncButton.on( 'click', app.pressSyncButton );
+ app.elCache.cancelButton.on( 'click', app.cancelSync );
+ app.elCache.downloadLog.on( 'click', app.downloadLog );
};
+ /**
+ * Handles the Press Sync "Sync" button.
+ *
+ * @since NEXT
+ * @param Object click_event The click event from the listener.
+ */
app.pressSyncButton = function( click_event ) {
+ app.running = true;
+
+ // @TODO probably should have this in a method similar to app.cleanup.
+ app.elCache.syncButton.hide();
+ app.elCache.cancelButton.show();
+ app.elCache.bulkSettings.hide();
+ app.elCache.logView.show();
+ app.elCache.logs.val('');
app.loadProgressBar();
return;
- }
+ };
+
+ /**
+ * Set the app.running flag to false to stop the current sync process.
+ *
+ * @since NEXT
+ */
+ app.cancelSync = function() {
+ app.running = false;
+ };
+
+ /**
+ * Update the timing array and smooth out timing samples.
+ *
+ * @since NEXT
+ * @param Number remaining_time The most recent remaining time estimate.
+ */
+ app.updateTiming = function( remaining_time ) {
+ app.times.push( remaining_time );
+
+ // Limit sample size.
+ if ( 100 < app.times.length ) {
+ app.times.shift();
+ }
+
+ app.times.map( function ( e ) {
+ return e += e;
+ } );
+
+ app.times = smooth( app.times, 0.85 );
+ };
app.loadProgressBar = function() {
$('.press-sync-button').hide();
@@ -72,24 +138,25 @@ window.PressSync = ( function( window, document, $ ) {
if ( request_time ) {
// Estimate time remaining.
- var remaining_time = ( ( ( total_objects - total_objects_processed ) / app.PAGE_SIZE ) * request_time );
- remaining_time = remaining_time / 60 / 60;
var time_left_suffix = 'hours';
+ var remaining_time = ( ( ( total_objects - total_objects_processed ) / app.PAGE_SIZE ) * request_time );
+ app.updateTiming( remaining_time );
+ remaining_time = remaining_time / app.times.length;
+ remaining_time = remaining_time / 60 / 60;
// Shift to minutes.
if ( 1 > remaining_time ) {
- remaining_time = remaining_time * 60;
+ remaining_time = remaining_time * 60;
time_left_suffix = 'minutes';
}
// Round to two decimal places, mostly.
- remaining_time = Math.round( remaining_time * 100 ) / 100;
-
- progress_string += ' (' + [ 'Estimated time remaining:', remaining_time, time_left_suffix ].join(' ') + ')';
+ remaining_time = Math.round( remaining_time * 100 ) / 100;
+ progress_string += ' (' + [ 'Estimated time remaining:', remaining_time, time_left_suffix ].join(' ') + ' )';
}
$('.progress-stats').text( progress_string );
- }
+ };
/**
* Syncs data to the remote site.
@@ -117,29 +184,140 @@ window.PressSync = ( function( window, document, $ ) {
objects_to_sync: objects_to_sync
}
}).done(function( response ) {
- // Convert request time from milliseconds to seconds.
- var request_time = ( new Date().getTime() - start_time ) / 1000;
+ if ( ! app.running ) {
+ app.cleanup( 'Sync canceled by user.' );
+ return;
+ }
- app.updateProgressBar( response.data.objects_to_sync, response.data.total_objects_processed, response.data.total_objects, request_time );
+ app.updateProgressBar(
+ response.data.ps_objects_to_sync,
+ response.data.total_objects_processed,
+ response.data.total_objects,
+ ( new Date().getTime() - start_time ) / 1000 // Convert request time from milliseconds to seconds.
+ );
+ app.Log( response );
+
+ // Bail if we're done.
if ( response.data.total_objects_processed >= response.data.total_objects ) {
// Start the next batch at page 1.
if ( next_args && next_args.order_to_sync_all && next_args.order_to_sync_all.length ) {
return app.syncData( 1, null, next_args );
}
- $('.press-sync-button').show();
- $('.progress-stats').text('Sync completed!');
+ app.cleanup( 'Sync completed!' );
return;
}
- app.syncData( response.data.next_page, response.data.objects_to_sync, next_args );
+ app.syncData( response.data.next_page, response.data.ps_objects_to_sync, next_args );
});
-
}
+ /**
+ * Logs messages from the remote server to the log window.
+ *
+ * @since NEXT
+ * @param Object response The response from the AJAX request.
+ */
+ app.Log = function( response ) {
+ if ( ! response.data ) {
+ return;
+ }
+
+ var loglines = [];
+
+ try {
+ var logs = response.data.log;
+ for ( var i = 0; i < logs.length; i++) {
+ loglines.push( logs[i] );
+ }
+
+ loglines.push("\n---BATCH END ---\n");
+ app.elCache.logs.val( app.elCache.logs.val() + loglines.join("\n") );
+ } catch ( e ) {
+ console.warn( "Could not log data, response: " + e );
+ console.warn( response.data );
+ }
+ };
+
+ /**
+ * Cleanup the view so it's back to a state similar to when we first
+ * visit the page.
+ *
+ * @since NEXT
+ * @param string message The message to display under the progress bar.
+ */
+ app.cleanup = function( message ){
+ app.elCache.syncButton.show();
+ app.elCache.cancelButton.hide();
+ app.elCache.bulkSettings.show();
+ app.elCache.status.text( message );
+ createLogFile();
+ app.elCache.downloadLog.show();
+ };
+
+ /**
+ * Click handler to download the log file.
+ *
+ * @since NEXT
+ */
+ app.downloadLog = function() {
+ app.elCache.downloadLog.attr( 'href', app.logFileURL );
+ };
+
$( document ).ready( app.init );
return app;
+ // Private methods.
+
+ /**
+ * Smooth a set of values.
+ *
+ * We use this to smooth out the timings in the array of request times to give a more accurate
+ * time estimation.
+ *
+ * Source: https://stackoverflow.com/q/32788836/1169389
+ */
+ function smooth(values, alpha) {
+ var weighted = average(values) * alpha;
+ var smoothed = [];
+ for (var i in values) {
+ var curr = values[i];
+ var prev = smoothed[i - 1] || values[values.length - 1];
+ var next = curr || values[0];
+ var improved = Number(average([weighted, prev, curr, next]).toFixed(2));
+ smoothed.push(improved);
+ }
+ return smoothed;
+ }
+
+ /**
+ * Gets the averate of a set of data.
+ *
+ * Source: https://stackoverflow.com/q/32788836/1169389
+ */
+ function average(data) {
+ var sum = data.reduce(function(sum, value) {
+ return sum + value;
+ }, 0);
+ var avg = sum / data.length;
+ return avg;
+ }
+
+ /**
+ * Creates a downloadable file using the Javascript Blob object.
+ *
+ * Source: https://stackoverflow.com/a/21016088/1169389
+ */
+ function createLogFile() {
+ var text = app.elCache.logs.val();
+ var data = new Blob([text], {type: 'text/plain'});
+
+ if ( null !== app.logFileURL ) {
+ window.URL.revokeObjectURL( app.logFileURL );
+ }
+
+ app.logFileURL = window.URL.createObjectURL(data);
+ }
} )( window, document, jQuery );
diff --git a/includes/class-api.php b/includes/class-api.php
index a246325..d8c914f 100644
--- a/includes/class-api.php
+++ b/includes/class-api.php
@@ -31,6 +31,14 @@ class API extends \WP_REST_Controller {
*/
private $fix_terms = false;
+ /**
+ * Array of log messages to return with API responses.
+ *
+ * @var array
+ * @since NEXT
+ */
+ private $logs = array();
+
/**
* Constructor.
*
@@ -158,7 +166,9 @@ public function validate_sync_key() {
$press_sync_key = get_option( 'ps_key' );
if ( ! $press_sync_key || ( $press_sync_key_from_remote !== $press_sync_key ) ) {
- return false;
+ wp_send_json_error( array(
+ 'message' => __( 'Invalid Press Sync key.', 'press-sync' ),
+ ) );
}
return true;
@@ -216,18 +226,24 @@ public function sync_objects( $request ) {
remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
$responses = array();
+ $this->log( date( DATE_RFC2822, time() ) . '(' . time() . ')' );
$objects_to_sync = in_array( $objects_to_sync, array( 'attachment', 'comment', 'user', 'option', 'taxonomy_term' ), true ) ? $objects_to_sync : 'post';
foreach ( $objects as $object ) {
$sync_method = "sync_{$objects_to_sync}";
+ $this->log( '=====================================' );
+ $this->log( sprintf( __( 'Syncing next object with method "%s".', 'press-sync' ), $sync_method ) );
+ $this->log( '-------------------------------------' );
$responses[] = $this->$sync_method( $object, $duplicate_action, $force_update );
}
add_filter( 'content_save_pre', 'wp_filter_post_kses' );
- return $responses;
+ // Remove empty lines and reset array indexes.
+ $log = array_values( array_filter( $this->get_log() ) );
+ return compact( 'responses', 'log' );
}
/**
@@ -260,67 +276,68 @@ public function sync_post( $post_args, $duplicate_action, $force_update = false
if ( $this->fix_terms ) {
if ( ! $local_post ) {
- return array(
- 'debug' => array(
- 'message' => __( 'Could not find a local post to attach the terms to.', 'press-sync' ),
- ),
- );
+ return $this->error_log( __( 'Could not find a local post to attach the terms to.', 'press-sync' ) );
}
+
return $this->fix_term_relationships( $local_post['ID'], $post_args );
}
$post_args['ID'] = isset( $local_post['ID'] ) ? $local_post['ID'] : 0;
+ $this->log( sprintf( __( 'Final post_args["ID"] = %d', 'press-sync' ), $post_args['ID'] ) );
// Replace embedded media.
if ( isset( $post_args['embedded_media'] ) ) {
-
foreach ( $post_args['embedded_media'] as $attachment_args ) {
+ $this->log( __( 'Attempting addition of embedded media.', 'press-sync' ) );
- $attachment_id = $this->sync_attachment( $attachment_args );
-
- if ( abinst( $attachment_id ) ) {
-
- $sync_source = $post_args['meta_input']['press_sync_source'];
- $attachment_url = str_ireplace( $sync_source, home_url(), $attachment_args['attachment_url'] );
-
+ if ( abinst( $attachment_id = $this->sync_attachment( $attachment_args ) ) ) {
+ $sync_source = $post_args['meta_input']['press_sync_source'];
+ $attachment_url = str_ireplace( $sync_source, home_url(), $attachment_args['attachment_url'] );
$post_args['post_content'] = str_ireplace( $attachment_args['attachment_url'], $attachment_url, $post_args['post_content'] );
}
}
}
+ $this->log( sprintf( __( 'Incoming author: %s', 'press-sync' ), $post_args['post_author'] ) );
+
// Set the correct post author.
$post_args['post_author'] = $this->get_press_sync_author_id( $post_args['post_author'] );
+ $this->log( sprintf( __( 'Matched author: %s', 'press-sync' ), $post_args['post_author'] ) );
+
// Check for post parent and update IDs accordingly.
if ( ! $this->preserve_ids && isset( $post_args['post_parent'] ) && $post_parent_id = $post_args['post_parent'] ) {
- $post_parent_args['post_type'] = $post_args['post_type'];
- $post_parent_args['meta_input']['press_sync_post_id'] = $post_parent_id;
+ $this->log( __( 'Attempting connection to parent post.', 'press-sync' ) );
+ $post_parent_args = array(
+ 'post_type' => $post_args['post_type'],
+ 'meta_input' => $post_parent_id,
+ );
$parent_post = $this->get_synced_post( $post_parent_args, false );
+ $found = $parent_post ? __( 'Found', 'press-sync' ) : __( 'Not found.', 'press-sync' );
+ $this->log( sprintf( __( 'Result of search for valid post_parent: %s', 'press-sync' ), $found ) );
$post_args['post_parent'] = ( $parent_post ) ? $parent_post['ID'] : 0;
}
// Keep the ID because we found a regular ol' duplicate.
if ( $this->preserve_ids && ! $local_post && ! empty( $post_args['ID'] ) ) {
+ $this->log( __( 'Preserving original post-type object ID.', 'press-sync' ) );
$post_args['import_id'] = $post_args['ID'];
unset( $post_args['ID'] );
}
// Determine which content is newer, local or remote.
if ( ! $force_update && $local_post && ( strtotime( $local_post['post_modified'] ) >= strtotime( $post_args['post_modified'] ) ) ) {
-
// If we're here, then we need to keep our local version.
$response['remote_post_id'] = $post_args['meta_input']['press_sync_post_id'];
$response['local_post_id'] = $local_post['ID'];
- $response['message'] = __( 'Local version is newer than remote version', 'press-sync' );
+ $this->log( __( 'Local version is newer than remote version.', 'press-sync' ) );
// Assign a press sync ID.
$this->add_press_sync_id( $local_post['ID'], $post_args );
-
- return array( 'debug' => $response );
-
+ return $response;
}
// Add categories.
@@ -329,6 +346,7 @@ public function sync_post( $post_args, $duplicate_action, $force_update = false
require_once( ABSPATH . '/wp-admin/includes/taxonomy.php' );
foreach ( $post_args['tax_input']['category'] as $category ) {
+ $this->log( sprintf( __( 'Creating new category "%s".', 'press-sync' ), $category['name'] ) );
wp_insert_category( array(
'cat_name' => $category['name'],
'category_description' => $category['description'],
@@ -342,31 +360,33 @@ public function sync_post( $post_args, $duplicate_action, $force_update = false
// Insert/update the post.
$local_post_id = wp_insert_post( $post_args, true );
+ $this->log( sprintf( __( 'Inserted new post with ID "%d", post type "%s", title "%s".', 'press-sync' ),
+ $local_post_id,
+ $post_args['post_type'],
+ $post_args['post_title']
+ ) );
+
// Bail if the insert didn't work.
if ( is_wp_error( $local_post_id ) ) {
- trigger_error( sprintf( 'Error inserting post: ', $local_post_id->get_error_message() ) );
- return array( 'debug' => $local_post_id );
+ return $this->error_log( sprintf( __( 'Error inserting post: %s', 'press-sync' ), $local_post_id->get_error_message() ) );
}
// Attach featured image.
- $featured_result = $this->attach_featured_image( $local_post_id, $post_args );
+ $this->attach_featured_image( $local_post_id, $post_args );
// Attach any comments.
$comments = isset( $post_args['comments'] ) ? $post_args['comments'] : array();
$this->attach_comments( $local_post_id, $comments );
-
$this->attach_terms( $local_post_id, $post_args );
// Run any secondary commands.
do_action( 'press_sync_sync_post', $local_post_id, $post_args );
+ $this->log( __( 'The post has been synced with the remote site', 'press-sync' ) );
return array(
- 'debug' => array(
- 'remote_post_id' => $post_args['meta_input']['press_sync_post_id'],
- 'local_post_id' => $local_post_id,
- 'message' => __( 'The post has been synced with the remote site', 'press-sync' ),
- 'featured_result' => $featured_result,
- ),
+ 'remote_post_id' => $post_args['meta_input']['press_sync_post_id'],
+ 'local_post_id' => $local_post_id,
+ 'featured_result' => $featured_result,
);
}
@@ -385,10 +405,11 @@ public function sync_attachment( $attachment_args, $duplicate_action = 'skip', $
$attachment_id = false;
$import_id = false;
$attachment_args = $this->clean_post_object_args( $attachment_args );
+ $error_message = null;
// Attachment URL does not exist so bail early.
if ( ! $this->skip_assets && ! array_key_exists( 'attachment_url', $attachment_args ) ) {
- return false;
+ return $this->error_log( __( 'Attachment data missing "attachment_url" parameter!', 'press-sync' ) );
}
if ( isset( $attachment_args['ID'] ) ) {
@@ -401,6 +422,7 @@ public function sync_attachment( $attachment_args, $duplicate_action = 'skip', $
// ID will only be set if we find the attachment is already here.
if ( ! isset( $attachment['ID'] ) ) {
+ $this->log( __( 'Inserting a new attachment.', 'press-sync' ) );
$filename = $attachment['filename'];
unset( $attachment['filename'] );
@@ -434,10 +456,10 @@ public function sync_attachment( $attachment_args, $duplicate_action = 'skip', $
if ( $attachment_id && $import_id ) {
update_post_meta( $attachment_id, 'press_sync_post_id', $import_id );
}
- }
- catch( \Exception $e ) {
- // @TODO log it more!
- error_log( $e->getMessage() );
+
+ $this->log( sprintf( __( 'Successfully uploaded a new attachment with ID "%d".', 'press-sync' ), $attachment_id ) );
+ } catch( \Exception $e ) {
+ $this->error_log( $e->getMessage() );
}
return $attachment_id;
@@ -455,27 +477,30 @@ public function sync_attachment( $attachment_args, $duplicate_action = 'skip', $
* @return array
*/
public function sync_user( $user_args, $duplicate_action, $force_update = false ) {
-
$username = isset( $user_args['user_login'] ) ? $user_args['user_login'] : '';
+ $this->log( sprintf( __( 'Attempting sync of user with login "%s"', 'press-sync' ), $username ) );
+
// Check to see if the user exists.
$user = get_user_by( 'login', $username );
if ( ! $user ) {
-
+ $this->log( __( 'Creating new user.', 'press-sync' ) );
$user_id = wp_insert_user( $user_args );
if ( is_wp_error( $user_id ) ) {
- return wp_send_json_error();
+ return $this->error_log( sprintf( __( 'There was an error creating this user: "%s".', 'press-sync' ), $user_id->get_error_message() ) );
}
$user = get_user_by( 'id', $user_id );
-
+ $this->log( sprintf( __( 'New user created with ID "%d".', 'press-sync' ), $user_id ) );
} else {
$user_id = $user->ID;
+ $this->log( sprintf( __( 'Found existing user with ID "%d".', 'press-sync' ), $user_id ) );
}
// Update the meta.
+ $this->debug_log( __( 'Attaching user metadata.', 'press-sync' ), $user_args['meta_input'] );
foreach ( $user_args['meta_input'] as $usermeta_key => $usermeta_value ) {
if ( 0 === strpos( $usermeta_key, 'press_sync_' ) ) {
$usermeta_key = $this->maybe_make_multisite_key( $usermeta_key );
@@ -486,13 +511,13 @@ public function sync_user( $user_args, $duplicate_action, $force_update = false
// Asign user role.
$user->add_role( $user_args['role'] );
+ $this->log( sprintf( __( 'Assigning user role of "%s".', 'press-sync' ), $user_args['role'] ) );
// Prepare response.
$response['user_id'] = $user_id;
$response['blog_id'] = get_current_blog_id();
return $response;
-
}
/**
@@ -510,11 +535,18 @@ public function sync_option( $option_args ) {
$option_value = isset( $option_args['option_value'] ) ? $option_args['option_value'] : '';
if ( empty( $option_value ) || empty( $option_name ) ) {
- return false;
+ return $this->error_log( __( 'Attempted syncing option, but missing arg of "option_name" or "option_value"!', 'press-sync' ) );
}
+ $this->log( sprintf( __( 'Syncing option "%s" with value "%s".', 'press-sync' ), $option_name, $option_value ) );
$response['option_id'] = update_option( $option_name, $option_value, $option_args['autoload'] );
+ $result = __( 'Updated', 'press-sync' );
+ if ( false === $response['option_id'] ) {
+ $result = __( 'Not updated.', 'press-sync' );
+ }
+
+ $this->log( sprintf( __( 'Update result: "%s".', 'press-sync' ), $result ) );
return $response;
}
@@ -549,9 +581,13 @@ public function get_synced_post( $post_args, $respect_post_type = true ) {
}
$sql .= ' LIMIT 1';
-
$prepared_sql = $wpdb->prepare( $sql, $prepare_args );
- $post = $wpdb->get_row( $prepared_sql, ARRAY_A );
+
+ $this->debug_log( __( 'Attempting to find a synced post with SQL: ', 'press-sync' ), $prepared_sql );
+
+ $post = $wpdb->get_row( $prepared_sql, ARRAY_A );
+
+ $this->log( sprintf( __( 'Synced post search result: %s.', 'press-sync' ), ( $post ? 'Found' : 'Not found' ) ) );
return ( $post ) ? $post : false;
@@ -594,7 +630,6 @@ public function media_exists( $media_url ) {
* @return boolean
*/
public function comment_exists( $comment_args = array() ) {
-
$press_sync_comment_id = isset( $comment_args['meta_input']['press_sync_comment_id'] ) ? $comment_args['meta_input']['press_sync_comment_id'] : 0;
$press_sync_source = isset( $comment_args['meta_input']['press_sync_source'] ) ? $comment_args['meta_input']['press_sync_source'] : 0;
@@ -617,6 +652,7 @@ public function comment_exists( $comment_args = array() ) {
$comment = get_comments( $query_args );
if ( $comment ) {
+ $this->log( __( 'Comment already exists, skipping.', 'press-sync' ) );
return (array) $comment[0];
}
@@ -669,12 +705,13 @@ public function get_press_sync_author_id( $user_id ) {
global $wpdb;
- $usermeta_key = $this->maybe_make_multisite_key( 'press_sync_user_id' );
- $sql = "SELECT user_id AS ID FROM {$wpdb->usermeta} WHERE meta_key = '{$usermeta_key}' AND meta_value = %d";
- $prepared_sql = $wpdb->prepare( $sql, $user_id );
-
+ $usermeta_key = $this->maybe_make_multisite_key( 'press_sync_user_id' );
+ $sql = "SELECT user_id AS ID FROM {$wpdb->usermeta} WHERE meta_key = '{$usermeta_key}' AND meta_value = %d";
+ $prepared_sql = $wpdb->prepare( $sql, $user_id );
$press_sync_user_id = $wpdb->get_var( $prepared_sql );
+ $this->debug_log( __( 'Attempted finding an author for this object with SQL: ', 'press-sync' ), $prepared_sql );
+
return ( $press_sync_user_id ) ? $press_sync_user_id : 1;
}
@@ -691,7 +728,7 @@ public function attach_featured_image( $post_id, $post_args ) {
// Post does not have a featured image so bail early.
if ( empty( $post_args['featured_image'] ) ) {
- return false;
+ return $this->log( __( 'No featured image attached.', 'press-sync' ) );
}
// Allow download_url() to use an external request to retrieve featured images.
@@ -705,7 +742,11 @@ public function attach_featured_image( $post_id, $post_args ) {
// Remove filter that allowed an external request to be made via download_url().
remove_filter( 'http_request_host_is_external', array( $this, 'allow_sync_external_host' ) );
- return $response ? '' : "Error attaching thumbnail {$thumbnail_id} to post {$post_id}";
+ if ( false === $response ) {
+ return $this->error_log( sprintf( __( 'Error attaching thumbnail "%d" to post "%d"', 'press-sync'), $thumbnail_id, $post_id ) );
+ }
+
+ return $this->log( __( 'Thumbnail meta attached.', 'press-sync' ) );
}
/**
@@ -733,18 +774,18 @@ public function allow_sync_external_host( $allow, $host, $url ) {
* @param array $comments The WP Comments.
*/
public function attach_comments( $post_id, $comments ) {
-
if ( empty( $post_id ) || ! $comments ) {
+ $this->log( __( 'No comments to attach.', 'press-sync' ) );
return;
}
foreach ( $comments as $comment_args ) {
-
// Check to see if the comment already exists.
if ( $comment = $this->comment_exists( $comment_args ) ) {
continue;
}
+ $this->log( __( 'Attaching new comment.', 'press-sync' ) );
// Set Comment Post ID to correct local Post ID.
$comment_args['comment_post_ID'] = $post_id;
@@ -753,11 +794,14 @@ public function attach_comments( $post_id, $comments ) {
$comment_id = wp_insert_comment( $comment_args );
- if ( ! is_wp_error( $comment_id ) ) {
+ if ( is_wp_error( $comment_id ) ) {
+ $this->error_log( sprintf( __( 'Error inserting comment: "%s"', 'press-sync' ), $comment_id->get_error_message() ) );
+ continue;
+ }
- foreach ( $comment_args['meta_input'] as $meta_key => $meta_value ) {
- update_comment_meta( $comment_id, $meta_key, $meta_value );
- }
+ $this->debug_log( __( 'Updating comment metadata.', 'press-sync' ), $comment_args['meta_input'] );
+ foreach ( $comment_args['meta_input'] as $meta_key => $meta_value ) {
+ update_comment_meta( $comment_id, $meta_key, $meta_value );
}
}
}
@@ -854,6 +898,8 @@ public function get_non_synced_duplicate( $post_args ) {
if ( ! empty( $post_args['post_name'] ) ) {
global $wpdb;
+ $this->log( sprintf( __( 'Looking for non-synced duplicate using post_name of "%s".', 'press-sync' ), $post_args['post_name'] ) );
+
$sql = "SELECT ID, post_title, post_content, post_type, post_modified FROM {$wpdb->posts} WHERE post_name = %s AND post_type = %s";
$prepared_sql = $wpdb->prepare( $sql, $post_args['post_name'], $post_args['post_type'] );
@@ -865,6 +911,7 @@ public function get_non_synced_duplicate( $post_args ) {
$content_threshold = $this->content_threshold;
if ( $duplicate_post && false !== $content_threshold && 0 !== absint( $content_threshold ) ) {
+ $this->log( sprintf( __( 'Looking for non-synced duplicate using post_content comparison threshold of %d%%.', 'press-sync' ), $content_threshold ) );
$content_threshold = absint( $content_threshold );
// Calculate how similar the post contents are (is?).
@@ -875,6 +922,8 @@ public function get_non_synced_duplicate( $post_args ) {
}
}
+ $this->log( sprintf( __( 'Result of search for non-synced duplicate: %s.', 'press-sync' ), ( $duplicate_post ? 'Found' : 'Not found' ) ) );
+
return $duplicate_post;
}
@@ -961,18 +1010,22 @@ public function get_sync_progress( $request ) {
}
private function maybe_upload_remote_attachment( $attachment_args ) {
+ $this->log( __( 'Attempting to upload remote attachment.', 'press-sync' ) );
+
$attachment_url = isset( $attachment_args['details']['url'] ) ? $attachment_args['details']['url'] : $attachment_args['attachment_url'];
$attachment_post_date = isset( $attachment_args['details']['post_date'] ) ? $attachment_args['details']['post_date'] : $attachment_args['post_date'];
$attachment_title = isset( $attachment_args['post_title'] ) ? $attachment_args['post_title'] : '';
$attachment_name = isset( $attachment_args['post_name'] ) ? $attachment_args['post_name'] : '';
+ $this->debug_log( __( 'Incoming attachment arguments: ', 'press-sync' ), $attachment_args );
+
// Check to see if the file already exists.
if ( $attachment_id = $this->plugin->file_exists( $attachment_url, $attachment_post_date ) ) {
+ $this->log( sprintf( __( 'Found existing attachment with ID "%d"', 'press-sync' ), $attachment_id ) );
return array( 'ID' => $attachment_id );
}
- $attachment_metadata = $this->get_attachment_metadata_from_request( $attachment_args );
- $temp_file = false;
+ $temp_file = false;
require_once( ABSPATH . '/wp-admin/includes/image.php' );
require_once( ABSPATH . '/wp-admin/includes/file.php' );
@@ -1041,22 +1094,14 @@ private function maybe_upload_remote_attachment( $attachment_args ) {
private function update_post_meta_array( $post_id, $meta_data = array() ) {
foreach ( $meta_data as $field => $values ) {
if ( is_array( $values ) ) {
- update_post_meta( $post_id, $field, current( $values ) );
- continue;
-
- // Handle $values as an array.
- if ( 1 === count( $values ) ) {
- update_post_meta( $post_id, $field, maybe_unserialize( current( $values ) ) );
- } else {
- // Also handle multiple keys by removing and re-adding.
- delete_post_meta( $post_id, $field );
- foreach ( $values as $value ) {
- add_post_meta( $post_id, $field, maybe_unserialize( $value ) );
- }
- }
+ $value = current( $values );
} else {
- update_post_meta( $post_id, $field, maybe_unserialize( $values ) );
+ $value = maybe_unserialize( $values );
}
+
+ update_post_meta( $post_id, $field, $value );
+
+ $this->log( sprintf( __( 'Attempted update of post meta field "%s" with value "%s".', 'press-sync' ), $field, $values ) );
}
}
@@ -1091,21 +1136,14 @@ public function sync_taxonomy_term( $object_args ) {
}
}
+ $this->log( sprintf( __( 'Added term "%s" to taxonomy "%s".', 'press-sync' ), $object_args['name'], $object_args['taxonomy'] ) );
+
if ( ! empty( $object_args['meta_input'] ) ) {
$this->maybe_update_term_meta( $term_ids['term_id'], $object_args['meta_input'] );
}
} catch ( \Exception $e ) {
- trigger_error( $e->getMessage() );
- return array(
- 'debug' => $e->getMessage(),
- );
+ $this->error_log( $e->getMessage() );
}
-
- return array(
- 'debug' => array(
- 'message' => __( 'The taxonomy term was succesfully added.', 'press-sync' ),
- ),
- );
}
/**
@@ -1120,10 +1158,12 @@ private function attach_terms( $post_id, $post_args ) {
if ( isset( $post_args['tax_input'] ) ) {
foreach ( $post_args['tax_input'] as $taxonomy => $terms ) {
if ( $this->is_partial_term_sync( $terms ) ) {
+ $this->log( sprintf( __( 'Doing partial term sync for term "%s" in taxonomy "%s".' , 'press-sync' ), $terms['slug'], $terms['taxonomy'] ) );
wp_set_object_terms( $post_id, $terms['slug'], $terms['taxonomy'], true );
wp_remove_object_terms( $post_id, 'uncategorized', 'category' );
continue;
}
+
$this->maybe_create_new_terms( $taxonomy, $terms );
wp_set_object_terms( $post_id, wp_list_pluck( $terms, 'slug' ), $taxonomy, false );
}
@@ -1140,11 +1180,7 @@ private function attach_terms( $post_id, $post_args ) {
*/
private function fix_term_relationships( $post_id, $post_args ) {
$this->attach_terms( $post_id, $post_args );
- return array(
- 'debug' => array(
- 'message' => __( 'Fixed term relationships.', 'press-sync' ),
- ),
- );
+ return $this->log( __( 'Fixed term relationships.', 'press-sync' ) );
}
/**
@@ -1186,6 +1222,7 @@ private function maybe_make_multisite_key( $key ) {
private function maybe_create_new_terms( $taxonomy, $terms ) {
foreach ( $terms as $term ) {
if ( term_exists( $term['slug'], $taxonomy ) ) {
+ $this->log( sprintf( __( 'Term "%s" already exists for taxonomy "%s".', 'press-sync' ), $term['name'], $taxonomy ) );
continue;
}
@@ -1206,13 +1243,14 @@ private function maybe_create_new_terms( $taxonomy, $terms ) {
* @param string $taxonomy The taxonomy to attach the term to.
*/
private function create_term( $term, $taxonomy ) {
+ $this->log( sprintf( __( 'Creating new term "%s" for taxonomy "%s".', 'press-sync' ), $term['name'], $taxonomy ) );
$term_result = wp_insert_term( $term['name'], $taxonomy, array(
'slug' => $term['slug'],
'description' => $term['description'],
) );
if ( is_wp_error( $term_result ) ) {
- trigger_error( sprintf( __( 'Could not insert new term "%s": %s.', 'press-sync' ), $term['name'], $term_result->get_error_message() ) );
+ $this->error_log( sprintf( __( 'Could not insert new term "%s": %s.', 'press-sync' ), $term['name'], $term_result->get_error_message() ) );
}
return $term_result['term_id'];
@@ -1228,14 +1266,21 @@ private function create_term( $term, $taxonomy ) {
private function maybe_update_term_meta( $term_id, $term_meta ) {
foreach ( $term_meta as $meta_key => $meta_value ) {
$meta_value = is_array( $meta_value ) ? current( $meta_value ) : $meta_value;
+
+ delete_term_meta( $term_id, $meta_key );
$meta_result = update_term_meta( $term_id, $meta_key, $meta_value );
- if ( is_wp_error( $meta_result ) ) {
- trigger_error( sprintf( __( 'Error updating term meta, ambiguous term ID: %s', 'press-sync' ), $meta_result->get_error_message() ) );
- }
+ switch( true ) {
+ case is_wp_error( $meta_result ):
+ $this->error_log( sprintf( __( 'Error updating term meta, ambiguous term ID: %s', 'press-sync' ), $meta_result->get_error_message() ) );
+ break;
- if ( false === $meta_result ) {
- trigger_error( sprintf( __( 'Could not add term meta for term %d.', 'press-sync' ), $term_id ) );
+ case false === $meta_result:
+ $this->error_log( sprintf( __( 'Could not add term meta for term %d, it is possible the term meta has not changed.', 'press-sync' ), $term_id ) );
+ break;
+
+ default:
+ $this->log( sprintf( __( 'Updated term meta, added "%s" with value "%s".', 'press-sync' ), $meta_key, var_export( $meta_value, 1 ) ) );
}
}
}
@@ -1248,7 +1293,7 @@ private function maybe_update_term_meta( $term_id, $term_meta ) {
* @return bool
*/
private function is_partial_term_sync( $terms ) {
- return 2 == count( $terms );
+ return 2 == count( $terms ) && ! is_array( current( $terms ) );
}
/**
@@ -1265,9 +1310,62 @@ private function clean_post_object_args( $args ) {
&& '0000-00-00 00:00:00' === $args['post_date']
&& ! in_array( $args['post_status'], array( 'draft', 'pending' ) )
) {
+ $this->log( __( 'Giving post a date of the Unix Epoch (incoming date was (0000-00-00 00:00:00").', 'press-sync' ) );
$args['post_date'] = date( 'Y-m-d H:i:s', 0 );
}
return $args;
}
+
+ /**
+ * Log a message.
+ *
+ * @since NEXT
+ * @param string $message The message to add to the logs.
+ * @param string $prefix This prefix will be prepended to the log message to denote the log type.
+ */
+ public function log( $message, $prefix = 'i' ) {
+ $this->logs[] = "[{$prefix}] {$message}";
+ }
+
+ /**
+ * Log an error message with a severity level.
+ *
+ * @since NEXT
+ * @param string $message The error message to add to the logs.
+ * @param int $level The log level to use (must be one of E_USER_NOTICE, E_USER_WARNING, or E_USER_ERROR).
+ */
+ public function error_log( $message, $level = E_USER_NOTICE ) {
+ trigger_error( $message, $level );
+ $this->log( $message, 'e' );
+ }
+
+ /**
+ * Log a debugging message and debugging information.
+ *
+ * @since NEXT
+ * @param string $message The debugging message to add to the logs.
+ * @param mixed $extra If supplied, this variable will be cleaned up and printed as a string in the logs.
+ */
+ public function debug_log( $message, $extra = null ) {
+ $this->log( $message, 'd' );
+
+ if ( $extra ) {
+ $extra = print_r( $extra, 1 );
+ $extra = trim( $extra );
+ $extra = strtr( $extra, [ "\n" => ' ' ] );
+ $extra = preg_replace( '/\s+/', ' ', $extra );
+ $this->log( $extra, 'd' );
+ }
+ }
+
+ /**
+ * Get the internal $logs property.
+ *
+ * @since NEXT
+ * @return array
+ */
+ public function get_log() {
+ return $this->logs;
+ }
}
diff --git a/includes/class-dashboard.php b/includes/class-dashboard.php
index 00a1690..3618062 100644
--- a/includes/class-dashboard.php
+++ b/includes/class-dashboard.php
@@ -128,6 +128,13 @@ public function load_scripts() {
wp_enqueue_script( 'press-sync', $press_sync_js, true );
wp_localize_script( 'press-sync', 'press_sync', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
+
+ // jQuery UI datepicker support.
+ wp_enqueue_script( 'jquery-ui-datepicker' );
+
+ // Get the styles for the datepicker.
+ wp_register_style('jquery-ui', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css');
+ wp_enqueue_style( 'jquery-ui' );
}
/**
diff --git a/includes/class-press-sync.php b/includes/class-press-sync.php
index 93fc097..38031f3 100644
--- a/includes/class-press-sync.php
+++ b/includes/class-press-sync.php
@@ -45,6 +45,22 @@ class Press_Sync {
*/
public $remote_domain = null;
+ /**
+ * Sync posts modified after this date.
+ *
+ * @var string
+ * @since 0.8.0
+ */
+ private $delta_date = false;
+
+ /**
+ * The last connection error when attempting to contact the remote site.
+ *
+ * @var string
+ * @since NEXT
+ */
+ private $last_connection_error = '';
+
/**
* Initialize the class instance.
*
@@ -185,7 +201,6 @@ public function init_connection( $remote_domain = '' ) {
* @return boolean
*/
public function check_connection( $url = '' ) {
-
$url = $this->get_remote_url( $url );
$remote_get_args = array(
@@ -194,15 +209,36 @@ public function check_connection( $url = '' ) {
$response = wp_remote_get( $url, $remote_get_args );
$response_code = wp_remote_retrieve_response_code( $response );
+ $response_body = null;
+ $success = false;
- if ( 200 === $response_code ) {
- $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
+ try {
+ if ( 200 === absint( $response_code ) ) {
+ $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
+ $success = isset( $response_body['success'] ) ? $response_body['success'] : false;
- return isset( $response_body['success'] ) ? $response_body['success'] : false;
- }
+ if ( ! $success ) {
+ $message = var_export( $response, 1 );
- return false;
+ if ( ! empty( $response_body['data']['message'] ) ) {
+ $message = $response_body['data']['message'];
+ }
+
+ throw new \Exception( $message );
+ }
+ }
+ if ( is_wp_error( $response ) ) {
+ throw new \Exception( $response->get_error_message() );
+ }
+
+ } catch ( \Exception $e ) {
+ $error = __( '
Got response code: "%d".
Got response: "%s"', 'press-sync' );
+ $message = $e->getMessage();
+ $this->last_connection_error = sprintf( $error, $response_code, $message );
+ }
+
+ return $success;
}
/**
@@ -405,7 +441,17 @@ public function get_relationships( $object_id, $taxonomies ) {
$sql = $GLOBALS['wpdb']->prepare( $sql, $object_id );
$terms = $GLOBALS['wpdb']->get_results( $sql, ARRAY_A );
- return $terms;
+ $final_terms = [];
+
+ foreach ( $terms as $term ) {
+ if ( ! isset( $final_terms[ $term['taxonomy'] ] ) ) {
+ $final_terms[ $term['taxonomy'] ] = [];
+ }
+
+ $final_terms[ $term['taxonomy'] ][] = $term['slug'];
+ }
+
+ return $final_terms;
}
foreach ( $taxonomies as $key => $taxonomy ) {
@@ -1018,8 +1064,19 @@ public function sync_batch( $content_type = 'post', $settings = array(), $next_p
$this->init_connection( $settings['ps_remote_domain'] );
// Build out the url and send the data to the remote site.
- $url = $this->get_remote_url( '', 'sync' );
- $logs = $this->send_data_to_remote_site( $url, $objects_args );
+ $url = $this->get_remote_url( '', 'sync' );
+ $response = $this->send_data_to_remote_site( $url, $objects_args );
+
+ $response = json_decode( $response, true );
+ $object_status = $logs = array();
+
+ if ( isset( $response['responses'] ) ) {
+ $object_status = $response['responses'];
+ }
+
+ if ( isset( $response['log'] ) ) {
+ $logs = $response['log'];
+ }
return array(
'ps_objects_to_sync' => $content_type,
@@ -1027,6 +1084,7 @@ public function sync_batch( $content_type = 'post', $settings = array(), $next_p
'total_objects_processed' => ( $next_page * $this->settings['ps_page_size'] ) - ( $this->settings['ps_page_size'] - count( $objects ) ),
'next_page' => $next_page + 1,
'log' => $logs,
+ 'status' => $object_status,
);
}
@@ -1386,7 +1444,7 @@ public function change_the_next_page( $next_page = 1 ) {
if ( 0 < $page_offset && 1 === absint( $next_page ) ) {
$page_offset = floor( $page_offset / $this->settings['ps_page_size'] );
- $next_page += ( $page_offset - 1);
+ $next_page += ( $page_offset - 1 );
error_log( '----NP: ' . $next_page );
}
@@ -1437,7 +1495,7 @@ public function get_synced_object_ids( $objects_to_sync ) {
update_option( $option_name, $response_body['data']['synced'] );
return $response_body['data']['synced'];
- }
+ }
/**
* Removes post IDs if the option to preserve them isn't active.
@@ -1500,7 +1558,7 @@ public function count_taxonomy_term_to_sync() {
* Gets the next set of taxonomies/terms to sync.
*
* @since 0.7.0
- * @param int $next_page The page of results to get.
+ * @param int $next_page The page of results to get.
* @return array
*/
public function get_taxonomy_term_to_sync( $next_page ) {
@@ -1597,6 +1655,7 @@ public function maybe_get_terms_for_post( $where ) {
object_id = %d
)
SQL;
+
$where = $GLOBALS['wpdb']->prepare( $where, $this->settings['ps_testing_post'] );
return $where;
}
@@ -1638,4 +1697,14 @@ public function prepare_post_delta_date( $settings ) {
$settings['ps_delta_date'] = date( 'y-m-d 00:00:00', strtotime( $settings['ps_delta_date'] ) ) ?: false;
return $settings;
}
+
+ /**
+ * Gets the last connection error.
+ *
+ * @since NEXT
+ * @return string
+ */
+ public function get_connection_error() {
+ return $this->last_connection_error;
+ }
}
diff --git a/views/dashboard/html-bulk-sync.php b/views/dashboard/html-bulk-sync.php
index e6be773..0cbe404 100644
--- a/views/dashboard/html-bulk-sync.php
+++ b/views/dashboard/html-bulk-sync.php
@@ -5,7 +5,10 @@
This tool allows you to synchronize this entire site (or a portion of it) with another WordPress site.
Enter your settings below. Save the changes. Press "Sync" to trigger Press Sync.
- ++ + +
Check your remote Press Sync key. You are not connected to the remote site.
@@ -18,88 +21,101 @@