'no', 'reserve_hours' => 24); }); add_filter('usedkit_save_settings', function ($out, $post) { return $out + array('reserve' => !empty($post['reserve']) ? 'yes' : 'no', 'reserve_hours' => max(1, min(168, absint($post['reserve_hours'] ?? 24)))); }, 10, 2); add_action('usedkit_settings_rows', function ($s) { ?>

get_meta('_usedkit_reserved_until'); if ($until && $until <= time()) { usedkit_release($product); return 0; } return $until; } function usedkit_release($product) { $product->delete_meta_data('_usedkit_reserved_until'); $product->delete_meta_data('_usedkit_reserved_by'); $product->save_meta_data(); wc_delete_product_transients($product->get_id()); } /** Where the shop's mail goes: the trade-in address, else the site's. */ function usedkit_shop_email() { $s = usedkit_settings(); return $s['trade_email'] ?: get_option('admin_email'); } /** The store's address from WooCommerce's settings, one line. */ function usedkit_store_address() { $parts = array(get_option('woocommerce_store_address'), get_option('woocommerce_store_address_2'), trim(get_option('woocommerce_store_postcode') . ' ' . get_option('woocommerce_store_city'))); return implode(', ', array_filter(array_map('trim', $parts))); } add_filter('woocommerce_is_purchasable', function ($ok, $product) { return $ok && !(usedkit_reserve_on() && usedkit_reserved_until($product)); }, 10, 2); add_action('woocommerce_single_product_summary', function () { global $product; if (!$product || !usedkit_reserve_on() || !$product->is_in_stock() || !$product->is_type('simple')) { return; } $msgs = array('reserved' => array('ok', __('Reserved for you. We have emailed you the details.', 'usedkit')), 'taken' => array('err', __('Someone reserved this a moment ago.', 'usedkit')), 'invalid' => array('err', __('Please fill in your name and a valid email address.', 'usedkit')), 'busy' => array('err', __('Too many requests. Please try again later.', 'usedkit'))); $state = usedkit_form_state($msgs); $until = usedkit_reserved_until($product); if ($until) { $when = wp_date(get_option('date_format') . ' ' . get_option('time_format'), $until); /* translators: %s: date and time */ $held = sprintf(__('Reserved until %s.', 'usedkit'), $when); echo wp_kses('

' . usedkit_icon('clock') . esc_html($held) . '

' . $state, usedkit_allowed_html()); return; } ?>
>

is_in_stock() || $product->get_status() !== 'publish') { $go('taken'); } if ($name === '' || !is_email($email)) { $go('invalid'); } if (usedkit_reserved_until($product)) { $go('taken'); } if (!usedkit_rate_ok('reserve', 3)) { $go('busy'); } $until = time() + (int) usedkit_settings()['reserve_hours'] * HOUR_IN_SECONDS; $product->update_meta_data('_usedkit_reserved_until', $until); $product->update_meta_data('_usedkit_reserved_by', array('name' => $name, 'email' => $email, 'phone' => $phone, 'at' => time())); $product->save_meta_data(); wc_delete_product_transients($id); $when = wp_date(get_option('date_format') . ' ' . get_option('time_format'), $until); $shop = get_bloginfo('name'); $address = usedkit_store_address(); /* translators: 1: item, 2: date and time */ wp_mail($email, sprintf(__('Reserved: %1$s until %2$s', 'usedkit'), $product->get_name(), $when), /* translators: 1: customer's name, 2: item, 3: date and time, 4: shop name, 5: shop address */ sprintf(__("Hi %1\$s,\n\n%2\$s is reserved for you until %3\$s. Come and see it at %4\$s%5\$s.\n\nIf you change your mind, just reply to this email.", 'usedkit'), $name, $product->get_name(), $when, $shop, $address ? ', ' . $address : ''), array('Reply-To: ' . $shop . ' <' . usedkit_shop_email() . '>')); wp_mail(usedkit_shop_email(), /* translators: %s: item */ sprintf(__('Reservation: %s', 'usedkit'), $product->get_name()), /* translators: 1: item, 2: date and time, 3: name, 4: email, 5: phone, 6: link to the product in the admin */ sprintf(__("%1\$s is reserved until %2\$s for:\n\n%3\$s\n%4\$s\n%5\$s\n\nRelease it or mark it sold: %6\$s", 'usedkit'), $product->get_name(), $when, $name, $email, $phone, admin_url('post.php?action=edit&post=' . $id)), array('Reply-To: ' . $name . ' <' . $email . '>')); $go('reserved'); } add_action('admin_post_usedkit_reserve', 'usedkit_handle_reserve'); add_action('admin_post_nopriv_usedkit_reserve', 'usedkit_handle_reserve'); /* On the item's edit screen: who holds it, and release or sold. */ add_action('add_meta_boxes', function () { if (!usedkit_reserve_on()) { return; } $product = wc_get_product(get_the_ID()); if (!$product || !usedkit_reserved_until($product)) { return; } add_meta_box('usedkit_hold', __('Reserved', 'usedkit'), function ($post) { $product = wc_get_product($post->ID); $by = (array) $product->get_meta('_usedkit_reserved_by'); printf('

%s
%s
%s
%s

', esc_html(sprintf( /* translators: %s: date and time */ __('Until %s for', 'usedkit'), wp_date(get_option('date_format') . ' ' . get_option('time_format'), usedkit_reserved_until($product)))), esc_html($by['name'] ?? ''), esc_html($by['email'] ?? ''), esc_html($by['phone'] ?? '')); foreach (array('release' => __('Release', 'usedkit'), 'sold' => __('Sold in the shop', 'usedkit')) as $do => $label) { printf('%s ', esc_url(wp_nonce_url(admin_url('admin-post.php?action=usedkit_hold&do=' . $do . '&product=' . $post->ID), 'usedkit_hold_' . $post->ID)), esc_html($label)); } }, 'product', 'side', 'high'); }); add_action('admin_post_usedkit_hold', function () { $id = absint($_GET['product'] ?? 0); check_admin_referer('usedkit_hold_' . $id); if (!current_user_can('edit_product', $id) || !($product = wc_get_product($id))) { wp_die(esc_html__('Not allowed.', 'usedkit')); } usedkit_release($product); if (sanitize_key($_GET['do'] ?? '') === 'sold') { $product = wc_get_product($id); if ($product->managing_stock()) { $product->set_stock_quantity(0); } $product->set_stock_status('outofstock'); $product->save(); } wp_safe_redirect(admin_url('post.php?action=edit&post=' . $id)); exit; }); /* Held items in the product list. */ add_filter('woocommerce_admin_stock_html', function ($html, $product) { if (usedkit_reserve_on() && ($until = usedkit_reserved_until($product))) { /* translators: %s: date and time */ $html .= '
' . esc_html(sprintf(__('Reserved until %s', 'usedkit'), wp_date('j.n. H:i', $until))) . ''; } return $html; }, 10, 2);