'', 'secret_key' => '', 'open' => 'button', 'button_text' => $sl ? 'Osvoji nagrado' : __('Win a prize', 'playmint'), 'position' => 'bottom-right', 'auto_apply' => 1, 'api' => PLAYMINT_DEFAULT_API, ]; } function playmint_settings($saved = null) { if ($saved === null) $saved = get_option(PLAYMINT_OPTION, []); $s = array_merge(playmint_defaults(), is_array($saved) ? $saved : []); // A broken value means "use the default", and so does a blank text or URL field. foreach (playmint_defaults() as $k => $default) { if (!is_scalar($s[$k]) || (in_array($k, ['button_text', 'api'], true) && $s[$k] === '')) $s[$k] = $default; } return $s; } // The secret key connects the site; Playmint then picks the campaign it shows. function playmint_is_configured($p) { return $p['public_key'] !== '' && playmint_has_secret($p); } function playmint_has_secret($p) { return strpos($p['secret_key'], 'sk_') === 0; } // Scripts and API calls only ever go to Playmint's own servers (production or dev). function playmint_is_service_url($url) { $parts = wp_parse_url($url); if (!is_array($parts) || ($parts['scheme'] ?? '') !== 'https' || empty($parts['host'])) return false; $host = strtolower($parts['host']); return $host === 'play-mint.com' || substr($host, -strlen('.play-mint.com')) === '.play-mint.com'; } // api.play-mint.com -> dashboard.play-mint.com, api-dev -> dashboard-dev. function playmint_dashboard_url($p, $route = '') { $host = (string) wp_parse_url($p['api'], PHP_URL_HOST); $dashboard = strpos($host, 'api') === 0 ? 'dashboard' . substr($host, 3) : 'dashboard.play-mint.com'; return 'https://' . $dashboard . '/' . ($route !== '' ? '#/' . $route : ''); } // The origins a shopper's browser sends: this site's address, with and without "www.". function playmint_site_origins() { $parts = wp_parse_url(home_url()); $scheme = $parts['scheme'] ?? 'https'; $host = $parts['host'] ?? ''; $port = isset($parts['port']) ? ':' . $parts['port'] : ''; $origins = [$scheme . '://' . $host . $port]; $is_name = $host !== '' && strpos($host, '.') !== false && !filter_var($host, FILTER_VALIDATE_IP); if ($is_name) { $other = strpos($host, 'www.') === 0 ? substr($host, 4) : 'www.' . $host; $origins[] = $scheme . '://' . $other . $port; } return $origins; } // ---- Which campaign runs ------------------------------------------------------------------------ // The campaign this site runs and how its players are known: the one chosen for it in the dashboard's Your shops // while it's live, else the newest live one. ['id' => …, 'identity' => 'merchant'|'email'] or null. // Cached five minutes, a failed lookup one, so a slow Playmint never slows every page. function playmint_campaign($p) { $for = md5($p['api'] . '|' . $p['public_key'] . '|' . $p['secret_key']); $cached = get_transient('playmint_campaign'); if (is_array($cached) && ($cached['for'] ?? '') === $for) return $cached['campaign']; $answered = false; $campaign = playmint_fetch_campaign($p, $answered); set_transient('playmint_campaign', ['for' => $for, 'campaign' => $campaign], $answered ? 5 * MINUTE_IN_SECONDS : MINUTE_IN_SECONDS); return $campaign; } function playmint_fetch_campaign($p, &$answered) { $answered = true; if (!playmint_has_secret($p)) return null; $res = wp_remote_get(add_query_arg(['siteUrl' => home_url()], untrailingslashit($p['api']) . '/api/woocommerce/campaign'), [ 'timeout' => 3, 'headers' => ['x-api-key' => $p['secret_key']], ]); $status = is_wp_error($res) ? 0 : (int) wp_remote_retrieve_response_code($res); $body = $status ? json_decode(wp_remote_retrieve_body($res), true) : null; $answered = $status === 200 || $status === 204; if ($status !== 200 || !is_array($body) || empty($body['campaignId'])) return null; return ['id' => (string) $body['campaignId'], 'identity' => ($body['playerIdentity'] ?? '') === 'email' ? 'email' : 'merchant']; } // ---- Front end: the game, its button and the held prize ----------------------------------------- // Proves the signed-in customer to Playmint, which refuses a sign-in play without it: a day's expiry and an HMAC // of the campaign, the customer and that expiry, keyed with the secret key, which never leaves this server. function playmint_user_token($p, $campaign_id, $user_id) { $expires = time() + DAY_IN_SECONDS; return $expires . '.' . hash_hmac('sha256', $campaign_id . '.' . $user_id . '.' . $expires, $p['secret_key']); } // The texts of the button's prize panel, the same as the Shopify app's. function playmint_front_texts() { $sl = playmint_is_sl(); return [ 'won' => $sl ? 'Osvojili ste' : __('You won', 'playmint'), /* translators: %s: the prize, e.g. "10% off" */ 'yours' => $sl ? 'Vaša nagrada: %s' : __('%s is yours', 'playmint'), 'prize' => $sl ? 'Vaša nagrada' : __('Your prize', 'playmint'), 'copy' => $sl ? 'Kopiraj' : __('Copy', 'playmint'), 'copied' => $sl ? 'Kopirano!' : __('Copied!', 'playmint'), 'apply' => $sl ? 'Uveljavi v košarici' : __('Apply to cart', 'playmint'), 'signIn' => $sl ? 'Prijavite se, da vidite svojo kodo.' : __('Sign in to see your code.', 'playmint'), 'signInEmail' => $sl ? 'Prijavite se z e-naslovom, s katerim ste igrali, da vidite svojo kodo.' : __('Sign in with the email you played with to see your code.', 'playmint'), 'notSaved' => $sl ? 'Vaša koda ni shranjena v tej napravi. Trgovino prosite, naj jo poišče po vašem e-naslovu.' : __('Your code isn\'t saved on this device. Ask the shop to look it up by your email.', 'playmint'), 'sameEmail' => $sl ? 'Ob nakupu uporabite isti e-naslov: koda deluje samo z njim.' : __('Use the same email at checkout: the code only works with it.', 'playmint'), 'onRecord' => $sl ? 'Ničesar vam ni treba vnesti, trgovina ima nagrado zabeleženo.' : __('Nothing to enter, the shop has this on record.', 'playmint'), 'notApplied' => $sl ? 'Kode ni bilo mogoče uveljaviti. Vnesite jo ob nakupu.' : __('The code couldn\'t be applied. Enter it at checkout.', 'playmint'), ]; } add_action('wp_enqueue_scripts', function () { $p = playmint_settings(); if (!playmint_is_configured($p)) return; $campaign = playmint_campaign($p); if (!$campaign) return; $options = [ 'apiKey' => $p['public_key'], 'apiUrl' => untrailingslashit($p['api']), 'campaignId' => $campaign['id'], 'identity' => $campaign['identity'], 'open' => $p['open'], 'buttonText' => $p['button_text'], 'position' => $p['position'], 'autoApply' => (bool) $p['auto_apply'], 'emailLookup' => playmint_email_lookup(), 'text' => playmint_front_texts(), ]; // Never pop up in the middle of paying. if (function_exists('is_checkout') && (is_checkout() || is_cart()) && $options['open'] === 'once') $options['open'] = 'button'; // Logged-out visitors are user 0, and they'd all share it; an email campaign takes no id at all. $user = wp_get_current_user(); if ($campaign['identity'] === 'merchant' && $user->ID) { $options['userId'] = (string) $user->ID; $options['userToken'] = playmint_user_token($p, $campaign['id'], $options['userId']); } if (function_exists('wc_get_page_permalink')) $options['loginUrl'] = wc_get_page_permalink('myaccount'); // Apply to cart goes through WooCommerce's own Store API, which keeps a guest's cart session and its nonce. if (class_exists('WooCommerce')) { $options['storeApi'] = esc_url_raw(rest_url('wc/store/v1/')); $options['cartUrl'] = wc_get_cart_url(); } // A signed-in customer's prize, looked up here with the secret key: on every page, from any device. if ($user->ID) { $prize = playmint_customer_prize($p, $user); if ($prize && (!class_exists('WooCommerce') || playmint_coupon_usable($prize['code']))) $options['held'] = $prize; } // The service's loader: about 3 KB. It downloads a game only when a visitor opens one. // Built from apps/playmint-loader (npm run build:wordpress). The game itself comes from Playmint when opened. wp_enqueue_script('playmint-loader', plugins_url('assets/playmint-loader.js', __FILE__), [], PLAYMINT_VERSION, true); wp_enqueue_script('playmint-front', plugins_url('assets/playmint-front.js', __FILE__), ['playmint-loader'], PLAYMINT_VERSION, true); wp_add_inline_script('playmint-front', 'window.playmintOptions = ' . wp_json_encode($options) . ';', 'before'); }); // ---- Used-code reporting ----------------------------------------------------------------------- function playmint_report_order($order) { $p = playmint_settings(); if (strpos($p['secret_key'], 'sk_') !== 0) return; if (!$order || $order->get_meta('_playmint_reported')) return; $codes = $order->get_coupon_codes(); if (!$codes) return; $order->update_meta_data('_playmint_reported', 1); $order->save(); // After the checkout's response has gone out where the server allows it. Codes that aren't Playmint's are ignored there. register_shutdown_function(function () use ($codes, $p) { if (function_exists('fastcgi_finish_request')) fastcgi_finish_request(); wp_remote_post(untrailingslashit($p['api']) . '/api/woocommerce/redeemed', [ 'timeout' => 3, 'headers' => ['Content-Type' => 'application/json', 'x-api-key' => $p['secret_key']], 'body' => wp_json_encode(['codes' => array_slice(array_values($codes), 0, 50)]), ]); }); } add_action('woocommerce_checkout_order_processed', function ($order_id) { playmint_report_order(wc_get_order($order_id)); }); add_action('woocommerce_store_api_checkout_order_processed', 'playmint_report_order'); // ---- Winners' codes, made by Playmint ---------------------------------------------------------- // Once Playmint knows this shop turns its codes into coupons, it gives each winner a code of its own // ("PM" and ten characters), so prizes need no pasted codes. Announced on every settings check and // daily; deactivating the plugin takes it back. Returns whether Playmint took it. function playmint_announce_site($p, $connected = true) { if (strpos($p['secret_key'], 'sk_') !== 0) return false; // Without WooCommerce the site is still listed and allowed, but no coupons can be made there. $woo = function_exists('get_woocommerce_currency'); $res = wp_remote_post(untrailingslashit($p['api']) . '/api/woocommerce/site' . ($connected ? '' : '/disconnect'), [ 'timeout' => 5, 'headers' => ['Content-Type' => 'application/json', 'x-api-key' => $p['secret_key']], 'body' => wp_json_encode(array_filter(['siteUrl' => home_url(), 'currency' => $woo ? get_woocommerce_currency() : null, 'coupons' => $woo], function ($v) { return $v !== null; })), ]); return !is_wp_error($res) && wp_remote_retrieve_response_code($res) === 204; } add_action('playmint_daily', function () { playmint_announce_site(playmint_settings()); }); // Scheduled here rather than on activation, which an update from an older version never runs. add_action('init', function () { if (!wp_next_scheduled('playmint_daily')) wp_schedule_event(time() + HOUR_IN_SECONDS, 'daily', 'playmint_daily'); }); register_deactivation_hook(__FILE__, function () { wp_clear_scheduled_hook('playmint_daily'); playmint_announce_site(playmint_settings(), false); }); // A winner's code becomes a real coupon the first time anything looks it up: applying it, or the prize // notice on the cart. Only codes shaped like Playmint's are asked about, and only while no coupon has it. // WooCommerce reads the coupon by its code right after this filter, so it finds the one just made. add_filter('woocommerce_get_shop_coupon_data', function ($data, $code) { if ($data !== false || !is_string($code) || !preg_match('/^pm[a-z2-9]{10}$/i', $code)) return $data; if (wc_get_coupon_id_by_code($code)) return $data; $win = playmint_lookup_win($code); if ($win) playmint_make_coupon($code, $win); return $data; }, 10, 2); // The win behind a code, or null when it isn't one or is used. A code Playmint doesn't know is // remembered for a minute, so a mistyped one isn't asked about on every cart refresh. function playmint_lookup_win($code) { $p = playmint_settings(); if (strpos($p['secret_key'], 'sk_') !== 0) return null; $miss = 'playmint_nowin_' . md5(strtolower($code)); if (get_transient($miss)) return null; // Saying which site asks: a code is only honoured on the site it was won on. $res = wp_remote_get(add_query_arg(['siteUrl' => home_url()], untrailingslashit($p['api']) . '/api/woocommerce/coupons/' . rawurlencode($code)), [ 'timeout' => 4, 'headers' => ['x-api-key' => $p['secret_key']], ]); if (is_wp_error($res)) return null; $win = json_decode(wp_remote_retrieve_body($res), true); if (wp_remote_retrieve_response_code($res) !== 200 || !is_array($win) || !empty($win['redeemed'])) { set_transient($miss, 1, MINUTE_IN_SECONDS); return null; } return $win; } // Who may use it: the email typed on an email campaign, or the account's email on a sign-in campaign. function playmint_winner_email($win) { if (!empty($win['email']) && is_email($win['email'])) return $win['email']; if (!empty($win['customerId']) && ctype_digit((string) $win['customerId'])) { $user = get_userdata((int) $win['customerId']); if ($user && is_email($user->user_email)) return $user->user_email; } return ''; } // Single use, for the winner's email only, gone when the campaign ends. function playmint_make_coupon($code, $win) { $discount = isset($win['discount']) && is_array($win['discount']) ? $win['discount'] : []; $kind = $discount['kind'] ?? ''; if (!in_array($kind, ['percent', 'amount', 'free_shipping'], true)) return; $amount = isset($discount['amount']) ? max(0, (float) $discount['amount']) : 0; // Two requests applying a brand-new code at once must not make two coupons. A lock a crashed // request left behind is taken over after a minute. $lock = 'playmint_making_' . md5(strtolower($code)); if (!add_option($lock, time(), '', 'no')) { if ((int) get_option($lock) > time() - MINUTE_IN_SECONDS) return; update_option($lock, time(), 'no'); } try { if (wc_get_coupon_id_by_code($code)) return; $coupon = new WC_Coupon(); $coupon->set_code($code); $coupon->set_discount_type($kind === 'percent' ? 'percent' : 'fixed_cart'); $coupon->set_amount($kind === 'percent' ? min(100, $amount) : ($kind === 'amount' ? $amount : 0)); if ($kind === 'free_shipping') $coupon->set_free_shipping(true); $coupon->set_individual_use(true); $coupon->set_usage_limit(1); $coupon->set_usage_limit_per_user(1); $expires = !empty($win['expiresAt']) ? strtotime((string) $win['expiresAt']) : false; if ($expires) $coupon->set_date_expires($expires); $email = playmint_winner_email($win); if ($email !== '') $coupon->set_email_restrictions([$email]); /* translators: %s: the prize's name, e.g. 10% off */ $coupon->set_description(sprintf(__('Playmint prize: %s', 'playmint'), sanitize_text_field((string) ($win['label'] ?? '')))); $coupon->save(); } finally { delete_option($lock); } } // ---- A logged-in customer's prize on any device ------------------------------------------------ // The id to ask /reward about: the customer id for sign-in campaigns, the account email for email // campaigns, and only when WooCommerce makes customers confirm it (they set their password from an // emailed link). Never an id from the browser: /identify hands those to anyone who types the email. // Whether an account here proves its email: WooCommerce emails new accounts a link to set their password. // Without that, anyone could register under a winner's address, so email campaigns look nothing up. function playmint_email_lookup() { return get_option('woocommerce_registration_generate_password') === 'yes'; } function playmint_prize_lookup_id($campaign, $user) { if ($campaign['identity'] === 'merchant') return (string) $user->ID; return playmint_email_lookup() ? $user->user_email : ''; } function playmint_prize_key($campaign, $lookup_id) { return 'playmint_prize_' . md5($campaign['id'] . '|' . $lookup_id); } // The customer's unspent prize from Playmint: ['label' => …, 'code' => …, 'emailOnly' => bool], or null. // Every page a signed-in customer opens asks, so answers are cached: a prize 5 minutes, "nothing" one, since // they may win in another browser any moment. An error means nothing, uncached. function playmint_customer_prize($p, $user) { $campaign = playmint_campaign($p); if (!$campaign) return null; $id = playmint_prize_lookup_id($campaign, $user); if ($id === '') return null; $key = playmint_prize_key($campaign, $id); $cached = get_transient($key); if (is_array($cached)) return $cached ?: null; $res = wp_remote_get(add_query_arg(['campaignId' => $campaign['id'], 'userId' => $id], untrailingslashit($p['api']) . '/api/sdk/reward'), [ 'timeout' => 2, 'headers' => ['x-api-key' => $p['secret_key']], ]); if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) return null; $won = json_decode(wp_remote_retrieve_body($res), true); $prize = (is_array($won) && empty($won['redeemed']) && !empty($won['code']) && is_string($won['code'])) ? ['label' => (string) ($won['label'] ?? ''), 'code' => $won['code'], 'emailOnly' => !empty($won['emailOnly'])] : []; set_transient($key, $prize, $prize ? 5 * MINUTE_IN_SECONDS : MINUTE_IN_SECONDS); return $prize ?: null; } // Signing in looks again: someone who just won with this account's address elsewhere expects the code now. add_action('wp_login', function ($login, $user) { $p = playmint_settings(); $campaign = playmint_is_configured($p) ? playmint_campaign($p) : null; if (!$campaign) return; $id = playmint_prize_lookup_id($campaign, $user); if ($id !== '') delete_transient(playmint_prize_key($campaign, $id)); }, 10, 2); function playmint_customer_code($p, $user) { $prize = playmint_customer_prize($p, $user); return $prize ? $prize['code'] : ''; } function playmint_on_prize_page() { return function_exists('is_cart') && (is_cart() || is_checkout() || is_account_page()); } // Exists, not used up, not expired. The cached answer can lag an order that just spent the code. function playmint_coupon_usable($code) { $coupon = new WC_Coupon($code); if (!$coupon->get_id()) return false; if ($coupon->get_usage_limit() && $coupon->get_usage_count() >= $coupon->get_usage_limit()) return false; $expires = $coupon->get_date_expires(); return !$expires || $expires->getTimestamp() > time(); } add_action('template_redirect', function () { if (!is_user_logged_in() || !class_exists('WooCommerce') || !playmint_on_prize_page()) return; if (is_wc_endpoint_url('order-received') || !WC()->cart || !WC()->session) return; $p = playmint_settings(); if (!playmint_has_secret($p)) return; $code = playmint_customer_code($p, wp_get_current_user()); if ($code === '' || WC()->cart->has_discount($code) || !playmint_coupon_usable($code)) return; // Once per session: a customer who removes the code, or whose cart doesn't qualify yet, gets the Apply notice instead. if ($p['auto_apply'] && !WC()->cart->is_empty() && WC()->session->get('playmint_auto_applied') !== $code) { WC()->session->set('playmint_auto_applied', $code); WC()->cart->apply_coupon($code); return; } $url = wp_nonce_url(add_query_arg('playmint_apply', rawurlencode($code), wc_get_cart_url()), 'playmint_apply'); $text = playmint_is_sl() ? sprintf('Imate nagrado: koda %s.', esc_html($code)) /* translators: %s: the coupon code the customer won */ : sprintf(esc_html__('You have a prize: code %s.', 'playmint'), esc_html($code)); $text .= sprintf(' %s', esc_url($url), playmint_is_sl() ? 'Uveljavi' : esc_html__('Apply', 'playmint')); if (!wc_has_notice($text, 'notice')) wc_add_notice($text, 'notice'); }); // The cart notice's Apply button. Only the customer's own unspent code can be applied, never a code from the URL. add_action('wp_loaded', function () { if (!isset($_GET['playmint_apply'], $_GET['_wpnonce']) || !is_user_logged_in() || !class_exists('WooCommerce')) return; if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'playmint_apply')) return; $p = playmint_settings(); $code = playmint_customer_code($p, wp_get_current_user()); $asked = sanitize_text_field(wp_unslash($_GET['playmint_apply'])); if ($code !== '' && strcasecmp($code, $asked) === 0 && WC()->cart) { if (WC()->session && !WC()->session->has_session()) WC()->session->set_customer_session_cookie(true); WC()->cart->apply_coupon($code); } wp_safe_redirect(wc_get_cart_url()); exit; }); // ---- Connection check -------------------------------------------------------------------------- // Asks Playmint for the campaign the way a shopper's browser does, so every setup mistake shows up here // with its fix, instead of as a game that silently never appears. Returns [['ok'|'warning'|'error', message], …]. function playmint_check_connection($p) { $api = untrailingslashit($p['api']); $origins = playmint_site_origins(); $results = []; delete_transient('playmint_campaign'); if (!playmint_has_secret($p)) { return [['error', __('Add the secret key too: it connects this site to your Playmint account. Copy it from Settings → API Keys in the Playmint dashboard; it starts with sk_.', 'playmint')]]; } if ($p['public_key'] === '') { return [['error', __('Add the public key too. Copy it from Settings → API Keys in the Playmint dashboard; it starts with pk_.', 'playmint')]]; } // The shop first: Playmint then lists it, allows its address and offers the prizes it makes codes for. $announced = playmint_announce_site($p); $res = wp_remote_get(add_query_arg(['siteUrl' => home_url()], $api . '/api/woocommerce/campaign'), [ 'timeout' => 8, 'headers' => ['x-api-key' => $p['secret_key']], ]); if (is_wp_error($res)) { /* translators: %s: the error from the HTTP request */ return [['error', sprintf(__('Could not reach Playmint: %s', 'playmint'), $res->get_error_message())]]; } $status = (int) wp_remote_retrieve_response_code($res); $body = json_decode(wp_remote_retrieve_body($res), true); if ($status === 401) { return [['error', __('The secret key isn\'t right. Copy it again from the Playmint dashboard; it starts with sk_.', 'playmint')]]; } if ($status === 204) { return array_merge( [['ok', __('Connected to Playmint. Next, create your first campaign in the Playmint dashboard: it shows on your site as soon as it\'s live.', 'playmint')]], playmint_secret_results($announced) ); } if ($status !== 200 || !is_array($body) || empty($body['campaignId'])) { /* translators: %d: an HTTP status code */ return [['error', sprintf(__('Unexpected answer from Playmint (HTTP %d). Try again in a minute.', 'playmint'), $status)]]; } $campaign_id = (string) $body['campaignId']; $res = wp_remote_get(add_query_arg(['campaignId' => $campaign_id], $api . '/api/sdk/campaign'), [ 'timeout' => 8, 'headers' => ['x-api-key' => $p['public_key'], 'Origin' => $origins[0]], ]); if (is_wp_error($res)) { /* translators: %s: the error from the HTTP request */ return [['error', sprintf(__('Could not reach Playmint: %s', 'playmint'), $res->get_error_message())]]; } $status = (int) wp_remote_retrieve_response_code($res); $body = json_decode(wp_remote_retrieve_body($res), true); $message = is_array($body) && isset($body['error']['message']) ? (string) $body['error']['message'] : ''; if ($status === 200) { $name = is_array($body) && isset($body['campaign']['name']) ? (string) $body['campaign']['name'] : ''; /* translators: %s: the campaign's name */ $results[] = ['ok', sprintf(__('Connected. The game for "%s" will show on your site.', 'playmint'), $name)]; } elseif ($status === 401 && stripos($message, 'origin') !== false) { $results[] = ['error', sprintf( /* translators: %s: the site's address(es), e.g. https://shop.com and https://www.shop.com */ __('Playmint doesn\'t accept this site\'s address yet. In the Playmint dashboard, under Settings → Allowed origins, add %s.', 'playmint'), implode(' ' . __('and', 'playmint') . ' ', $origins) )]; } elseif ($status === 401 && stripos($message, 'belong') !== false) { $results[] = ['error', __('The public key and the secret key are from different Playmint accounts. Copy both from the same account.', 'playmint')]; } elseif ($status === 401 && stripos($message, 'inactive') !== false) { $results[] = ['error', __('The campaign is paused. Set it live in the Playmint dashboard.', 'playmint')]; } elseif ($status === 401) { $results[] = ['error', __('The public key isn\'t right. Copy it again from the Playmint dashboard; it starts with pk_.', 'playmint')]; } elseif ($status === 400) { $results[] = ['error', __('The campaign isn\'t running right now. Check its start and end dates in the Playmint dashboard.', 'playmint')]; } elseif ($status === 402) { $results[] = ['error', __('This Playmint account has used this month\'s plays, so the game is paused until the 1st. More plays, under Billing in the Playmint dashboard, reopen it now.', 'playmint')]; } elseif ($status === 404 && stripos($message, 'winnable') !== false) { $results[] = ['error', __('No prize in this campaign can be won right now: its pasted codes have run out, or a prize needs codes and this site can\'t make them (it needs WooCommerce). Check the campaign in the Playmint dashboard.', 'playmint')]; } elseif ($status === 429) { $results[] = ['warning', __('Too many checks in a row. Try again in a minute.', 'playmint')]; } else { /* translators: %d: an HTTP status code */ $results[] = ['error', sprintf(__('Unexpected answer from Playmint (HTTP %d). Try again in a minute.', 'playmint'), $status)]; } return array_merge($results, playmint_secret_results($announced)); } // What the secret key brings: the site listed in Playmint and, with WooCommerce, a coupon of their own for each winner. function playmint_secret_results($announced) { if (!$announced) { return [['warning', __('Playmint couldn\'t be told about this site, so prizes need pasted codes for now. Press "Check connection" to try again.', 'playmint')]]; } if (!class_exists('WooCommerce')) { return [['warning', __('This site has no WooCommerce, so Playmint can\'t make coupons here: give prizes codes pasted in the Playmint dashboard, or make them physical prizes.', 'playmint')]]; } return [ ['ok', __('Secret key works: codes used at checkout are reported to Playmint.', 'playmint')], ['ok', __('Winners get a code of their own, which becomes a single-use WooCommerce coupon the first time it\'s used. No codes to paste.', 'playmint')], ]; } function playmint_store_check($p) { if ($p['public_key'] === '' && $p['secret_key'] === '') return; set_transient('playmint_check_' . get_current_user_id(), playmint_check_connection($p), MINUTE_IN_SECONDS); } // Checked on every save, so a mistake shows up on the page that saved it. The pre_update filter runs // even when nothing changed (update_option's own hooks don't), with the sanitized value. add_filter('pre_update_option_' . PLAYMINT_OPTION, function ($value) { playmint_store_check(playmint_settings($value)); return $value; }); add_action('admin_post_playmint_check', function () { check_admin_referer('playmint_check'); if (!current_user_can('manage_options')) wp_die(esc_html__('Not allowed.', 'playmint'), 403); playmint_store_check(playmint_settings()); wp_safe_redirect(admin_url('options-general.php?page=playmint')); exit; }); // ---- Settings ---------------------------------------------------------------------------------- add_action('admin_init', function () { register_setting('playmint', PLAYMINT_OPTION, ['sanitize_callback' => 'playmint_sanitize_settings']); }); function playmint_sanitize_settings($in) { $in = is_array($in) ? $in : []; $old = playmint_settings(); $text = function ($k) use ($in, $old) { return isset($in[$k]) ? trim(sanitize_text_field($in[$k])) : $old[$k]; }; // An empty secret field keeps the saved key; the saved key is never printed back into the form. $secret = isset($in['secret_key']) ? trim(sanitize_text_field($in['secret_key'])) : ''; if ($secret === '') $secret = $old['secret_key']; $url = function ($k) use ($in) { $value = isset($in[$k]) && is_string($in[$k]) ? esc_url_raw(trim($in[$k]), ['https']) : ''; if ($value === '' || playmint_is_service_url($value)) return $value; add_settings_error(PLAYMINT_OPTION, 'playmint_url', __('The API URL must be a Playmint address (https://….play-mint.com). The default is used instead.', 'playmint')); return ''; }; return [ 'public_key' => $text('public_key'), 'secret_key' => $secret, 'open' => in_array($in['open'] ?? '', ['button', 'once', 'manual'], true) ? $in['open'] : 'button', 'button_text' => $text('button_text'), 'position' => ($in['position'] ?? '') === 'bottom-left' ? 'bottom-left' : 'bottom-right', 'auto_apply' => empty($in['auto_apply']) ? 0 : 1, 'api' => $url('api'), ]; } add_action('admin_menu', function () { add_options_page('Playmint', 'Playmint', 'manage_options', 'playmint', 'playmint_render_page'); }); add_filter('plugin_action_links_' . plugin_basename(__FILE__), function ($links) { array_unshift($links, '' . esc_html__('Settings', 'playmint') . ''); return $links; }); // Only on the Plugins screen, and only until the plugin is connected. add_action('admin_notices', function () { $screen = function_exists('get_current_screen') ? get_current_screen() : null; if (!$screen || $screen->id !== 'plugins' || !current_user_can('manage_options')) return; if (playmint_is_configured(playmint_settings())) return; printf( '
%s %s
%s
' . esc_html__('Winners already get codes of their own. Make coupons here only to hand out your own codes instead: paste them into a reward in the Playmint dashboard.', 'playmint') . '
'; if (!class_exists('WC_Coupon')) { echo '' . esc_html__('The coupon maker needs WooCommerce.', 'playmint') . '
'; return; } if (!current_user_can('manage_woocommerce')) { echo '' . esc_html__('You need the shop manager role to make coupons.', 'playmint') . '
'; return; } $made = get_transient('playmint_coupons_' . get_current_user_id()); if ($made) { delete_transient('playmint_coupons_' . get_current_user_id()); /* translators: %d: how many coupons were made */ echo '' . esc_html(sprintf(_n('%d coupon made.', '%d coupons made.', count($made), 'playmint'), count($made))) . ' ' . esc_html__('Copy them into the reward in the Playmint dashboard.', 'playmint') . '
'; echo ''; } ?> set_code($code); $coupon->set_discount_type($type); $coupon->set_amount($amount); $coupon->set_individual_use(true); $coupon->set_usage_limit(1); $coupon->set_usage_limit_per_user(1); if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $expires)) $coupon->set_date_expires($expires); if ($minimum > 0) $coupon->set_minimum_amount($minimum); if ($free) $coupon->set_free_shipping(true); $coupon->save(); $codes[] = $code; } set_transient('playmint_coupons_' . get_current_user_id(), $codes, 10 * MINUTE_IN_SECONDS); wp_safe_redirect(admin_url('options-general.php?page=playmint')); exit; });