class-wc-admin-duplicate-product.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit;
  4. }
  5. /**
  6. * Duplicate product functionality
  7. *
  8. * @author WooCommerce
  9. * @category Admin
  10. * @package WooCommerce/Admin
  11. * @version 3.0.0
  12. */
  13. if ( class_exists( 'WC_Admin_Duplicate_Product', false ) ) {
  14. return new WC_Admin_Duplicate_Product();
  15. }
  16. /**
  17. * WC_Admin_Duplicate_Product Class.
  18. */
  19. class WC_Admin_Duplicate_Product {
  20. /**
  21. * Constructor.
  22. */
  23. public function __construct() {
  24. add_action( 'admin_action_duplicate_product', array( $this, 'duplicate_product_action' ) );
  25. add_filter( 'post_row_actions', array( $this, 'dupe_link' ), 10, 2 );
  26. add_action( 'post_submitbox_start', array( $this, 'dupe_button' ) );
  27. }
  28. /**
  29. * Show the "Duplicate" link in admin products list.
  30. *
  31. * @param array $actions Array of actions.
  32. * @param WP_Post $post Post object.
  33. * @return array
  34. */
  35. public function dupe_link( $actions, $post ) {
  36. global $the_product;
  37. if ( ! current_user_can( apply_filters( 'woocommerce_duplicate_product_capability', 'manage_woocommerce' ) ) ) {
  38. return $actions;
  39. }
  40. if ( 'product' !== $post->post_type ) {
  41. return $actions;
  42. }
  43. // Add Class to Delete Permanently link in row actions.
  44. if ( empty( $the_product ) || $the_product->get_id() !== $post->ID ) {
  45. $the_product = wc_get_product( $post );
  46. }
  47. if ( 'publish' === $post->post_status && $the_product && 0 < $the_product->get_total_sales() ) {
  48. $actions['trash'] = sprintf(
  49. '<a href="%s" class="submitdelete trash-product" aria-label="%s">%s</a>',
  50. get_delete_post_link( $the_product->get_id(), '', false ),
  51. /* translators: %s: post title */
  52. esc_attr( sprintf( __( 'Move &#8220;%s&#8221; to the Trash', 'woocommerce' ), $the_product->get_name() ) ),
  53. __( 'Trash', 'woocommerce' )
  54. );
  55. }
  56. $actions['duplicate'] = '<a href="' . wp_nonce_url( admin_url( 'edit.php?post_type=product&action=duplicate_product&amp;post=' . $post->ID ), 'woocommerce-duplicate-product_' . $post->ID ) . '" aria-label="' . esc_attr__( 'Make a duplicate from this product', 'woocommerce' )
  57. . '" rel="permalink">' . __( 'Duplicate', 'woocommerce' ) . '</a>';
  58. return $actions;
  59. }
  60. /**
  61. * Show the dupe product link in admin.
  62. */
  63. public function dupe_button() {
  64. global $post;
  65. if ( ! current_user_can( apply_filters( 'woocommerce_duplicate_product_capability', 'manage_woocommerce' ) ) ) {
  66. return;
  67. }
  68. if ( ! is_object( $post ) ) {
  69. return;
  70. }
  71. if ( 'product' !== $post->post_type ) {
  72. return;
  73. }
  74. if ( isset( $_GET['post'] ) ) {
  75. $notify_url = wp_nonce_url( admin_url( 'edit.php?post_type=product&action=duplicate_product&post=' . absint( $_GET['post'] ) ), 'woocommerce-duplicate-product_' . $_GET['post'] );
  76. ?>
  77. <div id="duplicate-action"><a class="submitduplicate duplication" href="<?php echo esc_url( $notify_url ); ?>"><?php _e( 'Copy to a new draft', 'woocommerce' ); ?></a></div>
  78. <?php
  79. }
  80. }
  81. /**
  82. * Duplicate a product action.
  83. */
  84. public function duplicate_product_action() {
  85. if ( empty( $_REQUEST['post'] ) ) {
  86. wp_die( __( 'No product to duplicate has been supplied!', 'woocommerce' ) );
  87. }
  88. $product_id = isset( $_REQUEST['post'] ) ? absint( $_REQUEST['post'] ) : '';
  89. check_admin_referer( 'woocommerce-duplicate-product_' . $product_id );
  90. $product = wc_get_product( $product_id );
  91. if ( false === $product ) {
  92. /* translators: %s: product id */
  93. wp_die( sprintf( __( 'Product creation failed, could not find original product: %s', 'woocommerce' ), $product_id ) );
  94. }
  95. $duplicate = $this->product_duplicate( $product );
  96. // Hook rename to match other woocommerce_product_* hooks, and to move away from depending on a response from the wp_posts table.
  97. do_action( 'woocommerce_product_duplicate', $duplicate, $product );
  98. 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.' );
  99. // Redirect to the edit screen for the new draft page
  100. wp_redirect( admin_url( 'post.php?action=edit&post=' . $duplicate->get_id() ) );
  101. exit;
  102. }
  103. /**
  104. * Function to create the duplicate of the product.
  105. *
  106. * @param WC_Product $product
  107. * @return WC_Product
  108. */
  109. public function product_duplicate( $product ) {
  110. // Filter to allow us to unset/remove data we don't want to copy to the duplicate. @since 2.6
  111. $meta_to_exclude = array_filter( apply_filters( 'woocommerce_duplicate_product_exclude_meta', array() ) );
  112. $duplicate = clone $product;
  113. $duplicate->set_id( 0 );
  114. $duplicate->set_name( sprintf( __( '%s (Copy)', 'woocommerce' ), $duplicate->get_name() ) );
  115. $duplicate->set_total_sales( 0 );
  116. if ( '' !== $product->get_sku( 'edit' ) ) {
  117. $duplicate->set_sku( wc_product_generate_unique_sku( 0, $product->get_sku( 'edit' ) ) );
  118. }
  119. $duplicate->set_status( 'draft' );
  120. $duplicate->set_date_created( null );
  121. $duplicate->set_slug( '' );
  122. $duplicate->set_rating_counts( 0 );
  123. $duplicate->set_average_rating( 0 );
  124. $duplicate->set_review_count( 0 );
  125. foreach ( $meta_to_exclude as $meta_key ) {
  126. $duplicate->delete_meta_data( $meta_key );
  127. }
  128. // This action can be used to modify the object further before it is created - it will be passed by reference. @since 3.0
  129. do_action( 'woocommerce_product_duplicate_before_save', $duplicate, $product );
  130. // Save parent product.
  131. $duplicate->save();
  132. // Duplicate children of a variable product.
  133. if ( ! apply_filters( 'woocommerce_duplicate_product_exclude_children', false, $product ) && $product->is_type( 'variable' ) ) {
  134. foreach ( $product->get_children() as $child_id ) {
  135. $child = wc_get_product( $child_id );
  136. $child_duplicate = clone $child;
  137. $child_duplicate->set_parent_id( $duplicate->get_id() );
  138. $child_duplicate->set_id( 0 );
  139. $child_duplicate->set_date_created( null );
  140. if ( '' !== $child->get_sku( 'edit' ) ) {
  141. $child_duplicate->set_sku( wc_product_generate_unique_sku( 0, $child->get_sku( 'edit' ) ) );
  142. }
  143. foreach ( $meta_to_exclude as $meta_key ) {
  144. $child_duplicate->delete_meta_data( $meta_key );
  145. }
  146. // This action can be used to modify the object further before it is created - it will be passed by reference. @since 3.0
  147. do_action( 'woocommerce_product_duplicate_before_save', $child_duplicate, $child );
  148. $child_duplicate->save();
  149. }
  150. // Get new object to reflect new children.
  151. $duplicate = wc_get_product( $duplicate->get_id() );
  152. }
  153. return $duplicate;
  154. }
  155. /**
  156. * Get a product from the database to duplicate.
  157. *
  158. * @deprecated 3.0.0
  159. * @param mixed $id
  160. * @return object|bool
  161. * @see duplicate_product
  162. */
  163. private function get_product_to_duplicate( $id ) {
  164. global $wpdb;
  165. $id = absint( $id );
  166. if ( ! $id ) {
  167. return false;
  168. }
  169. $post = $wpdb->get_row( $wpdb->prepare( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID = %d", $id ) );
  170. if ( isset( $post->post_type ) && 'revision' === $post->post_type ) {
  171. $id = $post->post_parent;
  172. $post = $wpdb->get_row( $wpdb->prepare( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID = %d", $id ) );
  173. }
  174. return $post;
  175. }
  176. }
  177. return new WC_Admin_Duplicate_Product();