'no', 'md_every' => 30, 'md_pct' => 5, 'md_min' => 10, 'md_floor' => 10, 'md_mode' => 'sale'); }); add_filter('usedkit_save_settings', function ($out, $post) { return $out + array( 'markdown' => !empty($post['markdown']) ? 'yes' : 'no', 'md_every' => max(1, min(365, absint($post['md_every'] ?? 30))), 'md_pct' => max(1, min(50, absint($post['md_pct'] ?? 5))), 'md_min' => max(0, absint($post['md_min'] ?? 0)), 'md_floor' => max(0, min(500, absint($post['md_floor'] ?? 0))), 'md_mode' => ($post['md_mode'] ?? '') === 'price' ? 'price' : 'sale', ); }, 10, 2); add_action('usedkit_settings_rows', function ($s) { ?>
', ''); ?>
', '', esc_html(get_woocommerce_currency_symbol())); ?>

(int) $s['md_every'], 'pct' => (float) $s['md_pct'], 'per_run' => 1, 'min_price' => (float) $s['md_min'], 'floor_pct' => (float) $s['md_floor']); } add_action('usedkit_item_panel', function ($product) { if (!usedkit_markdown_on()) { return; } usedkit_panel_heading(__('Markdowns', 'usedkit')); woocommerce_wp_checkbox(array('id' => '_usedkit_no_markdown', 'label' => __('Leave out', 'usedkit'), 'description' => __('Keep this item at the price I set', 'usedkit'), 'value' => $product && $product->get_meta('_usedkit_no_markdown') === 'yes' ? 'yes' : 'no')); }, 30); add_action('woocommerce_admin_process_product_object', function ($product) { if (usedkit_markdown_on() && usedkit_product_form_ok($product) && isset($_POST['usedkit_item_panel'])) { // phpcs:ignore WordPress.Security.NonceVerification -- verified in usedkit_product_form_ok() usedkit_set_meta($product, '_usedkit_no_markdown', !empty($_POST['_usedkit_no_markdown']) ? 'yes' : ''); // phpcs:ignore WordPress.Security.NonceVerification -- verified in usedkit_product_form_ok() } }); /** * Where an item stands: [base, days, done, cost], or null when it is not one to mark * down. A price that is not the one the markdown last set was set by the shop: the * clock starts again from it. */ function usedkit_markdown_state($product) { if (!$product->is_type('simple') || $product->get_status() !== 'publish' || !$product->is_in_stock() || $product->get_meta('_usedkit_no_markdown') === 'yes' || (float) $product->get_regular_price() <= 0 || (function_exists('usedkit_reserved_until') && usedkit_reserved_until($product))) { return null; } $mode = usedkit_settings()['md_mode']; $set = $product->get_meta('_usedkit_md_price'); $now = (float) $product->get_price(); $moved = $set === '' || abs((float) $set - $now) > 0.001 || ($mode === 'sale' && abs((float) $product->get_meta('_usedkit_md_base') - (float) $product->get_regular_price()) > 0.001); if ($moved) { if ($set === '' && $mode === 'sale' && $product->get_sale_price() !== '') { return null; // the shop's own sale: not ours to touch } // A new item's clock runs from its listing, but no further back than one step: // switching markdowns on must not walk a shelf of old stock down several steps. $since = time(); if ($set === '' && $product->get_date_created()) { $since = max($product->get_date_created()->getTimestamp(), time() - (int) usedkit_settings()['md_every'] * DAY_IN_SECONDS); } $base = $mode === 'sale' ? (float) $product->get_regular_price() : $now; $product->update_meta_data('_usedkit_md_base', $base); $product->update_meta_data('_usedkit_md_since', $since); $product->update_meta_data('_usedkit_md_steps', 0); $product->update_meta_data('_usedkit_md_price', $now); $product->save_meta_data(); } $days = (int) floor((time() - (int) $product->get_meta('_usedkit_md_since')) / DAY_IN_SECONDS); $cost = function_exists('usedkit_cost') ? usedkit_cost($product) : null; return array((float) $product->get_meta('_usedkit_md_base'), $days, (int) $product->get_meta('_usedkit_md_steps'), $cost); } /** The daily run. Returns how many prices it lowered. */ function usedkit_run_markdowns() { if (!usedkit_markdown_on()) { return 0; } $cfg = usedkit_markdown_cfg(); $mode = usedkit_settings()['md_mode']; $changed = 0; $page = 1; do { $ids = wc_get_products(array('limit' => 200, 'page' => $page, 'return' => 'ids', 'status' => 'publish', 'stock_status' => 'instock', 'type' => 'simple')); foreach ($ids as $id) { $product = wc_get_product($id); $state = $product ? usedkit_markdown_state($product) : null; if (!$state) { continue; } list($base, $days, $done, $cost) = $state; list($k, $price) = usedkit_markdown_plan($base, $days, $done, $cfg, $cost); if ($k <= $done) { continue; } if ($mode === 'sale') { $product->set_sale_price((string) $price); } else { $product->set_regular_price((string) $price); } $product->update_meta_data('_usedkit_md_steps', $k); $product->update_meta_data('_usedkit_md_price', $price); $product->save(); $changed++; } $page++; } while (count($ids) === 200); return $changed; } add_action('usedkit_daily', 'usedkit_run_markdowns'); /** For the pricing view: "−5 % on 3 Nov", "At its floor", or ''. */ function usedkit_markdown_outlook($product) { $state = usedkit_markdown_on() ? usedkit_markdown_state($product) : null; if (!$state) { return ''; } list($base, $days, $done, $cost) = $state; $cfg = usedkit_markdown_cfg(); list($k, $price) = usedkit_markdown_plan($base, ($done + 1) * $cfg['every'], $done, $cfg, $cost); if ($k <= $done) { return __('At its floor', 'usedkit'); } $in = usedkit_markdown_next_in($days, $done, $cfg['every']); /* translators: 1: new price, 2: date */ return sprintf(__('%1$s on %2$s', 'usedkit'), usedkit_money($price), wp_date(get_option('date_format'), time() + $in * DAY_IN_SECONDS)); }