PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 11.1.0
WooCommerce v11.1.0
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / assets / js / admin / meta-boxes-product.js
woocommerce / assets / js / admin Last commit date
api-keys.js 5 years ago api-keys.min.js 3 years ago backbone-modal.js 2 weeks ago backbone-modal.min.js 2 weeks ago marketplace-suggestions.js 1 year ago marketplace-suggestions.min.js 1 year ago meta-boxes-coupon.js 1 year ago meta-boxes-coupon.min.js 1 year ago meta-boxes-order.js 2 weeks ago meta-boxes-order.min.js 2 weeks ago meta-boxes-product-variation.js 2 weeks ago meta-boxes-product-variation.min.js 2 weeks ago meta-boxes-product.js 2 weeks ago meta-boxes-product.min.js 2 weeks ago meta-boxes.js 6 months ago meta-boxes.min.js 6 months ago network-orders.js 8 years ago network-orders.min.js 3 years ago order-attribution-admin.js 2 years ago order-attribution-admin.min.js 2 years ago product-editor.js 1 year ago product-editor.min.js 1 year ago product-ordering.js 5 months ago product-ordering.min.js 5 months ago quick-edit.js 2 weeks ago quick-edit.min.js 2 weeks ago reports.js 1 year ago reports.min.js 1 year ago settings-views-html-settings-tax.js 1 month ago settings-views-html-settings-tax.min.js 1 month ago settings.js 1 year ago settings.min.js 1 year ago system-status.js 1 month ago system-status.min.js 1 month ago term-ordering.js 5 months ago term-ordering.min.js 5 months ago users.js 5 years ago users.min.js 3 years ago variation-gallery.js 2 months ago variation-gallery.min.js 2 months ago wc-admin-notices.js 2 weeks ago wc-admin-notices.min.js 2 weeks ago wc-brands-enhanced-select.js 1 year ago wc-brands-enhanced-select.min.js 1 year ago wc-clipboard.js 5 years ago wc-clipboard.min.js 5 years ago wc-customer-stock-notifications.js 1 year ago wc-customer-stock-notifications.min.js 1 year ago wc-enhanced-select.js 2 weeks ago wc-enhanced-select.min.js 2 weeks ago wc-orders.js 3 weeks ago wc-orders.min.js 3 weeks ago wc-product-export.js 1 year ago wc-product-export.min.js 1 year ago wc-product-import.js 3 years ago wc-product-import.min.js 3 years ago wc-recent-reviews-widget-async.js 6 months ago wc-recent-reviews-widget-async.min.js 6 months ago wc-setup.js 5 years ago wc-setup.min.js 3 years ago wc-shipping-classes.js 1 year ago wc-shipping-classes.min.js 1 year ago wc-shipping-providers.js 5 months ago wc-shipping-providers.min.js 5 months ago wc-shipping-zone-methods.js 2 weeks ago wc-shipping-zone-methods.min.js 2 weeks ago wc-shipping-zones.js 1 year ago wc-shipping-zones.min.js 1 year ago wc-status-widget-async.js 6 months ago wc-status-widget-async.min.js 6 months ago wc-status-widget.js 1 year ago wc-status-widget.min.js 1 year ago woocommerce_admin.js 1 month ago woocommerce_admin.min.js 1 month ago
meta-boxes-product.js
1651 lines
1 /*global woocommerce_admin_meta_boxes, _ */
2 jQuery( function ( $ ) {
3 let isPageUnloading = false;
4
5 $( window ).on( 'beforeunload', function () {
6 isPageUnloading = true;
7 } );
8
9 function appendAttributeTermOption( select, term ) {
10 if (
11 ! select ||
12 ! term ||
13 term.term_id === undefined ||
14 term.name === undefined
15 ) {
16 return;
17 }
18
19 const option = document.createElement( 'option' );
20 option.value = String( term.term_id );
21 option.selected = true;
22 option.textContent = term.name;
23
24 if ( term.visual ) {
25 option.dataset.visual = JSON.stringify( term.visual );
26 }
27
28 select.appendChild( option );
29 }
30
31 // Scroll to first checked category
32 // https://github.com/scribu/wp-category-checklist-tree/blob/d1c3c1f449e1144542efa17dde84a9f52ade1739/category-checklist-tree.php
33 $( function () {
34 $( '[id$="-all"] > ul.categorychecklist' ).each( function () {
35 var $list = $( this );
36 var $firstChecked = $list.find( ':checked' ).first();
37
38 if ( ! $firstChecked.length ) {
39 return;
40 }
41
42 var pos_first = $list.find( 'input' ).position().top;
43 var pos_checked = $firstChecked.position().top;
44
45 $list
46 .closest( '.tabs-panel' )
47 .scrollTop( pos_checked - pos_first + 5 );
48 } );
49 } );
50
51 // Prevent enter submitting post form.
52 $( '#upsell_product_data' ).on( 'keypress', function ( e ) {
53 if ( e.keyCode === 13 ) {
54 return false;
55 }
56 } );
57
58 // Type box.
59 if ( $( 'body' ).hasClass( 'wc-wp-version-gte-55' ) ) {
60 $( '.type_box' ).appendTo( '#woocommerce-product-data .hndle' );
61 } else {
62 $( '.type_box' ).appendTo( '#woocommerce-product-data .hndle span' );
63 }
64
65 $( function () {
66 var woocommerce_product_data = $( '#woocommerce-product-data' );
67
68 // Prevent inputs in meta box headings opening/closing contents.
69 woocommerce_product_data.find( '.hndle' ).off( 'click.postboxes' );
70
71 woocommerce_product_data.on( 'click', '.hndle', function ( event ) {
72 // If the user clicks on some form input inside the h3 the box should not be toggled.
73 if (
74 $( event.target ).filter( 'input, option, label, select' )
75 .length
76 ) {
77 return;
78 }
79
80 if ( woocommerce_product_data.hasClass( 'closed' ) ) {
81 woocommerce_product_data.removeClass( 'closed' );
82 } else {
83 woocommerce_product_data.addClass( 'closed' );
84 }
85 } );
86 } );
87
88 // Catalog Visibility.
89 $( '#catalog-visibility' )
90 .find( '.edit-catalog-visibility' )
91 .on( 'click', function () {
92 if ( $( '#catalog-visibility-select' ).is( ':hidden' ) ) {
93 $( '#catalog-visibility-select' ).slideDown( 'fast' );
94 $( this ).hide();
95 }
96 return false;
97 } );
98 $( '#catalog-visibility' )
99 .find( '.save-post-visibility' )
100 .on( 'click', function () {
101 $( '#catalog-visibility-select' ).slideUp( 'fast' );
102 $( '#catalog-visibility' )
103 .find( '.edit-catalog-visibility' )
104 .show();
105
106 var label = $( 'input[name=_visibility]:checked' ).attr(
107 'data-label'
108 );
109
110 if ( $( 'input[name=_featured]' ).is( ':checked' ) ) {
111 label =
112 label + ', ' + woocommerce_admin_meta_boxes.featured_label;
113 $( 'input[name=_featured]' ).attr( 'checked', 'checked' );
114 }
115
116 $( '#catalog-visibility-display' ).text( label );
117 return false;
118 } );
119 $( '#catalog-visibility' )
120 .find( '.cancel-post-visibility' )
121 .on( 'click', function () {
122 $( '#catalog-visibility-select' ).slideUp( 'fast' );
123 $( '#catalog-visibility' )
124 .find( '.edit-catalog-visibility' )
125 .show();
126
127 var current_visibility = $( '#current_visibility' ).val();
128 var current_featured = $( '#current_featured' ).val();
129
130 $( 'input[name=_visibility]' ).prop( 'checked', false );
131 $(
132 'input[name=_visibility][value=' + current_visibility + ']'
133 ).attr( 'checked', 'checked' );
134
135 var label = $( 'input[name=_visibility]:checked' ).attr(
136 'data-label'
137 );
138
139 if ( 'yes' === current_featured ) {
140 label =
141 label + ', ' + woocommerce_admin_meta_boxes.featured_label;
142 $( 'input[name=_featured]' ).attr( 'checked', 'checked' );
143 } else {
144 $( 'input[name=_featured]' ).prop( 'checked', false );
145 }
146
147 $( '#catalog-visibility-display' ).text( label );
148 return false;
149 } );
150
151 // Product type specific options.
152 $( 'select#product-type' )
153 .on( 'change', function () {
154 // Get value.
155 var select_val = $( this ).val();
156
157 if ( 'variable' === select_val ) {
158 $( 'input#_manage_stock' ).trigger( 'change' );
159 $( 'input#_downloadable' ).prop( 'checked', false );
160 $( 'input#_virtual' ).prop( 'checked', false );
161 } else if ( 'grouped' === select_val ) {
162 $( 'input#_downloadable' ).prop( 'checked', false );
163 $( 'input#_virtual' ).prop( 'checked', false );
164 $( 'input#_manage_stock' )
165 .prop( 'checked', false )
166 .trigger( 'change' );
167 $( '[name="_stock_status"]' )
168 .val( [ 'instock' ] )
169 .trigger( 'change' );
170 } else if ( 'external' === select_val ) {
171 $( 'input#_downloadable' ).prop( 'checked', false );
172 $( 'input#_virtual' ).prop( 'checked', false );
173 $( 'input#_manage_stock' )
174 .prop( 'checked', false )
175 .trigger( 'change' );
176 $( '[name="_stock_status"]' )
177 .val( [ 'instock' ] )
178 .trigger( 'change' );
179 }
180
181 const cogs_field_tip = $( '._cogs_value_field' ).find(
182 '.woocommerce-help-tip'
183 );
184 const cogs_field_tip_text =
185 'variable' === select_val
186 ? woocommerce_admin_meta_boxes.cogs_value_tooltip_variable_products
187 : woocommerce_admin_meta_boxes.cogs_value_tooltip_simple_products;
188 $( cogs_field_tip ).attr( 'aria-label', cogs_field_tip_text );
189 $( cogs_field_tip ).tipTip( {
190 attribute: 'aria-label',
191 fadeIn: 50,
192 fadeOut: 50,
193 delay: 200,
194 keepAlive: true,
195 } );
196
197 show_and_hide_panels();
198 change_product_type_tip( get_product_tip_content( select_val ) );
199
200 $( 'ul.wc-tabs li:visible' ).eq( 0 ).find( 'a' ).trigger( 'click' );
201
202 $( document.body ).trigger(
203 'woocommerce-product-type-change',
204 select_val,
205 $( this )
206 );
207 } )
208 .trigger( 'change' );
209
210 $( 'input#_downloadable' ).on( 'change', function () {
211 show_and_hide_panels();
212 } );
213
214 $( 'input#_virtual' ).on( 'change', function () {
215 show_and_hide_panels();
216
217 // If user enables virtual while on shipping tab, switch to general tab.
218 if (
219 $( this ).is( ':checked' ) &&
220 $( '.shipping_options.shipping_tab' ).hasClass( 'active' )
221 ) {
222 $( '.general_options.general_tab > a' ).trigger( 'click' );
223 }
224 } );
225
226 function change_product_type_tip( content ) {
227 $( '#tiptip_holder' ).removeAttr( 'style' );
228 $( '#tiptip_arrow' ).removeAttr( 'style' );
229 $( '.woocommerce-product-type-tip' )
230 .attr( 'tabindex', '0' )
231 .attr( 'aria-label', $( '<div />' ).html( content ).text() ) // Remove HTML tags.
232 .tipTip( {
233 attribute: 'data-tip',
234 content: content,
235 fadeIn: 50,
236 fadeOut: 50,
237 delay: 200,
238 keepAlive: true,
239 } );
240 }
241
242 function get_product_tip_content( product_type ) {
243 switch ( product_type ) {
244 case 'simple':
245 return woocommerce_admin_meta_boxes.i18n_product_simple_tip;
246 case 'grouped':
247 return woocommerce_admin_meta_boxes.i18n_product_grouped_tip;
248 case 'external':
249 return woocommerce_admin_meta_boxes.i18n_product_external_tip;
250 case 'variable':
251 return woocommerce_admin_meta_boxes.i18n_product_variable_tip;
252 default:
253 return woocommerce_admin_meta_boxes.i18n_product_other_tip;
254 }
255 }
256
257 function show_and_hide_controls( context ) {
258 var product_type = $( 'select#product-type' ).val();
259 var is_virtual = $( 'input#_virtual:checked' ).length;
260 var is_downloadable = $( 'input#_downloadable:checked' ).length;
261
262 // Hide/Show all with rules.
263 var hide_classes = '.hide_if_downloadable, .hide_if_virtual';
264 var show_classes = '.show_if_downloadable, .show_if_virtual';
265
266 $.each(
267 woocommerce_admin_meta_boxes.product_types,
268 function ( index, value ) {
269 hide_classes = hide_classes + ', .hide_if_' + value;
270 show_classes = show_classes + ', .show_if_' + value;
271 }
272 );
273
274 $( hide_classes, context ).show();
275 $( show_classes, context ).hide();
276
277 // Shows rules.
278 if ( is_downloadable ) {
279 $( '.show_if_downloadable', context ).show();
280 }
281 if ( is_virtual ) {
282 $( '.show_if_virtual', context ).show();
283 }
284
285 $( '.show_if_' + product_type, context ).show();
286
287 // Hide rules.
288 if ( is_downloadable ) {
289 $( '.hide_if_downloadable', context ).hide();
290 }
291 if ( is_virtual ) {
292 $( '.hide_if_virtual', context ).hide();
293 }
294
295 $( '.hide_if_' + product_type, context ).hide();
296
297 // POS visibility - requires combination of type AND downloadable status.
298 var is_pos_supported =
299 ( product_type === 'simple' || product_type === 'variable' ) &&
300 ! is_downloadable;
301 if ( is_pos_supported ) {
302 $( '#pos_visibility_supported', context ).show();
303 $( '#pos_visibility_unsupported', context ).hide();
304 } else {
305 $( '#pos_visibility_supported', context ).hide();
306 $( '#pos_visibility_unsupported', context ).show();
307 }
308 }
309
310 function show_and_hide_panels() {
311 show_and_hide_controls();
312
313 $( 'input#_manage_stock' ).trigger( 'change' );
314
315 // Hide empty panels/tabs after display.
316 $( '.woocommerce_options_panel' ).each( function () {
317 var $children = $( this ).children( '.options_group' );
318
319 if ( 0 === $children.length ) {
320 return;
321 }
322
323 var $invisible = $children.filter( function () {
324 return 'none' === $( this ).css( 'display' );
325 } );
326
327 // Hide panel.
328 if ( $invisible.length === $children.length ) {
329 var $id = $( this ).prop( 'id' );
330 $( '.product_data_tabs' )
331 .find( 'li a[href="#' + $id + '"]' )
332 .parent()
333 .hide();
334 }
335 } );
336 }
337
338 // Sale price schedule.
339 $( '.sale_price_dates_fields' ).each( function () {
340 var $these_sale_dates = $( this );
341 var sale_schedule_set = false;
342 var $wrap = $these_sale_dates.closest( 'div, table' );
343
344 $these_sale_dates.find( 'input' ).each( function () {
345 if ( '' !== $( this ).val() ) {
346 sale_schedule_set = true;
347 }
348 } );
349
350 if ( sale_schedule_set ) {
351 $wrap.find( '.sale_schedule' ).hide();
352 $wrap.find( '.sale_price_dates_fields' ).show();
353 } else {
354 $wrap.find( '.sale_schedule' ).show();
355 $wrap.find( '.sale_price_dates_fields' ).hide();
356 }
357 } );
358
359 $( '#woocommerce-product-data' ).on(
360 'click',
361 '.sale_schedule',
362 function () {
363 var $wrap = $( this ).closest( 'div, table' );
364
365 $( this ).hide();
366 $wrap.find( '.cancel_sale_schedule' ).show();
367 $wrap.find( '.sale_price_dates_fields' ).show();
368
369 return false;
370 }
371 );
372 $( '#woocommerce-product-data' ).on(
373 'click',
374 '.cancel_sale_schedule',
375 function () {
376 var $wrap = $( this ).closest( 'div, table' );
377
378 $( this ).hide();
379 $wrap.find( '.sale_schedule' ).show();
380 $wrap.find( '.sale_price_dates_fields' ).hide();
381 $wrap.find( '.sale_price_dates_fields' ).find( 'input' ).val( '' );
382
383 return false;
384 }
385 );
386
387 // File inputs.
388 $( '#woocommerce-product-data' ).on(
389 'click',
390 '.downloadable_files a.insert',
391 function () {
392 $( this )
393 .closest( '.downloadable_files' )
394 .find( 'tbody' )
395 .append( $( this ).data( 'row' ) );
396 return false;
397 }
398 );
399 $( '#woocommerce-product-data' ).on(
400 'click',
401 '.downloadable_files a.delete',
402 function () {
403 $( this ).closest( 'tr' ).remove();
404 return false;
405 }
406 );
407
408 // Stock options.
409 function show_or_hide_stock_management_fields(
410 isStockManagementEnabled,
411 productType
412 ) {
413 const $stockManagementFields = $( '.stock_fields' );
414 const $stockStatusField = $( '.stock_status_field' );
415
416 $stockManagementFields.toggle( isStockManagementEnabled );
417 $stockStatusField.toggle(
418 ! isStockManagementEnabled &&
419 // do not show stock status field if it should be hidden for the product type
420 ! $stockStatusField.is( '.hide_if_' + productType )
421 );
422 }
423
424 $( 'input#_manage_stock' )
425 .on( 'change', function () {
426 const isStockManagementEnabled = $( this ).is( ':checked' );
427 const productType = $( 'select#product-type' ).val();
428
429 show_or_hide_stock_management_fields(
430 isStockManagementEnabled,
431 productType
432 );
433
434 $( 'input.variable_manage_stock' ).trigger( 'change' );
435 } )
436 .trigger( 'change' );
437
438 // Date picker fields.
439 function date_picker_select( datepicker ) {
440 var option = $( datepicker ).next().is( '.hasDatepicker' )
441 ? 'minDate'
442 : 'maxDate',
443 otherDateField =
444 'minDate' === option
445 ? $( datepicker ).next()
446 : $( datepicker ).prev(),
447 date = $( datepicker ).datepicker( 'getDate' );
448
449 $( otherDateField ).datepicker( 'option', option, date );
450 $( datepicker ).trigger( 'change' );
451 }
452
453 $( '.sale_price_dates_fields' ).each( function () {
454 $( this )
455 .find( 'input' )
456 .datepicker( {
457 defaultDate: '',
458 dateFormat: 'yy-mm-dd',
459 numberOfMonths: 1,
460 showButtonPanel: true,
461 onSelect: function () {
462 date_picker_select( $( this ) );
463 },
464 } );
465 $( this )
466 .find( 'input' )
467 .each( function () {
468 date_picker_select( $( this ) );
469 } );
470 } );
471
472 // Set up attributes, if current page has the attributes list.
473 const $product_attributes = $( '.product_attributes' );
474 if ( $product_attributes.length === 1 ) {
475 // When the attributes tab is shown, add an empty attribute to be filled out by the user.
476 $( '#product_attributes' ).on( 'woocommerce_tab_shown', function () {
477 remove_blank_custom_attribute_if_no_other_attributes();
478
479 const woocommerce_attribute_items = $product_attributes
480 .find( '.woocommerce_attribute' )
481 .get();
482
483 // If the product has no attributes, add an empty attribute to be filled out by the user.
484 if ( woocommerce_attribute_items.length === 0 ) {
485 add_custom_attribute_to_list();
486 }
487 } );
488
489 const woocommerce_attribute_items = $product_attributes
490 .find( '.woocommerce_attribute' )
491 .get();
492
493 // Sort the attributes by their position.
494 woocommerce_attribute_items.sort( function ( a, b ) {
495 var compA = parseInt( $( a ).attr( 'rel' ), 10 );
496 var compB = parseInt( $( b ).attr( 'rel' ), 10 );
497 return compA < compB ? -1 : compA > compB ? 1 : 0;
498 } );
499
500 $( woocommerce_attribute_items ).each( function ( index, el ) {
501 $product_attributes.append( el );
502 } );
503 }
504
505 function update_attribute_row_indexes() {
506 $( '.product_attributes .woocommerce_attribute' ).each( function (
507 index,
508 el
509 ) {
510 $( '.attribute_position', el ).val(
511 parseInt(
512 $( el ).index(
513 '.product_attributes .woocommerce_attribute'
514 ),
515 10
516 )
517 );
518 } );
519 }
520
521 var selectedAttributes = [];
522 var currentAttributeTermCreationContext = null;
523 $( '.product_attributes .woocommerce_attribute' ).each( function (
524 index,
525 el
526 ) {
527 if (
528 $( el ).css( 'display' ) !== 'none' &&
529 $( el ).is( '.taxonomy' )
530 ) {
531 selectedAttributes.push( $( el ).data( 'taxonomy' ) );
532 $( 'select.attribute_taxonomy' )
533 .find( 'option[value="' + $( el ).data( 'taxonomy' ) + '"]' )
534 .attr( 'disabled', 'disabled' );
535 }
536
537 if (
538 'undefined' === $( el ).attr( 'data-taxonomy' ) ||
539 false === $( el ).attr( 'data-taxonomy' ) ||
540 '' === $( el ).attr( 'data-taxonomy' )
541 ) {
542 add_placeholder_to_attribute_values_field( $( el ) );
543
544 $(
545 '.woocommerce_attribute input.woocommerce_attribute_used_for_variations'
546 ).on( 'change', function () {
547 add_placeholder_to_attribute_values_field( $( el ) );
548 } );
549 }
550 } );
551 $( 'select.wc-attribute-search' ).data(
552 'disabled-items',
553 selectedAttributes
554 );
555
556 function get_new_attribute_list_item_html(
557 indexInList,
558 globalAttributeId
559 ) {
560 return new Promise( function ( resolve, reject ) {
561 $.post( {
562 url: woocommerce_admin_meta_boxes.ajax_url,
563 data: {
564 action: 'woocommerce_add_attribute',
565 product_type: $( '#product-type' ).val(),
566 taxonomy: globalAttributeId ? globalAttributeId : '',
567 i: indexInList,
568 security: woocommerce_admin_meta_boxes.add_attribute_nonce,
569 },
570 success: function ( newAttributeListItemHtml ) {
571 resolve( newAttributeListItemHtml );
572 },
573 error: function ( jqXHR, textStatus, errorThrown ) {
574 reject( { jqXHR, textStatus, errorThrown } );
575 },
576 } );
577 } );
578 }
579
580 function block_attributes_tab_container() {
581 const $attributesTabContainer = $( '#product_attributes' );
582
583 $attributesTabContainer.block( {
584 message: null,
585 overlayCSS: {
586 background: '#fff',
587 opacity: 0.6,
588 },
589 } );
590 }
591
592 function unblock_attributes_tab_container() {
593 const $attributesTabContainer = $( '#product_attributes' );
594 $attributesTabContainer.unblock();
595 }
596
597 function toggle_expansion_of_attribute_list_item( $attributeListItem ) {
598 $attributeListItem.find( 'h3' ).trigger( 'click' );
599 }
600
601 function add_placeholder_to_attribute_values_field( $attributeListItem ) {
602 var $used_for_variations_checkbox = $attributeListItem.find(
603 'input.woocommerce_attribute_used_for_variations'
604 );
605
606 if (
607 $used_for_variations_checkbox.length &&
608 $used_for_variations_checkbox.is( ':checked' )
609 ) {
610 $attributeListItem
611 .find( 'textarea' )
612 .attr(
613 'placeholder',
614 woocommerce_admin_meta_boxes.i18n_attributes_used_for_variations_placeholder
615 );
616 } else {
617 $attributeListItem
618 .find( 'textarea' )
619 .attr(
620 'placeholder',
621 woocommerce_admin_meta_boxes.i18n_attributes_default_placeholder
622 );
623 }
624 }
625
626 function init_select_controls() {
627 $( document.body ).trigger( 'wc-enhanced-select-init' );
628 }
629
630 async function add_attribute_to_list( globalAttributeId ) {
631 try {
632 block_attributes_tab_container();
633
634 const numberOfAttributesInList = $(
635 '.product_attributes .woocommerce_attribute'
636 ).length;
637 const newAttributeListItemHtml =
638 await get_new_attribute_list_item_html(
639 numberOfAttributesInList,
640 globalAttributeId
641 );
642
643 const $attributesListContainer = $(
644 '#product_attributes .product_attributes'
645 );
646
647 const $attributeListItem = $( newAttributeListItemHtml ).appendTo(
648 $attributesListContainer
649 );
650
651 show_and_hide_controls( $attributeListItem );
652
653 init_select_controls(); // make sure any new select controls in the new list item are initialized
654
655 update_attribute_row_indexes();
656
657 toggle_expansion_of_attribute_list_item( $attributeListItem );
658
659 // Conditionally change the placeholder of product-level Attributes depending on the value of the "Use for variations" checkbox.
660 if ( 'undefined' === typeof globalAttributeId ) {
661 add_placeholder_to_attribute_values_field( $attributeListItem );
662
663 $(
664 '.woocommerce_attribute input.woocommerce_attribute_used_for_variations'
665 ).on( 'change', function () {
666 add_placeholder_to_attribute_values_field(
667 $( this ).closest( '.woocommerce_attribute' )
668 );
669 } );
670 }
671
672 $( document.body ).trigger( 'woocommerce_added_attribute' );
673
674 jQuery.maybe_disable_save_button();
675 } catch ( error ) {
676 if ( isPageUnloading ) {
677 // If the page is unloading, the outstanding ajax fetch may fail in Firefox (and possible other browsers, too).
678 // We don't want to show an error message in this case, because it was caused by the user leaving the page.
679 return;
680 }
681
682 alert(
683 woocommerce_admin_meta_boxes.i18n_add_attribute_error_notice
684 );
685 throw error;
686 } finally {
687 unblock_attributes_tab_container();
688 }
689 }
690
691 function add_global_attribute_to_list( globalAttributeId ) {
692 add_attribute_to_list( globalAttributeId );
693 }
694
695 function add_custom_attribute_to_list() {
696 add_attribute_to_list();
697 }
698
699 function add_if_not_exists( arr, item ) {
700 return arr.includes( item ) ? attr : [ ...arr, item ];
701 }
702
703 function disable_in_attribute_search( selectedAttributes ) {
704 $( 'select.wc-attribute-search' ).data(
705 'disabled-items',
706 selectedAttributes
707 );
708 }
709
710 function remove_blank_custom_attribute_if_no_other_attributes() {
711 const $attributes = $( '.product_attributes .woocommerce_attribute' );
712
713 if ( $attributes.length === 1 ) {
714 const $attribute = $attributes.first();
715
716 const $attributeName = $attribute.find(
717 'input[name="attribute_names[0]"]'
718 );
719 const $attributeValue = $attribute.find(
720 'input[name="attribute_values[0]"]'
721 );
722
723 if ( ! $attributeName.val() && ! $attributeValue.val() ) {
724 $attribute.remove();
725 }
726 }
727 }
728
729 // Handle the Attributes onboarding dismissible notice.
730 // If users dismiss the notice, never show it again.
731 if ( localStorage.getItem( 'attributes-notice-dismissed' ) ) {
732 $( '#product_attributes .notice' ).hide();
733 }
734
735 $( '#product_attributes .notice.woocommerce-message button' ).on(
736 'click',
737 function ( e ) {
738 $( '#product_attributes .notice' ).hide();
739 localStorage.setItem( 'attributes-notice-dismissed', 'true' );
740 }
741 );
742
743 $( 'select.wc-attribute-search' ).on( 'select2:select', function ( e ) {
744 const attributeId = e && e.params && e.params.data && e.params.data.id;
745
746 if ( attributeId ) {
747 remove_blank_custom_attribute_if_no_other_attributes();
748
749 add_global_attribute_to_list( attributeId );
750
751 selectedAttributes = add_if_not_exists(
752 selectedAttributes,
753 attributeId
754 );
755 disable_in_attribute_search( selectedAttributes );
756 }
757
758 $( this ).val( null );
759 $( this ).trigger( 'change' );
760
761 return false;
762 } );
763
764 // Add rows.
765
766 $( 'button.add_custom_attribute' ).on( 'click', function () {
767 add_custom_attribute_to_list();
768
769 return false;
770 } );
771
772 $( '.product_attributes' ).on( 'blur', 'input.attribute_name', function () {
773 var $inputElement = $( this );
774 var text = $inputElement.val();
775 var $attribute = $inputElement
776 .closest( '.woocommerce_attribute' )
777 .find( 'strong.attribute_name' );
778 if ( text === '' ) {
779 $attribute
780 .addClass( 'placeholder' )
781 .text(
782 woocommerce_admin_meta_boxes.i18n_attribute_name_placeholder
783 );
784 } else {
785 $attribute.removeClass( 'placeholder' ).text( text );
786 }
787 } );
788
789 $( '.product_attributes' ).on(
790 'click',
791 'button.select_all_attributes',
792 function () {
793 $( '.product_attributes' ).block( {
794 message: null,
795 overlayCSS: {
796 background: '#fff',
797 opacity: 0.6,
798 },
799 } );
800
801 var $wrapper = $( this ).closest( '.woocommerce_attribute' );
802 var attribute = $wrapper.data( 'taxonomy' );
803
804 var data = {
805 action: 'woocommerce_json_search_taxonomy_terms',
806 taxonomy: attribute,
807 security: wc_enhanced_select_params.search_taxonomy_terms_nonce,
808 };
809
810 $.get(
811 woocommerce_admin_meta_boxes.ajax_url,
812 data,
813 function ( response ) {
814 if ( response.errors ) {
815 // Error.
816 window.alert( response.errors );
817 } else if ( response && response.length > 0 ) {
818 // Success.
819 response.forEach( function ( term ) {
820 const currentItem = $wrapper.find(
821 'select.attribute_values option[value="' +
822 term.term_id +
823 '"]'
824 );
825 if ( currentItem && currentItem.length > 0 ) {
826 currentItem.prop( 'selected', 'selected' );
827 } else {
828 appendAttributeTermOption(
829 $wrapper.find(
830 'select.attribute_values'
831 )[ 0 ],
832 term
833 );
834 }
835 } );
836 $wrapper
837 .find( 'select.attribute_values' )
838 .trigger( 'change' );
839 }
840
841 $( '.product_attributes' ).unblock();
842 }
843 );
844 return false;
845 }
846 );
847
848 $( '.product_attributes' ).on(
849 'click',
850 'button.select_no_attributes',
851 function () {
852 $( this )
853 .closest( 'td' )
854 .find( 'select option' )
855 .prop( 'selected', false );
856 $( this ).closest( 'td' ).find( 'select' ).trigger( 'change' );
857 return false;
858 }
859 );
860
861 $( '#product_attributes' ).on(
862 'click',
863 '.product_attributes .remove_row',
864 function () {
865 var $parent = $( this ).parent().parent();
866 var isUsedForVariations = $parent
867 .find( 'input[name^="attribute_variation"]' )
868 .is( ':visible:checked' );
869
870 if (
871 ! isUsedForVariations ||
872 window.confirm(
873 woocommerce_admin_meta_boxes.i18n_remove_used_attribute_confirmation_message
874 )
875 ) {
876 if ( $parent.is( '.taxonomy' ) ) {
877 $parent.find( 'select, input[type=text]' ).val( '' );
878 $parent.hide();
879 $( 'select.attribute_taxonomy' )
880 .find(
881 'option[value="' + $parent.data( 'taxonomy' ) + '"]'
882 )
883 .prop( 'disabled', false );
884 selectedAttributes = selectedAttributes.filter(
885 ( attr ) => attr !== $parent.data( 'taxonomy' )
886 );
887 $( 'select.wc-attribute-search' ).data(
888 'disabled-items',
889 selectedAttributes
890 );
891 } else {
892 $parent.find( 'select, input[type=text]' ).val( '' );
893 $parent.hide();
894 update_attribute_row_indexes();
895 }
896
897 $parent.remove();
898
899 window.wcTracks.recordEvent( 'product_attributes_buttons', {
900 action: 'remove_attribute',
901 } );
902
903 jQuery.maybe_disable_save_button();
904 }
905 return false;
906 }
907 );
908
909 // Attribute ordering.
910 $( '.product_attributes' ).sortable( {
911 items: '.woocommerce_attribute',
912 cursor: 'move',
913 axis: 'y',
914 handle: 'h3',
915 scrollSensitivity: 40,
916 forcePlaceholderSize: true,
917 helper: 'clone',
918 opacity: 0.65,
919 placeholder: 'wc-metabox-sortable-placeholder',
920 start: function ( event, ui ) {
921 ui.item.css( 'background-color', '#f6f6f6' );
922 },
923 stop: function ( event, ui ) {
924 ui.item.removeAttr( 'style' );
925 update_attribute_row_indexes();
926 },
927 } );
928
929 $( document.body ).on(
930 'wc_backbone_modal_loaded',
931 function ( event, target ) {
932 if ( 'wc-modal-add-attribute-term' !== target ) {
933 return;
934 }
935
936 const modal = document.querySelector(
937 '.wc-backbone-modal-add-attribute-term'
938 );
939
940 if ( ! modal ) {
941 return;
942 }
943
944 const termInput = modal.querySelector(
945 '#wc-modal-add-attribute-term-input'
946 );
947 if ( termInput ) {
948 termInput.focus();
949 }
950
951 const form = modal.querySelector( '.wc-add-attribute-term-fields' );
952 if ( form ) {
953 form.addEventListener( 'submit', ( submitEvent ) => {
954 submitEvent.preventDefault();
955
956 const submitButton = modal.querySelector( '#btn-ok' );
957 if ( submitButton && ! submitButton.disabled ) {
958 submitButton.click();
959 }
960 } );
961 }
962 }
963 );
964
965 $( document.body ).on(
966 'wc_backbone_modal_validation',
967 function ( event, target, postedData ) {
968 if ( 'wc-modal-add-attribute-term' !== target ) {
969 return;
970 }
971
972 const modal = document.querySelector(
973 '.wc-backbone-modal-add-attribute-term'
974 );
975
976 if ( ! modal ) {
977 return;
978 }
979
980 const submitButton = modal.querySelector( '#btn-ok' );
981
982 if ( ! submitButton ) {
983 return;
984 }
985
986 const termName =
987 postedData && postedData.term ? postedData.term.trim() : '';
988 const hasValue = termName.length > 0;
989
990 submitButton.disabled = ! hasValue;
991 }
992 );
993
994 $( document.body ).on(
995 'wc_backbone_modal_response',
996 function ( event, target, postedData ) {
997 if ( 'wc-modal-add-attribute-term' !== target ) {
998 return;
999 }
1000
1001 if (
1002 ! currentAttributeTermCreationContext ||
1003 ! currentAttributeTermCreationContext.wrapper ||
1004 ! currentAttributeTermCreationContext.attribute
1005 ) {
1006 $( '.product_attributes' ).unblock();
1007 return;
1008 }
1009
1010 const termName =
1011 postedData && postedData.term ? postedData.term.trim() : '';
1012
1013 if ( ! termName ) {
1014 $( '.product_attributes' ).unblock();
1015 return;
1016 }
1017
1018 const wrapper = currentAttributeTermCreationContext.wrapper;
1019 const data = {
1020 action: 'woocommerce_add_new_attribute',
1021 taxonomy: currentAttributeTermCreationContext.attribute,
1022 term: termName,
1023 security: woocommerce_admin_meta_boxes.add_attribute_nonce,
1024 };
1025
1026 if (
1027 currentAttributeTermCreationContext.isVisualAttribute &&
1028 postedData
1029 ) {
1030 if ( postedData.wc_visual_attribute_type ) {
1031 data.wc_visual_attribute_type =
1032 postedData.wc_visual_attribute_type;
1033 }
1034
1035 if ( postedData.term_color ) {
1036 data.term_color = postedData.term_color;
1037 }
1038
1039 if ( postedData.term_image ) {
1040 data.term_image = postedData.term_image;
1041 }
1042 }
1043
1044 $.post(
1045 woocommerce_admin_meta_boxes.ajax_url,
1046 data,
1047 function ( response ) {
1048 if ( response.error ) {
1049 // Error.
1050 window.alert( response.error );
1051 } else if ( response.slug ) {
1052 // Success.
1053 const select = wrapper.querySelector(
1054 'select.attribute_values'
1055 );
1056 if ( select ) {
1057 appendAttributeTermOption( select, {
1058 term_id: response.term_id,
1059 name: response.name,
1060 visual: response.visual,
1061 } );
1062
1063 // Trigger change event natively.
1064 const changeEvent = new Event( 'change', {
1065 bubbles: true,
1066 } );
1067 select.dispatchEvent( changeEvent );
1068 }
1069 }
1070
1071 $( '.product_attributes' ).unblock();
1072 currentAttributeTermCreationContext = null;
1073 }
1074 );
1075 }
1076 );
1077
1078 // Add a new attribute (via ajax).
1079 $( '.product_attributes' ).on(
1080 'click',
1081 'button.add_new_attribute',
1082 function ( event ) {
1083 // Prevent form submission but allow event propagation.
1084 event.preventDefault();
1085
1086 $( '.product_attributes' ).block( {
1087 message: null,
1088 overlayCSS: {
1089 background: '#fff',
1090 opacity: 0.6,
1091 },
1092 } );
1093
1094 const wrapper = this.closest( '.woocommerce_attribute' );
1095 const attribute = wrapper ? wrapper.dataset.taxonomy : '';
1096 const isVisualAttribute = this.dataset.isVisualAttribute === 'yes';
1097
1098 currentAttributeTermCreationContext = {
1099 wrapper,
1100 attribute,
1101 isVisualAttribute,
1102 };
1103
1104 $( this ).WCBackboneModal( {
1105 template: 'wc-modal-add-attribute-term',
1106 variable: {
1107 isVisualAttribute,
1108 },
1109 } );
1110 }
1111 );
1112
1113 $( document.body ).on(
1114 'wc_backbone_modal_before_remove',
1115 function ( event, target, postedData, submitButtonCalled ) {
1116 if ( 'wc-modal-add-attribute-term' !== target ) {
1117 return;
1118 }
1119
1120 if ( submitButtonCalled ) {
1121 return;
1122 }
1123
1124 $( '.product_attributes' ).unblock();
1125 currentAttributeTermCreationContext = null;
1126 }
1127 );
1128
1129 // Save attributes and update variations.
1130 $( '.save_attributes' ).on( 'click', function ( event ) {
1131 if ( $( this ).hasClass( 'disabled' ) ) {
1132 event.preventDefault();
1133 return;
1134 }
1135 $( '.product_attributes' ).block( {
1136 message: null,
1137 overlayCSS: {
1138 background: '#fff',
1139 opacity: 0.6,
1140 },
1141 } );
1142
1143 var original_data = $( '.product_attributes' ).find(
1144 'input, select, textarea'
1145 );
1146 var data = {
1147 post_id: woocommerce_admin_meta_boxes.post_id,
1148 product_type: $( '#product-type' ).val(),
1149 data: original_data.serialize(),
1150 action: 'woocommerce_save_attributes',
1151 security: woocommerce_admin_meta_boxes.save_attributes_nonce,
1152 };
1153
1154 $.post(
1155 woocommerce_admin_meta_boxes.ajax_url,
1156 data,
1157 function ( response ) {
1158 if ( response.error ) {
1159 // Error.
1160 window.alert( response.error );
1161 } else if ( response.data ) {
1162 // Success.
1163 $( '.product_attributes' ).html( response.data.html );
1164 $( '.product_attributes' ).unblock();
1165
1166 // Hide the 'Used for variations' checkbox if not viewing a variable product
1167 show_and_hide_panels();
1168
1169 // Make sure the dropdown is not disabled for empty value attributes.
1170 $( 'select.attribute_taxonomy' )
1171 .find( 'option' )
1172 .prop( 'disabled', false );
1173
1174 var newSelectedAttributes = [];
1175 $( '.product_attributes .woocommerce_attribute' ).each(
1176 function ( index, el ) {
1177 if (
1178 $( el ).css( 'display' ) !== 'none' &&
1179 $( el ).is( '.taxonomy' )
1180 ) {
1181 newSelectedAttributes.push(
1182 $( el ).data( 'taxonomy' )
1183 );
1184 $( 'select.attribute_taxonomy' )
1185 .find(
1186 'option[value="' +
1187 $( el ).data( 'taxonomy' ) +
1188 '"]'
1189 )
1190 .prop( 'disabled', true );
1191 }
1192 }
1193 );
1194 selectedAttributes = newSelectedAttributes;
1195 $( 'select.wc-attribute-search' ).data(
1196 'disabled-items',
1197 newSelectedAttributes
1198 );
1199
1200 // Reload variations panel.
1201 var this_page = window.location.toString();
1202 this_page = this_page.replace(
1203 'post-new.php?',
1204 'post.php?post=' +
1205 woocommerce_admin_meta_boxes.post_id +
1206 '&action=edit&'
1207 );
1208
1209 $( '#variable_product_options' ).load(
1210 this_page + ' #variable_product_options_inner',
1211 function () {
1212 $( '#variable_product_options' ).trigger(
1213 'reload'
1214 );
1215 }
1216 );
1217
1218 $( document.body ).trigger(
1219 'woocommerce_attributes_saved'
1220 );
1221 }
1222 }
1223 );
1224 } );
1225
1226 // Go to attributes tab when clicking on link in variations message
1227 $( document.body ).on(
1228 'click',
1229 '#variable_product_options .add-attributes-message a[href="#product_attributes"]',
1230 function () {
1231 $(
1232 '#woocommerce-product-data .attribute_tab a[href="#product_attributes"]'
1233 ).trigger( 'click' );
1234 return false;
1235 }
1236 );
1237
1238 // Uploading files.
1239 var downloadable_file_frame;
1240 var file_path_field;
1241
1242 $( document.body ).on( 'click', '.upload_file_button', function ( event ) {
1243 var $el = $( this );
1244
1245 file_path_field = $el.closest( 'tr' ).find( 'td.file_url input' );
1246
1247 event.preventDefault();
1248
1249 // If the media frame already exists, reopen it.
1250 if ( downloadable_file_frame ) {
1251 downloadable_file_frame.open();
1252 return;
1253 }
1254
1255 var downloadable_file_states = [
1256 // Main states.
1257 new wp.media.controller.Library( {
1258 library: wp.media.query(),
1259 multiple: true,
1260 title: $el.data( 'choose' ),
1261 priority: 20,
1262 filterable: 'uploaded',
1263 } ),
1264 ];
1265
1266 // Create the media frame.
1267 downloadable_file_frame = wp.media.frames.downloadable_file = wp.media(
1268 {
1269 // Set the title of the modal.
1270 title: $el.data( 'choose' ),
1271 library: {
1272 type: '',
1273 },
1274 button: {
1275 text: $el.data( 'update' ),
1276 },
1277 multiple: true,
1278 states: downloadable_file_states,
1279 }
1280 );
1281
1282 // When an image is selected, run a callback.
1283 downloadable_file_frame.on( 'select', function () {
1284 var file_path = '';
1285 var selection = downloadable_file_frame.state().get( 'selection' );
1286
1287 selection.map( function ( attachment ) {
1288 attachment = attachment.toJSON();
1289 if ( attachment.url ) {
1290 file_path = attachment.url;
1291 }
1292 } );
1293
1294 file_path_field.val( file_path ).trigger( 'change' );
1295 } );
1296
1297 // Set post to 0 and set our custom type.
1298 downloadable_file_frame.on( 'ready', function () {
1299 downloadable_file_frame.uploader.options.uploader.params = {
1300 type: 'downloadable_product',
1301 };
1302 } );
1303
1304 // Finally, open the modal.
1305 downloadable_file_frame.open();
1306 } );
1307
1308 // Download ordering.
1309 $( '.downloadable_files tbody' ).sortable( {
1310 items: 'tr',
1311 cursor: 'move',
1312 axis: 'y',
1313 handle: 'td.sort',
1314 scrollSensitivity: 40,
1315 forcePlaceholderSize: true,
1316 helper: 'clone',
1317 opacity: 0.65,
1318 } );
1319
1320 // Product gallery file uploads.
1321 let product_gallery_frame;
1322 const $image_gallery_ids = $( '#product_image_gallery' );
1323 const $media_gallery = $( '#product_media_gallery' );
1324 const $product_images = $( '#product_images_container' ).find(
1325 'ul.product_images'
1326 );
1327
1328 function getProductGalleryImageSrc( attachment ) {
1329 return attachment.sizes && attachment.sizes.thumbnail
1330 ? attachment.sizes.thumbnail.url
1331 : attachment.url;
1332 }
1333
1334 function addProductGalleryMediaItemActions( $item, $el ) {
1335 $item.append(
1336 $( '<ul />', {
1337 class: 'actions',
1338 } ).append(
1339 $( '<li />' ).append(
1340 $( '<a />', {
1341 href: '#',
1342 class: 'delete tips',
1343 title: $el.data( 'delete' ),
1344 'data-tip': $el.data( 'delete' ),
1345 text: $el.data( 'text' ),
1346 } )
1347 )
1348 )
1349 );
1350
1351 return $item;
1352 }
1353
1354 function createProductGalleryImageItem( attachment, $el ) {
1355 const attachmentImage = getProductGalleryImageSrc( attachment );
1356
1357 if ( ! attachment.id || ! attachmentImage ) {
1358 return null;
1359 }
1360
1361 const $item = $( '<li />', {
1362 class: 'image',
1363 } )
1364 .attr( 'data-attachment_id', attachment.id )
1365 .attr( 'data-media_type', 'image' )
1366 .attr( 'data-source_type', 'attachment' );
1367
1368 $item.append(
1369 $( '<img />', {
1370 src: attachmentImage,
1371 } )
1372 );
1373
1374 return addProductGalleryMediaItemActions( $item, $el );
1375 }
1376
1377 function createProductGalleryVideoItem( attachment, $el ) {
1378 const posterId = parseInt( attachment.poster_id, 10 ) || 0;
1379
1380 if ( ! attachment.id || ! attachment.url ) {
1381 return null;
1382 }
1383
1384 const $item = $( '<li />', {
1385 class: 'image video',
1386 } )
1387 .attr( 'data-attachment_id', attachment.id )
1388 .attr( 'data-media_type', 'video' )
1389 .attr( 'data-source_type', 'attachment' )
1390 .attr( 'data-poster_id', posterId );
1391
1392 $item.append(
1393 $( '<video />', {
1394 class: 'woocommerce-product-gallery__video-preview',
1395 src: attachment.url,
1396 preload: 'metadata',
1397 muted: 'muted',
1398 'aria-hidden': 'true',
1399 } )
1400 );
1401
1402 return addProductGalleryMediaItemActions( $item, $el );
1403 }
1404
1405 function createProductGalleryMediaItem( attachment, $el, allowVideos ) {
1406 const mediaType = attachment.type;
1407
1408 if ( 'image' === mediaType ) {
1409 return createProductGalleryImageItem( attachment, $el );
1410 }
1411
1412 if ( allowVideos && 'video' === mediaType ) {
1413 return createProductGalleryVideoItem( attachment, $el );
1414 }
1415
1416 return null;
1417 }
1418
1419 function getProductGalleryMediaItemData( $item ) {
1420 const attachmentId = parseInt( $item.attr( 'data-attachment_id' ), 10 );
1421 const mediaType = $item.attr( 'data-media_type' ) || 'image';
1422
1423 if ( ! attachmentId ) {
1424 return null;
1425 }
1426
1427 const mediaItem = {
1428 media_type: mediaType,
1429 source_type: 'attachment',
1430 id: attachmentId,
1431 };
1432
1433 if ( 'video' === mediaType ) {
1434 const posterId = parseInt( $item.attr( 'data-poster_id' ), 10 );
1435
1436 if ( posterId ) {
1437 mediaItem.poster_id = posterId;
1438 }
1439 }
1440
1441 return mediaItem;
1442 }
1443
1444 function updateProductGalleryFields() {
1445 const mediaGallery = $( '#product_images_container' )
1446 .find( 'ul li.image' )
1447 .css( 'cursor', 'default' )
1448 .map( function () {
1449 return getProductGalleryMediaItemData( $( this ) );
1450 } )
1451 .get();
1452 const attachmentIds = mediaGallery
1453 .filter( ( mediaItem ) => 'image' === mediaItem.media_type )
1454 .map( ( mediaItem ) => mediaItem.id );
1455
1456 $image_gallery_ids.val( attachmentIds.join( ',' ) );
1457 $media_gallery.val( JSON.stringify( mediaGallery ) );
1458 }
1459
1460 $( '.add_product_images' ).on( 'click', 'a', function ( event ) {
1461 const $el = $( this );
1462 const allowVideos = 'yes' === $el.data( 'allow_videos' );
1463
1464 event.preventDefault();
1465
1466 // If the media frame already exists, reopen it.
1467 if ( product_gallery_frame ) {
1468 product_gallery_frame.open();
1469 return;
1470 }
1471
1472 // Create the media frame.
1473 product_gallery_frame = wp.media.frames.product_gallery = wp.media( {
1474 // Set the title of the modal.
1475 title: $el.data( 'choose' ),
1476 button: {
1477 text: $el.data( 'update' ),
1478 },
1479 states: [
1480 new wp.media.controller.Library( {
1481 title: $el.data( 'choose' ),
1482 library: wp.media.query( {
1483 type: allowVideos ? [ 'image', 'video' ] : 'image',
1484 } ),
1485 filterable: 'all',
1486 multiple: true,
1487 } ),
1488 ],
1489 } );
1490
1491 // When media is selected, run a callback.
1492 product_gallery_frame.on( 'select', function () {
1493 const selection = product_gallery_frame.state().get( 'selection' );
1494
1495 selection.map( function ( attachment ) {
1496 const attachmentData = attachment.toJSON();
1497 const $item = createProductGalleryMediaItem(
1498 attachmentData,
1499 $el,
1500 allowVideos
1501 );
1502
1503 if ( $item ) {
1504 $product_images.append( $item );
1505 }
1506 } );
1507
1508 updateProductGalleryFields();
1509 } );
1510
1511 // Finally, open the modal.
1512 product_gallery_frame.open();
1513 } );
1514
1515 // Media ordering.
1516 $product_images.sortable( {
1517 items: 'li.image',
1518 cursor: 'move',
1519 scrollSensitivity: 40,
1520 forcePlaceholderSize: true,
1521 forceHelperSize: false,
1522 helper: 'clone',
1523 opacity: 0.65,
1524 placeholder: 'wc-metabox-sortable-placeholder',
1525 start: function ( event, ui ) {
1526 ui.item.css( 'background-color', '#f6f6f6' );
1527 },
1528 stop: function ( event, ui ) {
1529 ui.item.removeAttr( 'style' );
1530 },
1531 update: updateProductGalleryFields,
1532 } );
1533
1534 // Remove media.
1535 $( '#product_images_container' ).on( 'click', 'a.delete', function () {
1536 $( this ).closest( 'li.image' ).remove();
1537
1538 updateProductGalleryFields();
1539
1540 // Remove any lingering tooltips.
1541 $( '#tiptip_holder' ).removeAttr( 'style' );
1542 $( '#tiptip_arrow' ).removeAttr( 'style' );
1543
1544 return false;
1545 } );
1546
1547 // Add a descriptive tooltip to the product description editor
1548 $( '#wp-content-media-buttons' )
1549 .append( '<span class="woocommerce-help-tip" tabindex="0"></span>' )
1550 .find( '.woocommerce-help-tip' )
1551 .attr( 'tabindex', '0' )
1552 .attr( 'for', 'content' )
1553 .attr(
1554 'aria-label',
1555 woocommerce_admin_meta_boxes.i18n_product_description_tip
1556 )
1557 .tipTip( {
1558 attribute: 'data-tip',
1559 content: woocommerce_admin_meta_boxes.i18n_product_description_tip,
1560 fadeIn: 50,
1561 fadeOut: 50,
1562 delay: 200,
1563 keepAlive: true,
1564 } );
1565
1566 // Add a descriptive tooltip to the product short description meta box title
1567 $( '#postexcerpt > .postbox-header > .hndle' )
1568 .append( '<span class="woocommerce-help-tip"></span>' )
1569 .find( '.woocommerce-help-tip' )
1570 .attr( 'tabindex', '0' )
1571 .attr(
1572 'aria-label',
1573 woocommerce_admin_meta_boxes.i18n_product_short_description_tip
1574 )
1575 .tipTip( {
1576 attribute: 'data-tip',
1577 content:
1578 woocommerce_admin_meta_boxes.i18n_product_short_description_tip,
1579 fadeIn: 50,
1580 fadeOut: 50,
1581 delay: 200,
1582 keepAlive: true,
1583 } );
1584
1585 // Add tooltips to the product image and product gallery actions.
1586 // Escape the translated label before interpolating into the attribute so a
1587 // translation containing quotes or markup cannot break the rendered span.
1588 const tooltipMarkup = `<span class="woocommerce-help-tip" tabindex="0" aria-label="${ _.escape(
1589 woocommerce_admin_meta_boxes.i18n_product_image_tip
1590 ) }"></span>`;
1591 const tooltipData = {
1592 attribute: 'data-tip',
1593 content: woocommerce_admin_meta_boxes.i18n_product_image_tip,
1594 fadeIn: 50,
1595 fadeOut: 50,
1596 delay: 200,
1597 keepAlive: true,
1598 };
1599 const productImageContainer = $( '#postimagediv .inside' );
1600 const productImageTooltipClass = 'woocommerce-product-image-help-tip';
1601
1602 const syncProductImageTooltip = () => {
1603 const removeProductImageLink = $( '#remove-post-thumbnail' );
1604 const productImageLink = removeProductImageLink.length
1605 ? removeProductImageLink
1606 : $( '#set-post-thumbnail' );
1607 const existingTooltip = productImageContainer.find(
1608 `.${ productImageTooltipClass }`
1609 );
1610
1611 if ( ! productImageLink.length ) {
1612 existingTooltip.remove();
1613 return;
1614 }
1615
1616 if (
1617 existingTooltip.length &&
1618 existingTooltip.prev()[ 0 ] === productImageLink[ 0 ]
1619 ) {
1620 return;
1621 }
1622
1623 existingTooltip.remove();
1624 $( tooltipMarkup )
1625 .addClass( productImageTooltipClass )
1626 .insertAfter( productImageLink )
1627 .tipTip( tooltipData );
1628 };
1629
1630 if ( productImageContainer.length ) {
1631 syncProductImageTooltip();
1632
1633 // WPSetThumbnailHTML() replaces the direct children of `.inside`, so
1634 // `childList` alone observes every image change. Omitting `subtree`
1635 // also keeps the observer blind to the tooltip's own insertion, which
1636 // happens one level deeper.
1637 new MutationObserver( syncProductImageTooltip ).observe(
1638 productImageContainer[ 0 ],
1639 { childList: true }
1640 );
1641 }
1642
1643 const addProductImagesLink = $( '.add_product_images > a' );
1644
1645 if ( addProductImagesLink.length ) {
1646 $( tooltipMarkup )
1647 .insertAfter( addProductImagesLink )
1648 .tipTip( tooltipData );
1649 }
1650 } );
1651