post_type ) { return $actions; } // Add Class to Delete Permanently link in row actions. if ( empty( $the_product ) || $the_product->get_id() !== $post->ID ) { $the_product = wc_get_product( $post ); } if ( 'publish' === $post->post_status && $the_product && 0 < $the_product->get_total_sales() ) { $actions['trash'] = sprintf( '%s', get_delete_post_link( $the_product->get_id(), '', false ), /* translators: %s: post title */ esc_attr( sprintf( __( 'Move “%s” to the Trash', 'woocommerce' ), $the_product->get_name() ) ), esc_html__( 'Trash', 'woocommerce' ) ); } $actions['duplicate'] = '' . esc_html__( 'Duplicate', 'woocommerce' ) . ''; return $actions; } /** * Show the dupe product link in admin. */ public function dupe_button() { global $post; if ( ! current_user_can( apply_filters( 'woocommerce_duplicate_product_capability', 'manage_woocommerce' ) ) ) { return; } if ( ! is_object( $post ) ) { return; } if ( 'product' !== $post->post_type ) { return; } $notify_url = wp_nonce_url( admin_url( 'edit.php?post_type=product&action=duplicate_product&post=' . absint( $post->ID ) ), 'woocommerce-duplicate-product_' . $post->ID ); ?>
product_duplicate( $product ); // Hook rename to match other woocommerce_product_* hooks, and to move away from depending on a response from the wp_posts table. do_action( 'woocommerce_product_duplicate', $duplicate, $product ); wc_do_deprecated_action( 'woocommerce_duplicate_product', array( $duplicate->get_id(), $this->get_product_to_duplicate( $product_id ) ), '3.0', 'Use woocommerce_product_duplicate action instead.' ); // Redirect to the edit screen for the new draft page. wp_redirect( admin_url( 'post.php?action=edit&post=' . $duplicate->get_id() ) ); exit; } /** * Function to create the duplicate of the product. * * @param WC_Product $product The product to duplicate. * @return WC_Product The duplicate. */ public function product_duplicate( $product ) { /** * Filter to allow us to exclude meta keys from product duplication.. * * @param array $exclude_meta The keys to exclude from the duplicate. * @param array $existing_meta_keys The meta keys that the product already has. * @since 2.6 */ $meta_to_exclude = array_filter( apply_filters( 'woocommerce_duplicate_product_exclude_meta', array(), array_map( function ( $datum ) { return $datum->key; }, $product->get_meta_data() ) ) ); $duplicate = clone $product; $duplicate->set_id( 0 ); /* translators: %s contains the name of the original product. */ $duplicate->set_name( sprintf( esc_html__( '%s (Copy)', 'woocommerce' ), $duplicate->get_name() ) ); $duplicate->set_total_sales( 0 ); if ( '' !== $product->get_sku( 'edit' ) ) { $duplicate->set_sku( wc_product_generate_unique_sku( 0, $product->get_sku( 'edit' ) ) ); } $duplicate->set_status( 'draft' ); $duplicate->set_date_created( null ); $duplicate->set_slug( '' ); $duplicate->set_rating_counts( 0 ); $duplicate->set_average_rating( 0 ); $duplicate->set_review_count( 0 ); foreach ( $meta_to_exclude as $meta_key ) { $duplicate->delete_meta_data( $meta_key ); } /** * This action can be used to modify the object further before it is created - it will be passed by reference. * * @since 3.0 */ do_action( 'woocommerce_product_duplicate_before_save', $duplicate, $product ); // Save parent product. $duplicate->save(); // Duplicate children of a variable product. if ( ! apply_filters( 'woocommerce_duplicate_product_exclude_children', false, $product ) && $product->is_type( 'variable' ) ) { foreach ( $product->get_children() as $child_id ) { $child = wc_get_product( $child_id ); $child_duplicate = clone $child; $child_duplicate->set_parent_id( $duplicate->get_id() ); $child_duplicate->set_id( 0 ); $child_duplicate->set_date_created( null ); // If we wait and let the insertion generate the slug, we will see extreme performance degradation // in the case where a product is used as a template. Every time the template is duplicated, each // variation will query every consecutive slug until it finds an empty one. To avoid this, we can // optimize the generation ourselves, avoiding the issue altogether. $this->generate_unique_slug( $child_duplicate ); if ( '' !== $child->get_sku( 'edit' ) ) { $child_duplicate->set_sku( wc_product_generate_unique_sku( 0, $child->get_sku( 'edit' ) ) ); } foreach ( $meta_to_exclude as $meta_key ) { $child_duplicate->delete_meta_data( $meta_key ); } /** * This action can be used to modify the object further before it is created - it will be passed by reference. * * @since 3.0 */ do_action( 'woocommerce_product_duplicate_before_save', $child_duplicate, $child ); $child_duplicate->save(); } // Get new object to reflect new children. $duplicate = wc_get_product( $duplicate->get_id() ); } return $duplicate; } /** * Get a product from the database to duplicate. * * @deprecated 3.0.0 * @param mixed $id The ID of the product to duplicate. * @return object|bool * @see duplicate_product */ private function get_product_to_duplicate( $id ) { global $wpdb; $id = absint( $id ); if ( ! $id ) { return false; } $post = $wpdb->get_row( $wpdb->prepare( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID = %d", $id ) ); if ( isset( $post->post_type ) && 'revision' === $post->post_type ) { $id = $post->post_parent; $post = $wpdb->get_row( $wpdb->prepare( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID = %d", $id ) ); } return $post; } /** * Generates a unique slug for a given product. We do this so that we can override the * behavior of wp_unique_post_slug(). The normal slug generation will run single * select queries on every non-unique slug, resulting in very bad performance. * * @param WC_Product $product The product to generate a slug for. * @since 3.9.0 */ private function generate_unique_slug( $product ) { global $wpdb; // We want to remove the suffix from the slug so that we can find the maximum suffix using this root slug. // This will allow us to find the next-highest suffix that is unique. While this does not support gap // filling, this shouldn't matter for our use-case. $root_slug = preg_replace( '/-[0-9]+$/', '', $product->get_slug() ); $results = $wpdb->get_results( $wpdb->prepare( "SELECT post_name FROM $wpdb->posts WHERE post_name LIKE %s AND post_type IN ( 'product', 'product_variation' )", $root_slug . '%' ) ); // The slug is already unique! if ( empty( $results ) ) { return; } // Find the maximum suffix so we can ensure uniqueness. $max_suffix = 1; foreach ( $results as $result ) { // Pull a numerical suffix off the slug after the last hyphen. $suffix = intval( substr( $result->post_name, strrpos( $result->post_name, '-' ) + 1 ) ); if ( $suffix > $max_suffix ) { $max_suffix = $suffix; } } $product->set_slug( $root_slug . '-' . ( $max_suffix + 1 ) ); } } return new WC_Admin_Duplicate_Product();