Plugin Directory

Changeset 1745585


Ignore:
Timestamp:
10/12/2017 05:39:50 PM (8 years ago)
Author:
davejesch
Message:
  • fix: improve checking for sync-specific meta data to be ignored
  • fix: only set user id if available in SyncApiController->push()
  • fix: improved code that recovers from errors displayed within JSON response data
  • fix: set $media_id property during 'upload_media' API calls to media type after create/update of image
  • fix: adjust parameter for wp_get_attachment_image_src()
  • fix: check parameter passed to debug_backtrace() in case of old (< 5.3.6) versions of PHP
  • fix: handle empty post content and featured images correctly (Thanks to Lucas C.)
  • fix: correct taxonomy term lookup on Target to preserve naming (Thanks to Calvin C.)
  • fix: when changing Target or Username configs, require passwords (Thanks to Erin M.)
  • fix: javascript compatibility issue with Visual Composer backend editor (Thanks to Carlos)
  • fix: set taxonmy type for each taxonomy entries when processing hierarchical taxonomies
  • fix: recover and continue from failures of media attachments rather than aborting
  • fix: add Source to Target URL fixups in meta data and excerpt (Thanks to Bryan A.)
  • enhancement: add hook to notify add-ons that images have completed processing
  • enhancement: allow add-ons to modify HTTP post content during 'upload_media' API calls
  • enhancement: allow authentication to check for email address, in addition to user name
  • enhancement: add detection and circumvention of 'Coming Soon' / 'Maintenance Mode' plugins during API calls
  • enhancement: allow add-ons to modify allowed mime types during 'upload_media' API calls
  • enhancement: allow add-ons to modify content_type column in sync table
  • enhancement: make display of error messages via UI easier
  • enhancement: add callback to remove data from WPSS tables when content is deleted
  • enhancement: add appropriate headers for AJAX responses
  • enhancement: add 'match-mode' option to allow users to perform new content lookups on target by post title or slug; deprecate SyncApiController::get_post_by_title()
  • enhancement: add fallback encryption when mcrypt is not available
Location:
wpsitesynccontent
Files:
48 added
19 edited

Legend:

Unmodified
Added
Removed
  • wpsitesynccontent/trunk/assets/css/sync-admin.css

    r1492115 r1745585  
    128128}
    129129
    130 #spectrom_sync #sync-message-container #sync-message span.error {
     130#spectrom_sync #sync-message-container span.sync-error {
    131131    color: red;
    132132}
  • wpsitesynccontent/trunk/assets/js/sync.js

    r1560226 r1745585  
    2222    this.original_value = '';
    2323    this.nonce = jQuery('#_sync_nonce').val();
     24    this.push_xhr = null;
     25    this.push_callback = null;                  // callback to perform push; returns true to continue processing; false to stop processing
     26    this.pull_callback = null;                  // callback to perform pull; returns true to continue processing; false to stop processing
    2427}
    2528
     
    6568 * @param {boolean|null} anim If set to true, display the animation image; otherwise animation will not be shown.
    6669 * @param {boolean|null) dismiss If set to true, will include a dismiss button for the message
    67  */
    68 WPSiteSyncContent.prototype.set_message = function(msg, anim, dismiss)
     70 * @param {string|null} CSS class to add to the message container
     71 */
     72WPSiteSyncContent.prototype.set_message = function(msg, anim, dismiss, css_class)
    6973{
    7074    if (!this.inited)
    7175        return;
    7276
    73     jQuery('#sync-message').html(msg);
     77    jQuery('#sync-message').attr('class', '').html(msg);
     78    if ('string' === typeof(css_class))
     79        jQuery('#sync-message').addClass(css_class);
     80
    7481    if ('boolean' === typeof(anim) && anim)
    7582        jQuery('#sync-content-anim').show();
     
    110117
    111118/**
    112  * Disables Sync Button every time the content changes. 
     119 * Disables Sync Button every time the content changes.
    113120 */
    114121WPSiteSyncContent.prototype.on_content_change = function()
     
    117124        this.disable = true;
    118125        jQuery('#sync-content').attr('disabled', true);
    119         this.set_message(jQuery('#sync-msg-update-changes').html());
     126        this.set_message(jQuery('#sync-msg-update-changes').html(), false, false, 'sync-error');
    120127//      jQuery('#disabled-notice-sync').show();
    121128    } else {
     
    132139WPSiteSyncContent.prototype.force_refresh = function()
    133140{
    134     jQuery(window).trigger('resize');
    135     jQuery('#sync-message').parent().hide().show(0);
     141//  jQuery(window).trigger('resize');
     142//  jQuery('#sync-message').parent().hide().show(0);
    136143};
    137144
     
    140147 * @param {string} op The name of the API to call
    141148 * @param {int} post_id The post ID for the API call or null if not applicable
     149 * @param {string} msg The message to be set
     150 * @param {string} msg_success The success message to be set
     151 * @param {object} values Optional values to add to data
    142152 * @returns {undefined}
    143153 */
    144 WPSiteSyncContent.prototype.api = function(op, post_id, msg, msg_success)
    145 {
    146 console.log('wpsitesync.api() performing "' + op + '" api request... ' + msg);
     154WPSiteSyncContent.prototype.api = function(op, post_id, msg, msg_success, values)
     155{
     156//console.log('wpsitesync.api() performing "' + op + '" api request... ' + msg);
    147157    // Do nothing when in a disabled state
    148158    if (this.disable || !this.inited)
    149159        return;
     160
     161    // add callback checks based on 'op' parameter values ... see .push() example
     162    switch (op) {
     163    case 'push':
     164        // check for a callback function - used to alter the behavior of the Push operation
     165        if (null !== this.push_callback) {
     166            var res = this.push_callback(post_id);
     167            if (!res)                           // if the callback returns a false
     168                return;                         // do not continue processing
     169        }
     170        break;
     171    case 'pull':
     172        // check for a callback function - used to alter the behavior of the Pull operation
     173        if (null !== this.pull_callback) {
     174            var res = this.pull_callback(post_id);
     175            if (!res)                           // if the callback returns a false
     176                return;                         // do not continue processing
     177        }
     178        break;
     179    }
    150180
    151181    // set the message while API is running
     
    157187        operation: op,
    158188        post_id: post_id,
    159         _sync_nonce:
    160         this.nonce
     189        _sync_nonce: this.nonce
    161190    };
    162191
    163     var push_xhr = {
     192    if ('undefined' !== typeof(values)) {
     193        _.extend(data, values);
     194    }
     195
     196    this.push_xhr = {
    164197        type: 'post',
    165198        async: true, // false,
     
    190223            if ('undefined' !== typeof(response.error_message))
    191224                wpsitesynccontent.set_message('<span class="error">' + response.error_message + '</span>', false, true);
     225            else
     226                wpsitesynccontent.set_message('<span class="error">' + jQuery('#sync-runtime-err-msg').html() + '</span>', false, true)
    192227//          jQuery('#sync-content-anim').hide();
    193228        }
     
    195230
    196231    // Allow other plugins to alter the ajax request
    197     jQuery(document).trigger('sync_api_call', [op, push_xhr]);
     232    jQuery(document).trigger('sync_api_call', [op, this.push_xhr]);
    198233//console.log('push() calling jQuery.ajax');
    199     jQuery.ajax(push_xhr);
     234    jQuery.ajax(this.push_xhr);
    200235//console.log('push() returned from ajax call');
    201236};
     
    212247        return;
    213248
    214     // clear the message to start things off
    215 //  jQuery('#sync-message').html('');
    216 //  jQuery('#sync-message').html(jQuery('#sync-working-msg').html());
    217 //  jQuery('#sync-content-anim').show();
    218 //  jQuery('#sync-message').parent().hide().show(0);
     249    // check for a callback function - used to alter the behavior of the Push operation
     250    if (null !== this.push_callback) {
     251        var res = this.push_callback(post_id);
     252        if (!res)                           // if the callback returns a false
     253            return;                         // do not continue processing
     254    }
     255
    219256    // set message to "working..."
    220257    this.set_message(jQuery('#sync-msg-working').text(), true);
     
    223260    var data = { action: 'spectrom_sync', operation: 'push', post_id: post_id, _sync_nonce: jQuery('#_sync_nonce').val() };
    224261
     262//console.log('push() calling AJAX');
    225263    var push_xhr = {
    226264        type: 'post',
     
    233271            wpsitesynccontent.clear_message();
    234272            if (response.success) {
     273//console.log('push() response.success');
    235274//              jQuery('#sync-message').text(jQuery('#sync-success-msg').text());
    236275                wpsitesynccontent.set_message(jQuery('#sync-success-msg').text(), false, true);
     
    241280                }
    242281            } else {
     282//console.log('push() !response.success');
    243283                if ('undefined' !== typeof(response.data.message))
    244284//                  jQuery('#sync-message').text(response.data.message);
    245                     wpsitesynccontent.set_message(response.data.message, false, true);
     285                    wpsitesynccontent.set_message(response.data.message, false, true, 'sync-error');
    246286            }
    247287        },
     
    252292            if ('undefined' !== typeof(response.error_message))
    253293                wpsitesynccontent.set_message('<span class="error">' + response.error_message + '</span>', false, true);
     294            else
     295                wpsitesynccontent.set_message('<span class="error">' + jQuery('#sync-runtime-err-msg').html() + '</span>', false, true)
    254296//          jQuery('#sync-content-anim').hide();
    255297        }
     
    264306
    265307/**
     308 * Set a callback function to be used to alter behavior of .push() method
     309 * @param {function} fn The function to store and use as a callback in .push()
     310 */
     311WPSiteSyncContent.prototype.set_push_callback = function(fn)
     312{
     313    this.push_callback = fn;
     314};
     315
     316/**
     317 * Set a callback function to be used to alter behavior of .pull() method
     318 * @param {function} fn The function to store and use as a callback in .pull()
     319 */
     320WPSiteSyncContent.prototype.set_pull_callback = function(fn)
     321{
     322    this.pull_callback = fn;
     323};
     324
     325/**
    266326 * Display message about WPSiteSync Pull feature
    267327 */
  • wpsitesynccontent/trunk/classes/admin.php

    r1510336 r1745585  
    2020        add_filter('plugin_action_links_wpsitesynccontent/wpsitesynccontent.php', array(&$this, 'plugin_action_links'));
    2121
     22        add_action('before_delete_post', array($this, 'before_delete_post'));
     23
    2224        // TODO: only init if running settings page
    23         SyncSettings::get_instance();       
     25        SyncSettings::get_instance();
    2426    }
    2527
     
    135137
    136138        // display the content details
    137         $content_details = $this->_get_content_details();
     139        $content_details = $this->get_content_details();
    138140        // TODO: set details content
    139141        echo '<div id="sync-details" style="display:none">';
     
    186188        if (!class_exists('WPSiteSync_Pull', FALSE))
    187189                echo '<div id="sync-pull-msg"><div style="color: #0085ba;">', __('Please activate the Pull extension.', 'wpsitesynccontent'), '</div></div>';
     190        echo '<div id="sync-runtime-err-msg">', __('A PHP runtime error occured while processing your request. Examine Target log files for more information.', 'wpsitesynccontent'), '</div>';
    188191        echo '</div>';
    189192
     
    192195        echo '<div style="display:none">';
    193196        echo '<span id="sync-msg-working">', __('Pushing Content to Target...', 'wpsitesynccontent'), '</span>';
    194         echo '<span id="sync-msg-update-changes"><span class="error"><b>', __('Please UPDATE your changes in order to Sync.', 'wpsitesynccontent'), '</b></span></span>';
     197        echo '<span id="sync-msg-update-changes"><b>', __('Please UPDATE your changes in order to Sync.', 'wpsitesynccontent'), '</b></span>';
    195198        do_action('spectrom_sync_ui_messages');
    196199        echo '</div>';
     
    212215     * @return string HTML contents to display within the Details section within the UI
    213216     */
    214     private function _get_content_details()
     217    public function get_content_details()
    215218    {
    216219        global $post;
     
    274277                    $response_data = $response_body->data;
    275278SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - target data: ' . var_export($response_data, TRUE));
    276                     // take the data returned from the API and 
     279                    // take the data returned from the API and
    277280                    $content_data = array(
    278281                        'target' => SyncOptions::get('target'),
     
    303306        return $content;
    304307    }
     308
     309    /**
     310     * Callback for delete post action. Removes all sync records associated with Content.
     311     * @param int $post_id The post ID being deleted
     312     */
     313    public function before_delete_post($post_id)
     314    {
     315        $model = new SyncModel();
     316        $model->remove_all_sync_data($post_id);
     317    }
    305318}
    306319
  • wpsitesynccontent/trunk/classes/ajax.php

    r1560226 r1745585  
    4040SyncDebug::log(__METHOD__."('{$operation}')");
    4141        $response = new SyncApiResponse(TRUE);
     42
     43        // set headers
     44//      header('Content-Type: text/html; charset=ISO-utf-8');
     45        header('Content-Type: application/json; charset=utf-8');
     46        header('Content-Encoding: ajax');
     47        header('Cache-Control: private, max-age=0');
     48        header('Expires: -1');
    4249
    4350        // perform authentication checking: must be logged in, an 'Author' role or higher
  • wpsitesynccontent/trunk/classes/apicontroller.php

    r1573030 r1745585  
    1212    private static $_instance = NULL;
    1313
    14     protected $media_id = 0;
     14    protected $media_id = 0;                        // id of the media being handled
    1515    protected $local_media_name = '';
    16     public $source_site_key = NULL;             // the Source site's key
     16    public $source_site_key = NULL;                 // the Source site's key
    1717
    1818    private $_headers = NULL;                       // stores request headers
     
    5353            $response->nosend = TRUE;
    5454
    55         $this->source_site_key = isset($args['site_key']) ? $args['site_key'] : $this->_get_header(self::HEADER_SITE_KEY);
    56 
    57         $this->source = untrailingslashit(isset($args['source']) ? $args['source'] : $this->_get_header(self::HEADER_SOURCE));
     55        $this->source_site_key = isset($args['site_key']) ? $args['site_key'] : $this->get_header(self::HEADER_SITE_KEY);
     56
     57        $this->source = untrailingslashit(isset($args['source']) ? $args['source'] : $this->get_header(self::HEADER_SOURCE));
    5858SyncDebug::log(__METHOD__.'() action=' . $action . ' source=' . $this->source . ' key=' . $this->source_site_key);
    5959
     
    177177     * @return string|NULL The requested header value or NULL if the named header is not found
    178178     */
    179     private function _get_header($name)
     179    public function get_header($name)
    180180    {
    181181        if (NULL === $this->_headers) {
     
    263263        do_action('spectrom_sync_pre_push_content', $post_data, $this->source_post_id, $target_post_id, $response);
    264264
     265        // allow add-ons to modify the content type
     266        $content_type = apply_filters('spectrom_sync_push_content_type', 'post', $target_post_id, $this);
     267
    265268        $post = NULL;
    266269        if (0 !== $target_post_id) {
     
    275278            // use source's site_key for the lookup
    276279            // TODO: use a better variable name than $sync_data
    277             $sync_data = $model->get_sync_data($this->source_post_id, $this->source_site_key);
     280            $sync_data = $model->get_sync_data($this->source_post_id, $this->source_site_key, $content_type);
    278281SyncDebug::log('   sync_data: ' . var_export($sync_data, TRUE));
    279282            if (NULL !== $sync_data) {
     
    285288            $this->post_id = $target_post_id;
    286289        }
    287 
     290###$post = NULL; ###
    288291        // Get post by title, if new
    289292        if (NULL === $post) {
    290 SyncDebug::log(' - still no post found - look up by title');
    291             $post = $this->get_post_by_title($post_data['post_title']);
    292             if (NULL !== $post)
    293                 $target_post_id = $post->ID;
     293            $mode = $this->get_header(self::HEADER_MATCH_MODE, 'title');
     294//SyncDebug::log(__METHOD__ . '():' . __LINE__ . ' - still no post found - use lookup_post() mode=' . $mode);
     295            $post_model = new SyncPostModel();
     296            $target_post_id = $post_model->lookup_post($post_data, $mode);
    294297        }
    295298
     
    310313
    311314        // check parent page- don't allow if parent doesn't exist
    312         if (0 !== intval($post_data['post_parent'])) {
     315        if (0 !== abs($post_data['post_parent'])) {
    313316            $model = new SyncModel();           // does this already exist?
    314317SyncDebug::log(__METHOD__.'() looking up parent post #' . $post_data['post_parent']);
    315 //          $parent_post = $model->get_sync_target_data(intval($post_data['post_parent']), $this->post('site_key'));
    316             $parent_post = $model->get_sync_data(intval($post_data['post_parent']), $this->source_site_key);
     318//          $parent_post = $model->get_sync_target_data(abs($post_data['post_parent']), $this->post('site_key'));
     319            $parent_post = $model->get_sync_data(abs($post_data['post_parent']), $this->source_site_key, $content_type);
    317320            if (NULL === $parent_post) {
    318321                // cannot find parent post on Target system- cannot allow push operation to continue
     
    322325            // fixup the Source's parent post id with the Target's id value
    323326SyncDebug::log(__METHOD__.'() setting parent post to #' . $parent_post->target_content_id);
    324             $post_data['post_parent'] = intval($parent_post->target_content_id);
     327            $post_data['post_parent'] = abs($parent_post->target_content_id);
    325328        }
    326329
    327330        // change references to source URL to target URL
    328331        $post_data['post_content'] = str_replace($this->source, site_url(), $post_data['post_content']);
     332        $post_data['post_excerpt'] = str_replace($this->source, site_url(), $post_data['post_excerpt']);
    329333SyncDebug::log(__METHOD__.'():' . __LINE__ . ' converting URLs ' . $this->source . ' -> ' . site_url());
    330334//      $post_data['post_content'] = str_replace($this->post('origin'), $url['host'], $post_data['post_content']);
    331         // TODO: check if we need to update anything else like `guid`, `post_excerpt`, `post_content_filtered`
     335        // TODO: check if we need to update anything else like `guid`, `post_content_filtered`
    332336
    333337        // set the user for post creation/update #70
    334         wp_set_current_user($this->_user->ID);
     338        if (isset($this->_user->ID))
     339            wp_set_current_user($this->_user->ID);
    335340
    336341        // add/update post
     
    370375            'source_content_id' => $this->source_post_id,
    371376            'target_content_id' => $this->post_id,
     377            'content_type' => $content_type,
    372378        );
    373379        $model->save_sync_data($save_sync);
     
    405411        // TOOD: probably better to remove all postmeta, then add_post_meta() for each item found
    406412//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' handling meta data');
     413        $ser = NULL;
    407414        foreach ($post_meta as $meta_key => $meta_value) {
    408415            foreach ($meta_value as $value) // loop through meta_value array
     
    413420//$_v = maybe_unserialize($_v);
    414421//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' new value=' . var_export($_v, TRUE));
     422                // change Source URL references to Target URL references in meta data
     423                $temp_val = maybe_unserialize(stripslashes ($value));
     424//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' meta key: "' . $meta_key . '" meta data: ' . $value);
     425//SyncDebug::log(' -- ' . var_export($temp_val, TRUE));
     426                if (is_array($temp_val)) {
     427                    if (NULL === $ser)
     428                        $ser = new SyncSerialize();
     429                    $fix_data = str_replace($this->source, site_url(), $value);
     430//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' fix data: ' . $fix_data);
     431                    $fix_data = $ser->fix_serialized_data($fix_data);
     432//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' fixing serialized data: ' . $fix_data);
     433                    $value = $fix_data;
     434                } else {
     435//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' not fixing serialized data');
     436                    $value = str_replace($this->source, site_url(), $value);
     437                }
    415438                update_post_meta($target_post_id, $meta_key, maybe_unserialize(stripslashes($value)));
    416439        }
     
    509532        if (isset($taxonomies['flat']) && !empty($taxonomies['flat'])) {
    510533            $tags = $taxonomies['flat'];
    511 SyncDebug::log(__METHOD__.'() found ' . count($tags) . ' taxonomy tags');
     534SyncDebug::log(__METHOD__.'():' . __LINE__ . ' found ' . count($tags) . ' taxonomy tags');
    512535            foreach ($tags as $term_info) {
    513536                $tax_type = $term_info['taxonomy'];
    514                 $term = get_term('slug', $term_info['slug'], $tax_type, OBJECT);
    515 SyncDebug::log(__METHOD__.'() found taxonomy ' . $tax_type);
     537                $term = get_term_by('slug', $term_info['slug'], $tax_type, OBJECT);
     538SyncDebug::log(__METHOD__.'():' . __LINE__ . ' found taxonomy ' . $tax_type . ': ' . var_export($term, TRUE));
    516539                if (FALSE === $term) {
    517540                    // term not found - create it
     
    521544                        'taxonomy' => $term_info['taxonomy'],
    522545                    );
     546SyncDebug::log(__METHOD__.'():' . __LINE__ . " wp_insert_term('{$term_info['name']}', {$tax_type}, " . var_export($args, TRUE) . ')');
    523547                    $ret = wp_insert_term($term_info['name'], $tax_type, $args);
    524 SyncDebug::log(__METHOD__.'() insert term [flat] result: ' . var_export($ret, TRUE));
     548SyncDebug::log(__METHOD__.'():' . __LINE__ . ' insert term [flat] result: ' . var_export($ret, TRUE));
    525549                } else {
    526 SyncDebug::log(__METHOD__.'() term already exists');
     550SyncDebug::log(__METHOD__.'():' . __LINE__ . ' term already exists');
    527551                }
    528552                $ret = wp_add_object_terms($post_id, $term_info['slug'], $tax_type);
    529 SyncDebug::log(__METHOD__.'() add [flat] object terms result: ' . var_export($ret, TRUE));
     553SyncDebug::log(__METHOD__.'():' . __LINE__ . ' add [flat] object terms result: ' . var_export($ret, TRUE));
    530554            }
    531555        }
     
    545569            $terms = $taxonomies['hierarchical'];
    546570            foreach ($terms as $term_info) {
    547                 $tax_type = $term_info['taxonomy'];
    548 SyncDebug::log(__METHOD__.'() build lineage for taxonomy: ' . $tax_type);
    549 
    550                 // first, build a lineage list of the taxonomy terms
    551                 $lineage = array();
    552                 $lineage[] = $term_info;            // always add the current term to the lineage
    553                 $parent = intval($term_info['parent']);
    554 SyncDebug::log(__METHOD__.'() looking for parent term #' . $parent);
    555                 if (isset($taxonomies['lineage'][$tax_type])) {
    556                     while (0 !== $parent) {
    557                         foreach ($taxonomies['lineage'][$tax_type] as $tax_term) {
    558 SyncDebug::log(__METHOD__.'() checking lineage for #' . $tax_term['term_id'] . ' - ' . $tax_term['slug']);
    559                             if ($tax_term['term_id'] == $parent) {
    560 SyncDebug::log(__METHOD__.'() - found term ' . $tax_term['slug'] . ' as a child of ' . $parent);
    561                                 $lineage[] = $tax_term;
    562                                 $parent = intval($tax_term['parent']);
    563                                 break;
    564                             }
    565                         }
    566                     }
    567                 } else {
    568 SyncDebug::log(__METHOD__.'() no taxonomy lineage found for: ' . $tax_type);
    569                 }
    570                 $lineage = array_reverse($lineage);             // swap array order to start loop with top-most term first
    571 SyncDebug::log(__METHOD__.'() taxonomy lineage: ' . var_export($lineage, TRUE));
    572 
    573                 // next, make sure each term in the hierarchy exists - we'll end on the taxonomy id that needs to be assigned
    574 SyncDebug::log(__METHOD__.'() setting taxonomy terms for taxonomy "' . $tax_type . '"');
    575                 $generation = $parent = 0;
    576                 foreach ($lineage as $tax_term) {
    577 SyncDebug::log(__METHOD__.'() checking term #' . $tax_term['term_id'] . ' ' . $tax_term['slug'] . ' parent=' . $tax_term['parent']);
    578                     $term = NULL;
    579                     if (0 === $parent) {
    580 SyncDebug::log(__METHOD__.'() getting top level taxonomy ' . $tax_term['slug'] . ' in taxonomy ' . $tax_type);
    581                         $term = get_term_by('slug', $tax_term['slug'], $tax_type, OBJECT);
    582                         if (is_wp_error($term) || FALSE === $term) {
    583 SyncDebug::log(__METHOD__.'() error=' . var_export($term, TRUE));
    584                             $term = NULL;                   // term not found, set to NULL so code below creates it
    585                         }
    586 SyncDebug::log(__METHOD__.'() no parent but found term: ' . var_export($term, TRUE));
    587                     } else {
    588                         $child_terms = get_term_children($parent, $tax_type);
    589 SyncDebug::log(__METHOD__.'() found ' . count($child_terms) . ' term children for #' . $parent);
    590                         if (!is_wp_error($child_terms)) {
    591                             // loop through the children until we find one that matches
    592                             foreach ($child_terms as $term_id) {
    593                                 $term_child = get_term_by('id', $term_id, $tax_type);
    594 SyncDebug::log(__METHOD__.'() term child: ' . $term_child->slug);
    595                                 if ($term_child->slug === $tax_term['slug']) {
    596                                     // found the child term
    597                                     $term = $term_child;
    598                                     break;
    599                                 }
    600                             }
    601                         }
    602                     }
    603 
    604                     // see if the term needs to be created
    605                     if (NULL === $term) {
    606                         // term not found - create it
    607                         $args = array(
    608                             'description'=> $tax_term['description'],
    609                             'slug' => $tax_term['slug'],
    610                             'taxonomy' => $tax_term['taxonomy'],
    611                             'parent' => $parent,                    // indicate parent for next loop iteration
    612                         );
    613 SyncDebug::log(__METHOD__.'() term does not exist- adding name ' . $tax_term['name'] . ' under "' . $tax_type . '" args=' . var_export($args, TRUE));
    614                         $ret = wp_insert_term($tax_term['name'], $tax_type, $args);
    615                         if (is_wp_error($ret)) {
    616                             $term_id = 0;
    617                             $parent = 0;
    618                         } else {
    619                             $term_id = intval($ret['term_id']);
    620                             $parent = $term_id;         // set the parent to this term id so next loop iteraction looks for term's children
    621                         }
    622 SyncDebug::log(__METHOD__.'() insert term [hier] result: ' . var_export($ret, TRUE));
    623                     } else {
    624 SyncDebug::log(__METHOD__.'() found term: ' . var_export($term, TRUE));
    625                         if (isset($term->term_id)) {
    626                             $term_id = $term->term_id;
    627                             $parent = $term_id;                         // indicate parent for next loop iteration
    628                         } else {
    629 SyncDebug::log(__METHOD__.'() ERROR: invalid term object');
    630                         }
    631                     }
    632                     ++$generation;
    633                 }
    634                 // the loop exits with $term_id set to 0 (error) or the child-most term_id to be assigned to the object
     571                $tax_type = $term_info['taxonomy'];         // get taxonomy name from API contents
     572                $term_id = $this->process_hierarchical_term($term_info, $taxonomies);
    635573                if (0 !== $term_id) {
    636 SyncDebug::log(__METHOD__.'() adding term #' . $term_id . ' to object ' . $post_id);
     574SyncDebug::log(__METHOD__.'():' . __LINE__ . ' adding term #' . $term_id . ' to object ' . $post_id);
    637575                    $ret = wp_add_object_terms($post_id, $term_id, $tax_type);
    638576SyncDebug::log(__METHOD__.'() add [hier] object terms result: ' . var_export($ret, TRUE));
    639577                }
    640             }
     578            } // END FOREACH
    641579        }
    642580
     
    687625                // if the $post_term assigned to the post is NOT in the $taxonomies list, it needs to be removed
    688626SyncDebug::log(__METHOD__.'() ** removing term #' . $post_term->term_id . ' ' . $post_term->slug . ' [' . $post_term->taxonomy . ']');
    689                 wp_remove_object_terms($post_id, intval($post_term->term_id), $post_term->taxonomy);
     627                wp_remove_object_terms($post_id, abs($post_term->term_id), $post_term->taxonomy);
    690628            }
    691629        }
     
    698636     */
    699637    // TODO: move this to a model class - doesn't belong in a controller class
    700     private function get_post_by_title($title)
     638/*  private function get_post_by_title($title)
    701639    {
    702640        global $wpdb;
     
    717655        }
    718656        return NULL;
    719     }
     657    } */
    720658
    721659    /**
     
    747685        // https://en.wikipedia.org/wiki/List_of_file_signatures
    748686
    749         $featured = isset($_POST['featured']) ? intval($_POST['featured']) : 0;
     687        $featured = isset($_POST['featured']) ? abs($_POST['featured']) : 0;
    750688        $path = $_FILES['sync_file_upload']['name'];
    751689
    752690        // check file type
     691        $img_type = wp_check_filetype($path);
    753692        // TODO: add validating method to SyncAttachModel class
    754         $img_type = wp_check_filetype($path);
    755         $mime_type = $img_type['type'];
     693        add_filter('spectrom_sync_upload_media_allowed_mime_type', array($this, 'filter_allowed_mime_types'), 10, 2);
    756694SyncDebug::log(__METHOD__.'() found image type=' . $img_type['ext'] . '=' . $img_type['type']);
    757         if (FALSE === strpos($mime_type, 'image/') && 'pdf' !== $img_type['ext']) {
     695        if (FALSE === apply_filters('spectrom_sync_upload_media_allowed_mime_type', FALSE, $img_type)) {
    758696            $response->error_code(SyncApiRequest::ERROR_INVALID_IMG_TYPE);
    759697            $response->send();
     
    770708//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' query results: ' . var_export($get_posts, TRUE));
    771709
    772         // TODO: move this to a model
     710        // TODO: move this to SyncAttachModel
    773711        global $wpdb;
    774712        $sql = "SELECT `ID`
     
    778716        $attachment_id = 0;
    779717        if (0 != count($res))
    780             $attachment_id = intval($res[0]);
     718            $attachment_id = abs($res[0]);
    781719SyncDebug::log(__METHOD__.'():' . __LINE__ . ' id=' . $attachment_id . ' sql=' . $stmt . ' res=' . var_export($res, TRUE));
    782720        // TODO: need to assume error and only set to success(TRUE) when file successfully processed
     
    784722
    785723        // convert source post id to target post id
    786         $source_post_id = intval($_POST['post_id']);
     724        $source_post_id = abs($_POST['post_id']);
    787725        $target_post_id = 0;
    788726        $model = new SyncModel();
    789         $sync_data = $model->get_sync_data($source_post_id, $this->source_site_key);
     727        $content_type = apply_filters('spectrom_sync_upload_media_content_type', 'post');
     728        $sync_data = $model->get_sync_data($source_post_id, $this->source_site_key, $content_type);
    790729        if (NULL !== $sync_data)
    791             $target_post_id = intval($sync_data->target_content_id);
     730            $target_post_id = abs($sync_data->target_content_id);
    792731SyncDebug::log(__METHOD__.'():' . __LINE__ . ' source id=' . $source_post_id . ' target id=' . $target_post_id);
    793732
     
    812751//          $file = media_handle_upload('sync_file_upload', $this->post('post_id', 0), array(), $overrides);
    813752//          $response->notice_code(SyncApiRequest::NOTICE_FILE_EXISTS);
     753            $this->media_id = $attachment_id;
    814754
    815755            // if it's the featured image, set that
     
    854794                wp_update_attachment_metadata($attach_id, $attach);
    855795                $response->set('post_id', $this->post('post_id'));
     796                $this->media_id = $attach_id;
    856797
    857798                // if it's the featured image, set that
    858799                if ($featured && 0 !== $target_post_id) {
    859800SyncDebug::log(__METHOD__."() set_post_thumbnail({$target_post_id}, {$attach_id})");
    860                     set_post_thumbnail($target_post_id, $attach_id /*intval($file)*/);
     801                    set_post_thumbnail($target_post_id, $attach_id /*abs($file)*/);
    861802                }
    862803            }
     
    878819            $media = new SyncMediaModel();
    879820            $media->log($media_data);
    880         }
     821
     822            // notify add-ons about media
     823            do_action('spectrom_sync_media_processed', $target_post_id, $attachment_id, $this->media_id);
     824        }
     825    }
     826
     827    /**
     828     * Filter the mime types allowed in upload_media()
     829     * @param boolean $default Current allowed state
     830     * @param array $img_type The mime type information with array keys of ['type'] and ['ext']
     831     * @return boolean TRUE to allow this mime type; otherwise FALSE
     832     */
     833    public function filter_allowed_mime_types($default, $img_type)
     834    {
     835        // TODO: use get_allowed_mime_types()
     836        // if the type contains 'image/'
     837        if (FALSE !== stripos($img_type['type'], 'image/'))
     838            return TRUE;
     839        // allow PDF files
     840        if ('pdf' === $img_type['ext'])
     841            return TRUE;
     842
     843        return $default;
    881844    }
    882845
     
    926889        return $info;
    927890    }
     891
     892    /**
     893     * Process hierarchical term. Searches for and creates taxonomy lineages in order to find child most term id that matches hierarchy.
     894     * @param array $term_info Array of term info from the Source site
     895     * @param array $taxonomies Array of taxonomies sent via API POST request
     896     * @return int 0 to indicate error or the child-most term_id to be assigned to the target
     897     */
     898    public function process_hierarchical_term($term_info, $taxonomies)
     899    {
     900        $tax_type = $term_info['taxonomy'];
     901SyncDebug::log(__METHOD__ . '() build lineage for taxonomy: ' . $tax_type);
     902
     903        // first, build a lineage list of the taxonomy terms
     904        $lineage = array();
     905        $lineage[] = $term_info;            // always add the current term to the lineage
     906        $parent = abs($term_info['parent']);
     907SyncDebug::log(__METHOD__ . '() looking for parent term #' . $parent);
     908        if (isset($taxonomies['lineage'][$tax_type])) {
     909            while (0 !== $parent) {
     910                foreach ($taxonomies['lineage'][$tax_type] as $tax_term) {
     911SyncDebug::log(__METHOD__ . '() checking lineage for #' . $tax_term['term_id'] . ' - ' . $tax_term['slug']);
     912                    if ($tax_term['term_id'] == $parent) {
     913SyncDebug::log(__METHOD__ . '() - found term ' . $tax_term['slug'] . ' as a child of ' . $parent);
     914                        $lineage[] = $tax_term;
     915                        $parent = abs($tax_term['parent']);
     916                        break;
     917                    }
     918                }
     919            }
     920        } else {
     921SyncDebug::log(__METHOD__ . '() no taxonomy lineage found for: ' . $tax_type);
     922        }
     923        $lineage = array_reverse($lineage);                // swap array order to start loop with top-most term first
     924SyncDebug::log(__METHOD__ . '() taxonomy lineage: ' . var_export($lineage, TRUE));
     925
     926        // next, make sure each term in the hierarchy exists - we'll end on the taxonomy id that needs to be assigned
     927SyncDebug::log(__METHOD__ . '() setting taxonomy terms for taxonomy "' . $tax_type . '"');
     928        $generation = $parent = 0;
     929        foreach ($lineage as $tax_term) {
     930SyncDebug::log(__METHOD__ . '() checking term #' . $tax_term['term_id'] . ' ' . $tax_term['slug'] . ' parent=' . $tax_term['parent']);
     931            $term = NULL;
     932            if (0 === $parent) {
     933SyncDebug::log(__METHOD__ . '() getting top level taxonomy ' . $tax_term['slug'] . ' in taxonomy ' . $tax_type);
     934                $term = get_term_by('slug', $tax_term['slug'], $tax_type, OBJECT);
     935                if (is_wp_error($term) || FALSE === $term) {
     936SyncDebug::log(__METHOD__ . '() error=' . var_export($term, TRUE));
     937                    $term = NULL;                    // term not found, set to NULL so code below creates it
     938                }
     939SyncDebug::log(__METHOD__ . '() no parent but found term: ' . var_export($term, TRUE));
     940            } else {
     941                $child_terms = get_term_children($parent, $tax_type);
     942SyncDebug::log(__METHOD__ . '() found ' . count($child_terms) . ' term children for #' . $parent);
     943                if (!is_wp_error($child_terms)) {
     944                    // loop through the children until we find one that matches
     945                    foreach ($child_terms as $term_id) {
     946                        $term_child = get_term_by('id', $term_id, $tax_type);
     947SyncDebug::log(__METHOD__ . '() term child: ' . $term_child->slug);
     948                        if ($term_child->slug === $tax_term['slug']) {
     949                            // found the child term
     950                            $term = $term_child;
     951                            break;
     952                        }
     953                    }
     954                }
     955            }
     956
     957            // see if the term needs to be created
     958            if (NULL === $term) {
     959                // term not found - create it
     960                $args = array(
     961                    'description' => $tax_term['description'],
     962                    'slug' => $tax_term['slug'],
     963                    'taxonomy' => $tax_term['taxonomy'],
     964                    'parent' => $parent,                    // indicate parent for next loop iteration
     965                );
     966SyncDebug::log(__METHOD__ . '() term does not exist- adding name ' . $tax_term['name'] . ' under "' . $tax_type . '" args=' . var_export($args, TRUE));
     967                $ret = wp_insert_term($tax_term['name'], $tax_type, $args);
     968                if (is_wp_error($ret)) {
     969                    $term_id = 0;
     970                    $parent = 0;
     971                } else {
     972                    $term_id = abs($ret['term_id']);
     973                    $parent = $term_id;            // set the parent to this term id so next loop iteraction looks for term's children
     974                }
     975SyncDebug::log(__METHOD__ . '() insert term [hier] result: ' . var_export($ret, TRUE));
     976            } else {
     977SyncDebug::log(__METHOD__ . '() found term: ' . var_export($term, TRUE));
     978                if (isset($term->term_id)) {
     979                    $term_id = $term->term_id;
     980                    $parent = $term_id;                            // indicate parent for next loop iteration
     981                } else {
     982SyncDebug::log(__METHOD__ . '() ERROR: invalid term object');
     983                }
     984            }
     985            ++$generation;
     986        }
     987
     988        // the loop exits with $term_id set to 0 (error) or the child-most term_id to be assigned to the object
     989        return $term_id;
     990    }
    928991}
    929992
  • wpsitesynccontent/trunk/classes/apiheaders.php

    r1400702 r1745585  
    1010    const HEADER_SOURCE = 'x-sync-source';                  // Source site's URL; used in requests
    1111    const HEADER_SITE_KEY = 'x-sync-site-key';              // Source site's site_key; used in requests
     12    const HEADER_MATCH_MODE = 'x-sync-match-mode';          // How to match Content on Target
    1213}
    1314
  • wpsitesynccontent/trunk/classes/apimodel.php

    r1492115 r1745585  
    2121
    2222        $this->options = wp_parse_args($options, $default_options);
     23
     24        // check for maintenance mode plugins
     25        $this->_maintenance_check();
    2326
    2427        add_action('init', array(&$this, 'register_api'), 1000);
     
    169172        return array_combine($keys, $values);
    170173    }
     174
     175    /**
     176     * Check for maintenance mode plugins and disable their actions that may interfere with WPSiteSync's API
     177     */
     178    private function _maintenance_check()
     179    {
     180        // first, check to see if the current request is for a WPSiteSync API call
     181        if (!isset($_GET['pagename']) || WPSiteSyncContent::API_ENDPOINT !== $_GET['pagename'])
     182            return;
     183
     184        // now we know it's a WPSiteSync API call. Detect and disable any maintenance mode type plugins
     185
     186        // look for 'WP Maintenance Mode' plugin - https://wordpress.org/plugins/wp-maintenance-mode/ - 400,000 installs
     187        if (class_exists('WP_Maintenance_Mode', FALSE)) {
     188            add_filter('option_wpmm_settings', array($this, 'filter_wpmm_options'), 10, 2);
     189            return;
     190        }
     191        // look for 'Maintenance' plugin - https://wordpress.org/plugins/maintenance/ - 300,000 installs
     192        if (class_exists('maintenance', FALSE)) {
     193            add_filter('option_maintenance_options', array($this, 'filter_maintenance_options'), 10, 2);
     194            return;
     195        }
     196        // look for 'Coming Soon' plugin - https://wordpress.org/plugins/coming-soon/ - 300,000 installs
     197        if (class_exists('SEED_CSP4', FALSE)) {
     198            add_filter('seed_csp4_get_settings', array($this, 'filter_coming_soon_options'), 10 ,1);
     199            return;
     200        }
     201
     202//die('inside ' . __METHOD__. '():' . __LINE__ . ' set=' . var_export($settings, TRUE));
     203    }
     204
     205    /**
     206     * Filter for WP Maintenance Mode plugins' configuration settings. Used to deactivate plugin behavior
     207     * @param array $value The settings to filter; called from get_option()
     208     * @param string $option The option name, in this case always 'maintenance_options'
     209     * @return array The modified settings for WP Maintenance Mode; with the maintenance mode deactivated.
     210     */
     211    public function filter_wpmm_options($value, $option = '')
     212    {
     213        $value['general']['status'] = 0;
     214        return $value;
     215    }
     216
     217    /**
     218     * Filter for the Maintenance plugin's configuration settings. Used to turn off maintenance mode.
     219     * @param array $value The settings to filter; called from get_option()
     220     * @param string $option The option name, in this case always 'maintenance_options'
     221     * @return array The modified settings for Maintenance; with the maintenance mode turned off.
     222     */
     223    public function filter_maintenance_options($value, $option = '')
     224    {
     225        if (isset($value['state']) && !empty($value['state']))
     226            $value['state'] = 0;    // this makes load_maintenance_page() not load the maintenance page
     227        return $value;
     228    }
     229
     230    /**
     231     * Filter for the Coming Soon plugin's configuration settings. Used to turn off it's features
     232     * @param array $settings The Coming Soon plugins settings
     233     * @return array The modified settings, with the 'status' value set to 0 to disable it
     234     */
     235    public function filter_coming_soon_options($settings)
     236    {
     237        $settings['status'] = '0';
     238        return $settings;
     239    }
    171240}
    172241
  • wpsitesynccontent/trunk/classes/apirequest.php

    r1573030 r1745585  
    3434    const ERROR_CANNOT_WRITE_TOKEN = 27;
    3535    const ERROR_UPLOAD_NO_CONTENT = 28;
     36    const ERROR_PHP_ERROR_ON_TARGET = 29;
    3637
    3738    const NOTICE_FILE_EXISTS = 1;
     
    4344    private $_source_domain = NULL;             // domain sending the post information
    4445
    45     private $_response = NULL;
     46    private $_response = NULL;                  // the SyncApiResponse instance for the current request
    4647
    4748    private $_user_id = 0;
     
    124125        if (is_wp_error($data) || $response->has_errors()) {
    125126            // an error occured somewhere along the way. report it and return
    126 //          $response->error_code(intval($res->get_message()));
     127//          $response->error_code(abs($res->get_message()));
    127128            return $response;
    128129        }
     
    143144//      $remote_args['headers'][self::HEADER_SITE_KEY] = WPSiteSyncContent::get_option('site_key'); // $model->generate_site_key();
    144145        $remote_args['headers'][self::HEADER_SITE_KEY] = SyncOptions::get('site_key'); // $model->generate_site_key();
     146        $remote_args['headers'][self::HEADER_MATCH_MODE] = SyncOptions::get('match_mode', 'title');
    145147//SyncDebug::log(__METHOD__.'() plugin sitekey=' . WPSiteSyncContent::get_option('site_key') . ' // option sitekey=' . SyncOptions::get('site_key'));
    146148        if (!isset($remote_args['timeout']))
     
    166168            // validate the host and credentials
    167169            if (!($request['response']['code'] >= 200 && $request['response']['code'] < 300)) {
    168                 $response->error_code(self::ERROR_BAD_POST_RESPONSE, intval($request['response']['code']));
     170                $response->error_code(self::ERROR_BAD_POST_RESPONSE, abs($request['response']['code']));
    169171            } else if (!isset($request['headers'][self::HEADER_SYNC_VERSION])) {
    170172                $response->error_code(self::ERROR_NOT_INSTALLED);
     
    179181            // API request went through, check for error_code returned in JSON results
    180182            $request_body = $this->_adjust_response_body($request['body']);
    181 //SyncDebug::log(__METHOD__.'():' . __LINE__ . ' response body: ' . $request_body); // $request['body']);
     183SyncDebug::log(__METHOD__.'():' . __LINE__ . ' response body: ' . $request_body); // $request['body']);
    182184            $response->response = json_decode($request_body /*$request['body']*/);
    183185            // TODO: convert error/notice codes into strings at this point.
    184186SyncDebug::log(__METHOD__.'():' . __LINE__ . ' received response from Target for "' . $action . '":');
    185 SyncDebug::log(var_export($response->response, TRUE));
     187//SyncDebug::log(__METHOD__.'():' . __LINE__ .' body: ' . $request_body);
     188//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' - ' . var_export($response->response, TRUE));
    186189
    187190            // examine the Target response's error codes and assign them to the local system's response object
     
    217220//$response->response->error_code) { //
    218221//              if (0 === $response->get_error_code()) {
    219                 if (0 === intval($response->response->error_code)) {
     222                if (0 === abs($response->response->error_code)) {
    220223                    $response->success(TRUE);
    221224                    // if it was an authentication request, store the auth cookies in user meta
     
    227230                    case 'auth':                    // no logging, but add to source table and set target site_key
    228231                        if (isset($response->response->data)) {
     232                            // TODO: deprecated
    229233                            update_user_meta($this->_user_id, 'spectrom_site_cookies', $response->response->data->auth_cookie);
    230234                            update_user_meta($this->_user_id, 'spectrom_site_nonce', $response->response->data->access_nonce);
     
    287291    private function _adjust_response_body($body)
    288292    {
     293        $body = trim($body);
     294        $error = FALSE;
    289295        if ('{' !== $body[0]) {
     296SyncDebug::log(__METHOD__.'() found extra data in response content: ' . var_export($body, TRUE));
     297            // checks to see that the JSON payload starts with '{"error_code":' - which is the initial data send in a SyncApiResponse object
    290298            $pos = strpos($body, '{"error_code":');
    291299            if (FALSE !== $pos)
    292300                $body = substr($body, $pos);
    293             $pos = strpos($body, '"}}');
    294             if (FALSE !== $pos)
    295                 $body = substr($body, 0, $pos + 3);
     301//          $pos = strpos($body, '"}}');
     302//          if (FALSE !== $pos)
     303//              $body = substr($body, 0, $pos + 3);
     304            // make sure that a '}' is the last character of the response data
     305            $pos = strrpos($body, '}');
     306            if ($pos !== strlen($body) - 1)
     307                $body = substr($body, 0, $pos + 1);
     308SyncDebug::log(__METHOD__.'():' . __LINE__ . ' response body=' . var_export($body, TRUE));
     309        }
     310        if (FALSE === strpos($body, '{')) {
     311            // no JSON data present in response
     312            $body = '{"error_code":' . self::ERROR_PHP_ERROR_ON_TARGET . ',"has_errors":1,"success":0,"data":{"error":"none"}}';
    296313        }
    297314        return $body;
     
    545562            $auth = new SyncAuth();
    546563            $data['password'] = $auth->encode_password($data['password'], $data['host']);
     564
     565            $parts = explode(':', $data['password']);
     566            $this->_target_data['password'] = $data['password'] = $parts[0];
     567            $this->_target_data['encode'] = $data['encode'] = $parts[1];
    547568        }
    548569//SyncDebug::log(__METHOD__.'() data: ' . var_export($data, TRUE));
     
    604625    private function _push($data)
    605626    {
    606         $post_id = intval($data['post_id']);
     627        $post_id = abs($data['post_id']);
    607628        return $this->get_push_data($post_id, $data);
    608629    }
     
    682703
    683704        // if no content, there's nothing to do
    684         if (empty($content))
    685             return;
     705//      if (empty($content))
     706//          return;
    686707
    687708        // sometimes the insert media into post doesn't add a space...this will hopefully fix that
    688709        $content = str_replace('alt="', ' alt="', $content);
     710        if (empty($content))
     711            return TRUE;
    689712
    690713        // TODO: add try..catch
     
    733756            foreach ($classes as $class) {
    734757                if ('wp-image-' === substr($class, 0, 9)) {
    735                     $img_id = intval(substr($class, 9));
     758                    $img_id = abs(substr($class, 9));
    736759                    $img_post = get_post($img_id, OBJECT);
    737760                    if (NULL !== $img_post) {
     
    756779                    }
    757780                }
    758                 if ($this->send_media($src_attr, $post_id, $post_thumbnail_id, $img_id))
    759                     return FALSE;
     781//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' calling send_media("' . $src_attr . '", ' . $post_id . ', ' . $post_thumbnail_id . ', ' . $img_id . ')');
     782//              if ($this->send_media($src_attr, $post_id, $post_thumbnail_id, $img_id))
     783//                  return FALSE;
     784                $this->send_media($src_attr, $post_id, $post_thumbnail_id, $img_id);
    760785            }
    761786        }
     
    783808                    }
    784809                }
    785                 $this->send_media($href_attr, $post_id, $post_thumbnail_id, $attach_id);
     810                if (0 !== $attach_id)           // https://wordpress.org/support/topic/bugs-68/
     811                    $this->send_media($href_attr, $post_id, $post_thumbnail_id, $attach_id);
    786812            } else {
    787813//SyncDebug::log(' - no attachment to send');
     
    792818        if ('' !== $post_thumbnail_id) {
    793819SyncDebug::log(__METHOD__.'() featured image:');
    794             $img = wp_get_attachment_image_src($post_thumbnail_id, 'large');
     820            $img = wp_get_attachment_image_src($post_thumbnail_id, 'full');
    795821SyncDebug::log('  src=' . var_export($img, TRUE));
    796822            // convert site url to relative path
     
    819845    public function set_source_domain($domain)
    820846    {
     847//SyncDebug::log(__METHOD__.'() domain=' . $domain);
    821848        // sanitize value to remove protocol and slashes
    822         $this->_source_domain = parse_url($domain, PHP_URL_HOST);
     849        if (FALSE !== stripos($domain, 'http') || FALSE !== strpos($domain, '/'))
     850            $domain = parse_url($domain, PHP_URL_HOST);
     851
     852//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' domain: ' . $domain);
     853        $this->_source_domain = $domain;
    823854    }
    824855
     
    879910//          'name' => 'value',
    880911            'post_id' => $post_id,
    881             'featured' => intval($featured),
     912            'featured' => abs($featured),
    882913            'boundary' => wp_generate_password(24),     // TODO: remove and generate when formatting POST content in _media()
    883914            'img_path' => dirname($file_path),
     
    892923            'attach_alt' => (NULL !== $attach_post) ? $attach_alt : '',
    893924        );
     925        // allow extensions to include data in upload_media operations
     926        $post_fields = apply_filters('spectrom_sync_upload_media_fields', $post_fields);
     927SyncDebug::log(__METHOD__.'():' . __LINE__ . ' fields: ' . var_export($post_fields, TRUE));
     928
    894929//$post_fields['content-len'] = strlen($post_fields['contents']);
    895930//$post_fields['content-type'] = gettype($post_fields['contents']);
     
    937972        case self::ERROR_BAD_NONCE:             $error = __('Unable to validate AJAX request.', 'wpsitesynccontent'); break;
    938973        case self::ERROR_UNRESOLVED_PARENT:     $error = __('Content has a Parent Page that has not been Sync\'d.', 'wpsitesynccontent'); break;
    939         case self::ERROR_NO_AUTH_TOKEN:         $error = __('Unable to authentication with Target site. Please re-enter credentials for this site.', 'wpsitesynccontent'); break;
     974        case self::ERROR_NO_AUTH_TOKEN:         $error = __('Unable to authenticate with Target site. Please re-enter credentials for this site.', 'wpsitesynccontent'); break;
    940975        case self::ERROR_NO_PERMISSION:         $error = __('User does not have permission to perform Sync. Check configured user on Target.', 'wpsitesynccontent'); break;
    941976        case self::ERROR_INVALID_IMG_TYPE:      $error = __('The image uploaded is not a valid image type.', 'wpsitesynccontent'); break;
     
    944979        case self::ERROR_CANNOT_WRITE_TOKEN:    $error = __('Cannot write authentication token.', 'wpsitesynccontent'); break;
    945980        case self::ERROR_UPLOAD_NO_CONTENT:     $error = __('Attachment upload failed. No content found; is there a broken link?', 'wpsitesynccontent'); break;
     981        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;
    946982
    947983        default:
     
    9831019    public static function get_error($error_code, $error_data = NULL)
    9841020    {
    985         $error_code = intval($error_code);
     1021        $error_code = abs($error_code);
    9861022        $msg = self::error_code_to_string($error_code);
    9871023        if (NULL !== $error_data)
     
    9901026        return new WP_Error($error_code, $msg);
    9911027    }
     1028
     1029    /**
     1030     * Returns the SyncApiResponse instance used to reply to the current API request
     1031     * @return SyncApiResponse instance
     1032     */
     1033    public function get_response()
     1034    {
     1035        return $this->_response;
     1036    }
    9921037}
    9931038
  • wpsitesynccontent/trunk/classes/apiresponse.php

    r1573030 r1745585  
    4646    public function has_errors()
    4747    {
    48         if (count($this->errors) || count($this->validation) || 0 !== $this->error_code)
     48        if (0 !== $this->error_code || count($this->errors) || count($this->validation))
    4949            return TRUE;
    5050        return FALSE;
     
    150150        // only allow one error code
    151151        if (0 === $this->error_code) {
    152             $this->error_code = intval($code);
     152            $this->error_code = abs($code);
    153153            if (NULL !== $data)
    154154                $this->error_data = $data;
  • wpsitesynccontent/trunk/classes/auth.php

    r1560226 r1745585  
    142142//SyncDebug::log(' - key: ' . $key);
    143143
    144         $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
    145         $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    146         $encrypted = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, utf8_encode($password), MCRYPT_MODE_ECB, $iv);
    147         $encoded = base64_encode($encrypted);
     144        $left = $right = '';
     145        if (function_exists('mcrypt_get_iv_size')) {
     146            $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
     147            $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
     148            $encrypted = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, utf8_encode($password), MCRYPT_MODE_ECB, $iv);
     149            $left = base64_encode($encrypted);
     150        }
     151
     152        $right = $this->enc_str($password, $key);
     153
     154        $encoded = $left . ':' . $right;
    148155        return $encoded;
    149156    }
     
    160167        $key = $this->get_key($target);
    161168//SyncDebug::log('  key: ' . $key);
    162         $decoded = base64_decode($password);
     169
     170        $left = $password;
     171        if (!empty($_POST['encode']))
     172            $right = $_POST['encode'];
     173
     174        $cleartext = NULL;
     175        if (function_exists('mcrypt_get_iv_size')) {
     176            $decoded = base64_decode($left);
    163177//SyncDebug::log('  decoded: ' . $decoded);
    164178
    165         $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
    166         $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    167         $cleartext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $decoded, MCRYPT_MODE_ECB, $iv);
     179            $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
     180            $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
     181            $cleartext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $decoded, MCRYPT_MODE_ECB, $iv);
    168182//SyncDebug::log('  cleartext: ' . var_export($cleartext, TRUE));
    169         $cleartext = trim($cleartext, "\0");
     183            $cleartext = trim($cleartext, "\0");
     184//SyncDebug::log(__METHOD__.'() decoded left "' . $left . '" into "' . $cleartext . '"');
     185        }
     186        if (empty($cleartext) && !empty($right)) {
     187            $cleartext = $this->dec_str($right, $key);
     188//SyncDebug::log(__METHOD__.'() decoded right "' . $right . '" into "' . $cleartext . '"');
     189        }
     190
    170191//SyncDebug::log('  cleartext: ' . var_export($cleartext, TRUE));
    171192        return $cleartext;
     193    }
     194
     195    /**
     196     * Encrypts a string
     197     * @param type $string
     198     * @param type $key
     199     * @return type
     200     */
     201    private function enc_str($string, $key)
     202    {
     203        $result = '';
     204        for ($i = 0; $i < strlen($string); ++$i) {
     205            $char = substr($string, $i, 1);
     206            $keychar = substr($key, ($i % strlen($key)) - 1, 1);
     207            $char = chr(ord($char) + ord($keychar));
     208            $result .= $char;
     209        }
     210
     211        return base64_encode($result);
     212    }
     213
     214    /**
     215     * Decrypts a string
     216     * @param type $string
     217     * @param type $key
     218     * @return type
     219     */
     220    function dec_str($string, $key)
     221    {
     222        $result = '';
     223        $string = base64_decode($string);
     224
     225        for ($i = 0; $i < strlen($string); ++$i) {
     226            $char = substr($string, $i, 1);
     227            $keychar = substr($key, ($i % strlen($key)) - 1, 1);
     228            $char = chr(ord($char) - ord($keychar));
     229            $result .= $char;
     230        }
     231
     232        return $result;
    172233    }
    173234
  • wpsitesynccontent/trunk/classes/debug.php

    r1560226 r1745585  
    44{
    55    const DEBUG = TRUE;
     6
     7    public static $_debug = FALSE;
    68
    79    public static $_debug_output = FALSE;
     
    3234    public static function log($msg = NULL, $backtrace = FALSE)
    3335    {
    34         if (!defined('WP_DEBUG') || !WP_DEBUG)
     36        if (!self::$_debug && !defined('WP_DEBUG') || !WP_DEBUG)
    3537            return;
    3638
     
    5860
    5961            if ($backtrace) {
    60                 $callers = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
     62                $callers = debug_backtrace(defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE);
    6163                array_shift($callers);
    6264                $path = dirname(dirname(dirname(plugin_dir_path(__FILE__)))) . DIRECTORY_SEPARATOR;
  • wpsitesynccontent/trunk/classes/licensing.php

    r1560226 r1745585  
    125125            );
    126126//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' sending ' . var_export($api_params, TRUE) . ' to ' . $this->_get_api_url());
    127             $response = wp_remote_get(add_query_arg($api_params, $this->_get_api_url()), array('timeout' => 15, 'sslverify' => FALSE));
     127            $response = wp_remote_get($remote_url = add_query_arg($api_params, $this->_get_api_url()), array('timeout' => 15, 'sslverify' => FALSE));
    128128            if (is_wp_error($response)) {
    129129                self::$_status[$slug] = FALSE;
     
    133133
    134134            // check response
    135             $license_data = json_decode(wp_remote_retrieve_body($response));
     135            $response_body = wp_remote_retrieve_body($response);
     136            if (!empty($response_body)) {
     137                $license_data = json_decode($response_body);
    136138//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' license data=' . var_export($license_data, TRUE));
    137             if ('valid' === $license_data->license) {
    138                 // this license is still valid
    139                 self::$_licenses[$slug . '_st'] = self::STATE_ACTIVE;
    140                 self::$_licenses[$slug . '_tr'] = time() + self::LICENSE_TTL;
    141                 self::$_licenses[$slug . '_vl'] = md5($slug . $name);
     139                if ('valid' === $license_data->license) {
     140                    // this license is still valid
     141                    self::$_licenses[$slug . '_st'] = self::STATE_ACTIVE;
     142                    self::$_licenses[$slug . '_tr'] = time() + self::LICENSE_TTL;
     143                    self::$_licenses[$slug . '_vl'] = md5($slug . $name);
    142144//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' [' . $slug . '] vl=' . self::$_licenses[$slug . '_vl']);
     145                } else {
     146                    // this license is no longer valid
     147                    self::$_licenses[$slug . '_st'] = self::STATE_UNKNOWN;
     148                    self::$_licenses[$slug . '_vl'] = '';
     149                }
     150                self::$_dirty = TRUE;
     151                $this->save_licenses();
    143152            } else {
    144                 // this license is no longer valid
    145                 self::$_licenses[$slug . '_st'] = self::STATE_UNKNOWN;
    146                 self::$_licenses[$slug . '_vl'] = '';
    147             }
    148             self::$_dirty = TRUE;
    149             $this->save_licenses();
     153SyncDebug::log(__METHOD__.'():' . __LINE__ . ' slug=' . $slug . ' url=' . $remote_url . ' with params: ' . var_export($api_params, TRUE) . ' returned: ' . $response_body);
     154            }
    150155//SyncDebug::log(__METHOD__.'():' . __LINE__ . ' setting dirty flag');
    151156        }
  • wpsitesynccontent/trunk/classes/model.php

    r1492115 r1745585  
    77    private $_sync_table = NULL;
    88    private static $_taxonomies = array();
     9    private $_edit_user_id = FALSE;
    910
    1011    public function __construct()
     
    4344
    4445    /**
     46     * Removes all WPSiteSync data for a given post ID
     47     * @param int $post_id The ID of the Content to remove. Can be used on Source or Target; uses site key to distinguish context.
     48     */
     49    public function remove_all_sync_data($post_id)
     50    {
     51        $site_key = SyncOptions::get('site_key');
     52
     53        global $wpdb;
     54        $sql = "DELETE FROM `{$this->_sync_table}`
     55            WHERE (`source_content_id`=%d AND `site_key`=%s) OR
     56                (`target_content_id`=%d AND `target_site_key`=%s)";
     57        $sql = $wpdb->prepare($sql, $post_id, $site_key, $post_id, $site_key);
     58SyncDebug::log(__METHOD__.'() sql=' . $sql);
     59        $wpdb->query($sql);
     60    }
     61
     62    /**
    4563     * Saves a sync record to the database.
    4664     * @param array $data The sync data.
     
    7896        } else {
    7997SyncDebug::log(__METHOD__.'() inserting ' . $data['source_content_id']);
    80             $wpdb->insert($this->_sync_table, $data);
     98            $res = $wpdb->insert($this->_sync_table, $data);
     99            // TODO: when insert fails, display error message/recover
     100//SyncDebug::log(__METHOD__.'() res=' . var_export($res, TRUE));
     101//if (FALSE === $res)
     102//  SyncDebug::log(__METHOD__.'() sql=' . $wpdb->last_query);
    81103        }
    82104    }
     
    165187
    166188    /**
     189     * Updates an existing record in the table with new information
     190     * @global type $wpdb
     191     * @param type $where
     192     * @param type $update
     193     */
     194    public function update($where, $update)
     195    {
     196        global $wpdb;
     197        $wpdb->update($this->_sync_table, $update, $where);
     198    }
     199
     200    /**
    167201     * Build the array of post data to be used in a Sync call
    168202     * @param int $post_id The post ID
     
    249283            foreach ($post_meta as $key => $value) {
    250284                // remove any '_spectrom_sync_' meta data and the '_edit...' meta data
    251                 if (strpos('_spectrom_sync_', $key) || in_array($key, $skip_keys)) {
     285                if ('_spectrom_sync_' === substr($key, 0, 15) || in_array($key, $skip_keys)) {
    252286                    unset($post_meta[$key]);
    253287                    continue;
  • wpsitesynccontent/trunk/classes/options.php

    r1446190 r1745585  
    2323     * 'min_role' = minimum role allowed to perform SYNC operations
    2424     * 'remove' = remove settings/tables on plugin deactivation
     25     * 'match_mode' = method for matching content on Target: 'title', 'slug', 'id'
    2526     */
    2627
  • wpsitesynccontent/trunk/classes/settings.php

    r1560226 r1745585  
    155155        echo '<input type="hidden" name="sync-settings-tab" value="', esc_attr($this->_tab), '" />';
    156156        do_settings_sections('sync');
    157         submit_button(); 
     157        submit_button();
    158158        echo '</form>';
    159159        echo '<p>', __('WPSiteSync for Content Site key: ', 'wpsitesynccontent'), '<b>', SyncOptions::get('site_key'), '</b></p>';
     
    210210                'min_role' => '',
    211211                'remove' => '0',
     212                'match_mode' => 'title',
    212213            )
    213214        );
     
    308309        );
    309310
     311        switch ($data['match_mode']) {
     312        case 'slug':        $desc = __('Slug - Search for matching Content on Target by Post Slug.', 'wpsitesynccontent');
     313            break;
     314        case 'id':          $desc = __('ID - Search for matching Content on Target by Post ID.', 'wpsitesynccontent');
     315            break;
     316        case 'title':       $desc = __('Post Title - Search for matching Content on Target by Post Title.', 'wpsitesynccontent');
     317        default:
     318            break;
     319        }
     320
     321        add_settings_field(
     322            'match_mode',                                       // field id
     323            __('Content Match Mode:', 'wpsitesynccontent'),     // title
     324            array($this, 'render_select_field'),            // callback
     325            self::SETTINGS_PAGE,                            // page
     326            $section_id,                                    // section id
     327            array(                                          // args
     328                'name' => 'match_mode',
     329                'value' => $data['match_mode'],
     330                'options' => array(
     331                    'title' => __('Post Title', 'wpsitesynccontent'),
     332                    'slug' => __('Post Slug', 'wpsitesynccontent'),
     333//                  'id' => __('Post ID', 'wpsitesynccontent'),
     334                ),
     335                'description' => $desc,
     336            )
     337        );
     338
    310339/*
    311340        add_settings_field(
     
    390419            $args['name'], $args['name'], esc_attr($args['value']));
    391420        foreach ($args['options'] as $key => $value) {
    392             echo '<option value="', esc_attr($key), '">', esc_html($value), '</option>';
     421            echo '<option value="', esc_attr($key), '" ', selected($key, $args['value']), '>', esc_html($value), '</option>';
    393422        }
    394423        echo '</select>';
     424
     425        if (!empty($args['description']))
     426            echo '<p><em>', esc_html($args['description']), '</em></p>';
    395427    }
    396428
     
    485517        $missing_error = FALSE;
    486518        $re_auth = FALSE;
     519
    487520        foreach ($values as $key => $value) {
    488521//SyncDebug::log(" key={$key}  value=[{$value}]");
     
    491524            } else {
    492525                if ('host' === $key) {
    493                     if (FALSE === $this->_is_valid_url($value)) {
    494                         add_settings_error('sync_options_group', 'invalid-url', __('Invalid URL.', 'wpsitesynccontent'));
     526                    // check to see if 'host' is changing and force use of password
     527                    if ($value !== $settings['host'] && empty($values['password'])) {
     528                        add_settings_error('sync_host_password', 'missing-password', __('When changing Target site, a password is required.', 'wpsitesynccontent'));
    495529                        $out[$key] = $settings[$key];
    496530                    } else {
    497                         $out[$key] = $value;
    498                         if ($out[$key] !== $settings[$key])
    499                             $re_auth = TRUE;
     531                        if (FALSE === $this->_is_valid_url($value)) {
     532                            add_settings_error('sync_options_group', 'invalid-url', __('Invalid URL.', 'wpsitesynccontent'));
     533                            $out[$key] = $settings[$key];
     534                        } else {
     535                            $out[$key] = $this->_normalize_url($value);
     536                            if ($out[$key] !== $settings[$key])
     537                                $re_auth = TRUE;
     538                        }
     539                    }
     540                } else if ('username' === $key) {
     541                    // TODO: refactor so that 'host' and 'username' password checking is combined
     542                    // check to see if 'username' is changing and force use of password
     543                    if ($value !== $settings['username'] && empty($values['password'])) {
     544                        add_settings_error('sync_username_password', 'missing-password', __('When changing Username, a password is required.', 'wpsitesynccontent'));
     545                        $out[$key] = $settings[$key];
     546                    } else {
     547                        if (!empty($value)) {
     548                            if ($value !== $settings[$key])
     549                                $re_auth = TRUE;
     550                            $out[$key] = $value;
     551                        } else
     552                            $out[$key] = $settings['username'];
    500553                    }
    501554                } else if (0 === strlen(trim($value))) {
     
    511564                } else {
    512565                    $out[$key] = $value;
    513                     if ('username' === $key && $out[$key] !== $settings[$key])
    514                         $re_auth = TRUE;
     566//                  if ('username' === $key && $out[$key] !== $settings[$key])
     567//                      $re_auth = TRUE;
    515568                }
    516569            }
     
    578631
    579632    /**
     633     * Normalizes the Target url, removes usernames, passwords, queries and fragments and forces trailing slash only when path is present.
     634     * @param string $url The URL to be normalized
     635     * @return string The normalized URL
     636     */
     637    private function _normalize_url($url)
     638    {
     639        $parts = parse_url($url);
     640//SyncDebug::log(__METHOD__.'() parts=' . var_export($parts, TRUE));
     641        $ret = $parts['scheme'] . '://' . $parts['host'];
     642        if (!empty($parts['port']))
     643            $ret .= ':' . $parts['port'];
     644        $path = isset($parts['path']) ? trim($parts['path'], '/') : '';
     645        if (!empty($path))
     646            $ret .= '/' . $path . '/';
     647        return $ret;
     648    }
     649
     650    /**
    580651     * Callback for adding contextual help to Sync Settings page
    581652     */
     
    605676                '<p>' . __('<strong>Username on Target</strong>: Enter the Administrator username for the Target website.', 'wpsitesynccontent') . '</p>' .
    606677                '<p>' . __('<strong>Password on Target</strong>: Enter the Administrator password for the Target website.', 'wpsitesynccontent') . '</p>' .
    607                 '<p>' . __('<strong>Strict Mode:</strong>: Select if WordPress and WPSiteSync for Content should be the same versions on the Source and the Target.', 'wpsitesynccontent') . '</p>'
     678                '<p>' . __('<strong>Strict Mode</strong>: Select if WordPress and WPSiteSync for Content should be the same versions on the Source and the Target.', 'wpsitesynccontent') . '</p>' .
     679                '<p>' . __('<strong>Match Mode</strong>: How WPSiteSync should match posts on the Target. You can select "Post Title" (default), or "Post Slug" to match Content by Title or Slug.', 'wpsitesynccontent') . '</p>'
    608680//              '<p>' . __('<strong>Authentication Salt:</strong>: Enter a salt to use when Content is sent to current site or leave blank.', 'wpsitesynccontent') . '</p>' .
    609681//              '<p>' . __('<strong>Minimum Role allowed to SYNC Content</strong>: Select minimum role of user who can Sync Content to current site.', 'wpsitesynccontent') . '</p>'
  • wpsitesynccontent/trunk/classes/sourcesmodel.php

    r1560226 r1745585  
    5656            $username = $res->auth_name;
    5757            $user = get_user_by('login', $username);
     58            // if lookup by login name failed, try email address #118
     59            if (FALSE === $user)
     60                $user = get_user_by('email', $username);
    5861            if (FALSE !== $user)
    5962                return $user;
  • wpsitesynccontent/trunk/install/activate.php

    r1510336 r1745585  
    124124        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    125125
    126         $charset_collate = '';
    127         if (!empty($wpdb->charset))
    128             $charset_collate = " DEFAULT CHARACTER SET {$wpdb->charset} ";
     126//      $charset_collate = '';
     127//      if (!empty($wpdb->charset))
     128//          $charset_collate = " DEFAULT CHARACTER SET {$wpdb->charset} ";
    129129
    130130        // determine default collation for tables being created
    131         $collate = NULL;
    132         if (defined('DB_COLLATE'))
    133             $collate = DB_COLLATE;                          // if the constant is declared, use it
    134         if ('utf8_unicode_ci' === $collate)                 // fix for CREATE TABLEs on WPEngine
    135             $collate = 'utf8mb4_unicode_ci';
    136         if (empty($collate) && !empty($wpdb->collate))      // otherwise allow wpdb class to specify
    137             $collate = $wpdb->collate;
    138         if (!empty($collate))
    139             $charset_collate .= " COLLATE {$collate} ";
     131//      $collate = NULL;
     132//      if (defined('DB_COLLATE'))
     133//          $collate = DB_COLLATE;                          // if the constant is declared, use it
     134//      if ('utf8_unicode_ci' === $collate)                 // fix for CREATE TABLEs on WPEngine
     135//          $collate = 'utf8mb4_unicode_ci';
     136//      if (empty($collate) && !empty($wpdb->collate))      // otherwise allow wpdb class to specify
     137//          $collate = $wpdb->collate;
     138//      if (!empty($collate))
     139//          $charset_collate .= " COLLATE {$collate} ";
     140        $charset_collate = $wpdb->get_charset_collate();
    140141
    141142        $aTables = $this->get_table_data();
  • wpsitesynccontent/trunk/readme.txt

    r1573030 r1745585  
    44Tags: attachments, content, content sync, data migration, desktopserver, export, import, migrate content, moving data, staging, synchronization, taxonomies
    55Requires at least: 3.5
    6 Tested up to: 4.7
     6Requires PHP: 5.3.1
     7Tested up to: 4.8.2
    78Stable tag: trunk
    89License: GPLv2 or later
     
    111112
    112113== Changelog ==
     114= 1.3.2 - Oct 12, 2017 =
     115* fix: improve checking for sync-specific meta data to be ignored
     116* fix: only set user id if available in SyncApiController->push()
     117* fix: improved code that recovers from errors displayed within JSON response data
     118* fix: set $media_id property during 'upload_media' API calls to media type after create/update of image
     119* fix: adjust parameter for wp_get_attachment_image_src()
     120* fix: check parameter passed to debug_backtrace() in case of old (< 5.3.6) versions of PHP
     121* fix: handle empty post content and featured images correctly (Thanks to Lucas C.)
     122* fix: correct taxonomy term lookup on Target to preserve naming (Thanks to Calvin C.)
     123* fix: when changing Target or Username configs, require passwords (Thanks to Erin M.)
     124* fix: javascript compatibility issue with Visual Composer backend editor (Thanks to Carlos)
     125* fix: set taxonmy type for each taxonomy entries when processing hierarchical taxonomies
     126* fix: recover and continue from failures of media attachments rather than aborting
     127* fix: add Source to Target URL fixups in meta data and excerpt (Thanks to Bryan A.)
     128* enhancement: add hook to notify add-ons that images have completed processing
     129* enhancement: allow add-ons to modify HTTP post content during 'upload_media' API calls
     130* enhancement: allow authentication to check for email address, in addition to user name
     131* enhancement: add detection and circumvention of 'Coming Soon' / 'Maintenance Mode' plugins during API calls
     132* enhancement: allow add-ons to modify allowed mime types during 'upload_media' API calls
     133* enhancement: allow add-ons to modify `content_type` column in sync table
     134* enhancement: make display of error messages via UI easier
     135* enhancement: add callback to remove data from WPSS tables when content is deleted
     136* enhancement: add appropriate headers for AJAX responses
     137* enhancement: add 'match-mode' option to allow users to perform new content lookups on target by post title or slug; deprecate SyncApiController::get_post_by_title()
     138* enhancement: add fallback encryption when mcrypt is not available
     139
    113140= 1.3.1 - Jan 11, 2017 =
    114141* Fix: add placeholder file to force creation of languages/ directory.
  • wpsitesynccontent/trunk/wpsitesynccontent.php

    r1573030 r1745585  
    66Author: WPSiteSync
    77Author URI: http://wpsitesync.com
    8 Version: 1.3.1
     8Version: 1.3.2
    99Text Domain: wpsitesynccontent
    1010Domain path: /language
     
    2525    class WPSiteSyncContent
    2626    {
    27         const PLUGIN_VERSION = '1.3.1';
     27        const PLUGIN_VERSION = '1.3.2';
    2828        const PLUGIN_NAME = 'WPSiteSyncContent';
    2929
     
    4747            register_deactivation_hook(__FILE__, array(&$this, 'deactivate'));
    4848
    49             add_action('plugins_loaded', array(&$this, 'endpoints_init'));
     49            add_action('plugins_loaded', array(&$this, 'endpoints_init'), 1);
    5050            // don't need the wp_ajax_noprov callback- AJAX calls are always within the admin
    5151            add_action('wp_ajax_spectrom_sync', array(&$this, 'check_ajax_query'));
     
    242242            return $filename;
    243243        }
     244
    244245        /**
    245246         * Callback for 'upgrader_pre_download' filter called in WP_Upgrader->download_package().
Note: See TracChangeset for help on using the changeset viewer.