purchase_price_allowed = TRUE; // Add the purchase price to WC products. add_action( 'woocommerce_product_options_pricing', array( $this, 'add_purchase_price_field' ) ); add_action( 'woocommerce_variation_options_pricing', array( $this, 'add_purchase_price_field' ), 10, 3 ); } /** * Filters the Product data tabs settings to add ATUM settings * * @since 1.4.1 * * @param array $data_tabs * * @return array */ public function add_product_data_tab( $data_tabs ) { // Add the ATUM tab to Simple and BOM products. $bom_tab = (array) apply_filters( 'atum/product_data/tab', array( 'atum' => array( 'label' => __( 'ATUM Inventory', ATUM_TEXT_DOMAIN ), 'target' => 'atum_product_data', 'class' => array_merge( array( 'show_if_simple', 'show_if_variable' ), Helpers::get_option_group_hidden_classes() ), 'priority' => 21, ), ) ); // Insert the ATUM tab under Inventory tab. $data_tabs = array_merge( array_slice( $data_tabs, 0, 2 ), $bom_tab, array_slice( $data_tabs, 2 ) ); return $data_tabs; } /** * Add the fields to ATUM Inventory tab within WC's Product Data meta box * * @since 1.4.1 */ public function add_product_data_tab_panel() { $product_id = get_the_ID(); $product = Helpers::get_atum_product( $product_id ); $product_status = get_post_status( $product_id ); $checkbox_wrapper_classes = (array) apply_filters( 'atum/product_data/atum_switch/classes', [ 'show_if_simple' ] ); $control_button_classes = (array) apply_filters( 'atum/product_data/control_button/classes', [ 'show_if_variable' ] ); Helpers::load_view( 'meta-boxes/product-data/atum-tab-panel', compact( 'product', 'product_status', 'checkbox_wrapper_classes', 'control_button_classes' ) ); } /** * Add the Product Levels meta boxes to the Product variations * * @since 0.0.3 * * @param int $loop The current item in the loop of variations. * @param array $variation_data The current variation data. * @param \WP_Post $variation The variation post. */ public function add_product_variation_data_panel( $loop, $variation_data, $variation ) { // Get the variation product. $variation = Helpers::get_atum_product( $variation->ID ); Helpers::load_view( 'meta-boxes/product-data/atum-variation-panel', compact( 'loop', 'variation_data', 'variation' ) ); } /** * Save all the fields within the Product Data's ATUM Inventory tab * * @since 1.4.1 */ private function save_product_data_panel() { $tab_data_key = $this->is_variation ? 'variation_atum_tab' : 'atum_product_tab'; $atum_tab_values = isset( $_POST[ $tab_data_key ] ) ? $_POST[ $tab_data_key ] : array(); $product_tab_fields = Globals::get_product_tab_fields(); $is_inheritable_product = ! $this->is_variation && Helpers::is_inheritable_type( esc_attr( $_POST['product-type'] ) ); $this->product_data['inheritable'] = $is_inheritable_product ? 'yes' : 'no'; foreach ( $product_tab_fields as $field_name => $field_type ) { // The ATUM's stock control must be always 'yes' for inheritable products. if ( Globals::ATUM_CONTROL_STOCK_KEY === $field_name && $is_inheritable_product ) { $this->product_data['atum_controlled'] = 'yes'; continue; } if ( $this->is_variation ) { $field_value = isset( $atum_tab_values[ $field_name ][ $this->loop ] ) ? $atum_tab_values[ $field_name ][ $this->loop ] : NULL; } else { $field_value = isset( $atum_tab_values[ $field_name ] ) ? $atum_tab_values[ $field_name ] : NULL; } // Sanitize the fields. switch ( $field_type ) { case 'checkbox': $field_value = 'yes' === $field_value ? 'yes' : 'no'; break; case 'number_int': $field_value = absint( $field_value ); break; case 'number_float': $field_value = floatval( $field_value ); break; case 'text': default: $field_value = wc_clean( $field_value ); break; } // The ATUM control key doesn't match to the new table column name. if ( Globals::ATUM_CONTROL_STOCK_KEY === $field_name ) { $field_name = 'atum_controlled'; } $this->product_data[ $field_name ] = $field_value; } } /** * Add the individual out stock threshold field to WC's WC's product data meta box * * @since 1.4.10 * * @param int $loop Only for variations. The loop item number. * @param array $variation_data Only for variations. The variation item data. * @param \WP_Post $variation Only for variations. The variation product. */ public function add_out_stock_threshold_field( $loop = NULL, $variation_data = array(), $variation = NULL ) { global $post; $meta_key = Globals::OUT_STOCK_THRESHOLD_KEY; $woocommerce_notify_no_stock_amount = get_option( 'woocommerce_notify_no_stock_amount' ); $product_id = empty( $variation ) ? $post->ID : $variation->ID; $product = Helpers::get_atum_product( $product_id ); /* @noinspection PhpUndefinedMethodInspection */ $out_stock_threshold = $product->get_out_stock_threshold(); $product_type = empty( $variation ) ? $product->get_type() : ''; $out_stock_threshold_field_name = empty( $variation ) ? $meta_key : "variation{$meta_key}[$loop]"; $out_stock_threshold_field_id = empty( $variation ) ? $meta_key : $meta_key . $loop; // If the user is not allowed to edit "Out of stock threshold", add a hidden input. if ( ! AtumCapabilities::current_user_can( 'edit_out_stock_threshold' ) ) : ?> product->get_type(), Globals::get_product_types_with_stock(), TRUE ) ) { return; } $out_stock_threshold = $this->is_variation ? $_POST[ 'variation' . Globals::OUT_STOCK_THRESHOLD_KEY ][ $this->loop ] : $_POST[ Globals::OUT_STOCK_THRESHOLD_KEY ]; // Force product validate and save to rebuild stock_status (probably _out_stock_threshold has been disabled for this product). if ( $out_stock_threshold ) { Helpers::force_rebuild_stock_status( $this->product ); } $this->product_data['out_stock_threshold'] = $out_stock_threshold; } /** * Add the purchase price field to WC's product data meta box * * @since 1.2.0 * * @param int $loop Only for variations. The loop item number. * @param array $variation_data Only for variations. The variation item data. * @param \WP_Post $variation Only for variations. The variation product. */ public function add_purchase_price_field( $loop = NULL, $variation_data = array(), $variation = NULL ) { if ( ! current_user_can( ATUM_PREFIX . 'edit_purchase_price' ) ) { return; } $field_title = __( 'Purchase price', ATUM_TEXT_DOMAIN ) . ' (' . get_woocommerce_currency_symbol() . ')'; if ( empty( $variation ) ) { $product_id = get_the_ID(); $wrapper_class = '_purchase_price_field ' . Helpers::get_option_group_hidden_classes( FALSE ); $field_id = $field_name = Globals::PURCHASE_PRICE_KEY; } else { $product_id = $variation->ID; $field_name = "variation_purchase_price[$loop]"; $field_id = "variation_purchase_price{$loop}"; $wrapper_class = "$field_name form-row form-row-first"; } $product = Helpers::get_atum_product( $product_id ); /* @noinspection PhpUndefinedMethodInspection */ $purchase_price = $product->get_purchase_price(); $field_value = '' === $purchase_price ? '' : (float) $purchase_price; $price = (float) $product->get_price(); Helpers::load_view( 'meta-boxes/product-data/purchase-price-field', compact( 'wrapper_class', 'field_title', 'field_name', 'field_id', 'field_value', 'price', 'variation', 'loop' ) ); } /** * Save the purchase price meta on product post savings * * @since 1.2.0 * * @return array */ private function save_purchase_price() { $product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) ); /* @noinspection PhpUndefinedMethodInspection */ $old_purchase_price = $this->product->get_purchase_price(); $new_purchase_price = $old_purchase_price ?: ''; // Variables, grouped and variations. if ( Helpers::is_inheritable_type( $product_type ) && 'bundle' !== $product_type ) { if ( $this->is_variation && isset( $_POST[ 'variation' . Globals::PURCHASE_PRICE_KEY ][ $this->loop ] ) ) { $purchase_price = wc_clean( $_POST[ 'variation' . Globals::PURCHASE_PRICE_KEY ][ $this->loop ] ); $new_purchase_price = '' === $purchase_price ? '' : $purchase_price; } } // Rest of product types (Bypass if "_puchase_price" meta is not coming). elseif ( ! $this->is_variation && isset( $_POST[ Globals::PURCHASE_PRICE_KEY ] ) ) { $purchase_price = wc_clean( $_POST[ Globals::PURCHASE_PRICE_KEY ] ); $new_purchase_price = '' === $purchase_price ? '' : $purchase_price; } $this->product_data['purchase_price'] = $new_purchase_price; return array( 'new_purchase_price' => $new_purchase_price, 'old_purchase_price' => $old_purchase_price, ); } /** * Adds the Supplier fields in WC's product data meta box * * @since 1.3.0 * * @param int $loop Only for variations. The loop item number. * @param array $variation_data Only for variations. The variation item data. * @param \WP_Post $variation Only for variations. The variation product. */ public function add_product_supplier_fields( $loop = NULL, $variation_data = array(), $variation = NULL ) { global $post; $product_id = empty( $variation ) ? $post->ID : $variation->ID; $product = Helpers::get_atum_product( $product_id ); if ( empty( $variation ) ) { // Do not add the field to variable products (every variation will have its own). if ( in_array( $product->get_type(), array_diff( Globals::get_inheritable_product_types(), [ 'grouped', 'bundle' ] ) ) ) { return; } } // Save the meta keys on a variable (some sites were experiencing weird issues when accessing to these constants directly). $supplier_meta = Suppliers::SUPPLIER_META_KEY; $supplier_sku_meta = Suppliers::SUPPLIER_SKU_META_KEY; /* @noinspection PhpUndefinedMethodInspection */ $supplier_id = $product->get_supplier_id(); /* @noinspection PhpUndefinedMethodInspection */ $supplier_sku = $product->get_supplier_sku(); $supplier = $supplier_id ? get_post( $supplier_id ) : NULL; $supplier_field_name = empty( $variation ) ? $supplier_meta : "variation{$supplier_meta}[$loop]"; $supplier_field_id = empty( $variation ) ? $supplier_meta : $supplier_meta . $loop; $supplier_sku_field_name = empty( $variation ) ? $supplier_sku_meta : "variation{$supplier_sku_meta}[$loop]"; $supplier_sku_field_id = empty( $variation ) ? $supplier_sku_meta : $supplier_sku_meta . $loop; // If the user is not allowed to edit Suppliers, add a hidden input. if ( ! AtumCapabilities::current_user_can( 'edit_supplier' ) ) : ?> product->get_type(), array_diff( Globals::get_inheritable_product_types(), [ 'grouped', 'bundle' ] ) ) ) { return; } if ( $this->is_variation ) { $this->product_data['supplier_id'] = isset( $_POST[ 'variation' . Suppliers::SUPPLIER_META_KEY ][ $this->loop ] ) ? $_POST[ 'variation' . Suppliers::SUPPLIER_META_KEY ][ $this->loop ] : NULL; $this->product_data['supplier_sku'] = isset( $_POST[ 'variation' . Suppliers::SUPPLIER_SKU_META_KEY ][ $this->loop ] ) ? $_POST[ 'variation' . Suppliers::SUPPLIER_SKU_META_KEY ][ $this->loop ] : NULL; } else { $this->product_data['supplier_id'] = isset( $_POST[ Suppliers::SUPPLIER_META_KEY ] ) ? $_POST[ Suppliers::SUPPLIER_META_KEY ] : NULL; $this->product_data['supplier_sku'] = isset( $_POST[ Suppliers::SUPPLIER_SKU_META_KEY ] ) ? $_POST[ Suppliers::SUPPLIER_SKU_META_KEY ] : NULL; } } /** * Save extra product data. * * @since 1.5.8 */ private function save_extra_data() { // If it's out of stock, how many days? $this->product_data['out_stock_days'] = Helpers::get_product_out_stock_days( $this->product ); // Has location terms assigned? $location_terms = wp_get_post_terms( $this->product->get_id(), Globals::PRODUCT_LOCATION_TAXONOMY ); $this->product_data['has_location'] = ! empty( $location_terms ); } /** * Hook callback after saving a product * * @since 1.5.0 * * @param int $product_id The saved product's ID. * @param \WP_Post $post The saved post. */ public function save_product_meta_boxes( $product_id, $post ) { if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! isset( $_POST['product-type'] ) ) { return; } // Disable cache to avoid saving the wrong data. $was_cache_disabled = AtumCache::is_cache_disabled(); if ( ! $was_cache_disabled ) { AtumCache::disable_cache(); } do_action( 'atum/product_data/before_save_product_meta_boxes', $product_id, $post ); $this->product = Helpers::get_atum_product( $product_id ); $this->is_variation = FALSE; $this->loop = NULL; $this->save_atum_meta_boxes(); do_action( 'atum/product_data/after_save_product_meta_boxes', $product_id, $post ); if ( ! $was_cache_disabled ) { AtumCache::enable_cache(); } } /** * Hook callback after saving a variation product * * @since 1.5.0 * * @param int $variation_id * @param int $loop */ public function save_product_variation_meta_boxes( $variation_id, $loop ) { // Disable cache to avoid saving the wrong data. $was_cache_disabled = AtumCache::is_cache_disabled(); if ( ! $was_cache_disabled ) { AtumCache::disable_cache(); } do_action( 'atum/product_data/before_save_product_variation_meta_boxes', $variation_id, $loop ); $this->product = Helpers::get_atum_product( $variation_id ); $this->product->set_object_read( TRUE ); $this->is_variation = TRUE; $this->loop = $loop; $this->save_atum_meta_boxes(); do_action( 'atum/product_data/after_save_product_variation_meta_boxes', $variation_id, $loop ); if ( ! $was_cache_disabled ) { AtumCache::enable_cache(); } } /** * Save all the collected data for the ATUM meta boxes at once * * @since 1.5.0 */ public function save_atum_meta_boxes() { $this->save_product_data_panel(); if ( 'yes' === Helpers::get_option( 'out_stock_threshold', 'no' ) ) { $this->save_out_stock_threshold_field(); } if ( AtumCapabilities::current_user_can( 'read_supplier' ) ) { $this->save_product_supplier_fields(); } if ( $this->purchase_price_allowed ) { $purchase_price = $this->save_purchase_price(); } // Save extra data (out of stock date, has_location, etc). $this->save_extra_data(); $this->product_data = (array) apply_filters( 'atum/product_data/data_to_save', $this->product_data ); if ( ! empty( $this->product_data ) ) { $errors = $this->product->set_props( $this->product_data ); if ( is_wp_error( $errors ) ) { \WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() ); } /* @noinspection PhpUndefinedMethodInspection */ $this->product->save_atum_data(); if ( isset( $purchase_price ) && $purchase_price['new_purchase_price'] !== $purchase_price['old_purchase_price'] ) { do_action( 'atum/product_data/after_save_purchase_price', $this->product->get_id(), $purchase_price['new_purchase_price'], $purchase_price['old_purchase_price'] ); } do_action( 'atum/product_data/after_save_data', $this->product_data, $this->product ); } } /******************** * Instance methods ********************/ /** * Cannot be cloned */ public function __clone() { _doing_it_wrong( __FUNCTION__, esc_attr__( 'Cheatin’ huh?', ATUM_TEXT_DOMAIN ), '1.0.0' ); } /** * Cannot be serialized */ public function __sleep() { _doing_it_wrong( __FUNCTION__, esc_attr__( 'Cheatin’ huh?', ATUM_TEXT_DOMAIN ), '1.0.0' ); } /** * Get Singleton instance * * @return ProductDataMetaBoxes instance */ public static function get_instance() { if ( ! ( self::$instance && is_a( self::$instance, __CLASS__ ) ) ) { self::$instance = new self(); } return self::$instance; } }