oduct from the feed, only the originals are allowed */ // For these channels parent products are allowed $allowed_channel_parents = array( 'skroutz', // "bestprice", 'google_dsa', 'google_product_review', ); if ( ! in_array( $feed_channel['fields'], $allowed_channel_parents ) ) { if ( ( $product->is_type( 'variable' ) ) && ( isset( $product_data['item_group_id'] ) ) ) { $product_data = array(); $product_data = null; } } /** * Remove variation products that are not THE default variation product */ if ( ( isset( $variation_pass ) ) && ( $variation_pass == 'false' ) ) { $product_data = array(); $product_data = null; } /** * And item_group_id is not allowed for simple products, prevent users from adding this to the feedd */ if ( ( $product->is_type( 'simple' ) ) || ( $product->is_type( 'external' ) ) || ( $product->is_type( 'woosb' ) ) || ( $product->is_type( 'mix-and-match' ) ) || ( $product->is_type( 'bundle' ) ) || ( $product->is_type( 'composite' ) ) || ( $product->is_type( 'auction' ) || ( $product->is_type( 'subscription' ) ) || ( $product->is_type( 'variable' ) ) ) ) { unset( $product_data['item_group_id'] ); } /** * Truncate length of product title when it is over 150 characters (requirement for Google Shopping, Pinterest and Facebook */ if ( isset( $product_data['title'] ) ) { $length_title = strlen( $product_data['title'] ); if ( $length_title > 149 ) { $product_data['title'] = mb_substr( $product_data['title'], 0, 150 ); } } /** * Filter to allow manipulation of product data before it is added to the feed. * * @since 13.3.6 * * @param array $product_data The product data array * @param object $feed The feed object * @param object $product The product object */ $product_data = apply_filters( 'adt_get_product_data', $product_data, $feed, $product ); // Initialize before the guarded block so the variable is always defined for the Google Shopping check below. $categories_before_rules = null; // Filter and rules execution. if ( is_array( $product_data ) && ! empty( $product_data ) ) { // Filter execution. $filters_instance = AdTribes\PFP\Classes\Filters::instance(); $product_data = $filters_instance->filter( $product_data, $feed ); // Capture categories before rules so we can detect rule modifications below. $categories_before_rules = $product_data['categories'] ?? null; // Rules execution. $rules_instance = AdTribes\PFP\Classes\Rules::instance(); $product_data = $rules_instance->rules( $product_data, $feed ); } /** * Localize the price attributes. */ if ( ! empty( $product_data ) ) { $product_data_instance = AdTribes\PFP\Classes\Product_Data::instance(); $product_data = $product_data_instance->localize_prices( $product_data, $feed ); } /** * Check if we need to add category taxonomy mappings (Google Shopping). * Skip mapping if a rule already modified the categories value — the rule takes precedence. */ if ( ! empty( $product_data ) && ! empty( $product_data['id'] ) && $feed_channel['taxonomy'] == 'google_shopping' ) { $categories_modified_by_rules = ( $product_data['categories'] ?? null ) !== $categories_before_rules; if ( ! $categories_modified_by_rules ) { if ( ! empty( $feed_mappings ) ) { $product_data = $this->woocommerce_sea_mappings( $feed_mappings, $product_data ); } else { $product_data['categories'] = ''; } } } /** * When product has passed the filter rules it can continue with the rest */ if ( ! empty( $product_data ) ) { /** * Determine what fields are allowed to make it to the csv and txt productfeed */ if ( ( $feed_channel['fields'] != 'standard' ) && ( ! isset( $tmp_attributes ) ) && is_array( $feed_attributes ) && ! empty( $feed_attributes ) ) { $old_attributes_config = $feed_attributes; $tmp_attributes = array(); foreach ( $feed_attributes as $key => $value ) { if ( strlen( $value['mapfrom'] ) > 0 ) { $tmp_attributes[ $value['mapfrom'] ] = 'true'; } } } if ( isset( $old_attributes_config ) && is_array( $old_attributes_config ) ) { $loop_count = 0; foreach ( $old_attributes_config as $attr_key => $attr_value ) { if ( ! $attr_line ) { if ( array_key_exists( 'static_value', $attr_value ) ) { if ( strlen( $attr_value['mapfrom'] ) ) { $attr_line = "'" . $attr_value['prefix'] . $attr_value['mapfrom'] . $attr_value['suffix'] . "'"; } else { $attr_line = "''"; } } elseif ( ( strlen( $attr_value['mapfrom'] ) ) && ( array_key_exists( $attr_value['mapfrom'], $product_data ) ) ) { if ( ( $attr_value['attribute'] == 'URL' ) || ( $attr_value['attribute'] == 'g:link' ) || ( $attr_value['attribute'] == 'g:link_template' ) || ( $attr_value['attribute'] == 'g:image_link' ) || ( $attr_value['attribute'] == 'link' ) || ( $attr_value['attribute'] == 'Final URL' ) || ( $attr_value['attribute'] == 'SKU' ) || ( $attr_value['attribute'] == 'g:itemid' ) ) { $attr_line = "'" . $attr_value['prefix'] . $product_data[ $attr_value['mapfrom'] ] . $attr_value['suffix'] . "'"; } else { $attr_line = "'" . $attr_value['prefix'] . $product_data[ $attr_value['mapfrom'] ] . $attr_value['suffix'] . "'"; } } else { $attr_line = "''"; } } elseif ( array_key_exists( 'static_value', $attr_value ) ) { $attr_line .= ",'" . $attr_value['prefix'] . $attr_value['mapfrom'] . $attr_value['suffix'] . "'"; } elseif( $attr_value['mapfrom'] === 'page_url' || $attr_value['mapfrom'] === 'post_url' ) { $post_or_page_id = $attr_value['value'] ?? ''; if ( $post_or_page_id ) { $attr_line .= ",'" . $attr_value['prefix'] . get_permalink( $post_or_page_id ) . $attr_value['suffix'] . "'"; } } else { if ( array_key_exists( $attr_value['mapfrom'], $product_data ) ) { if ( is_array( $product_data[ $attr_value['mapfrom'] ] ) ) { if ( $attr_value['mapfrom'] == 'product_tag' ) { $product_tag_str = ''; foreach ( $product_data['product_tag'] as $key => $value ) { $product_tag_str .= ','; $product_tag_str .= "$value"; } $product_tag_str = rtrim( $product_tag_str, ',' ); $product_tag_str = ltrim( $product_tag_str, ',' ); $attr_line .= ",'" . $product_tag_str . "'"; } elseif ( $attr_value['mapfrom'] == 'reviews' ) { $review_str = ''; foreach ( $product_data[ $attr_value['mapfrom'] ] as $key => $value ) { $review_str .= '||'; foreach ( $value as $k => $v ) { $review_str .= ":$v"; } } $review_str = ltrim( $review_str, '||' ); $review_str = rtrim( $review_str, ':' ); $review_str = ltrim( $review_str, ':' ); $review_str = str_replace( '||:', '||', $review_str ); $review_str .= '||'; $attr_line .= ",'" . $review_str . "'"; } else { $shipping_str = ''; foreach ( $product_data[ $attr_value['mapfrom'] ] as $key => $value ) { $shipping_str .= '||'; if ( is_array( $value ) ) { foreach ( $value as $k => $v ) { if ( preg_match( '/[0-9]/', $v ) ) { $shipping_str .= ":$attr_value[prefix]" . $v . "$attr_value[suffix]"; // $shipping_str .= ":$attr_value[prefix]".$v."$attr_value[suffix]"; } else { $shipping_str .= ":$v"; } } } } $shipping_str = ltrim( $shipping_str, '||' ); $shipping_str = rtrim( $shipping_str, ':' ); $shipping_str = ltrim( $shipping_str, ':' ); $shipping_str = str_replace( '||:', '||', $shipping_str ); $attr_line .= ",'" . $shipping_str . "'"; } } elseif ( isset( $product_data[ $attr_value['mapfrom'] ] ) ) { if ( ( $attr_value['attribute'] == 'URL' ) || ( $attr_value['attribute'] == 'g:link' ) || ( $attr_value['attribute'] == 'g:link_template' ) || ( $attr_value['attribute'] == 'g:image_link' ) || ( $attr_value['attribute'] == 'link' ) || ( $attr_value['attribute'] == 'Final URL' ) || ( $attr_value['attribute'] == 'SKU' ) || ( $attr_value['attribute'] == 'g:itemid' ) ) { if ( ( $product_data['product_type'] == 'variation' ) && ( preg_match( '/aelia_cs_currency/', $attr_value['suffix'] ) ) ) { $attr_value['suffix'] = str_replace( '?', '&', $attr_value['suffix'] ); $attr_line .= ",'" . $attr_value['prefix'] . $product_data[ $attr_value['mapfrom'] ] . $attr_value['suffix'] . "'"; } elseif ( ( $product_data['product_type'] == 'variation' ) && ( preg_match( '/currency/', $attr_value['suffix'] ) ) ) { $attr_value['suffix'] = str_replace( '?', '&', $attr_value['suffix'] ); $attr_line .= ",'" . $attr_value['prefix'] . $product_data[ $attr_value['mapfrom'] ] . $attr_value['suffix'] . "'"; } else { $attr_line .= ",'" . $attr_value['prefix'] . $product_data[ $attr_value['mapfrom'] ] . $attr_value['suffix'] . "'"; } } elseif ( $product_data[ $attr_value['mapfrom'] ] !== '' ) { $attr_line .= ",'" . $attr_value['prefix'] . $product_data[ $attr_value['mapfrom'] ] . $attr_value['suffix'] . "'"; } else { $attr_line .= ",''"; } } else { $attr_line .= ",''"; } } else { $attr_line .= ",''"; } } ++$loop_count; } $pieces_row = explode( "','", $attr_line ); $pieces_row = array_map( 'trim', $pieces_row ); // Fix the first and last elements to remove the extra quotes if (!empty($pieces_row)) { // Remove leading quote from first element $pieces_row[0] = ltrim($pieces_row[0], "'"); // Remove trailing quote from last element $last_index = count($pieces_row) - 1; $pieces_row[$last_index] = rtrim($pieces_row[$last_index], "'"); } /** * Filter to allow manipulation of CSV row data before it is written to the feed. * This allows channel-specific handling of attributes in CSV format. * * @since 13.4.9 * * @param array $pieces_row The CSV row data array * @param array $old_attributes_config The feed attributes configuration * @param array $product_data The product data array * @param object $feed The feed object * @return array Modified CSV row data */ $pieces_row = apply_filters( 'adt_product_feed_csv_row_data', $pieces_row, $old_attributes_config, $product_data, $feed ); // Identifier exists attribute for any channel with Plugin Calculation Value Attribute. // Some channels (e.g. Facebook DRM) declare these without the g: namespace prefix, // so we accept both naming conventions here. $identifier_attributes = array( 'g:brand', 'g:gtin', 'g:mpn', 'brand', 'gtin', 'mpn' ); $identifier_exists_attributes = array( 'g:identifier_exists', 'identifier_exists' ); $has_identifier_exists_calculation = false; $identifier_exists_position = -1; $identifier_attributes_positions = array(); $loop_count = 0; foreach ( $old_attributes_config as $attr ) { if ( isset( $attr['attribute'] ) ) { if ( in_array( $attr['attribute'], $identifier_exists_attributes, true ) ) { $identifier_exists_position = $loop_count; if ( isset( $attr['mapfrom'] ) && $attr['mapfrom'] === 'calculated' ) { $has_identifier_exists_calculation = true; } } if ( in_array( $attr['attribute'], $identifier_attributes, true ) ) { $identifier_attributes_positions[] = $loop_count; } } $loop_count++; } // Only compute identifier_exists if the user mapped it to Plugin Calculation. if ( $has_identifier_exists_calculation && $identifier_exists_position != -1 ) { $pieces_row[ $identifier_exists_position ] = 'no'; if ( ! empty( $identifier_attributes_positions ) ) { foreach ( $identifier_attributes_positions as $identifier_attribute_position ) { if ( isset( $pieces_row[ $identifier_attribute_position ] ) && '' !== $pieces_row[ $identifier_attribute_position ] ) { $pieces_row[ $identifier_exists_position ] = 'yes'; break; } } } } $attr_line = implode( "','", $pieces_row ); $products[] = array( $attr_line ); // Track preview mode product count (CSV/TXT feeds only) if ( $is_preview_mode && $file_format != 'xml' && $file_format != 'jsonl' && $file_format != 'jsonl.gz' ) { $preview_found_count++; // Exit the inner while loop if we have enough CSV products if ( $preview_found_count >= $preview_target_count ) { break; } } } else { $attr_line = ''; if ( ! empty( $tmp_attributes ) ) { foreach ( array_keys( $tmp_attributes ) as $attribute_key ) { if ( array_key_exists( $attribute_key, $product_data ) ) { if ( ! $attr_line ) { $attr_line = "'" . $product_data[ $attribute_key ] . "'"; } else { $attr_line .= ",'" . $product_data[ $attribute_key ] . "'"; } } } } $attr_line = trim( $attr_line, "'" ); $products[] = array( $attr_line ); // Track preview mode product count (CSV/TXT feeds only) if ( $is_preview_mode && $file_format != 'xml' && $file_format != 'jsonl' && $file_format != 'jsonl.gz' ) { $preview_found_count++; // Exit the inner while loop if we have enough CSV products if ( $preview_found_count >= $preview_target_count ) { break; } } } /** * Build an array needed for the adding Childs in the XML productfeed */ $ga = 0; $ca = 0; if ( ! empty( $tmp_attributes ) ) { foreach ( array_keys( $tmp_attributes ) as $attribute_key ) { /** * This loop used to be used to skip the numeric keys. * Which can be problematic if the attributes is a static value and it has numeric value. * Because the static_value is using the $attribute_key as the key for the attribute for some fucking reason. * Instead of removing the numeric keys, we now convert them to strings. **/ if ( is_numeric( $attribute_key ) ) { $attribute_key = strval( $attribute_key ); } if ( ! isset( $old_attributes_config ) ) { if ( ! $xml_product ) { $xml_product = array( $attribute_key => $product_data[ $attribute_key ], ); } elseif ( isset( $product_data[ $attribute_key ] ) ) { $xml_product = array_merge( $xml_product, array( $attribute_key => $product_data[ $attribute_key ] ) ); } } else { foreach ( $old_attributes_config as $attr_key => $attr_value ) { $is_attr_static = array_key_exists( 'static_value', $attr_value ); $attr_map_from = $attr_value['mapfrom'] ?? ''; $attr_attribute = $attr_value['attribute'] ?? ''; // The static value is the value set by the user in the feed settings. // It's stored in the mapfrom field. // If the mapfrom field is empty, it means the value is not set by the user. // In this case, we use the value from the product data. $attr_data_value = $is_attr_static ? $attr_map_from : $product_data[ $attr_map_from ] ?? ''; /** * Special handling for page_url and post_url attributes. * * If the attribute is a page_url or post_url, we need to get the permalink of the page or post. * And then set the value to the xml product. */ if ( $attr_map_from === 'page_url' || $attr_map_from === 'post_url' ) { $post_or_page_id = $attr_value['value'] ?? ''; if ( $post_or_page_id ) { $attr_data_value = get_permalink( $post_or_page_id ); } } if ( $attr_map_from != $attribute_key ) { continue; } if ( ! isset( $xml_product ) ) { $xml_product = array( $attr_attribute => "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]", ); } else { if ( is_array( $attr_data_value ) ) { if ( $attr_map_from == 'product_tag' ) { $product_tag_str = ''; foreach ( $product_data['product_tag'] as $key => $value ) { $product_tag_str .= ','; $product_tag_str .= "$value"; } $product_tag_str = ltrim( $product_tag_str, ',' ); $product_tag_str = rtrim( $product_tag_str, ',' ); $xml_product[ $attr_attribute ] = "$product_tag_str"; } elseif ( $attr_map_from == 'product_tag_space' ) { $product_tag_str_space = ''; foreach ( $product_data['product_tag'] as $key => $value ) { $product_tag_str_space .= ', '; $product_tag_str_space .= "$value"; } $product_tag_str_space = ltrim( $product_tag_str_space, ' ,' ); $product_tag_str_space = rtrim( $product_tag_str_space, ', ' ); $xml_product[ $attr_attribute ] = "$product_tag_str_space"; } elseif ( $attr_map_from == 'reviews' ) { $review_str = ''; foreach ( $attr_data_value as $key => $value ) { $review_str .= '||'; foreach ( $value as $k => $v ) { if ( $k == 'review_product_id' ) { $review_str .= ":::REVIEW_PRODUCT_ID##$v"; } elseif ( $k == 'reviewer_image' ) { $review_str .= ":::REVIEWER_IMAGE##$v"; } elseif ( $k == 'review_ratings' ) { $review_str .= ":::REVIEW_RATINGS##$v"; } elseif ( $k == 'review_id' ) { $review_str .= ":::REVIEW_ID##$v"; } elseif ( $k == 'reviewer_name' ) { $review_str .= ":::REVIEWER_NAME##$v"; } elseif ( $k == 'reviewer_id' ) { $review_str .= ":::REVIEWER_ID##$v"; } elseif ( $k == 'review_timestamp' ) { $v = str_replace( ' ', 'T', $v ); $v .= 'Z'; $review_str .= ":::REVIEW_TIMESTAMP##$v"; } elseif ( $k == 'review_url' ) { $review_str .= ":::REVIEW_URL##$v"; } elseif ( $k == 'title' ) { $review_str .= ":::TITLE##$v"; } elseif ( $k == 'content' ) { $review_str .= ":::CONTENT##$v"; } elseif ( $k == 'pros' ) { $review_str .= ":::PROS##$v"; } elseif ( $k == 'cons' ) { $review_str .= ":::CONS##$v"; } else { // UNKNOWN, DO NOT ADD } } } $review_str = ltrim( $review_str, '||' ); $review_str = rtrim( $review_str, ':' ); $review_str = ltrim( $review_str, ':' ); $review_str = str_replace( '||:', '||', $review_str ); $review_str .= '||'; $xml_product[ $attr_attribute ] = "$review_str"; } elseif ( $attr_map_from == 'shipping' ) { $shipping_str = ''; foreach ( $attr_data_value as $key => $value ) { $shipping_str .= '||'; foreach ( $value as $k => $v ) { if ( $k == 'country' ) { $shipping_str .= ':WOOSEA_COUNTRY##' . sanitize_text_field( $v ); } elseif ( $k == 'region' ) { $shipping_str .= ':WOOSEA_REGION##' . sanitize_text_field( $v ); } elseif ( $k == 'service' ) { $shipping_str .= ':WOOSEA_SERVICE##' . sanitize_text_field( $v ); } elseif ( $k == 'postal_code' ) { $shipping_str .= ':WOOSEA_POSTAL_CODE##' . sanitize_text_field( $v ); } elseif ( $k == 'price' ) { $shipping_str .= ':WOOSEA_PRICE##' . sanitize_text_field( $attr_value['prefix'] ) . sanitize_text_field( $v ) . sanitize_text_field( $attr_value['suffix'] ); } elseif ( $k == 'min_transit_time' ) { $shipping_str .= ':WOOSEA_MIN_TRANSIT_TIME##' . sanitize_text_field( $v ); } elseif ( $k == 'max_transit_time' ) { $shipping_str .= ':WOOSEA_MAX_TRANSIT_TIME##' . sanitize_text_field( $v ); } elseif ( $k == 'min_handling_time' ) { $shipping_str .= ':WOOSEA_MIN_HANDLING_TIME##' . sanitize_text_field( $v ); } elseif ( $k == 'max_handling_time' ) { $shipping_str .= ':WOOSEA_MAX_HANDLING_TIME##' . sanitize_text_field( $v ); } else { // UNKNOWN, DO NOT ADD } } } $shipping_str = ltrim( $shipping_str, '||' ); $shipping_str = rtrim( $shipping_str, ':' ); $shipping_str = ltrim( $shipping_str, ':' ); $shipping_str = str_replace( '||:', '||', $shipping_str ); $xml_product[ $attr_attribute ] = "$shipping_str"; } elseif ( $attr_map_from == 'free_shipping_threshold' ) { if ( ! is_array( $attr_data_value ) ) { continue; // Skip malformed data } $threshold_parts = []; foreach ( $attr_data_value as $key => $value ) { $threshold_part = ''; foreach ( $value as $k => $v ) { if ( $k == 'country' ) { $threshold_part .= ":WOOSEA_COUNTRY##$v"; } elseif ( $k == 'price_threshold' ) { $prefix = $attr_value['prefix'] ?? ''; $suffix = $attr_value['suffix'] ?? ''; $threshold_part .= ":WOOSEA_PRICE_THRESHOLD##$prefix$v$suffix"; } } if ( !empty( $threshold_part ) ) { $threshold_parts[] = ltrim( $threshold_part, ':' ); } } $xml_product[ $attr_attribute ] = implode( '||', $threshold_parts ); } else { // Array is returned and add to feed $arr_return = ''; if ( isset( $attr_data_value ) && is_array( $attr_data_value ) ) { foreach ( $attr_data_value as $key => $value ) { $arr_return .= $value . ','; } } $arr_return = rtrim( $arr_return, ',' ); $xml_product[ $attr_attribute ] = $arr_return; } } else { ++$ga; if ( array_key_exists( $attr_attribute, $xml_product ) ) { $ca = explode( '_', $attr_map_from ); $ca_extra = end( $ca ); // Google Shopping Actions, allow multiple product highlights, included destinations and excluded destinations in feed if ( in_array( $attr_attribute, array( 'g:product_highlight', 'g:included_destination', 'g:excluded_destination' ), true ) ) { $xml_product[ $attr_attribute . "_$ga" ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $attr_attribute == 'g:consumer_notice' ) { $xml_product[ $attr_attribute . "_$ga" ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $attr_attribute == 'g:product_detail' ) { $xml_product[ $attr_attribute . "_$ga" ] = "$attr_value[prefix]||" . $attr_map_from . '#' . $attr_data_value . "$attr_value[suffix]"; } elseif ( str_contains( $attr_attribute, 'g:additional_image_link' ) ) { // Skip empty additional image slots. When the product has no image // for this slot but a prefix/suffix is configured, the composed // value (e.g. "?gla090723_04") is non-empty and slips past the // output-stage empty guard, emitting an invalid node. Mirror the // guard already applied on the first-occurrence path below. if ( $attr_data_value !== '' ) { $xml_product[ $attr_attribute . "_$ca_extra" ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } } else { $xml_product[ $attr_attribute . "_$ca_extra" ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } } elseif ( isset( $attr_data_value ) ) { if ( $attr_attribute == 'URL' || $attr_attribute == 'g:link' || $attr_attribute == 'link' || $attr_attribute == 'g:link_template' ) { if ( $product_data['product_type'] == 'variation' && preg_match( '/aelia_cs_currency/', $attr_value['suffix'] ) ) { $attr_value['suffix'] = str_replace( '?', '&', $attr_value['suffix'] ); $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $product_data['product_type'] == 'variation' && preg_match( '/currency/', $attr_value['suffix'] ) ) { $attr_value['suffix'] = str_replace( '?', '&', $attr_value['suffix'] ); $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } else { $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } } elseif ( ( $attr_attribute == 'g:image_link' || str_contains( $attr_attribute, 'g:additional_image_link' ) || $attr_attribute == 'image_link' ) && $attr_data_value !== '' ) { $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( ( $attr_attribute == 'g:id' || $attr_attribute == 'id' || $attr_attribute == 'g:itemid' ) && $attr_data_value !== '' ) { $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $attr_attribute == 'g:item_group_id' && $attr_data_value !== '' ) { $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $attr_attribute == 'g:consumer_notice' && $attr_data_value !== '' ) { $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $attr_attribute == 'g:product_detail' && $attr_data_value !== '' ) { $xml_product[ $attr_attribute ] = "$attr_value[prefix]||" . $attr_map_from . '#' . $attr_data_value . "$attr_value[suffix]"; } elseif ( in_array( $attr_attribute, array( 'g:product_highlight', 'g:included_destination', 'g:excluded_destination' ), true ) && $attr_data_value !== '' ) { $xml_product[ $attr_attribute . "_$ga" ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } elseif ( $attr_data_value !== '' ) { $xml_product[ $attr_attribute ] = "$attr_value[prefix]" . $attr_data_value . "$attr_value[suffix]"; } } } } } } } } // Do we need to do some calculation on attributes for Google Shopping $xml_product = $this->woosea_calculate_value( $feed, $xml_product ); foreach ( $xml_product as $key_product => $value_product ) { if ( preg_match( '/custom_attributes_attribute_/', $key_product ) ) { $pieces = explode( 'custom_attributes_attribute_', $key_product ); unset( $xml_product[ $key_product ] ); $xml_product[ $pieces[1] ] = $value_product; } elseif ( preg_match( '/product_attributes_/', $key_product ) ) { $pieces = explode( 'product_attributes_', $key_product ); unset( $xml_product[ $key_product ] ); $xml_product[ $pieces[1] ] = $value_product; } } // For Skroutz template if ( $feed_channel['fields'] == 'skroutz' ) { if ( $product->get_type() == 'variation' ) { $product_id = $product->get_parent_id(); $xml_piece[ $product_id ]['skroutz_variations'][] = $xml_product; } else { $xml_piece[ $product->get_id() ] = $xml_product; } } else { // For JSONL formats, allow channel-specific product-level transformations // (e.g. shipping parsing and HTML entity decoding for OpenAI). if ( $file_format === 'jsonl' || $file_format === 'jsonl.gz' ) { /** * Filter a single JSONL product array before it is added to the batch. * * @since 13.5.2 * * @param array $xml_product The product data key/value array. * @param array $feed_channel The active channel configuration. * @param object $feed The feed object. */ $xml_product = apply_filters( 'adt_product_feed_jsonl_product', $xml_product, $feed_channel, $feed ); } $xml_piece[] = $xml_product; } // Track preview mode product count (XML/JSONL feeds only) if ( $is_preview_mode && ( $file_format == 'xml' || $file_format == 'jsonl' || $file_format == 'jsonl.gz' ) ) { $preview_found_count++; // Exit the inner while loop if we have enough products if ( $preview_found_count >= $preview_target_count ) { break; } } unset( $xml_product ); unset( $product_data ); } endwhile; // Store post count before resetting query $last_query_post_count = $prods->post_count; wp_reset_query(); wp_reset_postdata(); // Preview mode: Check if we need to query more products if ( $is_preview_mode ) { // Update offset for next query $preview_query_offset += $preview_batch_size; // Continue querying if: // 1. We haven't found enough products yet // 2. We haven't exceeded max queries // 3. The last query returned results (not exhausted all products) $should_continue = ( $preview_found_count < $preview_target_count && $preview_query_count < $preview_max_queries && $last_query_post_count > 0 ); } else { $should_continue = false; } } while ( $should_continue ); // Add processed products to array // if(get_option('woosea_duplicates')){ // update_option($channel_duplicates, $prevent_duplicates, 'no'); // } /** * Write row to CSV/TXT or XML or JSONL file */ if ( ( $file_format == 'jsonl' || $file_format == 'jsonl.gz' ) && is_array( $xml_piece ) && ! empty( $xml_piece ) ) { $jsonl_writer = \AdTribes\PFP\Classes\Feed_Writers\Feed_Writer_JSONL::instance(); $file = $jsonl_writer->write_feed( array_filter( $xml_piece ), $feed, false ); unset( $xml_piece ); } elseif ( $file_format != 'xml' && is_array( $products ) && ! empty( $products ) ) { $file = $this->woosea_create_csvtxt_feed( array_filter( $products ), $feed, 'false' ); } else { if ( is_array( $xml_piece ) && $file_format === 'xml' ) { $file = $this->woosea_create_xml_feed( array_filter( $xml_piece ), $feed, 'false' ); unset( $xml_piece ); } unset( $products ); } $feed->save(); /** * Ready creating file, clean up our feed configuration mess now */ delete_option( 'attributes_dropdown' ); delete_option( 'channel_attributes' ); } /** * Calculate the value of an attribute */ public function woosea_calculate_value( $feed, $xml_product ) { $feed_channel = $feed->channel; $feed_attributes = $feed->attributes; /** * Filter the feed attributes before processing. * * @since 13.5.0 * @param array $feed_attributes The feed attributes array. * @return array */ $feed_attributes = apply_filters( 'adt_feed_get_attributes', $feed_attributes ); if ( empty( $feed_channel ) || empty( $feed_attributes ) ) { return $xml_product; } // trim whitespaces from attribute values $xml_product = array_map( 'trim', $xml_product ); // Check if we need to 'calculate' the identifier_exists attribute value. // The helper call below further gates on the feed actually having identifier_exists // mapped to Plugin Calculation, so this runs for any channel that configured it. // Some channels (e.g. Facebook DRM) emit these keys without the g: namespace prefix, // so we accept both naming conventions here. if ( isset( $xml_product['g:condition'] ) && ! isset( $xml_product['g:identifier_exists'] ) && ! isset( $xml_product['identifier_exists'] ) ) { $identifier_exists = 'no'; // default value is no // Per Google's requirements, we only need identifier_exists if the product is new if ( strtolower( $xml_product['g:condition'] ) === 'new' ) { $brand = $xml_product['g:brand'] ?? $xml_product['brand'] ?? ''; $gtin = $xml_product['g:gtin'] ?? $xml_product['gtin'] ?? ''; $mpn = $xml_product['g:mpn'] ?? $xml_product['mpn'] ?? ''; if ( '' !== $brand && ( '' !== $gtin || '' !== $mpn ) ) { $identifier_exists = 'yes'; } } // Only add identifier_exists to the feed if it's configured as a Plugin Calculation, // and use whichever attribute name the channel declares (g:identifier_exists or identifier_exists). $identifier_exists_attribute = $this->get_calculated_identifier_exists_attribute( $feed_attributes ); if ( null !== $identifier_exists_attribute ) { $xml_product[ $identifier_exists_attribute ] = $identifier_exists; } } if ( $feed_channel['name'] == 'Mall.sk' ) { $has_calculated_attribute = false; foreach ($feed_attributes as $attr) { if (isset($attr['mapfrom']) && $attr['mapfrom'] === 'calculated') { $has_calculated_attribute = true; break; } } if ($has_calculated_attribute) { $xml_product['VARIABLE_PARAMS'] = 'calculated'; } } return $xml_product; } /** * Get the configured identifier_exists attribute name when it is mapped to Plugin Calculation. * * Some channels (e.g. Google Shopping) use the 'g:identifier_exists' name while others * (e.g. Facebook DRM) use the bare 'identifier_exists' name. Returns whichever variant is * configured on the feed, or null if no Plugin Calculation mapping exists for it. * * @param array $feed_attributes Feed attribute definitions. * @return string|null The configured attribute name or null when not set to Plugin Calculation. */ private function get_calculated_identifier_exists_attribute( $feed_attributes ) { $identifier_exists_attributes = array( 'g:identifier_exists', 'identifier_exists' ); foreach ( $feed_attributes as $attr ) { if ( ! isset( $attr['attribute'], $attr['mapfrom'] ) ) { continue; } if ( 'calculated' === $attr['mapfrom'] && in_array( $attr['attribute'], $identifier_exists_attributes, true ) ) { return $attr['attribute']; } } return null; } /** * Make start and end sale date readable */ public function get_sale_date( $id, $name ) { $date = $this->get_attribute_value( $id, $name ); if ( $date ) { if ( is_int( $date ) ) { return date( 'Y-m-d', $date ); } } return false; } /** * Get product stock */ public function get_stock( $id ) { $status = $this->get_attribute_value( $id, '_stock_status' ); if ( $status ) { if ( $status == 'instock' ) { return 'in stock'; } elseif ( $status == 'outofstock' ) { return 'out of stock'; } } return 'out of stock'; } /** * Create proper format image URL's */ public function get_image_url( $image_url = '' ) { if ( ! empty( $image_url ) ) { if ( substr( trim( $image_url ), 0, 4 ) === 'http' || substr( trim( $image_url ), 0, 5 ) === 'https' || substr( trim( $image_url ), 0, 3 ) === 'ftp' || substr( trim( $image_url ), 0, 4 ) === 'sftp' ) { return rtrim( $image_url, '/' ); } else { $base = get_site_url(); $image_url = $base . $image_url; return rtrim( $image_url, '/' ); } } return $image_url; } /** * Get attribute value */ public function get_attribute_value( $id, $name ) { if ( str_contains( $name, 'attribute_pa' ) ) { $taxonomy = str_replace( 'attribute_', '', $name ); $meta = get_post_meta( $id, $name, true ); $term = get_term_by( 'slug', $meta, $taxonomy ); return $term->name; } else { return get_post_meta( $id, $name, true ); } } /** * Execute category taxonomy mappings */ private function woocommerce_sea_mappings( $project_mappings, $product_data ) { $original_cat = $product_data['categories']; $original_cat = preg_replace( '/&/', '&', $original_cat ); $original_cat = preg_replace( '/>/', '>', $original_cat ); $original_cat = ltrim( $original_cat, '||' ); $tmp_cat = ''; $match = 'false'; foreach ( $project_mappings as $pm_key => $pm_array ) { // Strip slashes $pm_array['criteria'] = str_replace( '\\', '', $pm_array['criteria'] ); $pm_array['criteria'] = str_replace( '/', '', $pm_array['criteria'] ); $pm_array['criteria'] = trim( $pm_array['criteria'] ); $original_cat = str_replace( '\\', '', $original_cat ); $original_cat = str_replace( '/', '', $original_cat ); $original_cat = trim( $original_cat ); // First check if there is a category mapping for this specific product if ( ( preg_match( '/' . $pm_array['criteria'] . '/', $original_cat ) ) ) { if ( ! empty( $pm_array['map_to_category'] ) ) { $prod_id_cat = $product_data['id']; if ( $product_data['product_type'] == 'variation' ) { $prod_id_cat = $product_data['item_group_id']; } $product_cats_ids = wc_get_product_term_ids( $prod_id_cat, 'product_cat' ); if ( in_array( $pm_array['categoryId'], $product_cats_ids ) ) { $category_pieces = explode( '-', $pm_array['map_to_category'] ); $tmp_cat = $category_pieces[0]; $match = 'true'; } } } elseif ( $pm_array['criteria'] == $original_cat ) { $category_pieces = explode( '-', $pm_array['map_to_category'] ); $tmp_cat = $category_pieces[0]; $match = 'true'; } else { // Do nothing } } if ( $match == 'true' ) { if ( array_key_exists( 'id', $product_data ) ) { $product_data['categories'] = $tmp_cat; } } else { // No mapping found so make google_product_category empty $product_data['categories'] = ''; } return $product_data; } /** * Function to exclude products based on individual product exclusions */ private function woosea_exclude_individual( $product_data ) { $allowed = 1; // Check if product was already excluded from the feed $product_excluded = ucfirst( get_post_meta( $product_data['id'], '_woosea_exclude_product', true ) ); if ( $product_excluded == 'Yes' ) { $allowed = 0; } if ( $allowed < 1 ) { $product_data = array(); $product_data = null; } else { return $product_data; } } /** * Do analysis of product data for Google Shopping */ private function woosea_gs_analysis( $project, $product_data ) { $gs_analysis_check = array(); $gs_analysis_check['project_hash'] = $project['project_hash']; $gs_analysis_check['project_hash']['timestamp'] = 'llqlql'; // Check title criteria $length_title = strlen( $product_data['title'] ); $gs_analysis_check['project_hash'][ $product_data['id'] ]['title']['length'] = $length_title; $gs_analysis_check['project_hash'][ $product_data['id'] ]['title']['more_information'] = 'https://support.google.com/merchants/answer/6324415'; if ( $length_title > 150 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['notification'] = "Your title / product name is too long ($length_title characters), it has been truncated to 150 characters in order to meet Google's criteria."; } elseif ( $length_title < 64 and $length_title > 0 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['notification'] = "Your title / product name is too short ($length_title characters), make sure it is over 64 characters long. Best practice is to use all 150 characters. Include the important details that define your product."; } elseif ( $length_title < 1 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['notification'] = 'Your title / product name is empty, make sure it is over 64 characters long. Best practice is to use all 150 characters. Include the important details that define your product.'; } else { $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['title']['notification'] = 'The length of your title / product name is perfect, well done!'; } // Check description criteria $length_description = strlen( $product_data['description'] ); $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['length'] = $length_description; $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['more_information'] = 'https://support.google.com/merchants/answer/6324468'; if ( $length_description > 5000 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['notification'] = "Your product description is too long ($length_description characters), make sure your product description is no longer than 5000 characters."; } elseif ( $length_description < 160 and $length_description > 0 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['notification'] = "Your product description is too short ($length_description characters), make sure to list the most important details in the first 160 - 500 characters."; } elseif ( $length_description < 1 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['notification'] = 'Your product description is empty, make sure to list the most important details in the first 160 - 500 characters.'; } else { $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['description']['notification'] = 'The length of your title / product name is perfect, well done!'; } // Check availability $gs_analysis_check[ $product_data['project_hash']['id'] ]['availability']['more_information'] = 'https://support.google.com/merchants/answer/6324448'; $availability_allowed = array( 'in_stock', 'out_of_stock', 'preorder', 'backorder' ); $availability = $product_data['availability']; if ( ! in_array( $product_data['availability'], $availability_allowed ) ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['availability']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['availability']['notification'] = "Your availability value ($availability) does not meet Google's requirements (make sure the value is in_stock, out_of_stock, preorder or backorder)."; } else { $gs_analysis_check[ $product_data['project_hash']['id'] ]['availability']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['availability']['notification'] = "Your availability value ($availability) meets Google's requirements, well done!"; } // Check link $length_link = strlen( $product_data['link'] ); $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['length'] = $length_link; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['more_information'] = 'https://support.google.com/merchants/answer/6324416'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['notification'] = "Your product link (URL) meets Google's requirements."; if ( $length_link > 2000 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['notification'] = "Your product link (URL) is too long ($length_link characters), make sure the product link (URL) is no longer than 2000 characters."; } elseif ( $length_link < 1 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['notification'] = 'Your product link (URL) is empty.'; } else { } if ( ! filter_var( $product_data['link'], FILTER_VALIDATE_URL ) !== false ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['notification'] = "Your product link (URL) doesn't appear to be a valid URL."; } $url = parse_url( $product_data['link'] ); $allowed_schema = array( 'http', 'https' ); if ( ! in_array( $url['scheme'], $allowed_schema ) ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['link']['notification'] = "Your product link (URL) doesn't http or https, which is required by Google."; } // Check price $length_price = strlen( $product_data['price'] ); $product_price = $product_data['price']; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['length'] = $length_price; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['more_information'] = 'https://support.google.com/merchants/answer/6324371'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['notification'] = "Your product price ($product_price) meets Google's requirements."; if ( $length_price < 1 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['notification'] = 'Your product price is empty, make sure to add a price to all your products.'; } $separator = wc_get_price_decimal_separator(); if ( $separator == '.' ) { if ( ! is_numeric( $product_data['price'] ) ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['notification'] = "Your product price ($product_price) doesn't appear to be a valid price."; } } if ( empty( $product_data['price'] ) ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['notification'] = "Your product price ($product_price) doesn't appear to be a valid price, it cannot be empty of 0 (zero)"; } if ( $product_data['price'] == '0,00' ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['price']['notification'] = "Your product price ($product_price) doesn't appear to be a valid price, it cannot be empty of 0 (zero)"; } // Product type $length_product_type = strlen( $product_data['product_type'] ); $product_type = $product_data['product_type']; $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['length'] = $length_price; $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['more_information'] = 'https://support.google.com/merchants/answer/6324406'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['notification'] = "Your product type meets Google's requirements."; if ( $length_product_type > 750 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['notification'] = 'Your product type value is too long, make sure it is no longer than 750 characters.'; } elseif ( $length_product_type < 1 ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['product_type']['notification'] = 'Your product type field is empty.'; } else { } // Condition $gs_analysis_check[ $product_data['project_hash']['id'] ]['condition']['more_information'] = 'https://support.google.com/merchants/answer/6324469'; $condition_allowed = array( 'new', 'refurbished', 'used', 'New', 'Refurbished', 'Used' ); $condition = $product_data['condition']; if ( ! in_array( $product_data['condition'], $condition_allowed ) ) { $gs_analysis_check[ $product_data['project_hash']['id'] ]['condition']['passed_check'] = 'no'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['condition']['notification'] = "Your condition value ($condition) does not meet Google's requirements (make sure the value is new, refurbished or used)."; } else { $gs_analysis_check[ $product_data['project_hash']['id'] ]['condition']['passed_check'] = 'yes'; $gs_analysis_check[ $product_data['project_hash']['id'] ]['condition']['notification'] = "Your condition value ($condition) meets Google's requirements, well done!"; } // Save Google Shopping analysis results update_option( 'woosea_gs_analysis_results', $gs_analysis_check, false ); } /** * Get comma separated categories * * @param array $category_ids The category IDs. * @return string The comma separated categories. */ private function get_comma_separated_categories( $category_ids ) { $categories_comma_separated = array(); // Build category hierarchy map $categories_by_parent = array(); $all_categories = array(); foreach ( $category_ids as $category_id ) { $category = get_term( $category_id, 'product_cat' ); if ( ! is_wp_error( $category ) ) { $all_categories[ $category_id ] = $category; $parent_id = $category->parent; if ( ! isset( $categories_by_parent[ $parent_id ] ) ) { $categories_by_parent[ $parent_id ] = array(); } $categories_by_parent[ $parent_id ][] = $category_id; } } // Recursive function to add categories in hierarchical order $add_categories_hierarchically = function( $parent_id, $categories_by_parent, $all_categories, &$result ) use ( &$add_categories_hierarchically ) { if ( ! isset( $categories_by_parent[ $parent_id ] ) ) { return; } foreach ( $categories_by_parent[ $parent_id ] as $category_id ) { if ( isset( $all_categories[ $category_id ] ) ) { $result[] = $all_categories[ $category_id ]->name; // Recursively add children of this category $add_categories_hierarchically( $category_id, $categories_by_parent, $all_categories, $result ); } } }; // Start with root categories (parent = 0) and build hierarchically $add_categories_hierarchically( 0, $categories_by_parent, $all_categories, $categories_comma_separated ); return implode( ',', $categories_comma_separated ); } } https://kicker-pro.pl/post-sitemap.xml 2026-08-22T13:49:14+00:00 https://kicker-pro.pl/page-sitemap.xml 2026-09-16T20:10:25+00:00 https://kicker-pro.pl/product-sitemap.xml 2026-09-24T14:56:50+00:00 https://kicker-pro.pl/category-sitemap.xml 2026-08-22T13:49:14+00:00 https://kicker-pro.pl/product_brand-sitemap.xml 2026-09-24T14:56:50+00:00 https://kicker-pro.pl/product_cat-sitemap.xml 2026-09-24T14:56:50+00:00