sync_schedule(); // Admin notice for new sales + its dismiss handler. if (is_admin()) { add_action('admin_notices', array($this, 'render_notice')); add_action('admin_post_adverships_dismiss_alert', array($this, 'handle_dismiss')); } } /** Register the 15-minute interval used by the poll event. */ public function add_schedule($schedules) { if (!isset($schedules[self::SCHEDULE])) { $schedules[self::SCHEDULE] = array( 'interval' => 15 * MINUTE_IN_SECONDS, 'display' => __('Every 15 minutes (Adverships)', 'adverships-affiliate'), ); } return $schedules; } /** * Schedule the poll when connected AND alerts are enabled; clear it * otherwise. Idempotent — safe to call on every request. */ public function sync_schedule() { $s = Adverships_Affiliate_Client::settings(); $enabled = !empty($s['alerts_enabled']) && Adverships_Affiliate_Client::is_connected(); $next = wp_next_scheduled(self::EVENT); if ($enabled && !$next) { wp_schedule_event(time() + MINUTE_IN_SECONDS, self::SCHEDULE, self::EVENT); } elseif (!$enabled && $next) { wp_clear_scheduled_hook(self::EVENT); } } /** Remove the scheduled event (deactivation / disconnect). */ public static function unschedule() { $ts = wp_next_scheduled(self::EVENT); while ($ts) { wp_unschedule_event($ts, self::EVENT); $ts = wp_next_scheduled(self::EVENT); } } /** * The cron worker: fetch sales newer than our cursor, notify, advance the * cursor. On the very first run (no cursor yet) we only SEED the cursor to * the current high-water mark so the merchant isn't emailed their entire * history the moment they enable alerts. */ public function poll() { $s = Adverships_Affiliate_Client::settings(); if (empty($s['alerts_enabled']) || !Adverships_Affiliate_Client::is_connected()) { return; } $lastSeen = isset($s['alerts_last_seen']) ? (int) $s['alerts_last_seen'] : 0; // No cursor yet → initialise it without notifying. if ($lastSeen <= 0) { $res = Adverships_Affiliate_Client::post_signed('/api/wordpress/alerts.php', array(), true); if (!is_wp_error($res) && !empty($res['data']['ok'])) { Adverships_Affiliate_Client::update_settings(array( 'alerts_last_seen' => (int) ($res['data']['max_id'] ?? 0), )); } return; } $res = Adverships_Affiliate_Client::post_signed( '/api/wordpress/alerts.php', array('since_id' => $lastSeen, 'limit' => 50), true ); if (is_wp_error($res) || empty($res['data']['ok'])) { return; // transient failure — try again next tick, cursor unmoved. } $data = $res['data']; $sales = isset($data['sales']) && is_array($data['sales']) ? $data['sales'] : array(); $maxId = (int) ($data['max_id'] ?? $lastSeen); if (!empty($sales)) { $this->notify($sales); $this->queue_notice($sales); } // Advance the cursor (never backwards). if ($maxId > $lastSeen) { Adverships_Affiliate_Client::update_settings(array('alerts_last_seen' => $maxId)); } } // ---- notifications ---------------------------------------------------- /** Email the merchant a summary of new sales. */ private function notify(array $sales) { $s = Adverships_Affiliate_Client::settings(); $to = !empty($s['alerts_email']) ? $s['alerts_email'] : get_bloginfo('admin_email'); if (!is_email($to)) { return; } $site = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES); $count = count($sales); $subject = sprintf( /* translators: 1: number of sales, 2: site name */ _n('%1$d new Adverships sale on %2$s', '%1$d new Adverships sales on %2$s', $count, 'adverships-affiliate'), $count, $site ); $lines = array(); $lines[] = sprintf( /* translators: %s: site name */ __('New affiliate sales recorded on %s:', 'adverships-affiliate'), $site ); $lines[] = ''; foreach ($sales as $sale) { $lines[] = sprintf( '• %s — %s (%s) · %s', isset($sale['created_at']) ? $sale['created_at'] : '', $this->money($sale['amount'] ?? 0, $sale['currency'] ?? ''), isset($sale['creator']) ? $sale['creator'] : __('unknown creator', 'adverships-affiliate'), isset($sale['order_id']) ? ('#' . $sale['order_id']) : '' ); } $lines[] = ''; $lines[] = __('View your dashboard:', 'adverships-affiliate'); $lines[] = admin_url('admin.php?page=adverships'); wp_mail($to, $subject, implode("\n", $lines)); } /** Stash new sales so the next admin pageload shows a dismissible notice. */ private function queue_notice(array $sales) { $existing = get_option(self::NOTICE_OPTION, array()); if (!is_array($existing)) { $existing = array(); } // Keep the most recent 10 across ticks so the notice can't grow unbounded. $merged = array_merge($existing, $sales); if (count($merged) > 10) { $merged = array_slice($merged, -10); } update_option(self::NOTICE_OPTION, $merged, false); } /** Render the pending-sales admin notice (and a dismiss link). */ public function render_notice() { if (!current_user_can($this->cap())) { return; } $sales = get_option(self::NOTICE_OPTION, array()); if (empty($sales) || !is_array($sales)) { return; } $count = count($sales); $dismiss = wp_nonce_url( admin_url('admin-post.php?action=adverships_dismiss_alert'), self::DISMISS_NONCE ); $dashboard = admin_url('admin.php?page=adverships'); echo '
'; echo '

' . esc_html__('Adverships', 'adverships-affiliate') . ' — '; echo esc_html(sprintf( /* translators: %d: number of new sales */ _n('%d new affiliate sale', '%d new affiliate sales', $count, 'adverships-affiliate'), $count )); echo '

'; echo '' . esc_html__('View dashboard', 'adverships-affiliate') . ' '; echo '' . esc_html__('Dismiss', 'adverships-affiliate') . ''; echo '

'; } /** Clear the pending-notice payload. */ public function handle_dismiss() { if (!current_user_can($this->cap())) { wp_die(esc_html__('You do not have permission to do this.', 'adverships-affiliate')); } check_admin_referer(self::DISMISS_NONCE); delete_option(self::NOTICE_OPTION); wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=adverships')); exit; } // ---- helpers ---------------------------------------------------------- private function money($amount, $currency = '') { $amount = (float) $amount; if (function_exists('wc_price') && $currency !== '') { return wp_strip_all_tags(wc_price($amount, array('currency' => $currency))); } if (function_exists('wc_price')) { return wp_strip_all_tags(wc_price($amount)); } return number_format_i18n($amount, 2); } private function cap() { return current_user_can('manage_woocommerce') ? 'manage_woocommerce' : 'manage_options'; } }