An array of attachment IDs. * @since 3.2.6 */ public static function get_product_gallery( $product ) { $img_urls = []; $attachment_ids = []; if ( $product->is_type( 'variation' ) ) { $theme = wp_get_theme(); // gets the current theme // if ( 'Woodmart Child' == $theme->name || 'Twenty Twelve' == $theme->parent_theme ) { // // if you're here Twenty Twelve is the active theme or is // // the current theme's parent theme // } if ( class_exists( 'Woo_Variation_Gallery' ) ) { /** * Get Variation Additional Images for "Additional Variation Images Gallery for WooCommerce" * * @plugin Additional Variation Images Gallery for WooCommerce * @link https://wordpress.org/plugins/woo-variation-gallery/ */ $attachment_ids = \get_post_meta( $product->get_id(), 'woo_variation_gallery_images', true ); } elseif ( \class_exists( 'WooProductVariationGallery' ) ) { /** * Get Variation Additional Images for "Variation Images Gallery for WooCommerce" * * @plugin Variation Images Gallery for WooCommerce * @link https://wordpress.org/plugins/woo-product-variation-gallery/ */ $attachment_ids = \get_post_meta( $product->get_id(), 'rtwpvg_images', true ); } elseif ( \class_exists( 'WC_Additional_Variation_Images' ) ) { /** * Get Variation Additional Images for "WooCommerce Additional Variation Images" * * @plugin WooCommerce Additional Variation Images * @link https://woocommerce.com/products/woocommerce-additional-variation-images/ */ $attachment_ids = \explode( ',', \get_post_meta( $product->get_id(), '_wc_additional_variation_images', true ) ); } elseif ( \class_exists( 'WOODMART_Theme' ) ) { /** * Get Variation Additional Images for "WOODMART Theme -> Variation Gallery Images Feature" * * @theme WOODMART * @link https://themeforest.net/item/woodmart-woocommerce-wordpress-theme/20264492 */ $var_id = $product->get_id(); $parent_id = $product->get_parent_id(); $variation_obj = \get_post_meta( $parent_id, 'woodmart_variation_gallery_data', true ); if ( isset( $variation_obj, $variation_obj[ $var_id ] ) ) { $attachment_ids = \explode( ',', $variation_obj[ $var_id ] ); } else { $attachment_ids = \explode( ',', \get_post_meta( $var_id, 'wd_additional_variation_images_data', true ) ); } } elseif ( 'Woodmart Child' == $theme->name ) { /** * Get Variation Additional Images for "Woodmart Child Theme -> Variation Gallery Images Feature" * * @theme WOODMART * @link https://themeforest.net/item/woodmart-woocommerce-wordpress-theme/20264492 */ $var_id = $product->get_id(); $parent_id = $product->get_parent_id(); $variation_obj = \get_post_meta( $parent_id, 'woodmart_variation_gallery_data', true ); if ( isset( $variation_obj, $variation_obj[ $var_id ] ) ) { $attachment_ids = \explode( ',', $variation_obj[ $var_id ] ); } else { $attachment_ids = \explode( ',', \get_post_meta( $var_id, 'wd_additional_variation_images_data', true ) ); } } else { /** * If any Variation Gallery Image plugin not installed then get Variable Product Additional Image Ids . */ $attachment_ids = \wc_get_product( $product->get_parent_id() )->get_gallery_image_ids(); } } /** * Get Variable Product Gallery Image ids if Product is not a variation * or variation does not have any gallery images * * Test case write is pending */ if ( empty( $attachment_ids ) ) { $attachment_ids = $product->get_gallery_image_ids(); } if ( $attachment_ids && \is_array( $attachment_ids ) ) { $m_key = 1; foreach ( $attachment_ids as $attachment_id ) { $img_urls[ $m_key ] = Helper::woo_feed_get_formatted_url( \wp_get_attachment_url( $attachment_id ) ); $m_key ++; } } return $img_urls; } private static function count_identifiers_in_attributes( $product, $config ) { $count = 0; $feed_rules = $config->get_feed_rules()['option_value']['feedrules']; $identifiers = [ self::PRODUCT_CUSTOM_IDENTIFIER . 'identifier_gtin', self::PRODUCT_TAXONOMY_PREFIX . 'woo-feed-brand', self::PRODUCT_CUSTOM_IDENTIFIER . 'identifier_mpn', ]; foreach ( \array_intersect( $feed_rules['attributes'], $identifiers ) as $key => $result ) { if ( $feed_rules['type'][ $key ] === 'attribute' && self::get_custom_field( $result, $product, $config ) !== '' ) { $count ++; } } return $count; } /** * Retrieves custom field values for a WooCommerce product. * * @param string $field The custom field key. * @param WC_Product $product The WooCommerce product object. * @param mixed $config Additional configuration or context. * * @return mixed The formatted value of the custom field. */ public static function get_custom_field( $field, $product, $config ) { $field_name = $field; // Adjust the meta key for variation products. if ( strpos( $field, '_cattr') !== false ) { $field = str_replace(AttributeValueByType::POST_META_PREFIX, "", $field ); } if ( strpos( $field, '_var') !== false ) { // $meta_key = $product->is_type( 'variation' ) ? $field : str_replace("_var", "",$field ); $meta_key = $field ; }else{ $meta_key = $product->is_type( 'variation' ) ? $field . '_var' : $field; } // Allow filtering of the meta key. $meta_key = apply_filters( 'woo_feed_custom_field_meta', $meta_key, $product, $config ); // Determine the new and old meta keys based on the presence of '_identifier'. if ( \strpos( $meta_key, '_identifier' ) !== false ) { $new_meta_key = \str_replace( '_identifier', '', $meta_key ); $old_meta_key = $meta_key; } else { $new_meta_key = $meta_key; $old_meta_key = \str_replace( 'woo_feed_', 'woo_feed_identifier_', $meta_key ); } // Retrieve the values for the new and old meta keys. $new_meta_value = self::get_product_meta( $new_meta_key, $product, $config ); $old_meta_value = self::get_product_meta( $old_meta_key, $product, $config ); if ( strpos( $field_name, '_cattr') === false ) { if ( empty( $new_meta_value) && $product->is_type('variation')) { $new_meta_key = str_replace("_var", "", $new_meta_key ); $new_meta_value = self::get_product_meta( $new_meta_key, $product, $config ); } } // Return the formatted custom field value, preferring the new meta key. return empty( $new_meta_value ) ? self::format_custom_field_value( $old_meta_value, $meta_key ) : self::format_custom_field_value( $new_meta_value, $meta_key ); } /** * Retrieves a specific meta value for a WooCommerce product. Supports handling variations and RankMath integration. * * @param string $meta The meta key to retrieve. * @param WC_Product $product The WooCommerce product object. * @param mixed $config Additional configuration or context. * * @return mixed The value of the specified meta key. Filters the value through 'woo_feed_filter_product_meta'. */ public static function get_product_meta( $meta, $product, $config ) { $product_id = $product->get_id(); $value = \get_post_meta( $product_id, $meta, true ); // Attempt to retrieve the meta value from the parent product if it's a variation and the meta value is empty. if ( empty( $value ) && $product->is_type( 'variation' ) ) { $parent_id = $product->get_parent_id(); $value = \get_post_meta( $parent_id, $meta, true ); } // Handling for RankMath integration. if ( self::is_rank_math_active() && $meta === 'rank_math_primary_product_cat' && \is_numeric( $value ) ) { $term = \get_term( $value ); $value = $term instanceof WP_Term ? $term->name : $value; } // Handle taxonomy-related meta keys. if ( \strpos( $meta, self::PRODUCT_TAXONOMY_PREFIX ) !== false ) { $meta_key = \str_replace( self::PRODUCT_TAXONOMY_PREFIX, '', $meta ); $value = self::get_product_taxonomy( $meta_key, $product, $config ); } return apply_filters( 'woo_feed_filter_product_meta', $value, $product, $config ); } /** * Checks if Rank Math SEO plugin is active. * * @return bool True if Rank Math or Rank Math Pro is active, false otherwise. */ private static function is_rank_math_active() { return \class_exists( 'RankMath' ) || \class_exists( 'RankMathPro' ); } /** * Retrieves the taxonomy terms associated with a product. * * @param string $taxonomy The taxonomy for which to retrieve terms. * @param WC_Product $product The WooCommerce product object. * @param Config $config Additional configuration or context. * * @return string */ public static function overwrite_identifier_exists( $attribute, $product, $config ) { /** * Please add the plugins name if any plugin needs to do extra code to get proper identifier_exists attribute value. * * Here is the already compatible plugin list: * 1. EAN for WooCommerce * 2. Custom Post Type UI * 3. ACF */ $identifiers = array( 'brand', 'upc', 'sku', 'mpn', 'gtin' ); $structure = get_transient( 'ctx_feed_structure_transient' ); $have_attributes_value = []; foreach ( $identifiers as $single_identifier ) { $attribute_key = self::array_search_key( $single_identifier, $structure ); if ( ! is_array( $attribute_key ) ) { continue; } $attributeValueByType = new AttributeValueByType( $attribute_key['attribute'], $product, $config, $attribute_key['mattribute'] ); $value = $attributeValueByType->get_value(); if ( $value ) { $have_attributes_value[ $single_identifier ] = $value; } } return count( $have_attributes_value ) > 1 ? "yes" : "no"; } /** * Get merchant attribute and attribute * * @param $needle_key * @param $structure * * @return array|false */ private static function array_search_key( $needle_key, $structure ) { foreach ( $structure as $key => $value ) { if ( strpos( $key, $needle_key ) !== false ) { return [ 'attribute' => $value, 'mattribute' => $key, ]; } if ( is_array( $value ) ) { if ( ( $result = self::array_search_key( $needle_key, $value ) ) !== false ) { return $result; } } } return false; } /** * Get Product Taxonomy. ======= * @return string A string containing the taxonomy terms separated by the specified separator. >>>>>>> develop * * Note: Test case writing is pending for this function. */ public static function get_product_taxonomy( $taxonomy, $product, $config ) { $id = CommonHelper::parent_product_id( $product ); $separator = apply_filters( 'woo_feed_product_taxonomy_term_list_separator', ',', $config, $product ); $term_list = \get_the_term_list( $id, $taxonomy, '', $separator, '' ); if ( \is_object( $term_list ) && \get_class( $term_list ) === 'WP_Error' ) { $term_list = ''; } $getTaxonomy = CommonHelper::strip_all_tags( $term_list ); return apply_filters( 'woo_feed_filter_product_taxonomy', $getTaxonomy, $product, $config ); } /** * Formats the value of a custom field based on its metadata key. * * @param mixed $value The value of the custom field. * @param string $meta_key The metadata key used to determine the formatting. * * @return mixed Formatted value if specific formatting is required, otherwise returns the original value. */ private static function format_custom_field_value( $value, $meta_key ) { if ( \strpos( $meta_key, 'availability_date' ) !== false ) { $formatted_date = \strtotime( $value ); if ( $formatted_date === false ) { // Handle invalid date return $value; } return \date( 'c', $formatted_date ); } return $value; } private static function count_identifiers_in_mattributes( $product, $config ) { $count = 0; $feed_rules = $config->get_feed_rules()['option_value']['feedrules']; $identifier_keys = [ 'brand', 'upc', 'sku', 'mpn', 'gtin' ]; foreach ( \array_intersect( $feed_rules['mattributes'], $identifier_keys ) as $key => $result ) { $count += self::evaluate_mattribute( $feed_rules, $key, $product, $config ); } return $count; } private static function evaluate_mattribute( $feed_rules, $key, $product, $config ) { $attribute_key = $feed_rules['attributes'][ $key ]; if ( $feed_rules['type'][ $key ] === 'pattern' && $feed_rules['default'][ $key ] !== '' ) { return 1; } if ( $feed_rules['type'][ $key ] === 'attribute' ) { if ( $attribute_key === 'sku' && $product->get_sku() !== '' ) { return 1; } if ( $attribute_key !== '' && \strpos( $attribute_key, self::PRODUCT_ATTRIBUTE_PREFIX ) !== false ) { $attribute = \str_replace( self::PRODUCT_ATTRIBUTE_PREFIX, '', $attribute_key ); return self::get_product_attribute( $attribute, $product, $config ) !== '' ? 1 : 0; } if ( $attribute_key !== '' && \strpos( $attribute_key, self::PRODUCT_TAXONOMY_PREFIX ) !== false ) { return self::get_product_meta( $attribute_key, $product, $config ) !== '' ? 1 : 0; } } return 0; } /** * Retrieves a specified attribute from a WooCommerce product. * * @param string $attr The attribute slug to retrieve. * @param WC_Product $product The WooCommerce product object. * @param Config $config Additional configuration or context. * * @return string The value of the specified attribute. * @since 2.2.3 */ public static function get_product_attribute( $attr, $product, $config ) { $id = $product->get_id(); if ( woo_feed_wc_version_check( 3.2 ) ) { if ( woo_feed_wc_version_check( 3.6 ) ) { $attr = str_replace( 'pa_', '', $attr ); } if ( $product instanceof WC_Product ) { $value = $product->get_attribute( $attr ); } // if empty get attribute of parent post if ( '' === $value && $product->is_type( 'variation' ) ) { $product = wc_get_product( $product->get_parent_id() ); if ( $product instanceof WC_Product ) { $value = $product->get_attribute( $attr ); } } $getAttribute = $value; } else { $getAttribute = implode( ',', wc_get_product_terms( $id, $attr, array( 'fields' => 'names' ) ) ); } $value = self::fetch_product_attribute( $attr, $product ); // Retrieve attribute value from the parent product if it's a variation and the attribute value is empty. if ( $product instanceof WC_Product ) { if ('' === $value && $product->is_type('variation')) { $parent_product = \wc_get_product($product->get_parent_id()); if ($parent_product) { $value = self::fetch_product_attribute($attr, $parent_product); } } } return apply_filters( 'woo_feed_filter_product_attribute', $value, $attr, $product, $config ); } /** * Fetches the attribute value from a product. * * @param string $attr The attribute slug. * @param WC_Product $product The WooCommerce product object. * * @return string The attribute value. */ private static function fetch_product_attribute( $attr, $product ) { if ($product instanceof WC_Product) { if (\woo_feed_wc_version_check(3.2)) { return $product->get_attribute($attr); } // Fallback for WooCommerce versions below 3.2. return \implode(',', \wc_get_product_terms($product->get_id(), $attr, array('fields' => 'names'))); } return ''; } /** * Retrieves the value of an Advanced Custom Fields (ACF) field for a given WooCommerce product. * * @param WC_Product $product The WooCommerce product object. * @param string $field_key The ACF field key, with the prefix "acf_fields_". * * @return mixed|string The value of the ACF field, or an empty string if ACF is not available. * * Note: Test case writing is pending for this function. */ public static function get_acf_field( $product, $field_key ) { // Remove the prefix to get the actual ACF field key. $acf_field_key = \str_replace( 'acf_fields_', '', $field_key ); // Check if ACF is installed and active. if ( \class_exists( 'ACF' ) ) { // Retrieve and return the ACF field value. return \get_field( $acf_field_key, $product->get_id() ); } // Return an empty string if ACF is not available. return ''; } /** * Returns category mapping values by product ID, considering the parent product for variations. * * @param string $mapping_name Category Mapping Name * @param int $product_id Product ID / Parent Product ID for variation product * * @return mixed * * This function already exists in CTXFeed\V5\Output\CategoryMapping. * Test case is available in CTXFeed\tests\wpunit\Output\CategoryMappingTest. */ public static function get_category_mapping( $mapping_name, $product_id ) { $mapping_settings = \maybe_unserialize( \get_option( $mapping_name ) ); if ( ! isset( $mapping_settings['cmapping'], $mapping_settings['gcl-cmapping'] ) ) { return ''; } // Define suggestive category list merchants. $suggestive_category_list_merchants = [ 'google', 'facebook', 'pinterest', 'bing', 'bing_local_inventory', 'snapchat' ]; // Determine the appropriate mapping array. $cmapping = self::determine_mapping_array( $mapping_settings, $suggestive_category_list_merchants ); // Retrieve product categories and process them. $categories = \get_the_terms( $product_id, 'product_cat' ); if ( \is_array( $categories ) ) { foreach ( \array_reverse( $categories ) as $category ) { if ( ! empty( $cmapping[ $category->term_id ] ) ) { return $cmapping[ $category->term_id ]; } } } return ''; } /** * Determines the appropriate category mapping array. * * @param array $mapping_settings Configuration settings for mapping. * @param array $merchants List of suggestive category list merchants. * * @return array The determined mapping array. */ private static function determine_mapping_array( $mapping_settings, $merchants ) { if ( isset( $mapping_settings['gcl-cmapping'] ) && \in_array( $mapping_settings['mappingprovider'], $merchants, true ) ) { return \is_array( $mapping_settings['gcl-cmapping'] ) ? \array_reverse( $mapping_settings['gcl-cmapping'], true ) : $mapping_settings['gcl-cmapping']; } return \is_array( $mapping_settings['cmapping'] ) ? \array_reverse( $mapping_settings['cmapping'], true ) : $mapping_settings['cmapping']; } /** * Retrieves the mapped value for a specified attribute, considering the merchant attribute and configuration. * * @param WC_Product $product The product object. * @param string $attribute The attribute to map. * @param string $merchant_attribute The merchant attribute. * @param mixed $config Additional configuration or context. * * @return string The concatenated attribute values, separated by the defined glue or a space. * * This function already exists in CTXFeed\V5\Output\AttributesMapping. * Test case is available in CTXFeed\tests\wpunit\Output\AttributesMappingTest. */ public static function get_attribute_mapping( $product, $attribute, $merchant_attribute, $config ) { $attributes = \get_option( $attribute ); $glue = ! empty( $attributes['glue'] ) ? $attributes['glue'] : ' '; if ( ! isset( $attributes['mapping'] ) || ! \is_array( $attributes['mapping'] ) ) { return ''; } $get_attribute_value_by_type = new AttributeValueByType( $attribute, $merchant_attribute, $product, $config ); $output = self::build_attribute_output( $attributes['mapping'], $get_attribute_value_by_type, $glue ); // remove extra whitespace $output = \preg_replace( '!\s\s+!', ' ', $output ); return apply_filters( 'woo_feed_filter_attribute_mapping', $output, $attribute, $product, $config ); } /** * Builds the concatenated output for the attribute mapping. * * @param array $mapping The attribute mapping array. * @param AttributeValueByType $get_attribute_value_by_type The object to retrieve attribute values. * @param string $glue The glue used for concatenation. * * @return string The concatenated attribute values. */ private static function build_attribute_output( $mapping, $get_attribute_value_by_type, $glue ) { $output = []; foreach ( $mapping as $map ) { $value = $get_attribute_value_by_type->get_value( $map ); if ( ! empty( $value ) ) { $output[] = $value; } } return implode( $glue, $output ); } /** * Retrieves the value of a dynamic attribute for a product. * * @param WC_Product $product * @param string $attribute_name * @param string $merchant_attribute * @param mixed $config * * @return mixed|string * @since 3.2.0 * * This function already exists in CTXFeed\V5\Output\DynamicAttributes. * Test case is available in CTXFeed\tests\wpunit\Output\DynamicAttributesTest. */ public static function get_dynamic_attribute( $product, $attribute_name, $merchant_attribute, $config ) { $get_attribute_value_by_type = new AttributeValueByType( $attribute_name, $merchant_attribute, $product, $config ); $get_value = \maybe_unserialize( \get_option( $attribute_name ) ); $wf_dattribute_code = $get_value['wfDAttributeCode'] ?? ''; $attribute = isset( $get_value['attribute'] ) ? (array) $get_value['attribute'] : array(); $condition = isset( $get_value['condition'] ) ? (array) $get_value['condition'] : array(); $compare = isset( $get_value['compare'] ) ? (array) $get_value['compare'] : array(); $type = isset( $get_value['type'] ) ? (array) $get_value['type'] : array(); $prefix = isset( $get_value['prefix'] ) ? (array) $get_value['prefix'] : array(); $suffix = isset( $get_value['suffix'] ) ? (array) $get_value['suffix'] : array(); $value_attribute = isset( $get_value['value_attribute'] ) ? (array) $get_value['value_attribute'] : array(); $value_pattern = isset( $get_value['value_pattern'] ) ? (array) $get_value['value_pattern'] : array(); $default_type = $get_value['default_type'] ?? 'attribute'; $default_value_attribute = $get_value['default_value_attribute'] ?? ''; $default_value_pattern = $get_value['default_value_pattern'] ?? ''; $result = ''; // Check If Attribute Code exist if ( $wf_dattribute_code && count( $attribute ) ) { foreach ( $attribute as $key => $name ) { if ( empty( $name ) ) { continue; } $condition_name = $get_attribute_value_by_type->get_value( $name ); if ( 'weight' === $name ) { $unit = ' ' . \get_option( 'woocommerce_weight_unit' ); if ( ! empty( $unit ) ) { $condition_name = (float) \str_replace( $unit, '', $condition_name ); } } $condition_compare = $compare[ $key ]; $condition_operator = $condition[ $key ]; if ( ! empty( $condition_compare ) ) { $condition_compare = \trim( $condition_compare ); } $condition_value = ''; if ( 'pattern' === $type[ $key ] ) { $condition_value = $value_pattern[ $key ]; } elseif ( 'attribute' === $type[ $key ] ) { $condition_value = $get_attribute_value_by_type->get_value( $value_attribute[ $key ] ); } elseif ( 'remove' === $type[ $key ] ) { $condition_value = ''; } switch ( $condition_operator ) { case '==': if ( $condition_name === $condition_compare ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case '!=': if ( $condition_name !== $condition_compare ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case '>=': if ( $condition_name >= $condition_compare ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case '<=': if ( $condition_name <= $condition_compare ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case '>': if ( $condition_name > $condition_compare ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case '<': if ( $condition_name < $condition_compare ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case 'contains': if ( false !== stripos( $condition_name, $condition_compare ) ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case 'nContains': if ( stripos( $condition_name, $condition_compare ) === false ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } break; case 'between': $compare_items = explode( ',', $condition_compare ); if ( isset( $compare_items[1] ) && \is_numeric( $compare_items[0] ) && \is_numeric( $compare_items[1] ) ) { if ( $condition_name >= $compare_items[0] && $condition_name <= $compare_items[1] ) { $result = self::price_format( $name, $condition_name, $condition_value ); if ( '' !== $result ) { $result = $prefix[ $key ] . $result . $suffix[ $key ]; } } } else { $result = ''; } break; default: break; } } } if ( '' === $result ) { if ( 'pattern' === $default_type ) { $result = $default_value_pattern; } elseif ( 'attribute' === $default_type ) { if ( ! empty( $default_value_attribute ) ) { $result = $get_attribute_value_by_type->get_value( $default_value_attribute ); } } elseif ( 'remove' === $default_type ) { $result = ''; } } return apply_filters( 'woo_feed_after_dynamic_attribute_value', $result, $product, $attribute_name, $merchant_attribute, $config ); } /** * Formats a price or weight value based on the specified operation. * * @param string $name Attribute Name indicating whether it's a price or weight. * @param float $conditionName The initial value to be formatted. * @param string $result The operation and value to apply to the initial value. * * @return float|int|string Formatted result after applying the operation. * @since 3.2.0 */ public static function price_format( $name, $condition_name, $result ) { // calc and return the output. if ( false !== \strpos( $name, 'price' ) || false !== \strpos( $name, 'weight' ) ) { if ( false !== \strpos( $result, '+' ) && false !== \strpos( $result, '%' ) ) { $result = \str_replace_trim( '+', '', $result ); $result = \str_replace_trim( '%', '', $result ); if ( \is_numeric( $result ) ) { $result = $condition_name + ( $condition_name * $result / 100 ); } } elseif ( false !== \strpos( $result, '-' ) && false !== \strpos( $result, '%' ) ) { $result = \str_replace_trim( '-', '', $result ); $result = \str_replace_trim( '%', '', $result ); if ( \is_numeric( $result ) ) { // $result = ( ( $conditionName * $result ) / 100 ) - $conditionName; $result = $condition_name - ( $condition_name * $result / 100 ); } } elseif ( false !== \strpos( $result, '*' ) && false !== \strpos( $result, '%' ) ) { $result = \str_replace_trim( '*', '', $result ); $result = \str_replace_trim( '%', '', $result ); if ( \is_numeric( $result ) ) { $result = $condition_name * $result / 100; } } elseif ( false !== \strpos( $result, '+' ) ) { $result = \str_replace_trim( '+', '', $result ); if ( \is_numeric( $result ) ) { $result = $condition_name + $result; } } elseif ( false !== \strpos( $result, '-' ) ) { $result = \str_replace_trim( '-', '', $result ); if ( \is_numeric( $result ) ) { $result = $condition_name - $result; } } elseif ( false !== \strpos( $result, '*' ) ) { $result = \str_replace_trim( '*', '', $result ); if ( \is_numeric( $result ) ) { $result = $condition_name * $result; } } elseif ( false !== \strpos( $result, '/' ) ) { $result = \str_replace_trim( '/', '', $result ); if ( \is_numeric( $result ) ) { $result = $condition_name / $result; } } } return $result; } /** * Validates a given date string against a specified format. * * @param string $date The date string to validate. * @param string $format The date format to validate against. Defaults to 'Y-m-d'. * * @return bool True if the date is valid and matches the format, false otherwise. */ public static function validate_date( $date, $format = 'Y-m-d' ) { if ( ! \is_string( $date ) ) { // Optional: Add error logging or handling if $date is not a string return false; } $dateTime = DateTime::createFromFormat( $format, $date ); return $dateTime && $dateTime->format( $format ) === $date; } /** * Retrieves a product attribute value based on its type. * * @param string $attribute The name of the product attribute. * @param \WC_Product $product The product object, representing the context of the attribute. * @param \CTXFeed\V5\Utility\Config $config Configuration settings, affecting how attribute values are processed. * @param string|null $merchant_attribute Optional merchant-specific attribute, altering the return value based on merchant requirements. * @param \WC_Product $parent_product The product object. * * @return mixed The value of the attribute, which varies depending on the attribute type and configuration settings. */ public static function get_attribute_value_by_type( $attribute, $product, $config, $merchant_attribute = null, $parent_product = null ) { // Error handling: validate inputs if ( ! $product || ! $config ) { // Handle error or invalid input return null; } // Efficient handling of AttributeValueByType instance creation could be considered here $attribute_value = new AttributeValueByType( $attribute, $product, $config, $merchant_attribute, $parent_product ); return $attribute_value->get_value(); } /** * Replaces specific strings in a product attribute based on configuration rules. * * @param string $output The initial string to be modified. * @param string $productAttribute The product attribute to be checked for replacements. * @param Config $config Configuration containing the replacement rules. * * @return string The modified string after applying the replacement rules. * * @todo Write test cases for this method. */ public static function str_replace( $output, $product_attribute, $config ) { // str_replace array can contain duplicate subjects, so better loop through... foreach ( $config->get_string_replace() as $str_replace ) { if ( ! empty( $str_replace['subject'] ) && ( $product_attribute == $str_replace['subject'] || self::PRODUCT_ATTRIBUTE_PREFIX . $product_attribute == $str_replace['subject'] ) ) { if ( \strpos( $str_replace['search'], '/' ) === false ) { $output = \preg_replace( \stripslashes( '/' . $str_replace['search'] . '/mi' ), $str_replace['replace'], $output ); } else { $output = \str_replace( $str_replace['search'], $str_replace['replace'], $output ); } } } return $output; } /** * Adds a prefix and/or suffix to a given output string based on attribute configurations. * * @param string $output The string to which the prefix and suffix will be added. * @param string $attribute The product attribute to which the prefix and suffix apply. * @param \CTXFeed\V5\Utility\Config $config Configuration settings for determining prefix and suffix. * @param string|null $merchant_attribute Optional merchant-specific attribute. * * @return string The modified output string with the appropriate prefix and/or suffix added. */ public static function add_prefix_suffix( $output, $attribute, $config, $merchant_attribute ) { if ( $output == '' ) { return $output; } if ( ! $config || ! \method_exists( $config, 'get_prefix_suffix' ) ) { // Handle error or invalid configuration object return $output; } $prefix_suffix = $config->get_prefix_suffix( $attribute, $merchant_attribute ); $output = ( empty( $prefix_suffix['prefix'] ) ? '' : $prefix_suffix['prefix'] ) . $output; if ( ! empty( $prefix_suffix['suffix'] ) ) { if ( self::should_encode_attribute( $attribute ) ) { $output .= ( \preg_match( '/^\s/', $prefix_suffix['suffix'] ) ? '' : '' ) . $prefix_suffix['suffix']; }else{ $output .= ( \preg_match( '/^\s/', $prefix_suffix['suffix'] ) ? '' : ' ' ) . $prefix_suffix['suffix']; } } /** * Some attributes don't need any space like : link url, image url */ if ( self::should_encode_attribute( $attribute ) ) { $output = \str_replace( ' ', '%20', $output ); } return $output; } /** * Determines if a prefix or suffix should be stripped from a given attribute. * * @param string $attribute The attribute name to check. * * @return bool True if the prefix and suffix should be stripped for the given attribute, false otherwise. */ public static function should_encode_attribute( $attribute ) { // Validate attribute if ( ! \is_string( $attribute ) ) { // Handle error or invalid input return false; } // Consider defining these as a class constant or static variable if reused $attributes_to_strip = [ 'link', 'canonical_link', 'mobile_link', 'image', 'images', 'images_1', 'images_2', 'images_3', 'images_4', 'images_5', 'images_6', 'images_7', 'images_8', 'images_9', 'images_10', 'image_1', 'image_2', 'image_3', 'image_4', 'image_5', 'image_6', 'image_7', 'image_8', 'image_9', 'image_10' ]; return \in_array( $attribute, $attributes_to_strip ); } /** * Translates an attribute using the TranslatePress plugin. * * @param string $attribute Name of the product attribute. * @param mixed $attribute_value Value of the product attribute. * @param WC_Product $product Product object. * @param mixed $config Feed configuration settings. * * @return mixed Translated attribute value if TranslatePress is active and configured, else returns original value. * @since 5.2.12 */ public static function get_tp_translate( $attribute, $attribute_value, $product, $config ) { if ( \is_plugin_active( 'translatepress-multilingual/index.php' ) ) { $target_language = $config->get_feed_language( $attribute ); if ( ! empty( $target_language ) ) { if ( \class_exists( 'TRP_Settings' ) && \class_exists( 'TRP_Translation_Render' ) ) { $settings = ( new TRP_Settings() )->get_settings(); $trp_render = new TRP_Translation_Render( $settings ); global $TRP_LANGUAGE; $default_language = $TRP_LANGUAGE; $TRP_LANGUAGE = $target_language; $attribute_value = $trp_render->translate_page( $attribute_value ); // Resetting the global language to default $TRP_LANGUAGE = $default_language; } } } return $attribute_value; } /** * Retrieves a WooCommerce product object based on the product ID and configuration settings. * For variable products, it returns a specific variation based on the configured variation type. * * @param int $product_id Product ID. * @param Config $config Configuration settings for handling variable products. * * @return mixed The product object, which may be a variation for variable products. * @throws Exception If the product is not found or an error occurs. */ public static function get_product_object( $product_id, $config ) { $product = \wc_get_product( $product_id ); if ( ! $product ) { throw new Exception( 'Product not found.' ); } $variable_config = $config->get_variable_config(); $variation_type = $variable_config['is_variations']; if ( $product->is_type( 'variable' ) && \in_array( $variation_type, [ 'default', 'cheap', 'first', 'last', 'expensive', 'n' ], true ) ) { $id = self::determine_variable_product( $product, $variation_type ); return $id ? \wc_get_product( $id ) : $product; } if ( $config->get_categories_to_include() && $product->is_type( 'variable' ) ) { $products = []; if ( $variation_type == 'both' ) { array_push( $products, $product ); } $variations = $product->get_visible_children(); foreach ( $variations as $variation_id ) { $variation_product = wc_get_product( $variation_id ); array_push( $products, $variation_product ); } return $products; } return $product; } /** * Determines the ID of the variable product based on the variation type. * * @param WC_Product $product The variable product. * @param string $variationType The type of variation to retrieve. * * @return int|null The ID of the determined product variation, or null if not found. */ private static function determine_variable_product( $product, $variation_type ) { $variations = $product->get_visible_children(); $variations_price = $product->get_variation_prices(); switch ( $variation_type ) { case 'default': return self::get_default_product_variation( $product ); case 'first': return ! empty( $variations ) ? \reset( $variations ) : null; case 'last': return ! empty( $variations ) ? \end( $variations ) : null; case 'cheap': return (isset($variations_price['price']) && !empty($variations_price['price'])) ? \array_keys( $variations_price['price'], \min( $variations_price['price'] ) )[0] : null; case 'expensive': return isset( $variations_price['price'] ) ? \array_keys( $variations_price['price'], \max( $variations_price['price'] ) )[0] : null; } return null; } /** * Finds the default variation ID for a variable product. * * @param WC_Product $product The variable product. * * @return int|false The ID of the default variation, or false if the product is not a variable product. * @throws Exception If the product is not valid. */ public static function get_default_product_variation( $product ) { if ( $product->is_type( 'variable' ) ) { $attributes = $product->get_default_attributes(); foreach ( $attributes as $key => $value ) { if ( \strpos( $key, 'attribute_' ) === 0 ) { continue; } unset( $attributes[ $key ] ); $attributes[ \sprintf( 'attribute_%s', $key ) ] = $value; } return ( new WC_Product_Variation_Data_Store_CPT )->find_matching_product_variation( $product, $attributes ); } return false; } /** * Retrieves the product price including tax. * * @param float|string $price The base price of the product. * @param WC_Product $product The WooCommerce product object. * * @return float The product price including tax. */ public static function get_price_with_tax( $price, $product ) { if ( CommonHelper::wc_version_check( 3.0 ) ) { return \wc_get_price_including_tax( $product, array( 'price' => $price ) ); } return $product->get_price_including_tax( 1, $price ); } }