'no', 'condition_attr' => '', 'cond_scale' => 'words', 'cond_names' => array(), 'cond_descs' => array(), 'cond_use' => usedkit_grade_keys(), 'cond_page' => 'card', 'cond_loop' => 'pill'); }); add_filter('usedkit_save_settings', function ($out, $post) { $keys = usedkit_grade_keys(); $clean = function ($list) use ($keys) { $list = is_array($list) ? $list : array(); return array_intersect_key(array_map('sanitize_text_field', $list), array_flip($keys)); }; $use = array_values(array_intersect($keys, (array) ($post['cond_use'] ?? array()))); return $out + array( 'condition' => !empty($post['condition']) ? 'yes' : 'no', 'condition_attr' => sanitize_text_field($post['condition_attr'] ?? ''), 'cond_scale' => in_array($post['cond_scale'] ?? '', array('words', 'letters', 'ten'), true) ? $post['cond_scale'] : 'words', 'cond_names' => array_filter($clean($post['cond_names'] ?? array()), 'strlen'), 'cond_descs' => array_filter($clean($post['cond_descs'] ?? array()), 'strlen'), 'cond_use' => $use ? $use : $keys, 'cond_page' => in_array($post['cond_page'] ?? '', array('card', 'line', 'photo', 'none'), true) ? $post['cond_page'] : 'card', 'cond_loop' => in_array($post['cond_loop'] ?? '', array('pill', 'text', 'photo', 'scale', 'none'), true) ? $post['cond_loop'] : 'pill', ); }, 10, 2); add_action('usedkit_settings_rows', function ($s) { $scales = array('words' => __('Words', 'usedkit'), 'letters' => __('Letters (A+ to D)', 'usedkit'), 'ten' => __('Ten-point (10/10 to 5/10)', 'usedkit')); $pages = array('card' => __('A card with a scale', 'usedkit'), 'line' => __('One line', 'usedkit'), 'photo' => __('A badge on the product photo', 'usedkit'), 'none' => __('Not shown', 'usedkit')); $loops = array('pill' => __('A pill', 'usedkit'), 'text' => __('Text', 'usedkit'), 'scale' => __('A small scale', 'usedkit'), 'photo' => __('A badge on the product photo', 'usedkit'), 'none' => __('Not shown', 'usedkit')); $defaults = usedkit_scale_names($s['cond_scale']); $descs = usedkit_default_descriptions(); ?>

   

>

'A+', 'like_new' => 'A', 'excellent' => 'B+', 'good' => 'B', 'fair' => 'C', 'parts' => 'D'); } if ($scale === 'ten') { return array('new' => '10/10', 'like_new' => '9/10', 'excellent' => '8/10', 'good' => '7/10', 'fair' => '5/10', 'parts' => __('For parts', 'usedkit')); } return array( 'new' => __('New', 'usedkit'), 'like_new' => __('Like new', 'usedkit'), 'excellent' => __('Excellent', 'usedkit'), 'good' => __('Good', 'usedkit'), 'fair' => __('Fair', 'usedkit'), 'parts' => __('For parts', 'usedkit'), ); } function usedkit_default_descriptions() { return array( 'new' => __('Unused, in its original packaging.', 'usedkit'), 'like_new' => __('Used, no visible wear.', 'usedkit'), 'excellent' => __('Light signs of use, works perfectly.', 'usedkit'), 'good' => __('Clear signs of use, works perfectly.', 'usedkit'), 'fair' => __('Heavy wear or a minor fault, described below.', 'usedkit'), 'parts' => __('Does not work as it should. Sold as is.', 'usedkit'), ); } /** Every grade's name as the shop shows it: its own name, else the scale's. */ function usedkit_grade_names() { $s = usedkit_settings(); return array_merge(usedkit_scale_names($s['cond_scale']), array_filter((array) $s['cond_names'], 'strlen')); } /** Every grade's description as the shop shows it. */ function usedkit_grade_descriptions() { return array_merge(usedkit_default_descriptions(), array_filter((array) usedkit_settings()['cond_descs'], 'strlen')); } /** The grades the shop uses, best first: [key => name]. For pickers, filters, the table. */ function usedkit_grades() { $names = usedkit_grade_names(); $use = (array) usedkit_settings()['cond_use']; return array_intersect_key($names, array_flip($use ? $use : usedkit_grade_keys())); } /** [lit, of]: how far up the shop's own scale a grade is, for the bars. */ function usedkit_grade_level($g) { $used = array_keys(usedkit_grades()); if (!in_array($g, $used, true)) { $used = usedkit_grade_keys(); } $n = count($used); return array($n - array_search($g, $used, true), $n); } /** The bars of the scale, as markup. */ function usedkit_scale_html($g) { list($lit, $of) = usedkit_grade_level($g); $bars = ''; for ($k = 1; $k <= $of; $k++) { $bars .= $k <= $lit ? '' : ''; } return ''; } /** A product's grade: its own, else its parent's, else read from the shop's attribute. */ function usedkit_condition($product) { if (!$product) { return ''; } $g = $product->get_meta('_usedkit_condition'); if ($g === '' && $product->get_parent_id()) { return usedkit_condition(wc_get_product($product->get_parent_id())); } if ($g === '' && ($attr = usedkit_settings()['condition_attr']) !== '') { $g = usedkit_grade_from_label($product->get_attribute($attr) ?: $product->get_attribute('pa_' . sanitize_title($attr))); } return in_array($g, usedkit_grade_keys(), true) ? $g : ''; } /* Google for WooCommerce's product feed: the condition from the grade, where the shop has not chosen one for the product in that plugin's own field. */ add_filter('woocommerce_gla_product_attribute_values', function ($attributes, $product) { if (!usedkit_condition_on() || !empty($attributes['condition']) || !$product || $product->get_meta('_wc_gla_condition') !== '') { return $attributes; } $google = usedkit_grade_google(usedkit_condition($product)); if ($google !== '') { $attributes['condition'] = $google; } return $attributes; }, 10, 2); /* ---- the product's fields ---------------------------------------------------------- */ add_action('usedkit_item_panel', function ($product) { if (!usedkit_condition_on()) { return; } usedkit_panel_heading(__('Condition', 'usedkit')); $options = array('' => __('Not graded', 'usedkit')); foreach (usedkit_grades() as $k => $name) { $options[$k] = $name . ' – ' . usedkit_grade_descriptions()[$k]; } woocommerce_wp_select(array('id' => '_usedkit_condition', 'label' => __('Grade', 'usedkit'), 'options' => $options, 'value' => $product ? $product->get_meta('_usedkit_condition') : '')); woocommerce_wp_textarea_input(array('id' => '_usedkit_condition_note', 'label' => __('Condition note', 'usedkit'), 'placeholder' => __('Wear, marks, faults: what a buyer would want to know', 'usedkit'), 'value' => $product ? $product->get_meta('_usedkit_condition_note') : '')); woocommerce_wp_text_input(array('id' => '_usedkit_included', 'label' => __('Included', 'usedkit'), 'placeholder' => __('Box, charger, manual', 'usedkit'), 'description' => __('Separated by commas.', 'usedkit'), 'desc_tip' => true, 'value' => $product ? $product->get_meta('_usedkit_included') : '')); }, 10); add_action('woocommerce_admin_process_product_object', function ($product) { if (!usedkit_condition_on()) { return; } $g = usedkit_posted('_usedkit_condition'); usedkit_set_meta($product, '_usedkit_condition', in_array($g, usedkit_grade_keys(), true) ? $g : ($g === null ? null : '')); usedkit_set_meta($product, '_usedkit_condition_note', usedkit_posted('_usedkit_condition_note', true)); usedkit_set_meta($product, '_usedkit_included', usedkit_posted('_usedkit_included')); }); /* A grade read from the shop's attribute is kept on the product too, so the shop's * filter (a meta query) finds products graded either way. */ add_action('woocommerce_after_product_object_save', function ($product) { if (!usedkit_condition_on() || usedkit_settings()['condition_attr'] === '' || $product->get_meta('_usedkit_condition') !== '') { return; } $g = usedkit_condition($product); if ($g !== '') { update_post_meta($product->get_id(), '_usedkit_condition', $g); } }); /* ---- the shop ---------------------------------------------------------------------- */ add_action('woocommerce_single_product_summary', function () { global $product; if (!$product || !usedkit_condition_on()) { return; } echo wp_kses(usedkit_condition_html($product, usedkit_settings()['cond_page']), usedkit_allowed_html()); }, 12); /** The grade as a pill: [name], with a dot in the grade's colour. */ function usedkit_grade_pill($g, $extra = '') { return sprintf('%s', esc_attr($g), $extra ? ' ' . esc_attr($extra) : '', esc_html(usedkit_grade_names()[$g])); } /** * The condition on a product page, as the shop chose to show it: a card with the scale, * one line, or (with a badge on the photo, or the grade not shown) only the item's own * note and what is included -- those are facts about the unit, always worth showing. */ function usedkit_condition_html($product, $style = 'card') { $g = usedkit_condition($product); $note = $product->get_meta('_usedkit_condition_note'); $included = usedkit_split_list($product->get_meta('_usedkit_included')); $show = $g !== '' && in_array($style, array('card', 'line'), true); if (!$show && $note === '' && !$included) { return ''; } $out = '
'; if ($show && $style === 'card') { $out .= sprintf('
%s' . '%s
%s

%s

', esc_html__('Condition', 'usedkit'), esc_html(usedkit_grade_names()[$g]), usedkit_scale_html($g), esc_html(usedkit_grade_descriptions()[$g])); } elseif ($show) { $out .= '

' . usedkit_grade_pill($g) . ' ' . esc_html(usedkit_grade_descriptions()[$g]) . '

'; } if ($note !== '') { $out .= '

' . nl2br(esc_html($note)) . '

'; } if ($included) { $out .= ''; } return $out . '
'; } /* A badge on the product photo: on the first image of the product page's gallery. */ add_filter('woocommerce_single_product_image_thumbnail_html', function ($html, $attachment_id) { static $done = array(); global $product; if (!$product || !usedkit_condition_on() || usedkit_settings()['cond_page'] !== 'photo' || isset($done[$product->get_id()]) || ($g = usedkit_condition($product)) === '') { return $html; } $done[$product->get_id()] = true; return preg_replace('/^(\s*]*>)/', '$1' . wp_kses(usedkit_grade_pill($g, 'usedkit-on-photo'), usedkit_allowed_html()), $html, 1); }, 10, 2); /* In product listings: a pill, text or a small scale under the title, or a badge on the photo. */ add_action('woocommerce_after_shop_loop_item_title', function () { global $product; if (!$product || !usedkit_condition_on() || ($g = usedkit_condition($product)) === '') { return; } $style = usedkit_settings()['cond_loop']; if ($style === 'pill') { echo wp_kses(usedkit_grade_pill($g), usedkit_allowed_html()); } elseif ($style === 'text') { /* translators: %s: the item's condition grade, e.g. Good */ $text = sprintf(__('Condition: %s', 'usedkit'), usedkit_grade_names()[$g]); printf('%s', esc_attr($g), esc_html($text)); } elseif ($style === 'scale') { printf('%s%s', esc_attr($g), wp_kses(usedkit_scale_html($g), usedkit_allowed_html()), esc_html(usedkit_grade_names()[$g])); } }, 6); add_action('woocommerce_before_shop_loop_item_title', function () { global $product; if (!$product || !usedkit_condition_on() || usedkit_settings()['cond_loop'] !== 'photo' || ($g = usedkit_condition($product)) === '') { return; } echo wp_kses(usedkit_grade_pill($g, 'usedkit-on-photo'), usedkit_allowed_html()); }, 9); /* itemCondition in the product's structured data, on every offer. Ungraded items in a * used shop are still used. */ add_filter('woocommerce_structured_data_product', function ($markup, $product) { if (!usedkit_condition_on() || empty($markup['offers'])) { return $markup; } $g = usedkit_condition($product); $url = usedkit_grade_schema($g === '' ? 'good' : $g); foreach ($markup['offers'] as $i => $offer) { if (is_array($offer)) { $markup['offers'][$i]['itemCondition'] = $url; } } return $markup; }, 10, 2); /* The filter: ?condition=excellent,good on any product listing, and a select beside * the sorting that sets it. */ function usedkit_condition_filter() { $raw = sanitize_text_field(wp_unslash($_GET['condition'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification return array_values(array_intersect(explode(',', $raw), usedkit_grade_keys())); } add_action('woocommerce_product_query', function ($q) { if (!usedkit_condition_on() || !($grades = usedkit_condition_filter())) { return; } $meta = (array) $q->get('meta_query'); $meta[] = array('key' => '_usedkit_condition', 'value' => $grades, 'compare' => 'IN'); $q->set('meta_query', $meta); }); add_action('woocommerce_before_shop_loop', function () { if (!usedkit_condition_on()) { return; } $now = usedkit_condition_filter(); $now = count($now) === 1 ? $now[0] : ''; echo '
'; foreach ($_GET as $k => $v) { // phpcs:ignore WordPress.Security.NonceVerification if ($k !== 'condition' && $k !== 'paged' && is_string($v)) { printf('', esc_attr(sanitize_key($k)), esc_attr(sanitize_text_field(wp_unslash($v)))); } } echo '
'; }, 25); /* [usedkit_conditions]: the grades and what each means, for a shop's "Our grading" page. */ add_shortcode('usedkit_conditions', function () { $out = '
'; foreach (usedkit_grades() as $k => $name) { $out .= sprintf('
%s
%s
', esc_attr($k), esc_html($name), esc_html(usedkit_grade_descriptions()[$k])); } return wp_kses($out . '
', usedkit_allowed_html()); });