From 0403e244a38f1a49f97d10bbb6b4aa6a2e714618 Mon Sep 17 00:00:00 2001
From: wzp <2040239371@qq.com>
Date: 星期二, 31 三月 2026 15:26:47 +0800
Subject: [PATCH] fix:翻译成英文
---
映星教育/snippet-4-admin.php | 424 ++++++------
映星教育/snippet-4-admin copy.php | 1654 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1,866 insertions(+), 212 deletions(-)
diff --git "a/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin copy.php" "b/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin copy.php"
new file mode 100644
index 0000000..909e150
--- /dev/null
+++ "b/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin copy.php"
@@ -0,0 +1,1654 @@
+<?php
+/**
+ * WPCode 鐗囨 4/4 鈥� 鍚庡彴绠$悊 + Nginx 璺敱娉ㄥ唽
+ * 鍚嶇О寤鸿锛欼M - 鍚庡彴绠$悊
+ * 绫诲瀷锛歅HP Snippet
+ * 浣嶇疆锛歊un everywhere
+ *
+ * 渚濊禆锛氱墖娈� 1銆�2銆�3 蹇呴』鍏堝惎鐢�
+ *
+ * 鍖呭惈锛�
+ * - 鍊欓�変汉鍒楄〃椤碉紙鎼滅储 / 鐘舵�佺瓫閫� / 鍒嗛〉锛�
+ * - 鍊欓�変汉璇︽儏椤碉紙瀹屾暣琛ㄥ崟淇℃伅 / 闄勪欢涓嬭浇 / 闈㈣瘯璁板綍 / 瑙嗛鎾斁锛�
+ * - AJAX 鍙戦�侀潰璇曢個璇�
+ * - 宸ュ叿鑿滃崟锛歂ginx 閰嶇疆鍚戝 + 鐜妫�娴�
+ */
+
+defined('ABSPATH') || exit;
+
+/* ============================================================
+ 娉ㄥ唽鑿滃崟
+ ============================================================ */
+add_action('admin_menu', function () {
+ add_menu_page('鍊欓�変汉绠$悊', '鍊欓�変汉绠$悊', 'edit_others_posts', 'im-candidates', 'im_admin_list', 'dashicons-groups', 30);
+ add_submenu_page('im-candidates', '鍊欓�変汉鍒楄〃', '鍊欓�変汉鍒楄〃', 'edit_others_posts', 'im-candidates', 'im_admin_list');
+ add_submenu_page('im-candidates', '鍊欓�変汉璇︽儏', '', 'edit_others_posts', 'im-candidate-detail', 'im_admin_detail');
+ add_submenu_page('tools.php', 'IM 鏈嶅姟鍣ㄩ厤缃�', 'IM 鏈嶅姟鍣ㄩ厤缃�', 'manage_options', 'im-server-setup', 'im_server_setup');
+});
+
+/* ============================================================
+ 鍚庡彴鏍峰紡 + JS锛堝彧鍦ㄧ鐞嗛〉闈㈠姞杞斤級
+ ============================================================ */
+add_action('admin_head', function () {
+ $screen = get_current_screen();
+ if (!$screen)
+ return;
+ $pages = ['toplevel_page_im-candidates', 'candidates_page_im-candidate-detail'];
+ if (!in_array($screen->id, $pages, true) && strpos($screen->id, 'im-candidates') === false && strpos($screen->id, 'im-candidate-detail') === false)
+ return;
+ ?>
+ <style>
+ /* 鍩虹鍒楄〃鏍峰紡淇濈暀 */
+ .im-a {
+ max-width: 1200px
+ }
+
+ .im-a .widefat td {
+ vertical-align: middle;
+ padding: 11px 10px
+ }
+
+ .im-badge {
+ display: inline-block;
+ padding: 3px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 600;
+ border: 1px solid;
+ white-space: nowrap
+ }
+
+ .im-tag {
+ display: inline-block;
+ background: #eff6ff;
+ color: #1e40af;
+ border: 1px solid #bfdbfe;
+ border-radius: 6px;
+ padding: 4px 10px;
+ font-size: 13px;
+ margin: 3px 4px 3px 0;
+ font-weight: 500
+ }
+
+ .im-muted {
+ color: #9ca3af;
+ font-style: italic
+ }
+
+ .im-sub-cnt {
+ font-size: 12px;
+ color: #6b7280;
+ display: block;
+ margin-bottom: 3px
+ }
+
+ .im-empty {
+ text-align: center;
+ color: #9ca3af;
+ padding: 36px !important;
+ font-size: 15px
+ }
+
+ /* 楂樼骇璇︽儏椤垫牱寮� */
+ .im-detail-wrap {
+ margin-top: 20px;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background: #f3f4f6;
+ padding: 24px;
+ border-radius: 12px;
+ box-sizing: border-box
+ }
+
+ .im-detail-wrap * {
+ box-sizing: border-box
+ }
+
+ .im-back-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ color: #4b5563;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 14px;
+ margin-bottom: 24px;
+ transition: color 0.2s
+ }
+
+ .im-back-link:hover {
+ color: #009dff
+ }
+
+ .im-hero-card {
+ background: #fff;
+ border-radius: 16px;
+ padding: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
+ margin-bottom: 24px;
+ position: relative;
+ overflow: hidden
+ }
+
+ .im-hero-card::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 6px;
+ background: linear-gradient(180deg, #009dff, #53eee0)
+ }
+
+ .im-hero-profile {
+ display: flex;
+ align-items: center;
+ gap: 24px
+ }
+
+ .im-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #e0f2fe, #bae6fd);
+ color: #0369a1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 28px;
+ font-weight: 700;
+ flex-shrink: 0
+ }
+
+ .im-hero-info h1 {
+ margin: 0 0 8px;
+ font-size: 26px;
+ font-weight: 700;
+ color: #111827;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 0
+ }
+
+ .im-hero-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ color: #4b5563;
+ font-size: 14px
+ }
+
+ .im-hero-meta-item {
+ display: flex;
+ align-items: center;
+ gap: 6px
+ }
+
+ .im-hero-meta-item svg {
+ color: #9ca3af
+ }
+
+ .im-main-grid {
+ display: grid;
+ grid-template-columns: 1fr 400px;
+ gap: 24px
+ }
+
+ @media(max-width:1100px) {
+ .im-main-grid {
+ grid-template-columns: 1fr
+ }
+ }
+
+ .im-card-modern {
+ background: #fff;
+ border-radius: 16px;
+ box-shadow: 0 3px 15px rgba(0, 0, 0, 0.03);
+ padding: 24px;
+ margin-bottom: 24px
+ }
+
+ .im-card-title {
+ font-size: 18px;
+ font-weight: 700;
+ color: #111827;
+ margin: 0 0 20px;
+ display: flex;
+ align-items: center;
+ gap: 10px
+ }
+
+ .im-card-title svg {
+ color: #009dff
+ }
+
+ .im-info-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px
+ }
+
+ .im-info-item {
+ display: flex;
+ flex-direction: column;
+ gap: 6px
+ }
+
+ .im-info-label {
+ font-size: 13px;
+ color: #6b7280;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px
+ }
+
+ .im-info-val {
+ font-size: 15px;
+ color: #111827;
+ font-weight: 500;
+ line-height: 1.5
+ }
+
+ .im-info-val.full-width {
+ grid-column: 1/-1
+ }
+
+ .im-timeline {
+ position: relative;
+ padding-left: 24px;
+ border-left: 2px solid #e5e7eb;
+ margin-top: 12px
+ }
+
+ .im-timeline-item {
+ position: relative;
+ margin-bottom: 24px
+ }
+
+ .im-timeline-item:last-child {
+ margin-bottom: 0
+ }
+
+ .im-timeline-icon {
+ position: absolute;
+ left: -31px;
+ top: 0;
+ width: 14px;
+ height: 14px;
+ background: #fff;
+ border: 3px solid #009dff;
+ border-radius: 50%
+ }
+
+ .im-timeline-date {
+ font-size: 13px;
+ color: #009dff;
+ font-weight: 700;
+ margin-bottom: 4px
+ }
+
+ .im-timeline-title {
+ font-size: 16px;
+ font-weight: 700;
+ color: #111827;
+ margin: 0 0 4px
+ }
+
+ .im-timeline-sub {
+ font-size: 14px;
+ color: #4b5563
+ }
+
+ .im-doc-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ background: #f9fafb;
+ border: 1px solid #f3f4f6;
+ border-radius: 10px;
+ margin-bottom: 12px;
+ transition: all 0.2s
+ }
+
+ .im-doc-item:hover {
+ border-color: #bfdbfe;
+ background: #eff6ff
+ }
+
+ .im-doc-info {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 14px;
+ color: #111827;
+ font-weight: 500;
+ overflow: hidden
+ }
+
+ .im-doc-info svg {
+ color: #ef4444;
+ flex-shrink: 0
+ }
+
+ .im-doc-name {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis
+ }
+
+ .im-doc-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px
+ }
+
+ .im-doc-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: 6px;
+ background: #fff;
+ color: #4b5563;
+ border: 1px solid #d1d5db;
+ text-decoration: none;
+ transition: all 0.2s
+ }
+
+ .im-doc-btn:hover {
+ background: #f3f4f6;
+ color: #111827
+ }
+
+ .im-token-box {
+ background: #f8fafc;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ padding: 20px;
+ margin-bottom: 16px;
+ position: relative
+ }
+
+ .im-token-status {
+ position: absolute;
+ top: 20px;
+ right: 20px
+ }
+
+ .im-token-date {
+ font-size: 14px;
+ color: #374151;
+ font-weight: 600;
+ margin-bottom: 4px
+ }
+
+ .im-token-exp {
+ font-size: 13px;
+ color: #64748b;
+ margin-bottom: 16px
+ }
+
+ .im-video-player {
+ width: 100%;
+ border-radius: 8px;
+ background: #0f172a;
+ max-height: 240px;
+ margin-bottom: 12px
+ }
+
+ .im-video-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 13px;
+ color: #4b5563
+ }
+
+ .im-video-dl {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ color: #009dff;
+ text-decoration: none;
+ font-weight: 600
+ }
+
+ .im-video-dl:hover {
+ text-decoration: underline
+ }
+
+ .im-action-box {
+ background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
+ border: 1px solid #bae6fd;
+ border-radius: 16px;
+ padding: 24px;
+ text-align: center;
+ margin-bottom: 24px
+ }
+
+ .im-action-box h3 {
+ margin: 0 0 12px;
+ font-size: 18px;
+ color: #0369a1;
+ font-weight: 700
+ }
+
+ .im-action-box p {
+ color: #0284c7;
+ font-size: 14px;
+ margin: 0 0 20px
+ }
+
+ .im-btn-primary {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ background: linear-gradient(135deg, #009dff, #3de1fe);
+ color: #fff;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 15px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.3s;
+ box-shadow: 0 4px 12px rgba(0, 157, 255, 0.25);
+ width: 100%;
+ margin-bottom: 12px
+ }
+
+ .im-btn-primary:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(0, 157, 255, 0.35);
+ color: #fff
+ }
+
+ .im-btn-danger {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ background: linear-gradient(135deg, #ef4444, #f87171);
+ color: #fff;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 15px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.3s;
+ box-shadow: 0 4px 12px rgba(239, 68, 68, 0.25);
+ width: 100%
+ }
+
+ .im-btn-danger:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(239, 68, 68, 0.35);
+ color: #fff
+ }
+
+ .im-btn-success {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ background: linear-gradient(135deg, #10b981, #34d399);
+ color: #fff;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 15px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.3s;
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.25);
+ width: 100%
+ }
+
+ .im-btn-success:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(16, 185, 129, 0.35);
+ color: #fff
+ }
+
+ .im-modal-bg {
+ position: fixed;
+ inset: 0;
+ background: rgba(17, 24, 39, 0.7);
+ backdrop-filter: blur(4px);
+ z-index: 99999;
+ display: flex;
+ align-items: center;
+ justify-content: center
+ }
+
+ .im-modal {
+ background: #fff;
+ border-radius: 16px;
+ padding: 32px;
+ max-width: 440px;
+ width: 90%;
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25)
+ }
+
+ .im-modal h3 {
+ margin: 0 0 12px;
+ font-size: 20px;
+ font-weight: 700;
+ color: #111827
+ }
+
+ .im-modal p {
+ color: #4b5563;
+ margin: 0 0 24px;
+ line-height: 1.6
+ }
+
+ .im-modal textarea {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ min-height: 80px;
+ font-family: inherit
+ }
+
+ .im-modal-btns {
+ display: flex;
+ gap: 12px;
+ justify-content: flex-end
+ }
+
+ .im-btn-cancel {
+ background: #f3f4f6;
+ color: #374151;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer
+ }
+
+ .im-btn-cancel:hover {
+ background: #e5e7eb
+ }
+
+ .im-btn-confirm {
+ background: #009dff;
+ color: #fff;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer
+ }
+
+ .im-btn-confirm:hover {
+ background: #0284c7
+ }
+
+ .im-reject-reason {
+ background: #fef2f2;
+ border: 1px solid #fecaca;
+ color: #991b1b;
+ padding: 16px;
+ border-radius: 8px;
+ margin-bottom: 24px;
+ font-size: 14px
+ }
+ </style>
+ <script>
+ jQuery(function ($) {
+ var cid = null, actionType = '';
+ $(document).on('click', '.im-action-btn', function () {
+ cid = $(this).data('id');
+ actionType = $(this).data('action');
+ var name = $(this).data('name');
+ var title = '', body = '', showTextarea = false;
+
+ if (actionType === 'invite') {
+ title = '纭鍙戦�侀潰璇曢個璇�';
+ body = '灏嗗悜 <strong>' + name + '</strong> 鍙戦�侀潰璇曢個璇凤紝<br>閭欢鍖呭惈 <strong>24灏忔椂鏈夋晥</strong> 鐨勪笓灞為潰璇曢摼鎺ワ紝纭鍙戦�侊紵';
+ } else if (actionType === 'reject') {
+ title = '鎷掔粷鍊欓�変汉';
+ body = '纭鎷掔粷 <strong>' + name + '</strong> 鐨勭敵璇凤紵<br>绯荤粺灏嗗悜鍏跺彂閫佹嫆缁濋�氱煡閭欢銆�';
+ } else if (actionType === 'hire') {
+ title = '褰曞彇鍊欓�変汉';
+ body = '纭姝e紡褰曞彇 <strong>' + name + '</strong>锛�<br>绯荤粺灏嗗悜鍏跺彂閫佸綍鍙栭�氱煡閭欢鍙婂煿璁摼鎺ャ��';
+ } else if (actionType === 'resend_joinus') {
+ title = '閲嶆柊鍙戦�侀偖浠�';
+ body = '灏嗛噸鏂板悜 <strong>' + name + '</strong> 鍙戦�佲�濆畬鍠勮缁嗚〃鍗曗�濋摼鎺ラ偖浠躲��';
+ } else if (actionType === 'resend_training') {
+ title = '閲嶆柊鍙戦�佸煿璁摼鎺�';
+ body = '灏嗛噸鏂板悜 <strong>' + name + '</strong> 鍙戦�佸煿璁摼鎺ラ偖浠躲��';
+ } else if (actionType === 'resend_trained_email') {
+ title = '閲嶆柊鍙戦�佽处鍙烽偖浠�';
+ body = '灏嗛噸鏂板悜 <strong>' + name + '</strong> 鍙戦�� Microsoft Teams 涓存椂璐﹀彿閭欢銆�';
+ } else if (actionType === 'delete') {
+ title = '鍒犻櫎鍊欓�変汉';
+ body = '纭姘镐箙鍒犻櫎 <strong>' + name + '</strong> 鐨勬墍鏈夎褰曪紵<br><span style=鈥漜olor:#ef4444鈥�>姝ゆ搷浣滀笉鍙仮澶嶏紝灏嗗悓鏃跺垹闄よ鍊欓�変汉涓婁紶鐨勬墍鏈夋枃浠讹紙闄勪欢銆侀潰璇曡棰戠瓑锛夈��</span>';
+ }
+
+ $('#im-modal h3').text(title);
+ $('#im-modal-body').html(body);
+ if (showTextarea) {
+ $('#im-modal-textarea').show().val('');
+ } else {
+ $('#im-modal-textarea').hide();
+ }
+ $('#im-modal').fadeIn(150);
+ });
+
+ $(document).on('click', '.im-modal-cancel,.im-modal-bg', function (e) { if (e.target === this || $(this).hasClass('im-modal-cancel')) $('#im-modal').fadeOut(150); });
+ $(document).on('keydown', function (e) { if (e.key === 'Escape') $('#im-modal').fadeOut(150); });
+
+ $('#im-modal-ok').on('click', function (e) {
+ e.preventDefault();
+ if (!cid) return;
+ var reason = $('#im-modal-textarea').is(':visible') ? $.trim($('#im-modal-textarea').val()) : '';
+
+ var $b = $(this); $b.prop('disabled', true).text('澶勭悊涓�...');
+
+ var $modal = $('#im-modal');
+ var ajaxActUrl = $modal.data('ajaxurl') || '/wp-admin/admin-ajax.php';
+ var ajaxNonce = $modal.data('nonce') || '';
+
+ var ajaxAction = 'im_action_' + actionType;
+ $.ajax({
+ url: ajaxActUrl,
+ type: 'POST',
+ dataType: 'json',
+ data: { action: ajaxAction, nonce: ajaxNonce, candidate_id: cid, reason: reason },
+ success: function (r) {
+ $('#im-modal').fadeOut(150); $b.prop('disabled', false).text('纭');
+ var isSuccess = r && r.success;
+ var msg = r && r.data && r.data.message ? ((isSuccess ? '鉁� ' : '鉂� ') + r.data.message) : (isSuccess ? '鉁� 鎿嶄綔鎴愬姛' : '鉂� 鎿嶄綔澶辫触锛岃閲嶈瘯');
+ var $n = $('<div class="notice ' + (isSuccess ? 'notice-success' : 'notice-error') + ' is-dismissible" style="margin-top:16px"><p>' + msg + '</p></div>');
+ if ($('.wp-heading-inline').length) {
+ $('.wp-heading-inline').after($n);
+ } else if ($('.im-detail-wrap').length) {
+ $('.im-detail-wrap').prepend($n);
+ }
+ var $res = $('#im-res-' + cid);
+ if ($res.length) $res.removeClass('ok err').addClass(isSuccess ? 'ok' : 'err').text(msg);
+ if (isSuccess) setTimeout(function () { location.reload(); }, 1000);
+ else setTimeout(function () { $n.fadeOut(400, function () { $(this).remove(); }); }, 5000);
+ },
+ error: function (xhr, status, error) {
+ $('#im-modal').fadeOut(150); $b.prop('disabled', false).text('纭');
+ var errMsg = '缃戠粶绻佸繖鎴栨湇鍔″櫒鎶ラ敊锛岃閲嶈瘯銆�';
+ if (xhr.status === 403) errMsg = '璇锋眰琚嫆缁�(403 Forbidden)銆傝繖閫氬父鏄洜涓哄畨鍏ㄦ彃浠�(濡俉ordfence/瀹濆WAF)鎷︽埅浜嗘惡甯﹀弬鏁扮殑璇锋眰锛岃灏濊瘯灏嗗悗鍙板姞鍏ョ櫧鍚嶅崟鎴栬仈绯荤鐞嗗憳閲婃斁鎷︽埅銆�';
+ alert(errMsg);
+ }
+ });
+ });
+ });
+ </script>
+ <?php
+});
+
+/* ============================================================
+ AJAX 澶勭悊
+ ============================================================ */
+add_action('wp_ajax_im_action_invite', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+
+ $c = IM_Candidate::get($id);
+ if ($c && $c->status === 'screening') {
+ IM_Candidate::update_status($id, 'invited');
+ }
+ IM_Mailer::send_interview_invite($id)
+ ? wp_send_json_success(['message' => '闈㈣瘯閭�璇烽偖浠跺凡鎴愬姛鍙戦�侊紒'])
+ : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ワ紝璇锋鏌ラ偖浠堕厤缃��']);
+});
+
+add_action('wp_ajax_im_action_reject', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ IM_Candidate::update_status($id, 'rejected');
+ IM_Mailer::send_reject($id)
+ ? wp_send_json_success(['message' => '宸叉嫆缁濊鍊欓�変汉骞跺彂閫侀偖浠讹紒'])
+ : wp_send_json_error(['message' => '宸叉洿鏂扮姸鎬侊紝浣嗛偖浠跺彂閫佸け璐ャ��']);
+});
+
+add_action('wp_ajax_im_action_hire', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ IM_Mailer::send_hire($id)
+ ? wp_send_json_success(['message' => '宸插綍鍙栬鍊欓�変汉骞跺彂閫佸煿璁摼鎺ラ偖浠讹紒'])
+ : wp_send_json_error(['message' => '褰曞彇鎿嶄綔澶辫触锛岃妫�鏌ラ偖浠堕厤缃��']);
+});
+
+add_action('wp_ajax_im_action_resend_joinus', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ IM_Mailer::send_joinus_confirmation($id)
+ ? wp_send_json_success(['message' => '琛ㄥ崟閾炬帴閭欢宸查噸鏂板彂閫侊紒'])
+ : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ャ��']);
+});
+
+add_action('wp_ajax_im_action_resend_training', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ $c = IM_Candidate::get($id);
+ if (!$c || $c->status !== 'training')
+ wp_send_json_error(['message' => '璇ュ�欓�変汉涓嶅湪鍩硅鐘舵��']);
+ IM_Mailer::send_hire($id)
+ ? wp_send_json_success(['message' => '鍩硅閾炬帴閭欢宸查噸鏂板彂閫侊紒'])
+ : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ャ��']);
+});
+
+add_action('wp_ajax_im_action_resend_trained_email', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ $c = IM_Candidate::get($id);
+ if (!$c || $c->status !== 'trained')
+ wp_send_json_error(['message' => '璇ュ�欓�変汉涓嶅湪宸插畬鎴愬煿璁姸鎬�']);
+ IM_Mailer::send_training_complete($id)
+ ? wp_send_json_success(['message' => '璐﹀彿閭欢宸查噸鏂板彂閫侊紒'])
+ : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ャ��']);
+});
+
+add_action('wp_ajax_im_action_delete', function () {
+ check_ajax_referer('im_admin_nonce', 'nonce');
+ if (!current_user_can('edit_others_posts'))
+ wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ $id = (int) ($_POST['candidate_id'] ?? 0);
+ if (!$id)
+ wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ IM_Candidate::delete_with_files($id)
+ ? wp_send_json_success(['message' => '鍊欓�変汉璁板綍鍙婂叧鑱旀枃浠跺凡鍒犻櫎锛�'])
+ : wp_send_json_error(['message' => '鍒犻櫎澶辫触锛岃閲嶈瘯銆�']);
+});
+
+/* ============================================================
+ 鍊欓�変汉鍒楄〃椤�
+ ============================================================ */
+function im_admin_list()
+{
+ $page = max(1, (int) ($_GET['paged'] ?? 1));
+ $status = sanitize_text_field($_GET['status'] ?? '');
+ $search = sanitize_text_field($_GET['s'] ?? '');
+ $per_page = 20;
+ $args = compact('page', 'status', 'search') + ['per_page' => $per_page];
+ $items = IM_Candidate::get_list($args);
+ $total = IM_Candidate::count($args);
+ $pages = (int) ceil($total / $per_page);
+
+ $cnt = [
+ '' => IM_Candidate::count(),
+ 'applied' => IM_Candidate::count(['status' => 'applied']),
+ 'screening' => IM_Candidate::count(['status' => 'screening']),
+ 'invited' => IM_Candidate::count(['status' => 'invited']),
+ 'rejected' => IM_Candidate::count(['status' => 'rejected']),
+ 'completed' => IM_Candidate::count(['status' => 'completed']),
+ 'hired' => IM_Candidate::count(['status' => 'hired']),
+ 'training' => IM_Candidate::count(['status' => 'training']),
+ 'trained' => IM_Candidate::count(['status' => 'trained']),
+ ];
+ $tabs = [
+ '' => '鍏ㄩ儴',
+ 'applied' => '宸茬敵璇�',
+ 'screening' => '寰呯瓫閫�',
+ 'invited' => '宸查個璇�',
+ 'rejected' => '宸叉嫆缁�',
+ 'completed' => '宸插畬鎴�',
+ 'hired' => '宸插綍鍙�',
+ 'training' => '鏈畬鎴愬煿璁�',
+ 'trained' => '宸插畬鎴愬煿璁�'
+ ];
+
+ $status_lbl = [
+ 'applied' => '寰呭畬鍠勪俊鎭�',
+ 'screening' => '宸叉彁浜よ缁嗕俊鎭�',
+ 'invited' => '宸查個璇�',
+ 'rejected' => '宸叉嫆缁�',
+ 'completed' => '宸插畬鎴�',
+ 'hired' => '宸插綍鍙�',
+ 'training' => '鏈畬鎴愬煿璁�',
+ 'trained' => '宸插畬鎴愬煿璁�'
+ ];
+
+ $colors = [
+ 'applied' => '#f59e0b',
+ 'screening' => '#8b5cf6',
+ 'invited' => '#3b82f6',
+ 'rejected' => '#ef4444',
+ 'completed' => '#10b981',
+ 'hired' => '#059669',
+ 'training' => '#f97316',
+ 'trained' => '#06b6d4'
+ ];
+ ?>
+ <div class="wrap im-a">
+ <h1 class="wp-heading-inline">鍊欓�変汉绠$悊</h1>
+ <hr class="wp-header-end">
+
+ <ul class="subsubsub">
+ <?php foreach ($tabs as $key => $lbl):
+ $url = admin_url('admin.php?page=im-candidates' . ($key ? '&status=' . $key : ''));
+ ?>
+ <li><a href="<?= esc_url($url) ?>" <?= $status === $key ? 'class="current"' : '' ?>>
+ <?= $lbl ?> <span class="count">(<?= $cnt[$key] ?>)</span>
+ </a><?= $key !== 'trained' ? ' |' : '' ?></li>
+ <?php endforeach; ?>
+ </ul>
+
+ <form method="get" style="margin:8px 0 16px">
+ <input type="hidden" name="page" value="im-candidates">
+ <?php if ($status): ?><input type="hidden" name="status" value="<?= esc_attr($status) ?>"><?php endif; ?>
+ <p class="search-box" style="margin:0">
+ <input type="search" name="s" value="<?= esc_attr($search) ?>" placeholder="鎼滅储濮撳悕鎴栭偖绠�..." style="width:240px">
+ <button type="submit" class="button">鎼滅储</button>
+ </p>
+ </form>
+
+ <table class="wp-list-table widefat fixed striped">
+ <thead>
+ <tr>
+ <th width="160">濮撳悕</th>
+ <th>閭</th>
+ <th width="140">鏇存柊鏃堕棿</th>
+ <th width="120">鐘舵��</th>
+ <th>鎺堣绉戠洰 / 璇︽儏</th>
+ <th width="240">鎿嶄綔</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php if ($items):
+ foreach ($items as $c):
+ $detail = admin_url('admin.php?page=im-candidate-detail&id=' . $c->id);
+ $subs = IM_Candidate::get_subjects($c);
+ $color = $colors[$c->status] ?? '#6b7280';
+ $clabel = $status_lbl[$c->status] ?? $c->status;
+ $name_disp = $c->status === 'applied' ? esc_html($c->last_name . ' ' . $c->first_name) : '<a href="' . esc_url($detail) . '">' . esc_html($c->last_name . ' ' . $c->first_name) . '</a>';
+ ?>
+ <tr>
+ <td>
+ <strong><?= $name_disp ?></strong>
+ <?php if ($c->preferred_name): ?>
+ <span style="color:#9ca3af;font-size:12px">(<?= esc_html($c->preferred_name) ?>)</span>
+ <?php endif; ?>
+ </td>
+ <td><a href="mailto:<?= esc_attr($c->email) ?>"><?= esc_html($c->email) ?></a></td>
+ <td style="font-size:13px"><?= esc_html(date('Y-m-d H:i', strtotime($c->updated_at))) ?></td>
+ <td>
+ <span class="im-badge"
+ style="background:<?= $color ?>18;color:<?= $color ?>;border-color:<?= $color ?>44"><?= $clabel ?></span>
+ <?php if ($c->status === 'applied' && !empty($c->apply_opened_at)): ?>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� 宸叉墦寮�琛ㄥ崟</div>
+ <?php elseif ($c->status === 'applied' && empty($c->apply_opened_at)): ?>
+ <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� 鏈墦寮�琛ㄥ崟</div>
+ <?php elseif ($c->status === 'invited'): ?>
+ <?php $tk_list = IM_Token::get_by_candidate($c->id);
+ if (!empty($tk_list)):
+ $tk = $tk_list[0]; ?>
+ <?php if (!empty($tk->opened_at)): ?>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� 宸叉煡鐪嬮潰璇曢</div>
+ <?php else: ?>
+ <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� 鏈偣寮�闈㈣瘯閾炬帴</div>
+ <?php endif; ?>
+ <?php endif; ?>
+ <?php elseif ($c->status === 'training'): ?>
+ <?php if (!empty($c->training_opened_at)): ?>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� 宸叉墦寮�鍩硅椤甸潰</div>
+ <?php else: ?>
+ <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� 鏈墦寮�鍩硅椤甸潰</div>
+ <?php endif; ?>
+ <?php elseif ($c->status === 'trained'): ?>
+ <?php if (!empty($c->training_completed_at)): ?>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� <?= date('m/d H:i', strtotime($c->training_completed_at)) ?> 瀹屾垚</div>
+ <?php endif; ?>
+ <?php endif; ?>
+ </td>
+ <td>
+ <?php if ($c->status === 'applied'): ?>
+ <span class="im-muted">绛夊緟瀹屽杽淇℃伅</span>
+ <?php elseif ($subs): ?>
+ <?php foreach (array_slice($subs, 0, 3) as $s): ?>
+ <span class="im-tag"><?= esc_html($s) ?></span>
+ <?php endforeach; ?>
+ <?php if (count($subs) > 3): ?><span
+ class="im-tag im-tag-more">+<?= count($subs) - 3 ?></span><?php endif; ?>
+ <?php else: ?><span class="im-muted">鈥�</span><?php endif; ?>
+ </td>
+ <td>
+ <?php if ($c->status !== 'applied'): ?>
+ <a href="<?= esc_url($detail) ?>" class="button button-small">鏌ョ湅璇︽儏</a>
+ <?php endif; ?>
+
+ <?php if ($c->status === 'applied'): ?>
+ <button class="button button-small im-action-btn" data-action="resend_joinus"
+ data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->last_name . ' ' . $c->first_name) ?>">閲嶆柊鍙戦�侀偖浠�</button>
+ <?php elseif ($c->status === 'screening'): ?>
+ <button class="button button-small button-primary im-action-btn" data-action="invite"
+ data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鍙戦�侀潰璇曢個璇�</button>
+ <button class="button button-small im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鎷掔粷</button>
+ <?php elseif ($c->status === 'invited'): ?>
+ <button class="button button-small im-action-btn" data-action="invite" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">閲嶆柊鍙戦�侀個璇�</button>
+ <button class="button button-small im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鎷掔粷</button>
+ <?php elseif ($c->status === 'completed'): ?>
+ <button class="button button-small im-action-btn" style="border-color:#10b981;color:#10b981"
+ data-action="hire" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">褰曞彇</button>
+ <button class="button button-small im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鎷掔粷</button>
+ <?php elseif ($c->status === 'training'): ?>
+ <button class="button button-small im-action-btn" data-action="resend_training"
+ data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">閲嶆柊鍙戦�佸煿璁摼鎺�</button>
+ <?php elseif ($c->status === 'trained'): ?>
+ <button class="button button-small im-action-btn" data-action="resend_trained_email"
+ data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">閲嶆柊鍙戦�佽处鍙烽偖浠�</button>
+ <?php endif; ?>
+
+ <button class="button button-small im-action-btn" style="color:#ef4444;border-color:#fca5a5"
+ data-action="delete" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($c->last_name . ' ' . $c->first_name) ?>">鍒犻櫎</button>
+
+ <?php if (defined('IM_DEBUG_LINKS') && IM_DEBUG_LINKS): ?>
+ <div
+ style="margin-top:12px;padding-top:8px;border-top:1px dashed #e5e7eb;font-size:12px;word-break:break-all;line-height:1.5">
+ <?php if (empty($c->apply_token_used) && $c->apply_token): ?>
+ <div style="color:#6b7280;margin-bottom:4px">璋冭瘯: 琛ㄥ崟閾炬帴 <br><a
+ href="<?= esc_url(add_query_arg('im_apply_token', $c->apply_token, IM_APPLY_PAGE_URL)) ?>"
+ target="_blank"
+ style="color:#8b5cf6"><?= esc_html(add_query_arg('im_apply_token', $c->apply_token, IM_APPLY_PAGE_URL)) ?></a>
+ </div>
+ <?php endif; ?>
+ <?php if (in_array($c->status, ['invited', 'completed', 'hired', 'rejected', 'training', 'trained'])):
+ $latest_token = IM_Token::get_by_candidate($c->id);
+ if (!empty($latest_token)):
+ $tk2 = $latest_token[0]; ?>
+ <div style="color:#6b7280">璋冭瘯: 闈㈣瘯閾炬帴 <br><a
+ href="<?= esc_url(add_query_arg('im_token', $tk2->token, IM_INTERVIEW_PAGE_URL)) ?>" target="_blank"
+ style="color:#3b82f6"><?= esc_html(add_query_arg('im_token', $tk2->token, IM_INTERVIEW_PAGE_URL)) ?></a>
+ </div>
+ <?php endif; endif; ?>
+ <?php if (in_array($c->status, ['training', 'trained']) && !empty($c->training_token)): ?>
+ <div style="color:#6b7280">璋冭瘯: 鍩硅閾炬帴 <br><a
+ href="<?= esc_url(add_query_arg('im_training_token', $c->training_token, IM_TRAINING_PAGE_URL)) ?>" target="_blank"
+ style="color:#f97316"><?= esc_html(add_query_arg('im_training_token', $c->training_token, IM_TRAINING_PAGE_URL)) ?></a>
+ </div>
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+ </td>
+ </tr>
+ <?php endforeach; else: ?>
+ <tr>
+ <td colspan="6" class="im-empty">鏆傛棤鍊欓�変汉鏁版嵁</td>
+ </tr>
+ <?php endif; ?>
+ </tbody>
+ </table>
+
+ <?php if ($pages > 1):
+ $base = admin_url('admin.php?page=im-candidates' . ($status ? '&status=' . $status : '') . ($search ? '&s=' . urlencode($search) : ''));
+ ?>
+ <div class="tablenav bottom">
+ <div style="display:flex;gap:4px;padding:8px 0">
+ <?php for ($i = 1; $i <= $pages; $i++): ?>
+ <a href="<?= $base ?>&paged=<?= $i ?>"
+ class="button <?= $i === $page ? 'button-primary' : '' ?>"><?= $i ?></a>
+ <?php endfor; ?>
+ </div>
+ </div>
+ <?php endif; ?>
+ </div>
+ <?php im_admin_modal(); ?>
+<?php
+}
+
+/* ============================================================
+ 鍊欓�変汉璇︽儏椤�
+ ============================================================ */
+function im_admin_detail()
+{
+ $id = (int) ($_GET['id'] ?? 0);
+ $c = $id ? IM_Candidate::get($id) : null;
+ if (!$c) {
+ echo '<div class="wrap"><div class="notice notice-error"><p>鍊欓�変汉涓嶅瓨鍦ㄣ��</p></div></div>';
+ return;
+ }
+ if ($c->status === 'applied') {
+ echo '<div class="wrap"><div class="notice notice-warning"><p>璇ュ�欓�変汉灏氭湭鎻愪氦璇︾粏鐢宠琛紝鏃犳硶鏌ョ湅璇︽儏銆�</p></div><a href="' . admin_url('admin.php?page=im-candidates') . '" class="button">杩斿洖鍒楄〃</a></div>';
+ return;
+ }
+
+ $subjects = IM_Candidate::get_subjects($c);
+ $tokens = IM_Token::get_by_candidate($id);
+ $attachments = IM_Attachment::get_by_candidate($id);
+ $upload_dir = wp_upload_dir();
+ $status_lbl = [
+ 'applied' => '寰呭畬鍠勪俊鎭�',
+ 'screening' => '宸叉彁浜よ缁嗕俊鎭�',
+ 'invited' => '宸查個璇�',
+ 'rejected' => '宸叉嫆缁�',
+ 'completed' => '宸插畬鎴�',
+ 'hired' => '宸插綍鍙�',
+ 'training' => '鏈畬鎴愬煿璁�',
+ 'trained' => '宸插畬鎴愬煿璁�'
+ ];
+ $status_colors = [
+ 'applied' => ['bg' => '#fffbeb', 'text' => '#b45309', 'border' => '#fde68a'],
+ 'screening' => ['bg' => '#f3e8ff', 'text' => '#6d28d9', 'border' => '#e9d5ff'],
+ 'invited' => ['bg' => '#eff6ff', 'text' => '#1d4ed8', 'border' => '#bfdbfe'],
+ 'rejected' => ['bg' => '#fef2f2', 'text' => '#b91c1c', 'border' => '#fecaca'],
+ 'completed' => ['bg' => '#ecfdf5', 'text' => '#047857', 'border' => '#a7f3d0'],
+ 'hired' => ['bg' => '#ecfccb', 'text' => '#166534', 'border' => '#d9f99d'],
+ 'training' => ['bg' => '#fff7ed', 'text' => '#c2410c', 'border' => '#fed7aa'],
+ 'trained' => ['bg' => '#ecfeff', 'text' => '#0e7490', 'border' => '#a5f3fc']
+ ];
+
+ $full_name = esc_html($c->first_name . ' ' . $c->last_name);
+ $p_name = esc_html($c->preferred_name);
+ $initials = mb_substr(trim($c->first_name), 0, 1) . mb_substr(trim($c->last_name), 0, 1);
+ if (!$initials && $full_name)
+ $initials = mb_substr($full_name, 0, 1);
+ $s_col = $status_colors[$c->status];
+ ?>
+ <div class="im-a im-detail-wrap">
+ <a href="<?= admin_url('admin.php?page=im-candidates') ?>" class="im-back-link">
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
+ stroke-linecap="round" stroke-linejoin="round">
+ <path d="M19 12H5m7 7l-7-7 7-7" />
+ </svg>
+ 杩斿洖鍊欓�変汉鍒楄〃
+ </a>
+
+ <!-- Hero Card -->
+ <div class="im-hero-card">
+ <div class="im-hero-profile">
+ <div class="im-avatar"><?= strtoupper($initials) ?></div>
+ <div class="im-hero-info">
+ <h1><?= $full_name ?>
+ <?= $p_name ? '<span style="color:#6b7280;font-size:18px;font-weight:500">(' . $p_name . ')</span>' : '' ?>
+ </h1>
+ <div class="im-hero-meta">
+ <span class="im-hero-meta-item">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
+ <path d="M22 6l-10 7L2 6" />
+ </svg>
+ <a href="mailto:<?= esc_attr($c->email) ?>"
+ style="color:inherit;text-decoration:none"><?= esc_html($c->email) ?></a>
+ </span>
+ <?php if ($c->phone): ?>
+ <span class="im-hero-meta-item">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path
+ d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" />
+ </svg>
+ <?= esc_html($c->phone) ?>
+ </span>
+ <?php endif; ?>
+ <?php if (!empty($c->country) || !empty($c->city)): ?>
+ <span class="im-hero-meta-item">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" />
+ <circle cx="12" cy="10" r="3" />
+ </svg>
+ <?= esc_html(trim(($c->city ?? '') . (!empty($c->city) && !empty($c->country) ? ', ' : '') . ($c->country ?? ''))) ?>
+ </span>
+ <?php endif; ?>
+ </div>
+ </div>
+ </div>
+ <div>
+ <span class="im-badge"
+ style="background:<?= $s_col['bg'] ?>;color:<?= $s_col['text'] ?>;border-color:<?= $s_col['border'] ?>;padding:6px 16px;font-size:14px">
+ <?= $status_lbl[$c->status] ?>
+ </span>
+ </div>
+ </div>
+
+ <div class="im-main-grid">
+ <div class="im-col-main">
+ <!-- 璇︽儏鍗$墖锛氭暀鑲茶儗鏅� -->
+ <div class="im-card-modern">
+ <h2 class="im-card-title">
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path d="M22 10v6M2 10l10-5 10 5-10 5z" />
+ <path d="M6 12v5c3 3 9 3 12 0v-5" />
+ </svg>
+ 鏁欒偛鑳屾櫙
+ </h2>
+ <div class="im-timeline">
+ <div class="im-timeline-item">
+ <div class="im-timeline-icon"></div>
+ <?php $degree_map = ["Bachelor's" => '鏈', "Master's" => '纭曞+', 'PhD' => '鍗氬+']; ?>
+ <div class="im-timeline-date"><?= esc_html($c->grad_year ?: '鏈煡骞翠唤') ?> 姣曚笟
+ (<?= esc_html($degree_map[$c->degree_level] ?? ($c->degree_level ?: '鏈�楂樺鍘�')) ?>)</div>
+ <h3 class="im-timeline-title"><?= esc_html($c->university ?: '鈥�') ?></h3>
+ <div class="im-timeline-sub">涓撲笟锛�<?= esc_html($c->major ?: '鈥�') ?> |
+ GPA锛�<?= esc_html($c->gpa ?: '鈥�') ?>
+ <?= $c->deans_list ? ' | 馃弲 Dean\'s List' : '' ?>
+ </div>
+ </div>
+ <?php if ($c->ug_university): ?>
+ <div class="im-timeline-item">
+ <div class="im-timeline-icon" style="border-color:#d1d5db"></div>
+ <div class="im-timeline-date" style="color:#6b7280"><?= esc_html($c->ug_grad_year ?: '鏈煡骞翠唤') ?>
+ 姣曚笟 (鏈瀛﹀巻)</div>
+ <h3 class="im-timeline-title"><?= esc_html($c->ug_university) ?></h3>
+ <div class="im-timeline-sub">涓撲笟锛�<?= esc_html($c->ug_major ?: '鈥�') ?></div>
+ </div>
+ <?php endif; ?>
+ <?php if (!empty($c->ms_university)): ?>
+ <div class="im-timeline-item">
+ <div class="im-timeline-icon" style="border-color:#8b5cf6"></div>
+ <div class="im-timeline-date" style="color:#8b5cf6"><?= esc_html($c->ms_grad_year ?: '鏈煡骞翠唤') ?>
+ 姣曚笟 (纭曞+瀛﹀巻)</div>
+ <h3 class="im-timeline-title"><?= esc_html($c->ms_university) ?></h3>
+ <div class="im-timeline-sub">涓撲笟锛�<?= esc_html($c->ms_major ?: '鈥�') ?></div>
+ </div>
+ <?php endif; ?>
+ <?php if ($c->ca_highschool): ?>
+ <div class="im-timeline-item">
+ <div class="im-timeline-icon" style="border-color:#f59e0b"></div>
+ <div class="im-timeline-date" style="color:#f59e0b">Canadian High School</div>
+ <h3 class="im-timeline-title"><?= esc_html($c->ca_highschool_name ?: '鈥�') ?></h3>
+ <div class="im-timeline-sub">e.g. OSSD or BC curriculum</div>
+ </div>
+ <?php endif; ?>
+ </div>
+ </div>
+
+ <!-- 鎶�鑳戒笌缁忛獙 -->
+ <div class="im-card-modern">
+ <h2 class="im-card-title">
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path
+ d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
+ </svg>
+ 鎶�鑳戒笌缁忛獙
+ </h2>
+ <div class="im-info-grid">
+ <div class="im-info-item full-width">
+ <div class="im-info-label">鎺堣绉戠洰 (<?= count($subjects) ?>涓�)</div>
+ <div class="im-info-val">
+ <?php if ($subjects):
+ foreach ($subjects as $s): ?>
+ <span class="im-tag"><?= esc_html($s) ?></span>
+ <?php endforeach; else: ?>
+ <span class="im-muted">鏈�夋嫨浠讳綍绉戠洰</span>
+ <?php endif; ?>
+ </div>
+ </div>
+ <div class="im-info-item full-width">
+ <div class="im-info-label">鑻辫娴佸埄搴�</div>
+ <div class="im-info-val"><?php
+ $fluency_map = ['Native' => 'Native (姣嶈)', 'Fluent' => 'Fluent (娴佸埄)', 'Basic' => 'Basic (鍩虹)'];
+ echo esc_html($fluency_map[$c->languages] ?? ($c->languages ?: '鈥�'));
+ ?></div>
+ </div>
+ <div class="im-info-item full-width">
+ <div class="im-info-label">鏁欏缁忛獙</div>
+ <div class="im-info-val"><?= nl2br(esc_html($c->teaching_exp ?: '鈥�')) ?></div>
+ </div>
+ <?php if ($c->has_achievement): ?>
+ <div class="im-info-item full-width">
+ <div class="im-info-label">涓汉鎴愬氨 (<?= esc_html($c->achievement_type) ?>)</div>
+ <div class="im-info-val"
+ style="background:#f9fafb;padding:12px 16px;border-radius:8px;border:1px solid #f3f4f6">
+ <?= nl2br(esc_html($c->achievement_desc ?: '鈥�')) ?>
+ </div>
+ </div>
+ <?php endif; ?>
+ <?php if ($c->extra_notes): ?>
+ <div class="im-info-item full-width">
+ <div class="im-info-label">琛ュ厖璇存槑</div>
+ <div class="im-info-val"
+ style="background:#f9fafb;padding:12px 16px;border-radius:8px;border:1px solid #f3f4f6">
+ <?= nl2br(esc_html($c->extra_notes)) ?>
+ </div>
+ </div>
+ <?php endif; ?>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鍙充晶鍖哄煙 -->
+ <div class="im-col-side">
+ <!-- 鐘舵�佹搷浣滃尯 -->
+ <?php if ($c->status === 'screening'): ?>
+ <div class="im-action-box">
+ <h3>瀹℃牳鎿嶄綔</h3>
+ <p>璇峰喅瀹氭槸鍚﹂個璇疯鍊欓�変汉杩涜闈㈣瘯</p>
+ <button class="im-btn-primary im-action-btn" data-action="invite" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>">
+ 鍙戦�侀潰璇曢個璇�
+ </button>
+ <button class="im-btn-danger im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>">
+ 鎷掔粷璇ュ�欓�変汉
+ </button>
+ <div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
+ </div>
+ <?php elseif ($c->status === 'invited'): ?>
+ <div class="im-action-box">
+ <h3>绛夊緟闈㈣瘯涓�</h3>
+ <p>濡傛灉鍊欓�変汉鏈敹鍒伴偖浠跺彲閲嶆柊鍙戦��</p>
+ <button class="im-btn-primary im-action-btn" data-action="invite" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>">
+ 閲嶆柊鍙戦�侀個璇�
+ </button>
+ <div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
+ </div>
+ <?php elseif ($c->status === 'completed'): ?>
+ <div class="im-action-box" style="background:linear-gradient(135deg,#ecfdf5,#d1fae5);border-color:#a7f3d0">
+ <h3 style="color:#047857">闈㈣瘯瀹℃煡</h3>
+ <p style="color:#059669">瑙嗛宸叉彁浜わ紝璇锋煡鐪嬪苟鍦ㄩ�氳繃鍚庡綍鐢�</p>
+ <div style="display:flex;gap:12px;flex-wrap:wrap">
+ <button class="im-btn-success im-action-btn" data-action="hire" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>" style="flex:1">
+ 姝e紡褰曞彇鍊欓�変汉
+ </button>
+ <button class="im-btn-danger im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>" style="flex:1">
+ 鎷掔粷璇ュ�欓�変汉
+ </button>
+ </div>
+ <div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
+ </div>
+ <?php elseif ($c->status === 'training'): ?>
+ <div class="im-action-box" style="background:linear-gradient(135deg,#fff7ed,#ffedd5);border-color:#fed7aa">
+ <h3 style="color:#c2410c">鍩硅杩涜涓�</h3>
+ <p style="color:#ea580c">鍊欓�変汉姝e湪瀹屾垚鍩硅璇剧▼</p>
+ <?php if (!empty($c->training_opened_at)): ?>
+ <div style="font-size:13px;color:#059669;margin-bottom:12px;font-weight:600">鉁� 宸蹭簬 <?= date('Y/m/d H:i', strtotime($c->training_opened_at)) ?> 鎵撳紑鍩硅椤甸潰</div>
+ <?php else: ?>
+ <div style="font-size:13px;color:#9ca3af;margin-bottom:12px">鈼� 灏氭湭鎵撳紑鍩硅椤甸潰</div>
+ <?php endif; ?>
+ <button class="im-btn-primary im-action-btn" data-action="resend_training" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>">
+ 閲嶆柊鍙戦�佸煿璁摼鎺�
+ </button>
+ <div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
+ </div>
+ <?php elseif ($c->status === 'trained'): ?>
+ <div class="im-action-box" style="background:linear-gradient(135deg,#ecfeff,#cffafe);border-color:#a5f3fc">
+ <h3 style="color:#0e7490">鍩硅宸插畬鎴�</h3>
+ <p style="color:#0891b2">鍊欓�変汉宸查�氳繃鎵�鏈夊煿璁ā鍧�</p>
+ <?php if (!empty($c->training_completed_at)): ?>
+ <div style="font-size:13px;color:#059669;font-weight:600;margin-bottom:12px">鉁� 浜� <?= date('Y/m/d H:i', strtotime($c->training_completed_at)) ?> 瀹屾垚鍩硅</div>
+ <?php endif; ?>
+ <button class="im-btn-primary im-action-btn" data-action="resend_trained_email" data-id="<?= $c->id ?>"
+ data-name="<?= esc_attr($full_name) ?>">
+ 閲嶆柊鍙戦�佽处鍙烽偖浠�
+ </button>
+ <div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
+ </div>
+ <?php endif; ?>
+
+ <!-- 闄勪欢鍒楄〃 -->
+ <?php if ($attachments): ?>
+ <div class="im-card-modern">
+ <h2 class="im-card-title">
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z" />
+ <path d="M13 2v7h7" />
+ </svg>
+ 闄勪欢鏉愭枡
+ </h2>
+ <?php
+ $grouped = [];
+ foreach ($attachments as $att)
+ $grouped[$att->file_type][] = $att;
+ $att_groups = ['transcript_files' => '鎴愮哗鍗�', 'achievement_files' => '鎴愬氨璇佹槑', 'extra_files' => '琛ュ厖鏉愭枡'];
+ foreach ($att_groups as $type => $label):
+ if (empty($grouped[$type]))
+ continue;
+ ?>
+ <div style="margin-bottom:16px">
+ <div class="im-info-label" style="margin-bottom:8px"><?= $label ?></div>
+ <?php foreach ($grouped[$type] as $att):
+ $att_url = $upload_dir['baseurl'] . '/im-applications/' . $c->id . '/' . basename($att->file_path);
+ ?>
+ <div class="im-doc-item">
+ <div class="im-doc-info">
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
+ <path d="M14 2v6h6" />
+ <path d="M16 13H8" />
+ <path d="M16 17H8" />
+ <path d="M10 9H8" />
+ </svg>
+ <div class="im-doc-name" title="<?= esc_attr($att->file_name) ?>">
+ <?= esc_html($att->file_name) ?>
+ </div>
+ </div>
+ <div class="im-doc-actions">
+ <a href="<?= esc_url($att_url) ?>" target="_blank" class="im-doc-btn" title="鍦ㄦ柊鏍囩椤甸瑙�">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
+ <circle cx="12" cy="12" r="3" />
+ </svg>
+ </a>
+ <a href="<?= esc_url($att_url) ?>" download class="im-doc-btn" title="涓嬭浇鏂囦欢">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
+ </svg>
+ </a>
+ </div>
+ </div>
+ <?php endforeach; ?>
+ </div>
+ <?php endforeach; ?>
+ </div>
+ <?php endif; ?>
+
+ <!-- 闈㈣瘯璁板綍 -->
+ <?php if (in_array($c->status, ['invited', 'completed', 'hired', 'rejected', 'training', 'trained'])): ?>
+ <div class="im-card-modern">
+ <h2 class="im-card-title">
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path d="M23 7l-7 5 7 5V7z" />
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
+ </svg>
+ 闈㈣瘯璁板綍
+ </h2>
+ <?php if ($tokens):
+ foreach ($tokens as $tk):
+ $used = (bool) $tk->is_used;
+ $expired = strtotime($tk->expires_at) < time();
+ if ($used) {
+ $bs = '#dcfce7';
+ $bc = '#86efac';
+ $bt = '#166534';
+ $bl = '宸叉彁浜よ棰�';
+ } elseif ($expired) {
+ $bs = '#fee2e2';
+ $bc = '#fca5a5';
+ $bt = '#991b1b';
+ $bl = '閾炬帴宸茶繃鏈�';
+ } else {
+ $bs = '#dbeafe';
+ $bc = '#93c5fd';
+ $bt = '#1e40af';
+ $bl = '鏈夋晥涓�';
+ }
+ ?>
+ <div class="im-token-box">
+ <div class="im-token-status">
+ <span class="im-badge"
+ style="background:<?= $bs ?>;color:<?= $bt ?>;border-color:<?= $bc ?>"><?= $bl ?></span>
+ </div>
+ <div class="im-token-date"><?= date('Y/m/d H:i', strtotime($tk->created_at)) ?> 鍙戦��</div>
+ <div class="im-token-exp" style="margin-bottom:8px">鑷�
+ <?= date('m/d H:i', strtotime($tk->expires_at)) ?> 澶辨晥
+ </div>
+
+ <?php if (!empty($tk->opened_at)): ?>
+ <div class="im-token-exp" style="color:#059669;font-weight:600">
+ 鉁� 浜� <?= date('Y/m/d H:i', strtotime($tk->opened_at)) ?> 鏌ョ湅棰樼洰 <?= $used ? '' : '(鍊掕鏃惰繘琛屼腑)' ?>
+ </div>
+ <?php else: ?>
+ <div class="im-token-exp" style="color:#f59e0b">鈼� 鍊欓�変汉鏈墦寮�閾炬帴</div>
+ <?php endif; ?>
+
+ <?php if ($used && $tk->video_path && file_exists($tk->video_path)):
+ $vurl = $upload_dir['baseurl'] . '/interviews/' . $c->id . '/' . $tk->video_filename;
+ $fext = strtolower(pathinfo($tk->video_filename, PATHINFO_EXTENSION));
+ $archive_exts = ['zip', 'rar', '7z', 'gz', 'tar'];
+ $fsize = filesize($tk->video_path);
+ $fsize_str = $fsize > 1048576 ? round($fsize / 1048576, 1) . ' MB' : round($fsize / 1024, 1) . ' KB';
+ ?>
+ <?php if (in_array($fext, $archive_exts, true)): ?>
+ <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:20px 24px;display:flex;align-items:center;gap:16px;margin-top:8px">
+ <div style="width:48px;height:48px;background:#eef2ff;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0">
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" stroke-width="2"><path d="M21 8v13H3V3h12l6 5z"/><path d="M14 3v5h6"/></svg>
+ </div>
+ <div style="flex:1;min-width:0">
+ <div style="font-weight:600;color:#1e293b;font-size:14px;word-break:break-all"><?= esc_html($tk->video_filename) ?></div>
+ <div style="color:#64748b;font-size:13px;margin-top:2px"><?= strtoupper($fext) ?> 鍘嬬缉鍖� 路 <?= $fsize_str ?></div>
+ </div>
+ <a href="<?= esc_url($vurl) ?>" download style="display:inline-flex;align-items:center;gap:6px;background:#6366f1;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;text-decoration:none;white-space:nowrap">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>涓嬭浇
+ </a>
+ </div>
+ <div style="margin-top:6px;font-size:12px;color:#94a3b8"><?= date('m/d H:i', strtotime($tk->submitted_at)) ?> 鎻愪氦</div>
+ <?php else: ?>
+ <video controls class="im-video-player" preload="metadata">
+ <source src="<?= esc_url($vurl) ?>">
+ </video>
+ <div class="im-video-footer">
+ <span><?= date('m/d H:i', strtotime($tk->submitted_at)) ?> 鎻愪氦</span>
+ <a href="<?= esc_url($vurl) ?>" download class="im-video-dl">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2">
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
+ </svg>涓嬭浇
+ </a>
+ </div>
+ <?php endif; ?>
+ <?php elseif ($used): ?>
+ <p class="im-muted" style="margin:0;font-size:13px">瑙嗛宸茶劚鏈烘垨涓嶅瓨鍦�</p>
+ <?php endif; ?>
+ </div>
+ <?php endforeach; else: ?>
+ <div class="im-empty"
+ style="padding:24px!important;background:#f9fafb;border-radius:12px;border:1px dashed #e5e7eb">
+ 灏氭湭鍙戦�侀潰璇曢個璇�</div>
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+ </div>
+ </div>
+ </div>
+ <?php im_admin_modal(); ?>
+<?php
+}
+
+/* ============================================================
+ 纭寮圭獥 HTML
+ ============================================================ */
+function im_admin_modal()
+{ ?>
+ <div id="im-modal" style="display:none" class="im-modal-bg"
+ data-nonce="<?php echo esc_attr(wp_create_nonce('im_admin_nonce')); ?>"
+ data-ajaxurl="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
+ <div class="im-modal">
+ <h3>鎿嶄綔纭</h3>
+ <p id="im-modal-body"></p>
+ <textarea id="im-modal-textarea" style="display:none" placeholder="杈撳叆鍘熷洜..."></textarea>
+ <div class="im-modal-btns">
+ <button class="button im-modal-cancel">鍙栨秷</button>
+ <button class="button button-primary" id="im-modal-ok">纭</button>
+ </div>
+ </div>
+ </div>
+ <?php
+}
+
+/* ============================================================
+ Nginx 閰嶇疆鍚戝椤碉紙宸ュ叿鑿滃崟锛�
+ ============================================================ */
+function im_server_setup()
+{
+ $upload_dir = wp_upload_dir();
+ $home = rtrim(home_url('/'), '/');
+
+ $checks = [
+ ['upload_max_filesize', ini_get('upload_max_filesize'), wp_convert_hr_to_bytes(ini_get('upload_max_filesize')) >= 400 * 1024 * 1024, '寤鸿璁句负 500M'],
+ ['post_max_size', ini_get('post_max_size'), wp_convert_hr_to_bytes(ini_get('post_max_size')) >= 400 * 1024 * 1024, '寤鸿璁句负 512M'],
+ ['max_execution_time', ini_get('max_execution_time') . 's', (int) ini_get('max_execution_time') === 0 || (int) ini_get('max_execution_time') >= 180, '寤鸿璁句负 300'],
+ ['memory_limit', ini_get('memory_limit'), true, '鈥�'],
+ ];
+ ?>
+ <div class="wrap" style="max-width:860px">
+ <h1>Interview Manager 鈥� 鏈嶅姟鍣ㄩ厤缃悜瀵�</h1>
+
+ <div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
+ <h2 style="margin-top:0">鈶� Nginx 閰嶇疆锛堝繀椤绘墜鍔ㄥ畬鎴愶級</h2>
+ <p>鍦� <code>server { }</code> 鍧楀唴娣诲姞浠ヤ笅閰嶇疆锛岀劧鍚庢墽琛� <code>sudo nginx -t && sudo nginx -s reload</code>锛�</p>
+ <pre
+ style="background:#1e293b;color:#e2e8f0;padding:20px;border-radius:8px;font-size:13px;line-height:1.7;overflow-x:auto"># 瑙嗛涓婁紶闄愬埗
+ client_max_body_size 512M;
+ client_body_timeout 300s;
+ send_timeout 300s;
+
+ # WordPress 鍥哄畾閾炬帴锛堝凡鏈夊垯璺宠繃锛�
+ location / {
+ try_files $uri $uri/ /index.php?$args;
+ }
+
+ # 涓婁紶鐩綍瀹夊叏
+ location ~* ^/wp-content/uploads/(interviews|im-applications)/ {
+ add_header X-Content-Type-Options nosniff;
+ }</pre>
+ </div>
+
+ <div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
+ <h2 style="margin-top:0">鈶� PHP 閰嶇疆锛坧hp.ini 鎴� .user.ini锛�</h2>
+ <pre style="background:#1e293b;color:#e2e8f0;padding:20px;border-radius:8px;font-size:13px;line-height:1.7">upload_max_filesize = 500M
+ post_max_size = 512M
+ max_execution_time = 300
+ max_input_time = 300
+ memory_limit = 256M</pre>
+ <p>淇敼鍚庨噸鍚� PHP-FPM锛�<code>sudo systemctl restart php8.x-fpm</code></p>
+ </div>
+
+ <div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
+ <h2 style="margin-top:0">鈶� 鍒锋柊 WordPress 鍥哄畾閾炬帴</h2>
+ <p>杩涘叆 <strong>鍚庡彴 鈫� 璁剧疆 鈫� 鍥哄畾閾炬帴</strong>锛岀洿鎺ョ偣鍑汇�屼繚瀛樻洿鏀广�嶅嵆鍙紙鏃犻渶鏀逛换浣曡缃級銆�</p>
+ <p><a href="<?= admin_url('options-permalink.php') ?>" class="button button-primary" target="_blank">鍓嶅線鍥哄畾閾炬帴璁剧疆
+ 鈫�</a></p>
+ </div>
+
+ <div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
+ <h2 style="margin-top:0">鈶� WordPress 椤甸潰閰嶇疆</h2>
+ <p>闇�鍦� WordPress 鍚庡彴鍒涘缓浠ヤ笅涓や釜椤甸潰锛�</p>
+ <table class="widefat" style="max-width:600px">
+ <tr>
+ <th>椤甸潰</th>
+ <th>Shortcode</th>
+ <th>寤鸿 URL slug</th>
+ </tr>
+ <tr>
+ <td>Join Us 鎶ュ悕椤�</td>
+ <td><code>[im_joinus_form]</code></td>
+ <td><code>/join-us/</code></td>
+ </tr>
+ <tr>
+ <td>璇︾粏鐢宠琛ㄥ崟椤�</td>
+ <td><code>[im_apply_form]</code></td>
+ <td><code>/apply/</code></td>
+ </tr>
+ <tr>
+ <td>闈㈣瘯椤�</td>
+ <td><code>[im_interview]</code></td>
+ <td><code>/interview/</code></td>
+ </tr>
+ <tr>
+ <td>鍩硅椤�</td>
+ <td><code>[im_training]</code></td>
+ <td><code>/training/</code></td>
+ </tr>
+ </table>
+ <p style="margin-top:12px">鍒涘缓濂介〉闈㈠悗锛屽湪鐗囨 1 椤堕儴灏嗗搴斿父閲忔敼涓哄疄闄� URL锛�</p>
+ <code>define('IM_APPLY_PAGE_URL', home_url('/apply/'));</code><br>
+ <code>define('IM_INTERVIEW_PAGE_URL', home_url('/interview/'));</code><br>
+ <code>define('IM_TRAINING_PAGE_URL', home_url('/training/'));</code><br>
+ <code>define('IM_TRAINING_ACCOUNT', 'your_account@example.com');</code><br>
+ <code>define('IM_TRAINING_PASSWORD', 'your_password');</code>
+ </div>
+
+ <div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px">
+ <h2 style="margin-top:0">鈶� 褰撳墠鐜妫�娴�</h2>
+ <table class="widefat" style="max-width:640px">
+ <thead>
+ <tr>
+ <th>閰嶇疆椤�</th>
+ <th>褰撳墠鍊�</th>
+ <th>鐘舵��</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($checks as [$name, $val, $ok, $tip]): ?>
+ <tr>
+ <td><code><?= $name ?></code></td>
+ <td><?= esc_html($val) ?></td>
+ <td><?= $ok ? '鉁�' : '鈿狅笍 ' . $tip ?></td>
+ </tr>
+ <?php endforeach; ?>
+ <tr>
+ <td>瑙嗛瀛樺偍鐩綍</td>
+ <td style="font-size:12px"><?= esc_html($upload_dir['basedir'] . '/interviews/') ?></td>
+ <td><?= is_dir($upload_dir['basedir'] . '/interviews') ? (is_writable($upload_dir['basedir'] . '/interviews') ? '鉁� 鍙啓' : '鉂� 涓嶅彲鍐�') : '锛堥娆′笂浼犳椂鑷姩鍒涘缓锛�' ?>
+ </td>
+ </tr>
+ <tr>
+ <td>鐢宠鏂囦欢鐩綍</td>
+ <td style="font-size:12px"><?= esc_html($upload_dir['basedir'] . '/im-applications/') ?></td>
+ <td><?= is_dir($upload_dir['basedir'] . '/im-applications') ? (is_writable($upload_dir['basedir'] . '/im-applications') ? '鉁� 鍙啓' : '鉂� 涓嶅彲鍐�') : '锛堥娆′笂浼犳椂鑷姩鍒涘缓锛�' ?>
+ </td>
+ </tr>
+ <tr>
+ <td>闈㈣瘯椤甸潰 URL</td>
+ <td><a href="<?= esc_url(IM_INTERVIEW_PAGE_URL) ?>" target="_blank"
+ style="font-size:12px"><?= esc_html(IM_INTERVIEW_PAGE_URL) ?></a></td>
+ <td>鈥�</td>
+ </tr>
+ <tr>
+ <td>鍩硅椤甸潰 URL</td>
+ <td><a href="<?= esc_url(IM_TRAINING_PAGE_URL) ?>" target="_blank"
+ style="font-size:12px"><?= esc_html(IM_TRAINING_PAGE_URL) ?></a></td>
+ <td>鈥�</td>
+ </tr>
+ <tr>
+ <td>鍩硅涓存椂璐﹀彿</td>
+ <td style="font-size:12px"><?= esc_html(IM_TRAINING_ACCOUNT) ?></td>
+ <td><?= IM_TRAINING_ACCOUNT !== 'your_account@example.com' ? '鉁�' : '鈿狅笍 璇烽厤缃疄闄呰处鍙�' ?></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <?php
+}
diff --git "a/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin.php" "b/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin.php"
index 909e150..be21afd 100644
--- "a/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin.php"
+++ "b/\346\230\240\346\230\237\346\225\231\350\202\262/snippet-4-admin.php"
@@ -20,10 +20,10 @@
娉ㄥ唽鑿滃崟
============================================================ */
add_action('admin_menu', function () {
- add_menu_page('鍊欓�変汉绠$悊', '鍊欓�変汉绠$悊', 'edit_others_posts', 'im-candidates', 'im_admin_list', 'dashicons-groups', 30);
- add_submenu_page('im-candidates', '鍊欓�変汉鍒楄〃', '鍊欓�変汉鍒楄〃', 'edit_others_posts', 'im-candidates', 'im_admin_list');
- add_submenu_page('im-candidates', '鍊欓�変汉璇︽儏', '', 'edit_others_posts', 'im-candidate-detail', 'im_admin_detail');
- add_submenu_page('tools.php', 'IM 鏈嶅姟鍣ㄩ厤缃�', 'IM 鏈嶅姟鍣ㄩ厤缃�', 'manage_options', 'im-server-setup', 'im_server_setup');
+ add_menu_page('Candidate Management', 'Candidate Management', 'edit_others_posts', 'im-candidates', 'im_admin_list', 'dashicons-groups', 30);
+ add_submenu_page('im-candidates', 'Candidate List', 'Candidate List', 'edit_others_posts', 'im-candidates', 'im_admin_list');
+ add_submenu_page('im-candidates', 'Candidate Details', '', 'edit_others_posts', 'im-candidate-detail', 'im_admin_detail');
+ add_submenu_page('tools.php', 'IM Server Setup', 'IM Server Setup', 'manage_options', 'im-server-setup', 'im_server_setup');
});
/* ============================================================
@@ -614,26 +614,26 @@
var title = '', body = '', showTextarea = false;
if (actionType === 'invite') {
- title = '纭鍙戦�侀潰璇曢個璇�';
- body = '灏嗗悜 <strong>' + name + '</strong> 鍙戦�侀潰璇曢個璇凤紝<br>閭欢鍖呭惈 <strong>24灏忔椂鏈夋晥</strong> 鐨勪笓灞為潰璇曢摼鎺ワ紝纭鍙戦�侊紵';
+ title = 'Confirm Interview Invitation';
+ body = 'An interview invitation will be sent to <strong>' + name + '</strong>.<br>The email includes a dedicated interview link valid for <strong>24 hours</strong>. Proceed?';
} else if (actionType === 'reject') {
- title = '鎷掔粷鍊欓�変汉';
- body = '纭鎷掔粷 <strong>' + name + '</strong> 鐨勭敵璇凤紵<br>绯荤粺灏嗗悜鍏跺彂閫佹嫆缁濋�氱煡閭欢銆�';
+ title = 'Reject Candidate';
+ body = 'Are you sure you want to reject <strong>' + name + '</strong>\'s application?<br>A rejection email will be sent.';
} else if (actionType === 'hire') {
- title = '褰曞彇鍊欓�変汉';
- body = '纭姝e紡褰曞彇 <strong>' + name + '</strong>锛�<br>绯荤粺灏嗗悜鍏跺彂閫佸綍鍙栭�氱煡閭欢鍙婂煿璁摼鎺ャ��';
+ title = 'Hire Candidate';
+ body = 'Are you sure you want to hire <strong>' + name + '</strong>?<br>An offer email and training link will be sent.';
} else if (actionType === 'resend_joinus') {
- title = '閲嶆柊鍙戦�侀偖浠�';
- body = '灏嗛噸鏂板悜 <strong>' + name + '</strong> 鍙戦�佲�濆畬鍠勮缁嗚〃鍗曗�濋摼鎺ラ偖浠躲��';
+ title = 'Resend Email';
+ body = 'A follow-up email will be sent again to <strong>' + name + '</strong> with the "Complete Detailed Form" link.';
} else if (actionType === 'resend_training') {
- title = '閲嶆柊鍙戦�佸煿璁摼鎺�';
- body = '灏嗛噸鏂板悜 <strong>' + name + '</strong> 鍙戦�佸煿璁摼鎺ラ偖浠躲��';
+ title = 'Resend Training Link';
+ body = 'A training link email will be resent to <strong>' + name + '</strong>.';
} else if (actionType === 'resend_trained_email') {
- title = '閲嶆柊鍙戦�佽处鍙烽偖浠�';
- body = '灏嗛噸鏂板悜 <strong>' + name + '</strong> 鍙戦�� Microsoft Teams 涓存椂璐﹀彿閭欢銆�';
+ title = 'Resend Account Email';
+ body = 'A Microsoft Teams temporary account email will be resent to <strong>' + name + '</strong>.';
} else if (actionType === 'delete') {
- title = '鍒犻櫎鍊欓�変汉';
- body = '纭姘镐箙鍒犻櫎 <strong>' + name + '</strong> 鐨勬墍鏈夎褰曪紵<br><span style=鈥漜olor:#ef4444鈥�>姝ゆ搷浣滀笉鍙仮澶嶏紝灏嗗悓鏃跺垹闄よ鍊欓�変汉涓婁紶鐨勬墍鏈夋枃浠讹紙闄勪欢銆侀潰璇曡棰戠瓑锛夈��</span>';
+ title = 'Delete Candidate';
+ body = 'Permanently delete all records of <strong>' + name + '</strong>?<br><span style="color:#ef4444">This action is irreversible and will also delete all uploaded files (attachments, interview videos, etc.).</span>';
}
$('#im-modal h3').text(title);
@@ -654,7 +654,7 @@
if (!cid) return;
var reason = $('#im-modal-textarea').is(':visible') ? $.trim($('#im-modal-textarea').val()) : '';
- var $b = $(this); $b.prop('disabled', true).text('澶勭悊涓�...');
+ var $b = $(this); $b.prop('disabled', true).text('Processing...');
var $modal = $('#im-modal');
var ajaxActUrl = $modal.data('ajaxurl') || '/wp-admin/admin-ajax.php';
@@ -667,9 +667,9 @@
dataType: 'json',
data: { action: ajaxAction, nonce: ajaxNonce, candidate_id: cid, reason: reason },
success: function (r) {
- $('#im-modal').fadeOut(150); $b.prop('disabled', false).text('纭');
+ $('#im-modal').fadeOut(150); $b.prop('disabled', false).text('Confirm');
var isSuccess = r && r.success;
- var msg = r && r.data && r.data.message ? ((isSuccess ? '鉁� ' : '鉂� ') + r.data.message) : (isSuccess ? '鉁� 鎿嶄綔鎴愬姛' : '鉂� 鎿嶄綔澶辫触锛岃閲嶈瘯');
+ var msg = r && r.data && r.data.message ? ((isSuccess ? '鉁� ' : '鉂� ') + r.data.message) : (isSuccess ? '鉁� Action completed successfully' : '鉂� Action failed, please try again');
var $n = $('<div class="notice ' + (isSuccess ? 'notice-success' : 'notice-error') + ' is-dismissible" style="margin-top:16px"><p>' + msg + '</p></div>');
if ($('.wp-heading-inline').length) {
$('.wp-heading-inline').after($n);
@@ -682,9 +682,9 @@
else setTimeout(function () { $n.fadeOut(400, function () { $(this).remove(); }); }, 5000);
},
error: function (xhr, status, error) {
- $('#im-modal').fadeOut(150); $b.prop('disabled', false).text('纭');
- var errMsg = '缃戠粶绻佸繖鎴栨湇鍔″櫒鎶ラ敊锛岃閲嶈瘯銆�';
- if (xhr.status === 403) errMsg = '璇锋眰琚嫆缁�(403 Forbidden)銆傝繖閫氬父鏄洜涓哄畨鍏ㄦ彃浠�(濡俉ordfence/瀹濆WAF)鎷︽埅浜嗘惡甯﹀弬鏁扮殑璇锋眰锛岃灏濊瘯灏嗗悗鍙板姞鍏ョ櫧鍚嶅崟鎴栬仈绯荤鐞嗗憳閲婃斁鎷︽埅銆�';
+ $('#im-modal').fadeOut(150); $b.prop('disabled', false).text('Confirm');
+ var errMsg = 'Network is busy or server error occurred. Please try again.';
+ if (xhr.status === 403) errMsg = 'Request denied (403 Forbidden). This is usually caused by a security plugin (e.g., Wordfence/BtPanel WAF) blocking parameterized requests. Please whitelist the admin path or contact your administrator.';
alert(errMsg);
}
});
@@ -700,97 +700,97 @@
add_action('wp_ajax_im_action_invite', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
$c = IM_Candidate::get($id);
if ($c && $c->status === 'screening') {
IM_Candidate::update_status($id, 'invited');
}
IM_Mailer::send_interview_invite($id)
- ? wp_send_json_success(['message' => '闈㈣瘯閭�璇烽偖浠跺凡鎴愬姛鍙戦�侊紒'])
- : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ワ紝璇锋鏌ラ偖浠堕厤缃��']);
+ ? wp_send_json_success(['message' => 'Interview invitation email sent successfully!'])
+ : wp_send_json_error(['message' => 'Email sending failed. Please check mail configuration.']);
});
add_action('wp_ajax_im_action_reject', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
IM_Candidate::update_status($id, 'rejected');
IM_Mailer::send_reject($id)
- ? wp_send_json_success(['message' => '宸叉嫆缁濊鍊欓�変汉骞跺彂閫侀偖浠讹紒'])
- : wp_send_json_error(['message' => '宸叉洿鏂扮姸鎬侊紝浣嗛偖浠跺彂閫佸け璐ャ��']);
+ ? wp_send_json_success(['message' => 'Candidate rejected and email sent!'])
+ : wp_send_json_error(['message' => 'Status updated, but email sending failed.']);
});
add_action('wp_ajax_im_action_hire', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
IM_Mailer::send_hire($id)
- ? wp_send_json_success(['message' => '宸插綍鍙栬鍊欓�変汉骞跺彂閫佸煿璁摼鎺ラ偖浠讹紒'])
- : wp_send_json_error(['message' => '褰曞彇鎿嶄綔澶辫触锛岃妫�鏌ラ偖浠堕厤缃��']);
+ ? wp_send_json_success(['message' => 'Candidate hired and training link email sent!'])
+ : wp_send_json_error(['message' => 'Hiring failed. Please check mail configuration.']);
});
add_action('wp_ajax_im_action_resend_joinus', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
IM_Mailer::send_joinus_confirmation($id)
- ? wp_send_json_success(['message' => '琛ㄥ崟閾炬帴閭欢宸查噸鏂板彂閫侊紒'])
- : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ャ��']);
+ ? wp_send_json_success(['message' => 'Form link email resent!'])
+ : wp_send_json_error(['message' => 'Email sending failed.']);
});
add_action('wp_ajax_im_action_resend_training', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
$c = IM_Candidate::get($id);
if (!$c || $c->status !== 'training')
- wp_send_json_error(['message' => '璇ュ�欓�変汉涓嶅湪鍩硅鐘舵��']);
+ wp_send_json_error(['message' => 'Candidate is not in training status']);
IM_Mailer::send_hire($id)
- ? wp_send_json_success(['message' => '鍩硅閾炬帴閭欢宸查噸鏂板彂閫侊紒'])
- : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ャ��']);
+ ? wp_send_json_success(['message' => 'Training link email resent!'])
+ : wp_send_json_error(['message' => 'Email sending failed.']);
});
add_action('wp_ajax_im_action_resend_trained_email', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
$c = IM_Candidate::get($id);
if (!$c || $c->status !== 'trained')
- wp_send_json_error(['message' => '璇ュ�欓�変汉涓嶅湪宸插畬鎴愬煿璁姸鎬�']);
+ wp_send_json_error(['message' => 'Candidate is not in training-completed status']);
IM_Mailer::send_training_complete($id)
- ? wp_send_json_success(['message' => '璐﹀彿閭欢宸查噸鏂板彂閫侊紒'])
- : wp_send_json_error(['message' => '閭欢鍙戦�佸け璐ャ��']);
+ ? wp_send_json_success(['message' => 'Account email resent!'])
+ : wp_send_json_error(['message' => 'Email sending failed.']);
});
add_action('wp_ajax_im_action_delete', function () {
check_ajax_referer('im_admin_nonce', 'nonce');
if (!current_user_can('edit_others_posts'))
- wp_send_json_error(['message' => '鏉冮檺涓嶈冻']);
+ wp_send_json_error(['message' => 'Insufficient permissions']);
$id = (int) ($_POST['candidate_id'] ?? 0);
if (!$id)
- wp_send_json_error(['message' => '鍙傛暟閿欒']);
+ wp_send_json_error(['message' => 'Invalid parameters']);
IM_Candidate::delete_with_files($id)
- ? wp_send_json_success(['message' => '鍊欓�変汉璁板綍鍙婂叧鑱旀枃浠跺凡鍒犻櫎锛�'])
- : wp_send_json_error(['message' => '鍒犻櫎澶辫触锛岃閲嶈瘯銆�']);
+ ? wp_send_json_success(['message' => 'Candidate record and related files deleted!'])
+ : wp_send_json_error(['message' => 'Delete failed, please try again.']);
});
/* ============================================================
@@ -819,26 +819,26 @@
'trained' => IM_Candidate::count(['status' => 'trained']),
];
$tabs = [
- '' => '鍏ㄩ儴',
- 'applied' => '宸茬敵璇�',
- 'screening' => '寰呯瓫閫�',
- 'invited' => '宸查個璇�',
- 'rejected' => '宸叉嫆缁�',
- 'completed' => '宸插畬鎴�',
- 'hired' => '宸插綍鍙�',
- 'training' => '鏈畬鎴愬煿璁�',
- 'trained' => '宸插畬鎴愬煿璁�'
+ '' => 'All',
+ 'applied' => 'Applied',
+ 'screening' => 'Pending Review',
+ 'invited' => 'Invited',
+ 'rejected' => 'Rejected',
+ 'completed' => 'Completed',
+ 'hired' => 'Hired',
+ 'training' => 'Training In Progress',
+ 'trained' => 'Training Completed'
];
$status_lbl = [
- 'applied' => '寰呭畬鍠勪俊鎭�',
- 'screening' => '宸叉彁浜よ缁嗕俊鎭�',
- 'invited' => '宸查個璇�',
- 'rejected' => '宸叉嫆缁�',
- 'completed' => '宸插畬鎴�',
- 'hired' => '宸插綍鍙�',
- 'training' => '鏈畬鎴愬煿璁�',
- 'trained' => '宸插畬鎴愬煿璁�'
+ 'applied' => 'Awaiting Detailed Form',
+ 'screening' => 'Detailed Form Submitted',
+ 'invited' => 'Invited',
+ 'rejected' => 'Rejected',
+ 'completed' => 'Completed',
+ 'hired' => 'Hired',
+ 'training' => 'Training In Progress',
+ 'trained' => 'Training Completed'
];
$colors = [
@@ -853,7 +853,7 @@
];
?>
<div class="wrap im-a">
- <h1 class="wp-heading-inline">鍊欓�変汉绠$悊</h1>
+ <h1 class="wp-heading-inline">Candidate Management</h1>
<hr class="wp-header-end">
<ul class="subsubsub">
@@ -870,20 +870,20 @@
<input type="hidden" name="page" value="im-candidates">
<?php if ($status): ?><input type="hidden" name="status" value="<?= esc_attr($status) ?>"><?php endif; ?>
<p class="search-box" style="margin:0">
- <input type="search" name="s" value="<?= esc_attr($search) ?>" placeholder="鎼滅储濮撳悕鎴栭偖绠�..." style="width:240px">
- <button type="submit" class="button">鎼滅储</button>
+ <input type="search" name="s" value="<?= esc_attr($search) ?>" placeholder="Search by name or email..." style="width:240px">
+ <button type="submit" class="button">Search</button>
</p>
</form>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
- <th width="160">濮撳悕</th>
- <th>閭</th>
- <th width="140">鏇存柊鏃堕棿</th>
- <th width="120">鐘舵��</th>
- <th>鎺堣绉戠洰 / 璇︽儏</th>
- <th width="240">鎿嶄綔</th>
+ <th width="160">Name</th>
+ <th>Email</th>
+ <th width="140">Updated</th>
+ <th width="120">Status</th>
+ <th>Subjects / Details</th>
+ <th width="240">Actions</th>
</tr>
</thead>
<tbody>
@@ -908,34 +908,34 @@
<span class="im-badge"
style="background:<?= $color ?>18;color:<?= $color ?>;border-color:<?= $color ?>44"><?= $clabel ?></span>
<?php if ($c->status === 'applied' && !empty($c->apply_opened_at)): ?>
- <div style="font-size:12px;color:#059669;margin-top:4px">鉁� 宸叉墦寮�琛ㄥ崟</div>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� Form opened</div>
<?php elseif ($c->status === 'applied' && empty($c->apply_opened_at)): ?>
- <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� 鏈墦寮�琛ㄥ崟</div>
+ <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� Form not opened</div>
<?php elseif ($c->status === 'invited'): ?>
<?php $tk_list = IM_Token::get_by_candidate($c->id);
if (!empty($tk_list)):
$tk = $tk_list[0]; ?>
<?php if (!empty($tk->opened_at)): ?>
- <div style="font-size:12px;color:#059669;margin-top:4px">鉁� 宸叉煡鐪嬮潰璇曢</div>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� Interview questions viewed</div>
<?php else: ?>
- <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� 鏈偣寮�闈㈣瘯閾炬帴</div>
+ <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� Interview link not opened</div>
<?php endif; ?>
<?php endif; ?>
<?php elseif ($c->status === 'training'): ?>
<?php if (!empty($c->training_opened_at)): ?>
- <div style="font-size:12px;color:#059669;margin-top:4px">鉁� 宸叉墦寮�鍩硅椤甸潰</div>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� Training page opened</div>
<?php else: ?>
- <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� 鏈墦寮�鍩硅椤甸潰</div>
+ <div style="font-size:12px;color:#9ca3af;margin-top:4px">鈼� Training page not opened</div>
<?php endif; ?>
<?php elseif ($c->status === 'trained'): ?>
<?php if (!empty($c->training_completed_at)): ?>
- <div style="font-size:12px;color:#059669;margin-top:4px">鉁� <?= date('m/d H:i', strtotime($c->training_completed_at)) ?> 瀹屾垚</div>
+ <div style="font-size:12px;color:#059669;margin-top:4px">鉁� <?= date('m/d H:i', strtotime($c->training_completed_at)) ?> Completed</div>
<?php endif; ?>
<?php endif; ?>
</td>
<td>
<?php if ($c->status === 'applied'): ?>
- <span class="im-muted">绛夊緟瀹屽杽淇℃伅</span>
+ <span class="im-muted">Waiting for detailed form</span>
<?php elseif ($subs): ?>
<?php foreach (array_slice($subs, 0, 3) as $s): ?>
<span class="im-tag"><?= esc_html($s) ?></span>
@@ -946,49 +946,49 @@
</td>
<td>
<?php if ($c->status !== 'applied'): ?>
- <a href="<?= esc_url($detail) ?>" class="button button-small">鏌ョ湅璇︽儏</a>
+ <a href="<?= esc_url($detail) ?>" class="button button-small">View Details</a>
<?php endif; ?>
<?php if ($c->status === 'applied'): ?>
<button class="button button-small im-action-btn" data-action="resend_joinus"
data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->last_name . ' ' . $c->first_name) ?>">閲嶆柊鍙戦�侀偖浠�</button>
+ data-name="<?= esc_attr($c->last_name . ' ' . $c->first_name) ?>">Resend Email</button>
<?php elseif ($c->status === 'screening'): ?>
<button class="button button-small button-primary im-action-btn" data-action="invite"
data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鍙戦�侀潰璇曢個璇�</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Send Interview Invitation</button>
<button class="button button-small im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鎷掔粷</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Reject</button>
<?php elseif ($c->status === 'invited'): ?>
<button class="button button-small im-action-btn" data-action="invite" data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">閲嶆柊鍙戦�侀個璇�</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Resend Invitation</button>
<button class="button button-small im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鎷掔粷</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Reject</button>
<?php elseif ($c->status === 'completed'): ?>
<button class="button button-small im-action-btn" style="border-color:#10b981;color:#10b981"
data-action="hire" data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">褰曞彇</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Hire</button>
<button class="button button-small im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">鎷掔粷</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Reject</button>
<?php elseif ($c->status === 'training'): ?>
<button class="button button-small im-action-btn" data-action="resend_training"
data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">閲嶆柊鍙戦�佸煿璁摼鎺�</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Resend Training Link</button>
<?php elseif ($c->status === 'trained'): ?>
<button class="button button-small im-action-btn" data-action="resend_trained_email"
data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">閲嶆柊鍙戦�佽处鍙烽偖浠�</button>
+ data-name="<?= esc_attr($c->preferred_name ?: $c->first_name) ?>">Resend Account Email</button>
<?php endif; ?>
<button class="button button-small im-action-btn" style="color:#ef4444;border-color:#fca5a5"
data-action="delete" data-id="<?= $c->id ?>"
- data-name="<?= esc_attr($c->last_name . ' ' . $c->first_name) ?>">鍒犻櫎</button>
+ data-name="<?= esc_attr($c->last_name . ' ' . $c->first_name) ?>">Delete</button>
<?php if (defined('IM_DEBUG_LINKS') && IM_DEBUG_LINKS): ?>
<div
style="margin-top:12px;padding-top:8px;border-top:1px dashed #e5e7eb;font-size:12px;word-break:break-all;line-height:1.5">
<?php if (empty($c->apply_token_used) && $c->apply_token): ?>
- <div style="color:#6b7280;margin-bottom:4px">璋冭瘯: 琛ㄥ崟閾炬帴 <br><a
+ <div style="color:#6b7280;margin-bottom:4px">Debug: Form Link <br><a
href="<?= esc_url(add_query_arg('im_apply_token', $c->apply_token, IM_APPLY_PAGE_URL)) ?>"
target="_blank"
style="color:#8b5cf6"><?= esc_html(add_query_arg('im_apply_token', $c->apply_token, IM_APPLY_PAGE_URL)) ?></a>
@@ -998,13 +998,13 @@
$latest_token = IM_Token::get_by_candidate($c->id);
if (!empty($latest_token)):
$tk2 = $latest_token[0]; ?>
- <div style="color:#6b7280">璋冭瘯: 闈㈣瘯閾炬帴 <br><a
+ <div style="color:#6b7280">Debug: Interview Link <br><a
href="<?= esc_url(add_query_arg('im_token', $tk2->token, IM_INTERVIEW_PAGE_URL)) ?>" target="_blank"
style="color:#3b82f6"><?= esc_html(add_query_arg('im_token', $tk2->token, IM_INTERVIEW_PAGE_URL)) ?></a>
</div>
<?php endif; endif; ?>
<?php if (in_array($c->status, ['training', 'trained']) && !empty($c->training_token)): ?>
- <div style="color:#6b7280">璋冭瘯: 鍩硅閾炬帴 <br><a
+ <div style="color:#6b7280">Debug: Training Link <br><a
href="<?= esc_url(add_query_arg('im_training_token', $c->training_token, IM_TRAINING_PAGE_URL)) ?>" target="_blank"
style="color:#f97316"><?= esc_html(add_query_arg('im_training_token', $c->training_token, IM_TRAINING_PAGE_URL)) ?></a>
</div>
@@ -1015,7 +1015,7 @@
</tr>
<?php endforeach; else: ?>
<tr>
- <td colspan="6" class="im-empty">鏆傛棤鍊欓�変汉鏁版嵁</td>
+ <td colspan="6" class="im-empty">No candidate data available</td>
</tr>
<?php endif; ?>
</tbody>
@@ -1046,11 +1046,11 @@
$id = (int) ($_GET['id'] ?? 0);
$c = $id ? IM_Candidate::get($id) : null;
if (!$c) {
- echo '<div class="wrap"><div class="notice notice-error"><p>鍊欓�変汉涓嶅瓨鍦ㄣ��</p></div></div>';
+ echo '<div class="wrap"><div class="notice notice-error"><p>Candidate does not exist.</p></div></div>';
return;
}
if ($c->status === 'applied') {
- echo '<div class="wrap"><div class="notice notice-warning"><p>璇ュ�欓�変汉灏氭湭鎻愪氦璇︾粏鐢宠琛紝鏃犳硶鏌ョ湅璇︽儏銆�</p></div><a href="' . admin_url('admin.php?page=im-candidates') . '" class="button">杩斿洖鍒楄〃</a></div>';
+ echo '<div class="wrap"><div class="notice notice-warning"><p>This candidate has not submitted the detailed application form yet, so details are unavailable.</p></div><a href="' . admin_url('admin.php?page=im-candidates') . '" class="button">Back to List</a></div>';
return;
}
@@ -1059,14 +1059,14 @@
$attachments = IM_Attachment::get_by_candidate($id);
$upload_dir = wp_upload_dir();
$status_lbl = [
- 'applied' => '寰呭畬鍠勪俊鎭�',
- 'screening' => '宸叉彁浜よ缁嗕俊鎭�',
- 'invited' => '宸查個璇�',
- 'rejected' => '宸叉嫆缁�',
- 'completed' => '宸插畬鎴�',
- 'hired' => '宸插綍鍙�',
- 'training' => '鏈畬鎴愬煿璁�',
- 'trained' => '宸插畬鎴愬煿璁�'
+ 'applied' => 'Awaiting Detailed Form',
+ 'screening' => 'Detailed Form Submitted',
+ 'invited' => 'Invited',
+ 'rejected' => 'Rejected',
+ 'completed' => 'Completed',
+ 'hired' => 'Hired',
+ 'training' => 'Training In Progress',
+ 'trained' => 'Training Completed'
];
$status_colors = [
'applied' => ['bg' => '#fffbeb', 'text' => '#b45309', 'border' => '#fde68a'],
@@ -1092,7 +1092,7 @@
stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5m7 7l-7-7 7-7" />
</svg>
- 杩斿洖鍊欓�変汉鍒楄〃
+ Back to Candidate List
</a>
<!-- Hero Card -->
@@ -1153,16 +1153,16 @@
<path d="M22 10v6M2 10l10-5 10 5-10 5z" />
<path d="M6 12v5c3 3 9 3 12 0v-5" />
</svg>
- 鏁欒偛鑳屾櫙
+ Education
</h2>
<div class="im-timeline">
<div class="im-timeline-item">
<div class="im-timeline-icon"></div>
- <?php $degree_map = ["Bachelor's" => '鏈', "Master's" => '纭曞+', 'PhD' => '鍗氬+']; ?>
- <div class="im-timeline-date"><?= esc_html($c->grad_year ?: '鏈煡骞翠唤') ?> 姣曚笟
- (<?= esc_html($degree_map[$c->degree_level] ?? ($c->degree_level ?: '鏈�楂樺鍘�')) ?>)</div>
+ <?php $degree_map = ["Bachelor's" => 'Bachelor', "Master's" => 'Master', 'PhD' => 'PhD']; ?>
+ <div class="im-timeline-date"><?= esc_html($c->grad_year ?: 'Unknown year') ?> Graduated
+ (<?= esc_html($degree_map[$c->degree_level] ?? ($c->degree_level ?: 'Highest Degree')) ?>)</div>
<h3 class="im-timeline-title"><?= esc_html($c->university ?: '鈥�') ?></h3>
- <div class="im-timeline-sub">涓撲笟锛�<?= esc_html($c->major ?: '鈥�') ?> |
+ <div class="im-timeline-sub">Major: <?= esc_html($c->major ?: '鈥�') ?> |
GPA锛�<?= esc_html($c->gpa ?: '鈥�') ?>
<?= $c->deans_list ? ' | 馃弲 Dean\'s List' : '' ?>
</div>
@@ -1170,19 +1170,19 @@
<?php if ($c->ug_university): ?>
<div class="im-timeline-item">
<div class="im-timeline-icon" style="border-color:#d1d5db"></div>
- <div class="im-timeline-date" style="color:#6b7280"><?= esc_html($c->ug_grad_year ?: '鏈煡骞翠唤') ?>
- 姣曚笟 (鏈瀛﹀巻)</div>
+ <div class="im-timeline-date" style="color:#6b7280"><?= esc_html($c->ug_grad_year ?: 'Unknown year') ?>
+ Graduated (Bachelor Degree)</div>
<h3 class="im-timeline-title"><?= esc_html($c->ug_university) ?></h3>
- <div class="im-timeline-sub">涓撲笟锛�<?= esc_html($c->ug_major ?: '鈥�') ?></div>
+ <div class="im-timeline-sub">Major: <?= esc_html($c->ug_major ?: '鈥�') ?></div>
</div>
<?php endif; ?>
<?php if (!empty($c->ms_university)): ?>
<div class="im-timeline-item">
<div class="im-timeline-icon" style="border-color:#8b5cf6"></div>
- <div class="im-timeline-date" style="color:#8b5cf6"><?= esc_html($c->ms_grad_year ?: '鏈煡骞翠唤') ?>
- 姣曚笟 (纭曞+瀛﹀巻)</div>
+ <div class="im-timeline-date" style="color:#8b5cf6"><?= esc_html($c->ms_grad_year ?: 'Unknown year') ?>
+ Graduated (Master Degree)</div>
<h3 class="im-timeline-title"><?= esc_html($c->ms_university) ?></h3>
- <div class="im-timeline-sub">涓撲笟锛�<?= esc_html($c->ms_major ?: '鈥�') ?></div>
+ <div class="im-timeline-sub">Major: <?= esc_html($c->ms_major ?: '鈥�') ?></div>
</div>
<?php endif; ?>
<?php if ($c->ca_highschool): ?>
@@ -1203,34 +1203,34 @@
<path
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
- 鎶�鑳戒笌缁忛獙
+ Skills & Experience
</h2>
<div class="im-info-grid">
<div class="im-info-item full-width">
- <div class="im-info-label">鎺堣绉戠洰 (<?= count($subjects) ?>涓�)</div>
+ <div class="im-info-label">Subjects (<?= count($subjects) ?>)</div>
<div class="im-info-val">
<?php if ($subjects):
foreach ($subjects as $s): ?>
<span class="im-tag"><?= esc_html($s) ?></span>
<?php endforeach; else: ?>
- <span class="im-muted">鏈�夋嫨浠讳綍绉戠洰</span>
+ <span class="im-muted">No subjects selected</span>
<?php endif; ?>
</div>
</div>
<div class="im-info-item full-width">
- <div class="im-info-label">鑻辫娴佸埄搴�</div>
+ <div class="im-info-label">English Fluency</div>
<div class="im-info-val"><?php
- $fluency_map = ['Native' => 'Native (姣嶈)', 'Fluent' => 'Fluent (娴佸埄)', 'Basic' => 'Basic (鍩虹)'];
+ $fluency_map = ['Native' => 'Native', 'Fluent' => 'Fluent', 'Basic' => 'Basic'];
echo esc_html($fluency_map[$c->languages] ?? ($c->languages ?: '鈥�'));
?></div>
</div>
<div class="im-info-item full-width">
- <div class="im-info-label">鏁欏缁忛獙</div>
+ <div class="im-info-label">Teaching Experience</div>
<div class="im-info-val"><?= nl2br(esc_html($c->teaching_exp ?: '鈥�')) ?></div>
</div>
<?php if ($c->has_achievement): ?>
<div class="im-info-item full-width">
- <div class="im-info-label">涓汉鎴愬氨 (<?= esc_html($c->achievement_type) ?>)</div>
+ <div class="im-info-label">Personal Achievement (<?= esc_html($c->achievement_type) ?>)</div>
<div class="im-info-val"
style="background:#f9fafb;padding:12px 16px;border-radius:8px;border:1px solid #f3f4f6">
<?= nl2br(esc_html($c->achievement_desc ?: '鈥�')) ?>
@@ -1239,7 +1239,7 @@
<?php endif; ?>
<?php if ($c->extra_notes): ?>
<div class="im-info-item full-width">
- <div class="im-info-label">琛ュ厖璇存槑</div>
+ <div class="im-info-label">Additional Notes</div>
<div class="im-info-val"
style="background:#f9fafb;padding:12px 16px;border-radius:8px;border:1px solid #f3f4f6">
<?= nl2br(esc_html($c->extra_notes)) ?>
@@ -1255,69 +1255,69 @@
<!-- 鐘舵�佹搷浣滃尯 -->
<?php if ($c->status === 'screening'): ?>
<div class="im-action-box">
- <h3>瀹℃牳鎿嶄綔</h3>
- <p>璇峰喅瀹氭槸鍚﹂個璇疯鍊欓�変汉杩涜闈㈣瘯</p>
+ <h3>Review Actions</h3>
+ <p>Decide whether to invite this candidate for an interview</p>
<button class="im-btn-primary im-action-btn" data-action="invite" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>">
- 鍙戦�侀潰璇曢個璇�
+ Send Interview Invitation
</button>
<button class="im-btn-danger im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>">
- 鎷掔粷璇ュ�欓�変汉
+ Reject This Candidate
</button>
<div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
</div>
<?php elseif ($c->status === 'invited'): ?>
<div class="im-action-box">
- <h3>绛夊緟闈㈣瘯涓�</h3>
- <p>濡傛灉鍊欓�変汉鏈敹鍒伴偖浠跺彲閲嶆柊鍙戦��</p>
+ <h3>Awaiting Interview</h3>
+ <p>If the candidate did not receive the email, you can resend it</p>
<button class="im-btn-primary im-action-btn" data-action="invite" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>">
- 閲嶆柊鍙戦�侀個璇�
+ Resend Invitation
</button>
<div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
</div>
<?php elseif ($c->status === 'completed'): ?>
<div class="im-action-box" style="background:linear-gradient(135deg,#ecfdf5,#d1fae5);border-color:#a7f3d0">
- <h3 style="color:#047857">闈㈣瘯瀹℃煡</h3>
- <p style="color:#059669">瑙嗛宸叉彁浜わ紝璇锋煡鐪嬪苟鍦ㄩ�氳繃鍚庡綍鐢�</p>
+ <h3 style="color:#047857">Interview Review</h3>
+ <p style="color:#059669">Video has been submitted. Review it and hire if approved.</p>
<div style="display:flex;gap:12px;flex-wrap:wrap">
<button class="im-btn-success im-action-btn" data-action="hire" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>" style="flex:1">
- 姝e紡褰曞彇鍊欓�変汉
+ Formally Hire Candidate
</button>
<button class="im-btn-danger im-action-btn" data-action="reject" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>" style="flex:1">
- 鎷掔粷璇ュ�欓�変汉
+ Reject This Candidate
</button>
</div>
<div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
</div>
<?php elseif ($c->status === 'training'): ?>
<div class="im-action-box" style="background:linear-gradient(135deg,#fff7ed,#ffedd5);border-color:#fed7aa">
- <h3 style="color:#c2410c">鍩硅杩涜涓�</h3>
- <p style="color:#ea580c">鍊欓�変汉姝e湪瀹屾垚鍩硅璇剧▼</p>
+ <h3 style="color:#c2410c">Training In Progress</h3>
+ <p style="color:#ea580c">Candidate is completing training modules</p>
<?php if (!empty($c->training_opened_at)): ?>
- <div style="font-size:13px;color:#059669;margin-bottom:12px;font-weight:600">鉁� 宸蹭簬 <?= date('Y/m/d H:i', strtotime($c->training_opened_at)) ?> 鎵撳紑鍩硅椤甸潰</div>
+ <div style="font-size:13px;color:#059669;margin-bottom:12px;font-weight:600">鉁� Opened training page at <?= date('Y/m/d H:i', strtotime($c->training_opened_at)) ?></div>
<?php else: ?>
- <div style="font-size:13px;color:#9ca3af;margin-bottom:12px">鈼� 灏氭湭鎵撳紑鍩硅椤甸潰</div>
+ <div style="font-size:13px;color:#9ca3af;margin-bottom:12px">鈼� Training page not opened yet</div>
<?php endif; ?>
<button class="im-btn-primary im-action-btn" data-action="resend_training" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>">
- 閲嶆柊鍙戦�佸煿璁摼鎺�
+ Resend Training Link
</button>
<div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
</div>
<?php elseif ($c->status === 'trained'): ?>
<div class="im-action-box" style="background:linear-gradient(135deg,#ecfeff,#cffafe);border-color:#a5f3fc">
- <h3 style="color:#0e7490">鍩硅宸插畬鎴�</h3>
- <p style="color:#0891b2">鍊欓�変汉宸查�氳繃鎵�鏈夊煿璁ā鍧�</p>
+ <h3 style="color:#0e7490">Training Completed</h3>
+ <p style="color:#0891b2">Candidate has passed all training modules</p>
<?php if (!empty($c->training_completed_at)): ?>
- <div style="font-size:13px;color:#059669;font-weight:600;margin-bottom:12px">鉁� 浜� <?= date('Y/m/d H:i', strtotime($c->training_completed_at)) ?> 瀹屾垚鍩硅</div>
+ <div style="font-size:13px;color:#059669;font-weight:600;margin-bottom:12px">鉁� Completed training at <?= date('Y/m/d H:i', strtotime($c->training_completed_at)) ?></div>
<?php endif; ?>
<button class="im-btn-primary im-action-btn" data-action="resend_trained_email" data-id="<?= $c->id ?>"
data-name="<?= esc_attr($full_name) ?>">
- 閲嶆柊鍙戦�佽处鍙烽偖浠�
+ Resend Account Email
</button>
<div id="im-res-<?= $c->id ?>" style="margin-top:12px;font-size:14px;font-weight:600"></div>
</div>
@@ -1331,13 +1331,13 @@
<path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z" />
<path d="M13 2v7h7" />
</svg>
- 闄勪欢鏉愭枡
+ Attachments
</h2>
<?php
$grouped = [];
foreach ($attachments as $att)
$grouped[$att->file_type][] = $att;
- $att_groups = ['transcript_files' => '鎴愮哗鍗�', 'achievement_files' => '鎴愬氨璇佹槑', 'extra_files' => '琛ュ厖鏉愭枡'];
+ $att_groups = ['transcript_files' => 'Transcripts', 'achievement_files' => 'Achievement Proofs', 'extra_files' => 'Additional Materials'];
foreach ($att_groups as $type => $label):
if (empty($grouped[$type]))
continue;
@@ -1362,14 +1362,14 @@
</div>
</div>
<div class="im-doc-actions">
- <a href="<?= esc_url($att_url) ?>" target="_blank" class="im-doc-btn" title="鍦ㄦ柊鏍囩椤甸瑙�">
+ <a href="<?= esc_url($att_url) ?>" target="_blank" class="im-doc-btn" title="Preview in new tab">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</svg>
</a>
- <a href="<?= esc_url($att_url) ?>" download class="im-doc-btn" title="涓嬭浇鏂囦欢">
+ <a href="<?= esc_url($att_url) ?>" download class="im-doc-btn" title="Download file">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
@@ -1391,7 +1391,7 @@
<path d="M23 7l-7 5 7 5V7z" />
<rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
</svg>
- 闈㈣瘯璁板綍
+ Interview Records
</h2>
<?php if ($tokens):
foreach ($tokens as $tk):
@@ -1401,17 +1401,17 @@
$bs = '#dcfce7';
$bc = '#86efac';
$bt = '#166534';
- $bl = '宸叉彁浜よ棰�';
+ $bl = 'Video Submitted';
} elseif ($expired) {
$bs = '#fee2e2';
$bc = '#fca5a5';
$bt = '#991b1b';
- $bl = '閾炬帴宸茶繃鏈�';
+ $bl = 'Link Expired';
} else {
$bs = '#dbeafe';
$bc = '#93c5fd';
$bt = '#1e40af';
- $bl = '鏈夋晥涓�';
+ $bl = 'Active';
}
?>
<div class="im-token-box">
@@ -1419,17 +1419,17 @@
<span class="im-badge"
style="background:<?= $bs ?>;color:<?= $bt ?>;border-color:<?= $bc ?>"><?= $bl ?></span>
</div>
- <div class="im-token-date"><?= date('Y/m/d H:i', strtotime($tk->created_at)) ?> 鍙戦��</div>
- <div class="im-token-exp" style="margin-bottom:8px">鑷�
- <?= date('m/d H:i', strtotime($tk->expires_at)) ?> 澶辨晥
+ <div class="im-token-date"><?= date('Y/m/d H:i', strtotime($tk->created_at)) ?> Sent</div>
+ <div class="im-token-exp" style="margin-bottom:8px">Valid until
+ <?= date('m/d H:i', strtotime($tk->expires_at)) ?>
</div>
<?php if (!empty($tk->opened_at)): ?>
<div class="im-token-exp" style="color:#059669;font-weight:600">
- 鉁� 浜� <?= date('Y/m/d H:i', strtotime($tk->opened_at)) ?> 鏌ョ湅棰樼洰 <?= $used ? '' : '(鍊掕鏃惰繘琛屼腑)' ?>
+ 鉁� Viewed questions at <?= date('Y/m/d H:i', strtotime($tk->opened_at)) ?> <?= $used ? '' : '(countdown active)' ?>
</div>
<?php else: ?>
- <div class="im-token-exp" style="color:#f59e0b">鈼� 鍊欓�変汉鏈墦寮�閾炬帴</div>
+ <div class="im-token-exp" style="color:#f59e0b">鈼� Candidate has not opened the link</div>
<?php endif; ?>
<?php if ($used && $tk->video_path && file_exists($tk->video_path)):
@@ -1446,35 +1446,35 @@
</div>
<div style="flex:1;min-width:0">
<div style="font-weight:600;color:#1e293b;font-size:14px;word-break:break-all"><?= esc_html($tk->video_filename) ?></div>
- <div style="color:#64748b;font-size:13px;margin-top:2px"><?= strtoupper($fext) ?> 鍘嬬缉鍖� 路 <?= $fsize_str ?></div>
+ <div style="color:#64748b;font-size:13px;margin-top:2px"><?= strtoupper($fext) ?> archive 路 <?= $fsize_str ?></div>
</div>
<a href="<?= esc_url($vurl) ?>" download style="display:inline-flex;align-items:center;gap:6px;background:#6366f1;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;text-decoration:none;white-space:nowrap">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>涓嬭浇
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>Download
</a>
</div>
- <div style="margin-top:6px;font-size:12px;color:#94a3b8"><?= date('m/d H:i', strtotime($tk->submitted_at)) ?> 鎻愪氦</div>
+ <div style="margin-top:6px;font-size:12px;color:#94a3b8"><?= date('m/d H:i', strtotime($tk->submitted_at)) ?> submitted</div>
<?php else: ?>
<video controls class="im-video-player" preload="metadata">
<source src="<?= esc_url($vurl) ?>">
</video>
<div class="im-video-footer">
- <span><?= date('m/d H:i', strtotime($tk->submitted_at)) ?> 鎻愪氦</span>
+ <span><?= date('m/d H:i', strtotime($tk->submitted_at)) ?> submitted</span>
<a href="<?= esc_url($vurl) ?>" download class="im-video-dl">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
- </svg>涓嬭浇
+ </svg>Download
</a>
</div>
<?php endif; ?>
<?php elseif ($used): ?>
- <p class="im-muted" style="margin:0;font-size:13px">瑙嗛宸茶劚鏈烘垨涓嶅瓨鍦�</p>
+ <p class="im-muted" style="margin:0;font-size:13px">Video is offline or missing</p>
<?php endif; ?>
</div>
<?php endforeach; else: ?>
<div class="im-empty"
style="padding:24px!important;background:#f9fafb;border-radius:12px;border:1px dashed #e5e7eb">
- 灏氭湭鍙戦�侀潰璇曢個璇�</div>
+ Interview invitation has not been sent yet</div>
<?php endif; ?>
</div>
<?php endif; ?>
@@ -1494,12 +1494,12 @@
data-nonce="<?php echo esc_attr(wp_create_nonce('im_admin_nonce')); ?>"
data-ajaxurl="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
<div class="im-modal">
- <h3>鎿嶄綔纭</h3>
+ <h3>Action Confirmation</h3>
<p id="im-modal-body"></p>
- <textarea id="im-modal-textarea" style="display:none" placeholder="杈撳叆鍘熷洜..."></textarea>
+ <textarea id="im-modal-textarea" style="display:none" placeholder="Enter reason..."></textarea>
<div class="im-modal-btns">
- <button class="button im-modal-cancel">鍙栨秷</button>
- <button class="button button-primary" id="im-modal-ok">纭</button>
+ <button class="button im-modal-cancel">Cancel</button>
+ <button class="button button-primary" id="im-modal-ok">Confirm</button>
</div>
</div>
</div>
@@ -1515,83 +1515,83 @@
$home = rtrim(home_url('/'), '/');
$checks = [
- ['upload_max_filesize', ini_get('upload_max_filesize'), wp_convert_hr_to_bytes(ini_get('upload_max_filesize')) >= 400 * 1024 * 1024, '寤鸿璁句负 500M'],
- ['post_max_size', ini_get('post_max_size'), wp_convert_hr_to_bytes(ini_get('post_max_size')) >= 400 * 1024 * 1024, '寤鸿璁句负 512M'],
- ['max_execution_time', ini_get('max_execution_time') . 's', (int) ini_get('max_execution_time') === 0 || (int) ini_get('max_execution_time') >= 180, '寤鸿璁句负 300'],
+ ['upload_max_filesize', ini_get('upload_max_filesize'), wp_convert_hr_to_bytes(ini_get('upload_max_filesize')) >= 400 * 1024 * 1024, 'Suggested: 500M'],
+ ['post_max_size', ini_get('post_max_size'), wp_convert_hr_to_bytes(ini_get('post_max_size')) >= 400 * 1024 * 1024, 'Suggested: 512M'],
+ ['max_execution_time', ini_get('max_execution_time') . 's', (int) ini_get('max_execution_time') === 0 || (int) ini_get('max_execution_time') >= 180, 'Suggested: 300'],
['memory_limit', ini_get('memory_limit'), true, '鈥�'],
];
?>
<div class="wrap" style="max-width:860px">
- <h1>Interview Manager 鈥� 鏈嶅姟鍣ㄩ厤缃悜瀵�</h1>
+ <h1>Interview Manager 鈥� Server Setup Wizard</h1>
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
- <h2 style="margin-top:0">鈶� Nginx 閰嶇疆锛堝繀椤绘墜鍔ㄥ畬鎴愶級</h2>
- <p>鍦� <code>server { }</code> 鍧楀唴娣诲姞浠ヤ笅閰嶇疆锛岀劧鍚庢墽琛� <code>sudo nginx -t && sudo nginx -s reload</code>锛�</p>
+ <h2 style="margin-top:0">1) Nginx Configuration (manual required)</h2>
+ <p>Add the following config inside the <code>server { }</code> block, then run <code>sudo nginx -t && sudo nginx -s reload</code>:</p>
<pre
- style="background:#1e293b;color:#e2e8f0;padding:20px;border-radius:8px;font-size:13px;line-height:1.7;overflow-x:auto"># 瑙嗛涓婁紶闄愬埗
+ style="background:#1e293b;color:#e2e8f0;padding:20px;border-radius:8px;font-size:13px;line-height:1.7;overflow-x:auto"># Video upload limits
client_max_body_size 512M;
client_body_timeout 300s;
send_timeout 300s;
- # WordPress 鍥哄畾閾炬帴锛堝凡鏈夊垯璺宠繃锛�
+ # WordPress permalinks (skip if already set)
location / {
try_files $uri $uri/ /index.php?$args;
}
- # 涓婁紶鐩綍瀹夊叏
+ # Upload directory security
location ~* ^/wp-content/uploads/(interviews|im-applications)/ {
add_header X-Content-Type-Options nosniff;
}</pre>
</div>
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
- <h2 style="margin-top:0">鈶� PHP 閰嶇疆锛坧hp.ini 鎴� .user.ini锛�</h2>
+ <h2 style="margin-top:0">2) PHP Configuration (php.ini or .user.ini)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:20px;border-radius:8px;font-size:13px;line-height:1.7">upload_max_filesize = 500M
post_max_size = 512M
max_execution_time = 300
max_input_time = 300
memory_limit = 256M</pre>
- <p>淇敼鍚庨噸鍚� PHP-FPM锛�<code>sudo systemctl restart php8.x-fpm</code></p>
+ <p>Restart PHP-FPM after changes: <code>sudo systemctl restart php8.x-fpm</code></p>
</div>
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
- <h2 style="margin-top:0">鈶� 鍒锋柊 WordPress 鍥哄畾閾炬帴</h2>
- <p>杩涘叆 <strong>鍚庡彴 鈫� 璁剧疆 鈫� 鍥哄畾閾炬帴</strong>锛岀洿鎺ョ偣鍑汇�屼繚瀛樻洿鏀广�嶅嵆鍙紙鏃犻渶鏀逛换浣曡缃級銆�</p>
- <p><a href="<?= admin_url('options-permalink.php') ?>" class="button button-primary" target="_blank">鍓嶅線鍥哄畾閾炬帴璁剧疆
+ <h2 style="margin-top:0">3) Refresh WordPress Permalinks</h2>
+ <p>Go to <strong>Dashboard 鈫� Settings 鈫� Permalinks</strong>, then click "Save Changes" directly (no settings change needed).</p>
+ <p><a href="<?= admin_url('options-permalink.php') ?>" class="button button-primary" target="_blank">Go to Permalink Settings
鈫�</a></p>
</div>
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;margin-bottom:24px">
- <h2 style="margin-top:0">鈶� WordPress 椤甸潰閰嶇疆</h2>
- <p>闇�鍦� WordPress 鍚庡彴鍒涘缓浠ヤ笅涓や釜椤甸潰锛�</p>
+ <h2 style="margin-top:0">4) WordPress Page Setup</h2>
+ <p>Create the following pages in WordPress admin:</p>
<table class="widefat" style="max-width:600px">
<tr>
- <th>椤甸潰</th>
+ <th>Page</th>
<th>Shortcode</th>
- <th>寤鸿 URL slug</th>
+ <th>Suggested URL slug</th>
</tr>
<tr>
- <td>Join Us 鎶ュ悕椤�</td>
+ <td>Join Us Registration Page</td>
<td><code>[im_joinus_form]</code></td>
<td><code>/join-us/</code></td>
</tr>
<tr>
- <td>璇︾粏鐢宠琛ㄥ崟椤�</td>
+ <td>Detailed Application Form Page</td>
<td><code>[im_apply_form]</code></td>
<td><code>/apply/</code></td>
</tr>
<tr>
- <td>闈㈣瘯椤�</td>
+ <td>Interview Page</td>
<td><code>[im_interview]</code></td>
<td><code>/interview/</code></td>
</tr>
<tr>
- <td>鍩硅椤�</td>
+ <td>Training Page</td>
<td><code>[im_training]</code></td>
<td><code>/training/</code></td>
</tr>
</table>
- <p style="margin-top:12px">鍒涘缓濂介〉闈㈠悗锛屽湪鐗囨 1 椤堕儴灏嗗搴斿父閲忔敼涓哄疄闄� URL锛�</p>
+ <p style="margin-top:12px">After creating pages, update the constants at the top of snippet 1 with actual URLs:</p>
<code>define('IM_APPLY_PAGE_URL', home_url('/apply/'));</code><br>
<code>define('IM_INTERVIEW_PAGE_URL', home_url('/interview/'));</code><br>
<code>define('IM_TRAINING_PAGE_URL', home_url('/training/'));</code><br>
@@ -1600,13 +1600,13 @@
</div>
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px">
- <h2 style="margin-top:0">鈶� 褰撳墠鐜妫�娴�</h2>
+ <h2 style="margin-top:0">5) Current Environment Check</h2>
<table class="widefat" style="max-width:640px">
<thead>
<tr>
- <th>閰嶇疆椤�</th>
- <th>褰撳墠鍊�</th>
- <th>鐘舵��</th>
+ <th>Configuration</th>
+ <th>Current Value</th>
+ <th>Status</th>
</tr>
</thead>
<tbody>
@@ -1618,33 +1618,33 @@
</tr>
<?php endforeach; ?>
<tr>
- <td>瑙嗛瀛樺偍鐩綍</td>
+ <td>Video Storage Directory</td>
<td style="font-size:12px"><?= esc_html($upload_dir['basedir'] . '/interviews/') ?></td>
- <td><?= is_dir($upload_dir['basedir'] . '/interviews') ? (is_writable($upload_dir['basedir'] . '/interviews') ? '鉁� 鍙啓' : '鉂� 涓嶅彲鍐�') : '锛堥娆′笂浼犳椂鑷姩鍒涘缓锛�' ?>
+ <td><?= is_dir($upload_dir['basedir'] . '/interviews') ? (is_writable($upload_dir['basedir'] . '/interviews') ? '鉁� Writable' : '鉂� Not writable') : '(Auto-created on first upload)' ?>
</td>
</tr>
<tr>
- <td>鐢宠鏂囦欢鐩綍</td>
+ <td>Application Files Directory</td>
<td style="font-size:12px"><?= esc_html($upload_dir['basedir'] . '/im-applications/') ?></td>
- <td><?= is_dir($upload_dir['basedir'] . '/im-applications') ? (is_writable($upload_dir['basedir'] . '/im-applications') ? '鉁� 鍙啓' : '鉂� 涓嶅彲鍐�') : '锛堥娆′笂浼犳椂鑷姩鍒涘缓锛�' ?>
+ <td><?= is_dir($upload_dir['basedir'] . '/im-applications') ? (is_writable($upload_dir['basedir'] . '/im-applications') ? '鉁� Writable' : '鉂� Not writable') : '(Auto-created on first upload)' ?>
</td>
</tr>
<tr>
- <td>闈㈣瘯椤甸潰 URL</td>
+ <td>Interview Page URL</td>
<td><a href="<?= esc_url(IM_INTERVIEW_PAGE_URL) ?>" target="_blank"
style="font-size:12px"><?= esc_html(IM_INTERVIEW_PAGE_URL) ?></a></td>
<td>鈥�</td>
</tr>
<tr>
- <td>鍩硅椤甸潰 URL</td>
+ <td>Training Page URL</td>
<td><a href="<?= esc_url(IM_TRAINING_PAGE_URL) ?>" target="_blank"
style="font-size:12px"><?= esc_html(IM_TRAINING_PAGE_URL) ?></a></td>
<td>鈥�</td>
</tr>
<tr>
- <td>鍩硅涓存椂璐﹀彿</td>
+ <td>Training Temporary Account</td>
<td style="font-size:12px"><?= esc_html(IM_TRAINING_ACCOUNT) ?></td>
- <td><?= IM_TRAINING_ACCOUNT !== 'your_account@example.com' ? '鉁�' : '鈿狅笍 璇烽厤缃疄闄呰处鍙�' ?></td>
+ <td><?= IM_TRAINING_ACCOUNT !== 'your_account@example.com' ? '鉁�' : '鈿狅笍 Configure real account' ?></td>
</tr>
</tbody>
</table>
--
Gitblit v1.9.1