prefix . 'usedkit_offers'; } function usedkit_offers_install() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta('CREATE TABLE ' . usedkit_offers_table() . ' ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, product_id bigint(20) unsigned NOT NULL, name varchar(200) NOT NULL, email varchar(200) NOT NULL, amount decimal(19,4) NOT NULL, price decimal(19,4) NOT NULL, status varchar(20) NOT NULL, token varchar(40) NOT NULL, created_at datetime NOT NULL, decided_at datetime NULL, expires_at datetime NULL, PRIMARY KEY (id), KEY status (status), KEY token (token) ) ' . $wpdb->get_charset_collate() . ';'); update_option('usedkit_offers_version', USEDKIT_OFFERS_VERSION); } add_action('admin_init', function () { if ((int) get_option('usedkit_offers_version') !== USEDKIT_OFFERS_VERSION) { usedkit_offers_install(); } }); add_filter('usedkit_default_settings', function ($d) { return $d + array('offers' => 'no', 'offer_accept' => 95, 'offer_decline' => 70, 'offer_floor' => 10, 'offer_hours' => 48); }); add_filter('usedkit_save_settings', function ($out, $post) { return $out + array( 'offers' => !empty($post['offers']) ? 'yes' : 'no', 'offer_accept' => min(100, absint($post['offer_accept'] ?? 95)), 'offer_decline' => min(100, absint($post['offer_decline'] ?? 70)), 'offer_floor' => min(500, absint($post['offer_floor'] ?? 10)), 'offer_hours' => max(1, min(336, absint($post['offer_hours'] ?? 48))), ); }, 10, 2); add_action('usedkit_settings_rows', function ($s) { ?>
'), usedkit_allowed_html()); ?>
'), usedkit_allowed_html()); ?>
'), usedkit_allowed_html()); ?>
'), usedkit_allowed_html()); ?>

(float) $s['offer_accept'], 'decline' => (float) $s['offer_decline'], 'floor_pct' => (float) $s['offer_floor']); } function usedkit_get_offer($by, $value) { global $wpdb; $table = usedkit_offers_table(); $sql = $by === 'token' ? "SELECT * FROM $table WHERE token = %s" : "SELECT * FROM $table WHERE id = %d"; return $wpdb->get_row($wpdb->prepare($sql, $value), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- the table name only } /* ---- the customer's form ---------------------------------------------------------- */ add_action('woocommerce_single_product_summary', function () { global $product; if (!$product || !usedkit_offers_on() || !$product->is_type('simple') || !$product->is_in_stock() || !$product->is_purchasable() || (float) $product->get_price() <= 0) { return; } $msgs = array('accepted' => array('ok', __('Accepted! We have emailed you a link to buy it at your price.', 'usedkit')), 'review' => array('ok', __('Thanks! We will reply to your offer by email.', 'usedkit')), 'low' => array('err', __('Sorry, that is too low for this item.', 'usedkit')), 'invalid' => array('err', __('Please fill in your name, a valid email address and your offer.', 'usedkit')), 'busy' => array('err', __('Too many requests. Please try again later.', 'usedkit'))); $state = usedkit_form_state($msgs); ?>
>

is_in_stock() || !$product->is_purchasable() || $product->get_status() !== 'publish') { $go('low'); } if ($name === '' || !is_email($email) || $amount <= 0) { $go('invalid'); } if (!usedkit_rate_ok('offer', 5)) { $go('busy'); } $price = (float) $product->get_price(); $cost = function_exists('usedkit_cost') ? usedkit_cost($product) : null; $decision = usedkit_offer_decision($amount, $price, $cost, usedkit_offer_cfg()); if ($decision === 'decline') { $go('low'); // not stored: nothing to keep of it } global $wpdb; $token = strtolower(wp_generate_password(32, false)); $wpdb->insert(usedkit_offers_table(), array( 'product_id' => $id, 'name' => mb_substr($name, 0, 200), 'email' => $email, 'amount' => $amount, 'price' => $price, 'status' => 'pending', 'token' => $token, 'created_at' => current_time('mysql', true))); $offer = usedkit_get_offer('id', $wpdb->insert_id); if ($decision === 'accept') { usedkit_offer_decide($offer, 'accepted'); $go('accepted'); } wp_mail(usedkit_shop_email(), /* translators: 1: amount, 2: item */ sprintf(__('Offer: %1$s for %2$s', 'usedkit'), usedkit_money($amount), $product->get_name()), /* translators: 1: name, 2: email, 3: amount, 4: item, 5: its price, 6: link to the offers page */ sprintf(__("%1\$s <%2\$s> offers %3\$s for %4\$s (price %5\$s).\n\nAccept or decline: %6\$s", 'usedkit'), $name, $email, usedkit_money($amount), $product->get_name(), usedkit_money($price), admin_url('admin.php?page=usedkit-offers')), array('Reply-To: ' . $name . ' <' . $email . '>')); $go('review'); } add_action('admin_post_usedkit_offer', 'usedkit_handle_offer'); add_action('admin_post_nopriv_usedkit_offer', 'usedkit_handle_offer'); /** Accept or decline an offer, and tell the customer. */ function usedkit_offer_decide($offer, $status) { global $wpdb; $hours = (int) usedkit_settings()['offer_hours']; $wpdb->update(usedkit_offers_table(), array( 'status' => $status, 'decided_at' => current_time('mysql', true), 'expires_at' => $status === 'accepted' ? gmdate('Y-m-d H:i:s', time() + $hours * HOUR_IN_SECONDS) : null, ), array('id' => $offer['id'])); $product = wc_get_product($offer['product_id']); $item = $product ? $product->get_name() : '#' . $offer['product_id']; $shop = get_bloginfo('name'); $headers = array('Reply-To: ' . $shop . ' <' . usedkit_shop_email() . '>'); if ($status === 'accepted') { wp_mail($offer['email'], /* translators: %s: item */ sprintf(__('Your offer was accepted: %s', 'usedkit'), $item), /* translators: 1: name, 2: amount, 3: item, 4: hours, 5: link */ sprintf(__("Hi %1\$s,\n\nYour offer of %2\$s for %3\$s was accepted. Buy it at that price within %4\$d hours:\n\n%5\$s\n\nIt is one of a kind and for sale to others meanwhile, so do not wait too long.", 'usedkit'), $offer['name'], usedkit_money($offer['amount']), $item, $hours, add_query_arg('usedkit_offer', $offer['token'], home_url('/'))), $headers); } elseif ($status === 'declined') { wp_mail($offer['email'], /* translators: %s: item */ sprintf(__('Your offer for %s', 'usedkit'), $item), /* translators: 1: name, 2: item, 3: shop */ sprintf(__("Hi %1\$s,\n\nThank you for your offer for %2\$s. We cannot accept it this time, but the item is still for sale at its price.\n\n%3\$s", 'usedkit'), $offer['name'], $item, $shop), $headers); } } /* ---- buying at the offered price ---------------------------------------------------- */ /** An accepted offer that can still be used, or null. */ function usedkit_live_offer($token) { $offer = $token ? usedkit_get_offer('token', $token) : null; if (!$offer || $offer['status'] !== 'accepted' || strtotime($offer['expires_at'] . ' UTC') < time()) { return null; } return $offer; } add_action('template_redirect', function () { if (empty($_GET['usedkit_offer']) || !usedkit_offers_on() || !function_exists('WC') || !WC()->cart) { // phpcs:ignore WordPress.Security.NonceVerification return; } $offer = usedkit_live_offer(sanitize_key($_GET['usedkit_offer'])); // phpcs:ignore WordPress.Security.NonceVerification $product = $offer ? wc_get_product($offer['product_id']) : null; if (!$product || !$product->is_in_stock() || !$product->is_purchasable()) { wc_add_notice(__('This offer has ended or the item has sold.', 'usedkit'), 'error'); wp_safe_redirect($product ? get_permalink($product->get_id()) : wc_get_page_permalink('shop')); exit; } foreach (WC()->cart->get_cart() as $key => $line) { if ((int) $line['product_id'] === (int) $product->get_id()) { WC()->cart->remove_cart_item($key); } } WC()->cart->add_to_cart($product->get_id(), 1, 0, array(), array('usedkit_offer' => $offer['token'])); wp_safe_redirect(wc_get_cart_url()); exit; }); add_action('woocommerce_before_calculate_totals', function ($cart) { foreach ($cart->get_cart() as $line) { if (!empty($line['usedkit_offer'])) { $offer = usedkit_live_offer($line['usedkit_offer']); if ($offer && (int) $offer['product_id'] === (int) $line['product_id']) { $line['data']->set_price((float) $offer['amount']); } } } }, 20); add_filter('woocommerce_get_item_data', function ($data, $line) { if (!empty($line['usedkit_offer']) && usedkit_live_offer($line['usedkit_offer'])) { $data[] = array('key' => __('Price', 'usedkit'), 'value' => __('Your accepted offer', 'usedkit')); } return $data; }, 10, 2); add_action('woocommerce_checkout_create_order_line_item', function ($item, $key, $values) { if (!empty($values['usedkit_offer'])) { $item->add_meta_data('_usedkit_offer', $values['usedkit_offer'], true); } }, 10, 3); add_action('woocommerce_order_status_changed', function ($order_id, $from, $to, $order) { if (!in_array($to, array('processing', 'completed'), true) || !$order) { return; } global $wpdb; foreach ($order->get_items() as $item) { if ($token = $item->get_meta('_usedkit_offer')) { $wpdb->update(usedkit_offers_table(), array('status' => 'used'), array('token' => $token)); } } }, 10, 4); /* ---- the shop's page ------------------------------------------------------------------ */ add_action('admin_menu', function () { if (!usedkit_offers_on()) { return; } global $wpdb; $n = (int) $wpdb->get_var("SELECT COUNT(*) FROM " . usedkit_offers_table() . " WHERE status = 'pending'"); // phpcs:ignore $label = __('Offers', 'usedkit') . ($n ? ' ' . $n . '' : ''); add_submenu_page('woocommerce', __('Offers', 'usedkit'), $label, 'manage_woocommerce', 'usedkit-offers', 'usedkit_offers_page'); }, 20); add_action('admin_post_usedkit_offer_decide', function () { $id = absint($_GET['offer'] ?? 0); check_admin_referer('usedkit_offer_' . $id); if (!current_user_can('manage_woocommerce')) { wp_die(esc_html__('Not allowed.', 'usedkit')); } $offer = usedkit_get_offer('id', $id); $to = sanitize_key($_GET['do'] ?? '') === 'accept' ? 'accepted' : 'declined'; if ($offer && $offer['status'] === 'pending') { usedkit_offer_decide($offer, $to); } wp_safe_redirect(admin_url('admin.php?page=usedkit-offers')); exit; }); function usedkit_offers_page() { if (!current_user_can('manage_woocommerce')) { return; } global $wpdb; $rows = $wpdb->get_results("SELECT * FROM " . usedkit_offers_table() . " ORDER BY status = 'pending' DESC, created_at DESC LIMIT 200", ARRAY_A); // phpcs:ignore $names = array('pending' => __('Waiting for you', 'usedkit'), 'accepted' => __('Accepted', 'usedkit'), 'declined' => __('Declined', 'usedkit'), 'used' => __('Bought', 'usedkit')); ?>

0 ? round(100 * $o['amount'] / $o['price']) : 0; ?>
get_id())) . '">' . esc_html($product->get_name()) . '' : '#' . (int) $o['product_id']; ?> is_in_stock()) : ?> () ( %)
__('Accept', 'usedkit'), 'decline' => __('Decline', 'usedkit')) as $do => $label) { printf('%s ', esc_attr($do === 'accept' ? ' button-primary' : ''), esc_url(wp_nonce_url(admin_url('admin-post.php?action=usedkit_offer_decide&do=' . $do . '&offer=' . $o['id']), 'usedkit_offer_' . $o['id'])), esc_html($label)); } else : echo esc_html($status); endif; ?>
query($wpdb->prepare('DELETE FROM ' . usedkit_offers_table() . ' WHERE created_at < %s', gmdate('Y-m-d H:i:s', time() - 365 * DAY_IN_SECONDS))); // phpcs:ignore });