request->param('k', UnsubscribeService::KIND_EXPERT)); $id = intval($this->request->param('id', 0)); $token = trim((string)$this->request->param('t', '')); $audience = $this->loadAudience($kind, $id); if (!$audience) { return $this->html($this->pageInvalid(), 404); } if (!UnsubscribeService::verifyToken($kind, $id, (string)$audience['email'], $token)) { return $this->html($this->pageInvalid(), 403); } if (!empty($audience['unsubscribed'])) { return $this->html($this->pageAlreadyDone((string)$audience['email'])); } return $this->html($this->pageConfirm( $kind, $id, $token, (string)$audience['email'], (string)$audience['name'] )); } /** * 真正执行退订(POST 推荐;GET 也允许) */ public function confirm() { $kind = UnsubscribeService::normalizeKind($this->request->param('k', UnsubscribeService::KIND_EXPERT)); $id = intval($this->request->param('id', 0)); $token = trim((string)$this->request->param('t', '')); $audience = $this->loadAudience($kind, $id); if (!$audience) { return $this->html($this->pageInvalid(), 404); } if (!UnsubscribeService::verifyToken($kind, $id, (string)$audience['email'], $token)) { return $this->html($this->pageInvalid(), 403); } if (empty($audience['unsubscribed'])) { $table = $kind === UnsubscribeService::KIND_USER ? 'user' : 'expert'; $pk = $kind === UnsubscribeService::KIND_USER ? 'user_id' : 'expert_id'; Db::name($table)->where($pk, $id)->update([ 'unsubscribed' => 1, ]); } return $this->html($this->pageSuccess((string)$audience['email'])); } /** * 按 kind 加载受众的最少必要信息(id, email, name, unsubscribed) */ private function loadAudience($kind, $id) { if ($id <= 0) return null; if ($kind === UnsubscribeService::KIND_USER) { $row = Db::name('user') ->where('user_id', $id) ->field('user_id, email, realname AS name, unsubscribed') ->find(); } else { $row = Db::name('expert') ->where('expert_id', $id) ->field('expert_id, email, name, unsubscribed') ->find(); } return $row ?: null; } // ==================== HTML 页面 ==================== private function html($html, $status = 200) { $resp = Response::create($html, 'html', $status); $resp->header('Content-Type', 'text/html; charset=utf-8'); return $resp; } private function pageShell($title, $bodyHtml) { $titleSafe = htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); $css = " body{margin:0;padding:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:#f5f7fa;color:#1f2937;} .wrap{max-width:520px;margin:64px auto;padding:32px;background:#fff;border-radius:12px;box-shadow:0 2px 16px rgba(0,0,0,.06);} h1{font-size:22px;margin:0 0 16px;color:#111827;} p{font-size:15px;line-height:1.6;margin:0 0 16px;color:#374151;} .email{font-weight:600;color:#111827;word-break:break-all;} .btn{display:inline-block;padding:10px 24px;border-radius:6px;border:0;cursor:pointer;font-size:15px;text-decoration:none;} .btn-primary{background:#dc2626;color:#fff;} .btn-primary:hover{background:#b91c1c;} .btn-secondary{background:#e5e7eb;color:#374151;margin-left:8px;} .muted{color:#6b7280;font-size:13px;margin-top:24px;} .ok{color:#16a34a;font-weight:600;} .warn{color:#d97706;font-weight:600;} .err{color:#dc2626;font-weight:600;} "; return '
' . '' . '' . 'Hi ' . $nameSafe . ',
' . 'You are about to unsubscribe ' . $emailSafe . ' from all promotion and invitation emails sent by TMR Journals. ' . 'After unsubscribing you will no longer receive marketing emails from us.
' . '' . 'If you didn\'t expect this email, you can safely close this page.
'; return $this->pageShell('Confirm unsubscribe - TMR Journals', $body); } private function pageSuccess($email) { $emailSafe = htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); $body = '' . $emailSafe . ' will no longer receive promotion or invitation emails from TMR Journals.
' . 'If this was a mistake, please contact us and we will be happy to resubscribe you.
' . 'Thank you for your past interest in our journals.
'; return $this->pageShell('Unsubscribed - TMR Journals', $body); } private function pageAlreadyDone($email) { $emailSafe = htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); $body = '' . $emailSafe . ' is already unsubscribed from our promotion emails.
' . 'No further action is needed.
'; return $this->pageShell('Already unsubscribed - TMR Journals', $body); } private function pageInvalid() { $body = 'This unsubscribe link is invalid or has expired.
' . 'Please use the most recent unsubscribe link in our emails, or contact us for help.
'; return $this->pageShell('Invalid link - TMR Journals', $body); } }