Emailsubscribe.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. namespace app\admin\controller;
  3. use app\common\controller\Backend;
  4. use app\common\model\EmailSubscribe as EmailSubscribeModel;
  5. use app\common\model\EmailSubscribeLog as EmailSubscribeLogModel;
  6. use app\common\model\EmailTemplate as EmailTemplateModel;
  7. use think\facade\Db;
  8. use think\facade\Validate;
  9. class Emailsubscribe extends Backend
  10. {
  11. protected $modelClass = null;
  12. protected $modelValidate = false;
  13. protected function initialize()
  14. {
  15. parent::initialize();
  16. $this->modelClass = new EmailSubscribeModel();
  17. $this->assign('statusList', $this->modelClass->getStatusList());
  18. $this->assign('confirmStatusList', $this->modelClass->getConfirmStatusList());
  19. $this->assign('sendStatusList', $this->modelClass->getSendStatusList());
  20. $this->assign('logStatusList', (new EmailSubscribeLogModel())->getStatusList());
  21. if (strtolower($this->request->action()) === 'log') {
  22. $config = $this->view->config ?: [];
  23. $config['jsname'] = 'backend/emailsubscribe';
  24. $this->view->config = $config;
  25. $this->assign('config', $config);
  26. }
  27. $this->assign('broadcastUrl', url('Emailbroadcast/index'));
  28. }
  29. protected function sendSubscribeEmail(string $email, string $subject, string $content): array
  30. {
  31. try {
  32. $config = get_addon_config('phpmailer');
  33. if (!$config) {
  34. return [false, 'NO_CONFIG', '未找到 phpmailer 插件配置'];
  35. }
  36. $mailer = new \addons\phpmailer\library\Mailer($config);
  37. $result = $mailer->to($email)->subject($subject)->message($content, true)->send();
  38. if ($result) {
  39. return [true, 'SUCCESS', '邮件发送成功'];
  40. }
  41. return [false, 'FAIL', $mailer->getError() ?: '邮件发送失败'];
  42. } catch (\Throwable $e) {
  43. return [false, 'EXCEPTION', $e->getMessage()];
  44. }
  45. }
  46. protected function buildMailContext(array $data): array
  47. {
  48. $context = [];
  49. foreach ($data as $key => $value) {
  50. $context[$key] = is_scalar($value) || $value === null ? (string)$value : json_encode($value, JSON_UNESCAPED_UNICODE);
  51. }
  52. return $context;
  53. }
  54. protected function renderTemplateContent(string $content, array $context): string
  55. {
  56. $replace = [];
  57. foreach ($context as $key => $value) {
  58. $replace['{{' . $key . '}}'] = (string)$value;
  59. $replace['{{ ' . $key . ' }}'] = (string)$value;
  60. }
  61. return strtr($content, $replace);
  62. }
  63. protected function getTemplateByType(string $type, string $lang): ?EmailTemplateModel
  64. {
  65. $lang = strtolower(trim($lang));
  66. $lang = $lang !== '' ? $lang : 'cn';
  67. $template = EmailTemplateModel::where(['type' => $type, 'lang' => $lang, 'status' => 1])
  68. ->order('listorder', 'asc')
  69. ->order('id', 'asc')
  70. ->find();
  71. if ($template) {
  72. return $template;
  73. }
  74. if ($lang !== 'cn') {
  75. return EmailTemplateModel::where(['type' => $type, 'lang' => 'cn', 'status' => 1])
  76. ->order('listorder', 'asc')
  77. ->order('id', 'asc')
  78. ->find();
  79. }
  80. return null;
  81. }
  82. protected function buildMailByType(string $type, string $lang, array $context = []): array
  83. {
  84. $template = $this->getTemplateByType($type, $lang);
  85. if ($template) {
  86. return [
  87. 'subject' => $this->renderTemplateContent((string)$template->subject, $context),
  88. 'content' => $this->renderTemplateContent((string)$template->content, $context),
  89. ];
  90. }
  91. $isCn = in_array(strtolower(trim($lang)), ['cn', 'zh', 'zh-cn'], true);
  92. return [
  93. 'subject' => $isCn ? '感谢订阅 XTU 最新动态' : 'Thanks for subscribing to XTU updates',
  94. 'content' => $isCn
  95. ? '您已成功订阅 XTU 的新品发布与品牌动态邮件通知,后续如有更新我们会第一时间通过邮件发送给您。'
  96. : 'You have successfully subscribed to XTU news, new arrivals, and brand updates. We will email you when new updates are available.',
  97. ];
  98. }
  99. public function index()
  100. {
  101. if ($this->request->isAjax()) {
  102. $page = (int)$this->request->param('page/d', 1);
  103. $limit = (int)$this->request->param('limit/d', 20);
  104. $sort = (string)$this->request->param('sort/s', 'id');
  105. $order = (string)$this->request->param('order/s', 'desc');
  106. $email = trim((string)$this->request->param('email/s', ''));
  107. $lang = trim((string)$this->request->param('lang/s', ''));
  108. $sourceKey = trim((string)$this->request->param('source_key/s', ''));
  109. $status = $this->request->param('status/s', '');
  110. $confirmStatus = $this->request->param('confirm_status/s', '');
  111. $lastSendStatus = $this->request->param('last_send_status/s', '');
  112. $createtimeStart = trim((string)$this->request->param('createtime_start/s', ''));
  113. $createtimeEnd = trim((string)$this->request->param('createtime_end/s', ''));
  114. $query = $this->modelClass;
  115. if ($email !== '') {
  116. $query = $query->where('email', 'like', '%' . $email . '%');
  117. }
  118. if ($lang !== '') {
  119. $query = $query->where('lang', $lang);
  120. }
  121. if ($sourceKey !== '') {
  122. $query = $query->where('source_key', $sourceKey);
  123. }
  124. if ($status !== '') {
  125. $query = $query->where('status', (int)$status);
  126. }
  127. if ($confirmStatus !== '') {
  128. $query = $query->where('confirm_status', (int)$confirmStatus);
  129. }
  130. if ($lastSendStatus !== '') {
  131. $query = $query->where('last_send_status', (int)$lastSendStatus);
  132. }
  133. if ($createtimeStart !== '') {
  134. $query = $query->where('createtime', '>=', strtotime($createtimeStart));
  135. }
  136. if ($createtimeEnd !== '') {
  137. $query = $query->where('createtime', '<=', strtotime($createtimeEnd));
  138. }
  139. $count = $query->count();
  140. $data = $query->order($sort, $order)->page($page, $limit)->select();
  141. return json(['code' => 0, 'count' => $count, 'data' => $data]);
  142. }
  143. return $this->fetch();
  144. }
  145. public function log()
  146. {
  147. if ($this->request->isAjax()) {
  148. $page = (int)$this->request->param('page/d', 1);
  149. $limit = (int)$this->request->param('limit/d', 20);
  150. $sort = (string)$this->request->param('sort/s', 'id');
  151. $order = (string)$this->request->param('order/s', 'desc');
  152. $email = trim((string)$this->request->param('email/s', ''));
  153. $subscribeId = (int)$this->request->param('subscribe_id/d', 0);
  154. $mailType = trim((string)$this->request->param('mail_type/s', ''));
  155. $status = $this->request->param('status/s', '');
  156. $sendTimeStart = trim((string)$this->request->param('send_time_start/s', ''));
  157. $sendTimeEnd = trim((string)$this->request->param('send_time_end/s', ''));
  158. $query = new EmailSubscribeLogModel();
  159. if ($email !== '') {
  160. $query = $query->where('email', 'like', '%' . $email . '%');
  161. }
  162. if ($subscribeId > 0) {
  163. $query = $query->where('subscribe_id', $subscribeId);
  164. }
  165. if ($mailType !== '') {
  166. $query = $query->where('mail_type', $mailType);
  167. }
  168. if ($status !== '') {
  169. $query = $query->where('status', (int)$status);
  170. }
  171. if ($sendTimeStart !== '') {
  172. $query = $query->where('send_time', '>=', strtotime($sendTimeStart));
  173. }
  174. if ($sendTimeEnd !== '') {
  175. $query = $query->where('send_time', '<=', strtotime($sendTimeEnd));
  176. }
  177. $count = $query->count();
  178. $data = $query->order($sort, $order)->page($page, $limit)->select();
  179. return json(['code' => 0, 'count' => $count, 'data' => $data]);
  180. }
  181. return $this->fetch();
  182. }
  183. public function resend()
  184. {
  185. if (!$this->request->isPost()) {
  186. $this->error('请求方式错误');
  187. }
  188. $id = (int)$this->request->post('id/d', 0);
  189. if ($id <= 0) {
  190. $this->error('缺少订阅记录ID');
  191. }
  192. $row = $this->modelClass->find($id);
  193. if (!$row) {
  194. $this->error('订阅记录不存在');
  195. }
  196. if (!$row->email || !Validate::is($row->email, 'email')) {
  197. $this->error('邮箱为空或格式不正确');
  198. }
  199. if ((int)$row->status !== 1) {
  200. $this->error('仅已订阅状态允许补发邮件');
  201. }
  202. $mail = $this->buildMailByType('subscribe_manual', (string)$row->lang, $this->buildMailContext([
  203. 'email' => (string)$row->email,
  204. 'lang' => (string)$row->lang,
  205. 'source_page' => (string)$row->source_page,
  206. 'source_key' => (string)$row->source_key,
  207. 'remark' => (string)$row->remark,
  208. 'ip' => (string)$row->ip,
  209. 'user_agent' => (string)$row->user_agent,
  210. 'unsubscribe_url' => url('index/emailsubscribe/unsubscribe', ['token' => (string)$row->unsubscribe_token], true, true),
  211. ]));
  212. $subject = $mail['subject'];
  213. $content = $mail['content'];
  214. $now = time();
  215. [$sendResult, $responseCode, $responseMsg] = $this->sendSubscribeEmail((string)$row->email, $subject, $content);
  216. EmailSubscribeLogModel::create([
  217. 'subscribe_id' => $row->id,
  218. 'email' => $row->email,
  219. 'mail_type' => 'subscribe_manual',
  220. 'subject' => $subject,
  221. 'content' => $content,
  222. 'status' => $sendResult ? 1 : 2,
  223. 'response_code' => $responseCode,
  224. 'response_msg' => $responseMsg,
  225. 'retry_count' => $sendResult ? 0 : 1,
  226. 'send_time' => $sendResult ? $now : 0,
  227. 'createtime' => $now,
  228. 'updatetime' => $now,
  229. ]);
  230. $row->last_send_status = $sendResult ? 1 : 2;
  231. $row->last_send_time = $sendResult ? $now : (int)$row->last_send_time;
  232. $row->updatetime = $now;
  233. $row->save();
  234. $this->success($sendResult ? '补发邮件成功' : '补发邮件失败:' . $responseMsg);
  235. }
  236. public function changeStatus()
  237. {
  238. if (!$this->request->isPost()) {
  239. $this->error('请求方式错误');
  240. }
  241. $id = (int)$this->request->post('id/d', 0);
  242. $status = (int)$this->request->post('status/d', 1);
  243. if ($id <= 0) {
  244. $this->error('缺少订阅记录ID');
  245. }
  246. if (!in_array($status, [0, 1, 9], true)) {
  247. $this->error('状态值不正确');
  248. }
  249. $row = $this->modelClass->find($id);
  250. if (!$row) {
  251. $this->error('订阅记录不存在');
  252. }
  253. $row->status = $status;
  254. $row->updatetime = time();
  255. $row->save();
  256. $this->success('状态更新成功');
  257. }
  258. public function del()
  259. {
  260. if (!$this->request->isPost()) {
  261. $this->error('请求方式错误');
  262. }
  263. $ids = $this->request->post('ids/a', []);
  264. if (!$ids) {
  265. $this->error('请先选择订阅记录');
  266. }
  267. Db::startTrans();
  268. try {
  269. Db::name('email_subscribe_log')->where('subscribe_id', 'in', $ids)->delete();
  270. $this->modelClass->where('id', 'in', $ids)->delete();
  271. Db::commit();
  272. } catch (\Throwable $e) {
  273. Db::rollback();
  274. $this->error('删除失败:' . $e->getMessage());
  275. }
  276. $this->success('删除成功');
  277. }
  278. public function delLog()
  279. {
  280. if (!$this->request->isPost()) {
  281. $this->error('请求方式错误');
  282. }
  283. $ids = $this->request->post('ids/a', []);
  284. if (!$ids) {
  285. $this->error('请先选择邮件日志');
  286. }
  287. (new EmailSubscribeLogModel())->where('id', 'in', $ids)->delete();
  288. $this->success('删除成功');
  289. }
  290. }