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 / includes / admin / class-wc-admin-attributes.php
woocommerce / includes / admin Last commit date
helper 2 weeks ago importers 2 weeks ago list-tables 1 week ago marketplace-suggestions 1 year ago meta-boxes 1 week ago notes 3 months ago plugin-updates 2 years ago reports 2 weeks ago settings 2 weeks ago views 2 weeks ago class-wc-admin-addons.php 10 months ago class-wc-admin-api-keys-table-list.php 3 years ago class-wc-admin-api-keys.php 1 month ago class-wc-admin-assets.php 2 weeks ago class-wc-admin-attributes.php 2 weeks ago class-wc-admin-brands.php 2 weeks ago class-wc-admin-customize.php 6 years ago class-wc-admin-dashboard-setup.php 2 weeks ago class-wc-admin-dashboard.php 2 weeks ago class-wc-admin-duplicate-product.php 2 weeks ago class-wc-admin-exporters.php 1 year ago class-wc-admin-help.php 2 years ago class-wc-admin-importers.php 1 year ago class-wc-admin-log-table-list.php 6 months ago class-wc-admin-marketplace-promotions.php 1 month ago class-wc-admin-menus.php 2 weeks ago class-wc-admin-meta-boxes.php 2 months ago class-wc-admin-notices.php 2 weeks ago class-wc-admin-permalink-settings.php 5 years ago class-wc-admin-pointers.php 3 years ago class-wc-admin-post-types.php 2 weeks ago class-wc-admin-profile.php 1 year ago class-wc-admin-reports.php 6 months ago class-wc-admin-settings.php 2 weeks ago class-wc-admin-setup-wizard.php 4 days ago class-wc-admin-status.php 1 year ago class-wc-admin-taxonomies.php 2 months ago class-wc-admin-upload-downloadable-product.php 2 years ago class-wc-admin-webhooks-table-list.php 2 months ago class-wc-admin-webhooks.php 1 month ago class-wc-admin.php 2 weeks ago wc-admin-functions.php 2 weeks ago wc-meta-box-functions.php 1 year ago woocommerce-legacy-reports.php 1 year ago
class-wc-admin-attributes.php
647 lines
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
11 defined( 'ABSPATH' ) || exit;
12
13 use Automattic\WooCommerce\Internal\ProductAttributes\AttributeSlugLength;
14 use Automattic\WooCommerce\Internal\ProductAttributes\VisualAttributeTermAdmin;
15
16 /**
17 * WC_Admin_Attributes Class.
18 */
19 class WC_Admin_Attributes {
20
21 /**
22 * Edited attribute ID.
23 *
24 * @var int
25 */
26 private static $edited_attribute_id;
27
28 /**
29 * Handles output of the attributes page in admin.
30 *
31 * Shows the created attributes and lets you add new ones or edit existing ones.
32 * The added attributes are stored in the database and can be used for layered navigation.
33 */
34 public static function output() {
35 $result = '';
36 $action = '';
37
38 // Action to perform: add, edit, delete or none.
39 if ( ! empty( $_POST['add_new_attribute'] ) ) { // WPCS: CSRF ok.
40 $action = 'add';
41 } elseif ( ! empty( $_POST['save_attribute'] ) && ! empty( $_GET['edit'] ) ) { // WPCS: CSRF ok.
42 $action = 'edit';
43 } elseif ( ! empty( $_GET['delete'] ) ) {
44 $action = 'delete';
45 }
46
47 switch ( $action ) {
48 case 'add':
49 $result = self::process_add_attribute();
50 break;
51 case 'edit':
52 $result = self::process_edit_attribute();
53 break;
54 case 'delete':
55 $result = self::process_delete_attribute();
56 break;
57 }
58
59 if ( is_wp_error( $result ) ) {
60 echo '<div id="woocommerce_errors" class="error"><p>' . wp_kses_post( $result->get_error_message() ) . '</p></div>';
61 }
62
63 // Show admin interface.
64 if ( ! empty( $_GET['edit'] ) ) {
65 self::edit_attribute();
66 } else {
67 self::add_attribute();
68 }
69 }
70
71 /**
72 * Get and sanitize posted attribute data.
73 *
74 * @return array
75 */
76 private static function get_posted_attribute() {
77 $attribute = array(
78 'attribute_label' => isset( $_POST['attribute_label'] ) ? wc_clean( wp_unslash( $_POST['attribute_label'] ) ) : '', // WPCS: input var ok, CSRF ok.
79 'attribute_name' => isset( $_POST['attribute_name'] ) ? wc_sanitize_taxonomy_name( wp_unslash( $_POST['attribute_name'] ) ) : '', // WPCS: input var ok, CSRF ok, sanitization ok.
80 'attribute_type' => isset( $_POST['attribute_type'] ) ? wc_clean( wp_unslash( $_POST['attribute_type'] ) ) : 'select', // WPCS: input var ok, CSRF ok.
81 'attribute_orderby' => isset( $_POST['attribute_orderby'] ) ? wc_clean( wp_unslash( $_POST['attribute_orderby'] ) ) : '', // WPCS: input var ok, CSRF ok.
82 'attribute_public' => isset( $_POST['attribute_public'] ) ? 1 : 0, // WPCS: input var ok, CSRF ok.
83 );
84
85 if ( empty( $attribute['attribute_type'] ) ) {
86 $attribute['attribute_type'] = 'select';
87 }
88 if ( empty( $attribute['attribute_label'] ) ) {
89 $attribute['attribute_label'] = ucfirst( $attribute['attribute_name'] );
90 }
91 if ( empty( $attribute['attribute_name'] ) ) {
92 $attribute['attribute_name'] = wc_sanitize_taxonomy_name( $attribute['attribute_label'] );
93 }
94
95 return $attribute;
96 }
97
98 /**
99 * Add an attribute.
100 *
101 * @return bool|WP_Error
102 */
103 private static function process_add_attribute() {
104 check_admin_referer( 'woocommerce-add-new_attribute' );
105
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
115 $id = wc_create_attribute( $args );
116
117 if ( is_wp_error( $id ) ) {
118 return $id;
119 }
120
121 VisualAttributeTermAdmin::seed_visual_attribute_terms( $id );
122
123 return true;
124 }
125
126 /**
127 * Edit an attribute.
128 *
129 * @return bool|WP_Error
130 */
131 private static function process_edit_attribute() {
132 $attribute_id = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
133 check_admin_referer( 'woocommerce-save-attribute_' . $attribute_id );
134
135 $attribute = self::get_posted_attribute();
136 $args = array(
137 'name' => $attribute['attribute_label'],
138 'slug' => $attribute['attribute_name'],
139 'type' => $attribute['attribute_type'],
140 'order_by' => $attribute['attribute_orderby'],
141 'has_archives' => $attribute['attribute_public'],
142 );
143
144 $id = wc_update_attribute( $attribute_id, $args );
145
146 if ( is_wp_error( $id ) ) {
147 return $id;
148 }
149
150 self::$edited_attribute_id = $id;
151
152 return true;
153 }
154
155 /**
156 * Delete an attribute.
157 *
158 * @return bool
159 */
160 private static function process_delete_attribute() {
161 $attribute_id = isset( $_GET['delete'] ) ? absint( $_GET['delete'] ) : 0;
162 check_admin_referer( 'woocommerce-delete-attribute_' . $attribute_id );
163
164 return wc_delete_attribute( $attribute_id );
165 }
166
167 /**
168 * Build the slug field description shown to users (and served to search engines and
169 * no-JavaScript clients as the static copy).
170 *
171 * States the authoritative byte limit alongside a character estimate tailored to the
172 * script of the user's language, so users on non-Latin scripts get a meaningful
173 * figure rather than a raw byte count they must translate into characters themselves.
174 *
175 * @return string
176 */
177 private static function slug_field_description(): string {
178 return sprintf(
179 /* translators: 1: maximum slug length in bytes, 2: approximate maximum number of characters for the user's language. */
180 __( 'Unique slug/reference for the attribute. Limited to %1$d bytes — roughly %2$d characters in your language; non-ASCII characters use 2–4 bytes each.', 'woocommerce' ),
181 wc_get_attribute_slug_max_byte_length(),
182 AttributeSlugLength::get_character_estimate()
183 );
184 }
185
186 /**
187 * Output an inline script that shows a live UTF-8 byte count next to the
188 * slug input and visually warns when the value approaches or exceeds the
189 * server-side byte limit.
190 *
191 * The HTML `maxlength` attribute counts characters, not bytes, so a
192 * multibyte slug (e.g. Cyrillic, Chinese) can pass the browser's guard
193 * and still be rejected by `register_taxonomy()`'s 32-byte name limit.
194 * This counter closes that feedback gap before submission.
195 *
196 * When the live counter activates, it also swaps the server-rendered field
197 * description for a leaner note: the counter now carries the concrete byte
198 * numbers, so the locale-tailored character estimate becomes redundant.
199 */
200 private static function slug_byte_counter_script(): void {
201 $max_bytes = wc_get_attribute_slug_max_byte_length();
202 /* translators: 1: current byte count, 2: maximum allowed bytes. */
203 $template = wp_json_encode( __( '%1$d / %2$d bytes', 'woocommerce' ), JSON_HEX_TAG | JSON_HEX_AMP );
204 if ( false === $template ) {
205 // Encoding the counter template failed (e.g. invalid UTF-8 in the translated
206 // string); skip the counter rather than emit broken inline JavaScript.
207 return;
208 }
209 // Leaner description for JS clients: the live counter below carries the concrete
210 // numbers, so the locale-tailored character estimate becomes redundant noise.
211 $js_description = wp_json_encode(
212 /* translators: %d: maximum slug length in bytes. */
213 sprintf( __( 'Unique slug/reference for the attribute. Non-ASCII characters use 2–4 bytes of the %d-byte limit.', 'woocommerce' ), $max_bytes ),
214 JSON_HEX_TAG | JSON_HEX_AMP
215 );
216 if ( false === $js_description ) {
217 // Keep the server-rendered description if encoding fails.
218 $js_description = 'null';
219 }
220 ?>
221 <script type="text/javascript">
222 ( function() {
223 var input = document.getElementById( 'attribute_name' );
224 if ( ! input || ! ( 'TextEncoder' in window ) ) {
225 return;
226 }
227 var wrapper = input.closest( 'td, .form-field' );
228 var description = wrapper ? wrapper.querySelector( 'p.description' ) : null;
229 if ( ! description ) {
230 return;
231 }
232 var maxBytes = <?php echo (int) $max_bytes; ?>;
233 var template = <?php echo $template; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode with JSON_HEX_TAG produces JS-safe output. ?>;
234 var jsDescription = <?php echo $js_description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode with JSON_HEX_TAG produces JS-safe output; 'null' fallback is a literal. ?>;
235 var encoder = new TextEncoder();
236 var counter = document.createElement( 'span' );
237 counter.style.display = 'block';
238 counter.style.marginTop = '4px';
239 if ( jsDescription ) {
240 // Swap the verbose server-rendered guidance for the lean note now that
241 // the live counter provides the concrete byte feedback.
242 description.textContent = jsDescription;
243 }
244 description.appendChild( counter );
245 function update() {
246 var bytes = encoder.encode( input.value ).length;
247 if ( 0 === bytes ) {
248 counter.textContent = '';
249 counter.style.color = '';
250 return;
251 }
252 counter.textContent = template
253 .replace( '%1$d', bytes )
254 .replace( '%2$d', maxBytes );
255 if ( bytes > maxBytes ) {
256 counter.style.color = '#d63638';
257 } else if ( bytes >= maxBytes - 3 ) {
258 // Within one multibyte character (up to 3 bytes) of the limit.
259 counter.style.color = '#996800';
260 } else {
261 counter.style.color = '';
262 }
263 }
264 input.addEventListener( 'input', update );
265 update();
266 } )();
267 </script>
268 <?php
269 }
270
271 /**
272 * Edit Attribute admin panel.
273 *
274 * Shows the interface for changing an attributes type between select and text.
275 */
276 public static function edit_attribute() {
277 global $wpdb;
278
279 $edit = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
280
281 $attribute_to_edit = $wpdb->get_row(
282 $wpdb->prepare(
283 "
284 SELECT attribute_type, attribute_label, attribute_name, attribute_orderby, attribute_public
285 FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = %d
286 ",
287 $edit
288 )
289 );
290
291 ?>
292 <div class="wrap woocommerce">
293 <h1><?php esc_html_e( 'Edit attribute', 'woocommerce' ); ?></h1>
294
295 <?php
296 if ( ! $attribute_to_edit ) {
297 echo '<div id="woocommerce_errors" class="error"><p>' . esc_html__( 'Error: non-existing attribute ID.', 'woocommerce' ) . '</p></div>';
298 } else {
299 if ( self::$edited_attribute_id > 0 ) {
300 echo '<div id="message" class="updated"><p>' . esc_html__( 'Attribute updated successfully', 'woocommerce' ) . '</p><p><a href="' . esc_url( admin_url( 'edit.php?post_type=product&amp;page=product_attributes' ) ) . '">' . esc_html__( 'Back to Attributes', 'woocommerce' ) . '</a></p></div>';
301 self::$edited_attribute_id = null;
302 }
303 $att_type = $attribute_to_edit->attribute_type;
304 $att_label = format_to_edit( $attribute_to_edit->attribute_label );
305 $att_name = $attribute_to_edit->attribute_name;
306 $att_orderby = $attribute_to_edit->attribute_orderby;
307 $att_public = $attribute_to_edit->attribute_public;
308 ?>
309 <form action="edit.php?post_type=product&amp;page=product_attributes&amp;edit=<?php echo absint( $edit ); ?>" method="post">
310 <table class="form-table">
311 <tbody>
312 <?php do_action( 'woocommerce_before_edit_attribute_fields' ); ?>
313 <tr class="form-field form-required">
314 <th scope="row" valign="top">
315 <label for="attribute_label"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
316 </th>
317 <td>
318 <input name="attribute_label" id="attribute_label" type="text" value="<?php echo esc_attr( $att_label ); ?>" />
319 <p class="description"><?php esc_html_e( 'Name for the attribute (shown on the front-end).', 'woocommerce' ); ?></p>
320 </td>
321 </tr>
322 <tr class="form-field form-required">
323 <th scope="row" valign="top">
324 <label for="attribute_name"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></label>
325 </th>
326 <td>
327 <input name="attribute_name" id="attribute_name" type="text" value="<?php echo esc_attr( $att_name ); ?>" maxlength="29" />
328 <p class="description"><?php echo esc_html( self::slug_field_description() ); ?></p>
329 </td>
330 </tr>
331 <tr class="form-field form-required">
332 <th scope="row" valign="top">
333 <label for="attribute_public"><?php esc_html_e( 'Enable archives?', 'woocommerce' ); ?></label>
334 </th>
335 <td>
336 <input name="attribute_public" id="attribute_public" type="checkbox" value="1" <?php checked( $att_public, 1 ); ?> />
337 <p class="description"><?php esc_html_e( 'Enable this if you want this attribute to have product archives in your store.', 'woocommerce' ); ?></p>
338 </td>
339 </tr>
340 <?php
341 /**
342 * Attribute types can change the way attributes are displayed on the frontend and admin.
343 *
344 * By Default WooCommerce only includes the `select` type. Others can be added with the
345 * `product_attributes_type_selector` filter. If there is only the default type registered,
346 * this setting will be hidden.
347 */
348 if ( wc_has_custom_attribute_types() ) {
349 ?>
350 <tr class="form-field form-required">
351 <th scope="row" valign="top">
352 <label for="attribute_type"><?php esc_html_e( 'Type', 'woocommerce' ); ?></label>
353 </th>
354 <td>
355 <select name="attribute_type" id="attribute_type">
356 <?php foreach ( wc_get_attribute_types() as $key => $value ) : ?>
357 <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $att_type, $key ); ?>><?php echo esc_html( $value ); ?></option>
358 <?php endforeach; ?>
359 <?php
360 /**
361 * Deprecated action in favor of product_attributes_type_selector filter.
362 *
363 * @todo Remove in 4.0.0
364 * @deprecated 2.4.0
365 */
366 do_action( 'woocommerce_admin_attribute_types' );
367 ?>
368 </select>
369 <p class="description"><?php esc_html_e( "Determines how this attribute's values are displayed.", 'woocommerce' ); ?></p>
370 </td>
371 </tr>
372 <?php
373 }//end if
374 ?>
375 <tr class="form-field form-required">
376 <th scope="row" valign="top">
377 <label for="attribute_orderby"><?php esc_html_e( 'Default sort order', 'woocommerce' ); ?></label>
378 </th>
379 <td>
380 <select name="attribute_orderby" id="attribute_orderby">
381 <option value="menu_order" <?php selected( $att_orderby, 'menu_order' ); ?>><?php esc_html_e( 'Custom ordering', 'woocommerce' ); ?></option>
382 <option value="name" <?php selected( $att_orderby, 'name' ); ?>><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
383 <option value="name_num" <?php selected( $att_orderby, 'name_num' ); ?>><?php esc_html_e( 'Name (numeric)', 'woocommerce' ); ?></option>
384 <option value="id" <?php selected( $att_orderby, 'id' ); ?>><?php esc_html_e( 'Term ID', 'woocommerce' ); ?></option>
385 </select>
386 <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>
387 </td>
388 </tr>
389 <?php do_action( 'woocommerce_after_edit_attribute_fields' ); ?>
390 </tbody>
391 </table>
392 <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>
393 <?php wp_nonce_field( 'woocommerce-save-attribute_' . $edit ); ?>
394 </form>
395 <?php self::slug_byte_counter_script(); ?>
396 <?php
397 }//end if
398 ?>
399 </div>
400 <?php
401 }
402
403 /**
404 * Add Attribute admin panel.
405 *
406 * Shows the interface for adding new attributes.
407 */
408 public static function add_attribute() {
409 ?>
410 <div class="wrap woocommerce">
411 <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
412
413 <br class="clear" />
414 <div id="col-container">
415 <div id="col-right">
416 <div class="col-wrap">
417 <table class="widefat attributes-table wp-list-table ui-sortable" style="width:100%">
418 <thead>
419 <tr>
420 <th scope="col"><?php esc_html_e( 'Name', 'woocommerce' ); ?></th>
421 <th scope="col"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></th>
422 <?php if ( wc_has_custom_attribute_types() ) : ?>
423 <th scope="col"><?php esc_html_e( 'Type', 'woocommerce' ); ?></th>
424 <?php endif; ?>
425 <th scope="col"><?php esc_html_e( 'Order by', 'woocommerce' ); ?></th>
426 <th scope="col"><?php esc_html_e( 'Terms', 'woocommerce' ); ?></th>
427 </tr>
428 </thead>
429 <tbody>
430 <?php
431 $attribute_taxonomies = wc_get_attribute_taxonomies();
432 if ( $attribute_taxonomies ) {
433 /**
434 * Filters the maximum number of terms that will be displayed for each taxonomy in the Attributes page.
435 *
436 * @param int @default_max_terms_to_display Default value.
437 * @returns int Actual value to use, may be zero.
438 *
439 * @since 6.9.0
440 */
441 $max_terms_to_display = apply_filters( 'woocommerce_max_terms_displayed_in_attributes_page', 100 );
442 foreach ( $attribute_taxonomies as $tax ) :
443 $taxonomy = wc_attribute_taxonomy_name( $tax->attribute_name );
444 $actions = array(
445 'edit' => '<a href="' . esc_url( add_query_arg( 'edit', $tax->attribute_id, 'edit.php?post_type=product&amp;page=product_attributes' ) ) . '">' . esc_html__( 'Edit', 'woocommerce' ) . '</a>',
446 'delete' => '<a class="delete" href="' . 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 ) ) . '">' . esc_html__( 'Delete', 'woocommerce' ) . '</a>',
447 );
448
449 /**
450 * Filters the row actions for a global product attribute taxonomy on the Products > Attributes screen.
451 *
452 * The action values are HTML links. Default actions are escaped before this filter runs, and callbacks
453 * should return safe, escaped HTML.
454 *
455 * @param array<string,string> $actions Action link HTML keyed by action name.
456 * @param object $tax Attribute taxonomy object.
457 * @param string $taxonomy Full taxonomy name, including the `pa_` prefix.
458 *
459 * @since 11.1.0
460 */
461 $actions = (array) apply_filters( 'woocommerce_attribute_taxonomy_row_actions', $actions, $tax, $taxonomy );
462 $row_actions = array();
463
464 foreach ( $actions as $action => $link ) {
465 $row_actions[] = '<span class="' . esc_attr( $action ) . '">' . $link . '</span>';
466 }
467 ?>
468 <tr>
469 <td>
470 <strong><a href="edit-tags.php?taxonomy=<?php echo esc_attr( $taxonomy ); ?>&amp;post_type=product"><?php echo esc_html( $tax->attribute_label ); ?></a></strong>
471
472 <?php echo '<div class="row-actions">' . implode( ' | ', $row_actions ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Row action values are link HTML. Default values are escaped above; filtered callbacks should return safe HTML. ?>
473 </td>
474 <td><?php echo esc_html( $tax->attribute_name ); ?></td>
475 <?php if ( wc_has_custom_attribute_types() ) : ?>
476 <td><?php echo esc_html( wc_get_attribute_type_label( $tax->attribute_type ) ); ?> <?php echo $tax->attribute_public ? esc_html__( '(Public)', 'woocommerce' ) : ''; ?></td>
477 <?php endif; ?>
478 <td>
479 <?php
480 switch ( $tax->attribute_orderby ) {
481 case 'name':
482 esc_html_e( 'Name', 'woocommerce' );
483 break;
484 case 'name_num':
485 esc_html_e( 'Name (numeric)', 'woocommerce' );
486 break;
487 case 'id':
488 esc_html_e( 'Term ID', 'woocommerce' );
489 break;
490 default:
491 esc_html_e( 'Custom ordering', 'woocommerce' );
492 break;
493 }
494 ?>
495 </td>
496 <td class="attribute-terms">
497 <?php
498 if ( taxonomy_exists( $taxonomy ) ) {
499 $total_count = (int) get_terms(
500 array(
501 'taxonomy' => $taxonomy,
502 'fields' => 'count',
503 'hide_empty' => false,
504 )
505 );
506 if ( 0 === $total_count ) {
507 echo '<span class="na">&ndash;</span>';
508 } elseif ( $max_terms_to_display > 0 ) {
509 $terms = get_terms(
510 array(
511 'taxonomy' => $taxonomy,
512 'number' => $max_terms_to_display,
513 'fields' => 'names',
514 'hide_empty' => false,
515 )
516 );
517 $terms_string = implode( ', ', $terms );
518 if ( $total_count > $max_terms_to_display ) {
519 $remaining = $total_count - $max_terms_to_display;
520 /* translators: 1: Comma-separated terms list, 2: how many terms are hidden */
521 $terms_string = sprintf( __( '%1$s... (%2$s more)', 'woocommerce' ), $terms_string, $remaining );
522 }
523 echo esc_html( $terms_string );
524 } elseif ( 1 === $total_count ) {
525 echo esc_html( __( '1 term', 'woocommerce' ) );
526 } else {
527 /* translators: %s: Total count of terms available for the attribute */
528 echo esc_html( sprintf( __( '%s terms', 'woocommerce' ), $total_count ) );
529 }//end if
530 } else {
531 echo '<span class="na">&ndash;</span><br />';
532 }//end if
533 ?>
534 <br /><a href="edit-tags.php?taxonomy=<?php echo esc_attr( $taxonomy ); ?>&amp;post_type=product" class="configure-terms"><?php esc_html_e( 'Configure terms', 'woocommerce' ); ?></a>
535 </td>
536 </tr>
537 <?php
538 endforeach;
539 } else {
540 $column_count = wc_has_custom_attribute_types() ? '5' : '4';
541 ?>
542 <tr>
543 <td colspan="<?php echo esc_attr( $column_count ); ?>"><?php esc_html_e( 'No attributes currently exist.', 'woocommerce' ); ?></td>
544 </tr>
545 <?php
546 }//end if
547 ?>
548 </tbody>
549 </table>
550 </div>
551 </div>
552 <div id="col-left">
553 <div class="col-wrap">
554 <div class="form-wrap">
555 <h2><?php esc_html_e( 'Add new attribute', 'woocommerce' ); ?></h2>
556 <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>
557 <form action="edit.php?post_type=product&amp;page=product_attributes" method="post">
558 <?php do_action( 'woocommerce_before_add_attribute_fields' ); ?>
559
560 <div class="form-field">
561 <label for="attribute_label"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
562 <input name="attribute_label" id="attribute_label" type="text" value="" />
563 <p class="description"><?php esc_html_e( 'Name for the attribute (shown on the front-end).', 'woocommerce' ); ?></p>
564 </div>
565
566 <div class="form-field">
567 <label for="attribute_name"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></label>
568 <input name="attribute_name" id="attribute_name" type="text" value="" maxlength="29" />
569 <p class="description"><?php echo esc_html( self::slug_field_description() ); ?></p>
570 </div>
571
572 <div class="form-field">
573 <label for="attribute_public"><input name="attribute_public" id="attribute_public" type="checkbox" value="1" /> <?php esc_html_e( 'Enable Archives?', 'woocommerce' ); ?></label>
574
575 <p class="description"><?php esc_html_e( 'Enable this if you want this attribute to have product archives in your store.', 'woocommerce' ); ?></p>
576 </div>
577
578 <?php
579 /**
580 * Attribute types can change the way attributes are displayed on the frontend and admin.
581 *
582 * By Default WooCommerce only includes the `select` type. Others can be added with the
583 * `product_attributes_type_selector` filter. If there is only the default type registered,
584 * this setting will be hidden.
585 */
586 if ( wc_has_custom_attribute_types() ) {
587 ?>
588 <div class="form-field">
589 <label for="attribute_type"><?php esc_html_e( 'Type', 'woocommerce' ); ?></label>
590 <select name="attribute_type" id="attribute_type">
591 <?php foreach ( wc_get_attribute_types() as $key => $value ) : ?>
592 <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $value ); ?></option>
593 <?php endforeach; ?>
594 <?php
595 /**
596 * Deprecated action in favor of product_attributes_type_selector filter.
597 *
598 * @todo Remove in 4.0.0
599 * @deprecated 2.4.0
600 */
601 do_action( 'woocommerce_admin_attribute_types' );
602 ?>
603 </select>
604 <p class="description"><?php esc_html_e( "Determines how this attribute's values are displayed.", 'woocommerce' ); ?></p>
605 </div>
606 <?php
607 }//end if
608 ?>
609
610 <div class="form-field">
611 <label for="attribute_orderby"><?php esc_html_e( 'Default sort order', 'woocommerce' ); ?></label>
612 <select name="attribute_orderby" id="attribute_orderby">
613 <option value="menu_order"><?php esc_html_e( 'Custom ordering', 'woocommerce' ); ?></option>
614 <option value="name"><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
615 <option value="name_num"><?php esc_html_e( 'Name (numeric)', 'woocommerce' ); ?></option>
616 <option value="id"><?php esc_html_e( 'Term ID', 'woocommerce' ); ?></option>
617 </select>
618 <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>
619 </div>
620
621 <?php do_action( 'woocommerce_after_add_attribute_fields' ); ?>
622
623 <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>
624 <?php wp_nonce_field( 'woocommerce-add-new_attribute' ); ?>
625 </form>
626 </div>
627 </div>
628 </div>
629 </div>
630 <script type="text/javascript">
631 /* <![CDATA[ */
632
633 jQuery( 'a.delete' ).on( 'click', function() {
634 if ( window.confirm( '<?php esc_html_e( 'Are you sure you want to delete this attribute?', 'woocommerce' ); ?>' ) ) {
635 return true;
636 }
637 return false;
638 });
639
640 /* ]]> */
641 </script>
642 <?php self::slug_byte_counter_script(); ?>
643 </div>
644 <?php
645 }
646 }
647