wzp
2026-03-31 0403e244a38f1a49f97d10bbb6b4aa6a2e714618
fix:翻译成英文
1个文件已修改
1个文件已添加
2078 ■■■■ 已修改文件
映星教育/snippet-4-admin copy.php 1654 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
映星教育/snippet-4-admin.php 424 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Ó³ÐǽÌÓý/snippet-4-admin copy.php
New file
@@ -0,0 +1,1654 @@
<?php
/**
 * WPCode ç‰‡æ®µ 4/4 â€” åŽå°ç®¡ç† + Nginx è·¯ç”±æ³¨å†Œ
 * åç§°å»ºè®®ï¼šIM - åŽå°ç®¡ç†
 * ç±»åž‹ï¼šPHP Snippet
 * ä½ç½®ï¼šRun everywhere
 *
 * ä¾èµ–:片段 1、2、3 å¿…须先启用
 *
 * åŒ…含:
 *  - å€™é€‰äººåˆ—表页(搜索 / çŠ¶æ€ç­›é€‰ / åˆ†é¡µï¼‰
 *  - å€™é€‰äººè¯¦æƒ…页(完整表单信息 / é™„件下载 / é¢è¯•记录 / è§†é¢‘播放)
 *  - AJAX å‘送面试邀请
 *  - å·¥å…·èœå•:Nginx é…ç½®å‘导 + çŽ¯å¢ƒæ£€æµ‹
 */
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 = '确认正式录取 <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=”color:#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)。这通常是因为安全插件(如Wordfence/宝塔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 ?: '—') ?> &nbsp;|&nbsp;
                                GPA:<?= esc_html($c->gpa ?: '—') ?>
                                <?= $c->deans_list ? '&nbsp;|&nbsp; ðŸ… 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">
                                æ­£å¼å½•取候选人
                            </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">候选人正在完成培训课程</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 é…ç½®ï¼ˆphp.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
}
Ó³ÐǽÌÓý/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 = '确认正式录取 <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=”color:#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)。这通常是因为安全插件(如Wordfence/宝塔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 ?: '—') ?> &nbsp;|&nbsp;
                            <div class="im-timeline-sub">Major: <?= esc_html($c->major ?: '—') ?> &nbsp;|&nbsp;
                                GPA:<?= esc_html($c->gpa ?: '—') ?>
                                <?= $c->deans_list ? '&nbsp;|&nbsp; ðŸ… 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">
                                æ­£å¼å½•取候选人
                                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">候选人正在完成培训课程</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 é…ç½®ï¼ˆphp.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>