class-wc-admin-taxonomies.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. <?php
  2. /**
  3. * Handles taxonomies in admin
  4. *
  5. * @class WC_Admin_Taxonomies
  6. * @version 2.3.10
  7. * @package WooCommerce/Admin
  8. * @category Class
  9. * @author WooThemes
  10. */
  11. if ( ! defined( 'ABSPATH' ) ) {
  12. exit; // Exit if accessed directly
  13. }
  14. /**
  15. * WC_Admin_Taxonomies class.
  16. */
  17. class WC_Admin_Taxonomies {
  18. /**
  19. * Constructor.
  20. */
  21. public function __construct() {
  22. // Category/term ordering
  23. add_action( 'create_term', array( $this, 'create_term' ), 5, 3 );
  24. add_action( 'delete_term', array( $this, 'delete_term' ), 5 );
  25. // Add form
  26. add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) );
  27. add_action( 'product_cat_edit_form_fields', array( $this, 'edit_category_fields' ), 10 );
  28. add_action( 'created_term', array( $this, 'save_category_fields' ), 10, 3 );
  29. add_action( 'edit_term', array( $this, 'save_category_fields' ), 10, 3 );
  30. // Add columns
  31. add_filter( 'manage_edit-product_cat_columns', array( $this, 'product_cat_columns' ) );
  32. add_filter( 'manage_product_cat_custom_column', array( $this, 'product_cat_column' ), 10, 3 );
  33. // Add row actions.
  34. add_filter( 'product_cat_row_actions', array( $this, 'product_cat_row_actions' ), 10, 2 );
  35. add_filter( 'admin_init', array( $this, 'handle_product_cat_row_actions' ) );
  36. // Taxonomy page descriptions
  37. add_action( 'product_cat_pre_add_form', array( $this, 'product_cat_description' ) );
  38. add_action( 'after-product_cat-table', array( $this, 'product_cat_notes' ) );
  39. $attribute_taxonomies = wc_get_attribute_taxonomies();
  40. if ( ! empty( $attribute_taxonomies ) ) {
  41. foreach ( $attribute_taxonomies as $attribute ) {
  42. add_action( 'pa_' . $attribute->attribute_name . '_pre_add_form', array( $this, 'product_attribute_description' ) );
  43. }
  44. }
  45. // Maintain hierarchy of terms
  46. add_filter( 'wp_terms_checklist_args', array( $this, 'disable_checked_ontop' ) );
  47. }
  48. /**
  49. * Order term when created (put in position 0).
  50. *
  51. * @param mixed $term_id
  52. * @param mixed $tt_id
  53. * @param string $taxonomy
  54. */
  55. public function create_term( $term_id, $tt_id = '', $taxonomy = '' ) {
  56. if ( 'product_cat' != $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
  57. return;
  58. }
  59. $meta_name = taxonomy_is_product_attribute( $taxonomy ) ? 'order_' . esc_attr( $taxonomy ) : 'order';
  60. update_woocommerce_term_meta( $term_id, $meta_name, 0 );
  61. }
  62. /**
  63. * When a term is deleted, delete its meta.
  64. *
  65. * @param mixed $term_id
  66. */
  67. public function delete_term( $term_id ) {
  68. global $wpdb;
  69. $term_id = absint( $term_id );
  70. if ( $term_id && get_option( 'db_version' ) < 34370 ) {
  71. $wpdb->delete( $wpdb->woocommerce_termmeta, array( 'woocommerce_term_id' => $term_id ), array( '%d' ) );
  72. }
  73. }
  74. /**
  75. * Category thumbnail fields.
  76. */
  77. public function add_category_fields() {
  78. ?>
  79. <div class="form-field term-display-type-wrap">
  80. <label for="display_type"><?php _e( 'Display type', 'woocommerce' ); ?></label>
  81. <select id="display_type" name="display_type" class="postform">
  82. <option value=""><?php _e( 'Default', 'woocommerce' ); ?></option>
  83. <option value="products"><?php _e( 'Products', 'woocommerce' ); ?></option>
  84. <option value="subcategories"><?php _e( 'Subcategories', 'woocommerce' ); ?></option>
  85. <option value="both"><?php _e( 'Both', 'woocommerce' ); ?></option>
  86. </select>
  87. </div>
  88. <div class="form-field term-thumbnail-wrap">
  89. <label><?php _e( 'Thumbnail', 'woocommerce' ); ?></label>
  90. <div id="product_cat_thumbnail" style="float: left; margin-right: 10px;"><img src="<?php echo esc_url( wc_placeholder_img_src() ); ?>" width="60px" height="60px" /></div>
  91. <div style="line-height: 60px;">
  92. <input type="hidden" id="product_cat_thumbnail_id" name="product_cat_thumbnail_id" />
  93. <button type="button" class="upload_image_button button"><?php _e( 'Upload/Add image', 'woocommerce' ); ?></button>
  94. <button type="button" class="remove_image_button button"><?php _e( 'Remove image', 'woocommerce' ); ?></button>
  95. </div>
  96. <script type="text/javascript">
  97. // Only show the "remove image" button when needed
  98. if ( ! jQuery( '#product_cat_thumbnail_id' ).val() ) {
  99. jQuery( '.remove_image_button' ).hide();
  100. }
  101. // Uploading files
  102. var file_frame;
  103. jQuery( document ).on( 'click', '.upload_image_button', function( event ) {
  104. event.preventDefault();
  105. // If the media frame already exists, reopen it.
  106. if ( file_frame ) {
  107. file_frame.open();
  108. return;
  109. }
  110. // Create the media frame.
  111. file_frame = wp.media.frames.downloadable_file = wp.media({
  112. title: '<?php _e( 'Choose an image', 'woocommerce' ); ?>',
  113. button: {
  114. text: '<?php _e( 'Use image', 'woocommerce' ); ?>'
  115. },
  116. multiple: false
  117. });
  118. // When an image is selected, run a callback.
  119. file_frame.on( 'select', function() {
  120. var attachment = file_frame.state().get( 'selection' ).first().toJSON();
  121. var attachment_thumbnail = attachment.sizes.thumbnail || attachment.sizes.full;
  122. jQuery( '#product_cat_thumbnail_id' ).val( attachment.id );
  123. jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', attachment_thumbnail.url );
  124. jQuery( '.remove_image_button' ).show();
  125. });
  126. // Finally, open the modal.
  127. file_frame.open();
  128. });
  129. jQuery( document ).on( 'click', '.remove_image_button', function() {
  130. jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>' );
  131. jQuery( '#product_cat_thumbnail_id' ).val( '' );
  132. jQuery( '.remove_image_button' ).hide();
  133. return false;
  134. });
  135. jQuery( document ).ajaxComplete( function( event, request, options ) {
  136. if ( request && 4 === request.readyState && 200 === request.status
  137. && options.data && 0 <= options.data.indexOf( 'action=add-tag' ) ) {
  138. var res = wpAjax.parseAjaxResponse( request.responseXML, 'ajax-response' );
  139. if ( ! res || res.errors ) {
  140. return;
  141. }
  142. // Clear Thumbnail fields on submit
  143. jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>' );
  144. jQuery( '#product_cat_thumbnail_id' ).val( '' );
  145. jQuery( '.remove_image_button' ).hide();
  146. // Clear Display type field on submit
  147. jQuery( '#display_type' ).val( '' );
  148. return;
  149. }
  150. } );
  151. </script>
  152. <div class="clear"></div>
  153. </div>
  154. <?php
  155. }
  156. /**
  157. * Edit category thumbnail field.
  158. *
  159. * @param mixed $term Term (category) being edited
  160. */
  161. public function edit_category_fields( $term ) {
  162. $display_type = get_woocommerce_term_meta( $term->term_id, 'display_type', true );
  163. $thumbnail_id = absint( get_woocommerce_term_meta( $term->term_id, 'thumbnail_id', true ) );
  164. if ( $thumbnail_id ) {
  165. $image = wp_get_attachment_thumb_url( $thumbnail_id );
  166. } else {
  167. $image = wc_placeholder_img_src();
  168. }
  169. ?>
  170. <tr class="form-field">
  171. <th scope="row" valign="top"><label><?php _e( 'Display type', 'woocommerce' ); ?></label></th>
  172. <td>
  173. <select id="display_type" name="display_type" class="postform">
  174. <option value="" <?php selected( '', $display_type ); ?>><?php _e( 'Default', 'woocommerce' ); ?></option>
  175. <option value="products" <?php selected( 'products', $display_type ); ?>><?php _e( 'Products', 'woocommerce' ); ?></option>
  176. <option value="subcategories" <?php selected( 'subcategories', $display_type ); ?>><?php _e( 'Subcategories', 'woocommerce' ); ?></option>
  177. <option value="both" <?php selected( 'both', $display_type ); ?>><?php _e( 'Both', 'woocommerce' ); ?></option>
  178. </select>
  179. </td>
  180. </tr>
  181. <tr class="form-field">
  182. <th scope="row" valign="top"><label><?php _e( 'Thumbnail', 'woocommerce' ); ?></label></th>
  183. <td>
  184. <div id="product_cat_thumbnail" style="float: left; margin-right: 10px;"><img src="<?php echo esc_url( $image ); ?>" width="60px" height="60px" /></div>
  185. <div style="line-height: 60px;">
  186. <input type="hidden" id="product_cat_thumbnail_id" name="product_cat_thumbnail_id" value="<?php echo $thumbnail_id; ?>" />
  187. <button type="button" class="upload_image_button button"><?php _e( 'Upload/Add image', 'woocommerce' ); ?></button>
  188. <button type="button" class="remove_image_button button"><?php _e( 'Remove image', 'woocommerce' ); ?></button>
  189. </div>
  190. <script type="text/javascript">
  191. // Only show the "remove image" button when needed
  192. if ( '0' === jQuery( '#product_cat_thumbnail_id' ).val() ) {
  193. jQuery( '.remove_image_button' ).hide();
  194. }
  195. // Uploading files
  196. var file_frame;
  197. jQuery( document ).on( 'click', '.upload_image_button', function( event ) {
  198. event.preventDefault();
  199. // If the media frame already exists, reopen it.
  200. if ( file_frame ) {
  201. file_frame.open();
  202. return;
  203. }
  204. // Create the media frame.
  205. file_frame = wp.media.frames.downloadable_file = wp.media({
  206. title: '<?php _e( 'Choose an image', 'woocommerce' ); ?>',
  207. button: {
  208. text: '<?php _e( 'Use image', 'woocommerce' ); ?>'
  209. },
  210. multiple: false
  211. });
  212. // When an image is selected, run a callback.
  213. file_frame.on( 'select', function() {
  214. var attachment = file_frame.state().get( 'selection' ).first().toJSON();
  215. var attachment_thumbnail = attachment.sizes.thumbnail || attachment.sizes.full;
  216. jQuery( '#product_cat_thumbnail_id' ).val( attachment.id );
  217. jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', attachment_thumbnail.url );
  218. jQuery( '.remove_image_button' ).show();
  219. });
  220. // Finally, open the modal.
  221. file_frame.open();
  222. });
  223. jQuery( document ).on( 'click', '.remove_image_button', function() {
  224. jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>' );
  225. jQuery( '#product_cat_thumbnail_id' ).val( '' );
  226. jQuery( '.remove_image_button' ).hide();
  227. return false;
  228. });
  229. </script>
  230. <div class="clear"></div>
  231. </td>
  232. </tr>
  233. <?php
  234. }
  235. /**
  236. * save_category_fields function.
  237. *
  238. * @param mixed $term_id Term ID being saved
  239. * @param mixed $tt_id
  240. * @param string $taxonomy
  241. */
  242. public function save_category_fields( $term_id, $tt_id = '', $taxonomy = '' ) {
  243. if ( isset( $_POST['display_type'] ) && 'product_cat' === $taxonomy ) {
  244. update_woocommerce_term_meta( $term_id, 'display_type', esc_attr( $_POST['display_type'] ) );
  245. }
  246. if ( isset( $_POST['product_cat_thumbnail_id'] ) && 'product_cat' === $taxonomy ) {
  247. update_woocommerce_term_meta( $term_id, 'thumbnail_id', absint( $_POST['product_cat_thumbnail_id'] ) );
  248. }
  249. }
  250. /**
  251. * Description for product_cat page to aid users.
  252. */
  253. public function product_cat_description() {
  254. echo wpautop( __( 'Product categories for your store can be managed here. To change the order of categories on the front-end you can drag and drop to sort them. To see more categories listed click the "screen options" link at the top-right of this page.', 'woocommerce' ) );
  255. }
  256. /**
  257. * Add some notes to describe the behavior of the default category.
  258. */
  259. public function product_cat_notes() {
  260. $category_id = get_option( 'default_product_cat', 0 );
  261. $category = get_term( $category_id, 'product_cat' );
  262. $category_name = ( ! $category || is_wp_error( $category ) ) ? _x( 'Uncategorized', 'Default category slug', 'woocommerce' ) : $category->name;
  263. ?>
  264. <div class="form-wrap edit-term-notes">
  265. <p>
  266. <strong><?php _e( 'Note:', 'woocommerce' ); ?></strong><br>
  267. <?php
  268. printf(
  269. /* translators: %s: default category */
  270. __( 'Deleting a category does not delete the products in that category. Instead, products that were only assigned to the deleted category are set to the category %s.', 'woocommerce' ),
  271. '<strong>' . esc_html( $category_name ) . '</strong>'
  272. );
  273. ?>
  274. </p>
  275. </div>
  276. <?php
  277. }
  278. /**
  279. * Description for shipping class page to aid users.
  280. */
  281. public function product_attribute_description() {
  282. echo wpautop( __( 'Attribute terms can be assigned to products and variations.<br/><br/><b>Note</b>: Deleting a term will remove it from all products and variations to which it has been assigned. Recreating a term will not automatically assign it back to products.', 'woocommerce' ) );
  283. }
  284. /**
  285. * Thumbnail column added to category admin.
  286. *
  287. * @param mixed $columns
  288. * @return array
  289. */
  290. public function product_cat_columns( $columns ) {
  291. $new_columns = array();
  292. if ( isset( $columns['cb'] ) ) {
  293. $new_columns['cb'] = $columns['cb'];
  294. unset( $columns['cb'] );
  295. }
  296. $new_columns['thumb'] = __( 'Image', 'woocommerce' );
  297. $columns = array_merge( $new_columns, $columns );
  298. $columns['handle'] = '';
  299. return $columns;
  300. }
  301. /**
  302. * Adjust row actions.
  303. *
  304. * @param array $actions Array of actions.
  305. * @param object $term Term object.
  306. * @return array
  307. */
  308. public function product_cat_row_actions( $actions = array(), $term ) {
  309. $default_category_id = absint( get_option( 'default_product_cat', 0 ) );
  310. if ( $default_category_id !== $term->term_id && current_user_can( 'edit_term', $term->term_id ) ) {
  311. $actions['make_default'] = sprintf(
  312. '<a href="%s" aria-label="%s">%s</a>',
  313. wp_nonce_url( 'edit-tags.php?action=make_default&amp;taxonomy=product_cat&amp;tag_ID=' . absint( $term->term_id ), 'make_default_' . absint( $term->term_id ) ),
  314. /* translators: %s: taxonomy term name */
  315. esc_attr( sprintf( __( 'Make &#8220;%s&#8221; the default category', 'woocommerce' ), $term->name ) ),
  316. __( 'Make default', 'woocommerce' )
  317. );
  318. }
  319. return $actions;
  320. }
  321. /**
  322. * Handle custom row actions.
  323. */
  324. public function handle_product_cat_row_actions() {
  325. if ( isset( $_GET['action'], $_GET['tag_ID'], $_GET['_wpnonce'] ) && 'make_default' === $_GET['action'] ) {
  326. $make_default_id = absint( $_GET['tag_ID'] );
  327. if ( wp_verify_nonce( $_GET['_wpnonce'], 'make_default_' . $make_default_id ) && current_user_can( 'edit_term', $make_default_id ) ) {
  328. update_option( 'default_product_cat', $make_default_id );
  329. }
  330. }
  331. }
  332. /**
  333. * Thumbnail column value added to category admin.
  334. *
  335. * @param string $columns
  336. * @param string $column
  337. * @param int $id
  338. *
  339. * @return string
  340. */
  341. public function product_cat_column( $columns, $column, $id ) {
  342. if ( 'thumb' === $column ) {
  343. // Prepend tooltip for default category.
  344. $default_category_id = absint( get_option( 'default_product_cat', 0 ) );
  345. if ( $default_category_id === $id ) {
  346. $columns .= wc_help_tip( __( 'This is the default category and it cannot be deleted. It will be automatically assigned to products with no category.', 'woocommerce' ) );
  347. }
  348. $thumbnail_id = get_woocommerce_term_meta( $id, 'thumbnail_id', true );
  349. if ( $thumbnail_id ) {
  350. $image = wp_get_attachment_thumb_url( $thumbnail_id );
  351. } else {
  352. $image = wc_placeholder_img_src();
  353. }
  354. // Prevent esc_url from breaking spaces in urls for image embeds. Ref: https://core.trac.wordpress.org/ticket/23605
  355. $image = str_replace( ' ', '%20', $image );
  356. $columns .= '<img src="' . esc_url( $image ) . '" alt="' . esc_attr__( 'Thumbnail', 'woocommerce' ) . '" class="wp-post-image" height="48" width="48" />';
  357. }
  358. if ( 'handle' === $column ) {
  359. $columns .= '<input type="hidden" name="term_id" value="' . esc_attr( $id ) . '" />';
  360. }
  361. return $columns;
  362. }
  363. /**
  364. * Maintain term hierarchy when editing a product.
  365. *
  366. * @param array $args
  367. * @return array
  368. */
  369. public function disable_checked_ontop( $args ) {
  370. if ( ! empty( $args['taxonomy'] ) && 'product_cat' === $args['taxonomy'] ) {
  371. $args['checked_ontop'] = false;
  372. }
  373. return $args;
  374. }
  375. }
  376. new WC_Admin_Taxonomies();