'yes'); }); add_filter('usedkit_save_settings', function ($out, $post) { return $out + array('reference' => !empty($post['reference']) ? 'yes' : 'no'); }, 10, 2); add_action('usedkit_settings_rows', function ($s) { ?> key]: stored ones while the title is unchanged, the * rest asked of the service in one call and stored. When the service cannot be reached * (or is not to be asked: a customer's page view does not wait on it) the title's words * stand in, unstored, so it is asked again next time. */ function usedkit_model_keys($products, $ask_service = true) { $out = array(); $ask = array(); foreach ($products as $p) { $for = md5(USEDKIT_KEYS_VERSION . $p->get_name()); if ($p->get_meta('_usedkit_model_for') === $for && $p->get_meta('_usedkit_model') !== '') { $out[$p->get_id()] = $p->get_meta('_usedkit_model'); } else { $ask[$p->get_id()] = $p; } } foreach (array_chunk($ask, 500, true) as $chunk) { $res = $ask_service ? usedkit_call('/v1/keys', array('external_ids' => array_map('strval', array_keys($chunk)))) : new WP_Error('usedkit_not_asked', ''); $found = (!is_wp_error($res) && wp_remote_retrieve_response_code($res) === 200) ? (json_decode(wp_remote_retrieve_body($res), true)['products'] ?? array()) : null; foreach ($chunk as $id => $p) { $key = $found[(string) $id]['key'] ?? ''; $key = $key ?: usedkit_plain_key($p->get_name()); $out[$id] = $key; if ($found !== null && $key !== '') { update_post_meta($id, '_usedkit_model', $key); update_post_meta($id, '_usedkit_model_for', md5(USEDKIT_KEYS_VERSION . $p->get_name())); // The model's name as the service knows it, for its page (seo.php). update_post_meta($id, '_usedkit_model_label', (string) ($found[(string) $id]['label'] ?? '')); } } } return $out; } /** The model key a customer's words name (the trade-in form). */ function usedkit_query_key($query) { $res = usedkit_call('/v1/keys', array('queries' => array($query))); $found = (!is_wp_error($res) && wp_remote_retrieve_response_code($res) === 200) ? (json_decode(wp_remote_retrieve_body($res), true)['queries'][$query]['key'] ?? '') : ''; return $found ?: usedkit_plain_key($query); } /** What a product sold for: the order's price where recorded, else its price. */ function usedkit_sold_price($product) { $p = $product->get_meta('_usedkit_sold_price'); return $p !== '' ? (float) $p : (float) $product->get_price(); } /** Every sale of the last years by model key, [key => [sale, ...]]; kept half a day, * rebuilt when an item sells or comes back. */ function usedkit_sales_index() { $index = get_transient('usedkit_sales_index'); if (is_array($index)) { return $index; } $products = wc_get_products(array('limit' => 3000, 'stock_status' => 'outofstock', 'status' => array('publish', 'private'), 'meta_key' => '_usedkit_sold_at', 'meta_compare' => '>=', 'meta_value' => time() - USEDKIT_REFERENCE_YEARS * 365 * DAY_IN_SECONDS)); $keys = usedkit_model_keys($products); $index = array(); foreach ($products as $p) { $key = $keys[$p->get_id()] ?? ''; if ($key === '') { continue; } $sold = (int) $p->get_meta('_usedkit_sold_at'); $listed = $p->get_date_created() ? $p->get_date_created()->getTimestamp() : null; $index[$key][] = array('id' => $p->get_id(), 'price' => usedkit_sold_price($p), 'at' => $sold, 'days' => $listed ? max(0, (int) round(($sold - $listed) / DAY_IN_SECONDS)) : null, 'grade' => function_exists('usedkit_condition') ? usedkit_condition($p) : ''); } set_transient('usedkit_sales_index', $index, 12 * HOUR_IN_SECONDS); return $index; } function usedkit_sales_index_reset() { delete_transient('usedkit_sales_index'); } add_action('woocommerce_product_set_stock_status', 'usedkit_sales_index_reset'); add_action('usedkit_sold_price_recorded', 'usedkit_sales_index_reset'); /** The reference for a key, leaving one product out (the one being priced). */ function usedkit_reference($key, $except = 0) { if ($key === '') { return null; } $sales = array_filter(usedkit_sales_index()[$key] ?? array(), function ($s) use ($except) { return $s['id'] !== $except; }); return usedkit_reference_stats($sales); } function usedkit_product_reference($product) { $keys = usedkit_model_keys(array($product)); return usedkit_reference($keys[$product->get_id()] ?? '', $product->get_id()); } /** "4 sold: median 410 € (350–460 €), typically 12 days to sell." */ function usedkit_reference_text($r) { $range = $r['count'] > 1 ? ' (' . usedkit_money($r['low']) . '–' . usedkit_money($r['high']) . ')' : ''; /* translators: 1: number sold, 2: median price, 3: price range in parentheses or nothing */ $text = sprintf(_n('%1$d sold: %2$s%3$s.', '%1$d sold: median %2$s%3$s.', $r['count'], 'usedkit'), $r['count'], usedkit_money($r['median']), $range); if ($r['days'] !== null) { /* translators: %d: number of days */ $text .= ' ' . sprintf(_n('Typically %d day to sell.', 'Typically %d days to sell.', $r['days'], 'usedkit'), $r['days']); } return $text; } /* A box beside the price while editing an item. */ add_action('add_meta_boxes', function () { if (!usedkit_reference_on()) { return; } add_meta_box('usedkit_reference', __('Your sales of this model', 'usedkit'), function ($post) { $product = wc_get_product($post->ID); $r = $product && $product->get_name() !== '' ? usedkit_product_reference($product) : null; if (!$r) { echo '

' . esc_html__('None sold yet: the first one sets the price.', 'usedkit') . '

'; return; } echo '

' . esc_html(usedkit_reference_text($r)) . '

'; }, 'product', 'side', 'default'); });