diff --git a/js/products.js b/js/products.js new file mode 100644 index 0000000..07cc8da --- /dev/null +++ b/js/products.js @@ -0,0 +1,48 @@ +class NineteenEightyWooProducts { + static actionSelect() { + return document.getElementById( 'bulk-action-selector-top' ); + } + + static addProductIdInputToActions() { + let actionsContainer = document.querySelector( '#posts-filter .bulkactions' ); + let actionButton = document.getElementById( 'doaction' ); + let productIdInput = document.createElement( 'input' ); + let spacerTextNode = document.createTextNode( ' ' ); + + productIdInput.setAttribute( 'type', 'text' ); + productIdInput.setAttribute( 'name', 'action_post_id' ); + productIdInput.setAttribute( 'id', 'action_post_id_input' ); + productIdInput.setAttribute( 'placeholder', 'Parent ID' ); + + actionsContainer.insertBefore( productIdInput, actionButton ); + actionsContainer.insertBefore( spacerTextNode, actionButton ); + } + + static removeProductInputFromActions() { + let element = document.getElementById( 'action_post_id_input' ); + + if ( element !== null ) { + element.remove(); + } + } + + static selectMenuEvent( e ) { + if ( e.target.value == 'convert_to_variant' ) { + NineteenEightyWooProducts.addProductIdInputToActions(); + } else { + NineteenEightyWooProducts.removeProductInputFromActions(); + } + } +} + +window.addEventListener( + 'DOMContentLoaded', + () => { + NineteenEightyWooProducts.actionSelect().addEventListener( + 'change', + ( e ) => { + NineteenEightyWooProducts.selectMenuEvent( e ); + } + ); + } +); diff --git a/src/Export/Product.php b/src/Export/Product.php index 207ba29..e83d9f6 100644 --- a/src/Export/Product.php +++ b/src/Export/Product.php @@ -424,7 +424,7 @@ public static function to_updated_product_body( if ( ProductHelper::name_sync_enabled( $wc_product ) ) { $name_props = array( - 'Description' => $wc_product->get_title(), + 'Description' => $wc_product->get_name(), ); if ( $wc_product instanceof WC_Product_Variation ) { @@ -434,16 +434,7 @@ public static function to_updated_product_body( $product_props = array_merge( $product_props, $name_props ); } - $product_dk_currency = $wc_product->get_meta( - '1984_woo_dk_dk_currency', - true, - 'edit' - ); - - if ( - ( get_woocommerce_currency() === $product_dk_currency ) && - ProductHelper::price_sync_enabled( $wc_product ) - ) { + if ( ProductHelper::price_sync_enabled( $wc_product ) ) { $price_props = array( 'CurrencyCode' => get_woocommerce_currency(), 'TaxPercent' => ProductHelper::tax_rate( $wc_product ), diff --git a/src/Helpers/Product.php b/src/Helpers/Product.php index 68538a9..bc52327 100644 --- a/src/Helpers/Product.php +++ b/src/Helpers/Product.php @@ -7,9 +7,12 @@ use NineteenEightyFour\NineteenEightyWoo\Config; use NineteenEightyFour\NineteenEightyWoo\Brick\Math\BigDecimal; use NineteenEightyFour\NineteenEightyWoo\Brick\Math\RoundingMode; +use NineteenEightyFour\NineteenEightyWoo\Hooks\WooUpdateProduct as WooUpdateProductHooks; use WC_Product; +use WC_Product_Variation; use WC_Tax; use WC_DateTime; +use WP_Post; /** * The Product Helper Class @@ -28,6 +31,10 @@ class Product { * @return bool True if name sync is enabled, false if not. */ public static function name_sync_enabled( WC_Product $wc_product ): bool { + if ( $wc_product instanceof WC_Product_Variation ) { + $wc_product = wc_get_product( $wc_product->get_parent_id() ); + } + $meta_value = $wc_product->get_meta( '1984_woo_dk_name_sync', true, @@ -55,6 +62,26 @@ public static function name_sync_enabled( WC_Product $wc_product ): bool { * @return bool True if price sync is enabled, false if not. */ public static function price_sync_enabled( WC_Product $wc_product ): bool { + if ( $wc_product instanceof WC_Product_Variation ) { + $parent = wc_get_product( $wc_product->get_parent_id() ); + if ( $parent ) { + $wc_product = $parent; + } + } + + $product_dk_currency = $wc_product->get_meta( + '1984_woo_dk_dk_currency', + true, + 'edit' + ); + + if ( + ( ! empty( $product_dk_currency ) ) && + ( get_woocommerce_currency() !== $product_dk_currency ) + ) { + return false; + } + $meta_value = $wc_product->get_meta( '1984_woo_dk_price_sync', true, @@ -84,6 +111,13 @@ public static function price_sync_enabled( WC_Product $wc_product ): bool { public static function quantity_sync_enabled( WC_Product $wc_product ): bool { + if ( $wc_product instanceof WC_Product_Variation ) { + $parent = wc_get_product( $wc_product->get_parent_id() ); + if ( $parent ) { + $wc_product = $parent; + } + } + $meta_value = $wc_product->get_meta( '1984_woo_dk_stock_sync', true, @@ -248,4 +282,39 @@ public static function get_ledger_codes( WC_Product $wc_product ): false|object } return false; } + + /** + * Convert a product to variant + * + * @param int $product_id The product ID. + * @param int $parent_id The ID of the new variant's parent. + * + * @return bool True on success, false on failure. + */ + public static function convert_to_variant( + int $product_id, + int $parent_id + ): bool { + if ( 'product' !== get_post_type( $product_id ) ) { + return false; + } + + set_post_type( $product_id, 'product_variation' ); + + $wc_product = wc_get_product( $product_id ); + $original_name = $wc_product->get_name(); + + if ( 'draft' === $wc_product->get_status( 'edit' ) ) { + $wc_product->set_status( 'private' ); + } + + $wc_product->set_parent_id( $parent_id ); + $wc_product->set_name( $original_name ); + + if ( 0 !== $wc_product->save() ) { + return true; + } + + return false; + } } diff --git a/src/Hooks/Admin.php b/src/Hooks/Admin.php index f7a68b5..3202cba 100644 --- a/src/Hooks/Admin.php +++ b/src/Hooks/Admin.php @@ -8,7 +8,9 @@ use NineteenEightyFour\NineteenEightyWoo\Export\Product; use NineteenEightyFour\NineteenEightyWoo\Export\SalesPerson; use NineteenEightyFour\NineteenEightyWoo\Export\Customer; +use NineteenEightyFour\NineteenEightyWoo\Helpers\Product as ProductHelper; use stdClass; +use WP_Screen; /** * The NineteenEightyWoo Admin class @@ -40,6 +42,118 @@ public function __construct() { array( __CLASS__, 'enqueue_styles_and_scripts' ) ); } + + add_action( + 'current_screen', + array( __CLASS__, 'enqueue_products_styles_and_scripts' ), + 10 + ); + + add_filter( + 'bulk_actions-edit-product', + array( __CLASS__, 'register_product_to_variant_bulk_action' ), + 10 + ); + + add_filter( + 'handle_bulk_actions-edit-product', + array( __CLASS__, 'handle_product_to_variant_bulk_action' ), + 10, + 3 + ); + } + + /** + * Enqueue the styles and scripts for the products screen + * + * @param WP_Screen $current_screen The current screen. In our case it + * should be the `edit-product` screen. + */ + public static function enqueue_products_styles_and_scripts( WP_Screen $current_screen ): void { + if ( 'edit-product' === $current_screen->id ) { + wp_enqueue_style( + handle: 'nineteen-eighty-woo-products', + src: plugins_url( 'style/products.css', dirname( __DIR__ ) ), + ver: self::ASSET_VERSION + ); + + wp_enqueue_script( + 'nineteen-eighty-woo', + plugins_url( 'js/products.js', dirname( __DIR__ ) ), + array( 'wp-api', 'wp-data' ), + self::ASSET_VERSION, + false, + ); + } + } + + /** + * Register the product-to-variant bulk action + * + * This enables a bulk action that changes products into variants of + * another. This is useful when fetching products from DK as there is no + * orhodox way for managing variant products in DK. + * + * @param array $bulk_actions The current bulk actions. + * + * @return array The modified array, with `convert_to_variant` added to it. + */ + public static function register_product_to_variant_bulk_action( + array $bulk_actions + ): array { + $bulk_actions['convert_to_variant'] = __( + 'Convert to Product Variant', + '1984-dk-woo' + ); + + return $bulk_actions; + } + + /** + * The handler for the product-to-variant bulk action + * + * @param string $sendback The original redirect URL. + * @param string $doaction The name of the action; in our case `convert_to_variant`. + * @param array $post_ids An array containing the IDs for the posts/products selected. + * + * @return string The URL to redirect to. + */ + public static function handle_product_to_variant_bulk_action( + string $sendback, + string $doaction, + array $post_ids + ): string { + if ( 'convert_to_variant' !== $doaction ) { + return $sendback; + } + + // Nonce check is handled by the WP Core. + // phpcs:ignore WordPress.Security.NonceVerification + if ( ! isset( $_GET['action_post_id'] ) ) { + return $sendback; + } + + $parent_id = intval( + sanitize_text_field( + // Nonce check is handled by the WP Core. + // phpcs:ignore WordPress.Security.NonceVerification + wp_unslash( $_GET['action_post_id'] ) + ) + ); + + if ( false === wc_get_product( $parent_id ) ) { + return $sendback; + } + + foreach ( $post_ids as $p ) { + ProductHelper::convert_to_variant( $p, $parent_id ); + } + + return add_query_arg( + 'bulk_emailed_posts', + count( $post_ids ), + $sendback + ); } /** diff --git a/src/Hooks/WooUpdateProduct.php b/src/Hooks/WooUpdateProduct.php index 4c34b1c..a133a99 100644 --- a/src/Hooks/WooUpdateProduct.php +++ b/src/Hooks/WooUpdateProduct.php @@ -94,7 +94,13 @@ public static function before_post_delete( int $post_id ): void { return; } - if ( 'product' === get_post_type( $post_id ) ) { + if ( + in_array( + get_post_type( $post_id ), + array( 'product', 'product_variation' ), + true + ) + ) { $wc_product = wc_get_product( $post_id ); if ( ! ProductHelper::should_sync( $wc_product ) ) { @@ -126,11 +132,18 @@ public static function post_status_change( return; } - if ( 'product' !== get_post_type( $post ) ) { + if ( + false === + in_array( + get_post_type( $post ), + array( 'product', 'product_variation' ), + true + ) + ) { return; } - $wc_product = wc_get_product( $post ); + $wc_product = wc_get_product( $post->ID ); if ( ! ProductHelper::should_sync( $wc_product ) ) { return; @@ -140,6 +153,9 @@ public static function post_status_change( case 'draft': ExportProduct::hide_from_webshop_in_dk( $wc_product ); break; + case 'private': + ExportProduct::hide_from_webshop_in_dk( $wc_product ); + break; case 'publish': ExportProduct::show_in_webshop_in_dk( $wc_product ); } diff --git a/src/Import/Products.php b/src/Import/Products.php index 8624bac..a27177b 100644 --- a/src/Import/Products.php +++ b/src/Import/Products.php @@ -16,6 +16,7 @@ use WC_Product; use WP_Error; use WC_Tax; +use WC_Product_Variation; /** * The Products import class @@ -329,7 +330,11 @@ public static function update_product_from_json( if ( true === $json_object->ShowItemInWebShop ) { $wc_product->set_status( 'Publish' ); } else { - $wc_product->set_status( 'Draft' ); + if ( $wc_product instanceof WC_Product_Variation ) { + $wc_product->set_status( 'Private' ); + } else { + $wc_product->set_status( 'Draft' ); + } } if ( diff --git a/style/products.css b/style/products.css new file mode 100644 index 0000000..ff3e8b7 --- /dev/null +++ b/style/products.css @@ -0,0 +1,3 @@ +input#action_post_id_input { + width: 7em; +} diff --git a/views/admin.php b/views/admin.php index 635f851..8e589d9 100644 --- a/views/admin.php +++ b/views/admin.php @@ -122,7 +122,7 @@ class="regular-text api-key-input"

- +