HEX
Server: Apache
System: Linux web5.visualcom.es 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: ensaco.es (10019)
PHP: 8.3.32
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/ensaco.es/httpdocs/wp-content/plugins/woo-product-bundle/includes/class-product.php
<?php
declare( strict_types=1 );
defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'WC_Product_Woosb' ) && class_exists( 'WC_Product' ) ) {
	class WC_Product_Woosb extends WC_Product {
		protected $items = null;
		protected $bundled_products = [];
		protected $helper = null;

		public function __construct( $product = 0 ) {
			// Cache helper instance
			$this->helper = WPCleverWoosb_Helper();

			$this->supports[] = 'ajax_add_to_cart';
			parent::__construct( $product );

			// Preload all metadata at once
			$this->preload_meta();

			$this->build_items();
		}

		public function get_type(): string {
			return 'woosb';
		}

		public function add_to_cart_url(): string {
			$product_id = $this->get_id();

			$can_add_directly = $this->is_purchasable()
			                    && $this->is_in_stock()
			                    && ! $this->has_variables()
			                    && ! $this->has_optional();

			$url = $can_add_directly
				? remove_query_arg( 'added-to-cart', add_query_arg( 'add-to-cart', $product_id ) )
				: get_permalink( $product_id );

			return (string) apply_filters(
				'woosb_product_add_to_cart_url',
				apply_filters( 'woocommerce_product_add_to_cart_url', $url, $this ),
				$this
			);
		}

		public function add_to_cart_text(): string {
			if ( ! $this->is_purchasable() || ! $this->is_in_stock() ) {
				$text = $this->helper->localization( 'button_read', esc_html__( 'Read more', 'woo-product-bundle' ) );
			} else {
				$button_type  = ( ! $this->has_variables() && ! $this->has_optional() ) ? 'button_add' : 'button_select';
				$default_text = ( $button_type === 'button_add' ) ? esc_html__( 'Add to cart', 'woo-product-bundle' ) : esc_html__( 'Select options', 'woo-product-bundle' );
				$text         = $this->helper->localization( $button_type, $default_text );
			}

			return (string) apply_filters(
				'woosb_product_add_to_cart_text',
				apply_filters( 'woocommerce_product_add_to_cart_text', $text, $this ),
				$this
			);
		}

		public function single_add_to_cart_text(): string {
			$default_text = esc_html__( 'Add to cart', 'woo-product-bundle' );

			return (string) apply_filters(
				'woosb_product_single_add_to_cart_text',
				apply_filters( 'woocommerce_product_single_add_to_cart_text', $this->helper->localization( 'button_single', $default_text ), $this ),
				$this
			);
		}

		public function is_on_sale( $context = 'view' ): bool {
			if ( $this->is_fixed_price() ) {
				return parent::is_on_sale( $context );
			}

			// Cache discount values to avoid multiple method calls
			$discount_amount     = $this->get_discount_amount();
			$discount_percentage = $this->get_discount_percentage();

			// Return true if either discount is set, otherwise check parent
			return $discount_amount || $discount_percentage || parent::is_on_sale( $context );
		}

		public function get_regular_price( $context = 'view' ) {
			// Early return for non-view context or fixed price
			if ( $context !== 'view' || $this->is_fixed_price() ) {
				return parent::get_regular_price( $context );
			}

			$regular_price = 0;

			// Check items existence early
			if ( empty( $this->items ) ) {
				return $regular_price;
			}

			// Process items
			foreach ( $this->items as $item ) {
				// Get cached product object
				$_product = $this->get_bundled_product_object( $item['id'] );

				// Skip invalid products or woosb type
				if ( ! $_product || $_product->is_type( 'woosb' ) ) {
					continue;
				}

				// Calculate item price
				if ( $_product->is_type( 'variable' ) ) {
					$regular_price += (float) $_product->get_variation_regular_price( 'max' ) * (float) $item['qty'];
				} else {
					$regular_price += (float) $_product->get_regular_price() * (float) $item['qty'];
				}
			}

			return $regular_price ?: parent::get_regular_price( $context );
		}

		public function get_sale_price( $context = 'view' ) {
			// Early return for non-view context or fixed price
			if ( $context !== 'view' || $this->is_fixed_price() ) {
				return parent::get_sale_price( $context );
			}

			// Cache discount values
			$discount_amount     = $this->get_discount_amount();
			$discount_percentage = $this->get_discount_percentage();

			// Early return if no discount
			if ( ! $discount_amount && ! $discount_percentage ) {
				return '';
			}

			$sale_price = 0;

			// Check items existence early
			if ( empty( $this->items ) ) {
				return $sale_price;
			}

			// Process items
			foreach ( $this->items as $item ) {
				// Get cached product object
				$_product = $this->get_bundled_product_object( $item['id'] );

				// Skip invalid products or woosb type
				if ( ! $_product || $_product->is_type( 'woosb' ) ) {
					continue;
				}

				// Calculate item price
				$_price = (float) $this->helper->get_price( $_product ) * (float) $item['qty'];

				// Apply discount percentage if applicable
				if ( $discount_percentage ) {
					$sale_price += $this->helper->round_price( $_price * ( 100 - $discount_percentage ) / 100 );
				} else {
					$sale_price += $_price;
				}
			}

			// Apply a fixed discount amount if applicable
			return $discount_amount ? ( $sale_price - $discount_amount ) : $sale_price;
		}

		public function get_price( $context = 'view' ) {
			// Early return if not view context
			if ( $context !== 'view' ) {
				return parent::get_price( $context );
			}

			// Cache values to avoid multiple method calls
			$regular_price = (float) $this->get_regular_price();
			$parent_price  = (float) parent::get_price( $context );

			// Return '0' if either price is zero
			if ( $regular_price === 0.0 || $parent_price === 0.0 ) {
				return '0';
			}

			return parent::get_price( $context );
		}

		public function get_manage_stock( $context = 'view' ) {
			$parent_manage = parent::get_manage_stock( $context );

			// Early return if stock management is disabled globally or via filter
			if (
				'yes' !== get_option( 'woocommerce_manage_stock' ) ||
				apply_filters( 'woosb_disable_inventory_management', false )
			) {
				return $parent_manage;
			}

			// Early return if no items or has optional items
			if ( empty( $this->items ) || ( $this->has_optional() && ! apply_filters( 'woosb_manage_stock_optional_items', false ) ) ) {
				return $parent_manage;
			}

			$exclude_unpurchasable = $this->exclude_unpurchasable();

			foreach ( $this->items as $item ) {
				$product = $this->get_bundled_product_object( $item['id'] );

				// Skip invalid products or those meeting exclusion criteria
				if (
					! $product ||
					$product->is_type( 'woosb' ) ||
					( $exclude_unpurchasable &&
					  ( ! $product->is_purchasable() || ! $this->helper->is_in_stock( $product ) ) )
				) {
					continue;
				}

				// Return true if the product manages stock
				if ( $product->get_manage_stock( $context ) === true ) {
					return true;
				}

				// Check the parent product if this is a variation
				if ( $product->is_type( 'variation' ) ) {
					$parent_product = $this->get_bundled_product_object( $product->get_parent_id() );

					if ( $parent_product && $parent_product->get_manage_stock( $context ) === true ) {
						return true;
					}
				}
			}

			// Return parent manages stock setting if this product manages stock
			return $this->is_manage_stock() ? $parent_manage : false;
		}

		public function get_stock_status( $context = 'view' ) {
			$parent_status = parent::get_stock_status( $context );

			// Early return if inventory management is disabled
			if ( apply_filters( 'woosb_disable_inventory_management', false ) ) {
				return $parent_status;
			}

			// Early return if no items
			if ( empty( $this->items ) ) {
				return $parent_status;
			}

			$exclude_unpurchasable = $this->exclude_unpurchasable();
			$stock_status          = 'instock';
			$all_out_of_stock      = true;

			foreach ( $this->items as $item ) {
				// Skip if the product doesn't exist
				$_product = $this->get_bundled_product_object( $item['id'] );

				if ( ! $_product || $_product->is_type( 'woosb' ) ) {
					continue;
				}

				$_qty = (float) $item['qty'];

				if ( ! empty( $item['optional'] ) ) {
					$_qty = ! empty( $item['min'] ) ? (float) $item['min'] : 0;
				}

				// Cache commonly used method results
				$is_in_stock      = $this->helper->is_in_stock( $_product );
				$has_enough_stock = $this->helper->has_enough_stock( $_product, $_qty );

				if ( $is_in_stock && $has_enough_stock ) {
					$all_out_of_stock = false;
				}

				if ( $exclude_unpurchasable && ( ! $_product->is_purchasable() || ! $is_in_stock ) ) {
					continue;
				}

				if ( $_qty && ( $_product->get_stock_status( $context ) === 'outofstock' || ! $has_enough_stock ) ) {
					return 'outofstock';
				}

				if (
					$_product->get_stock_status( $context ) === 'onbackorder' ||
					( $_qty && ! $has_enough_stock && $_product->backorders_allowed() )
				) {
					$stock_status = 'onbackorder';
				}
			}

			if ( $all_out_of_stock ) {
				return 'outofstock';
			}

			if ( $this->is_manage_stock() ) {
				return $parent_status === 'instock' ? $stock_status : $parent_status;
			}

			return $stock_status;
		}

		public function get_stock_quantity( $context = 'view' ) {
			$parent_quantity = parent::get_stock_quantity( $context );

			// Early return if stock management is disabled
			if (
				'yes' !== get_option( 'woocommerce_manage_stock' ) ||
				apply_filters( 'woosb_disable_inventory_management', false )
			) {
				return $parent_quantity;
			}

			$product_id            = $this->id;
			$exclude_unpurchasable = $this->exclude_unpurchasable();
			$items                 = $this->items;

			// Early return if no items or has optional items
			if ( ! $items || ( $this->has_optional() && ! apply_filters( 'woosb_manage_stock_optional_items', false ) ) ) {
				if ( apply_filters( 'woosb_update_stock', true ) ) {
					update_post_meta( $product_id, '_stock', $parent_quantity );
				}

				return $parent_quantity;
			}

			$available_qty = [];

			foreach ( $items as $item ) {
				// Skip if quantity is not positive
				if ( $item['qty'] <= 0 ) {
					continue;
				}

				$_product = $this->get_bundled_product_object( $item['id'] );

				// Cache stock quantity to avoid multiple calls
				$stock_quantity = $this->helper->get_stock_quantity( $_product );

				// Skip invalid products or those not meeting criteria
				if (
					! $_product ||
					$_product->is_type( 'woosb' ) ||
					! $_product->get_manage_stock() ||
					$stock_quantity === null ||
					( $exclude_unpurchasable && ( ! $_product->is_purchasable() || ! $this->helper->is_in_stock( $_product ) ) )
				) {
					continue;
				}

				$available_qty[] = floor( $stock_quantity / (float) $item['qty'] );
			}

			// If no available quantities found, update and return the parent quantity
			if ( empty( $available_qty ) ) {
				if ( apply_filters( 'woosb_update_stock', true ) ) {
					update_post_meta( $product_id, '_stock', $parent_quantity );
				}

				return $parent_quantity;
			}

			// Find minimum available quantity without sorting a full array
			$min_available = min( $available_qty );

			// Use parent quantity if it's lower and stock is managed
			if ( $this->is_manage_stock() && $parent_quantity < $min_available ) {
				if ( apply_filters( 'woosb_update_stock', true ) ) {
					update_post_meta( $product_id, '_stock', $parent_quantity );
				}

				return $parent_quantity;
			}

			if ( apply_filters( 'woosb_update_stock', true ) ) {
				update_post_meta( $product_id, '_stock', $min_available );
			}

			return $min_available;
		}

		public function get_backorders( $context = 'view' ) {
			$parent_backorders = parent::get_backorders( $context );

			// Early return if inventory management is disabled
			if ( apply_filters( 'woosb_disable_inventory_management', false ) ) {
				return $parent_backorders;
			}

			// Early return if no items or has optional items
			if ( empty( $this->items ) || ( $this->has_optional() && ! apply_filters( 'woosb_manage_stock_optional_items', false ) ) ) {
				return $parent_backorders;
			}

			$backorders            = 'yes';
			$exclude_unpurchasable = $this->exclude_unpurchasable();

			foreach ( $this->items as $item ) {
				// Get product once
				$product = $this->get_bundled_product_object( $item['id'] ?: 0 );

				// Skip if the product doesn't meet criteria
				if ( ! $product || ! is_a( $product, 'WC_Product' ) || $product->is_type( 'woosb' ) ) {
					continue;
				}

				// Skip if the product doesn't meet criteria
				if ( ! $product->get_manage_stock() || ( $exclude_unpurchasable && ( ! $product->is_purchasable() || ! $this->helper->is_in_stock( $product ) ) ) ) {
					continue;
				}

				// Check backorders status
				$product_backorders = $product->get_backorders( $context );

				if ( $product_backorders === 'no' ) {
					return 'no';
				}

				if ( $product_backorders === 'notify' ) {
					$backorders = 'notify';
				}
			}

			// Simplified return logic
			if ( $this->is_manage_stock() ) {
				return $parent_backorders === 'yes' ? $backorders : $parent_backorders;
			}

			return $backorders;
		}

		public function get_sold_individually( $context = 'view' ) {
			$parent_individually = parent::get_sold_individually( $context );

			// Early return if inventory management is disabled
			if ( apply_filters( 'woosb_disable_inventory_management', false ) ) {
				return $parent_individually;
			}

			// Early return if no items or has optional items
			if ( empty( $this->items ) || ( $this->has_optional() && ! apply_filters( 'woosb_manage_stock_optional_items', false ) ) ) {
				return $parent_individually;
			}

			$exclude_unpurchasable = $this->exclude_unpurchasable();

			foreach ( $this->items as $item ) {
				$product = wc_get_product( $item['id'] );

				// Skip invalid products or those meeting exclusion criteria
				if (
					! $product ||
					$product->is_type( 'woosb' ) ||
					( $exclude_unpurchasable &&
					  ( ! $product->is_purchasable() || ! $this->helper->is_in_stock( $product ) ) )
				) {
					continue;
				}

				// Return true if any product is sold individually
				if ( $product->is_sold_individually() ) {
					return true;
				}
			}

			return $parent_individually;
		}

		public function needs_shipping() {
			return apply_filters( 'woocommerce_product_needs_shipping', ! $this->is_virtual() && ( $this->get_meta( 'woosb_shipping_fee' ) !== 'each' ), $this );
		}

		// extra functions

		public function has_variables() {
			// Early return if no items
			if ( empty( $this->items ) ) {
				return apply_filters( 'woosb_has_variables', false, $this );
			}

			// Use array_reduce for better performance
			$has_variables = array_reduce( $this->items, function ( $carry, $item ) {
				if ( $carry ) {
					return true;
				} // Skip if we already found a variable product

				if ( $product = $this->get_bundled_product_object( (int) $item['id'] ) ) {
					return $product->is_type( 'variable' ) ? true : $carry;
				}

				return $carry;
			}, false );

			return apply_filters( 'woosb_has_variables', $has_variables, $this );
		}

		public function has_optional() {
			// Early return if no items
			if ( empty( $this->items ) ) {
				return apply_filters( 'woosb_has_optional', false, $this );
			}

			// Use array_reduce for better performance
			$has_optional = array_reduce( $this->items, function ( $carry, $item ) {
				return $carry || ! empty( $item['optional'] );
			}, false );

			return apply_filters( 'woosb_has_optional', $has_optional, $this );
		}

		public function is_optional() {
			// new version 8.0
			return self::has_optional();
		}

		public function is_manage_stock() {
			return apply_filters( 'woosb_is_manage_stock', $this->get_meta( 'woosb_manage_stock' ) === 'on', $this );
		}

		public function is_fixed_price() {
			$disable_auto_price = $this->get_meta( 'woosb_disable_auto_price' ) ?: apply_filters( 'woosb_disable_auto_price_default', 'off' );

			return apply_filters( 'woosb_is_fixed_price', $disable_auto_price === 'on', $this );
		}

		public function exclude_unpurchasable() {
			// Get meta-value once
			$exclude_unpurchasable = $this->get_meta( 'woosb_exclude_unpurchasable' );

			// Check if we need to use the default setting
			if ( ! $exclude_unpurchasable || in_array( $exclude_unpurchasable, [ 'unset', 'default' ], true ) ) {
				$exclude_unpurchasable = $this->helper->get_setting( 'exclude_unpurchasable', 'no' );
			}

			return apply_filters( 'woosb_exclude_unpurchasable', $exclude_unpurchasable === 'yes', $this );
		}

		public function get_discount_amount() {
			// Early return if fixed price
			if ( $this->is_fixed_price() ) {
				return apply_filters( 'woosb_get_discount_amount', 0, $this );
			}

			// Get and cast discount amount in one step
			$discount_amount = (float) $this->get_meta( 'woosb_discount_amount' );

			return apply_filters( 'woosb_get_discount_amount', $discount_amount, $this );
		}

		public function get_discount_percentage() {
			// Early returns for fixed price or if discount amount exists
			if ( $this->is_fixed_price() || $this->get_discount_amount() ) {
				return apply_filters( 'woosb_get_discount_percentage', 0, $this );
			}

			// Get discount percentage
			$discount_percentage = $this->get_meta( 'woosb_discount' );

			// Validate discount percentage
			if ( is_numeric( $discount_percentage ) ) {
				$discount_percentage = (float) $discount_percentage;
				if ( $discount_percentage > 0 && $discount_percentage < 100 ) {
					return apply_filters( 'woosb_get_discount_percentage', $discount_percentage, $this );
				}
			}

			return apply_filters( 'woosb_get_discount_percentage', 0, $this );
		}

		public function get_discount() {
			$discount = $this->get_discount_amount() ?: $this->get_discount_percentage() . '%';

			return apply_filters( 'woosb_get_discount', $discount, $this );
		}

		public function get_ids() {
			return apply_filters( 'woosb_get_ids', $this->get_meta( 'woosb_ids' ), $this );
		}

		public function get_ids_str() {
			$ids = $this->get_ids();

			if ( ! is_array( $ids ) ) {
				return apply_filters( 'woosb_get_ids_str', $ids, $this );
			}

			$ids_str = implode( ',', array_map(
				function ( $key, $item ) {
					$use_sku    = apply_filters( 'woosb_use_sku', false );
					$product_id = $this->id;

					if ( $use_sku && ! empty( $item['sku'] ) ) {
						$new_id = $this->helper->get_product_id_from_sku( $item['sku'] );

						if ( $new_id ) {
							$item['id'] = $new_id;
						}
					}

					return ! empty( $item['id'] ) && ( $item['id'] != $product_id ) ? "{$item['id']}/{$key}/{$item['qty']}" : null;
				},
				array_keys( $ids ),
				$ids
			) );

			return apply_filters( 'woosb_get_ids_str', $ids_str, $this );
		}

		public function build_items( $ids = null ) {
			$items = [];
			$ids   = $ids ?: $this->get_meta( 'woosb_ids' );

			// Early return if no IDs
			if ( empty( $ids ) ) {
				$this->items = $items;

				return;
			}

			$product_id = $this->id;

			if ( is_array( $ids ) ) {
				// Process array format (v7.0+)
				// Cache meta values for better performance
				$limit_each_min         = $this->get_meta( 'woosb_limit_each_min' );
				$limit_each_min_default = $this->get_meta( 'woosb_limit_each_min_default' ) === 'on';
				$limit_each_max         = $this->get_meta( 'woosb_limit_each_max' );
				$use_sku                = apply_filters( 'woosb_use_sku', false );

				foreach ( $ids as $key => $item ) {
					// Set default values
					$item = array_merge( [
						'id'    => 0,
						'sku'   => '',
						'qty'   => 0,
						'attrs' => []
					], $item );

					// Process SKU if enabled
					if ( $use_sku && ! empty( $item['sku'] ) ) {
						$new_id = $this->helper->get_product_id_from_sku( $item['sku'] );

						if ( $new_id ) {
							$item['id'] = $new_id;
						}
					}

					if ( $item['id'] == $product_id ) {
						// prevent infinity loop
						continue;
					}

					// Set min/max values if not set (v8.0+)
					if ( ! isset( $item['min'] ) ) {
						$item['min'] = $limit_each_min_default ? (float) $item['qty'] : $limit_each_min;
						$item['max'] = $limit_each_max;
					}

					$item['id']  = apply_filters( 'woosb_item_id', $item['id'] );
					$item['sku'] = apply_filters( 'woosb_item_sku', $item['sku'] );

					$items[ $key ] = $item;
				}
			} else {
				// Process string format
				$ids_arr = explode( ',', $ids );

				if ( ! empty( $ids_arr ) ) {
					foreach ( $ids_arr as $ids_item ) {
						if ( empty( $ids_item ) ) {
							continue;
						}

						$data = explode( '/', $ids_item );
						$id   = rawurldecode( $data[0] ?? 0 );

						if ( empty( $id ) ) {
							continue;
						}

						// Get product ID and SKU
						if ( ! is_numeric( $id ) ) {
							// Process SKU
							$sku = $id;
							$id  = wc_get_product_id_by_sku( ltrim( $id, 'sku-' ) );
						} else {
							// Process ID
							$product = $this->get_bundled_product_object( (int) $id );
							$sku     = $product ? $product->get_sku() : '';
						}

						if ( $id == $product_id ) {
							// prevent infinity loop
							continue;
						}

						// Get key and quantity
						$key = isset( $data[1] )
							? ( is_numeric( $data[1] ) && ! isset( $data[2] )
								? $this->helper->generate_key()
								: $data[1] )
							: $this->helper->generate_key();

						$qty = isset( $data[1] )
							? ( is_numeric( $data[1] ) && ! isset( $data[2] )
								? (float) $data[1]
								: (float) ( $data[2] ?? 1 ) )
							: 1;

						// Build item array
						$items[ $key ] = [
							'id'    => apply_filters( 'woosb_item_id', $id ),
							'sku'   => apply_filters( 'woosb_item_sku', $sku ),
							'qty'   => $qty,
							'attrs' => isset( $data[3] )
								? (array) json_decode( rawurldecode( $data[3] ) )
								: []
						];
					}
				}
			}

			$this->items = $items;
		}

		protected function preload_meta() {
			// WC 3.0+ handles meta caching in Data Store, but we can ensure it's loaded
			$this->get_meta_data();
		}

		public function get_items() {
			return apply_filters( 'woosb_get_items', $this->items, $this );
		}

		/**
		 * Get cached bundled product object
		 *
		 * @param int $product_id
		 *
		 * @return WC_Product|null
		 */
		protected function get_bundled_product_object( $product_id ) {
			if ( ! isset( $this->bundled_products[ $product_id ] ) ) {
				$this->bundled_products[ $product_id ] = wc_get_product( $product_id );
			}

			return $this->bundled_products[ $product_id ] ?: null;
		}
	}
}