Plugin Directory

Changeset 3475673


Ignore:
Timestamp:
03/05/2026 01:17:41 PM (4 weeks ago)
Author:
api2cartdev
Message:

Updated bridge to 198 version. Added Polylang multilingual plugin support.

Location:
api2cart-bridge-connector/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • api2cart-bridge-connector/trunk/api2cart-bridge-connector.php

    r3439288 r3475673  
    55Author: API2Cart
    66Author URI: https://api2cart.com/
    7 Version: 3.0.10
     7Version: 3.0.11
    88*/
    99
  • api2cart-bridge-connector/trunk/bridge2cart/bridge.php

    r3439288 r3475673  
    441441    public function getAdapterPath( $cartType ) {
    442442        return A2CBC_STORE_BASE_DIR . A2CBC_BRIDGE_DIRECTORY_NAME . DIRECTORY_SEPARATOR . 'app' .
    443                      DIRECTORY_SEPARATOR . 'class' . DIRECTORY_SEPARATOR . 'config_adapter' . DIRECTORY_SEPARATOR . $cartType . '.php';
     443            DIRECTORY_SEPARATOR . 'class' . DIRECTORY_SEPARATOR . 'config_adapter' . DIRECTORY_SEPARATOR . $cartType . '.php';
    444444    }
    445445
     
    766766
    767767    private $_wpmlEnabled = false;
     768
     769    private $_polylangEnabled = false;
    768770
    769771    /**
     
    929931
    930932        if ( file_exists( $pluginsDir . DIRECTORY_SEPARATOR . 'shopp' . DIRECTORY_SEPARATOR . 'Shopp.php' )
    931                  || file_exists( $pluginsDir . DIRECTORY_SEPARATOR . 'wp-e-commerce' . DIRECTORY_SEPARATOR . 'editor.php' )
     933            || file_exists( $pluginsDir . DIRECTORY_SEPARATOR . 'wp-e-commerce' . DIRECTORY_SEPARATOR . 'editor.php' )
    932934        ) {
    933935            $this->imagesDir              = wp_upload_dir( null, false )['basedir'] . DIRECTORY_SEPARATOR . 'wpsc' . DIRECTORY_SEPARATOR;
     
    9991001            }
    10001002
     1003            if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
     1004                $this->_wpmlEnabled = true;
     1005            }
     1006
     1007            $pll = null;
     1008
     1009            if ( function_exists( 'PLL' ) ) {
     1010                $pll = PLL();
     1011            }
     1012
     1013            if ( $pll ) {
     1014                $this->_polylangEnabled = true;
     1015            } elseif ( defined( 'POLYLANG_VERSION' ) ) {
     1016                $this->_polylangEnabled = true;
     1017            }
     1018
    10011019            $this->cartVars['wp_content_url'] = content_url();
    10021020
     
    10171035
    10181036        return true;
     1037    }
     1038
     1039    /**
     1040     * @param int|null $storeId Store ID
     1041     *
     1042     * @return void
     1043     */
     1044    private function _initWooCommerceInMultiSite( $storeId = null )
     1045    {
     1046        $wooLoaded = false;
     1047
     1048        if ( class_exists( 'WC_Product' ) || class_exists( 'WooCommerce' ) ) {
     1049            $wooLoaded = true;
     1050        }
     1051
     1052        $wooPluginRel = 'woocommerce/woocommerce.php';
     1053        $activePlugins = (array)get_option( 'active_plugins', [] );
     1054
     1055        if ( $storeId !== null && function_exists( 'get_blog_option' ) && function_exists( 'is_multisite' ) && is_multisite() ) {
     1056            $activePlugins = (array)get_blog_option( (int)$storeId, 'active_plugins', [] );
     1057        }
     1058
     1059        $isActive = in_array( $wooPluginRel, $activePlugins, true );
     1060        $sitewide = [];
     1061
     1062        if ( function_exists( 'is_multisite' ) && is_multisite() ) {
     1063            $sitewide = (array)get_site_option( 'active_sitewide_plugins', [] );
     1064        }
     1065
     1066        $shouldLoadWoo = true;
     1067
     1068        if ( $wooLoaded ) {
     1069            $shouldLoadWoo = false;
     1070        }
     1071
     1072        if ( $shouldLoadWoo ) {
     1073            if ( $isActive || isset( $sitewide[$wooPluginRel] ) ) {
     1074                $pluginPath = WP_PLUGIN_DIR . '/' . $wooPluginRel;
     1075
     1076                if ( is_readable( $pluginPath ) ) {
     1077                    include_once $pluginPath;
     1078                }
     1079
     1080                if ( function_exists( 'WC' ) ) {
     1081                    WC();
     1082
     1083                    if ( !function_exists( 'did_action' ) || 0 === did_action( 'woocommerce_init' ) ) {
     1084                        WC()->init();
     1085                    }
     1086                }
     1087
     1088                if ( function_exists( 'taxonomy_exists' ) && class_exists( 'WC_Post_Types' )
     1089                    && !( taxonomy_exists( 'product_cat' ) && taxonomy_exists( 'product_tag' ) )
     1090                ) {
     1091                    WC_Post_Types::register_taxonomies();
     1092                }
     1093
     1094                if ( function_exists( 'post_type_exists' ) && class_exists( 'WC_Post_Types' ) && !post_type_exists( 'product' ) ) {
     1095                    WC_Post_Types::register_post_types();
     1096                }
     1097            }
     1098        }
     1099
     1100        $wpmlPluginRel = 'sitepress-multilingual-cms/sitepress.php';
     1101        $wpmlActive = in_array( $wpmlPluginRel, $activePlugins, true );
     1102
     1103        if ( $wpmlActive || isset( $sitewide[$wpmlPluginRel] ) ) {
     1104            $wpmlPath = WP_PLUGIN_DIR . '/' . $wpmlPluginRel;
     1105
     1106            if ( is_readable( $wpmlPath ) ) {
     1107                include_once $wpmlPath;
     1108            }
     1109        }
     1110
     1111        if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' )
     1112            && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' )
     1113        ) {
     1114            $this->_wpmlEnabled = true;
     1115
     1116            $hasWpmlDic = false;
     1117
     1118            if (isset($GLOBALS['wpml_dic'])) {
     1119                $hasWpmlDic = true;
     1120            }
     1121
     1122            if ($hasWpmlDic === false) {
     1123                $wpmlBootstrap = WP_PLUGIN_DIR . '/sitepress-multilingual-cms/vendor/wpml/wpml/wpml.php';
     1124
     1125                if (is_readable($wpmlBootstrap)) {
     1126                    include_once $wpmlBootstrap;
     1127                }
     1128            }
     1129
     1130
     1131            return;
     1132        }
     1133
     1134        $polylangPluginRel = 'polylang/polylang.php';
     1135        $polylangWcPluginRel = 'polylang-wc/polylang-wc.php';
     1136        $polylangActive = in_array( $polylangPluginRel, $activePlugins, true );
     1137        $polylangWcActive = in_array( $polylangWcPluginRel, $activePlugins, true );
     1138
     1139        if ( $polylangActive || isset( $sitewide[$polylangPluginRel] ) ) {
     1140            $polylangPath = WP_PLUGIN_DIR . '/' . $polylangPluginRel;
     1141
     1142            if ( is_readable( $polylangPath ) ) {
     1143                include_once $polylangPath;
     1144            }
     1145
     1146            if ( $polylangWcActive || isset( $sitewide[$polylangWcPluginRel] ) ) {
     1147                $polylangWcPath = WP_PLUGIN_DIR . '/' . $polylangWcPluginRel;
     1148
     1149                if ( is_readable( $polylangWcPath ) ) {
     1150                    include_once $polylangWcPath;
     1151                }
     1152            }
     1153        }
     1154
     1155        if ( class_exists( 'Polylang' ) && !isset( $GLOBALS['polylang'] ) ) {
     1156            $polylangBootstrap = new Polylang();
     1157            $GLOBALS['polylang'] = $polylangBootstrap;
     1158
     1159            if ( method_exists( $polylangBootstrap, 'init' ) ) {
     1160                $polylangBootstrap->init();
     1161            }
     1162        }
     1163
     1164        if ( function_exists( 'PLL' ) || defined( 'POLYLANG_VERSION' ) ) {
     1165            $this->_polylangEnabled = true;
     1166        }
     1167
     1168        if ( $this->_polylangEnabled ) {
     1169            $pll = null;
     1170            $model = null;
     1171
     1172            if ( function_exists( 'PLL' ) ) {
     1173                $pll = PLL();
     1174            }
     1175
     1176            if ( $pll && isset( $pll->model ) ) {
     1177                $model = $pll->model;
     1178            }
     1179
     1180            $this->_initPolylangWc( $model );
     1181        }
     1182    }
     1183
     1184    /**
     1185     * @param int $storeId Store ID
     1186     *
     1187     * @return void
     1188     */
     1189    private function _initWooCommerceInMultiSiteContext( $storeId )
     1190    {
     1191        if ( function_exists( 'switch_to_blog' ) ) {
     1192            switch_to_blog( $storeId );
     1193        }
     1194
     1195        if ( function_exists( 'is_multisite' ) && is_multisite() ) {
     1196            $mainBlogId = 1;
     1197
     1198            if ( function_exists( 'get_main_site_id' ) ) {
     1199                $mainBlogId = (int)get_main_site_id();
     1200            }
     1201
     1202            if ( (int)$storeId !== $mainBlogId ) {
     1203                $this->_initWooCommerceInMultiSite( $storeId );
     1204            }
     1205        }
     1206    }
     1207
     1208    /**
     1209     * Ensure Polylang-WC taxonomies and cache are initialized
     1210     *
     1211     * @param object|null $model Polylang model
     1212     *
     1213     * @return void
     1214     */
     1215    protected function _initPolylangWc($model = null)
     1216    {
     1217        $pllWC = null;
     1218        $languages = array();
     1219        $languagesReady = false;
     1220
     1221        if ( function_exists( 'PLLWC' ) ) {
     1222            $pllWC = PLLWC();
     1223        }
     1224
     1225        if ( function_exists( 'pll_languages_list' ) ) {
     1226            $languages = pll_languages_list();
     1227        }
     1228
     1229        if ( is_array( $languages ) && $languages ) {
     1230            $languagesReady = true;
     1231        }
     1232
     1233        if ( $pllWC && $languagesReady && method_exists( $pllWC, 'init' ) ) {
     1234            $pllWC->init();
     1235        }
     1236
     1237        if ( $model && $languagesReady && isset( $model->cache )
     1238            && is_object( $model->cache ) && method_exists( $model->cache, 'clean' )
     1239        ) {
     1240            $model->cache->clean( 'taxonomies' );
     1241
     1242            if ( method_exists( $model, 'get_translated_taxonomies' ) ) {
     1243                $model->get_translated_taxonomies( false );
     1244            }
     1245        }
    10191246    }
    10201247
     
    11161343
    11171344                        if ( empty($data) ) {
    1118                             $entity->update_meta_data( '_wc_shipment_tracking_items', [] );
     1345                            $entity->delete_meta_data( '_wc_shipment_tracking_items' );
    11191346                        } else {
    11201347                            $entity->update_meta_data( '_wc_shipment_tracking_items', $data );
     
    11351362                            // translators: %1$s is the tracking provider, %2$s is the tracking number
    11361363                            $note = sprintf(
    1137                                 esc_html__( 'Tracking info was deleted for tracking provider %1$s with tracking number %2$s', 'api2cart-bridge-connector' ),
     1364                                esc_html__( 'Tracking info was deleted for tracking provider %1$s with tracking number %2$s', 'bridge-connector' ),
    11381365                                $trackingProvider,
    11391366                                $trackingNumber );
     
    14061633                        $keys = implode( '\', \'', $wpdb->_escape( array_keys( $a2cData['meta'] ) ) );
    14071634
    1408                         switch ( $a2cData['entity'] ) {
    1409                             case 'product':
    1410                             case 'order':
    1411                                 $wooOrderTableEnabled = 'order' === $a2cData['entity'] && get_option('woocommerce_custom_orders_table_enabled') === 'yes';
    1412 
    1413                                 if ($wooOrderTableEnabled) {
    1414                                     $metaTable = $wpdb->prefix . 'wc_orders_meta';
    1415                                     $metaIdField = 'm.id AS meta_id';
    1416                                     $entityIdField = 'm.order_id';
    1417                                 } else {
    1418                                     $metaTable = $wpdb->postmeta;
    1419                                     $metaIdField = 'm.meta_id';
    1420                                     $entityIdField = 'm.post_id';
    1421                                 }
    1422 
    1423                 $sql = "
     1635                    switch ( $a2cData['entity'] ) {
     1636                        case 'product':
     1637                        case 'order':
     1638                            $wooOrderTableEnabled = 'order' === $a2cData['entity'] && get_option('woocommerce_custom_orders_table_enabled') === 'yes';
     1639
     1640                            if ($wooOrderTableEnabled) {
     1641                                $metaTable = $wpdb->prefix . 'wc_orders_meta';
     1642                                $metaIdField = 'm.id AS meta_id';
     1643                                $entityIdField = 'm.order_id';
     1644                            } else {
     1645                                $metaTable = $wpdb->postmeta;
     1646                                $metaIdField = 'm.meta_id';
     1647                                $entityIdField = 'm.post_id';
     1648                            }
     1649
     1650                            $sql = "
    14241651                                SELECT {$metaIdField}, m.meta_key, m.meta_value
    14251652                                FROM {$metaTable} AS m
    14261653                                WHERE {$entityIdField} = %d
    14271654                                    AND m.meta_key IN ('{$keys}')";
    1428                 $qRes = $wpdb->get_results($wpdb->prepare( $sql, $id ));
    1429                 break;
    1430 
    1431                             case 'customer':
     1655                            $qRes = $wpdb->get_results($wpdb->prepare( $sql, $id ));
     1656                            break;
     1657
     1658                        case 'customer':
    14321659                                $qRes = $wpdb->get_results(
    14331660                                    $wpdb->prepare( '
     
    16031830                if ( empty( $a2cData['from'] ) ) {
    16041831                    /* translators: %s: new order status */
    1605                     $transition_note = sprintf( esc_html__( 'Order status set to %s.', 'api2cart-bridge-connector' ), wc_get_order_status_name( $a2cData['to'] ) );
     1832                    $transition_note = sprintf( esc_html__( 'Order status set to %s.', 'bridge-connector' ), wc_get_order_status_name( $a2cData['to'] ) );
    16061833
    16071834                    if ( empty( $a2cData['added_by_user'] ) ) {
     
    16121839                } else {
    16131840                    /* translators: 1: old order status 2: new order status */
    1614                     $transition_note = sprintf( esc_html__( 'Order status changed from %1$s to %2$s.', 'api2cart-bridge-connector' ),
     1841                    $transition_note = sprintf( esc_html__( 'Order status changed from %1$s to %2$s.', 'bridge-connector' ),
    16151842                        wc_get_order_status_name( $a2cData['from'] ),
    16161843                        wc_get_order_status_name( $a2cData['to'] ) );
     
    17441971            $entity = WC()->order_factory->get_order( $a2cData['order']['id'] );
    17451972
    1746             if (!empty( $a2cData['order']['meta_data'] ) ) {
    1747                 foreach ($a2cData['order']['meta_data'] as $metaData) {
    1748                     $entity->update_meta_data($metaData['key'], $metaData['value']);
     1973            if (!empty( $a2cData["order"]["meta_data"] ) ) {
     1974                foreach ( $a2cData["order"]["meta_data"] as $metaData ) {
     1975                    $entity->update_meta_data( $metaData['key'], $metaData['value'] );
    17491976                }
    17501977            }
     
    18192046
    18202047        try {
     2048            $this->_initWooCommerceInMultiSiteContext( $a2cData['store_id'] );
     2049
    18212050            $termType = 'product_cat';
    1822 
    1823             if ( function_exists( 'switch_to_blog' ) ) {
    1824                 switch_to_blog( $a2cData['store_id'] );
    1825             }
    18262051
    18272052            $termArgs = [
     
    18972122                    do_action( 'wpml_set_element_language_details', $setLanguageArgs );
    18982123                } else {
    1899                     throw new Exception( esc_html__( '[BRIDGE ERROR]: Can\'t create category!', 'api2cart-bridge-connector' ) );
     2124                    throw new Exception( esc_html__( '[BRIDGE ERROR]: Can\'t create category!', 'bridge-connector' ) );
     2125                }
     2126            } elseif ( $this->_polylangEnabled ) {
     2127                $polylangLangCode = isset( $a2cData['meta_data']['lang_code'] ) ? $a2cData['meta_data']['lang_code'] : null;
     2128
     2129                if ( !$polylangLangCode && function_exists( 'pll_default_language' ) ) {
     2130                    $polylangLangCode = pll_default_language( 'slug' );
     2131                }
     2132
     2133                $polylangModel = $this->_getPolylangModel();
     2134                $polylangTermModel = $polylangModel && isset( $polylangModel->term ) ? $polylangModel->term : null;
     2135
     2136                if ( $polylangLangCode && !empty( $termArgs['parent'] ) ) {
     2137                    $translatedParentId = $this->_createPolylangTermTranslation(
     2138                        (int)$termArgs['parent'],
     2139                        $polylangLangCode,
     2140                        $termType,
     2141                        $polylangTermModel
     2142                    );
     2143
     2144                    if ( $translatedParentId ) {
     2145                        $termArgs['parent'] = $translatedParentId;
     2146                    }
     2147                }
     2148
     2149                $newTerm = wp_insert_term( $a2cData['meta_data']['tag-name'], $termType, $termArgs );
     2150
     2151                if ( $newTerm && !is_wp_error( $newTerm ) ) {
     2152                    $termId = 0;
     2153
     2154                    if ( $newTerm instanceof WP_Term ) {
     2155                        $termId = (int)$newTerm->term_id;
     2156                    } elseif ( is_array( $newTerm ) && isset( $newTerm['term_id'] ) ) {
     2157                        $termId = (int)$newTerm['term_id'];
     2158                    }
     2159
     2160                    if ( $termId > 0 ) {
     2161                        if ( function_exists( 'pll_set_term_language' ) ) {
     2162                            pll_set_term_language( $termId, $polylangLangCode );
     2163                        } elseif ( $polylangTermModel ) {
     2164                            $polylangTermModel->set_language( $termId, $polylangLangCode );
     2165                        }
     2166                    }
    19002167                }
    19012168            } else {
     
    19302197        };
    19312198
    1932         if ( function_exists( 'switch_to_blog' ) ) {
    1933             switch_to_blog( $a2cData['store_id'] );
    1934         }
     2199        $this->_initWooCommerceInMultiSiteContext( $a2cData['store_id'] );
    19352200
    19362201        if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
     
    19672232
    19682233                if ( is_wp_error( $term ) ) {
    1969                     throw new Exception( esc_html__( 'Can\'t create category! Error: ' . $term->get_error_message(), 'api2cart-bridge-connector' ) );
     2234                    throw new Exception( esc_html__( 'Can\'t create category! Error: ' . $term->get_error_message(), 'bridge-connector' ) );
    19702235                }
    19712236
     
    19802245                if ( $this->_wpmlEnabled ) {
    19812246                    $this->_translateTaxonomy( [$termId], $taxonomy, $activeLanguages, true );
     2247                } elseif ( $this->_polylangEnabled ) {
     2248                    $polylangTermModel = null;
     2249                    $polylangModel = $this->_getPolylangModel();
     2250
     2251                    if ( $polylangModel && isset( $polylangModel->term ) ) {
     2252                        $polylangTermModel = $polylangModel->term;
     2253                    }
     2254
     2255                    $polylangLanguages = $this->_getPolylangLanguagesList( $polylangModel );
     2256
     2257                    $termLangCode = isset( $item['lang_code'] ) ? $item['lang_code'] : null;
     2258
     2259                    if ( !$termLangCode && function_exists( 'pll_default_language' ) ) {
     2260                        $termLangCode = pll_default_language( 'slug' );
     2261                    }
     2262
     2263                    if ( !$termLangCode && $polylangLanguages ) {
     2264                        $termLangCode = $polylangLanguages[0];
     2265                    }
     2266
     2267                    $this->_polylangTranslateTaxonomy( [ $termId ], $taxonomy, $polylangLanguages, $polylangTermModel, $termLangCode );
    19822268                }
    19832269
     
    20042290                                    update_term_meta( $trId, 'thumbnail_id', $attachmentId );
    20052291                                }
     2292                            }
     2293                        } elseif ( $this->_polylangEnabled ) {
     2294                            $termIds = [ $termId ];
     2295                            $translations = $this->_getPolylangTermTranslationIds( $termId );
     2296
     2297                            foreach ( $translations as $translationId ) {
     2298                                if ( (int)$translationId > 0 ) {
     2299                                    $termIds[] = (int)$translationId;
     2300                                }
     2301                            }
     2302
     2303                            foreach ( array_unique( $termIds ) as $translationId ) {
     2304                                update_term_meta( $translationId, 'thumbnail_id', $attachmentId );
    20062305                            }
    20072306                        } else {
     
    20362335
    20372336    /**
    2038      * @param array $a2cData Data
    2039      *
    2040      * @return array|mixed
    2041      */
     2337    * @param array $a2cData Data
     2338    *
     2339    * @return array|mixed
     2340    */
    20422341    public function orderCalculate( $a2cData )
    20432342    {
     
    23882687
    23892688    /**
    2390      * Image Add
    2391      *
    23922689     * @param array $a2cData Data
    23932690     *
     
    23992696            'error_code' => self::ERROR_CODE_SUCCESS,
    24002697            'error'      => null,
    2401             'result'     => array(),
     2698            'result'     => array()
    24022699        );
    24032700
     
    24212718        try {
    24222719            require_once( ABSPATH . 'wp-admin/includes/image.php' );
    2423             $productIds = $a2cData[ 'product_ids' ];
    2424             $productId  = $a2cData[ 'product_ids' ][ 0 ];
    2425             $variantIds = $a2cData[ 'variant_ids' ];
     2720            $productIds = [];
     2721            $variantIds = [];
     2722            $categoryIds = [];
     2723            $isProduct = true;
     2724            $productId = 0;
     2725
     2726            if ( isset( $a2cData[ 'category_id' ] ) ) {
     2727                $categoryIds = [ (int)$a2cData[ 'category_id' ] ];
     2728                $isProduct = false;
     2729            } elseif ( isset( $a2cData[ 'category_ids' ] ) && is_array( $a2cData[ 'category_ids' ] ) ) {
     2730                foreach ( $a2cData[ 'category_ids' ] as $catId ) {
     2731                    $categoryIds[] = (int)$catId;
     2732                }
     2733                $isProduct = false;
     2734            }
     2735
     2736            if ( $isProduct === true ) {
     2737                $productIds = $a2cData[ 'product_ids' ];
     2738                $productId = $a2cData[ 'product_ids' ][ 0 ];
     2739                $variantIds = $a2cData[ 'variant_ids' ];
     2740            }
    24262741
    24272742            $alt = '';
    24282743
    2429             if ( !empty( $a2cData[ 'alt' ] ) ) {
     2744            if ( empty( $a2cData[ 'alt' ] ) === false ) {
    24302745                $alt = $a2cData[ 'alt' ];
    24312746            }
    24322747
    2433             if ( function_exists( 'switch_to_blog' ) ) {
    2434                 switch_to_blog( $a2cData[ 'store_id' ] );
    2435             }
    2436 
    2437             if ( $a2cData['content'] ) {
    2438                 $img      = str_replace( 'data:image/jpeg;base64,', '', $a2cData[ 'content' ] );
    2439                 $img      = str_replace( ' ', '+', $img );
    2440                 $decoded  = base64_decode( $img );
     2748            $this->_initWooCommerceInMultiSiteContext( $a2cData[ 'store_id' ] );
     2749
     2750            if ( $a2cData[ 'content' ] ) {
     2751                $img = str_replace( 'data:image/jpeg;base64,', '', $a2cData[ 'content' ] );
     2752                $img = str_replace( ' ', '+', $img );
     2753                $decoded = base64_decode( $img );
    24412754                $filename = $a2cData[ 'name' ];
    24422755
     
    24442757                if ( $file[ 'error' ] !== false ) {
    24452758                    /* translators: %s: File name */
    2446                     throw new Exception( sprintf( esc_html__( '[BRIDGE ERROR]: File save failed %s!', 'api2cart-bridge-connector' ), $filename ) );
     2759                    throw new Exception( sprintf( esc_html__( '[BRIDGE ERROR]: File save failed %s!', 'bridge-connector' ), $filename ) );
    24472760                }
    24482761
    24492762            } elseif ( $a2cData['source'] ) {
    2450                 $imageUrl  = $a2cData[ 'source' ];
     2763                $imageUrl = $a2cData[ 'source' ];
    24512764                $filename = $a2cData[ 'name' ];
    24522765                $parsedUrl = wp_parse_url( $imageUrl );
     
    24542767                if ( !$parsedUrl || !is_array( $parsedUrl ) ) {
    24552768                    /* translators: %s: Image Url */
    2456                     throw new Exception( sprintf( esc_html__( '[BRIDGE ERROR]: Invalid URL %s!', 'api2cart-bridge-connector' ), $parsedUrl ) );
     2769                    throw new Exception( sprintf( esc_html__( '[BRIDGE ERROR]: Invalid URL %s!', 'bridge-connector' ), $parsedUrl ) );
    24572770                }
    24582771
    24592772                $imageUrl = esc_url_raw( $imageUrl );
    24602773
    2461                 if ( !function_exists( 'download_url' ) ) {
     2774                if ( function_exists( 'download_url' ) === false ) {
    24622775                    include_once ABSPATH . 'wp-admin/includes/file.php';
    24632776                }
     
    24692782                if ( is_wp_error( $fileArray[ 'tmp_name' ] ) ) {
    24702783                    throw new Exception(
    2471                         /* translators: %s: Image Url */
    2472                         sprintf( esc_html__( '[BRIDGE ERROR]: Some error occurred while retrieving the remote image by URL: %s!', 'api2cart-bridge-connector' ), $imageUrl ) . ' ' . sprintf(
     2784                        /* translators: %s: Image Url */
     2785                        sprintf( esc_html__( '[BRIDGE ERROR]: Some error occurred while retrieving the remote image by URL: %s!', 'bridge-connector' ), $imageUrl ) . ' ' . sprintf(
    24732786                            /* translators: %s: Error message */
    2474                             esc_html__( 'Error: %s', 'api2cart-bridge-connector' ),
     2787                            esc_html__( 'Error: %s', 'bridge-connector' ),
    24752788                            $fileArray[ 'tmp_name' ]->get_error_message()
    24762789                        )
     
    24972810
    24982811            if ( empty( $file['file'] ) ) {
    2499                 throw new Exception( esc_html__( '[BRIDGE ERROR]: No image has been uploaded!', 'api2cart-bridge-connector' ) );
    2500             }
    2501 
    2502             if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
    2503                 global $sitepress;
    2504 
    2505                 $sitepress       = WPML\Container\make( '\SitePress' );
    2506                 $currentLanguage = apply_filters( 'wpml_current_language', null );
    2507 
    2508                 foreach ( $productIds as $productId ) {
    2509                     $objectId = apply_filters( 'wpml_object_id', $productId, 'post_product', true, $currentLanguage );
    2510 
    2511                     if ( $objectId === null ) {
    2512                         continue;
    2513                     }
    2514 
    2515                     $trid         = apply_filters( 'wpml_element_trid', null, $objectId, 'post_product' );
    2516                     $translations = apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product', false, true );
    2517 
    2518                     foreach ( $translations as $translation ) {
    2519                         if ( is_object( $translation ) ) {
    2520                             $productIds[] = $translation->{'element_id'};
    2521                         } elseif ( is_array( $translation ) ) {
    2522                             $productIds[] = $translation[ 'element_id' ];
     2812                throw new Exception( esc_html__( '[BRIDGE ERROR]: No image has been uploaded!', 'bridge-connector' ) );
     2813            }
     2814
     2815            if ( $isProduct === true ) {
     2816                if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
     2817                    global $sitepress;
     2818
     2819                    $sitepress       = WPML\Container\make( '\SitePress' );
     2820                    $currentLanguage = apply_filters( 'wpml_current_language', null );
     2821
     2822                    foreach ( $productIds as $productId ) {
     2823                        $objectId = apply_filters( 'wpml_object_id', $productId, 'post_product', true, $currentLanguage );
     2824
     2825                        if ( $objectId === null ) {
     2826                            continue;
    25232827                        }
    2524                     }
    2525                 }
    2526 
    2527                 foreach ( $variantIds as $variantId ) {
    2528                     $objectId = apply_filters( 'wpml_object_id', $variantId, 'post_product_variation', true, $currentLanguage );
    2529 
    2530                     if ( $objectId === null ) {
    2531                         continue;
    2532                     }
    2533 
    2534                     $trid         = apply_filters( 'wpml_element_trid', null, $objectId, 'post_product_variation' );
    2535                     $translations = apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product_variation', false, true );
    2536 
    2537                     foreach ( $translations as $translation ) {
    2538                         if ( is_object( $translation ) ) {
    2539                             $variantIds[] = $translation->{'element_id'};
    2540                         } elseif ( is_array( $translation ) ) {
    2541                             $variantIds[] = $translation[ 'element_id' ];
     2828
     2829                        $trid         = apply_filters( 'wpml_element_trid', null, $objectId, 'post_product' );
     2830                        $translations = apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product', false, true );
     2831
     2832                        foreach ( $translations as $translation ) {
     2833                            if ( is_object( $translation ) ) {
     2834                                $productIds[] = $translation->{'element_id'};
     2835                            } elseif ( is_array( $translation ) ) {
     2836                                $productIds[] = $translation[ 'element_id' ];
     2837                            }
    25422838                        }
    25432839                    }
    2544                 }
    2545 
    2546                 $productIds = array_unique( $productIds );
    2547                 $variantIds = array_unique( $variantIds );
     2840
     2841                    foreach ( $variantIds as $variantId ) {
     2842                        $objectId = apply_filters( 'wpml_object_id', $variantId, 'post_product_variation', true, $currentLanguage );
     2843
     2844                        if ( $objectId === null ) {
     2845                            continue;
     2846                        }
     2847
     2848                        $trid         = apply_filters( 'wpml_element_trid', null, $objectId, 'post_product_variation' );
     2849                        $translations = apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product_variation', false, true );
     2850
     2851                        foreach ( $translations as $translation ) {
     2852                            if ( is_object( $translation ) ) {
     2853                                $variantIds[] = $translation->{'element_id'};
     2854                            } elseif ( is_array( $translation ) ) {
     2855                                $variantIds[] = $translation[ 'element_id' ];
     2856                            }
     2857                        }
     2858                    }
     2859
     2860                    $productIds = array_unique( $productIds );
     2861                    $variantIds = array_unique( $variantIds );
     2862                }
     2863            } else {
     2864                $applyToTranslations = true;
     2865
     2866                if ( isset( $a2cData[ 'apply_to_translations' ] ) ) {
     2867                    $applyToTranslations = (bool)$a2cData[ 'apply_to_translations' ];
     2868                }
     2869
     2870                $categoryIds = $this->_getCategoryImageTermIds( $categoryIds[ 0 ], $applyToTranslations );
     2871            }
     2872
     2873            if ( $isProduct ) {
     2874                $attachmentParentId = $productId;
     2875            } else {
     2876                $attachmentParentId = 0;
    25482877            }
    25492878
    25502879            $attachmentId = wp_insert_attachment(
    25512880                [
    2552                     'guid'           => wp_upload_dir()['url'] . '/' . basename( $file['file'] ),
     2881                    'guid'           => wp_upload_dir()[ 'url' ] . '/' . basename( $file[ 'file' ] ),
    25532882                    'post_mime_type' => $file[ 'type' ],
    25542883                    'post_title'     => basename( $file[ 'file' ] ),
     
    25572886                ],
    25582887                $file[ 'file' ],
    2559                 $productId
     2888                $attachmentParentId
    25602889            );
    25612890
     
    25682897            wp_update_attachment_metadata( $attachmentId, $attachmentData );
    25692898
    2570             foreach ( $productIds as $productId ) {
    2571                 if ( $a2cData[ 'is_thumbnail' ] ) {
    2572                     set_post_thumbnail( $productId, $attachmentId );
    2573                 }
    2574 
    2575                 if ( $a2cData[ 'is_gallery' ] ) {
    2576                     $WCProduct = WC()->product_factory->get_product( $productId );
    2577 
    2578                     if ( $WCProduct->get_type() !== 'variation' ) {
    2579                         $galleryIds   = $WCProduct->get_gallery_image_ids();
    2580                         $galleryIds[] = $attachmentId;
    2581                         $WCProduct->set_gallery_image_ids( array_unique( $galleryIds ) );
    2582                         $WCProduct->save();
    2583                     }
    2584                 }
    2585             }
    2586 
    2587             foreach ( $variantIds as $variantId ) {
    2588                 if ( $a2cData[ 'is_thumbnail' ] ) {
    2589                     set_post_thumbnail( $variantId, $attachmentId );
    2590                 }
    2591 
    2592                 if ( $a2cData[ 'is_gallery' ] ) {
    2593                     $images = get_post_meta( $variantId, 'woo_variation_gallery_images', true );//https://wordpress.org/plugins/woo-variation-gallery support
    2594 
    2595                     if ( empty( $images ) ) {
    2596                         $variationGallery = [ $attachmentId ];
    2597                     } else {
    2598                         $variationGallery = array_unique( array_merge( [ $attachmentId ], $images ) );
    2599                     }
    2600 
    2601                     update_post_meta( $variantId, 'woo_variation_gallery_images', $variationGallery );
     2899            if ( $isProduct ) {
     2900                foreach ( $productIds as $productId ) {
     2901                    if ( $a2cData[ 'is_thumbnail' ] ) {
     2902                        set_post_thumbnail( $productId, $attachmentId );
     2903                    }
     2904
     2905                    if ( $a2cData[ 'is_gallery' ] ) {
     2906                        $WCProduct = WC()->product_factory->get_product( $productId );
     2907
     2908                        if ( $WCProduct->get_type() !== 'variation' ) {
     2909                            $galleryIds   = $WCProduct->get_gallery_image_ids();
     2910                            $galleryIds[] = $attachmentId;
     2911                            $WCProduct->set_gallery_image_ids( array_unique( $galleryIds ) );
     2912                            $WCProduct->save();
     2913                        }
     2914                    }
     2915                }
     2916
     2917                foreach ( $variantIds as $variantId ) {
     2918                    if ( $a2cData[ 'is_thumbnail' ] ) {
     2919                        set_post_thumbnail( $variantId, $attachmentId );
     2920                    }
     2921
     2922                    if ( $a2cData[ 'is_gallery' ] ) {
     2923                        $images = get_post_meta( $variantId, 'woo_variation_gallery_images', true );//https://wordpress.org/plugins/woo-variation-gallery support
     2924
     2925                        if ( empty( $images ) ) {
     2926                            $variationGallery = [ $attachmentId ];
     2927                        } else {
     2928                            $variationGallery = array_unique( array_merge( [ $attachmentId ], $images ) );
     2929                        }
     2930
     2931                        update_post_meta( $variantId, 'woo_variation_gallery_images', $variationGallery );
     2932                    }
     2933                }
     2934            } else {
     2935                foreach ( $categoryIds as $termId ) {
     2936                    update_term_meta( $termId, 'thumbnail_id', $attachmentId );
    26022937                }
    26032938            }
     
    26132948
    26142949        return $response;
     2950    }
     2951
     2952    /**
     2953     * @param int  $categoryId          Category ID
     2954     * @param bool $applyToTranslations Apply to all translations
     2955     *
     2956     * @return array
     2957     */
     2958    protected function _getCategoryImageTermIds( $categoryId, $applyToTranslations )
     2959    {
     2960        $categoryIds = array( $categoryId );
     2961        $translations = array();
     2962
     2963        if ( $this->_wpmlEnabled ) {
     2964            $translations = $this->_getWpmlTermTranslationIds( $categoryId, 'product_cat' );
     2965        } elseif ( $this->_polylangEnabled ) {
     2966            $translations = $this->_getPolylangTermTranslationIds( $categoryId );
     2967        }
     2968
     2969        foreach ( $translations as $termId ) {
     2970            $termId = (int)$termId;
     2971
     2972            if ( $termId > 0 ) {
     2973                $categoryIds[] = $termId;
     2974            }
     2975        }
     2976
     2977        if ( $applyToTranslations ) {
     2978            return array_values( array_unique( $categoryIds ) );
     2979        }
     2980
     2981        return array_values( array_unique( array( $categoryId ) ) );
     2982    }
     2983
     2984    /**
     2985     * @param int    $termId   Term ID
     2986     * @param string $taxonomy Taxonomy
     2987     *
     2988     * @return array
     2989     */
     2990    protected function _getWpmlTermTranslationIds( $termId, $taxonomy )
     2991    {
     2992        $categoryIds = array();
     2993        $taxonomyId = 0;
     2994        $term = get_term( $termId, $taxonomy );
     2995
     2996        if ( $term && !is_wp_error( $term ) ) {
     2997            $taxonomyId = (int)$term->term_taxonomy_id;
     2998        }
     2999
     3000        if ( $taxonomyId > 0 ) {
     3001            $trid = apply_filters( 'wpml_element_trid', null, $taxonomyId, 'tax_' . $taxonomy );
     3002
     3003            if ( $trid ) {
     3004                $translations = apply_filters( 'wpml_get_element_translations', null, $trid, 'tax_' . $taxonomy, false, true );
     3005
     3006                foreach ( $translations as $translation ) {
     3007                    $termTranslationId = 0;
     3008
     3009                    if ( is_object( $translation ) && isset( $translation->term_id ) ) {
     3010                        $termTranslationId = (int)$translation->term_id;
     3011                    } elseif ( is_array( $translation ) && isset( $translation[ 'term_id' ] ) ) {
     3012                        $termTranslationId = (int)$translation[ 'term_id' ];
     3013                    }
     3014
     3015                    if ( $termTranslationId > 0 ) {
     3016                        $categoryIds[] = $termTranslationId;
     3017                    }
     3018                }
     3019            }
     3020        }
     3021
     3022        return $categoryIds;
     3023    }
     3024
     3025    /**
     3026     * @param int $termId Term ID
     3027     *
     3028     * @return array
     3029     */
     3030    protected function _getPolylangTermTranslationIds( $termId )
     3031    {
     3032        $termModel = null;
     3033        $categoryIds = array();
     3034
     3035        if ( function_exists( 'PLL' ) ) {
     3036            $pll = PLL();
     3037
     3038            if ( $pll && isset( $pll->model ) && isset( $pll->model->term ) ) {
     3039                $termModel = $pll->model->term;
     3040            }
     3041        }
     3042
     3043        $translations = $this->_getPolylangTermTranslations( $termId, $termModel );
     3044
     3045        foreach ( $translations as $termId ) {
     3046            $termId = (int)$termId;
     3047
     3048            if ( $termId > 0 ) {
     3049                $categoryIds[] = $termId;
     3050            }
     3051        }
     3052
     3053        return $categoryIds;
    26153054    }
    26163055
     
    26353074
    26363075        try {
    2637             $response['result']['product_id'] = $this->_importProduct( $a2cData );
     3076            $response['result']['product_id'] = $this->_importProduct( $a2cData, 0, $productId );
    26383077        } catch ( Exception $e ) {
    26393078            $this->_cleanGarbage( $productId );
     
    26713110        };
    26723111
    2673         if ( function_exists( 'switch_to_blog' ) ) {
    2674             switch_to_blog( $a2cData['store_id'] );
    2675         }
     3112        $this->_initWooCommerceInMultiSiteContext( $a2cData['store_id'] );
    26763113
    26773114        if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
     
    27883225        };
    27893226
    2790         if ( function_exists( 'switch_to_blog' ) ) {
    2791             switch_to_blog( $a2cData['store_id'] );
    2792         }
     3227        $this->_initWooCommerceInMultiSiteContext( $a2cData['store_id'] );
    27933228
    27943229        if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
     
    28843319        };
    28853320
    2886         if ( function_exists( 'switch_to_blog' ) ) {
    2887             switch_to_blog( $a2cData['store_id'] );
    2888         }
     3321        $this->_initWooCommerceInMultiSiteContext( $a2cData['store_id'] );
    28893322
    28903323        if ( defined( 'ICL_SITEPRESS_VERSION' ) && defined( 'ICL_PLUGIN_INACTIVE' ) && !ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
     
    29263359
    29273360    /**
    2928      * @param array $a2cData Data
    2929      * @param int   $id      ID
    2930      *
    2931      * @return int
    2932      * @throws Exception
    2933      */
    2934     protected function _importProduct( array $a2cData, int $id = 0 ) {
    2935         if ( function_exists( 'switch_to_blog' ) ) {
    2936             switch_to_blog( $a2cData['store_id'] );
    2937         }
     3361    * @param array $a2cData   Data
     3362    * @param int   $id        ID
     3363    * @param int   $productId Product ID
     3364    *
     3365    * @return int
     3366    * @throws Exception
     3367    */
     3368    protected function _importProduct( array $a2cData, int $id = 0, int &$productId = 0 ) {
     3369        $this->_initWooCommerceInMultiSiteContext( $a2cData['store_id'] );
    29383370
    29393371        if ( isset( $a2cData['product_data']['internal_data']['wpml_current_lang_id'] ) ) {
     
    29453377        if ( !class_exists( $className ) ) {
    29463378            /* translators: %s: Class name */
    2947             throw new Exception( sprintf( esc_html__( '[BRIDGE ERROR]: Class %s not exist!', 'api2cart-bridge-connector' ), $className ) );
     3379            throw new Exception( sprintf( esc_html__( '[BRIDGE ERROR]: Class %s not exist!', 'bridge-connector' ), $className ) );
    29483380        }
    29493381
     
    29943426            remove_filter('content_save_pre', 'wp_filter_post_kses');
    29953427        }
    2996 
    29973428        $product->save();
    29983429
     
    30143445
    30153446        if ( isset( $a2cData['product_data']['terms_data'] ) ) {
    3016             foreach ( $a2cData['product_data']['terms_data'] as $termName => $terms ) {
    3017                 $termIds = array_unique( array_column( $terms, 'name' ) );
    3018                 $append  = false;
    3019 
    3020                 if ( isset( $terms[0]['append'] ) ) {
    3021                     $append = (bool)$terms[0]['append'];
    3022                 }
    3023 
    3024                 if ( !$append ) {
    3025                     wp_delete_object_term_relationships( $productId, $termName );
    3026                     wp_set_post_terms( $productId, $termIds, $termName );
     3447            $this->_setProductTerms( $productId, $a2cData['product_data']['terms_data'] );
     3448        }
     3449
     3450        if ( $this->_wpmlEnabled ) {
     3451            $this->_wpmlSync( $product, $a2cData, $metaChanges );
     3452        } elseif ( $this->_polylangEnabled ) {
     3453            $this->_polylangSync( $product, $a2cData, $id );
     3454        }
     3455
     3456        return $productId;
     3457    }
     3458
     3459    /**
     3460     * Reads attributes data and sets them to the product.
     3461     *
     3462     * @param int   $productId Product ID
     3463     * @param array $termsData Terms data
     3464     *
     3465     * @return void
     3466     */
     3467    protected function _setProductTerms( $productId, array $termsData )
     3468    {
     3469        foreach ( $termsData as $termName => $terms ) {
     3470            $termIds = [];
     3471
     3472            foreach ( array_column( $terms, 'name' ) as $termId ) {
     3473                if ( !empty( $termId ) ) {
     3474                    $termIds[] = $termId;
     3475                }
     3476            }
     3477
     3478            $termIds = array_values( array_unique( $termIds ) );
     3479            $append = false;
     3480
     3481            if ( isset( $terms[0]['append'] ) ) {
     3482                $append = (bool)$terms[0]['append'];
     3483            }
     3484
     3485            if ( empty( $termIds ) ) {
     3486                continue;
     3487            }
     3488
     3489            if ( $append ) {
     3490                wp_set_post_terms( $productId, $termIds, $termName, true );
     3491            } else {
     3492                wp_delete_object_term_relationships( $productId, $termName );
     3493                wp_set_post_terms( $productId, $termIds, $termName );
     3494            }
     3495        }
     3496    }
     3497
     3498    /**
     3499     * Gets Polylang translation ID for the post by language code.
     3500     *
     3501     * @param int    $postId   Post ID
     3502     * @param string $langCode Lang code
     3503     *
     3504     * @return int
     3505     */
     3506    protected function _getPolylangTranslationId( $postId, $langCode )
     3507    {
     3508        $translationId = 0;
     3509
     3510        if ( $langCode ) {
     3511            if ( function_exists( 'pll_get_post' ) ) {
     3512                $translationId = (int)pll_get_post( $postId, $langCode );
     3513
     3514                if ( $translationId ) {
     3515                    return $translationId;
     3516                }
     3517
     3518                if ( function_exists( 'pll_get_post_translations' ) ) {
     3519                    $translations = pll_get_post_translations( $postId );
     3520
     3521                    if ( is_array( $translations ) && isset( $translations[$langCode] ) ) {
     3522                        return (int)$translations[$langCode];
     3523                    }
     3524                }
     3525            }
     3526        }
     3527
     3528        return $translationId;
     3529    }
     3530
     3531    /**
     3532     * Initializes Polylang duplication compatibility by removing term setting on duplication.
     3533     * @return void
     3534     */
     3535    private
     3536    function _initPolylangDuplicateProduct()
     3537    {
     3538        if ( class_exists( 'PLLWC_Admin_Product_Duplicate' ) && function_exists( 'PLL' ) ) {
     3539            $pll = PLL();
     3540
     3541            if ( $pll && isset( $pll->posts ) ) {
     3542                // Mirrors Polylang-WC admin duplication behavior (admin/admin-product-duplicate.php).
     3543                remove_action( 'set_object_terms', [ $pll->posts, 'set_object_terms' ] );
     3544            }
     3545        }
     3546    }
     3547
     3548    /**
     3549     * Gets Polylang model instance if Polylang is active and model is accessible.
     3550     * @return object|null
     3551     */
     3552    protected function _getPolylangModel()
     3553    {
     3554        $model = null;
     3555        $pll = null;
     3556
     3557        if ( function_exists( 'PLL' ) ) {
     3558            $pll = PLL();
     3559        }
     3560
     3561        if ( $pll && isset( $pll->model ) ) {
     3562            $model = $pll->model;
     3563        }
     3564
     3565        return $model;
     3566    }
     3567
     3568    /**
     3569     * @param WC_Product $product  WC Product
     3570     * @param array      $a2cData  Data
     3571     * @param int|null   $sourceId Source ID
     3572     *
     3573     * @return void
     3574     * @throws Exception
     3575     */
     3576    protected function _polylangSync( WC_Product $product, $a2cData, $sourceId = 0 )
     3577    {
     3578        $polylangData = $this->_initPolylangData( $a2cData );
     3579        $productId = $product->get_id();
     3580        $model = $this->_getPolylangModel();
     3581        $postModel = null;
     3582        $termModel = null;
     3583
     3584        if ( is_object( $model ) ) {
     3585            $postModel = isset( $model->post ) ? $model->post : null;
     3586            $termModel = isset( $model->term ) ? $model->term : null;
     3587        }
     3588
     3589        if ( $product->get_type() === 'variation' ) {
     3590            $this->_polylangSyncVariations( $product, $polylangData, $postModel, $model );
     3591            return;
     3592        }
     3593
     3594        $activeLanguages = $this->_getPolylangLanguagesList( $model );
     3595
     3596        if ( $activeLanguages ) {
     3597            $this->_polyLangTranslateTerms( $a2cData, $activeLanguages );
     3598        }
     3599
     3600        if ( !$polylangData['langCode'] && !$polylangData['onlyTranslateTo'] ) {
     3601            $this->_handlePolylangWithoutTranslation( $product, $productId, $postModel, $model, $termModel );
     3602            return;
     3603        }
     3604
     3605        $defaultLangPrId = $sourceId > 0 ? $sourceId : $productId;
     3606        $createdIds = array();
     3607
     3608        try {
     3609            $skipSetLang = $this->_isSkipPolylangLanguageSet( $sourceId, $productId, $polylangData['langCode'], $postModel );
     3610
     3611            if ( $polylangData['langCode'] && !$skipSetLang ) {
     3612                $this->_polylangSetLanguage( $productId, $polylangData['langCode'], $postModel );
     3613                $this->_setPolylangLanguageForTerm( $productId, $polylangData['langCode'], $postModel, $model );
     3614            }
     3615
     3616            $this->_savePolylangProductTranslation( $sourceId, $productId, $polylangData['langCode'], $postModel );
     3617
     3618            $termLangCode = $this->_polylangGetTermLanguage( $polylangData['langCode'], $skipSetLang, $productId, $postModel );
     3619
     3620            if ( $termLangCode ) {
     3621                $this->_setPolylangLanguageForProductTerms( $product, $termLangCode, $termModel );
     3622            }
     3623
     3624            $langList = $this->_normalizeLanguageList( $polylangData['onlyTranslateTo'] );
     3625            $productTranslations = $this->_preparePolylangProductTranslations(
     3626                $product,
     3627                $defaultLangPrId,
     3628                $langList,
     3629                $polylangData['langCode'],
     3630                $postModel,
     3631                $createdIds
     3632            );
     3633
     3634            if ( empty( $productTranslations ) ) {
     3635                $productTranslations = $this->_getOrInitializePolylangTranslations( $defaultLangPrId, $postModel );
     3636            }
     3637
     3638            if ( $polylangData['langCode'] && $productId !== $defaultLangPrId && !isset( $productTranslations[$polylangData['langCode']] ) ) {
     3639                $productTranslations[$polylangData['langCode']] = $productId;
     3640            }
     3641
     3642            $termsData = $polylangData['termsData'];
     3643
     3644            if ( empty( $termsData ) ) {
     3645                $termsData = $this->_getPolylangProductTermsData( $defaultLangPrId );
     3646            }
     3647
     3648            if ( $termsData && $productTranslations ) {
     3649                $this->_syncPolylangProductTerms( $productTranslations, $termsData, $termModel );
     3650            }
     3651
     3652            if ( $product->get_type() === 'variable' ) {
     3653                $this->_polylangSyncAttributes( $product, $polylangData, $postModel, $termModel, $model );
     3654            }
     3655
     3656            if ( $skipSetLang ) {
     3657                $this->_rollbackPolylangBaseProtectedData( $defaultLangPrId, $a2cData, $postModel );
     3658            }
     3659        } catch ( Exception $e ) {
     3660            if ( $createdIds ) {
     3661                $a2cData['product_data']['internal_data']['polylang_created_ids'] = $createdIds;
     3662                $this->_rollbackProtectedData( $productId, $a2cData, null );
     3663            }
     3664            throw $e;
     3665        }
     3666    }
     3667
     3668    /**
     3669     * Sync Polylang translations for product variations
     3670     *
     3671     * @param WC_Product  $variation    Variation product
     3672     * @param array       $polylangData Polylang data
     3673     * @param object|null $postModel    Polylang post model
     3674     * @param object|null $model        Polylang main model
     3675     *
     3676     * @return void
     3677     */
     3678    protected function _polylangSyncVariations( WC_Product $variation, $polylangData, $postModel, $model )
     3679    {
     3680        $variationId = $variation->get_id();
     3681        $parentId = $variation->get_parent_id();
     3682        $languages = $this->_getPolylangLanguagesList( $model );
     3683
     3684        if ( !$parentId ) {
     3685            return;
     3686        }
     3687
     3688        $parentTranslations = $this->_polylangGetTranslations( $parentId, $postModel );
     3689
     3690        if ( empty( $parentTranslations ) ) {
     3691            $langCode = $polylangData['langCode'];
     3692
     3693            if ( $langCode ) {
     3694                $this->_polylangSetLanguage( $variationId, $langCode, $postModel );
     3695            }
     3696
     3697            return;
     3698        }
     3699
     3700        $variationLangCode = $polylangData['langCode'];
     3701
     3702        if ( !$variationLangCode ) {
     3703            $variationLangCode = $this->_polylangGetLanguage( $variationId, $postModel );
     3704        }
     3705
     3706        if ( !$variationLangCode ) {
     3707            $variationLangCode = $this->_polylangGetLanguage( $parentId, $postModel );
     3708        }
     3709
     3710        if ( !$variationLangCode && function_exists( 'pll_default_language' ) ) {
     3711            $variationLangCode = pll_default_language( 'slug' );
     3712        }
     3713
     3714        if ( $variationLangCode ) {
     3715            $this->_polylangSetLanguage( $variationId, $variationLangCode, $postModel );
     3716        }
     3717
     3718        $variationTranslations = $this->_polylangGetTranslations( $variationId, $postModel );
     3719
     3720        if ( empty( $variationTranslations ) && $variationLangCode ) {
     3721            $variationTranslations = array( $variationLangCode => $variationId );
     3722        }
     3723
     3724        $langList = $this->_normalizeLanguageList( $polylangData['onlyTranslateTo'] );
     3725
     3726        if ( empty( $langList ) ) {
     3727            $langList = array_keys( $parentTranslations );
     3728        }
     3729
     3730        foreach ( $langList as $langCode ) {
     3731            if ( isset( $variationTranslations[$langCode] ) ) {
     3732                $trVariationId = $variationTranslations[$langCode];
     3733
     3734                if ( $trVariationId != $variationId ) {
     3735                    $trVariation = wc_get_product( $trVariationId );
     3736
     3737                    if ( $trVariation instanceof WC_Product ) {
     3738                        $this->_polylangSyncVariationAttributes( $variation, $trVariation, $langCode, $model, $languages );
     3739                        $trVariation->save();
     3740                    }
     3741                }
     3742
     3743                continue;
     3744            }
     3745
     3746            if ( $langCode === $variationLangCode ) {
     3747                continue;
     3748            }
     3749
     3750            $trParentId = isset( $parentTranslations[$langCode] ) ? $parentTranslations[$langCode] : null;
     3751
     3752            if ( !$trParentId ) {
     3753                continue;
     3754            }
     3755
     3756            $trVariationId = $this->_duplicatePolylangVariation( $variation, $trParentId, $langCode, $postModel );
     3757
     3758            if ( $trVariationId ) {
     3759                $variationTranslations[$langCode] = $trVariationId;
     3760
     3761                $trVariation = wc_get_product( $trVariationId );
     3762
     3763                if ( $trVariation instanceof WC_Product ) {
     3764                    $this->_polylangSyncVariationAttributes( $variation, $trVariation, $langCode, $model, $languages );
     3765                    $trVariation->save();
     3766                }
     3767            }
     3768        }
     3769
     3770        if ( count( $variationTranslations ) > 1 ) {
     3771            $this->_polylangSaveTranslations( $variationId, $variationTranslations, $postModel );
     3772        }
     3773    }
     3774
     3775
     3776    /**
     3777     * Sync Polylang translations for product variations
     3778     *
     3779     * @param WC_Product  $parentProduct Variation product
     3780     * @param array       $polylangData  Polylang data
     3781     * @param object|null $postModel     Polylang post model
     3782    * @param object|null $termModel     Polylang term model
     3783     * @param object|null $model         Polylang main model
     3784     *
     3785     * @return void
     3786     */
     3787    protected function _polylangSyncAttributes( WC_Product $parentProduct, $polylangData, $postModel, $termModel, $model )
     3788    {
     3789        $parentId = $parentProduct->get_id();
     3790
     3791        if ( $parentId === 0 ) {
     3792            return;
     3793        }
     3794
     3795        $parentTranslations = $this->_polylangGetTranslations( $parentId, $postModel );
     3796
     3797        if ( $parentTranslations === [] || $parentTranslations === null ) {
     3798            if ( $polylangData['langCode'] ) {
     3799                $this->_polylangSetLanguage( $parentId, $polylangData['langCode'], $postModel );
     3800            }
     3801
     3802            return;
     3803        }
     3804
     3805        $defaultLangCode = null;
     3806
     3807        if ( function_exists( 'pll_default_language' ) ) {
     3808            $defaultLangCode = pll_default_language( 'slug' );
     3809        } elseif ( $model && method_exists( $model, 'get_default_language' ) ) {
     3810            $langObj = $model->get_default_language();
     3811
     3812            if ( $langObj && isset( $langObj->slug ) ) {
     3813                $defaultLangCode = $langObj->slug;
     3814            }
     3815        }
     3816
     3817        if ( $defaultLangCode === null || $defaultLangCode === '' ) {
     3818            return;
     3819        }
     3820
     3821        if ( isset( $parentTranslations[$defaultLangCode] ) ) {
     3822            $defaultParentId = (int)$parentTranslations[$defaultLangCode];
     3823        } else {
     3824            return;
     3825        }
     3826
     3827        $defaultParent = wc_get_product( $defaultParentId );
     3828
     3829        if ( $defaultParent instanceof WC_Product ) {
     3830            $attributes = $defaultParent->get_attributes();
     3831        } else {
     3832            return;
     3833        }
     3834
     3835        if ( $attributes === [] || $attributes === null ) {
     3836            return;
     3837        }
     3838
     3839        $activeLanguages = $this->_getPolylangLanguagesList( $model );
     3840
     3841        if ( $activeLanguages ) {
     3842            /** @var WC_Product_Attribute $attribute */
     3843            foreach ( $attributes as $attribute ) {
     3844                if ( $attribute->is_taxonomy() ) {
     3845                    $terms = $attribute->get_terms();
     3846                    $termIds = [];
     3847
     3848                    if ( $terms ) {
     3849                        foreach ( $terms as $term ) {
     3850                            $termIds[] = (int)$term->term_id;
     3851                        }
     3852                    }
     3853
     3854                    $termIds = array_values( array_unique( $termIds ) );
     3855
     3856                    if ( $termIds ) {
     3857                        $this->_polylangTranslateTaxonomy( $termIds, $attribute->get_taxonomy(), $activeLanguages, $termModel, $defaultLangCode );
     3858                    }
     3859                }
     3860            }
     3861        }
     3862
     3863        if ( class_exists( 'PLLWC_Products' ) && method_exists( 'PLLWC_Products', 'maybe_translate_attributes' ) ) {
     3864            $attributes = PLLWC_Products::maybe_translate_attributes( $attributes, $defaultLangCode );
     3865        }
     3866
     3867        $defaultParent->set_attributes( $attributes );
     3868        $defaultParent->save();
     3869
     3870        foreach ( $parentTranslations as $trLangCode => $trParentId ) {
     3871            $trParentId = (int)$trParentId;
     3872
     3873            if ( $trLangCode === $defaultLangCode || $trParentId === 0 ) {
     3874                continue;
     3875            }
     3876
     3877            $trParent = wc_get_product( $trParentId );
     3878
     3879            if ( $trParent instanceof WC_Product ) {
     3880                $trAttributes = $attributes;
     3881
     3882                if ( class_exists( 'PLLWC_Products' ) && method_exists( 'PLLWC_Products', 'maybe_translate_attributes' ) ) {
     3883                    $trAttributes = PLLWC_Products::maybe_translate_attributes( $attributes, $trLangCode );
     3884                }
     3885
     3886                $trParent->set_attributes( $trAttributes );
     3887                $trParent->save();
     3888            }
     3889        }
     3890    }
     3891
     3892    /**
     3893     * Duplicate a variation for Polylang translation
     3894     *
     3895     * @param WC_Product  $variation      Source variation
     3896     * @param int         $targetParentId Target parent product ID
     3897     * @param string      $langCode       Target language code
     3898     * @param object|null $postModel      Polylang post model
     3899     *
     3900     * @return int
     3901     */
     3902    protected function _duplicatePolylangVariation( WC_Product $variation, $targetParentId, $langCode, $postModel )
     3903    {
     3904        $duplicateId = 0;
     3905
     3906        if ( !class_exists( 'WC_Product_Variation' ) ) {
     3907            return $duplicateId;
     3908        }
     3909
     3910        $targetParent = wc_get_product( $targetParentId );
     3911
     3912        if ( !$targetParent || $targetParent->get_type() !== 'variable' ) {
     3913            return $duplicateId;
     3914        }
     3915
     3916        $newVariation = new WC_Product_Variation();
     3917        $newVariation->set_parent_id( $targetParentId );
     3918
     3919        $newVariation->set_sku( $variation->get_sku() );
     3920        $newVariation->set_regular_price( $variation->get_regular_price() );
     3921        $newVariation->set_sale_price( $variation->get_sale_price() );
     3922        $newVariation->set_date_on_sale_from( $variation->get_date_on_sale_from() );
     3923        $newVariation->set_date_on_sale_to( $variation->get_date_on_sale_to() );
     3924        $newVariation->set_manage_stock( $variation->get_manage_stock() );
     3925        $newVariation->set_stock_quantity( $variation->get_stock_quantity() );
     3926        $newVariation->set_stock_status( $variation->get_stock_status() );
     3927        $newVariation->set_backorders( $variation->get_backorders() );
     3928        $newVariation->set_weight( $variation->get_weight() );
     3929        $newVariation->set_length( $variation->get_length() );
     3930        $newVariation->set_width( $variation->get_width() );
     3931        $newVariation->set_height( $variation->get_height() );
     3932        $newVariation->set_tax_class( $variation->get_tax_class() );
     3933        $newVariation->set_shipping_class_id( $variation->get_shipping_class_id() );
     3934        $newVariation->set_image_id( $variation->get_image_id() );
     3935        $newVariation->set_virtual( $variation->get_virtual() );
     3936        $newVariation->set_downloadable( $variation->get_downloadable() );
     3937        $newVariation->set_description( $variation->get_description() );
     3938        $newVariation->set_status( $variation->get_status() );
     3939
     3940        $attributes = $variation->get_attributes();
     3941
     3942        if ( $attributes ) {
     3943            $newVariation->set_attributes( $attributes );
     3944        }
     3945
     3946        $duplicateId = $newVariation->save();
     3947
     3948        if ( $duplicateId ) {
     3949            if ( function_exists( 'pll_set_post_language' ) ) {
     3950                pll_set_post_language( $duplicateId, $langCode );
     3951            } elseif ( $postModel ) {
     3952                $postModel->set_language( $duplicateId, $langCode );
     3953            }
     3954
     3955            if ( function_exists( 'wc_delete_product_transients' ) ) {
     3956                wc_delete_product_transients( $targetParentId );
     3957            }
     3958        }
     3959
     3960        return $duplicateId;
     3961    }
     3962
     3963    /**
     3964     * Sync variation attributes to translation
     3965     *
     3966     * @param WC_Product  $variation   Source variation
     3967     * @param WC_Product  $trVariation Translated variation
     3968     * @param string      $langCode    Target language code
     3969     * @param object|null $model       Polylang main model
     3970     * @param array       $languages   Polylang languages
     3971     *
     3972     * @return void
     3973     */
     3974    protected function _polylangSyncVariationAttributes( WC_Product $variation, WC_Product $trVariation, $langCode, $model, array $languages = array() )
     3975    {
     3976        $attributes = $variation->get_attributes();
     3977
     3978        if ( $attributes ) {
     3979            $termModel = null;
     3980
     3981            if ( is_object( $model ) && isset( $model->term ) ) {
     3982                $termModel = $model->term;
     3983            }
     3984
     3985            $termIdsByTaxonomy = [];
     3986
     3987            if ( $languages ) {
     3988                foreach ( $attributes as $attributeName => $attributeValue ) {
     3989                    if ( taxonomy_exists( $attributeName ) ) {
     3990                        $term = get_term_by( 'slug', $attributeValue, $attributeName );
     3991
     3992                        if ( $term && !is_wp_error( $term ) ) {
     3993                            if ( !isset( $termIdsByTaxonomy[$attributeName] ) ) {
     3994                                $termIdsByTaxonomy[$attributeName] = [];
     3995                            }
     3996
     3997                            $termIdsByTaxonomy[$attributeName][] = (int)$term->term_id;
     3998                        }
     3999                    }
     4000                }
     4001            }
     4002
     4003            if ( $termIdsByTaxonomy ) {
     4004                foreach ( $termIdsByTaxonomy as $taxonomy => $termIds ) {
     4005                    $termIds = array_values( array_unique( $termIds ) );
     4006
     4007                    if ( $termIds ) {
     4008                        $this->_polylangTranslateTaxonomy( $termIds, $taxonomy, $languages, $termModel, $langCode );
     4009                    }
     4010                }
     4011            }
     4012
     4013            if ( class_exists( 'PLLWC_Products' )
     4014                && method_exists( 'PLLWC_Products', 'maybe_translate_attributes' )
     4015                && function_exists( 'pll_is_translated_taxonomy' )
     4016            ) {
     4017                $translatedAttributes = PLLWC_Products::maybe_translate_attributes( $attributes, $langCode );
     4018                $trVariation->set_attributes( $translatedAttributes );
     4019
     4020                if ( $imageId = $variation->get_image_id() ) {
     4021                    $trVariation->set_image_id( $imageId );
     4022                }
     4023
     4024                return;
     4025            }
     4026
     4027            $translatedAttributes = array();
     4028
     4029            foreach ( $attributes as $attributeName => $attributeValue ) {
     4030                if ( taxonomy_exists( $attributeName ) ) {
     4031                    $term = get_term_by( 'slug', $attributeValue, $attributeName );
     4032
     4033                    if ( $term && !is_wp_error( $term ) ) {
     4034                        $translatedTermId = $this->_polylangGetTranslatedTermId( $term->term_id, $langCode, $termModel );
     4035
     4036                        if ( !$translatedTermId ) {
     4037                            $translatedTermId = $this->_createPolylangTermTranslation( $term->term_id, $langCode, $attributeName, $termModel );
     4038                        }
     4039
     4040                        if ( $translatedTermId ) {
     4041                            $translatedTerm = get_term_by( 'id', $translatedTermId, $attributeName );
     4042
     4043                            if ( $translatedTerm && !is_wp_error( $translatedTerm ) ) {
     4044                                $translatedAttributes[$attributeName] = $translatedTerm->slug;
     4045                            } else {
     4046                                $translatedAttributes[$attributeName] = $attributeValue;
     4047                            }
     4048
     4049                            $trParentId = $trVariation->get_parent_id();
     4050
     4051                            if ( $trParentId ) {
     4052                                wp_set_object_terms( $trParentId, (int)$translatedTermId, $attributeName, true );
     4053                            }
     4054                        } else {
     4055                            $translatedAttributes[$attributeName] = $attributeValue;
     4056                        }
     4057                    } else {
     4058                        $translatedAttributes[$attributeName] = $attributeValue;
     4059                    }
    30274060                } else {
    3028                     wp_set_post_terms( $productId, $termIds, $termName, true );
    3029                 }
    3030             }
    3031         }
    3032 
    3033         $this->_wpmlSync( $product, $a2cData, $metaChanges );
    3034 
    3035         return $productId;
     4061                    $translatedAttributes[$attributeName] = $attributeValue;
     4062                }
     4063            }
     4064
     4065            $trVariation->set_attributes( $translatedAttributes );
     4066
     4067            if ( $imageId = $variation->get_image_id() ) {
     4068                $trVariation->set_image_id( $imageId );
     4069            }
     4070        }
     4071    }
     4072
     4073    /**
     4074     * Get translated term ID for Polylang
     4075     *
     4076     * @param int         $termId    Term ID
     4077     * @param string      $langCode  Target language code
     4078     * @param object|null $termModel Polylang term model
     4079     *
     4080     * @return int
     4081     */
     4082    protected function _polylangGetTranslatedTermId( $termId, $langCode, $termModel )
     4083    {
     4084        $translatedId = 0;
     4085
     4086        if ( function_exists( 'pll_get_term' ) ) {
     4087            $translatedId = pll_get_term( $termId, $langCode );
     4088        } elseif ( $termModel && method_exists( $termModel, 'get_translation' ) ) {
     4089            $translatedId = $termModel->get_translation( $termId, $langCode );
     4090        } elseif ( $termModel && method_exists( $termModel, 'get_translations' ) ) {
     4091            $translations = $termModel->get_translations( $termId );
     4092
     4093            if ( isset( $translations[$langCode] ) ) {
     4094                $translatedId = $translations[$langCode];
     4095            }
     4096        }
     4097
     4098        return (int)$translatedId;
     4099    }
     4100
     4101    /**
     4102     * Initialize Polylang data from product data array
     4103     *
     4104     * @param array $a2cData A2C Data
     4105     *
     4106     * @return array
     4107     */
     4108    private function _initPolylangData( array $a2cData )
     4109    {
     4110        $internalData = array();
     4111        $termsData = array();
     4112
     4113        if ( isset( $a2cData['product_data']['internal_data'] ) && is_array( $a2cData['product_data']['internal_data'] ) ) {
     4114            $internalData = $a2cData['product_data']['internal_data'];
     4115        }
     4116
     4117        if ( isset( $a2cData['product_data']['terms_data'] ) && is_array( $a2cData['product_data']['terms_data'] ) ) {
     4118            $termsData = $a2cData['product_data']['terms_data'];
     4119        }
     4120
     4121        return array(
     4122            'langCode'        => isset( $internalData['polylang_lang_code'] ) ? $internalData['polylang_lang_code'] : null,
     4123            'onlyTranslateTo' => isset( $internalData['polylang_only_translate_to'] ) ? $internalData['polylang_only_translate_to'] : null,
     4124            'termsData'       => $termsData,
     4125        );
     4126    }
     4127
     4128    /**
     4129     * @param int $productId Product ID
     4130     *
     4131     * @return array
     4132     */
     4133    protected function _getPolylangProductTermsData( $productId )
     4134    {
     4135        $termsData = array();
     4136        $taxonomies = array( 'product_cat', 'product_tag' );
     4137
     4138        foreach ( $taxonomies as $taxonomy ) {
     4139            if ( $taxonomy === 'product_tag' ) {
     4140                $terms = wp_get_post_terms( $productId, $taxonomy );
     4141
     4142                if ( is_wp_error( $terms ) ) {
     4143                    continue;
     4144                }
     4145
     4146                if ( $terms ) {
     4147                    $items = array();
     4148
     4149                    foreach ( $terms as $term ) {
     4150                        if ( isset( $term->name ) && $term->name !== '' ) {
     4151                            $items[] = array( 'name' => $term->name );
     4152                        }
     4153                    }
     4154
     4155                    if ( $items ) {
     4156                        $termsData[$taxonomy] = $items;
     4157                    }
     4158                }
     4159            } else {
     4160                $termIds = wp_get_post_terms( $productId, $taxonomy, array( 'fields' => 'ids' ) );
     4161
     4162                if ( is_wp_error( $termIds ) ) {
     4163                    continue;
     4164                }
     4165
     4166                if ( $termIds ) {
     4167                    $items = array();
     4168
     4169                    foreach ( $termIds as $termId ) {
     4170                        $items[] = array( 'name' => (int)$termId );
     4171                    }
     4172
     4173                    if ( $items ) {
     4174                        $termsData[$taxonomy] = $items;
     4175                    }
     4176                }
     4177            }
     4178        }
     4179
     4180        return $termsData;
     4181    }
     4182
     4183    /**
     4184     * Normalize language list to array format
     4185     *
     4186     * @param array|string $languages Language or languages list
     4187     *
     4188     * @return array
     4189     */
     4190    private function _normalizeLanguageList( $languages )
     4191    {
     4192        if ( !$languages ) {
     4193            return array();
     4194        }
     4195
     4196        return is_array( $languages ) ? $languages : array( $languages );
     4197    }
     4198
     4199    /**
     4200     * Get or initialize translations with fallback to base language
     4201     *
     4202     * @param int         $postId    Post ID
     4203     * @param object|null $postModel Polylang post model
     4204     *
     4205     * @return array
     4206     */
     4207    private function _getOrInitializePolylangTranslations( $postId, $postModel )
     4208    {
     4209        $translations = $this->_polylangGetTranslations( $postId, $postModel );
     4210
     4211        if ( !empty( $translations ) ) {
     4212            return $translations;
     4213        }
     4214
     4215        $baseLang = $this->_polylangGetLanguage( $postId, $postModel );
     4216        if ( $baseLang ) {
     4217            return array( $baseLang => $postId );
     4218        }
     4219
     4220        return array();
     4221    }
     4222
     4223    /**
     4224     * Determine if language set should be skipped
     4225     *
     4226     * @param int         $sourceId  Source product ID
     4227     * @param int         $productId Current product ID
     4228     * @param string|null $langCode  Language code
     4229     * @param object|null $postModel Polylang post model
     4230     *
     4231     * @return bool
     4232     */
     4233    private function _isSkipPolylangLanguageSet( $sourceId, $productId, $langCode, $postModel )
     4234    {
     4235        if ( $sourceId <= 0 || !$langCode ) {
     4236            return false;
     4237        }
     4238
     4239        $sourceLang = $this->_polylangGetLanguage( $sourceId, $postModel );
     4240        $translationId = $this->_getPolylangTranslationId( $sourceId, $langCode );
     4241
     4242        // Reset if source language differs and translation ID equals source ID
     4243        if ( $sourceLang && $langCode !== $sourceLang && $translationId === $sourceId ) {
     4244            $translationId = 0;
     4245        }
     4246
     4247        // Skip if this is the first time and product equals source
     4248        return $translationId === 0 && $productId === $sourceId;
     4249    }
     4250
     4251    /**
     4252     * Save product translation for new language
     4253     *
     4254     * @param int         $sourceId  Source product ID
     4255     * @param int         $productId Current product ID
     4256     * @param string|null $langCode  Language code
     4257     * @param object|null $postModel
     4258     *
     4259     * @return void
     4260     */
     4261    private function _savePolylangProductTranslation( $sourceId, $productId, $langCode, $postModel )
     4262    {
     4263        if ( $sourceId <= 0 || $productId === $sourceId || !$langCode ) {
     4264            return;
     4265        }
     4266
     4267        $sourceLang = $this->_polylangGetLanguage( $sourceId, $postModel );
     4268        if ( !$sourceLang ) {
     4269            return;
     4270        }
     4271
     4272        $translations = $this->_polylangGetTranslations( $sourceId, $postModel );
     4273
     4274        if ( $translations ) {
     4275            $translations[$langCode] = $productId;
     4276        } else {
     4277            $translations = array(
     4278                $sourceLang => $sourceId,
     4279                $langCode   => $productId,
     4280            );
     4281        }
     4282
     4283        $this->_polylangSaveTranslations( $sourceId, $translations, $postModel );
     4284    }
     4285
     4286    /**
     4287     * Process language variants and create duplicates as needed
     4288     *
     4289     * @param WC_Product  $product         WC Product
     4290     * @param int         $defaultLangPrId Default language product ID
     4291     * @param array       $langList        Language list to process
     4292     * @param string|null $langCode        Current product language code
     4293     * @param object|null $postModel       Polylang post model
     4294     * @param array       $createdIds      Reference to array for storing created product IDs for rollback
     4295     *
     4296     * @return array
     4297     */
     4298    private function _preparePolylangProductTranslations( $product, $defaultLangPrId, $langList, $langCode, $postModel, &$createdIds )
     4299    {
     4300        if ( !$langList ) {
     4301            return array();
     4302        }
     4303
     4304        $productId = $product->get_id();
     4305        $translations = $this->_getOrInitializePolylangTranslations( $defaultLangPrId, $postModel );
     4306
     4307        foreach ( $langList as $langItem ) {
     4308            if ( isset( $translations[$langItem] ) ) {
     4309                continue;
     4310            }
     4311
     4312            if ( $langItem === $langCode && $productId !== $defaultLangPrId ) {
     4313                $translations[$langItem] = $productId;
     4314            } else {
     4315                $duplicatePrId = $this->_duplicatePolylangProduct( $product, $langItem, $postModel );
     4316
     4317                if ( $duplicatePrId ) {
     4318                    $createdIds[] = $duplicatePrId;
     4319                    $translations[$langItem] = $duplicatePrId;
     4320                }
     4321            }
     4322        }
     4323
     4324        $this->_polylangSaveTranslations( $defaultLangPrId, $translations, $postModel );
     4325
     4326        return $translations;
     4327    }
     4328
     4329    /**
     4330     * Handle Polylang sync when no language code is set
     4331     *
     4332     * @param WC_Product  $product   Product object
     4333     * @param int         $productId Product ID
     4334     * @param object|null $postModel Polylang post model
     4335     * @param object|null $model     Polylang main model
     4336     * @param object|null $termModel Polylang term model
     4337     *
     4338     * @return void
     4339     */
     4340    private function _handlePolylangWithoutTranslation( $product, $productId, $postModel, $model, $termModel )
     4341    {
     4342        $termLangCode = $this->_polylangGetTermLanguage( null, false, $productId, $postModel );
     4343
     4344        if ( $termLangCode ) {
     4345            $this->_setPolylangLanguageForTerm( $productId, $termLangCode, $postModel, $model );
     4346            $this->_setPolylangLanguageForProductTerms( $product, $termLangCode, $termModel );
     4347        }
     4348    }
     4349
     4350    /**
     4351     * Translate product terms based on active languages and product language code
     4352     *
     4353     * @param array $a2cData   Data
     4354     * @param array $languages Active languages
     4355     *
     4356     * @return void
     4357     */
     4358    protected function _polyLangTranslateTerms( array $a2cData, array $languages )
     4359    {
     4360        $model = $this->_getPolylangModel();
     4361        $activeLanguages = $languages;
     4362        $internalData = [];
     4363        $productLangCode = null;
     4364
     4365        if ( isset( $a2cData['product_data']['internal_data'] ) && is_array( $a2cData['product_data']['internal_data'] ) ) {
     4366            $internalData = $a2cData['product_data']['internal_data'];
     4367        }
     4368
     4369        if ( isset( $internalData['polylang_lang_code'] ) ) {
     4370            $productLangCode = $internalData['polylang_lang_code'];
     4371        }
     4372
     4373        if ( $productLangCode === null || $productLangCode === '' ) {
     4374            if ( function_exists( 'pll_default_language' ) ) {
     4375                $defaultLang = pll_default_language( 'slug' );
     4376
     4377                if ( $defaultLang ) {
     4378                    $productLangCode = $defaultLang;
     4379                }
     4380            }
     4381        }
     4382
     4383        if ( $activeLanguages && $model && isset( $model->term ) ) {
     4384            if ( isset( $a2cData['product_data']['terms_data']['product_cat'] ) ) {
     4385                $termNames = array_unique( array_column( $a2cData['product_data']['terms_data']['product_cat'], 'name' ) );
     4386
     4387                if ( $termNames ) {
     4388                    $this->_polylangTranslateTaxonomy( $termNames, 'product_cat', $activeLanguages, $model->term, $productLangCode );
     4389                }
     4390            }
     4391
     4392            if ( isset( $a2cData['product_data']['terms_data']['product_tag'] ) ) {
     4393                $termNames = array_unique( array_column( $a2cData['product_data']['terms_data']['product_tag'], 'name' ) );
     4394
     4395                if ( $termNames ) {
     4396                    $this->_polylangTranslateTaxonomy( $termNames, 'product_tag', $activeLanguages, $model->term, $productLangCode );
     4397                }
     4398            }
     4399        }
     4400    }
     4401
     4402    /**
     4403     * Get Polylang language for a post
     4404     *
     4405     * @param int         $postId    Post ID
     4406     * @param string|null $langCode  Language code
     4407     * @param object|null $postModel Polylang post model
     4408     *
     4409     * @return void
     4410     */
     4411    protected function _polylangSetLanguage( $postId, $langCode, $postModel )
     4412    {
     4413        if ( $langCode && function_exists( 'pll_set_post_language' ) ) {
     4414            pll_set_post_language( $postId, $langCode );
     4415        } elseif ( $langCode && $postModel ) {
     4416            $postModel->set_language( $postId, $langCode );
     4417        }
     4418    }
     4419
     4420    /**
     4421     * Get Polylang language for a post
     4422     *
     4423     * @param int         $postId    Post ID
     4424     * @param string|null $langCode  Language code
     4425     * @param object|null $postModel Polylang post model
     4426     * @param object|null $model     Polylang main model
     4427     *
     4428     * @return void
     4429     */
     4430    protected function _setPolylangLanguageForTerm( $postId, $langCode, $postModel, $model )
     4431    {
     4432        if ( $langCode ) {
     4433            $currentLang = $this->_polylangGetLanguage( $postId, $postModel );
     4434
     4435            if ( $currentLang === $langCode ) {
     4436                return;
     4437            }
     4438
     4439            if ( $model ) {
     4440                $langObj = $model->get_language( $langCode );
     4441
     4442                if ( $langObj && isset( $langObj->term_id ) ) {
     4443                    if ( function_exists( 'taxonomy_exists' ) && taxonomy_exists( 'language' ) ) {
     4444                        wp_set_object_terms( $postId, (int)$langObj->term_id, 'language' );
     4445                    }
     4446                }
     4447            }
     4448        }
     4449    }
     4450
     4451    /**
     4452     * Set Polylang language for product terms
     4453     *
     4454     * @param WC_Product  $product   WC Product
     4455     * @param string|null $langCode  Language code
     4456     * @param object|null $termModel Polylang term model
     4457     *
     4458     * @return void
     4459     */
     4460    protected function _setPolylangLanguageForProductTerms( WC_Product $product, $langCode, $termModel )
     4461    {
     4462        if ( $langCode ) {
     4463            $taxonomies = [ 'product_cat', 'product_tag' ];
     4464            $attributes = $product->get_attributes();
     4465
     4466            foreach ( $attributes as $attribute ) {
     4467                if ( is_object( $attribute ) && method_exists( $attribute, 'is_taxonomy' ) && $attribute->is_taxonomy() ) {
     4468                    $taxonomies[] = $attribute->get_taxonomy();
     4469                }
     4470            }
     4471
     4472            $taxonomies = array_unique( $taxonomies );
     4473            $productId = $product->get_id();
     4474
     4475            foreach ( $taxonomies as $taxonomy ) {
     4476                $termIds = wp_get_post_terms( $productId, $taxonomy, [ 'fields' => 'ids' ] );
     4477
     4478                if ( is_wp_error( $termIds ) ) {
     4479                    continue;
     4480                }
     4481
     4482                if ( $termIds ) {
     4483                    foreach ( $termIds as $termId ) {
     4484                        $this->_setPolylangTermLanguage( (int)$termId, $langCode, $termModel );
     4485                    }
     4486                }
     4487            }
     4488        }
     4489    }
     4490
     4491    /**
     4492     * Map product terms to corresponding terms in the target language
     4493     *
     4494     * @param array       $translations Language translations array
     4495     * @param array       $termsData    Terms data array
     4496     * @param object|null $termModel    Polylang term model
     4497     *
     4498     * @return void
     4499     */
     4500    protected function _syncPolylangProductTerms( array $translations, array $termsData, $termModel )
     4501    {
     4502        if ( $translations && $termsData ) {
     4503            $filteredTermsData = [];
     4504            $nonTranslatedTerms = [];
     4505
     4506            foreach ( $termsData as $taxonomy => $items ) {
     4507                $hasValues = false;
     4508
     4509                foreach ( $items as $item ) {
     4510                    if ( !empty( $item['name'] ) ) {
     4511                        $hasValues = true;
     4512                        break;
     4513                    }
     4514                }
     4515
     4516                if ( $hasValues ) {
     4517                    $filteredTermsData[$taxonomy] = $items;
     4518
     4519                    if ( isset( $items[0]['all_lang'] ) && $items[0]['all_lang'] === false ) {
     4520                        $nonTranslatedTerms[$taxonomy] = $items;
     4521                    }
     4522                }
     4523            }
     4524
     4525            if ( $filteredTermsData ) {
     4526                foreach ( $translations as $langCode => $translationId ) {
     4527                    $termsToMap = $filteredTermsData;
     4528
     4529                    if ( $nonTranslatedTerms ) {
     4530                        foreach ( $nonTranslatedTerms as $taxonomy => $items ) {
     4531                            if ( isset( $termsToMap[$taxonomy] ) ) {
     4532                                unset( $termsToMap[$taxonomy] );
     4533                            }
     4534                        }
     4535                    }
     4536
     4537                    if ( $termsToMap ) {
     4538                        $mappedTerms = $this->_mapPolylangTerms( $termsToMap, $langCode, $termModel );
     4539
     4540                        if ( $nonTranslatedTerms ) {
     4541                            $mappedTerms = array_merge( $mappedTerms, $nonTranslatedTerms );
     4542                        }
     4543                    } else {
     4544                        $mappedTerms = $filteredTermsData;
     4545                    }
     4546
     4547                    $this->_setProductTerms( $translationId, $mappedTerms );
     4548                }
     4549            }
     4550        }
     4551    }
     4552
     4553    /**
     4554     * Map product terms to corresponding terms in the target language
     4555     *
     4556     * @param int         $termId    Term ID
     4557     * @param string|null $langCode  Language code
     4558     * @param object|null $termModel Polylang term model
     4559     *
     4560     * @return void
     4561     */
     4562    protected function _setPolylangTermLanguage( $termId, $langCode, $termModel )
     4563    {
     4564        if ( $langCode ) {
     4565            $currentLang = null;
     4566
     4567            if ( function_exists( 'pll_get_term_language' ) ) {
     4568                $currentLang = pll_get_term_language( $termId );
     4569            } elseif ( $termModel ) {
     4570                $langObj = $termModel->get_language( $termId );
     4571
     4572                if ( $langObj && isset( $langObj->slug ) ) {
     4573                    $currentLang = $langObj->slug;
     4574                }
     4575            }
     4576
     4577            if ( empty( $currentLang ) ) {
     4578                if ( function_exists( 'pll_set_term_language' ) ) {
     4579                    pll_set_term_language( $termId, $langCode );
     4580                } elseif ( $termModel ) {
     4581                    $termModel->set_language( $termId, $langCode );
     4582                }
     4583            }
     4584        }
     4585    }
     4586
     4587    /**
     4588     * Get language code for product terms with multiple fallback options
     4589     *
     4590     * @param string|null $langCode    Language code
     4591     * @param bool        $skipSetLang Whether to skip setting language for the product
     4592     * @param int         $productId   Product ID
     4593     * @param object|null $postModel   Polylang post model
     4594     *
     4595     * @return string|null
     4596     */
     4597    protected function _polylangGetTermLanguage( $langCode, $skipSetLang, $productId, $postModel = null )
     4598    {
     4599        $termLangCode = null;
     4600        $model = null;
     4601
     4602        if ( $postModel === null ) {
     4603            $model = $this->_getPolylangModel();
     4604
     4605            if ( $model && isset( $model->post ) ) {
     4606                $postModel = $model->post;
     4607            }
     4608        }
     4609
     4610        if ( $langCode && !$skipSetLang ) {
     4611            $termLangCode = $langCode;
     4612        }
     4613
     4614        if ( empty( $termLangCode ) && $postModel ) {
     4615            $termLangCode = $this->_polylangGetLanguage( $productId, $postModel );
     4616        }
     4617
     4618        if ( empty( $termLangCode ) ) {
     4619            if ( function_exists( 'pll_default_language' ) ) {
     4620                $termLangCode = pll_default_language( 'slug' );
     4621            } elseif ( $model && method_exists( $model, 'get_default_language' ) ) {
     4622                $langObj = $model->get_default_language();
     4623
     4624                if ( $langObj && isset( $langObj->slug ) ) {
     4625                    $termLangCode = $langObj->slug;
     4626                }
     4627            }
     4628        }
     4629
     4630        return $termLangCode;
     4631    }
     4632
     4633    /**
     4634     * Get Polylang language for a post
     4635     *
     4636     * @param int         $postId    Post ID
     4637     * @param object|null $postModel Polylang post model
     4638     *
     4639     * @return string|null
     4640     */
     4641    protected function _polylangGetLanguage( $postId, $postModel )
     4642    {
     4643        $lang = null;
     4644
     4645        if ( function_exists( 'pll_get_post_language' ) ) {
     4646            $lang = pll_get_post_language( $postId );
     4647        } elseif ( $postModel ) {
     4648            $langObj = $postModel->get_language( $postId );
     4649
     4650            if ( $langObj && isset( $langObj->slug ) ) {
     4651                $lang = $langObj->slug;
     4652            }
     4653        }
     4654
     4655        return $lang;
     4656    }
     4657
     4658    /**
     4659     * Get Polylang translations for a post
     4660     *
     4661     * @param int         $postId    Post ID
     4662     * @param object|null $postModel Polylang post model
     4663     *
     4664     * @return array
     4665     */
     4666    protected function _polylangGetTranslations( $postId, $postModel )
     4667    {
     4668        $translations = [];
     4669
     4670        if ( function_exists( 'pll_get_post_translations' ) ) {
     4671            $translations = pll_get_post_translations( $postId );
     4672        } elseif ( $postModel ) {
     4673            $translations = $postModel->get_translations( $postId );
     4674        }
     4675
     4676        if ( is_array( $translations ) ) {
     4677            return $translations;
     4678        }
     4679
     4680        return [];
     4681    }
     4682
     4683    /**
     4684     * Save Polylang translations for a post
     4685     *
     4686     * @param int         $sourceId     Post ID of the source product
     4687     * @param array       $translations Translations array in format [lang_code => post_id]
     4688     * @param object|null $postModel    Polylang post model
     4689     *
     4690     * @return void
     4691     */
     4692    protected function _polylangSaveTranslations( $sourceId, array $translations, $postModel )
     4693    {
     4694        if ( $translations ) {
     4695            if ( function_exists( 'pll_save_post_translations' ) ) {
     4696                pll_save_post_translations( $translations );
     4697            } elseif ( $postModel ) {
     4698                $postModel->save_translations( $sourceId, $translations );
     4699            }
     4700        }
     4701    }
     4702
     4703    /**
     4704     * Duplicate product for Polylang translation
     4705     *
     4706     * @param WC_Product  $product   Product
     4707     * @param string      $langCode  Language code
     4708     * @param object|null $postModel Post model
     4709     *
     4710     * @return int
     4711     */
     4712    protected function _duplicatePolylangProduct( WC_Product $product, $langCode, $postModel )
     4713    {
     4714        $duplicatePrId = 0;
     4715        $usePllWC = false;
     4716        $pllWCDuplicator = null;
     4717
     4718        if ( $langCode && class_exists( 'WC_Admin_Duplicate_Product' ) ) {
     4719            $this->_initPolylangDuplicateProduct();
     4720
     4721            if ( class_exists( 'PLLWC_Admin_Product_Duplicate' ) ) {
     4722                $usePllWC = true;
     4723                $pllWCDuplicator = new PLLWC_Admin_Product_Duplicate();
     4724            }
     4725
     4726            $duplicator = new WC_Admin_Duplicate_Product();
     4727            $productDuplicate = $duplicator->product_duplicate( $product );
     4728
     4729            if ( $productDuplicate instanceof WC_Product ) {
     4730                if ( $usePllWC ) {
     4731                    $pllWCDuplicator->product_duplicate( $productDuplicate, $product );
     4732                }
     4733
     4734                $productDuplicate->set_name( $product->get_name() );
     4735                $productDuplicate->set_description( $product->get_description() );
     4736                $productDuplicate->set_short_description( $product->get_short_description() );
     4737                $productDuplicate->set_status( $product->get_status() );
     4738                $productDuplicate->save();
     4739
     4740                $duplicatePrId = $productDuplicate->get_id();
     4741
     4742                if ( $duplicatePrId ) {
     4743                    $origSku = $product->get_sku();
     4744
     4745                    if ( $origSku !== null && $origSku !== '' ) {
     4746                        update_post_meta( $duplicatePrId, '_sku', $origSku );
     4747
     4748                        if ( function_exists( 'wc_update_product_lookup_tables' ) ) {
     4749                            wc_update_product_lookup_tables( $duplicatePrId );
     4750                        }
     4751
     4752                        if ( function_exists( 'wc_delete_product_transients' ) ) {
     4753                            wc_delete_product_transients( $duplicatePrId );
     4754                        }
     4755                    }
     4756
     4757                    if ( function_exists( 'pll_set_post_language' ) ) {
     4758                        pll_set_post_language( $duplicatePrId, $langCode );
     4759                    } elseif ( $postModel ) {
     4760                        $postModel->set_language( $duplicatePrId, $langCode );
     4761                    }
     4762                }
     4763            }
     4764        }
     4765
     4766        return $duplicatePrId;
     4767    }
     4768
     4769    /**
     4770     * Map product terms to corresponding terms in the target language
     4771     *
     4772     * @param array       $termsData Terms data
     4773     * @param string|null $langCode  Lang code
     4774     * @param object|null $termModel Term model
     4775     *
     4776     * @return array
     4777     */
     4778    protected function _mapPolylangTerms( array $termsData, $langCode, $termModel = null )
     4779    {
     4780        $result = $termsData;
     4781
     4782        if ( $langCode ) {
     4783            $result = [];
     4784
     4785            foreach ( $termsData as $taxonomy => $terms ) {
     4786                $mapped = [];
     4787
     4788                foreach ( $terms as $term ) {
     4789                    $termId = 0;
     4790                    $foundTermId = 0;
     4791
     4792                    if ( $taxonomy === 'product_tag' ) {
     4793                        if ( isset( $term['name'] ) && $term['name'] !== '' ) {
     4794                            $foundTermId = $this->_findPolylangTermByNameAndLang( $term['name'], $taxonomy, $langCode, $termModel );
     4795                        }
     4796
     4797                        if ( $foundTermId ) {
     4798                            $termId = $foundTermId;
     4799                        } else {
     4800                            $termObj = get_term_by( 'name', $term['name'], $taxonomy );
     4801
     4802                            if ( $termObj instanceof WP_Term ) {
     4803                                $termId = (int)$termObj->term_id;
     4804                            }
     4805                        }
     4806                    } else {
     4807                        $termId = (int)$term['name'];
     4808                    }
     4809
     4810                    if ( $termId ) {
     4811                        $termLangCode = null;
     4812
     4813                        if ( function_exists( 'pll_get_term_language' ) ) {
     4814                            $termLangCode = pll_get_term_language( $termId );
     4815                        } elseif ( $termModel && method_exists( $termModel, 'get_language' ) ) {
     4816                            $langObj = $termModel->get_language( $termId );
     4817
     4818                            if ( $langObj && isset( $langObj->slug ) ) {
     4819                                $termLangCode = $langObj->slug;
     4820                            }
     4821                        }
     4822
     4823                        if ( $termLangCode === $langCode ) {
     4824                            $term['name'] = (int)$termId;
     4825                            $mapped[] = $term;
     4826                            continue;
     4827                        }
     4828
     4829                        $trId = 0;
     4830
     4831                        if ( function_exists( 'pll_get_term' ) ) {
     4832                            $trId = (int)pll_get_term( $termId, $langCode );
     4833                        } elseif ( $termModel ) {
     4834                            $trId = (int)$termModel->get( $termId, $langCode );
     4835                        }
     4836
     4837                        if ( $trId ) {
     4838                            $term['name'] = (int)$trId;
     4839                        } else {
     4840                            if ( $foundTermId === 0 ) {
     4841                                $termObj = get_term( $termId, $taxonomy );
     4842
     4843                                if ( $termObj && !is_wp_error( $termObj ) ) {
     4844                                    $foundTermId = $this->_findPolylangTermByNameAndLang( $termObj->name, $taxonomy, $langCode, $termModel );
     4845                                }
     4846                            }
     4847
     4848                            if ( $foundTermId ) {
     4849                                $term['name'] = (int)$foundTermId;
     4850                            } else {
     4851                                $createdTermId = $this->_createPolylangTermTranslation( $termId, $langCode, $taxonomy, $termModel );
     4852
     4853                                if ( $createdTermId ) {
     4854                                    $term['name'] = (int)$createdTermId;
     4855                                }
     4856                            }
     4857                        }
     4858                    }
     4859
     4860                    $mapped[] = $term;
     4861                }
     4862
     4863                $result[$taxonomy] = $mapped;
     4864            }
     4865        }
     4866
     4867        return $result;
     4868    }
     4869
     4870    /**
     4871     * Find Polylang term by name and language
     4872     *
     4873     * @param string      $termName  Term name
     4874     * @param string      $taxonomy  Taxonomy name
     4875     * @param string|null $langCode  Lang code
     4876     * @param object|null $termModel Term model
     4877     *
     4878     * @return int
     4879     */
     4880    protected function _findPolylangTermByNameAndLang( $termName, $taxonomy, $langCode, $termModel = null )
     4881    {
     4882        if ( $termName && $langCode ) {
     4883            $terms = get_terms( [
     4884                'taxonomy'   => $taxonomy,
     4885                'name'       => $termName,
     4886                'hide_empty' => false,
     4887            ] );
     4888
     4889            if ( is_wp_error( $terms ) ) {
     4890                return 0;
     4891            }
     4892
     4893            if ( $terms ) {
     4894                foreach ( $terms as $term ) {
     4895                    $termLangCode = null;
     4896
     4897                    if ( function_exists( 'pll_get_term_language' ) ) {
     4898                        $termLangCode = pll_get_term_language( $term->term_id );
     4899                    } elseif ( $termModel && method_exists( $termModel, 'get_language' ) ) {
     4900                        $langObj = $termModel->get_language( $term->term_id );
     4901
     4902                        if ( $langObj && isset( $langObj->slug ) ) {
     4903                            $termLangCode = $langObj->slug;
     4904                        }
     4905                    }
     4906
     4907                    if ( $termLangCode === $langCode ) {
     4908                        return (int)$term->term_id;
     4909                    }
     4910                }
     4911            }
     4912        }
     4913
     4914        return 0;
     4915    }
     4916
     4917    /**
     4918     * Translate taxonomy terms for a product based on active languages and product language code
     4919     *
     4920     * @param array       $termIds         Term IDs or names
     4921     * @param string      $taxonomy        Taxonomy name
     4922     * @param array       $languages       Language slugs
     4923     * @param object|null $termModel       Term model
     4924     * @param string|null $productLangCode Product language code
     4925     *
     4926     * @return void
     4927     */
     4928    protected function _polylangTranslateTaxonomy( array $termIds, $taxonomy, array $languages, $termModel = null, $productLangCode = null )
     4929    {
     4930        $terms = [];
     4931
     4932        foreach ( $termIds as $termId ) {
     4933            if ( $taxonomy === 'product_tag' ) {
     4934                $term = get_term_by( 'name', $termId, $taxonomy );
     4935            } else {
     4936                $term = get_term_by( 'id', (int)$termId, $taxonomy );
     4937            }
     4938
     4939            if ( $term && !is_wp_error( $term ) ) {
     4940                $terms[] = $term;
     4941            }
     4942        }
     4943
     4944        if ( $terms ) {
     4945            foreach ( $terms as $term ) {
     4946                $termLangCode = null;
     4947
     4948                if ( function_exists( 'pll_get_term_language' ) ) {
     4949                    $termLangCode = pll_get_term_language( $term->term_id );
     4950                } elseif ( $termModel && method_exists( $termModel, 'get_language' ) ) {
     4951                    $langObj = $termModel->get_language( $term->term_id );
     4952
     4953                    if ( $langObj && isset( $langObj->slug ) ) {
     4954                        $termLangCode = $langObj->slug;
     4955                    }
     4956                }
     4957
     4958                if ( $termLangCode === null || $termLangCode === '' ) {
     4959                    if ( $productLangCode ) {
     4960                        if ( function_exists( 'pll_set_term_language' ) ) {
     4961                            pll_set_term_language( $term->term_id, $productLangCode );
     4962                        } elseif ( $termModel && method_exists( $termModel, 'set_language' ) ) {
     4963                            $termModel->set_language( $term->term_id, $productLangCode );
     4964                        }
     4965
     4966                        $termLangCode = $productLangCode;
     4967                    }
     4968                }
     4969
     4970                foreach ( $languages as $langCode ) {
     4971                    if ( $termLangCode && $termLangCode === $langCode ) {
     4972                        continue;
     4973                    }
     4974
     4975                    $translationId = 0;
     4976
     4977                    if ( function_exists( 'pll_get_term' ) ) {
     4978                        $translationId = (int)pll_get_term( $term->term_id, $langCode );
     4979                    } elseif ( $termModel ) {
     4980                        $translationId = (int)$termModel->get( $term->term_id, $langCode );
     4981                    }
     4982
     4983                    if ( $translationId === 0 ) {
     4984                        $this->_createPolylangTermTranslation( $term->term_id, $langCode, $taxonomy, $termModel );
     4985                    }
     4986                }
     4987            }
     4988        }
     4989    }
     4990
     4991    /**
     4992     * Get Polylang languages list
     4993     *
     4994     * @param object|null $model Polylang model
     4995     *
     4996     * @return array
     4997     */
     4998    protected function _getPolylangLanguagesList( $model = null )
     4999    {
     5000        $languages = [];
     5001
     5002        if ( function_exists( 'pll_languages_list' ) ) {
     5003            $languages = pll_languages_list( [ 'fields' => 'slug' ] );
     5004        } elseif ( $model && method_exists( $model, 'get_languages_list' ) ) {
     5005            $languages = $model->get_languages_list( array( 'fields' => 'slug' ) );
     5006        }
     5007
     5008        if ( is_array( $languages ) ) {
     5009            return $languages;
     5010        }
     5011
     5012        return [];
     5013    }
     5014
     5015    /**
     5016     * Create Polylang term translation for a given term ID and language code
     5017     *
     5018     * @param int         $termId    Term ID
     5019     * @param string|null $langCode  Language code
     5020     * @param string      $taxonomy  Taxonomy name
     5021     * @param object|null $termModel Polylang term model
     5022     *
     5023     * @return int
     5024     */
     5025    protected function _createPolylangTermTranslation( $termId, $langCode, $taxonomy, $termModel )
     5026    {
     5027        if ( $langCode ) {
     5028            $term = get_term( $termId, $taxonomy );
     5029
     5030            if ( $term && !is_wp_error( $term ) ) {
     5031                $translations = $this->_getPolylangTermTranslations( $termId, $termModel );
     5032
     5033                if ( isset( $translations[$langCode] ) ) {
     5034                    return (int)$translations[$langCode];
     5035                }
     5036
     5037                if ( $translations === [] ) {
     5038                    $baseLangCode = null;
     5039
     5040                    if ( function_exists( 'pll_get_term_language' ) ) {
     5041                        $baseLangCode = pll_get_term_language( $termId );
     5042                    } elseif ( $termModel && method_exists( $termModel, 'get_language' ) ) {
     5043                        $langObj = $termModel->get_language( $termId );
     5044
     5045                        if ( $langObj && isset( $langObj->slug ) ) {
     5046                            $baseLangCode = $langObj->slug;
     5047                        }
     5048                    }
     5049
     5050                    if ( $baseLangCode ) {
     5051                        $translations[$baseLangCode] = $termId;
     5052                    } else {
     5053                        if ( function_exists( 'pll_set_term_language' ) ) {
     5054                            pll_set_term_language( $termId, $langCode );
     5055                        } elseif ( $termModel ) {
     5056                            $termModel->set_language( $termId, $langCode );
     5057                        }
     5058
     5059                        return $termId;
     5060                    }
     5061                }
     5062
     5063                $args = [];
     5064
     5065                if ( is_taxonomy_hierarchical( $taxonomy ) && $term->parent ) {
     5066                    $parentTranslationId = $this->_createPolylangTermTranslation( $term->parent, $langCode, $taxonomy, $termModel );
     5067
     5068                    if ( $parentTranslationId ) {
     5069                        $args['parent'] = $parentTranslationId;
     5070                    }
     5071                }
     5072
     5073                if ( !empty( $term->description ) ) {
     5074                    $args['description'] = $term->description;
     5075                }
     5076
     5077                $termName = $term->name;
     5078                $newTerm = null;
     5079                $model = $this->_getPolylangModel();
     5080
     5081                if ( $termModel && $model && method_exists( $termModel, 'insert' ) ) {
     5082                    $langObj = $model->get_language( $langCode );
     5083
     5084                    if ( $langObj ) {
     5085                        $newTerm = $termModel->insert( $termName, $taxonomy, $langObj, $args );
     5086                    }
     5087                }
     5088
     5089                $newTermId = 0;
     5090
     5091                if ( $newTerm === null ) {
     5092                    $newTerm = wp_insert_term( $termName, $taxonomy, $args );
     5093                }
     5094
     5095                if ( $newTerm instanceof WP_Error && $newTerm->get_error_data( 'term_exists' ) ) {
     5096                    $uniqueArgs = $args;
     5097                    $uniqueName = $termName . ' (' . $langCode . ')';
     5098                    $uniqueSlug = '';
     5099
     5100                    if ( $term->slug ) {
     5101                        $uniqueSlug = $term->slug . '-' . $langCode;
     5102                    }
     5103
     5104                    if ( $uniqueSlug ) {
     5105                        $uniqueArgs['slug'] = sanitize_title( $uniqueSlug );
     5106                    } else {
     5107                        $uniqueArgs['slug'] = sanitize_title( $uniqueName );
     5108                    }
     5109
     5110                    $newTerm = null;
     5111
     5112                    if ( $termModel && $model && method_exists( $termModel, 'insert' ) ) {
     5113                        $langObj = $model->get_language( $langCode );
     5114
     5115                        if ( $langObj ) {
     5116                            $newTerm = $termModel->insert( $uniqueName, $taxonomy, $langObj, $uniqueArgs );
     5117                        }
     5118                    }
     5119
     5120                    if ( $newTerm === null ) {
     5121                        $newTerm = wp_insert_term( $uniqueName, $taxonomy, $uniqueArgs );
     5122                    }
     5123                }
     5124
     5125                if ( $newTerm instanceof WP_Error ) {
     5126                    $termExistsId = $newTerm->get_error_data( 'term_exists' );
     5127
     5128                    if ( $termExistsId ) {
     5129                        $newTermId = (int)$termExistsId;
     5130                    }
     5131                } elseif ( $newTerm instanceof WP_Term ) {
     5132                    $newTermId = (int)$newTerm->term_id;
     5133                } elseif ( is_array( $newTerm ) && isset( $newTerm['term_id'] ) ) {
     5134                    $newTermId = (int)$newTerm['term_id'];
     5135                } elseif ( is_int( $newTerm ) && $newTerm > 0 ) {
     5136                    $newTermId = $newTerm;
     5137                }
     5138
     5139                if ( $newTermId > 0 ) {
     5140                    if ( function_exists( 'pll_set_term_language' ) ) {
     5141                        pll_set_term_language( $newTermId, $langCode );
     5142                    } elseif ( $termModel ) {
     5143                        $termModel->set_language( $newTermId, $langCode );
     5144                    }
     5145
     5146                    $translations[$langCode] = $newTermId;
     5147                    $this->_savePolylangTermTranslations( $newTermId, $translations, $termModel );
     5148
     5149                    return $newTermId;
     5150                }
     5151            }
     5152        }
     5153
     5154        return 0;
     5155    }
     5156
     5157    /**
     5158     * Get Polylang translations for a term
     5159     *
     5160     * @param int         $termId    Term ID
     5161     * @param object|null $termModel Polylang term model
     5162     *
     5163     * @return array
     5164     */
     5165    protected function _getPolylangTermTranslations( $termId, $termModel )
     5166    {
     5167        $translations = [];
     5168
     5169        if ( function_exists( 'pll_get_term_translations' ) ) {
     5170            $translations = pll_get_term_translations( $termId );
     5171        } elseif ( $termModel ) {
     5172            $translations = $termModel->get_translations( $termId );
     5173        }
     5174
     5175        if ( is_array( $translations ) ) {
     5176            return $translations;
     5177        }
     5178
     5179        return [];
     5180    }
     5181
     5182    /**
     5183     * Save Polylang translations for a term
     5184     *
     5185     * @param int         $termId       Term ID
     5186     * @param array       $translations Translations array in format [lang_code => term_id]
     5187     * @param object|null $termModel    Polylang term model
     5188     *
     5189     * @return void
     5190     */
     5191    protected function _savePolylangTermTranslations( $termId, array $translations, $termModel )
     5192    {
     5193        if ( $translations ) {
     5194            if ( function_exists( 'pll_save_term_translations' ) ) {
     5195                pll_save_term_translations( $translations );
     5196            } elseif ( $termModel ) {
     5197                $termModel->save_translations( $termId, $translations );
     5198            }
     5199        }
    30365200    }
    30375201
     
    32275391
    32285392            if ( !empty( $ids ) ) {
    3229                 foreach ( $ids as $id ) {
    3230                     if ( $id && $id > 0 ) {
    3231                         $upsells[] = $id;
     5393                foreach ( $ids as $upsellId ) {
     5394                    if ( $upsellId && $upsellId > 0 ) {
     5395                        $upsells[] = $upsellId;
    32325396                    }
    32335397                }
     
    32425406
    32435407            if ( !empty( $ids ) ) {
    3244                 foreach ( $ids as $id ) {
    3245                     if ( $id && $id > 0 ) {
    3246                         $crosssells[] = $id;
     5408                foreach ( $ids as $crossSellId ) {
     5409                    if ( $crossSellId && $crossSellId > 0 ) {
     5410                        $crosssells[] = $crossSellId;
    32475411                    }
    32485412                }
     
    32545418        if ( isset( $data['product_data']['meta_data'] ) ) {
    32555419            foreach ( $data['product_data']['meta_data'] as $metaKey => $metaData ) {
    3256                 if ( in_array( $metaKey, ['_product_attributes', '_upsell_ids', '_crosssell_ids', '_sku'] ) ) {
     5420                if ( in_array( $metaKey, ['_product_attributes', '_upsell_ids', '_crosssell_ids'] ) ) {
    32575421                    continue;
    32585422                } else {
     
    32755439
    32765440        if ( isset( $data['product_data']['terms_data'] ) ) {
    3277             foreach ( $data['product_data']['terms_data'] as $termName => $terms ) {
    3278                 $termIds = array_unique( array_column( $terms, 'name' ) );
    3279                 $append  = false;
    3280 
    3281                 if ( isset( $terms[0]['append'] ) ) {
    3282                     $append = (bool)$terms[0]['append'];
    3283                 }
    3284 
    3285                 if ( !$append ) {
    3286                     wp_delete_object_term_relationships( $productId, $termName );
    3287                     wp_set_post_terms( $productId, $termIds, $termName );
    3288                 } else {
    3289                     wp_set_post_terms( $productId, $termIds, $termName, true );
    3290                 }
    3291             }
    3292         }
    3293 
    3294         if ( isset( $data['product_data']['internal_data']['no_wpml_sync'] ) && $data['product_data']['internal_data']['no_wpml_sync'] ) {
    3295             return $product;
    3296         }
    3297 
    3298         $this->_wpmlSync( $product, $data, $metaChanges );
     5441            $this->_setProductTerms( $productId, $data['product_data']['terms_data'] );
     5442        }
     5443
     5444        if ( $this->_wpmlEnabled ) {
     5445            $this->_wpmlSync( $product, $data, $metaChanges );
     5446        } elseif ( $this->_polylangEnabled ) {
     5447            $this->_polylangSync( $product, $data, $id );
     5448        }
    32995449
    33005450        return $product;
     
    33445494                    /* translators: %s: attachment id */
    33455495                    throw new WC_Data_Exception(
    3346                         'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'api2cart-bridge-connector' ), $attachment_id )
     5496                        'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'bridge-connector' ), $attachment_id )
    33475497                    );
    33485498                }
     
    36645814                    }
    36655815                } else {
    3666                     $trAttributeValue = apply_filters( 'wpml_translate_single_string', $attributeValue, 'api2cart-bridge-connector', $attributeName, $langCode);
     5816                    $trAttributeValue = apply_filters( 'wpml_translate_single_string', $attributeValue, 'bridge-connector', $attributeName, $langCode);
    36675817                }
    36685818
     
    37775927                        $values = explode( WC_DELIMITER, $attribute['options'] );
    37785928                    }
     5929
     5930                    $existingValues = array();
     5931                    $existingAttrs = $product->get_attributes();
     5932
     5933                    if ( $existingAttrs ) {
     5934                        foreach ( $existingAttrs as $existingAttr ) {
     5935                            if ( $existingAttr instanceof WC_Product_Attribute && !$existingAttr->is_taxonomy() ) {
     5936                                if ( $existingAttr->get_name() === $attributeName ) {
     5937                                    $existingValues = (array)$existingAttr->get_options();
     5938                                    break;
     5939                                }
     5940                            }
     5941                        }
     5942                    }
     5943
     5944                    if ( $existingValues ) {
     5945                        $values = array_merge( $existingValues, $values );
     5946                        $values = array_values( array_unique( array_map( 'trim', $values ) ) );
     5947                    } else {
     5948                        $values = array_values( array_unique( array_map( 'trim', $values ) ) );
     5949                    }
     5950                   
    37795951                    $attributeObject = new WC_Product_Attribute();
    37805952                    $attributeObject->set_name( $attributeName );
     
    39336105
    39346106                    $wpdb->query(
    3935                         $wpdb->prepare("
     6107                        $wpdb->prepare( "
     6108                            UPDATE {$wpdb->posts}
     6109                            SET {$fields}
     6110                            WHERE ID = %d",
     6111                            $values
     6112                        )
     6113                    );
     6114                }
     6115            }
     6116        }
     6117
     6118        if ( isset( $a2cData['product_data']['internal_data']['polylang_created_ids'] ) ) {
     6119            $createdIds = $a2cData['product_data']['internal_data']['polylang_created_ids'];
     6120
     6121            if ( is_array( $createdIds ) ) {
     6122                foreach ( $createdIds as $createdId ) {
     6123                    $product = WC()->product_factory->get_product( (int)$createdId );
     6124
     6125                    if ( $product instanceof WC_Product ) {
     6126                        $product->delete( true );
     6127                    }
     6128                }
     6129            }
     6130        }
     6131    }
     6132
     6133    /**
     6134     * @param int         $productId Product ID
     6135     * @param array       $a2cData   Data
     6136     * @param object|null $postModel Post model
     6137     *
     6138     * @return void
     6139     */
     6140    protected function _rollbackPolylangBaseProtectedData( $productId, $a2cData, $postModel )
     6141    {
     6142        global $wpdb;
     6143
     6144        if ( isset( $a2cData['product_data']['protected_data'] ) && is_array( $a2cData['product_data']['protected_data'] ) ) {
     6145            $protectedData = $a2cData['product_data']['protected_data'];
     6146            $langCode = $this->_polylangGetTermLanguage( null, false, $productId, $postModel );
     6147            $allowedFields = [
     6148                'post_title'   => true,
     6149                'post_content' => true,
     6150                'post_excerpt' => true,
     6151                'post_name'    => true,
     6152                'post_status'  => true,
     6153            ];
     6154
     6155            $fieldsData = [];
     6156
     6157            if ( $langCode && isset( $protectedData[$langCode] ) && is_array( $protectedData[$langCode] ) ) {
     6158                $fieldsData = $protectedData[$langCode];
     6159            } elseif ( array_intersect_key( $protectedData, $allowedFields ) ) {
     6160                $fieldsData = $protectedData;
     6161            }
     6162
     6163            if ( $fieldsData ) {
     6164                $fieldsData = array_intersect_key( $fieldsData, $allowedFields );
     6165                $fieldAssignments = [];
     6166                $values = [];
     6167
     6168                foreach ( $fieldsData as $field => $value ) {
     6169                    $fieldAssignments[] = "$field = %s";
     6170                    $values[] = $value;
     6171                }
     6172
     6173                if ( $fieldAssignments ) {
     6174                    $fields = implode( ', ', $fieldAssignments );
     6175                    $values[] = (int)$productId;
     6176
     6177                    $wpdb->query(
     6178                        $wpdb->prepare( "
    39366179                            UPDATE {$wpdb->posts}
    39376180                            SET {$fields}
     
    39916234                    $term_args['slug'] = $slug;
    39926235
     6236                    if ( !empty( $term->description ) ) {
     6237                        $term_args['description'] = $term->description;
     6238                    }
     6239
    39936240                    $new_term = wp_insert_term( $term_name, $termType, $term_args );
    39946241
     
    40596306            }
    40606307        } else {
    4061             $comment_author        = esc_html__( 'WooCommerce', 'api2cart-bridge-connector' );
    4062             $comment_author_email  = esc_html__( 'WooCommerce', 'api2cart-bridge-connector' ) . '@';
     6308            $comment_author        = esc_html__( 'WooCommerce', 'bridge-connector' );
     6309            $comment_author_email  = esc_html__( 'WooCommerce', 'bridge-connector' ) . '@';
    40636310            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ) : 'noreply.com';
    40646311            $comment_author_email  = sanitize_email( $comment_author_email );
     
    52747521if ( ! defined( 'ABSPATH' ) ) exit;
    52757522
    5276 define( 'A2CBC_BRIDGE_VERSION', '192' );
     7523define( 'A2CBC_BRIDGE_VERSION', '198' );
    52777524define( 'A2CBC_BRIDGE_DIRECTORY_NAME', basename( getcwd() ) );
    5278 define( 'A2CBC_BRIDGE_PUBLIC_KEY', '-----BEGIN PUBLIC KEY-----
    5279 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzndeF8B8e+ElYjoRTBaC
    5280 aNySz31gK3zasphhRlIOT6LwKQFutTYmWgkd4id2XwZE/wglsgnUXXmPcPp4+OdR
    5281 6va+rgS3rtJA7I5DDj4oBVJ2cyOHgKtTnu5QnGXKM1nSBnx6t/BRZ+sVnTpKcyd0
    5282 WtGakDIrzaouvOBYWFS5AT6LgWZnFj408Qx+UmAFQDR5oFAlkdCb1kUWSYhV1eNG
    5283 vEAfIeDnFCbi+oyuzvMoMF8mFaNJ2MWfoPVHJl4TqnqK4RLTaYf3KLVAZrxniMQ2
    5284 lN75G21rFG3ammcPtqiXiD6N034N0AviHCc2EkmmcFspH9qmOy/ITOyHHs1w9NNx
    5285 uwIDAQAB
    5286 -----END PUBLIC KEY-----
    5287 ');
    5288 define( 'A2CBC_BRIDGE_PUBLIC_KEY_ID', '4947ae9e4d350512cd240ec52d60c3a1' );
    5289 define( 'A2CBC_BRIDGE_ENABLE_ENCRYPTION', extension_loaded( 'openssl' ) );
    5290 
    5291 A2C_show_error( 0 );
     7525define( 'A2CBC_BRIDGE_PUBLIC_KEY', '' );
     7526define( 'A2CBC_BRIDGE_PUBLIC_KEY_ID', '' );
     7527define( 'A2CBC_BRIDGE_ENABLE_ENCRYPTION', false );
     7528
     7529ini_set( 'display_errors', false );
    52927530
    52937531require_once 'config.php';
     
    53007538    die( 'ERROR_TOKEN_LENGTH' );
    53017539}
    5302 
    5303 function A2C_show_error( $status ) {
    5304     if ( $status ) {
    5305         if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
    5306             if ( substr( phpversion(), 0, 1 ) >= 5 ) {
    5307                 error_reporting( E_ALL & ~E_STRICT );
    5308                 return;
    5309             } else {
    5310                 error_reporting( E_ALL );
    5311                 return;
    5312             }
    5313         }
    5314     }
    5315 
    5316     error_reporting( 0 );
    5317 }
    5318 
    5319 /**
    5320  * A2C_exceptions_error_handler
    5321  *
    5322  * @param $severity
    5323  * @param $message
    5324  * @param $filename
    5325  * @param $lineno
    5326  *
    5327  * @throws ErrorException
    5328  */
    5329 function A2C_exceptions_error_handler( $severity, $message, $filename, $lineno ) {
    5330     if ( error_reporting() === 0 ) {
    5331         return;
    5332     }
    5333 
    5334     if ( strpos( $message, 'Declaration of' ) === 0 ) {
    5335         return;
    5336     }
    5337 
    5338     if ( error_reporting() & $severity ) {
    5339         throw new ErrorException( $message, 0, $severity, $filename, $lineno );
    5340     }
    5341 }
    5342 
    5343 set_error_handler( 'A2C_exceptions_error_handler' );
    53447540
    53457541/**
     
    53937589
    53947590        if ( !openssl_public_encrypt( $aesKey, $encryptedKey, A2CBC_BRIDGE_PUBLIC_KEY, OPENSSL_PKCS1_OAEP_PADDING ) ) {
    5395             throw new Exception( esc_html__( 'ERROR_ENCRYPT', 'api2cart-bridge-connector' ) );
     7591            throw new Exception( esc_html__( 'ERROR_ENCRYPT', 'bridge-connector' ) );
    53967592        }
    53977593
     
    54207616
    54217617        if ( json_last_error() !== JSON_ERROR_NONE || !isset( $payload['encryptedKey'], $payload['iv'] ) ) {
    5422             throw new Exception( esc_html__( 'ERROR_PARSE_DECRYPT', 'api2cart-bridge-connector' ) );
     7618            throw new Exception( esc_html__( 'ERROR_PARSE_DECRYPT', 'bridge-connector' ) );
    54237619        }
    54247620
     
    54317627
    54327628        if ( $result === false ) {
    5433             throw new Exception( esc_html__( 'ERROR_DECRYPT', 'api2cart-bridge-connector' ) );
     7629            throw new Exception( esc_html__( 'ERROR_DECRYPT', 'bridge-connector' ) );
    54347630        }
    54357631
  • api2cart-bridge-connector/trunk/readme.txt

    r3439288 r3475673  
    44Requires at least: 4.5
    55Tested up to: 6.8
    6 Stable tag: 1.0.0
     6Stable tag: 3.0.11
    77License: GPLv2
    88License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    9797= 3.0.8 =* Fixed product.variant.update method. Updated bridge to 188 version.
    9898= 3.0.9 =* Updated bridge to 190 version. Fixed cart.meta_data.set method.
    99 = 3.0.10 =*'Updated bridge to 192 version. Added compatibility with WooCommerce 10+ version.
     99= 3.0.10 =* Updated bridge to 192 version. Added compatibility with WooCommerce 10+ version.
     100= 3.0.11 =* Updated bridge to 198 version. Added Polylang multilingual plugin support.
Note: See TracChangeset for help on using the changeset viewer.