Plugin Directory

Changeset 2491116


Ignore:
Timestamp:
03/10/2021 12:04:14 AM (5 years ago)
Author:
davejesch
Message:
  • fix: Ensure JSON encoded metadata maintains escaped characters within strings. (Thanks Kris B.)
  • fix: Mimic WordPress's "feature" of slash encoding special characters in password strings. (Thanks Brian S.)
  • fix: Add braces to denote code block. (Thanks Miguel D.)
  • fix: Improve handling of multiple postmeta entries with the same meta_key. (Thanks Miguel D.)
  • enhancement: Allow WPSiteSync API operations to work when Members' "Enable Private Site" setting is turned on. (Thanks Christopher B.)
  • enhancement: Allow connections to DesktopServer's Ngrok site. (Thanks Wrenford H.)
  • enhancement: Add mechanism to inform Target site of any add-ons required to process data from Source.
  • enhancement: Add before/after API request processing hooks.
  • enhancement: Change declaration of parse_shortcodes() so it can be used by Elementor add-on.
  • enhancement: Check for Gutenberg block property's existence before accessing to prevent invalid references.
  • enhancement: Update compatibility with Coming Soon v6+ (Thanks Ned G.)
  • enhancement: Improve error detection in processing AJAX requests (Thanks Ned G.)
  • enhancement: Improve Block property detection and error recovery.
Location:
wpsitesynccontent
Files:
59 added
21 edited

Legend:

Unmodified
Added
Removed
  • wpsitesynccontent/trunk/assets/js/sync.js

    r2247839 r2491116  
    9797WPSiteSyncContent.prototype.is_gutenberg = function()
    9898{
    99     if ('undefined' !== typeof(wp.blocks) && 'undefined' !== typeof(wp.blocks.registerBlockType)) {
     99    if ('undefined' !== typeof(wp.blocks) && 'undefined' !== typeof(wp.blocks.registerBlockType) &&
     100        'undefined' !== typeof(wp.data) && null !== wp.data.select('core/editor')) {
    100101//console.log('.is_gutenberg() returning true');
     102
    101103        return true;
    102104    }
     
    536538            }
    537539        },
    538         error: function(response) {
     540        error: function(response, status) {
    539541//console.log('push() failure response:');
    540542//console.log(response);
     
    542544            if ('undefined' !== typeof(response.error_message))
    543545                wpsitesynccontent.set_message('<span class="error">' + response.error_message + '</span>', false, true);
    544             else
     546            else if (response.status >= 500) {
     547                wpsitesynccontent.set_message('<span class="error">' +
     548                    jQuery('#sync-server-err-msg').html().replace('%err%', response.status) +
     549                    '</span>', false, true);
     550            } else
    545551                wpsitesynccontent.set_message('<span class="error">' + jQuery('#sync-runtime-err-msg').html() + '</span>', false, true);
    546552//          jQuery('#sync-content-anim').hide();
  • wpsitesynccontent/trunk/classes/admin.php

    r2321211 r2491116  
    2020        add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
    2121        add_action('wp_dashboard_setup', array($this, 'dashboard_init'), 50);
     22        // TODO: use a 'spectrom_sync_allow_sync', post_id, post_type filter and the post ID to allow add-ons to control whether or not the metabox is displayed for specific posts
    2223        add_action('add_meta_boxes', array($this, 'add_sync_metabox'));
    2324        add_filter('plugin_action_links_wpsitesynccontent/wpsitesynccontent.php', array($this, 'plugin_action_links'));
     
    2930
    3031        // TODO: only init if running settings page
     32//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' getting SyncSettings instance action=' . current_action());
    3133        SyncSettings::get_instance();
    3234    }
     
    8385            return;
    8486//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' registering "sync"');
    85         wp_register_script('sync', WPSiteSyncContent::get_asset('js/sync.js'), array('jquery'), WPSiteSyncContent::PLUGIN_VERSION, TRUE);
    86         wp_register_script('sync-settings', WPSiteSyncContent::get_asset('js/settings.js'), array('jquery'), WPSiteSyncContent::PLUGIN_VERSION, TRUE);
     87        wp_register_script('sync', WPSiteSyncContent::get_asset('js/sync.js'), array('jquery'),
     88            WPSiteSyncContent::PLUGIN_VERSION, TRUE);
     89        wp_register_script('sync-settings', WPSiteSyncContent::get_asset('js/settings.js'),
     90            array('jquery'), WPSiteSyncContent::PLUGIN_VERSION, TRUE);
     91        wp_register_script('sync-common', WPSiteSyncContent::get_asset('js/sync-common.js'),
     92            array('jquery'), WPSiteSyncContent::PLUGIN_VERSION, TRUE);
    8793
    8894        wp_register_style('sync-admin', WPSiteSyncContent::get_asset('css/sync-admin.css'), array(), WPSiteSyncContent::PLUGIN_VERSION, 'all');
     
    104110            }
    105111            wp_enqueue_script('sync-settings');
    106 
    107             $option_data = array('site_key' => SyncOptions::get('site_key'));
    108             wp_localize_script('sync-settings', 'syncdata', $option_data);
    109 
    110112            wp_enqueue_style('sync-admin');
    111113        }
     114
     115        $option_data = array('site_key' => SyncOptions::get('site_key'));
     116        // allow extensions to modify the data
     117        $option_data = apply_filters('spectrom_sync_option_data', $option_data);
     118SyncDebug::log(__METHOD__.'():' . __LINE__ . ' syncdata=' . var_export($option_data, TRUE));
     119        wp_localize_script('sync-common', 'syncdata', $option_data);
     120        wp_enqueue_script('sync-common');
    112121    }
    113122
     
    142151                    __('WPSiteSync logo', 'wpsitesynccontent') . '" title="' . __('WPSiteSync for Content', 'wpsitesynccontent') . '" />&#8482';
    143152                add_meta_box(
    144                     'spectrom_sync',                // TODO: update name
     153                    // TODO: update name
     154                    'spectrom_sync',
    145155                    $img, // __('WPSiteSync for Content', 'wpsitesynccontent'),
    146156                    array($this, 'render_sync_metabox'),
     
    364374        if ($pull_disabled)
    365375            echo '<div id="sync-pull-msg"><div style="color: #0085ba;">', __('Please activate the Pull extension.', 'wpsitesynccontent'), '</div></div>';
    366         echo '<div id="sync-runtime-err-msg">', __('A PHP runtime error occurred while processing your request. Examine Target log files for more information.', 'wpsitesynccontent'), '</div>';
     376        echo '<div id="sync-runtime-err-msg">', __('A PHP runtime error occurred while processing your request. Examine log files for more information.', 'wpsitesynccontent'), '</div>';
     377        echo '<div id="sync-server-err-msg">', __('An HTTP %err% error occured in sending the request to the Source site.', 'wpsitesynccontent'), '</div>';
    367378        echo '<div id="sync-error-msg">', __('Error: error encountered during request.', 'wpsitesynccontent'), '</div>';
    368379        echo '<span id="sync-msg-working">', __('Pushing Content to Target...', 'wpsitesynccontent'), '</span>';
  • wpsitesynccontent/trunk/classes/admindashboard.php

    r2158145 r2491116  
    6464            $msg = sprintf(__('Thank you for using <a href="%1$s" target="_blank">WPSiteSync for Content</a>. Please consider <a href="%2$s" target="_blank">rating us</a> on <a href="%2$s" target="_blank">WordPress.org</a>!', 'wpsitesynccontent'),
    6565                esc_url('https://wpsitesync.com'),
    66                 esc_url('https://wordpress.org/support/view/plugin-reviews/wpsitesynccontent?filter=5#postform')
     66                esc_url('https://wordpress.org/support/plugin/wpsitesynccontent/reviews?rate=5#new-post')
    6767            );
    6868            break;
  • wpsitesynccontent/trunk/classes/ajax.php

    r2321211 r2491116  
    3838        $operation = $this->post('operation');
    3939        $response = new SyncApiResponse(TRUE);
     40//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' op=' . var_export($operation, TRUE));
    4041
    4142        // set headers
     
    4950            $response->send();
    5051        }
    51         $operation = sanitize_key($operation);
     52SyncDebug::log(__METHOD__.'():' . __LINE__ . ' op=' . var_export($operation, TRUE));
    5253
    5354        // perform authentication checking: must be logged in
     
    103104                break;
    104105            default:
     106//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' filter "spectrom_sync_ajax_operation" op=' . var_export($operation, TRUE));
    105107                // allow add-ons a chance to handle their own AJAX request operation types
    106108                if (FALSE === apply_filters('spectrom_sync_ajax_operation', FALSE, $operation, $response)) {
  • wpsitesynccontent/trunk/classes/apicontroller.php

    r2321211 r2491116  
    2020    private $_target_urls = NULL;                   // list of Target URLs for domain transposition
    2121    private $_action = NULL;                        // API action being performed
    22     private $_parent_action = NULL;                 // the parent action for the current API call
     22    public $_parent_action = NULL;                  // the parent action for the current API call
    2323    private $_sync_model = NULL;                    // class property for SyncModel- used in push_complete API calls
    2424
     
    101101            if ($this->_response->has_errors()) {
    102102                $this->_response->send(NULL === $this->args['parent_action']);  // calls die() if there is a parent action
    103                 return;
    104             }
    105         }
     103                return;         // for Pull requests we want to return and continue processing
     104            }
     105        }
     106
     107        // signal add-ons before processing API request
     108        do_action('spectrom_sync_before_api', $this->_action);
    106109
    107110//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' check action: ' . $action);
     
    136139
    137140        default:
     141SyncDebug::log(__METHOD__.'():' . __LINE__ . ' checking for dependent add-ons');
     142            // TODO: move before switch so this is done for all API requests
     143            $required = $this->get_header(SyncApiHeaders::HEADER_SYNC_REQUIRED_ADDON);
     144SyncDebug::log(__METHOD__.'():' . __LINE__ . ' header: ' . $required);
     145            if (!empty($required)) {
     146                // we know there's a dependency. check that it's active
     147                $ext = apply_filters('spectrom_sync_active_extensions', array(), FALSE); // get active extensions
     148SyncDebug::log(__METHOD__.'():' . __LINE__ . ' extensions: ' . var_export($ext, TRUE));
     149                $found = FALSE;
     150                foreach ($ext as $extension) {
     151                    if ($required === $extension['name']) {
     152                        $found = TRUE;
     153                        break;
     154                    }
     155                }
     156                if (!$found) {
     157                    $this->_response->error_code(SyncApiRequest::ERROR_EXTENSION_MISSING, $required);
     158                    $this->_response->send();
     159                    return;
     160                }
     161            }
     162
    138163SyncDebug::log(__METHOD__."() sending action '{$this->_action}' to filter 'spectrom_sync_api'");
    139164            // let add-ons have a chance to process the request
     
    166191        $inst = self::get_instance($args);
    167192if (NULL === $inst)
    168     SyncDebug::log(__METhOD__.'():' . __LINE__ . ' instance is NULL');
     193    SyncDebug::log(__METHOD__.'():' . __LINE__ . ' instance is NULL');
    169194SyncDebug::log(__METHOD__.'():' . __LINE__ . ' dispatching...');
    170195        $inst->dispatch();
     
    269294                $this->_headers[strtolower($key)] = $value;
    270295            }
    271 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' read request headers: ' . var_export($this->_headers, TRUE));
     296SyncDebug::log(__METHOD__.'():' . __LINE__ . ' read request headers: ' . var_export($this->_headers, TRUE)); ##
    272297        }
    273298
     
    318343SyncDebug::log(__METHOD__.'():'.__LINE__);
    319344//SyncDebug::log(' post data: ' . SyncDebug::arr_sanitize($_POST));
    320 //SyncDebug::log(' request data: ' . var_export($_REQUEST, TRUE));
     345//SyncDebug::log(' request data: ' . SyncDebug::arr_sanitize($_REQUEST));
    321346        // TODO: need to assume failure, not success - then set to success when successful
    322347        $response->success(TRUE);
     
    420445        if (0 !== SyncOptions::get_user_id())
    421446            wp_set_current_user(SyncOptions::get_user_id());
     447
     448        // adjust post_time and post_modified based on Time Zone settings for Source and Target #278
     449        $source_offset = (float) $this->post_raw('gmt_offset', '');
     450        $target_offset = (float) get_option('gmt_offset', '');
     451        // check for empty, not a 0 hour adjustment #278
     452        if (!empty($this->post_raw('gmt_offset')) && $source_offset !== $target_offset) {
     453            $post_date_gmt = DateTime::createFromFormat('Y-m-d H:i:s', $post_data['post_date_gmt']);
     454            $post_date_gmt->add(new DateInterval('PT' . ($target_offset - $source_offset) . 'H'));
     455            $new_post_date = $post_date_gmt->format('Y-m-d H:i:s');
     456SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post_date_gmt=' . $post_data['post_date_gmt'] . ' new post_date=' . $new_post_date);
     457##          $post_data['post_date'] = $new_post_date;
     458            // same process for post_modified
     459            $post_modified_gmt = DateTime::createFromFormat('Y-m-d H:i:s', $post_data['post_modified_gmt']);
     460            $post_modified_gmt->add(new DateInterval('PT' . ($target_offset - $source_offset) . 'H'));
     461            $new_post_modified = $post_modified_gmt->format('Y-m-d H:i:s');
     462SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post_modified_gmt=' . $post_data['post_modified_gmt'] . ' new post_modified=' . $new_post_modified);
     463##          $post_data['post_modified'] = $new_post_date;
     464        }
    422465
    423466        // add/update post
     
    451494SyncDebug::log(__METHOD__.'():' . __LINE__ . ' content: ' . $post_data['post_content']);
    452495                $target_post_id = wp_insert_post($new_post_data); // ;here;
     496SyncDebug::log(__METHOD__.'():' . __LINE__ . ' created post ID #' . $target_post_id);
    453497            } else {
    454498SyncDebug::log(__METHOD__.'():' . __LINE__ . ' user does not have permission to update content');
     
    473517        $model->save_sync_data($save_sync);
    474518
    475         // log the Push operation in the ‘spectrom_sync_push’ table
     519        // log the Push operation in the 'spectrom_sync_push' table
    476520        $logger = new SyncLogModel();
    477521//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' starting logger');
     
    500544            unstick_post($target_post_id);
    501545
    502         // sync metadata
     546        // handle metadata
     547        $this->_process_postmeta($target_post_id);
     548
     549        // handle taxonomy information
     550SyncDebug::log(__METHOD__.'():' . __LINE__ . ' handling taxonomies');
     551        $this->_process_taxonomies($target_post_id);
     552
     553        // check post thumbnail
     554        $thumbnail = $this->post('thumbnail', '');
     555        if ('' === $thumbnail) {
     556            // remove the thumbnail -- it's no longer attached on the Source
     557            delete_post_thumbnail($target_post_id);
     558        }
     559
     560        // this lets add-ons know that the Push operation is complete. Ex: CPT add-on can handle additional taxonomies to update
     561SyncDebug::log(__METHOD__.'():'.__LINE__ . " calling action 'spectrom_sync_push_content'");
     562        do_action('spectrom_sync_push_content', $target_post_id, $post_data, $response);
     563
     564//$temp_post = get_post($target_post_id);
     565//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' push complete, content=' . $temp_post->post_content);
     566    }
     567
     568    /**
     569     * Handles processing of postmeta data
     570     * @param int $post_id The Target's post ID where postmeta data will be written
     571     */
     572    private function _process_postmeta($post_id)
     573    {
    503574        // TODO: note, this is in $_POST['post_data']['post_meta']
    504575        $post_meta = $this->post_raw('post_meta', array());
     576        $model = new SyncModel();
     577
    505578        // TODO: need to handle deletes - postmeta that doesn't exist in Source any more but does on Target
    506579        // TOOD: probably better to remove all postmeta, then add_post_meta() for each item found
    507580//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' handling meta data');
     581
     582        // initialize this. if needed and NULL, a new SyncSerialize() instance will be instantiated
    508583        $ser = NULL;
    509         // TODO: move postmeta processing into _process_postmeta() method
     584
     585        $pmmodel = new SyncPostMetaModel();
     586
    510587        foreach ($post_meta as $meta_key => $meta_value) {
    511             foreach ($meta_value as $value) // loop through meta_value array
     588            // handle multiple postmeta entries #287
     589            $current_entries = $pmmodel->get_post_meta($post_id, $meta_key);
     590SyncDebug::log(__METHOD__.'():' . __LINE__ . ' current postmeta=' . var_export($current_entries, TRUE));
     591
     592            // find the lower of Source vs. Target postmeta entries for this meta_key
     593            $entries = min(count($meta_value), count($current_entries));
     594            // loop through all the entries. adds and deletes are handled after this
     595            for ($idx = 0; $idx < $entries; ++$idx) { // loop through meta_value array #286
     596//          foreach ($meta_value as $value) { // loop through meta_value array #286
     597                $value = $meta_value[$idx];
    512598//$_v = $value;
    513 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' (' . gettype($value) . ') value=' . var_export($_v, TRUE));
     599//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' (' . gettype($value) . ') value=' . SyncDebug::arr_sanitize($_v));
    514600//$_v = stripslashes($_v);
    515 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' (' . gettype($value) . ') value=' . var_export($_v, TRUE));
     601//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' (' . gettype($value) . ') value=' . SyncDebug::arr_sanitize($_v));
    516602//$_v = maybe_unserialize($_v);
    517 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' new value=' . var_export($_v, TRUE));
     603//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' new value=' . SyncDebug::arr_sanitize($_v));
    518604                // change Source URL references to Target URL references in meta data
    519605//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' meta key: "' . $meta_key . '" meta data: ' . $value);
    520606SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key start=' . $meta_key . ' value=' . $value);
    521607                $value = stripslashes($value);          // removes slashes added via HTTP POST operation
     608
     609//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' stripped slashes=' . $value);
     610                $is_json = $model->is_json($value);
     611SyncDebug::log(__METHOD__.'():' . __LINE__ . ' is_json=' . ($is_json ? 'TRUE' : 'FALSE'));
    522612                // replace token with *double* escaped quote because update_post_meta() calls wp_unslash() #257
    523                 $value = str_replace('~syncescquote~', '\\\"', $value);
     613                $value = str_replace('~syncescquote~', $is_json ? '\"' : '\\\"', $value);
    524614//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key before=' . $meta_key . ' value=' . $value);
    525615                if (FALSE !== strpos($value, '~syncuni~')) {
    526                     $value = str_replace('~syncuni~', '\\\\u', $value);     // need to double escape unicode for update_post_meta()
    527                 }
    528 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' value=' . $value);
     616                    $value = str_replace('~syncuni~', $is_json ? '\\u' : '\\\\u', $value);      // need to double escape unicode for update_post_meta()
     617                }
     618//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' value=' . $value);
    529619                if (is_serialized($value)) {
    530620                    if (NULL === $ser)
     
    536626                    $value = $ser->parse_data($value, array($this, 'fixup_url_references'));
    537627                } else {
    538 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' not fixing serialized data');
     628SyncDebug::log(__METHOD__.'():' . __LINE__ . ' not fixing non-serialized data');
    539629                    $value = str_replace($this->source, site_url(), $value);
    540630                }
     631
     632                // adjust serialized JSON data containing escaped content #275
     633                if ($is_json) {
     634                    // if it's JSON data, need to add escaping
     635                    // this preserves the escaped data because update_post_meta() calls wp_unslash()
     636                    $value = wp_slash($value);
     637SyncDebug::log(__METHOD__.'():' . __LINE__ . ' add wp_slash() to JSON data ' . var_export($value, TRUE));
     638                }
     639
    541640#               if ('_wp_page_template' === $meta_key && class_exists('Elementor\Plugin', FALSE)) {
    542641#                   // #184: bug in Elementor- modules/page-templates/module.php:345 $common is not initialized
     
    547646#                       $elementor->init_common();
    548647#               }
    549 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' writing key=' . $meta_key . ' value=' . $value);
    550                 update_post_meta($target_post_id, $meta_key, maybe_unserialize($value));
    551         }
    552 
    553         // handle taxonomy information
    554 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' handling taxonomies');
    555         $this->_process_taxonomies($target_post_id);
    556 
    557         // check post thumbnail
    558         $thumbnail = $this->post('thumbnail', '');
    559         if ('' === $thumbnail) {
    560             // remove the thumbnail -- it's no longer attached on the Source
    561             delete_post_thumbnail($target_post_id);
    562         }
    563 
    564         // this lets add-ons know that the Push operation is complete. Ex: CPT add-on can handle additional taxonomies to update
    565 SyncDebug::log(__METHOD__.'():'.__LINE__ . " calling action 'spectrom_sync_push_content'");
    566         do_action('spectrom_sync_push_content', $target_post_id, $post_data, $response);
    567 
    568 $temp_post = get_post($target_post_id);
    569 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' push complete, content=' . $temp_post->post_content);
    570     }
     648
     649SyncDebug::log(__METHOD__.'():' . __LINE__ . ' writing key=' . $meta_key . ' value=' . var_export($value, TRUE));
     650//              update_post_meta($post_id, $meta_key, maybe_unserialize($value));
     651                $pmmodel->update_post_meta($current_entries[$idx], $value);     // #287
     652            } // for $idx < $entries
     653
     654            if (count($current_entries) > count($meta_value)) {
     655                // more entries on Target than on Source. Remove extra entries on Target #287
     656SyncDebug::log(__METHOD__.'():' . __LINE__ . ' removing extra entries');
     657                for (; $idx < count($current_entries); ++$idx)
     658                    $pmmodel->remove_post_meta($current_entries[$idx]);
     659            } else if (count($current_entries) < count($meta_value)) {
     660                // more entries on Source than on Target. Add extra entries on Target #287
     661                for (; $idx < count($meta_value); ++$idx) {
     662                    // TODO: move postmeta data fixup into a helper method
     663                    $value = $meta_value[$idx];
     664                    $value = stripslashes($value);
     665                    $is_json = $model->is_json($value);
     666
     667                    $value = str_replace('~syncescquote~', $is_json ? '\"' : '\\\"', $value);
     668//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key before=' . $meta_key . ' value=' . $value);
     669                    if (FALSE !== strpos($value, '~syncuni~')) {
     670                        $value = str_replace('~syncuni~', $is_json ? '\\u' : '\\\\u', $value);      // need to double escape unicode for update_post_meta()
     671                    }
     672//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $meta_key . ' value=' . $value);
     673                    if (is_serialized($value)) {
     674                        if (NULL === $ser)
     675                            $ser = new SyncSerialize();
     676///                     $fix_data = str_replace($this->source, site_url(), $value);
     677//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' fix data: ' . $fix_data);
     678///                     $fix_data = $ser->fix_serialized_data($fix_data);
     679//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' fixing serialized data: ' . $fix_data);
     680                        $value = $ser->parse_data($value, array($this, 'fixup_url_references'));
     681                    } else {
     682SyncDebug::log(__METHOD__.'():' . __LINE__ . ' not fixing non-serialized data');
     683                        $value = str_replace($this->source, site_url(), $value);
     684                    }
     685
     686                    // adjust serialized JSON data containing escaped content #275
     687                    if ($is_json) {
     688                        // if it's JSON data, need to add escaping
     689                        // this preserves the escaped data because update_post_meta() calls wp_unslash() to remove it
     690                        $value = wp_slash($value);
     691SyncDebug::log(__METHOD__.'():' . __LINE__ . ' add wp_slash() to JSON data ' . var_export($value, TRUE));
     692                    }
     693
     694SyncDebug::log(__METHOD__.'():' . __LINE__ . ' writing key=' . $meta_key . ' value=' . var_export($value, TRUE));
     695                    add_post_meta($post_id, $meta_key, maybe_unserialize($value));
     696                }
     697            }
     698        } // foreach $post_meta
     699SyncDebug::log(__METHOD__.'():' . __LINE__ . ' done processing postmeta');
     700    }
     701
    571702
    572703    /**
     
    576707    private function _process_gutenberg($response)
    577708    {
    578 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post=' . var_export($_POST, TRUE));
     709SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post=' . SyncDebug::arr_sanitize($_POST));
    579710        // https://premium.wpmudev.org/blog/a-tour-of-the-gutenberg-editor-for-wordpress/
    580711
     
    774905                                    if (NULL === $sync_data) {
    775906                                        // no id found
    776 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' cannot find Target ID for Source Content ID ' . $source_ref_id);
     907SyncDebug::log(__METHOD__.'():' . __LINE__ . ' cannot find Target ID for Source Content id ' . $source_ref_id);
     908                                        $response->error_code(SyncApiRequest::ERROR_GB_ATTACHMENT_ID, $block_name);
    777909                                    } else {
    778910                                        $target_ref_id = abs($sync_data->target_content_id);
     
    795927                            case 'wp:video':                        // Video Block- resource reference
    796928                            case 'wp:image':                        // Image Block- resource reference
    797                                 $source_ref_id = abs($obj->id);
     929                                $source_ref_id = isset($obj->id) ? abs($obj->id) : 0;
    798930                                if (0 !== $source_ref_id) {
    799931SyncDebug::log(__METHOD__.'():' . __LINE__ . ' found reference to post id ' . $source_ref_id);
     
    802934                                    if (NULL === $sync_data) {
    803935                                        // no id found
    804 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' ERROR: cannot find Target ID for Source Content ID ' . $source_ref_id);
    805                                         // TODO: error recovery
     936SyncDebug::log(__METHOD__.'():' . __LINE__ . ' cannot find Target ID for Source Content id ' . $source_ref_id);
     937                                        $response->error_code(SyncApiRequest::ERROR_GB_ATTACHMENT_ID, $block_name);
    806938                                    } else {
    807939                                        $target_ref_id = abs($sync_data->target_content_id);
     
    8751007                            case 'wp:gallery':                      // Gallery Block- multiple image references
    8761008                                $source_ids = $obj->ids;            // array of Gellery Image IDs
    877 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' source ids=' . implode(',', $source_ids));
     1009SyncDebug::log(__METHOD__.'():' . __LINE__ . ' source ids=' . implode(',', $source_ids) . ' = ' . var_export($source_ids, TRUE));
    8781010                                $new_ids = array();                 // initialize array of new ids
    8791011                                foreach ($source_ids as $source_ref_id) {
    880                                     $source_ref_id = abs($source_ref_id);
    881 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' source ref id=' . $source_ref_id);
    882                                     if (0 !== $source_ref_id) {
    883                                         // look up source post ID to see what the Target ID is
    884                                         $sync_data = $sync_model->get_sync_data($source_ref_id, $this->source_site_key);
    885                                         if (NULL === $sync_data) {
    886                                             // no id found
     1012SyncDebug::log(__METHOD__.'():' . __LINE__ . ' source ref id=' . var_export($source_ref_id, TRUE));
     1013                                    // check for NULLs and maintain array size, just skip attachment ID update process #280
     1014                                    if (NULL === $source_ref_id) {
     1015SyncDebug::log(__METHOD__.'():' . __LINE__ . ' skipping NULL ref id');
     1016                                        $new_ids[] = NULL;
     1017                                    } else {
     1018                                        $source_ref_id = abs($source_ref_id);
     1019
     1020                                        if (0 !== $source_ref_id) {
     1021                                            // look up source post ID to see what the Target ID is
     1022                                            $sync_data = $sync_model->get_sync_data($source_ref_id, $this->source_site_key);
     1023                                            if (NULL === $sync_data) {
     1024                                                // no id found
    8871025SyncDebug::log(__METHOD__.'():' . __LINE__ . ' ERROR: cannot find Target ID for Source Content ID ' . $source_ref_id);
    888                                         } else {
    889                                             $target_ref_id = abs($sync_data->target_content_id);
     1026                                            } else {
     1027                                                $target_ref_id = abs($sync_data->target_content_id);
    8901028//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' target ref id: ' . $target_ref_id);
    891                                             $att_post = get_post($target_ref_id);
    892 //                                          $obj->url = $att_post->guid;        // no need to update url, that was fixed in 'push' operation
    893                                             $new_ids[] = $target_ref_id;        // add to list of new ids
    894                                         }
    895                                     }
     1029                                                $att_post = get_post($target_ref_id);
     1030//                                              $obj->url = $att_post->guid;        // no need to update url, that was fixed in 'push' operation
     1031                                                $new_ids[] = $target_ref_id;        // add to list of new ids
     1032                                            }
     1033                                        } // 0 !== $source_ref_id
     1034                                    } // NULL check
    8961035                                    // TODO: error recovery
    8971036                                }
     1037SyncDebug::log(__METHOD__.'():' . __LINE__ . ' updated gallery ids: ' . var_export($new_ids, TRUE));
    8981038                                $from = array();
    8991039                                $to = array();
     
    10831223    private function _process_shortcodes(SyncApiResponse $apiresponse)
    10841224    {
    1085 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' POST=' . var_export($_POST, TRUE));
     1225//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' POST=' . SyncDebug::arr_sanitize($_POST));
    10861226/*
    10871227        [caption id="attachment_1995" align="aligncenter" width="808"]
     
    12761416    public function get_info(SyncApiResponse $response)
    12771417    {
    1278 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - post=' . var_export($_POST, TRUE));
     1418//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - post=' . SyncDebug::arr_sanitize($_POST));
    12791419        $input = new SyncInput();
    12801420        $target_post_id = $input->post_int('post_id', 0);
     
    15141654            }
    15151655            // check to see if $post_term was included in $taxonomies data provided via the API call
    1516             if ($found) {
    1517 //SyncDebug::log(__METHOD__.'() post term found in taxonomies list- not removing it');
    1518             } else {
     1656            if (!$found) {
    15191657                // if the $post_term assigned to the post is NOT in the $taxonomies list, it needs to be removed
    15201658//SyncDebug::log(__METHOD__.'() ** removing term #' . $post_term->term_id . ' ' . $post_term->slug . ' [' . $post_term->taxonomy . ']');
    15211659                wp_remove_object_terms($post_id, abs($post_term->term_id), $post_term->taxonomy);
     1660            } else {
     1661                // if the $post_term is present we don't need to do anything
     1662                $found = TRUE;
     1663//SyncDebug::log(__METHOD__.'() post term found in taxonomies list- not removing it');
    15221664            }
    15231665
    15241666            // Note: no need to remove term info saved via SyncModel- term IDs don't change, only the post IDs referring to them
    1525         }
     1667        } // foreach ($assigned_terms)
    15261668    }
    15271669
     
    15431685        require_once ABSPATH . 'wp-admin/includes/file.php';
    15441686        require_once ABSPATH . 'wp-admin/includes/media.php';
    1545 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' FILES=' . var_export($_FILES, TRUE));
    1546 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' POST=' . var_export($_POST, TRUE));
     1687SyncDebug::log(__METHOD__.'():' . __LINE__ . ' FILES=' . SyncDebug::arr_sanitize($_FILES));
     1688SyncDebug::log(__METHOD__.'():' . __LINE__ . ' POST=' . SyncDebug::arr_sanitize($_POST));
    15471689
    15481690        if (!isset($_FILES['sync_file_upload'])) {
     
    17601902        $this->_process_shortcodes($response);  #102
    17611903
     1904        $target_post_id = 0;
    17621905        $source_post_id = $this->post_int('post_id');
    17631906        $sync_data = $this->_sync_model->get_sync_data($source_post_id, $this->source_site_key);
     
    18121955SyncDebug::log(__METHOD__.'():' . __LINE__ . ' cannot find Target post from Source ID #' . $source_post_id);
    18131956        }
     1957
     1958        // signal add-ons that Push is complete and all Source IDs have been updated to Target IDs
     1959        do_action('spectrom_sync_push_complete', $source_post_id, $target_post_id, $response);
    18141960    }
    18151961
  • wpsitesynccontent/trunk/classes/apiheaders.php

    r2321211 r2491116  
    77{
    88    // TODO: x- headers are deprecated (rfc6648). change to spectrom-sync- headers
    9     const HEADER_SYNC_VERSION = 'x-sync-version';               // SYNC version number; used in requests and responses
    10     const HEADER_WP_VERSION = 'x-wp-version';                   // WP version number; used in requests and responses
    11     const HEADER_SOURCE = 'x-sync-source';                      // Source site's URL; used in requests
    12     const HEADER_SITE_KEY = 'x-sync-site-key';                  // Source site's site_key; used in requests
    13     const HEADER_MATCH_MODE = 'x-sync-match-mode';              // How to match Content on Target
    14     const HEADER_SYNC_REQUEST_ID = 'spectrom-sync-api-request'; // unique API request identifier
     9    const HEADER_SYNC_VERSION = 'x-sync-version';                       // SYNC version number; used in requests and responses
     10    const HEADER_WP_VERSION = 'x-wp-version';                           // WP version number; used in requests and responses
     11    const HEADER_SOURCE = 'x-sync-source';                              // Source site's URL; used in requests
     12    const HEADER_SITE_KEY = 'x-sync-site-key';                          // Source site's site_key; used in requests
     13    const HEADER_MATCH_MODE = 'x-sync-match-mode';                      // How to match Content on Target
     14    const HEADER_SYNC_REQUEST_ID = 'spectrom-sync-api-request';         // unique API request identifier
     15    const HEADER_SYNC_REQUIRED_ADDON = 'spectrom-sync-required-addon';  // API request requires the specified add-on
    1516}
    1617
  • wpsitesynccontent/trunk/classes/apimodel.php

    r2321211 r2491116  
    212212            return;
    213213        }
     214        // look for 'Comming Soon' v6+ plugin
     215        if (defined('SEEDPROD_VERSION')) {
     216            add_filter('option_seedprod_settings', array($this, 'filter_coming_soon_6_setting'), 10, 1);
     217            return;
     218        }
     219        // disable the Membership plugin's redirect to login for "Private Sites" #281
     220        add_filter('members_is_private_blog', '__return_false', 100);
    214221        // look for iThemes Security
    215222/*      if (class_exists('ITSEC_Core', FALSE)) {
     
    258265
    259266    /**
    260      * Filter for the Coming Soon plugin's configuration settings. Used to turn off it's features
     267     * Filter for the Coming Soon plugin's configuration settings. Used to turn off it's features during API requests.
    261268     * @param array $settings The Coming Soon plugins settings
    262269     * @return array The modified settings, with the 'status' value set to 0 to disable it
     
    266273        $settings['status'] = '0';
    267274        return $settings;
     275    }
     276    /**
     277     * Filter for Comming Soon v6+ plugin's configuration settings. Used to turn off it's features during API requests.
     278     * @param string $option The Coming Soon plugin's settings
     279     * @return string The modified settings, with the 'enable_coming_soon_mode' turned off.
     280     */
     281    public function filter_coming_soon_6_setting($option)
     282    {
     283        return '{"enable_maintenance_mode":false,"enable_coming_soon_mode":false}';
    268284    }
    269285
  • wpsitesynccontent/trunk/classes/apirequest.php

    r2325466 r2491116  
    1111    const ERROR_BAD_CREDENTIALS = 4;
    1212    const ERROR_SESSION_EXPIRED = 5;
    13     const ERROR_CONTENT_EDITING = 6;        // deprecated
     13    const ERROR_CONTENT_EDITING = 6;  // deprecated
    1414    const ERROR_CONTENT_LOCKED = 7;
    1515    const ERROR_POST_DATA_INCOMPLETE = 8;
     
    4242    const ERROR_CLOUDFLARE_BLOCKED = 35;
    4343    const ERROR_CLOUDFRONT_BLOCKED = 36;
     44    const ERROR_MISSING_TOKEN = 37;
     45    const ERROR_MISSING_USER = 38;
     46    const ERROR_GB_ATTACHMENT_ID = 39;
     47    const ERROR_GB_TAX_ID = 40;
    4448
    4549    const NOTICE_FILE_EXISTS = 1;
     
    4852
    4953    // TODO: rename to $target
    50     public $host = NULL;                        // URL of the host site we're pushing to
    51     public $id_refs = array();                  // list if image/content references that need to be adjusted
    52     public $post_children = array();            // list of post children to be adjusted during push_complete API handling
    53     public $gutenberg_queue = array();          // list of Gutenberg post IDs that need to be parsed for references
    54     public $gutenberg_processed = array();      // list of Gutenberg post IDs that have been processed (used to skip duplicate references)
    55     public $post_id = 0;                        // post ID being processed
    56     public $thumbnail_id = 0;                   // the post's thumbnail ID
    57     private $_post_data = NULL;                 // reference to the $post_data array being constructed
    58     private $_source_domain = NULL;             // domain sending the post information
    59     private $_response = NULL;                  // the SyncApiResponse instance for the current request
     54    public $host = NULL;      // URL of the host site we're pushing to
     55    public $id_refs = array();   // list if image/content references that need to be adjusted
     56    public $post_children = array();   // list of post children to be adjusted during push_complete API handling
     57    public $gutenberg_queue = array();   // list of Gutenberg post IDs that need to be parsed for references
     58    public $gutenberg_processed = array();  // list of Gutenberg post IDs that have been processed (used to skip duplicate references)
     59    public $gutenberg_obj = NULL;   // the current Gutenberg block data as an object
     60    public $post_id = 0;      // post ID being processed
     61    public $thumbnail_id = 0;    // the post's thumbnail ID
     62    private $_post_data = NULL;  // reference to the $post_data array being constructed
     63    private $_source_domain = NULL; // domain sending the post information
     64    private $_response = NULL;   // the SyncApiResponse instance for the current request
    6065    private $_user_id = 0;
    6166    private $_target_data = array();
    6267    private $_auth_cookie = '';
    6368    private $_queue = array();
    64     private $_processing = FALSE;               // set to TRUE when processing the $_queue
    65     private $_sent_images = array();            // list of image attachments/references within post
    66     private $_trigger_push_complete = FALSE;    // set to TRUE when 'spectrom_sync_push_queue_complete' needs to be triggered
    67     private $_triggered_push_complete = FALSE;  // set to TRUE when 'spectrom_sync_push_queue_complete' action has been triggered
     69    private $_processing = FALSE;   // set to TRUE when processing the $_queue
     70    private $_sent_images = array();   // list of image attachments/references within post
     71    private $_trigger_push_complete = FALSE; // set to TRUE when 'spectrom_sync_push_queue_complete' needs to be triggered
     72    private $_triggered_push_complete = FALSE; // set to TRUE when 'spectrom_sync_push_queue_complete' action has been triggered
    6873
    6974    /**
     
    104109        if (is_array($data)) {
    105110            $ret = $this->_auth($data);
    106             // check $res for WP_Error
     111            // check $ret for WP_Error
    107112            if (is_wp_error($ret)) {
    108113//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' error authenticating: ' . var_export($ret, TRUE));
     
    110115                return $response;
    111116            }
    112 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' current auth data: ' . SyncDebug::arr_sanitize($data));
     117SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' current auth data: ' . SyncDebug::arr_sanitize($data));
    113118        }
    114119
    115120        // TODO: do some sanity checking on $data contents
    116 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' checking action="' . $action . '"');
     121SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' checking action="' . $action . '"');
    117122        switch ($action) {
    118123        case 'auth':
     
    128133        case 'upload_media':
    129134            $data = apply_filters('spectrom_sync_api_request_media', $data, $action, $remote_args);
    130             $data = $this->_media($data, $remote_args);     // converts $data to a string
     135            $data = $this->_media($data, $remote_args);  // converts $data to a string
    131136            break;
    132137        case 'getinfo':
     
    141146        }
    142147// reduced logging                                                              #!#
    143 if (is_string($data))                                                           #!#
    144 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' action="' . $action . '" data=' . SyncDebug::post_sanitize($data));  #!#
    145 else                                                                            #!#
    146 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' action="' . $action . '" data=' . SyncDebug::arr_sanitize($data));       #!#
    147 
     148if (is_string($data))              #!#
     149SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' action="' . $action . '" data=' . SyncDebug::post_sanitize($data));#!#
     150else                   #!#
     151SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' action="' . $action . '" data=' . SyncDebug::arr_sanitize($data));#!#
    148152        // check value returned from API call
    149153        // check for filter returning a WP_Error instance
     
    164168//      if (NULL !== ($api_controller = SyncApiController::get_instance())) {
    165169//          if ('pull' === $api_controller->get_parent_action()) {
    166 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' adding id ref data to post data: ' . var_export($this->id_refs, TRUE));
    167                 $data['id_refs'] = $this->id_refs;
     170SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' adding id ref data to post data: ' . var_export($this->id_refs, TRUE));
     171            $data['id_refs'] = $this->id_refs;
    168172//          }
    169173        }
     
    183187
    184188        if (!isset($remote_args['timeout']))
    185             $remote_args['timeout'] = 30;               // default to a 30 second timeout if not already specified
    186 
    187         // send data where it's going
     189            $remote_args['timeout'] = 30;   // default to a 30 second timeout if not already specified
     190
     191
     192// send data where it's going
    188193        $url = $this->host . '?pagename=' . WPSiteSyncContent::API_ENDPOINT . '&action=' . $action;
    189 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' sending API request to ' . $url, TRUE);
     194SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' sending API request to ' . $url, TRUE);
    190195
    191196        $remote_args = apply_filters('spectrom_sync_api_arguments', $remote_args, $action);
    192197        $remote_args['headers'][self::HEADER_SYNC_REQUEST_ID] = substr(SyncOptions::get('site_key'), -16) . '-' . date('z.His') . '-' . rand(100, 999);
    193 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' req id=' . $remote_args['headers'][self::HEADER_SYNC_REQUEST_ID]);
     198SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' req id=' . $remote_args['headers'][self::HEADER_SYNC_REQUEST_ID]);
    194199
    195200        $request = wp_remote_post($url, $remote_args);
     
    208213
    209214            // validate the host and credentials
    210             if (abs($request['response']['code']) >= 400 &&
    211                 (FALSE !== stripos($request['body'], 'Wordfence') && FALSE !== stripos($request['body'], 'A potentially unsafe operation has been detected'))) {
     215            $http_code = abs($request['response']['code']);
     216            $http_server = isset($request['headers']->data) && isset($request['headers']->data['server']) ? $request['headers']->data['server'] : '';
     217            $http_body = str_replace('\\', '', $request['body']);
     218            if ($http_code >= 400 &&
     219                (FALSE !== stripos($http_body, 'Wordfence') && FALSE !== stripos($http_body, 'A potentially unsafe operation has been detected'))) {
    212220                $response->error_code(self::ERROR_WORDFENCE_BLOCKED);
    213             } else if (abs($request['response']['code']) >= 400 &&
    214                 (FALSE !== stripos($request['body'], 'Attention Required! | Cloudflare') && FALSE !== stripos($request['body'], 'Please complete the security check to access'))) {
    215             } else if (abs($request['response']['code']) >= 400 &&
    216                 (isset($request['headers']->data) &&
    217                     (isset($request['headers']->data['server']) && 'cloudflare' === $request['headers']->data['server']) &&
    218                     ($request['headers']->data['cf-request-id']))) {
    219                 $response->error_code(self::ERROR_CLOUDFLARE_BLOCKED);
    220             } else if (abs($request['response']['code']) > 200 &&
    221                 (FALSE !== stripos($request['body'], 'CloudFront attempted to establish a connection') && FALSE !== stripos($request['body'], 'Generated by cloudfront'))) {
     221            } else if ($http_code >= 400 &&
     222                (FALSE !== stripos($http_body, 'Attention Required! | Cloudflare') && FALSE !== stripos($http_body, 'Please complete the security check to access'))) {
     223                $response->error_code(self::ERROR_CLOUDFLARE_BLOCKED, __('Complete security check.', 'wpsitesynccontent'));
     224            } else if ($http_code >= 400 && 'cloudflare' === $http_server &&
     225                FALSE !== stripos($http_body, 'You don\'t have permission to access')) {
     226                $response->error_code(self::ERROR_CLOUDFLARE_BLOCKED, __('No permission', 'wpsitesynccontent'));
     227            } else if ($http_code >= 400 && 'cloudflare' === $http_server) {
     228                $response->error_code(self::ERROR_CLOUDFLARE_BLOCKED, sprintf(__('HTTP Response: %1$d.', 'wpsitesync'), $http_code));
     229            } else if ($http_code > 200 &&
     230                (FALSE !== stripos($http_body, 'CloudFront attempted to establish a connection') &&
     231                FALSE !== stripos($http_body, 'Generated by cloudfront'))) {
    222232                $response->error_code(self::ERROR_CLOUDFRONT_BLOCKED);
    223             } else if (406 === abs($request['response']['code']) && FALSE !== stripos($request['body'], 'This error was generated by Mod_Security.')) {
     233            } else if (406 === $http_code && FALSE !== stripos($http_body, 'This error was generated by Mod_Security.')) {
    224234                $response->error_code(self::ERROR_MODSECURITY_BLOCKED);
    225             } else if (!($request['response']['code'] >= 200 && $request['response']['code'] < 300)) {
    226                 $response->error_code(self::ERROR_BAD_POST_RESPONSE, abs($request['response']['code']));
     235            } else if (!($http_code >= 200 && $http_code < 300)) {
     236                $response->error_code(self::ERROR_BAD_POST_RESPONSE, $http_code);
    227237            } else if (!isset($request['headers'][self::HEADER_SYNC_VERSION])) {
    228238                $response->error_code(self::ERROR_NOT_INSTALLED);
     
    252262            // examine the Target response's error codes and assign them to the local system's response object
    253263            // TODO: Use SyncResponse::copy() method
    254             if (isset($response->response->error_code)) {
     264            if (isset($response->response->error_code) && 0 !== abs($response->response->error_code)) {
    255265                $msg = NULL;
    256266                if (!empty($response->response->error_data))
    257267                    $msg = $response->response->error_data;
    258 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' error code=' . $response->response->error_code . ' msg=' . var_export($msg, TRUE));
     268SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error code=' . $response->response->error_code . ' msg=' . var_export($msg, TRUE));
    259269                $response->error_code($response->response->error_code, $msg);
    260             } else if (isset($response->response->has_errors) && $response->response->has_errors) {
    261 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' error code=' . $response->response->error_code);
     270            } else if (isset($response->response->has_errors) && 0 !== abs($response->response->has_errors)) {
     271SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error code=' . $response->response->error_code);
    262272                $response->error_code($response->response->error_code);
    263273            }
     
    292302SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' perform after action logging action="' . $action . '"');
    293303                    switch ($action) {
    294                     case 'auth':    // no logging, but add to source table and set target site_key
     304                    case 'auth':  // no logging, but add to source table and set target site_key
    295305                        if (isset($response->response->data)) {
    296306                            // TODO: deprecated
     
    341351                                'target_site_key' => SyncOptions::get('target_site_key'),
    342352                            );
    343                             // TODO: should be 'post' since media are stored in posts table
    344 //                          if ('upload_media' === $action)
    345 //                              $sync_data['content_type'] = 'media';
     353//SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' logging data ' . var_export($sync_data, TRUE));
    346354
    347355                            $model = new SyncModel();
     
    356364
    357365                    default:
    358 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - triggering "spectrom_sync_action_success" on action="' . $action . '" data=' . SyncDebug::arr_sanitize($data));
     366SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' - triggering "spectrom_sync_action_success" on action="' . $action . '" data=' . SyncDebug::arr_sanitize($data));
    359367                        if (isset($data['post_id']))
    360368                            do_action('spectrom_sync_action_success', $action, abs($data['post_id']), $data, $response);
    361369                    }
    362370                }
    363 else SyncDebug::log(__METHOD__.'():' . __LINE__ . ' error code=' . $response->get_error_code());    #!#
     371else SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error code=' . $response->get_error_code());#!#
    364372            }
    365373
     
    449457        $this->_queue[] = array('action' => $action, 'data' => $data);
    450458    }
     459
    451460    public function add_queue($action, $data)
    452461    {
     
    540549        // serialize the data into a JSON string.
    541550//      $push_data = json_encode($push_data);
    542         // use the wp_remote_post() API to perform a connection/authenticate operation on the Target site using the Target sites configured credentials and send the JSON data.
    543         $target = new SyncApiRequest();
     551        // use the wp_remote_post() API to perform a connection/authenticate operation on the Target site using the Target site's configured credentials and send the JSON data.
     552//      $target = new SyncApiRequest();
    544553
    545554        $result = $this->api('push', $push_data); // $target->api('push', $push_data); // send data
     
    552561        if (!is_wp_error($result)) {
    553562            // PARSE IMAGES FROM SOURCE ONLY
    554             $this->_parse_media($result->data->post_id, $push_data['post_data']['post_content'], $target, $response);
     563            $this->_parse_media($result->data->post_id, $push_data['post_data']['post_content']); //, $target, $response);
    555564            $response->success(TRUE);
    556565            $response->notice_code(SyncApiRequest::NOTICE_CONTENT_SYNCD);
     
    586595    private function _auth(&$data)
    587596    {
    588 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' data: ' . var_export($data, TRUE));
     597//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' data: ' . SyncDebug::arr_sanitize($data));
    589598        // TODO: indicate error if target system not set up
    590599        // if no Target credentials provided, get them from the config
     
    592601//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' using credentials from config');
    593602            $source_model = new SyncSourcesModel();
    594             $opts = new SyncOptions();
    595             $row = $source_model->find_target($opts->get('host'));
     603            $row = $source_model->find_target(SyncOptions::get('host'));
    596604            if (NULL === $row) {
    597605                $this->_response->error_code(self::ERROR_NO_AUTH_TOKEN);
     606//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' target not found');
    598607                return new WP_Error($this->error_code_to_string(self::ERROR_NO_AUTH_TOKEN));
    599608            }
     
    602611
    603612            if (!isset($data['username']))
    604                 $data['username'] = $opts->get('username');
     613                $data['username'] = SyncOptions::get('username');
    605614            if (!isset($data['token']))
    606615                $data['token'] = $row->token;
    607616            // TODO: change name to ['target'] to be more consistent
    608617            if (!isset($data['host']))
    609                 $data['host'] = $opts->get('host');
     618                $data['host'] = SyncOptions::get('host');
    610619        } else if (!empty($data['username']) && !empty($data['password']) && !empty($data['host'])) {
    611620            // using passed-in credentials. add $data[] values into $this->_target_data
     621//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' using credentials provided');
    612622            $this->_target_data['host'] = $data['host'];
    613623            $this->_target_data['username'] = $data['username'];
     
    641651        }
    642652
    643 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - adding authentication data to array');
     653//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' adding authentication data to array');
    644654        // add authentication to the data array
    645655        $data['auth'] = array(
     
    660670        }
    661671//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' data: ' . SyncDebug::arr_sanitize($data));
    662 
    663672        return NULL;
    664673    }
     
    668677        $this->_post_data = array();
    669678    }
     679
    670680    public function set_post_data($data)
    671681    {
     
    685695//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post id=' . $post_id);
    686696        $post_data = $model->build_sync_data($post_id);
    687         $this->_post_data = &$post_data;                                // save a copy for the gutenberg_taxonomy_block() method
     697        $this->_post_data = &$post_data;        // save a copy for the gutenberg_taxonomy_block() method
    688698//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post data: ' . var_export($post_data, TRUE));
    689699        if (0 === count($post_data))
     
    711721        if (isset($post_data['thumbnail']))
    712722            $data['thumbnail'] = $post_data['thumbnail'];
    713         $this->_post_data = &$data;                         // reset reference to new data
     723        $data['gmt_offset'] = get_option('gmt_offset', ''); // include GMT offset in API data #278
     724        $this->_post_data = &$data;    // reset reference to new data
    714725        $this->post_id = $post_id;
    715         $this->id_refs = array();                           // init list of reference IDs
    716 
     726        $this->id_refs = array();      // init list of reference IDs
    717727        // parse images from source only
    718728        $res = $this->_parse_media($post_id, $post_data['post_data']['post_content']);
     
    722732
    723733        // parse for Shortcodes #102
    724         $res = $this->_parse_shortcodes($post_id, $post_data['post_data']['post_content'], $data);
    725 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' after _parse_shortcodes() res=' . var_export($res, TRUE));
     734        $res = $this->parse_shortcodes($post_id, $post_data['post_data']['post_content'], $data);
     735SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' after parse_shortcodes() res=' . var_export($res, TRUE));
    726736        if (is_wp_error($res))
    727737            return $res;
     
    733743
    734744        $data = apply_filters('spectrom_sync_api_push_content', $data, $this);
    735         $this->_post_data = &$data;                         // reset reference to new data
    736 
     745        $this->_post_data = &$data;    // reset reference to new data
    737746        // after all processing, check for performing 'push_complete' API call
    738 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' all content processed; id refs=' . count($this->id_refs) . ' sent images=' . count($this->_sent_images) . ' trigger=' . ($this->_trigger_push_complete ? 'TRUE' : 'FALSE'));
     747SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' all content processed; id refs=' . count($this->id_refs) . ' sent images=' . count($this->_sent_images) . ' trigger=' . ($this->_trigger_push_complete ? 'TRUE' : 'FALSE'));
    739748        // there are IDs to update, use the 'push_complete' API to indicate completion and update IDs on Target
    740749        // TODO: check ApiResponse instance for has_errors()
    741750        if ($this->_trigger_push_complete ||
    742751            (0 !== count($this->id_refs) || 0 !== count($this->_sent_images))) {
    743             $this->trigger_push_complete();                                     // indicate that 'push_complete' API is requred
     752            $this->trigger_push_complete();       // indicate that 'push_complete' API is requred
    744753        }
    745754
     
    775784        unset($data['contents']);
    776785        /**
    777         array (
    778             'username' =>
    779             'password' =>
    780             'host' =>
    781             'auth' =>
    782             array (
    783             'cookie' =>
    784             'nonce' =>
    785             'site_key' =>
    786         )
     786          array (
     787          'username' =>
     788          'password' =>
     789          'host' =>
     790          'auth' =>
     791          array (
     792          'cookie' =>
     793          'nonce' =>
     794          'site_key' =>
     795          )
    787796         */
    788797        $headers = array(
     
    842851        // TODO: we'll need to add the media sizes on the Source to the data being sent so the Target can generate image sizes
    843852
    844         if (empty($content)) {  // need to continue even with empty content. otherwise featured image doesn't get processed
    845             $xml = NULL;        // use this to denote empty content and skip looping through <img> and <a> tags #180
     853        if (empty($content)) { // need to continue even with empty content. otherwise featured image doesn't get processed
     854            $xml = NULL;  // use this to denote empty content and skip looping through <img> and <a> tags #180
    846855SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' content is empty, not searching <img> and <a> tags');
    847856        } else {
     
    852861                // TODO: can we use get_media_embedded_in_content()?
    853862                libxml_clear_errors();
    854                 libxml_use_internal_errors(TRUE);       // disable the error messages generated from unrecognized DOM elements
     863                libxml_use_internal_errors(TRUE);  // disable the error messages generated from unrecognized DOM elements
    855864                $xml = new DOMDocument();
    856865
     
    861870                $xml->loadHTML($content);
    862871            } catch (Exception $ex) {
    863                 $xml = NULL;    // any errors in parsing; mark it as empty content so processing continues #180
     872                $xml = NULL; // any errors in parsing; mark it as empty content so processing continues #180
    864873            }
    865874        }
     
    868877        $post_thumbnail_id = abs(get_post_thumbnail_id($post_id));
    869878SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' post thumb id=' . $post_thumbnail_id);
    870         $this->_sent_images = array();      // list of images already sent. Used by _send_image() to not send the same image twice
     879        $this->_sent_images = array();  // list of images already sent. Used by _send_image() to not send the same image twice
    871880        // set source domain; used to detect media elements to be added to push queue
    872         $this->set_source_domain(site_url('url'));
     881        $this->set_source_domain();
    873882
    874883        // used in processing <a> tags. Don't need to do this if content is empty #180
     
    884893            $post_children = NULL;
    885894//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' children=' . var_export($post_children, TRUE));
    886 
    887895        // only used in processing <img> tags. Don't need to do this if content is empty #180
    888896        $attach_model = new SyncAttachModel();
     
    890898
    891899        // search for <img> tags within content
    892         if (NULL !== $xml) {        // don't look for <img> elements if content is empty #180
     900        if (NULL !== $xml) {  // don't look for <img> elements if content is empty #180
    893901            $tags = $xml->getElementsByTagName('img');
    894902SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found ' . $tags->length . ' <img> tags');
     
    918926                                $src_attr = NULL;
    919927                        } else {
    920                             $img_id = 0;    // if not valid, clear id to indicate use of fallback method below #162
     928                            $img_id = 0; // if not valid, clear id to indicate use of fallback method below #162
    921929                        }
    922930                        break;
     
    960968
    961969        // search through <a> tags within content
    962         if (NULL !== $xml) {        // don't look for <img> elements if content is empty #180
     970        if (NULL !== $xml) {  // don't look for <img> elements if content is empty #180
    963971            $tags = $xml->getElementsByTagName('a');
    964972SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found ' . $tags->length . ' <a> tags');
     
    983991                        }
    984992                    }
    985                     if (0 !== $attach_id)   // https://wordpress.org/support/topic/bugs-68/
     993                    if (0 !== $attach_id) // https://wordpress.org/support/topic/bugs-68/
    986994                        $this->send_media($href_attr, $post_id, $post_thumbnail_id, $attach_id);
    987995                } else {
     
    9971005                $this->post_children[] = $attachment->ID;
    9981006            }
    999             $this->trigger_push_complete();             // need to force push_complete API to handle attaching post children
     1007            $this->trigger_push_complete(); // need to force push_complete API to handle attaching post children
    10001008        }
    10011009
     
    10041012SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' featured image: ' . $post_thumbnail_id);
    10051013            $img = wp_get_attachment_image_src($post_thumbnail_id, 'full');
    1006 if (FALSE === $img) SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' wp_get_attachment_image_src() failed');    #!#
     1014if (FALSE === $img) SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' wp_get_attachment_image_src() failed');#!#
    10071015            // check image URL and see if it doesn't match Source domain. #131
    10081016SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' source domain: ' . $this->_source_domain);
     
    10161024                }
    10171025            }
    1018 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' src=' . var_export($img, TRUE));
     1026SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' src=' . var_export($img, TRUE));
    10191027            // convert site url to relative path
    10201028            if (FALSE !== $img) {
     
    10591067            } // 0 !== count
    10601068        } // isset
    1061 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' done processing media');
     1069SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' done processing media');
    10621070        return TRUE;
    10631071    }
     
    10701078     * @return boolean|WP_Error return boolean TRUE or WP_Error instance with more information
    10711079     */
    1072     private function _parse_shortcodes($post_id, $content, &$data)
     1080    public function parse_shortcodes($post_id, $content, &$data)
    10731081    {
    10741082        // get a list of all shortcodes to be processed
     
    10791087
    10801088        $num = preg_match_all('/' . $pattern . '/s', $content, $matches);
    1081 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' matches=' . var_export($matches, TRUE));
     1089SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' matches=' . var_export($matches, TRUE));
    10821090        if ($num > 0 && array_key_exists(2, $matches)) {
    10831091            // one or more of the shortcodes is found within the content
    10841092            $idx = 0;
    1085             foreach ($matches[2] as $shortcode) {                           // examine each shortcode
     1093            foreach ($matches[2] as $shortcode) {      // examine each shortcode
    10861094                $sce = new SyncShortcodeEntry($shortcode, $matches[0][$idx], $matches[3][$idx]);
    10871095
    10881096                // check all known attributes to see if they need processing
    10891097                foreach ($sce->parse_attributes($shortcodes[$shortcode]) as $type_name => $type_code) {
    1090                     if ($sce->has_attribute($type_name)) {              // if the current shortcode has the mentioned attribute
     1098                    if ($sce->has_attribute($type_name)) {  // if the current shortcode has the mentioned attribute
    10911099                        // TODO: the [gallery include= exclude=] list to do nothing on Source, update IDs on Target
    10921100                        switch ($type_code) {
    10931101                        case SyncShortcodeEntry::TYPE_IMAGE_ID:
    10941102                        case SyncShortcodeEntry::TYPE_POST_ID:
    1095                             // TODO: push all attachments of this post ID
     1103                        // TODO: push all attachments of this post ID
    10961104                        case SyncShortcodeEntry::TYPE_IMAGE_LIST:
    10971105                        case SyncShortcodeEntry::TYPE_POST_LIST:
     
    10991107                            $ids = trim($ids, '"\'');
    11001108                            $id_list = explode(',', $ids);
    1101                             foreach ($id_list as $id) {             // look up all IDs referenced by the attribute
     1109                            foreach ($id_list as $id) { // look up all IDs referenced by the attribute
    11021110                                $id = abs($id);
    1103 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' found post reference: ' . $id);
    1104                                 if (0 !== $id ) {
    1105 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' handling post ID ' . $id);
     1111SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found post reference: ' . $id);
     1112                                if (0 !== $id) {
     1113SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' handling post ID ' . $id);
    11061114                                    if (SyncShortcodeEntry::TYPE_IMAGE_ID === $type_code || SyncShortcodeEntry::TYPE_IMAGE_LIST === $type_code) {
    11071115                                        // id refers to an image- make sure image gets pushed
    1108 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' calling send_media_by_id(' . $id . ')');
     1116SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' calling send_media_by_id(' . $id . ')');
    11091117                                        $this->send_media_by_id($id);
    11101118                                    } else if (SyncShortcodeEntry::TYPE_POST_ID === $type_code || SyncShortcodeEntry::TYPE_POST_LIST === $type_code) {
    11111119                                        // id refers to a post- give Target site a change to update them
    1112 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' signaling "push_complete" is required');
    1113                                         $this->_trigger_push_complete = TRUE;       // signal 'push_complete' is required
     1120SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' signaling "push_complete" is required');
     1121                                        $this->_trigger_push_complete = TRUE;  // signal 'push_complete' is required
    11141122                                    } else {
    1115 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' type code=' . $type_code);
     1123SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' type code=' . $type_code);
    11161124                                    }
    11171125                                }
     
    11201128
    11211129                        case SyncShortcodeEntry::TYPE_POST_ATTACH:
    1122                             $id = abs($sce->get_attribute($type_name));         // post ID to use for gallery
    1123 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' check gallery id reference ' . $id);
     1130                            $id = abs($sce->get_attribute($type_name));   // post ID to use for gallery
     1131SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' check gallery id reference ' . $id);
    11241132                            if (0 !== $id) {
    11251133                                $model = new SyncModel();
    11261134                                $sync_data = $model->get_sync_data($id);
    11271135                                if (NULL === $sync_data) {
    1128 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' dependent post id ' . $id . ' has not been pushed');
     1136SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' dependent post id ' . $id . ' has not been pushed');
    11291137                                    $this->_response->error_code(self::ERROR_UNRESOLVED_PARENT, $id);
    11301138                                } else {
     
    11321140                                    // are sent to the Target
    11331141                                    $attachments = get_children($id, OBJECT);
    1134 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' attachments: ' . var_export($attachments, TRUE));
     1142SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' attachments: ' . var_export($attachments, TRUE));
    11351143                                    foreach ($attachments as $attachment) {
    11361144                                        $attach_id = $attachment->ID;
    1137 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' adding attachment id #'. $attach_id . ': ' . var_export($attachment, TRUE));
     1145SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' adding attachment id #' . $attach_id . ': ' . var_export($attachment, TRUE));
    11381146                                        $this->send_media_by_id($attach_id);
    11391147                                    }
     
    11591167
    11601168                        default:
    1161 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' unrecognized attribute type code: ' . $type_code);
     1169SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' unrecognized attribute type code: ' . $type_code);
    11621170                            break;
    11631171                        } // switch ($type_code)
     
    11961204        // <!-- wp:file {"id":{post_id},"href":"{file-uri}"} -->
    11971205        // <!-- wp:media-text {"mediaId":{post_id},"mediaType":"video"} -->
    1198 
    11991206        // don't bother processing if there's no Gutenberg content
    12001207        if (function_exists('has_blocks') && !has_blocks($content))
     
    12031210            return;
    12041211
     1212        if (NULL === $this->_source_domain)
     1213            $this->set_source_domain();
     1214
    12051215        $post_thumbnail_id = abs(get_post_thumbnail_id($post_id)); // needed for send_media() calls
    1206         $attach_model = new SyncAttachModel();  // used for regex image search #211
    1207 
    1208         $this->gutenberg_queue = array();           // init work queueu
    1209         $this->gutenberg_processed = array();       // init processed queue
     1216        $attach_model = new SyncAttachModel();  // used for regex image search #211
     1217
     1218        $this->gutenberg_queue = array();   // init work queueu
     1219        $this->gutenberg_processed = array();  // init processed queue
    12101220        $this->gutenberg_add_queue($post_id);
    1211         $error = FALSE;             // set initial error condition
     1221        $error = FALSE; // set initial error condition
    12121222        // Process all items in queue. During processing Shared Blocks are added to queue
    12131223        // so that image references within those Blocks can also be checked.
     
    12171227                $content = $work_post->post_content;
    12181228            }
    1219             $len = strlen($content);        // length of content
    1220             $offset = 0;            // pointer into string for where search currently is
     1229            $len = strlen($content);  // length of content
     1230            $offset = 0;   // pointer into string for where search currently is
    12211231SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' starting work on ID ' . $work_post_id . ' with ' . $len . ' bytes of content');
    12221232            do {
     
    12251235                if (FALSE !== $pos) {
    12261236                    // found a beginning marker: "<!-- wp:"
    1227                     $ref_id = 0;        // initialize reference ID value
     1237                    $ref_id = 0;  // initialize reference ID value
    12281238
    12291239                    $pos_space = strpos($content, ' ', $pos + 5); // space after block name
     
    12421252                            --$end;
    12431253//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' end=' . $end . ' [' . substr($content, $end - 3, 10) . ']');
    1244                         $json = NULL;       // initialize decoded json object
     1254                        $json = NULL;  // initialize decoded json object
    12451255                        if (FALSE !== $end) {
    12461256                            $end -= 2;
    12471257                            $json = substr($content, $start, $end - $start + 1);
    12481258//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' json=[' . $json . ']');
    1249                             if (empty($json))       // if json string is empty
    1250                                 $json = NULL;       // reset to NULL
     1259                            if (empty($json))  // if json string is empty
     1260                                $json = NULL;  // reset to NULL
    12511261                        } else {
    1252                             SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' could not find end of block marker. off=' . $offset . ' data=' . substr($content, $start, 30));
     1262SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' could not find end of block marker. off=' . $offset . ' data=' . substr($content, $start, 30));
    12531263                        }
    12541264//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' json=[' . $json . ']');
    12551265                        // if there is json data, decode and process it
    12561266                        if (NULL !== $json) {
    1257                             $obj = json_decode($json);
     1267                            $obj = $this->gutenberg_obj = json_decode($json);
    12581268SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found json string: "' . $json . '"');
    12591269
    1260                             // TODO: need to add error checking when referenced IDs are missing, etc.
     1270                            $process = TRUE;
     1271                            // check .url property and skip if it references s.w.org #279
     1272                            if (property_exists($obj, 'url')) {
     1273                                $url_host = parse_url($obj->url, PHP_URL_HOST);
     1274                                // TODO: check if host !== Source host
     1275                                if ($url_host !== $this->_source_domain)
     1276                                // the URL refers to s.w.org or some other non-Source domain
     1277                                // skip these so we don't update Patterns, etc.
     1278                                    $process = FALSE;
     1279                                // TODO: check for additional references that need to be skipped
     1280SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' process block=' . ($process ? 'TRUE' : 'FALSE'));
     1281                            }
     1282//$process = TRUE;
    12611283                            // TODO: how does Gutenberg deal with these ID references if they've been deleted after the ID is written to the Block Marker?
    1262                             // handle each Block Marker individually
    1263                             switch ($block_name) {
    1264                             case 'wp:block':        // Shared Block reference - post reference
    1265                                 $ref_id = abs($obj->ref);
    1266                                 $this->gutenberg_add_queue($ref_id); // add Shared Block post ID to the work queue
     1284                            // check to see if this block should be processed
     1285                            if ($process) {
     1286                                // handle each Block Marker individually
     1287                                switch ($block_name) {
     1288                                case 'wp:block':  // Shared Block reference - post reference
     1289                                    $ref_id = isset($obj->ref) ? abs($obj->ref) : 0;
     1290                                    if (0 !== $ref_id) {
     1291                                        $this->gutenberg_add_queue($ref_id); // add Shared Block post ID to the work queue
    12671292//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' found shared block reference: ' . $ref_id . ' at pos ' . ($pos + 21));
    1268                                 if (0 !== $ref_id && !isset($this->id_refs[$ref_id])) {
    1269                                     // ID has not already been processed (no duplicates)
    1270                                     $ref_post = get_post($ref_id, ARRAY_A);
    1271                                     $this->id_refs[$ref_id] = array($block_name, $ref_post);
     1293                                        if (0 !== $ref_id && !isset($this->id_refs[$ref_id])) {
     1294                                            // ID has not already been processed (no duplicates)
     1295                                            $ref_post = get_post($ref_id, ARRAY_A);
     1296                                            $this->id_refs[$ref_id] = array($block_name, $ref_post);
    12721297SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found reference to post id ' . $ref_id . ' "' . $ref_post['post_title'] . '"');
    1273                                 }
    1274                                 // TODO: error recovery
    1275                                 break;
    1276 
    1277                             case 'wp:cover':        // Cover Block - image reference
    1278                                 if (FALSE === $this->gutenberg_attachment_block($obj->id, $work_post_id, $post_thumbnail_id, $block_name)) {
    1279                                     // TODO: handle error recovery
    1280                                 }
    1281                                 break;
    1282 
    1283                             case 'wp:audio':        // Audio Block- resource reference
    1284                             case 'wp:video':        // Video Block- resource reference
    1285                             case 'wp:image':        // Image Block- resource reference
    1286                                 if (FALSE === $this->gutenberg_attachment_block($obj->id, $work_post_id, $post_thumbnail_id, $block_name)) {
    1287                                     // TODO: handle error recovery
    1288                                 }
    1289                                 break;
    1290 
    1291                             case 'wp:media-text':
     1298                                        }
     1299                                    }
     1300                                    // TODO: error recovery
     1301                                    break;
     1302
     1303                                case 'wp:cover':  // Cover Block - image reference
     1304                                    // check for NULL references in "Pattern" blocks. these can be skipped #280
     1305                                    if (isset($obj->id) && NULL !== $obj->id) {
     1306SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' wp:cover id=' . $obj->id . ' post id=' . $word_post_id);
     1307                                        if (FALSE === $this->gutenberg_attachment_block($obj->id, $work_post_id, $post_thumbnail_id, $block_name)) {
     1308                                            // attachment not found, report error
     1309SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error handling cover block id ' . $obj->id . ' in block "' . $block_name . '"');
     1310                                            $this->_response->error_code(self::ERROR_GB_ATTACHMENT_ID, $block_name);
     1311                                        }
     1312                                    }
     1313                                    break;
     1314
     1315                                case 'wp:audio':  // Audio Block- resource reference
     1316                                case 'wp:video':  // Video Block- resource reference
     1317                                case 'wp:image':  // Image Block- resource reference
     1318                                    // check for NULL references in "Pattern" blocks. these can be skipped #280
     1319                                    $obj_id = isset($obj->id) ? abs($obj->id) : 0;
     1320                                    if (0 !== $obj_id) {
     1321                                        if (FALSE === $this->gutenberg_attachment_block($obj_id, $work_post_id, $post_thumbnail_id, $block_name)) {
     1322                                            // attachment not found, report error
     1323SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error handling attachment block id ' . $obj_id . ' in block "' . $block_name . '"');
     1324                                            $this->_response->error_code(self::ERROR_GB_ATTACHMENT_ID, $block_name);
     1325                                        }
     1326                                    }
     1327                                    break;
     1328
     1329                                case 'wp:media-text':
     1330                                    // check for NULL references in "Pattern" blocks. these can be skipped #280
     1331                                    if (isset($obj->mediaId) && NULL !== $obj->mediaId) {
    12921332SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' mediaID=' . $obj->mediaId);
    1293                                 if (FALSE === $this->gutenberg_attachment_block($obj->mediaId, $work_post_id, $post_thumbnail_id, $block_name)) {
    1294 SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error handling attachment block id=' . $obj->mediaID);
    1295                                     // TODO: handle error recovery
    1296                                 }
    1297                                 break;
    1298 
    1299                             case 'wp:gallery':      // Gallery Block- multiple image references
    1300                                 $ref_ids = $obj->ids;
    1301 SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found reference ids=' . implode(', ', $ref_ids));
    1302                                 foreach ($ref_ids as $ref_id) {
    1303                                     if (FALSE === $this->gutenberg_attachment_block($ref_id, $work_post_id, $post_thumbnail_id, $block_name)) {
    1304                                         // TODO: handle error
     1333                                        if (FALSE === $this->gutenberg_attachment_block($obj->mediaId, $work_post_id, $post_thumbnail_id, $block_name)) {
     1334                                            // attachment not found, report error
     1335SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error handling attachment block id ' . $obj->mediaID . ' in block "' . $block_name . '"');
     1336                                            $this->_response->error_code(self::ERROR_GB_ATTACHMENT_ID, $block_name);
     1337                                        }
    13051338                                    }
    1306                                 }
    1307                                 break;
    1308 
    1309                             case 'wp:file':         // File Block- resource reference
    1310                                 $ref_id = abs($obj->id);
    1311                                 if ($this->gutenberg_attachment_block($ref_id, $work_post_id, $post_thumbnail_id, $block_name)) {
    1312                                     $ref_post = get_post($ref_id);
    1313                                     if (NULL !== $ref_post) {
     1339                                    break;
     1340
     1341                                case 'wp:gallery':  // Gallery Block- multiple image references
     1342                                    $ref_ids = isset($obj->ids) ? $obj->ids : array();
     1343SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' found reference ids=' . var_export($ref_ids, TRUE)); // implode(', ', $ref_ids));
     1344                                    foreach ($ref_ids as $ref_id) {
     1345SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' checking id=' . var_export($ref_id, TRUE));
     1346                                        // check for NULL references in "Pattern" blocks. these can be skipped #280
     1347                                        if (NULL !== $ref_id) {
     1348                                            if (FALSE === $this->gutenberg_attachment_block($ref_id, $work_post_id, $post_thumbnail_id, $block_name)) {
     1349                                                // attachment not found, report error
     1350SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error findling attachment block id ' . $ref_id . ' in block "' . $block_name . '"');
     1351                                                $this->_response->error_code(self::ERROR_GB_ATTACHMENT_ID, $block_name);
     1352                                            }
     1353                                        }
     1354                                    }
     1355                                    break;
     1356
     1357                                case 'wp:file':   // File Block- resource reference
     1358                                    $ref_id = isset($obj->id) ? abs($obj->id) : 0;
     1359                                    if (0 !== $ref_id && $this->gutenberg_attachment_block($ref_id, $work_post_id, $post_thumbnail_id, $block_name)) {
     1360                                        $ref_post = get_post($ref_id);
     1361                                        if (NULL !== $ref_post) {
    13141362SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' adding reference id ' . $ref_id . ' post ' . var_export($ref_post, TRUE));
    1315                                         $this->id_refs[$ref_id] = array($block_name, $ref_post);
     1363                                            $this->id_refs[$ref_id] = array($block_name, $ref_post);
     1364                                        }
     1365                                    } else {
     1366                                        // attachment not found, report error
     1367SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error finding attachment block id ' . $ref_id . ' in block "' . $block_name . '"');
     1368                                        $this->_response->error_code(self::ERROR_GB_ATTACHMENT_ID, $block_name);
    13161369                                    }
    1317                                 } else {
    1318                                     // TODO: handle error recovery
    1319                                 }
    1320                                 break;
    1321 
    1322                             case 'wp:latest-posts':
    1323                                 $cat_id = abs($obj->categories);
    1324                                 if (0 !== $cat_id) {
    1325                                     if (FALSE === $this->gutenberg_taxonomy_block($cat_id, $post_id, $block_name)) {
    1326                                         // TODO: error recovery
     1370                                    break;
     1371
     1372                                case 'wp:latest-posts':
     1373                                    $cat_id = isset($obj->categories) ? abs($obj->categories) : 0;
     1374                                    if (0 !== $cat_id) {
     1375                                        if (FALSE === $this->gutenberg_taxonomy_block($cat_id, $post_id, $block_name)) {
     1376                                            // taxonomy not found, report error
     1377SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' error finding taxonomy id ' . $cat_id . ' in "' . $block_name . '"');
     1378                                            $this->_response->error_code(self::ERROR_GB_TAX_ID, $block_name);
     1379                                        }
     1380                                        $this->trigger_push_complete(); // indicate that 'push_complete' API is requred
    13271381                                    }
    1328                                     $this->trigger_push_complete(); // indicate that 'push_complete' API is requred
    1329                                 }
    1330                                 break;
    1331 
    1332                             default:
    1333 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' calling do_action "spectrom_sync_parse_gutenberg_block"');
    1334                                 // give other add-ons a chance to process this block
    1335                                 do_action('spectrom_sync_parse_gutenberg_block', $block_name, $json, $post_id, $data, $pos, $this);
    1336                                 break;
    1337                             } // switch ($block_name)
     1382                                    break;
     1383
     1384                                default:
     1385SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' calling do_action "spectrom_sync_parse_gutenberg_block"');
     1386                                    // give other add-ons a chance to process this block
     1387                                    do_action('spectrom_sync_parse_gutenberg_block', $block_name, $json, $post_id, $data, $pos, $this);
     1388                                    break;
     1389                                } // switch ($block_name)
     1390                            } // $process
    13381391                        } else {
    13391392SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' could not parse json object');
    13401393                        } // NULL !== $json
    1341                         $offset = $end + 3; // move offset to the end of the Block Marker comment
     1394                        $offset = $end + 3; // move offset to the end of the Block Marker comment
    13421395                    } else {
    13431396SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' no json object in block');
     
    14241477    public function gutenberg_taxonomy_block($term_id, $post_id, $block_name)
    14251478    {
    1426 SyncDebug::log(__METHOD__."({$term_id}, {$post_id}, '{$block_name}')");
     1479SyncDebug::log(__METHOD__ . "({$term_id}, {$post_id}, '{$block_name}')");
    14271480        $term = get_term($term_id);
    1428 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' term=' . var_export($term, TRUE));
     1481SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' term=' . var_export($term, TRUE));
    14291482        if (NULL !== $term && !is_wp_error($term)) {
    14301483            $tax = get_taxonomy($term->taxonomy);
    1431 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' tax=' . var_export($tax, TRUE));
     1484SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' tax=' . var_export($tax, TRUE));
    14321485            if (FALSE !== $tax) {
    1433 SyncDebug::log(__METHOD__.'():'.__LINE__ . ' term=' . var_export($term, TRUE));
     1486SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' term=' . var_export($term, TRUE));
    14341487                if ($tax->hierarchical) {
    1435 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' hierarchical taxonomy');
     1488SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' hierarchical taxonomy');
    14361489                    // handle the hierarchical taxonomy stuff
    1437                     $term->ref_only = 1;    // signifies it's a term reference, not to be assigned to the post via wp_add_object_terms()
     1490                    $term->ref_only = 1; // signifies it's a term reference, not to be assigned to the post via wp_add_object_terms()
    14381491                    $this->_post_data['taxonomies']['hierarchical'][] = $term;
    14391492
     
    14451498                        $parent = $term->parent;
    14461499                    }
    1447 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' added term to [taxonomy][hierarchical] list: ' . var_export($this->_post_data['taxonomies']['hierarchical'], TRUE));
     1500SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' added term to [taxonomy][hierarchical] list: ' . var_export($this->_post_data['taxonomies']['hierarchical'], TRUE));
    14481501                } else {
    1449 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' non-hierarchical taxonomy');
    1450                     $term->ref_only = 1;    // signifies it's a term reference, not to be assigned to the post via wp_add_object_terms()
     1502SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' non-hierarchical taxonomy');
     1503                    $term->ref_only = 1; // signifies it's a term reference, not to be assigned to the post via wp_add_object_terms()
    14511504                    $this->_post_data['taxonomies']['flat'][] = $term;
    1452 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' add term to [taxonomy][flat] list: ' . var_export($this->_post_data['taxonomies']['flat'], TRUE));
     1505SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' add term to [taxonomy][flat] list: ' . var_export($this->_post_data['taxonomies']['flat'], TRUE));
    14531506                }
    14541507                return TRUE;
    14551508            }
    1456 SyncDebug::log(__METHOD__.'():'.__LINE__ . ' tax data=' . var_export($this->_post_data['taxonomies'], TRUE));
     1509SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' tax data=' . var_export($this->_post_data['taxonomies'], TRUE));
    14571510        }
    14581511        return FALSE;
     
    14811534SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' adding "push_complete" callback');
    14821535            add_action('spectrom_sync_push_queue_complete', array($this, 'push_complete_api'), 10, 1);
    1483             $this->_triggered_push_complete = TRUE;                             // indicate this has been triggered
     1536            $this->_triggered_push_complete = TRUE;     // indicate this has been triggered
    14841537        }
    14851538    }
     
    14941547        // we've sent image/content references to the Target- need to send process compelte API call to Target
    14951548        $api_data = array(
    1496             'post_id' => $this->post_id,            // the Source Post ID to be updated
    1497             'id_refs' => $this->id_refs,            // data and Source IDs to update
    1498             'children' => $this->post_children,     // list of attachments as post children
     1549            'post_id' => $this->post_id, // the Source Post ID to be updated
     1550            'id_refs' => $this->id_refs, // data and Source IDs to update
     1551            'children' => $this->post_children, // list of attachments as post children
    14991552        );
    15001553SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' data=' . var_export($api_data, TRUE));
     
    15071560     * @param string $domain The domain name to set. Use domain only, no protocol or slashes
    15081561     */
    1509     public function set_source_domain($domain)
    1510     {
     1562    public function set_source_domain($domain = NULL)
     1563    {
     1564        if (NULL === $domain)
     1565            $domain = site_url('url');  // assume current site's domain name
     1566
    15111567//SyncDebug::log(__METHOD__.'() domain=' . $domain);
    15121568        // sanitize value to remove protocol and slashes
     
    15271583    {
    15281584        $image_id = abs($image_id);
    1529 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' image reference ' . $image_id);
     1585SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' image reference ' . $image_id);
    15301586        if (0 !== $image_id) {
    15311587            $post = get_post($image_id);
    1532 if ('attachment' !== $post->post_type) {
    1533     SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post id ' . $image_id . ' is not an image (' . $post->post_type . ')');
    1534 }
     1588            if ('attachment' !== $post->post_type) {
     1589SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' post id ' . $image_id . ' is not an image (' . $post->post_type . ')');
     1590            }
    15351591            if (NULL !== $post) {
    15361592                $attach = $this->url_to_path($post->guid);
     
    15441600                    return TRUE;
    15451601                } else {
    1546 SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' ERROR adding media file "' . $ref_att . '" to queue');
     1602SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' ERROR adding media file "' . $attach . '" to queue');
    15471603//                  return FALSE;
    15481604                }
     
    15781634
    15791635        $src_parts = parse_url($url);
    1580         if (empty($src_parts['host']))                  // allow for empty host with relative urls #250
     1636        if (empty($src_parts['host']))   // allow for empty host with relative urls #250
    15811637            $src_parts['host'] = '';
    15821638SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' url=' . $url . ' parts=' . var_export($src_parts, TRUE));
     
    16571713            'attach_id' => $attach_id,
    16581714            'featured' => abs($featured),
    1659             'boundary' => wp_generate_password(24), // TODO: remove and generate when formatting POST content in _media()
     1715            // TODO: remove and generate when formatting POST content in _media()
     1716            'boundary' => wp_generate_password(24),
    16601717            'img_path' => dirname($file_path),
    16611718            'img_name' => basename($file_path),
     
    16931750    private function _get_image_contents($file_path)
    16941751    {
    1695 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' path=' . $file_path);
     1752SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' path=' . $file_path);
    16961753
    16971754        // adjust file path if running within multisite #167
     
    16991756            $to_dir = '/wp-content/blogs.dir/' . get_current_blog_id() . '/';
    17001757            $file_path = str_replace('/wp-content/files/', $to_dir, $file_path);
    1701 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' adjusted multisite file path to ' . $file_path);
    1702         }
    1703 
    1704 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' reading file contents: ' . $file_path);
     1758SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' adjusted multisite file path to ' . $file_path);
     1759        }
     1760
     1761SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' reading file contents: ' . $file_path);
    17051762        // TODO: rework to use CURLFile class and @filename specifier (for PHP < 5.5) to save memory on large files #165
    17061763        // first, try file_get_contents()
     
    17191776        }
    17201777
    1721 if (FALSE === $contents)                                                                    #!#
    1722 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' ERROR: unable to obtain image contents');    #!#
    1723 
     1778        if (FALSE === $contents)                 #!#
     1779SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' ERROR: unable to obtain image contents');#!#
    17241780        // TODO: try using filesystem
    17251781
     
    17341790    public function url_to_path($url)
    17351791    {
    1736 SyncDebug::log(__METHOD__."('{$url}'):" . __LINE__);
     1792SyncDebug::log(__METHOD__ . "('{$url}'):" . __LINE__);
    17371793//      $domain = parse_url(site_url(), PHP_URL_HOST);              // local install's domain name
    17381794        $parts = parse_url($url);
    1739 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' url parts=' . var_export($parts, TRUE));
     1795SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' url parts=' . var_export($parts, TRUE));
    17401796        $site_url = site_url();
    17411797        // check for empty elements when referencing relative urls #250
     
    17451801            $parts['scheme'] = parse_url($site_url, PHP_URL_SCHEME);
    17461802        $domain = $parts['host'];
    1747 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' url parts=' . var_export($parts, TRUE));
     1803//SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' url parts=' . var_export($parts, TRUE));
    17481804
    17491805        // adjust path to account for subdirectory installs #223
    17501806        if (0 !== ($pos = stripos($parts['path'], '/wp-content'))) {
    1751             $parts['path'] = substr($parts['path'], $pos);
     1807#226911         $parts['path'] = substr($parts['path'], $pos);
    17521808        }
    17531809SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' domain=' . $domain . ' site=' . $site_url . ' parts=' . var_export($parts, TRUE));
     
    17551811        // if it's a URL reference and on the same host, convert to filesystem path
    17561812        if (('http' === $parts['scheme'] || 'https' === $parts['scheme']) &&
    1757             0 === strcasecmp($domain, $parts['host'])) {    // compare case insignificant #170
     1813            0 === strcasecmp($domain, $parts['host'])) { // compare case insignificant #170
    17581814//          if (!function_exists('get_home_path')) {
    17591815//              require_once ABSPATH . '/wp-admin/file.php';
    17601816//          }
    1761             $home = $this->get_home_path(); // get directory of WP install #187
     1817            $home = $this->get_home_path(); // get directory of WP install #187
    17621818SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' home=' . $home);
    17631819SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' path=' . $parts['path']);
     
    18131869        $error = '';
    18141870        switch ($code) {
    1815         case self::ERROR_CANNOT_CONNECT:            $error = __('Unable to connect to Target site.', 'wpsitesynccontent'); break;
    1816         case self::ERROR_UNRECOGNIZED_REQUEST:      $error = __('The requested action is not recognized. Is plugin activated on Target?', 'wpsitesynccontent'); break;
    1817         case self::ERROR_NOT_INSTALLED:             $error = __('WPSiteSync for Content is not installed and activated on Target site.', 'wpsitesynccontent'); break;
    1818         case self::ERROR_BAD_CREDENTIALS:           $error = __('Unable to authenticate on Target site.', 'wpsitesynccontent'); break;
    1819         case self::ERROR_SESSION_EXPIRED:           $error = __('User session has expired.', 'wpsitesynccontent'); break;
    1820         case self::ERROR_CONTENT_EDITING:           $error = __('A user is currently editing this Content on the Target site.', 'wpsitesynccontent'); break;
     1871        case self::ERROR_CANNOT_CONNECT: $error = __('Unable to connect to Target site.', 'wpsitesynccontent'); break;
     1872        case self::ERROR_UNRECOGNIZED_REQUEST: $error = __('The requested action is not recognized. Is plugin activated on Target?', 'wpsitesynccontent'); break;
     1873        case self::ERROR_NOT_INSTALLED: $error = __('WPSiteSync for Content is not installed and activated on Target site.', 'wpsitesynccontent'); break;
     1874        case self::ERROR_BAD_CREDENTIALS: $error = __('Unable to authenticate on Target site.', 'wpsitesynccontent'); break;
     1875        case self::ERROR_SESSION_EXPIRED: $error = __('User session has expired.', 'wpsitesynccontent'); break;
     1876        case self::ERROR_CONTENT_EDITING: $error = __('A user is currently editing this Content on the Target site.', 'wpsitesynccontent'); break;
    18211877        case self::ERROR_CONTENT_LOCKED:
    18221878            if (NULL !== $data)
     
    18251881                $error - __('A user is currently editing this Content on the Target site.', 'wpsitesynccontent');
    18261882            break;
    1827         case self::ERROR_POST_DATA_INCOMPLETE:      $error = __('Some or all of the data for the request is missing.', 'wpsitesynccontent'); break;
    1828         case self::ERROR_USER_NOT_FOUND:            $error = __('The username does not exist on the Target site.', 'wpsitesynccontent'); break;
    1829         case self::ERROR_FILE_UPLOAD:               $error = __('Error while handling file upload.', 'wpsitesynccontent'); break;
    1830         case self::ERROR_PERMALINK_MISMATCH:        $error = __('The Permalink settings are different on the Target site.', 'wpsitesynccontent'); break;
    1831         case self::ERROR_WP_VERSION_MISMATCH:       $error = __('The WordPress versions are different on the Source and Target sites.', 'wpsitesynccontent'); break;
    1832         case self::ERROR_SYNC_VERSION_MISMATCH:     $error = __('The WPSiteSync versions are different on the Source and Target sites.', 'wpsitesynccontent'); break;
    1833         case self::ERROR_EXTENSION_MISSING:         $error = __('The required WPSiteSync extension is not active on the Target site.', 'wpsitesynccontent'); break;
    1834         case self::ERROR_INVALID_POST_TYPE:         $error = __('The post type is not allowed.', 'wpsitesynccontent'); break;
    1835         case self::ERROR_REMOTE_REQUEST_FAILED:     $error = __('Unable to make API request to Target system.', 'wpsitesynccontent'); break;
     1883        case self::ERROR_POST_DATA_INCOMPLETE: $error = __('Some or all of the data for the request is missing.', 'wpsitesynccontent'); break;
     1884        case self::ERROR_USER_NOT_FOUND: $error = __('The username does not exist on the Target site.', 'wpsitesynccontent'); break;
     1885        case self::ERROR_FILE_UPLOAD: $error = __('Error while handling file upload.', 'wpsitesynccontent'); break;
     1886        case self::ERROR_PERMALINK_MISMATCH: $error = __('The Permalink settings are different on the Target site.', 'wpsitesynccontent'); break;
     1887        case self::ERROR_WP_VERSION_MISMATCH: $error = __('The WordPress versions are different on the Source and Target sites.', 'wpsitesynccontent'); break;
     1888        case self::ERROR_SYNC_VERSION_MISMATCH: $error = __('The WPSiteSync versions are different on the Source and Target sites.', 'wpsitesynccontent'); break;
     1889        case self::ERROR_EXTENSION_MISSING:
     1890            if (NULL === $data && !empty($data))
     1891                $error = __('The required WPSiteSync extension is not active on the Target site.', 'wpsitesynccontent');
     1892            else
     1893                $error = sprintf(__('The required WPSiteSync extension, %1$s, is not active on the Target site.', 'wpsitesynccontent'), $data);
     1894                break;
     1895        case self::ERROR_INVALID_POST_TYPE: $error = __('The post type is not allowed.', 'wpsitesynccontent'); break;
     1896        case self::ERROR_REMOTE_REQUEST_FAILED: $error = __('Unable to make API request to Target system.', 'wpsitesynccontent'); break;
    18361897        case self::ERROR_BAD_POST_RESPONSE:
    18371898            if (NULL !== $data)
     
    18401901                $error = __('Target system did not respond with success code.', 'wpsitesynccontent');
    18411902            break;
    1842         case self::ERROR_MISSING_SITE_KEY:          $error = __('Site Key for Target system has not been obtained.', 'wpsitesynccontent'); break;
    1843         case self::ERROR_POST_CONTENT_NOT_FOUND:    $error = __('Unable to determine post content.', 'wpsitesynccontent'); break;
    1844         case self::ERROR_BAD_NONCE:                 $error = __('Unable to validate AJAX request.', 'wpsitesynccontent'); break;
     1903        case self::ERROR_MISSING_SITE_KEY: $error = __('Site Key for Target system has not been obtained.', 'wpsitesynccontent'); break;
     1904        case self::ERROR_POST_CONTENT_NOT_FOUND: $error = __('Unable to determine post content.', 'wpsitesynccontent'); break;
     1905        case self::ERROR_BAD_NONCE: $error = __('Unable to validate AJAX request.', 'wpsitesynccontent'); break;
    18451906        case self::ERROR_UNRESOLVED_PARENT:
    1846                 if (NULL === $data)
    1847                     $error = __('Content has a dependent Page that has not been Sync\'d. Please Push the Parent page.', 'wpsitesynccontent');
    1848                 else {
    1849                     $post_id = abs($data);
    1850                     $post = get_post($post_id, OBJECT);
    1851                     $error = sprintf(__('Content has a dependent Page that has not been Sync\'d. Please push the #%1$d "%2$s" Content first.', 'wpsitesynccontent'),
    1852                         $post_id, (NULL !== $post ? $post->post_title : ''));
    1853                 }
    1854                 break;
    1855         case self::ERROR_NO_AUTH_TOKEN:             $error = __('Unable to authenticate with Target site. Please re-enter credentials for this site.', 'wpsitesynccontent'); break;
    1856         case self::ERROR_NO_PERMISSION:             $error = __('User does not have permission to perform Sync. Check configured user on Target.', 'wpsitesynccontent'); break;
    1857         case self::ERROR_INVALID_IMG_TYPE:          $error = __('The image uploaded is not a valid image type.', 'wpsitesynccontent'); break;
    1858         case self::ERROR_POST_NOT_FOUND:            $error = __('Requested post cannot be found on Target.', 'wpsitesynccontent'); break;
     1907            if (NULL === $data)
     1908                $error = __('Content has a dependent Page that has not been Sync\'d. Please Push the Parent page.', 'wpsitesynccontent');
     1909            else {
     1910                $post_id = abs($data);
     1911                $post = get_post($post_id, OBJECT);
     1912                $error = sprintf(__('Content has a dependent Page that has not been Sync\'d. Please push the #%1$d "%2$s" Content first.', 'wpsitesynccontent'),
     1913                    $post_id, (NULL !== $post ? $post->post_title : ''));
     1914            }
     1915            break;
     1916        case self::ERROR_NO_AUTH_TOKEN: $error = __('Unable to authenticate with Target site. Please re-enter credentials for this site.', 'wpsitesynccontent'); break;
     1917        case self::ERROR_NO_PERMISSION: $error = __('User does not have permission to perform Sync. Check configured user on Target.', 'wpsitesynccontent'); break;
     1918        case self::ERROR_INVALID_IMG_TYPE: $error = __('The image uploaded is not a valid image type.', 'wpsitesynccontent'); break;
     1919        case self::ERROR_POST_NOT_FOUND: $error = __('Requested post cannot be found on Target.', 'wpsitesynccontent'); break;
    18591920        case self::ERROR_CONTENT_UPDATE_FAILED:
    18601921            if (NULL === $data)
     
    18631924                $error = sprintf(__('Content update on Target failed: %1$s.', 'wpsitesynccontent'), $data);
    18641925            break;
    1865         case self::ERROR_CANNOT_WRITE_TOKEN:        $error = __('Cannot write authentication token.', 'wpsitesynccontent'); break;
    1866         case self::ERROR_UPLOAD_NO_CONTENT:         $error = __('Attachment upload failed. No content found; is there a broken link?', 'wpsitesynccontent'); break;
    1867         case self::ERROR_PHP_ERROR_ON_TARGET:       $error = __('A PHP error occurred on Target while processing your request. Examine log files for more information.', 'wpsitesynccontent'); break;
    1868         case self::ERROR_SSL_PROTOCOL_ERROR:        $error = __('Unknown SSL protocol error on Target. Check DNS settings on Target domain.', 'wpsitesynccontent'); break;
    1869         case self::ERROR_WORDFENCE_BLOCKED:         $error = __('Wordfence has blocked the Push operation. Try Learning Mode.', 'wpsitesyncontent'); break;
    1870         case self::ERROR_MODSECURITY_BLOCKED:       $error = __('Mod_Security has blocked the Push operation. Try adding Source site to white list.', 'wpsitesynccontent'); break;
    1871         case self::ERROR_CLOUDFLARE_BLOCKED:        $error = __('Cloudflare has blocked the Push operation. Try adding Source site to white list.', 'wpsitesynccontent'); break;
    1872         case self::ERROR_CLOUDFRONT_BLOCKED:        $error = __('CloudFront has blocked the Push operation. Try adding Source site to white list.', 'wpsitesynccontent'); break;
    1873         case self::ERROR_INVALID_RESPONSE_TYPE:     $error = sprintf(__('Target site responded with non-JSON content type: %1$s.', 'wpsitesynccontent'), $data); break;
    1874         case self::ERROR_UNRECOGNIZED_OPERATION:    $error = __('WPSiteSync operation is not recognized.', 'wpsitesynccontent'); break;
    1875 
     1926        case self::ERROR_CANNOT_WRITE_TOKEN: $error = __('Cannot write authentication token.', 'wpsitesynccontent'); break;
     1927        case self::ERROR_UPLOAD_NO_CONTENT: $error = __('Attachment upload failed. No content found; is there a broken link?', 'wpsitesynccontent'); break;
     1928        case self::ERROR_PHP_ERROR_ON_TARGET: $error = __('A PHP error occurred on Target while processing your request. Examine log files for more information.', 'wpsitesynccontent'); break;
     1929        case self::ERROR_SSL_PROTOCOL_ERROR: $error = __('Unknown SSL protocol error on Target. Check DNS settings on Target domain.', 'wpsitesynccontent'); break;
     1930        case self::ERROR_WORDFENCE_BLOCKED: $error = __('Wordfence has blocked the Push operation. Try Learning Mode.', 'wpsitesyncontent'); break;
     1931        case self::ERROR_MODSECURITY_BLOCKED: $error = __('Mod_Security has blocked the Push operation. Try adding Source site to white list.', 'wpsitesynccontent'); break;
     1932        case self::ERROR_CLOUDFLARE_BLOCKED: $error = __('Cloudflare has blocked the Push operation. Try adding Source site to white list.', 'wpsitesynccontent'); break;
     1933        case self::ERROR_CLOUDFRONT_BLOCKED: $error = __('CloudFront has blocked the Push operation. Try adding Source site to white list.', 'wpsitesynccontent'); break;
     1934        case self::ERROR_INVALID_RESPONSE_TYPE: $error = sprintf(__('Target site responded with non-JSON content type: %1$s.', 'wpsitesynccontent'), $data); break;
     1935        case self::ERROR_UNRECOGNIZED_OPERATION: $error = __('WPSiteSync operation is not recognized.', 'wpsitesynccontent'); break;
     1936        case self::ERROR_MISSING_TOKEN: $error = __('Missing Authentication Token.', 'wpsitesynccontent'); break;
     1937        case self::ERROR_MISSING_USER: $error = __('Athenticating user does not exist.', 'wpsitesynccontent'); break;
     1938        case self::ERROR_GB_ATTACHMENT_ID: $error = sprintf(__('Attachment ID could not be found in block "%1$s".', 'wpsitesynccontent'), $data); break;
     1939        case self::ERROR_GB_TAX_ID: $error = sprintf(__('Taxonomy ID could not be found in block "%1$s".', 'wpsitesynccontent'), $data); break;
    18761940        default:
    18771941            $error = apply_filters('spectrom_sync_error_code_to_text', sprintf(__('Unrecognized error: %d', 'wpsitesynccontent'), $code), $code, $data);
     
    18921956        $notice = '';
    18931957        switch ($code) {
    1894         case self::NOTICE_FILE_EXISTS:      $notice = __('The file name already exists.', 'wpsitesynccontent'); break;
    1895         case self::NOTICE_CONTENT_SYNCD:    $notice = __('Content Synchronized.', 'wpsitesynccontent'); break;
    1896         case self::NOTICE_INTERNAL_ERROR:   $notice = __('Internal error:', 'wpsitesynccontent'); break;
     1958        case self::NOTICE_FILE_EXISTS: $notice = __('The file name already exists.', 'wpsitesynccontent'); break;
     1959        case self::NOTICE_CONTENT_SYNCD: $notice = __('Content Synchronized.', 'wpsitesynccontent'); break;
     1960        case self::NOTICE_INTERNAL_ERROR: $notice = __('Internal error:', 'wpsitesynccontent'); break;
    18971961        default:
    18981962            $notice = apply_filters('spectrom_sync_notice_code_to_text',
     
    19291993        return $this->_response;
    19301994    }
     1995
    19311996}
    19321997
  • wpsitesynccontent/trunk/classes/apiresponse.php

    r2321211 r2491116  
    291291    public function send($exit = TRUE)
    292292    {
     293        // TODO: use wp_send_json()?
     294
    293295        if ($this->nosend)
    294296            return;
     
    320322            if (NULL !== $this->request_id)
    321323                header(self::HEADER_SYNC_REQUEST_ID . ': ' . $this->request_id);                // send this header so Sources get feedback with the ID the request was made with
    322 else SyncDebug::log(__METHOD__.'():' . __LINE__ . ' no request id found');
     324else SyncDebug::log(__METHOD__.'():' . __LINE__ . ' no request id found');      #!#
    323325
    324326            header('Content-Type: application/json');
  • wpsitesynccontent/trunk/classes/auth.php

    r2325466 r2491116  
    2121//SyncDebug::log(__METHOD__.'()');
    2222        $info = array();
    23 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post data=' . var_export($_POST, TRUE));
     23//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post data=' . SyncDebug::arr_sanitize($_POST));
    2424        $username = $this->post('username', NULL);
    2525        $password = $this->post('password', NULL);
    2626        $token = $this->post('token', NULL);
    27 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' user=' . $username . ' pass=' . strlen($password) . ' characters token=' . substr($token, -16));
     27//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' user=' . $username . ' password=' . strlen($password) . ' characters token=' . substr($token, -16));
    2828//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' post= ' . SyncDebug::arr_sanitize($_POST));
     29//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' password=' . $password);
    2930        $source_model = new SyncSourcesModel();
    3031        $api_controller = SyncApiController::get_instance();
     
    4243        } else {
    4344            $info['user_login'] = $username;
    44 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - target: ' . get_bloginfo('wpurl'));
    45             $info['user_password'] = $this->decode_password($password, get_bloginfo('wpurl'));
     45//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' target: ' . get_bloginfo('wpurl'));
     46
     47            $target = get_bloginfo('wpurl');
     48            if (isset($_POST['host']))
     49                $target = $_POST['host'];
     50            $info['user_password'] = $this->decode_password($password, $target);
    4651            $info['remember'] = FALSE;
    4752
     
    6772            $error_code = 0;
    6873
    69             // handle nonSyncAuthError instances #
     74            // handle nonSyncAuthError instances
    7075            $err_type = SyncAuthError::TYPE_VALIDATION_FAILED;
    7176            if (is_a($user_signon, 'SyncAuthError'))
     
    8085            default:
    8186                $error_code = SyncApiResponse::ERROR_BAD_CREDENTIALS;           // default to generic error
    82 SyncDebug::log(__METHOD__.'():' . __LINE__ . ' unrecognized exception code ' . $user_signon->type);
     87SyncDebug::log(__METHOD__.'():' . __LINE__ . ' unrecognized exception code ' . $err_type);
    8388            }
    8489            $resp->error_code($error_code, NULL);
     
    209214        }
    210215
     216        $cleartext = wp_slash($cleartext);  #277
    211217//SyncDebug::log(' cleartext: ' . var_export($cleartext, TRUE));
    212218        return $cleartext;
  • wpsitesynccontent/trunk/classes/autherror.php

    r2321211 r2491116  
    1111    public function __construct($type)
    1212    {
    13         new WP_Error($code, $message, $data);
     13//      new WP_Error($code, $message, $data);
    1414        $this->type = $type;
    1515        switch ($type) {
    16         case SyncApiRequest::ERROR_BAD_CREDENTIALS:
    17             $code = __('Token Validation Failed', 'wpsitesynccontent');
     16        default:
     17        case self::TYPE_VALIDATION_FAILED:
     18            $code = 'token_invalid';
    1819            break;
    19         case SyncApiRequest::ERROR_MISSING_TOKEN:
    20             $code = __('Token Missing', 'wpsitesynccontent');
     20        case self::TYPE_MISSING_TOKEN:
     21            $code = 'token_missing';
    2122            break;
    22         case SyncApiRequest::ERROR_MISSING_USER:
    23             $code = __('User Invalid', 'wpsitesynccontent');
     23        case self::TYPE_INVALID_USER:
     24            $code = 'invalid_user';
    2425            break;
    2526        }
    26         parent::__construct($code);
     27        parent::__construct($code, __('Authentication error', 'wpsitesynccontent'));
    2728    }
    2829}
  • wpsitesynccontent/trunk/classes/gutenbergentry.php

    r2321211 r2491116  
    4545        if (FALSE !== ($pos = strpos($prop, ':'))) {
    4646            switch (substr($prop, $pos)) {
     47            default:
    4748            case ':i':          $this->prop_type = self::PROPTYPE_IMAGE;        break;
    4849            case ':l':          $this->prop_type = self::PROPTYPE_LINK;         break;
  • wpsitesynccontent/trunk/classes/input.php

    r1451657 r2491116  
    33class SyncInput
    44{
     5    const SANITIZE_EMAIL = 0x01;
     6    const SANITIZE_FILENAME = 0x02;
     7    const SANITIZE_HEXCOLOR = 0x04;
     8    const SANITIZE_KEY = 0x08;
     9    const SANITIZE_URL = 0x10;
     10
    511    /*
    612     * Return the named element for a given get variable
     
    915     * @return mixed The named get parameter if found, otherwise the $default value provided.
    1016     */
    11     public function get($name, $default = '')
     17    public function get($name, $default = '', $sanitize = 0)
    1218    {
     19        $ret = $default;
    1320        if (isset($_GET[$name]))
    14             return sanitize_text_field($_GET[$name]);
    15         return $default;
     21            $ret = sanitize_text_field($_GET[$name]);
     22        if (self::SANITIZE_EMAIL & $sanitize)
     23            $ret = sanitize_email($ret);
     24        if (self::SANITIZE_FILENAME & $sanitize)
     25            $ret = sanitize_file_name($ret);
     26        if (self::SANITIZE_HEXCOLOR & $sanitize)
     27            $ret = sanitize_hex_color($ret);
     28        if (self::SANITIZE_KEY & $sanitize)
     29            $ret = sanitize_key($ret);
     30        if (self::SANITIZE_URL & $sanitize)
     31            $ret = esc_url_raw($ret);
     32        return $ret;
     33#       sanitize_html_class($class);
     34#       sanitize_mime_type($mime_type);
     35#       sanitize_user($username);
    1636    }
    1737
  • wpsitesynccontent/trunk/classes/licensesettings.php

    r2247839 r2491116  
    136136    {
    137137        // TODO: $values is always NULL. why?
    138 SyncDebug::log(__METHOD__.'() values=' . var_export($values, TRUE));
    139 //SyncDebug::log(' - post=' . var_export($_POST, TRUE));
     138SyncDebug::log(__METHOD__.'() values=' . SyncDebug::arr_sanitize($values));
     139//SyncDebug::log(' - post=' . SyncDebug::arr_sanitize($_POST));
    140140        $input = $this->post('spectrom_sync_settings', array());
    141 //SyncDebug::log(' - input=' . var_export($input, TRUE));
     141//SyncDebug::log(' - input=' . SyncDebug::arr_sanitize($input));
    142142//SyncDebug::log(' method=' . $_SERVER['REQUEST_METHOD']);
    143143
  • wpsitesynccontent/trunk/classes/model.php

    r2321211 r2491116  
    6969    public function save_sync_data($data)
    7070    {
    71         global $wpdb;
    72 
    7371        // sanitize the `content_type` data
    7472        if (!isset($data['content_type']))
     
    7775            $data['content_type'] = sanitize_key($data['content_type']);
    7876
    79         if (!in_array($data['content_type'], array('comment', 'post', 'term', 'user')))
     77        if (!in_array($data['content_type'], array('comment', 'post', 'postmeta', 'term', 'user')))
    8078            throw new Exception('The `content_type` passed to save_sync_data() is invalid');
    8179
     
    9694            $data['content_type']);
    9795
    98 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' data=' . SyncDebug::arr_santize($data));
     96        global $wpdb;
     97
     98//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' data=' . SyncDebug::arr_sanitize($data));
    9999        if (NULL !== $sync_data) {
    100100//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' read: ' . SyncDebug::arr_sanitize($sync_data));
     
    124124        $type = sanitize_key($type);
    125125        if (defined('WP_DEBUG') && WP_DEBUG) {
    126             if (!in_array($type, array('comment', 'post', 'term', 'user')))
     126            if (!in_array($type, array('comment', 'post', 'postmeta', 'term', 'user')))
    127127                throw new Exception('The `type` passed to get_sync_data() is invalid');
    128128        }
     
    163163        $type = sanitize_key($type);
    164164        if (defined('WP_DEBUG') && WP_DEBUG) {
    165             if (!in_array($type, array('comment', 'post', 'term', 'user')))
     165            if (!in_array($type, array('comment', 'post', 'postmeta', 'term', 'user')))
    166166                throw new Exception('The `type` passed to get_sync_target_data() is invalid');
    167167        }
     
    199199        $type = sanitize_key($type);
    200200        if (defined('WP_DEBUG') && WP_DEBUG) {
    201             if (!in_array($type, array('comment', 'post', 'term', 'user')))
     201            if (!in_array($type, array('comment', 'post', 'postmeta', 'term', 'user')))
    202202                throw new Exception('The `type` passed to get_sync_target_post() is invalid');
    203203        }
     
    308308        $push_data['origin'] = $url;
    309309
    310         // also include the ‘site_key’ value generated on initial installation.
     310        // also include the 'site_key' value generated on initial installation.
    311311        $push_data['site_key'] = SyncOptions::get('site_key');
    312312
     
    335335    private function _build_post_meta($post_id)
    336336    {
    337         // any postmeta data associated with the current post ID that is prefixed with ‘_spectrom_sync_’ is not to be collected
     337        // any postmeta data associated with the current post ID that is prefixed with '_spectrom_sync_' is not to be collected
    338338        $post_meta = get_post_meta($post_id);
    339339        if ($post_meta) {
     
    348348                foreach ($value as &$meta_entry) {
    349349//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' key=' . $key . ' value=' . $meta_entry);
    350                     if ($this->_is_json($meta_entry))
     350                    if ($this->is_json($meta_entry))
    351351                        $meta_entry = str_replace('\"', '~syncescquote~', $meta_entry);
    352352                    $meta_entry = str_replace('\\u', '~syncuni~', $meta_entry);
     
    365365     * @return boolean TRUE if string contains JSON encoded data; FALSE if not a string or not containing JSON
    366366     */
    367     private function _is_json($str)
    368     {
    369         if (!is_string($str))
     367    public function is_json($str)
     368    {
     369        if (!is_string($str)) {
    370370            return FALSE;
     371        }
    371372        if (!empty($str)) {
     373            if (FALSE === stripos($str, '{') ||
     374                FALSE === stripos($str, '}'))
     375                return FALSE;
     376
     377            $str = str_replace(array('~syncescquote~', '~syncuni~'), array('\\"', '\\u'), $str);
    372378            @json_decode($str);
    373379            return (JSON_ERROR_NONE === json_last_error());
  • wpsitesynccontent/trunk/classes/options.php

    r2321211 r2491116  
    9595        }
    9696
    97         // override any settings with defines declared via wp-content #239
     97        // override any settings with defines declared via wp-config #239
    9898        if (defined('WPSITESYNC_TARGET'))
    9999            self::$_options['host'] = WPSITESYNC_TARGET;
  • wpsitesynccontent/trunk/classes/settings.php

    r2321211 r2491116  
    1515    private $_tab = '';
    1616
    17     const SETTINGS_PAGE = 'sync';       // TODO: update name
     17    // TODO: update name
     18    const SETTINGS_PAGE = 'sync';
    1819
    1920    private function __construct()
     
    154155    public function settings_api_init()
    155156    {
     157SyncDebug::log(__METHOD__.'():' . __LINE__ . ' action=' . current_action());
    156158        // check that current user is capable of performing operation
    157159        if (!current_user_can('manage_options') || !SyncOptions::has_cap())
     
    188190    private function _init_general_settings()
    189191    {
    190 //SyncDebug::log(__METHOD__.'() tab=' . $this->_tab);
     192SyncDebug::log(__METHOD__.'() tab=' . $this->_tab . ' action=' . current_action());
    191193        $data = $option_values = SyncOptions::get_all(); // $this->_options;
    192194
     
    320322        case 'id':          $desc = __('ID - Search for matching Content on Target by Post ID.', 'wpsitesynccontent');
    321323            break;
    322         case 'title':       $desc = __('Post Title - Search for matching Content on Target by Post Title only.', 'wpsitesynccontent');
    323         default:
    324             break;
    325324        case 'title-slug':  $desc = __('Title, then Slug - Search for matching Content on Target by Post Title, then by Slug.', 'wpsitesynccontent');
     325            break;
     326        case 'title':
     327        default:            $desc = __('Post Title - Search for matching Content on Target by Post Title only.', 'wpsitesynccontent');
    326328            break;
    327329        }
     
    350352        $min_role = isset($data['min_role']) ? $data['min_role'] : 'author';
    351353        switch ($min_role) {
     354        default:
    352355        case 'author':          $default_role = '|author|editor|administrator|';
    353         default:
    354356            break;
    355357        case 'editor':          $default_role = '|editor|administrator|';
     
    534536    public function render_button_field($args)
    535537    {
    536         echo '<button type="button" id="spectrom-button-', $args['name'], '" class="button-primary spectrom-ui-button">', $args['title'], '</button>';
     538//      $title = '';
     539//      if (isset($args['title']))
     540//          $title = ' title="' . esc_attr($args['title']) . '" ';
     541        $click = '';
     542        if (isset($args['click']))
     543            $click = ' onclick="' . esc_attr($args['click']) . '" ';
     544
     545        echo '<button type="button" id="spectrom-button-', $args['name'], '" class="button-primary spectrom-ui-button"',
     546            $click, '>', $args['title'], '</button>';
     547
    537548        if (!empty($args['message']))
    538549            echo '<p>', $args['message'], '</p>';
     
    578589    {
    579590//SyncDebug::log(__METHOD__.'() tab=' . $this->_tab);
    580 //SyncDebug::log(__METHOD__.'() values=' . var_export($values, TRUE));
    581591        $settings = SyncOptions::get_all(); // $this->_options;
    582592//SyncDebug::log(__METHOD__.'() settings: ' . var_export($settings, TRUE));
     
    609619
    610620        foreach ($values as $key => $value) {
    611 //SyncDebug::log(" key={$key} value=[" . var_export($value, TRUE) . ']');
    612621            if (empty($values[$key]) && 'password' === $key) {
    613622                // ignore this so that passwords are not required on every settings update
     623                unset($out['password']);
    614624            } else {
    615625                if ('password' !== $key && !is_array($value))
     
    623633                        if (empty($value)) {
    624634                            // do nothing
     635                            $value = '';
    625636                        } else if (FALSE === $this->_is_valid_url($value)) {
    626637                            add_settings_error('sync_options_group', 'invalid-url', __('Invalid URL.', 'wpsitesynccontent'));
     
    635646                    // TODO: refactor so that 'host' and 'username' password checking is combined
    636647                    // check to see if 'username' is changing and force use of password
    637 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' change username: user="' . $value . '" password="' . $values['password'] . '"');
    638                     if ('' === $value && '' === $values['password'] /* empty($value) && empty($values['password']) */ ) {
     648                    if ('' === $value && '' === $values['password']) {
    639649                        // do nothing
     650                        $value = '';
    640651                    } else if ($value !== $settings['username'] && empty($values['password'])) {
    641652                        add_settings_error('sync_username_password', 'missing-password', __('When changing Username, a password is required.', 'wpsitesynccontent'));
     
    652663                    $out[$key] = '1' === $value ? '1' : '0';
    653664                } else if ('roles' === $key) {
    654 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' POST=' . var_export($_POST, TRUE));
    655665                    $roles = array();
    656 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' value=' . var_export($value, TRUE));
    657666                    foreach ($value as $role => $on) {
    658667                        $roles[] = $role;
     
    661670                        $roles[] = 'administrator';             // always force administrator access
    662671                    $out[$key] = SyncOptions::ROLE_DELIMITER . implode(SyncOptions::ROLE_DELIMITER, $roles) . SyncOptions::ROLE_DELIMITER;
    663 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' roles: ' . $out[$key]);
    664672                } else if (0 === strlen(trim($value))) {
    665673                    if (!$missing_error) {
     
    679687            }
    680688        }
    681 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' output array: ' . var_export($out, TRUE));
    682689
    683690        // authenticate if there was a password provided or the host/username are different
     
    700707        if (!empty($out['password']) || $re_auth) {
    701708            $out['auth'] = 0;
    702 
    703 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' authenticating with data ' . var_export($out, TRUE));
    704709            $api = new SyncApiRequest();
    705             $res = $api->api('auth', $out);
     710            $auth = array(
     711                'host' => $out['host'],
     712                'username' => $out['username'],
     713                'password' => $out['password'],
     714            );
     715SyncDebug::log(__METHOD__.'():' . __LINE__ . ' authenticating...');
     716            $res = $api->api('auth', $auth);
    706717            if (!is_wp_error($res)) {
    707 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' response from auth request: ' . var_export($res, TRUE));
    708718                if (isset($res->response->success) && $res->response->success) {
    709719                    $out['auth'] = 1;
    710720                    $out['target_site_key'] = $res->response->data->site_key;
    711 //SyncDebug::log(__METHOD__.'() got token: ' . substr($res->response->data->token, -16));
    712721                } else {
    713722                    // authentication failure response from Target- report this to user
    714 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' authentication response from Target');
    715723                    $msg = SyncApiRequest::error_code_to_string($res->error_code);
    716724                    $msg .= ' <a href="https://wpsitesync.com/knowledgebase/wpsitesync-error-messages/#error' . $res->error_code . '" target="_blank" style="text-decoration:none"><span class="dashicons dashicons-info"></span></a>';
     
    737745
    738746        $ret = apply_filters('spectrom_sync_validate_settings', $out, $values);
    739 //SyncDebug::log(__METHOD__.'() validated settings: ' . SyncDebug::arr_sanitize($ret));
    740747        return $ret;
    741748    }
     
    834841        $rate_text = sprintf(__('Thank you for using <a href="%1$s" target="_blank">WPSiteSync for Content</a>! Please <a href="%2$s" target="_blank">rate us</a> on <a href="%2$s" target="_blank">WordPress.org</a>', 'wpsitesynccontent'),
    835842            esc_url('https://wpsitesync.com'),
    836             esc_url('https://wordpress.org/support/view/plugin-reviews/wpsitesynccontent?filter=5#postform')
     843            esc_url('https://wordpress.org/support/plugin/wpsitesynccontent/reviews?rate=5#new-post')
    837844        );
    838845
  • wpsitesynccontent/trunk/classes/shortcodeentry.php

    r2247839 r2491116  
    132132            case 'p':       $type = self::TYPE_POST_ID;                     break;      // post
    133133            case 'pa':      $type = self::TYPE_POST_ATTACH;                 break;      // post attachments
    134             case 'l':       $type = self::TYPE_IMAGE_LIST;                  break;      // list TODO: change to 'il'
     134            // TODO: change to 'il' to be consistent with type/list format
     135            case 'l':       $type = self::TYPE_IMAGE_LIST;                  break;      // list
    135136            case 'pl':      $type = self::TYPE_POST_LIST;                   break;      // post list
    136137            case 'a':       $type = self::TYPE_ATTACHMENT;                  break;      // attachment
  • wpsitesynccontent/trunk/install/pluginupdater.php

    r2247839 r2491116  
    8787            return $_transient_data;
    8888        }
    89 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' trans data=' . var_export($_transient_data, TRUE));
     89//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' trans data=' . SyncDebug::arr_sanitize($_transient_data));
    9090        if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
    9191            return $_transient_data;
     
    107107            $_transient_data->last_checked = time();
    108108            $_transient_data->checked[ $this->name ] = $this->version;
    109 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' checked:[' . $this->name . ']=' . var_export($_transient_data->checked[$this->name], TRUE));
     109//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' checked:[' . $this->name . ']=' . SyncDebug::arr_sanitize($_transient_data->checked[$this->name]));
    110110        }
    111111        return $_transient_data;
  • wpsitesynccontent/trunk/readme.txt

    r2340021 r2491116  
    55Requires at least: 3.5
    66Requires PHP: 5.3.1
    7 Tested up to: 5.4.2
     7Tested up to: 5.7
    88Stable tag: trunk
    99License: GPLv2 or later
     
    1414== Description ==
    1515
    16 WPSiteSync for Content helps Designers, Developers and Content Creators Synchronize SPECIFIC Content (i.e. Posts and Pages) between WordPress sites, AFTER a site has gone live. You can do this in Real-Time, with a simple CLICK of a button!
     16WPSiteSync for Content helps Designers, Developers and Content Creators Synchronize
     17SPECIFIC Content (i.e. Posts and Pages) between WordPress sites, AFTER a site has gone live.
     18You can do this in Real-Time, with a simple CLICK of a button!
    1719
    1820A typical development workflow looks like this:
     
    23254. Deploy Site to LIVE host
    2426
    25 The challenge presented once you've deployed is that any future content changes must either be done on the live site, or a full deployment is necessary if the changes were made locally or on a staging site.
    26 
    27 WPSiteSync is unique in that it solves this problem by offering the ability to only deploy the CONTENT that has changed. This means ZERO down-time and ZERO data loss. It is the missing piece of the development puzzle, allowing for a proper workflow beyond the deployment and into its ongoing operation.
     27The challenge presented once you've deployed is that any future content changes must
     28either be done on the live site, or a full deployment is necessary if the changes were
     29made locally or on a staging site.
     30
     31WPSiteSync is unique in that it solves this problem by offering the ability to only
     32deploy the CONTENT that has changed. This means ZERO down-time and ZERO data loss.
     33It is the missing piece of the development puzzle, allowing for a proper workflow
     34beyond the deployment and into its ongoing operation.
    2835
    2936With WPSiteSync, the new workflow looks like this:
     
    4552* You only need to synchronize specific content (not the complete database)
    4653
    47 You can use <em>WPSiteSync for Content</em> to synchronize your Posts and Pages between any two WordPress sites, in any configuration. Some examples include:
     54You can use <em>WPSiteSync for Content</em> to synchronize your Posts and Pages between any two
     55WordPress sites, in any configuration. Some examples include:
    4856
    4957* Local -&gt; Staging
     
    5462[youtube https://www.youtube.com/watch?v=KpeiTMbdj_Y]
    5563
    56 ><strong>Support Details:</strong> We are happy to provide support and help troubleshoot issues. Visit our Support page at <a href="https://serverpress.com/contact/" target="_blank">https://serverpress.com/contact/</a>. Users should know however, that we check the WordPress.org support forums once a week on Wednesdays from 6pm to 8pm PST (UTC -8).
    57 
    58 The <em>WPSiteSync for Content</em> plugin was specifically designed to simplify your workflow when creating content between development, staging and live servers. This tool removes the need to migrate an entire database, potentially overwriting new content on your live site such as comments, reviews, purchases, etc. Now you can update individual Pages or Posts as desired, leaving everything else intact. The best part is that it's a simple click of a button, reducing errors and saving you time.
    59 
    60 WPSiteSync for Content is fully functional in any WordPress environment. We recommend using DesktopServer for your Local Development Environment, but it is not a requirement.
     64><strong>Support Details:</strong> We are happy to provide support and help troubleshoot
     65issues. Visit our Support page at <a href="https://serverpress.com/contact/" target="_blank">https://serverpress.com/contact/</a>.
     66Users should know however, that we check the WordPress.org support forums once
     67a week on Wednesdays from 6pm to 8pm PST (UTC -8).
     68
     69The <em>WPSiteSync for Content</em> plugin was specifically designed to simplify
     70your workflow when creating content between development, staging and live servers.
     71This tool removes the need to migrate an entire database, potentially overwriting
     72new content on your live site such as comments, reviews, purchases, etc. Now you
     73can update individual Pages or Posts as desired, leaving everything else intact.
     74The best part is that it's a simple click of a button, reducing errors and saving
     75you time.
     76
     77WPSiteSync for Content is fully functional in any WordPress environment. We recommend
     78using DesktopServer for your Local Development Environment, but it is not a requirement.
    6179
    6280<strong>This benefits your Development Workflow in more ways than one:</strong>
     
    95113* FULL access to ALL future Premium Extensions
    96114
    97 <strong>For more perks such as Early Access</strong> and <strong>Exclusive Preview</strong> of upcoming Features, please visit us at <a href="https://wpsitesync.com">WPSiteSync.com</a> and join our newsletter.
    98 
    99 <strong>ServerPress, LLC is not responsible for any loss of data that may occur as a result of WPSiteSync for Content's use.</strong> Always backup your data before initial use of this product. Should you experience such an issue, we want to know about it right away. Please <a href="https://serverpress.com/contact/" target="_blank">contact our Support Team</a> and we'll do everything we can to get you up and running.
     115<strong>For more perks such as Early Access</strong> and <strong>Exclusive Preview</strong>
     116of upcoming Features, please visit us at <a href="https://wpsitesync.com">WPSiteSync.com</a>
     117and join our newsletter.
     118
     119<strong>ServerPress, LLC is not responsible for any loss of data that may occur as a result
     120of WPSiteSync for Content's use.</strong> Always backup your data before initial use of this
     121product. Should you experience such an issue, we want to know about it right away. Please
     122<a href="https://serverpress.com/contact/" target="_blank">contact our Support Team</a>
     123and we'll do everything we can to get you up and running.
    100124
    101125== Installation ==
     
    1151392. Activate the plugin through the 'Plugins' menu in WordPress.
    116140
    117 You will need to Install and Activate the WPSiteSync for Content plugin on your development website (the Source) as well as the Target web site (where the Content is being moved to).
    118 
    119 Once activated, you can use the Configuration page found at Settings -&gt; WPSiteSync, on the Source website to set the URL of the Target site and enter the login credentials to use when sending data. This will allow the WPSiteSync for Content plugin to communicate with the Target website, authenticate, and then move the data between the websites. You do not need to Configure WPSiteSync for Content on the Target website as this will only be receiving Synchronization requests from the Source site.
     141You will need to Install and Activate the WPSiteSync for Content plugin on your
     142development website (the Source) as well as the Target web site (where the Content
     143is being moved to).
     144
     145Once activated, you can use the Configuration page found at Settings -&gt; WPSiteSync,
     146on the Source website to set the URL of the Target site and enter the login credentials
     147to use when sending data. This will allow the WPSiteSync for Content plugin to
     148communicate with the Target website, authenticate, and then move the data between
     149the websites. You do not need to Configure WPSiteSync for Content on the Target
     150website as this will only be receiving Synchronization requests from the Source
     151site.
    120152
    121153== Frequently Asked Questions ==
     
    123155= Do I need to Install WPSiteSync for Content on both sites? =
    124156
    125 Yes! The WPSiteSync for Content needs to be installed on the local or Staging server (the website you're moving the data from - the Source), as well as the Live server (the website you're moving the data to - the Target).
     157Yes! The WPSiteSync for Content needs to be installed on the local or Staging server
     158(the website you're moving the data from - the Source), as well as the Live server
     159(the website you're moving the data to - the Target).
    126160
    127161= Does this plugin Synchronize all of my content (Pages and Posts) at once? =
    128162
    129 No. WPSiteSync for Content will only synchronize the one Page or Post content that you are editing. And it will only Synchronize the content when you tell it to. This allows you to control exactly what content is moved between sites and when it will be moved.
     163No. WPSiteSync for Content will only synchronize the one Page or Post content that
     164you are editing. And it will only Synchronize the content when you tell it to. This
     165allows you to control exactly what content is moved between sites and when it will
     166be moved.
    130167
    131168= Will this overwrite data while I am editing? =
    132169
    133 No. WPSiteSync checks to see if the Content is being edited by someone else on the Target web site. If it is, it will not update the Content, allowing you to coordinate with the users on the Target web site.
    134 
    135 In addition, each time Content is updated or Synchronized on the Target web site, a Post Revision is created (using the Post Revision settings). This allows you to recover Content to a previous version if needed.
     170No. WPSiteSync checks to see if the Content is being edited by someone else on
     171the Target web site. If it is, it will not update the Content, allowing you to
     172coordinate with the users on the Target web site.
     173
     174In addition, each time Content is updated or Synchronized on the Target web site,
     175a Post Revision is created (using the Post Revision settings). This allows you to
     176recover Content to a previous version if needed.
    136177
    137178= Does WPSiteSync only update Page and Posts Content? =
    138179
    139 Yes. However, support for synchronizing Custom Post Types and Custom Taxonomies is available with our <a href="https://wpsitesync.com/downloads/wpsitesync-for-custom-post-types/" target="_blank">WPSiteSync for Custom Post Types</a> add-on. Additional plugins for User Attribution, Synchronizing Menus and Pulling content are available as well.
    140 
    141 More complex data, such as WooCommerce products, Forms (like Gravity Forms or Ninja Forms), and other plugins that use custom database tables are supported by additional plugins that work with those products.
     180Yes. However, support for synchronizing Custom Post Types is available with our
     181<a href="https://wpsitesync.com/downloads/wpsitesync-for-custom-post-types/" target="_blank">WPSiteSync
     182for Custom Post Types</a> add-on. Additional plugins for User Attribution, Synchronizing
     183Menus and Pulling content are available as well.
     184
     185More complex data, such as WooCommerce products, Forms (like Gravity Forms or Ninja
     186Forms), and other plugins that use custom database tables are supported by
     187additional plugins that work with those products.
    142188
    143189= Is WPSiteSync Gutenberg Compatible? =
    144190
    145 Yes! The free version of <em>WPSiteSync for Content</em> supports all Gutenberg blocks that are part of WordPress. There are many plugins that add more block types than those available in WordPress, however. Our add-on product, <a href="https://wpsitesync.com/downloads/wpsitesync-for-gutenberg-blocks/" target="_blank">WPSiteSync for Gutenberg Blocks</a> adds support for several of the more popular third party Gutenberg add-ons.
    146 
    147 If your favorite Gutenberg Block plugin is not currently supported, let us know and we can add support for it.
     191Yes! The free version of <em>WPSiteSync for Content</em> supports all Gutenberg
     192blocks that are part of WordPress. There are many plugins that add more block types
     193than those available in WordPress, however. Our add-on product, <a href="https://wpsitesync.com/downloads/wpsitesync-for-gutenberg-blocks/"
     194target="_blank">WPSiteSync for Gutenberg Blocks</a> adds support for several of
     195the more popular third party Gutenberg add-ons.
     196
     197If your favorite Gutenberg Block plugin is not currently supported, let us know
     198and we can add support for it.
    148199
    149200== Screenshots ==
     
    153204
    154205== Changelog ==
     206= 1.7 - Mar 9, 2021 =
     207* fix: Ensure JSON encoded metadata maintains escaped characters within strings. (Thanks Kris B.)
     208* fix: Mimic WordPress's "feature" of slash encoding special characters in password strings. (Thanks Brian S.)
     209* fix: Add braces to denote code block. (Thanks Miguel D.)
     210* fix: Improve handling of multiple postmeta entries with the same meta_key. (Thanks Miguel D.)
     211* enhancement: Allow WPSiteSync API operations to work when Members' "Enable Private Site" setting is turned on. (Thanks Christopher B.)
     212* enhancement: Allow connections to DesktopServer's Ngrok site. (Thanks Wrenford H.)
     213* enhancement: Add mechanism to inform Target site of any add-ons required to process data from Source.
     214* enhancement: Add before/after API request processing hooks.
     215* enhancement: Change declaration of parse_shortcodes() so it can be used by Elementor add-on.
     216* enhancement: Check for Gutenberg block property's existence before accessing to prevent invalid references.
     217* enhancement: Update compatibility with Coming Soon v6+ (Thanks Ned G.)
     218* enhancement: Improve error detection in processing AJAX requests (Thanks Ned G.)
     219* enhancement: Improve Block property detection and error recovery.
     220
    155221= 1.6.1 - Jun 16, 2020 =
    156222* fix: Find child attachments to a post in cases where the post_content is empty. (Thanks Paul W.)
  • wpsitesynccontent/trunk/wpsitesynccontent.php

    r2325466 r2491116  
    66Author: WPSiteSync
    77Author URI: https://wpsitesync.com
    8 Version: 1.6.1
     8Version: 1.7
    99Text Domain: wpsitesynccontent
    1010Domain path: /language
     
    2626    class WPSiteSyncContent
    2727    {
    28         const PLUGIN_VERSION = '1.6.1';
     28        const PLUGIN_VERSION = '1.7';
    2929        const PLUGIN_NAME = 'WPSiteSyncContent';
    3030
     
    226226
    227227            // send usage information
    228             if ('1' === SyncOptions::get('report', '0'))
     228            if (self::$report || '1' === SyncOptions::get('report', '0'))
    229229                new SyncUsage();
    230230
Note: See TracChangeset for help on using the changeset viewer.