class-wc-admin-attributes.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. <?php
  2. /**
  3. * Attributes Page
  4. *
  5. * The attributes section lets users add custom attributes to assign to products - they can also be used in the "Filter Products by Attribute" widget.
  6. *
  7. * @package WooCommerce/Admin
  8. * @version 2.3.0
  9. */
  10. defined( 'ABSPATH' ) || exit;
  11. /**
  12. * WC_Admin_Attributes Class.
  13. */
  14. class WC_Admin_Attributes {
  15. /**
  16. * Handles output of the attributes page in admin.
  17. *
  18. * Shows the created attributes and lets you add new ones or edit existing ones.
  19. * The added attributes are stored in the database and can be used for layered navigation.
  20. */
  21. public static function output() {
  22. $result = '';
  23. $action = '';
  24. // Action to perform: add, edit, delete or none.
  25. if ( ! empty( $_POST['add_new_attribute'] ) ) {
  26. $action = 'add';
  27. } elseif ( ! empty( $_POST['save_attribute'] ) && ! empty( $_GET['edit'] ) ) {
  28. $action = 'edit';
  29. } elseif ( ! empty( $_GET['delete'] ) ) {
  30. $action = 'delete';
  31. }
  32. switch ( $action ) {
  33. case 'add':
  34. $result = self::process_add_attribute();
  35. break;
  36. case 'edit':
  37. $result = self::process_edit_attribute();
  38. break;
  39. case 'delete':
  40. $result = self::process_delete_attribute();
  41. break;
  42. }
  43. if ( is_wp_error( $result ) ) {
  44. echo '<div id="woocommerce_errors" class="error"><p>' . wp_kses_post( $result->get_error_message() ) . '</p></div>';
  45. }
  46. // Show admin interface.
  47. if ( ! empty( $_GET['edit'] ) ) {
  48. self::edit_attribute();
  49. } else {
  50. self::add_attribute();
  51. }
  52. }
  53. /**
  54. * Get and sanitize posted attribute data.
  55. *
  56. * @return array
  57. */
  58. private static function get_posted_attribute() {
  59. $attribute = array(
  60. 'attribute_label' => isset( $_POST['attribute_label'] ) ? wc_clean( stripslashes( $_POST['attribute_label'] ) ) : '',
  61. 'attribute_name' => isset( $_POST['attribute_name'] ) ? wc_sanitize_taxonomy_name( stripslashes( $_POST['attribute_name'] ) ) : '',
  62. 'attribute_type' => isset( $_POST['attribute_type'] ) ? wc_clean( $_POST['attribute_type'] ) : 'select',
  63. 'attribute_orderby' => isset( $_POST['attribute_orderby'] ) ? wc_clean( $_POST['attribute_orderby'] ) : '',
  64. 'attribute_public' => isset( $_POST['attribute_public'] ) ? 1 : 0,
  65. );
  66. if ( empty( $attribute['attribute_type'] ) ) {
  67. $attribute['attribute_type'] = 'select';
  68. }
  69. if ( empty( $attribute['attribute_label'] ) ) {
  70. $attribute['attribute_label'] = ucfirst( $attribute['attribute_name'] );
  71. }
  72. if ( empty( $attribute['attribute_name'] ) ) {
  73. $attribute['attribute_name'] = wc_sanitize_taxonomy_name( $attribute['attribute_label'] );
  74. }
  75. return $attribute;
  76. }
  77. /**
  78. * Add an attribute.
  79. *
  80. * @return bool|WP_Error
  81. */
  82. private static function process_add_attribute() {
  83. check_admin_referer( 'woocommerce-add-new_attribute' );
  84. $attribute = self::get_posted_attribute();
  85. $args = array(
  86. 'name' => $attribute['attribute_label'],
  87. 'slug' => $attribute['attribute_name'],
  88. 'type' => $attribute['attribute_type'],
  89. 'order_by' => $attribute['attribute_orderby'],
  90. 'has_archives' => $attribute['attribute_public'],
  91. );
  92. $id = wc_create_attribute( $args );
  93. if ( is_wp_error( $id ) ) {
  94. return $id;
  95. }
  96. return true;
  97. }
  98. /**
  99. * Edit an attribute.
  100. *
  101. * @return bool|WP_Error
  102. */
  103. private static function process_edit_attribute() {
  104. $attribute_id = absint( $_GET['edit'] );
  105. check_admin_referer( 'woocommerce-save-attribute_' . $attribute_id );
  106. $attribute = self::get_posted_attribute();
  107. $args = array(
  108. 'name' => $attribute['attribute_label'],
  109. 'slug' => $attribute['attribute_name'],
  110. 'type' => $attribute['attribute_type'],
  111. 'order_by' => $attribute['attribute_orderby'],
  112. 'has_archives' => $attribute['attribute_public'],
  113. );
  114. $id = wc_update_attribute( $attribute_id, $args );
  115. if ( is_wp_error( $id ) ) {
  116. return $id;
  117. }
  118. echo '<div class="updated"><p>' . __( 'Attribute updated successfully', 'woocommerce' ) . '</p><p><a href="' . esc_url( admin_url( 'edit.php?post_type=product&amp;page=product_attributes' ) ) . '">' . __( 'Back to Attributes', 'woocommerce' ) . '</a></p></div>';
  119. return true;
  120. }
  121. /**
  122. * Delete an attribute.
  123. *
  124. * @return bool
  125. */
  126. private static function process_delete_attribute() {
  127. $attribute_id = absint( $_GET['delete'] );
  128. check_admin_referer( 'woocommerce-delete-attribute_' . $attribute_id );
  129. return wc_delete_attribute( $attribute_id );
  130. }
  131. /**
  132. * Edit Attribute admin panel.
  133. *
  134. * Shows the interface for changing an attributes type between select and text.
  135. */
  136. public static function edit_attribute() {
  137. global $wpdb;
  138. $edit = absint( $_GET['edit'] );
  139. $attribute_to_edit = $wpdb->get_row( 'SELECT attribute_type, attribute_label, attribute_name, attribute_orderby, attribute_public FROM ' . $wpdb->prefix . "woocommerce_attribute_taxonomies WHERE attribute_id = '$edit'" );
  140. ?>
  141. <div class="wrap woocommerce">
  142. <h1><?php esc_html_e( 'Edit attribute', 'woocommerce' ); ?></h1>
  143. <?php
  144. if ( ! $attribute_to_edit ) {
  145. echo '<div id="woocommerce_errors" class="error"><p>' . esc_html__( 'Error: non-existing attribute ID.', 'woocommerce' ) . '</p></div>';
  146. } else {
  147. $att_type = $attribute_to_edit->attribute_type;
  148. $att_label = $attribute_to_edit->attribute_label;
  149. $att_name = $attribute_to_edit->attribute_name;
  150. $att_orderby = $attribute_to_edit->attribute_orderby;
  151. $att_public = $attribute_to_edit->attribute_public;
  152. ?>
  153. <form action="edit.php?post_type=product&amp;page=product_attributes&amp;edit=<?php echo absint( $edit ); ?>" method="post">
  154. <table class="form-table">
  155. <tbody>
  156. <?php do_action( 'woocommerce_before_edit_attribute_fields' ); ?>
  157. <tr class="form-field form-required">
  158. <th scope="row" valign="top">
  159. <label for="attribute_label"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
  160. </th>
  161. <td>
  162. <input name="attribute_label" id="attribute_label" type="text" value="<?php echo esc_attr( $att_label ); ?>" />
  163. <p class="description"><?php esc_html_e( 'Name for the attribute (shown on the front-end).', 'woocommerce' ); ?></p>
  164. </td>
  165. </tr>
  166. <tr class="form-field form-required">
  167. <th scope="row" valign="top">
  168. <label for="attribute_name"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></label>
  169. </th>
  170. <td>
  171. <input name="attribute_name" id="attribute_name" type="text" value="<?php echo esc_attr( $att_name ); ?>" maxlength="28" />
  172. <p class="description"><?php esc_html_e( 'Unique slug/reference for the attribute; must be no more than 28 characters.', 'woocommerce' ); ?></p>
  173. </td>
  174. </tr>
  175. <tr class="form-field form-required">
  176. <th scope="row" valign="top">
  177. <label for="attribute_public"><?php esc_html_e( 'Enable archives?', 'woocommerce' ); ?></label>
  178. </th>
  179. <td>
  180. <input name="attribute_public" id="attribute_public" type="checkbox" value="1" <?php checked( $att_public, 1 ); ?> />
  181. <p class="description"><?php esc_html_e( 'Enable this if you want this attribute to have product archives in your store.', 'woocommerce' ); ?></p>
  182. </td>
  183. </tr>
  184. <?php
  185. /**
  186. * Attribute types can change the way attributes are displayed on the frontend and admin.
  187. *
  188. * By Default WooCommerce only includes the `select` type. Others can be added with the
  189. * `product_attributes_type_selector` filter. If there is only the default type registered,
  190. * this setting will be hidden.
  191. */
  192. if ( wc_has_custom_attribute_types() ) {
  193. ?>
  194. <tr class="form-field form-required">
  195. <th scope="row" valign="top">
  196. <label for="attribute_type"><?php esc_html_e( 'Type', 'woocommerce' ); ?></label>
  197. </th>
  198. <td>
  199. <select name="attribute_type" id="attribute_type">
  200. <?php foreach ( wc_get_attribute_types() as $key => $value ) : ?>
  201. <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $att_type, $key ); ?>><?php echo esc_attr( $value ); ?></option>
  202. <?php endforeach; ?>
  203. <?php
  204. /**
  205. * Deprecated action in favor of product_attributes_type_selector filter.
  206. *
  207. * @todo Remove in 4.0.0
  208. * @deprecated 2.4.0
  209. */
  210. do_action( 'woocommerce_admin_attribute_types' );
  211. ?>
  212. </select>
  213. <p class="description"><?php esc_html_e( "Determines how this attribute's values are displayed.", 'woocommerce' ); ?></p>
  214. </td>
  215. </tr>
  216. <?php
  217. }
  218. ?>
  219. <tr class="form-field form-required">
  220. <th scope="row" valign="top">
  221. <label for="attribute_orderby"><?php esc_html_e( 'Default sort order', 'woocommerce' ); ?></label>
  222. </th>
  223. <td>
  224. <select name="attribute_orderby" id="attribute_orderby">
  225. <option value="menu_order" <?php selected( $att_orderby, 'menu_order' ); ?>><?php esc_html_e( 'Custom ordering', 'woocommerce' ); ?></option>
  226. <option value="name" <?php selected( $att_orderby, 'name' ); ?>><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
  227. <option value="name_num" <?php selected( $att_orderby, 'name_num' ); ?>><?php esc_html_e( 'Name (numeric)', 'woocommerce' ); ?></option>
  228. <option value="id" <?php selected( $att_orderby, 'id' ); ?>><?php esc_html_e( 'Term ID', 'woocommerce' ); ?></option>
  229. </select>
  230. <p class="description"><?php esc_html_e( 'Determines the sort order of the terms on the frontend shop product pages. If using custom ordering, you can drag and drop the terms in this attribute.', 'woocommerce' ); ?></p>
  231. </td>
  232. </tr>
  233. <?php do_action( 'woocommerce_after_edit_attribute_fields' ); ?>
  234. </tbody>
  235. </table>
  236. <p class="submit"><button type="submit" name="save_attribute" id="submit" class="button-primary" value="<?php esc_attr_e( 'Update', 'woocommerce' ); ?>"><?php esc_html_e( 'Update', 'woocommerce' ); ?></button></p>
  237. <?php wp_nonce_field( 'woocommerce-save-attribute_' . $edit ); ?>
  238. </form>
  239. <?php } ?>
  240. </div>
  241. <?php
  242. }
  243. /**
  244. * Add Attribute admin panel.
  245. *
  246. * Shows the interface for adding new attributes.
  247. */
  248. public static function add_attribute() {
  249. ?>
  250. <div class="wrap woocommerce">
  251. <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
  252. <br class="clear" />
  253. <div id="col-container">
  254. <div id="col-right">
  255. <div class="col-wrap">
  256. <table class="widefat attributes-table wp-list-table ui-sortable" style="width:100%">
  257. <thead>
  258. <tr>
  259. <th scope="col"><?php esc_html_e( 'Name', 'woocommerce' ); ?></th>
  260. <th scope="col"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></th>
  261. <?php if ( wc_has_custom_attribute_types() ) : ?>
  262. <th scope="col"><?php esc_html_e( 'Type', 'woocommerce' ); ?></th>
  263. <?php endif; ?>
  264. <th scope="col"><?php esc_html_e( 'Order by', 'woocommerce' ); ?></th>
  265. <th scope="col"><?php esc_html_e( 'Terms', 'woocommerce' ); ?></th>
  266. </tr>
  267. </thead>
  268. <tbody>
  269. <?php
  270. if ( $attribute_taxonomies = wc_get_attribute_taxonomies() ) :
  271. foreach ( $attribute_taxonomies as $tax ) :
  272. ?>
  273. <tr>
  274. <td>
  275. <strong><a href="edit-tags.php?taxonomy=<?php echo esc_html( wc_attribute_taxonomy_name( $tax->attribute_name ) ); ?>&amp;post_type=product"><?php echo esc_html( $tax->attribute_label ); ?></a></strong>
  276. <div class="row-actions"><span class="edit"><a href="<?php echo esc_url( add_query_arg( 'edit', $tax->attribute_id, 'edit.php?post_type=product&amp;page=product_attributes' ) ); ?>"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a> | </span><span class="delete"><a class="delete" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'delete', $tax->attribute_id, 'edit.php?post_type=product&amp;page=product_attributes' ), 'woocommerce-delete-attribute_' . $tax->attribute_id ) ); ?>"><?php esc_html_e( 'Delete', 'woocommerce' ); ?></a></span></div>
  277. </td>
  278. <td><?php echo esc_html( $tax->attribute_name ); ?></td>
  279. <?php if ( wc_has_custom_attribute_types() ) : ?>
  280. <td><?php echo esc_html( wc_get_attribute_type_label( $tax->attribute_type ) ); ?> <?php echo $tax->attribute_public ? esc_html__( '(Public)', 'woocommerce' ) : ''; ?></td>
  281. <?php endif; ?>
  282. <td>
  283. <?php
  284. switch ( $tax->attribute_orderby ) {
  285. case 'name':
  286. esc_html_e( 'Name', 'woocommerce' );
  287. break;
  288. case 'name_num':
  289. esc_html_e( 'Name (numeric)', 'woocommerce' );
  290. break;
  291. case 'id':
  292. esc_html_e( 'Term ID', 'woocommerce' );
  293. break;
  294. default:
  295. esc_html_e( 'Custom ordering', 'woocommerce' );
  296. break;
  297. }
  298. ?>
  299. </td>
  300. <td class="attribute-terms">
  301. <?php
  302. $taxonomy = wc_attribute_taxonomy_name( $tax->attribute_name );
  303. if ( taxonomy_exists( $taxonomy ) ) {
  304. if ( 'menu_order' === wc_attribute_orderby( $taxonomy ) ) {
  305. $terms = get_terms( $taxonomy, 'hide_empty=0&menu_order=ASC' );
  306. } else {
  307. $terms = get_terms( $taxonomy, 'hide_empty=0&menu_order=false' );
  308. }
  309. switch ( $tax->attribute_orderby ) {
  310. case 'name_num':
  311. usort( $terms, '_wc_get_product_terms_name_num_usort_callback' );
  312. break;
  313. case 'parent':
  314. usort( $terms, '_wc_get_product_terms_parent_usort_callback' );
  315. break;
  316. }
  317. $terms_string = implode( ', ', wp_list_pluck( $terms, 'name' ) );
  318. if ( $terms_string ) {
  319. echo esc_html( $terms_string );
  320. } else {
  321. echo '<span class="na">&ndash;</span>';
  322. }
  323. } else {
  324. echo '<span class="na">&ndash;</span>';
  325. }
  326. ?>
  327. <br /><a href="edit-tags.php?taxonomy=<?php echo esc_html( wc_attribute_taxonomy_name( $tax->attribute_name ) ); ?>&amp;post_type=product" class="configure-terms"><?php esc_html_e( 'Configure terms', 'woocommerce' ); ?></a>
  328. </td>
  329. </tr>
  330. <?php
  331. endforeach;
  332. else :
  333. ?>
  334. <tr>
  335. <td colspan="6"><?php esc_html_e( 'No attributes currently exist.', 'woocommerce' ); ?></td>
  336. </tr>
  337. <?php
  338. endif;
  339. ?>
  340. </tbody>
  341. </table>
  342. </div>
  343. </div>
  344. <div id="col-left">
  345. <div class="col-wrap">
  346. <div class="form-wrap">
  347. <h2><?php esc_html_e( 'Add new attribute', 'woocommerce' ); ?></h2>
  348. <p><?php esc_html_e( 'Attributes let you define extra product data, such as size or color. You can use these attributes in the shop sidebar using the "layered nav" widgets.', 'woocommerce' ); ?></p>
  349. <form action="edit.php?post_type=product&amp;page=product_attributes" method="post">
  350. <?php do_action( 'woocommerce_before_add_attribute_fields' ); ?>
  351. <div class="form-field">
  352. <label for="attribute_label"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
  353. <input name="attribute_label" id="attribute_label" type="text" value="" />
  354. <p class="description"><?php esc_html_e( 'Name for the attribute (shown on the front-end).', 'woocommerce' ); ?></p>
  355. </div>
  356. <div class="form-field">
  357. <label for="attribute_name"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></label>
  358. <input name="attribute_name" id="attribute_name" type="text" value="" maxlength="28" />
  359. <p class="description"><?php esc_html_e( 'Unique slug/reference for the attribute; must be no more than 28 characters.', 'woocommerce' ); ?></p>
  360. </div>
  361. <div class="form-field">
  362. <label for="attribute_public"><input name="attribute_public" id="attribute_public" type="checkbox" value="1" /> <?php esc_html_e( 'Enable Archives?', 'woocommerce' ); ?></label>
  363. <p class="description"><?php esc_html_e( 'Enable this if you want this attribute to have product archives in your store.', 'woocommerce' ); ?></p>
  364. </div>
  365. <?php
  366. /**
  367. * Attribute types can change the way attributes are displayed on the frontend and admin.
  368. *
  369. * By Default WooCommerce only includes the `select` type. Others can be added with the
  370. * `product_attributes_type_selector` filter. If there is only the default type registered,
  371. * this setting will be hidden.
  372. */
  373. if ( wc_has_custom_attribute_types() ) {
  374. ?>
  375. <div class="form-field">
  376. <label for="attribute_type"><?php esc_html_e( 'Type', 'woocommerce' ); ?></label>
  377. <select name="attribute_type" id="attribute_type">
  378. <?php foreach ( wc_get_attribute_types() as $key => $value ) : ?>
  379. <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_attr( $value ); ?></option>
  380. <?php endforeach; ?>
  381. <?php
  382. /**
  383. * Deprecated action in favor of product_attributes_type_selector filter.
  384. *
  385. * @todo Remove in 4.0.0
  386. * @deprecated 2.4.0
  387. */
  388. do_action( 'woocommerce_admin_attribute_types' );
  389. ?>
  390. </select>
  391. <p class="description"><?php esc_html_e( "Determines how this attribute's values are displayed.", 'woocommerce' ); ?></p>
  392. </div>
  393. <?php
  394. }
  395. ?>
  396. <div class="form-field">
  397. <label for="attribute_orderby"><?php esc_html_e( 'Default sort order', 'woocommerce' ); ?></label>
  398. <select name="attribute_orderby" id="attribute_orderby">
  399. <option value="menu_order"><?php esc_html_e( 'Custom ordering', 'woocommerce' ); ?></option>
  400. <option value="name"><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
  401. <option value="name_num"><?php esc_html_e( 'Name (numeric)', 'woocommerce' ); ?></option>
  402. <option value="id"><?php esc_html_e( 'Term ID', 'woocommerce' ); ?></option>
  403. </select>
  404. <p class="description"><?php esc_html_e( 'Determines the sort order of the terms on the frontend shop product pages. If using custom ordering, you can drag and drop the terms in this attribute.', 'woocommerce' ); ?></p>
  405. </div>
  406. <?php do_action( 'woocommerce_after_add_attribute_fields' ); ?>
  407. <p class="submit"><button type="submit" name="add_new_attribute" id="submit" class="button button-primary" value="<?php esc_attr_e( 'Add attribute', 'woocommerce' ); ?>"><?php esc_html_e( 'Add attribute', 'woocommerce' ); ?></button></p>
  408. <?php wp_nonce_field( 'woocommerce-add-new_attribute' ); ?>
  409. </form>
  410. </div>
  411. </div>
  412. </div>
  413. </div>
  414. <script type="text/javascript">
  415. /* <![CDATA[ */
  416. jQuery( 'a.delete' ).click( function() {
  417. if ( window.confirm( '<?php esc_html_e( 'Are you sure you want to delete this attribute?', 'woocommerce' ); ?>' ) ) {
  418. return true;
  419. }
  420. return false;
  421. });
  422. /* ]]> */
  423. </script>
  424. </div>
  425. <?php
  426. }
  427. }