(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')); ?>