HEX
Server: Apache/2.4.37 (CentOS Stream) OpenSSL/1.1.1k
System: Linux ysnet.com.tw 4.18.0-553.5.1.el8.x86_64 #1 SMP Tue May 21 05:46:01 UTC 2024 x86_64
User: test (521)
PHP: 7.4.33
Disabled: NONE
Upload Files
File: //var/www/test/Installationlist/repair_submit.php
<?php
// ============================================================
// repair_submit.php  ─  電子派工維修單送出處理
// ============================================================
ob_start();
ini_set('display_errors', 0);
error_reporting(E_ALL);
session_start();
header('Content-Type: application/json; charset=utf-8');

register_shutdown_function(function() {
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        while(ob_get_level()) ob_end_clean();
        header('HTTP/1.1 200 OK');
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['success'=>false,'message'=>'PHP Fatal: '.$err['message'].' (line '.$err['line'].')'], JSON_UNESCAPED_UNICODE);
    }
});

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    if (in_array($errno, [E_ERROR, E_WARNING, E_PARSE])) {
        while(ob_get_level()) ob_end_clean();
        http_response_code(200);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['success'=>false,'message'=>"PHP[$errno]: $errstr (line $errline)"], JSON_UNESCAPED_UNICODE);
        exit;
    }
    return false;
});

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    ob_clean(); echo json_encode(['success'=>false,'message'=>'不合法的請求']); exit;
}

// ── 輔助函式(務必放在最前面,任何使用之前先宣告完畢)─────
function fmtD($s) {
    if (!$s) return '—';
    $dt = DateTime::createFromFormat('Y-m-d', $s);
    return $dt ? $dt->format('Y年m月d日') : $s;
}

function imgB64tmp($key) {
    if (!isset($_FILES[$key]) || $_FILES[$key]['error'] !== 0) return '';
    $tmp  = $_FILES[$key]['tmp_name'];
    $mime = mime_content_type($tmp) ?: 'image/png';
    return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($tmp));
}

// $e 是 htmlspecialchars 包裝用的閉包,先宣告好,後面才能安全使用
$e = function($v) { return htmlspecialchars((string)$v); };

// ── POST 資料 ────────────────────────
$userId        = $_POST['user_id']        ?? '';
$enname        = $_POST['enname']         ?? '';
$apname        = $_POST['apname']         ?? '';
$repairContent = $_POST['repair_content'] ?? '';
$repairDate    = $_POST['repair_date']    ?? date('Y-m-d');
$feeMode       = $_POST['fee_mode']       ?? 'free';
$feeTotal      = (int)($_POST['fee_total'] ?? 0);
$feesJson      = $_POST['fees_json']      ?? '[]';
$devicesJson   = $_POST['devices_json']   ?? '[]';
$formDataJson  = $_POST['form_data']      ?? '{}';

$fees    = json_decode($feesJson,    true) ?: [];
$devices = json_decode($devicesJson, true) ?: [];
$G       = json_decode($formDataJson, true) ?: [];

$csigB64 = imgB64tmp('csig');

$isStaticIP = !empty($G['ipaddress']);

// ── 頁首 ─────────────────────────────
$pf  = '<div style="font-size:8.5px;text-align:center;color:#666;padding:1mm 0 1.5mm">公司地址:桃園市桃園區大興西路二段61號16樓 電話:03-358-5867 傳真:03-301-2115 網址:www.ysnet.com.tw 營業時間:10:00~20:00</div>';
// 標題絕對置中;日期放在最右側;LOGO 在左側,三者互不影響排版
$ph2 = '<div style="position:relative;border-bottom:1.5px solid #000;padding-bottom:4px;margin-bottom:6px;min-height:26px">'
     . '<div style="padding-left:3px;line-height:1.4"><strong style="font-size:14px">YSNET</strong><br><span style="font-size:8.5px;color:#555">亞訊股份有限公司</span></div>'
     . '<div style="position:absolute;top:3px;left:0;right:0;text-align:center;font-size:18px;font-weight:bold;pointer-events:none">亞訊寬頻派工維修單</div>'
     . '<div style="position:absolute;top:3px;right:3px;font-size:9.5px;color:#555">日期:'.fmtD($repairDate).'</div>'
     . '</div>';

// ── 使用設備列(只定義一次)─────────────
// 每筆設備資料結構:{action:'新增'|'回收', loc:'住戶端'|'據點', type:設備名稱, sn:料號, basenum:據點編號}
$hasDevices = !empty($devices);
$deviceRows = '';
if ($hasDevices) {
    foreach ($devices as $dv) {
        $action  = htmlspecialchars($dv['action'] ?? '');
        $loc     = htmlspecialchars($dv['loc'] ?? '');
        $type    = htmlspecialchars($dv['type'] ?? '');
        $sn      = htmlspecialchars($dv['sn'] ?? '—');
        $basenum = htmlspecialchars($dv['basenum'] ?? '');
        $locDisplay = $loc === '據點' && $basenum ? $loc.'('.$basenum.')' : $loc;
        $deviceRows .= '<tr><td>'.$action.'</td><td>'.$locDisplay.'</td><td>'.$type.'</td><td>'.$sn.'</td></tr>';
    }
}

// ── 費用列(只定義一次)─────────────
// 無收費:PDF 只顯示「本次無收費」文字,不顯示項目/金額表格
// 有收費:列出各項目;押金獨立列出且不計入總計
$feeRows    = '';
$depositAmt = 0;
$showFeeTable = ($feeMode === 'paid' && !empty($fees));
if ($showFeeTable) {
    foreach ($fees as $f) {
        $fType = $f['type'] ?? '';
        $fAmt  = (int)($f['amt'] ?? 0);
        if ($fType === '押金') {
            $depositAmt += $fAmt;
            continue; // 押金不列在一般項目列表中,改在下方獨立顯示
        }
        $feeRows .= '<tr><td>'.htmlspecialchars($fType).'</td><td style="text-align:right">$'.number_format($fAmt).'</td></tr>';
    }
    $feeRows .= '<tr><td style="text-align:right;font-weight:bold;border-top:1.5px solid #000">本次收費總計</td><td style="text-align:right;font-weight:bold;color:#C00;border-top:1.5px solid #000">$'.number_format($feeTotal).'</td></tr>';
    if ($depositAmt > 0) {
        $feeRows .= '<tr><td style="text-align:right">押金(不列入總計)</td><td style="text-align:right;color:#8B5000">$'.number_format($depositAmt).'</td></tr>';
    }
}

// ── 表單列輔助:值為空時顯示破折號 ──────
function rowVal($val) {
    $val = trim((string)$val);
    return $val !== '' ? htmlspecialchars($val) : '<span style="color:#999">—</span>';
}

// ── 網路資訊:PPPoE 或 IP 二選一的表單列 ──
// IP 上網時整行只顯示「IP 上網」文字,不展開帳密細節
$netRows = $isStaticIP
    ? '<tr><td class="lbl">連線方式</td><td colspan="3" style="font-weight:bold;color:#3730A3">IP 上網</td></tr>'
    : '<tr><td class="lbl">PPPoE帳號</td><td>'.rowVal($G['pppoeid'] ?? '').'</td><td class="lbl">PPPoE密碼</td><td>'.rowVal($G['pppoepw'] ?? '').'</td></tr>';

// ── 組合完整 PDF HTML(單頁,仿紙本派工單版面)──────────
$pdfHtml = '<!DOCTYPE html><html lang="zh-Hant"><head><meta charset="UTF-8">
<style>
*{box-sizing:border-box}
body{font-family:"Microsoft JhengHei","WenQuanYi Micro Hei",sans-serif;font-size:11px;color:#111;margin:0;padding:0}
.page{width:100%;padding:3mm 0}
table{border-collapse:collapse;width:100%;margin-bottom:7px;table-layout:fixed}
td{border:1px solid #000;padding:4px 6px;vertical-align:top;font-size:10.5px;word-break:break-word}
.sec{background:#f0f0f0;font-weight:bold;font-size:11.5px;text-align:center}
.lbl{font-weight:bold;background:#f7f7f7;width:25%}
.pre{white-space:pre-wrap;line-height:1.7;min-height:20mm}
</style></head><body>
<div class="page">
'.$pf.$ph2.'

<!-- 用戶基本資料:三欄式,對應紙本排列 -->
<table>
<tr><td class="lbl" style="width:13%">用戶編號</td><td style="width:22%">'.rowVal($G['user_id'] ?? '').'</td><td class="lbl" style="width:11%">社區名稱</td><td style="width:18%">'.rowVal($G['member'] ?? '').'</td><td class="lbl" style="width:13%">申請人姓名</td><td style="width:23%">'.rowVal($G['user_name'] ?? '').'</td></tr>
<tr><td class="lbl">住家電話</td><td>'.rowVal($G['homenumber'] ?? '').'</td><td class="lbl">行動電話</td><td colspan="3">'.rowVal($G['phone'] ?? '').'</td></tr>
<tr><td class="lbl">裝機地址</td><td colspan="5">'.rowVal($G['memberaddress'] ?? '').'</td></tr>
<tr><td class="lbl">電子郵件</td><td colspan="5" style="font-size:9.5px">'.rowVal($G['email'] ?? '').'</td></tr>
</table>

<!-- 網路資訊 -->
<table>
<tr><td colspan="4" class="sec">網路資訊</td></tr>
'.$netRows.'
<tr><td class="lbl">據點編號</td><td>'.rowVal($G['basenumber'] ?? '').'</td><td class="lbl">使用方案</td><td>'.rowVal($G['speed'] ?? '').'</td></tr>
</table>

<!-- 設備資訊:獨立表格,避免被上方4欄網格的table-layout:fixed基準覆蓋寬度設定 -->
<table>
<tr><td colspan="3" class="sec">設備資訊</td></tr>
<tr>
<td style="width:40%;padding:0;vertical-align:top">
  <div style="font-weight:bold;background:#f7f7f7;border-bottom:1px solid #000;padding:4px 6px;white-space:nowrap">屋內網路設備</div>
  <div style="padding:4px 6px">'.rowVal($G['homene'] ?? '').'</div>
</td>
<td style="width:40%;padding:0;vertical-align:top;border-left:1px solid #000">
  <div style="font-weight:bold;background:#f7f7f7;border-bottom:1px solid #000;padding:4px 6px;white-space:nowrap">屋內資訊箱設備</div>
  <div style="padding:4px 6px">'.rowVal($G['inforne'] ?? '').'</div>
</td>
<td style="width:20%;padding:0;vertical-align:top;border-left:1px solid #000">
  <div style="font-weight:bold;background:#f7f7f7;border-bottom:1px solid #000;padding:4px 6px;white-space:nowrap">施工方式</div>
  <div style="padding:4px 6px">'.rowVal($G['networkcm'] ?? '').'</div>
</td>
</tr>
</table>


<!-- 問題狀況描述 -->
<table>
<tr><td class="sec">問題狀況描述</td></tr>
<tr><td class="pre">'.rowVal($G['question'] ?? '').'</td></tr>
</table>

<!-- 維修概況紀錄 -->
<table>
<tr><td class="sec">維修概況紀錄</td></tr>
<tr><td class="pre" style="min-height:28mm">'.$e($repairContent).'</td></tr>
</table>

<!-- 使用設備:無資料時整個區塊不顯示 -->
'.($hasDevices ? '
<table>
<tr><td colspan="4" class="sec">使用設備</td></tr>
<tr><td style="font-weight:bold;background:#f7f7f7;width:14%">類別</td><td style="font-weight:bold;background:#f7f7f7;width:24%">地點</td><td style="font-weight:bold;background:#f7f7f7;width:38%">設備名稱</td><td style="font-weight:bold;background:#f7f7f7">料號</td></tr>
'.$deviceRows.'
</table>
' : '').'

<!-- 收費紀錄:無收費時只顯示文字,不顯示項目表格 -->
<table>
<tr><td class="sec">收費紀錄</td></tr>
'.($showFeeTable
    ? '<tr><td style="padding:0"><table style="margin-bottom:0"><tr><td style="font-weight:bold;background:#f7f7f7;width:50%">項目</td><td style="font-weight:bold;background:#f7f7f7">金額</td></tr>'.$feeRows.'</table></td></tr>'
    : '<tr><td style="text-align:center;color:#1D9E75;font-weight:bold;padding:10px 6px">本次無收費</td></tr>'
).'
</table>

<!-- 簽名確認 + 服務須知:併入同一頁,左右並排 -->
<table>
<tr><td colspan="2" class="sec">簽名確認</td></tr>
<tr>
<!-- 左:客戶簽章(整欄專用於簽名,加大顯示) -->
<td style="width:58%;vertical-align:top;padding:6px">
  <div style="font-weight:bold;font-size:10.5px;margin-bottom:4px">客戶簽章</div>
  '.($csigB64 ? '<img src="'.$csigB64.'" style="width:100%;max-height:65mm;object-fit:contain;border:1px solid #ccc;display:block">' : '<div style="color:red;font-size:10px;height:50mm;display:flex;align-items:center;justify-content:center;border:1px solid #ccc">未提供簽名</div>').'
</td>
<!-- 右:服務須知 + 工程師資訊(移至下方) -->
<td style="width:42%;vertical-align:top;padding:6px">
  <div style="font-size:9px;color:#555;line-height:1.6">
1. 本公司銷售之新品設備,非人為因素造成之故障,皆保固壹年,保固期限自購買日起生效。<br>
2. 本公司維修及銷售一律採現金收費(含稅)為付款條件,恕不接受賒欠或期票。<br>
3. 本公司收到款項後,由綠界科技系統開立電子發票,開立完成後會以電子郵件或簡訊方式通知。
  </div>
  <div style="margin-top:10px;font-size:10.5px;color:#333;border-top:1px solid #ddd;padding-top:6px;line-height:1.9">
    維修工程師:<span style="font-size:13px;font-weight:bold;color:#085041">'.$e($enname).'</span><br>
    連絡電話:'.$e($G['ENphone'] ?? '').'<br>
    維修日期:'.fmtD($repairDate).'
  </div>
</td>
</tr>
</table>

</div>
</body></html>';

// ── wkhtmltopdf ─────────────────────
$wkPaths  = ['/usr/bin/wkhtmltopdf', '/usr/local/bin/wkhtmltopdf'];
$wkBin    = '';
$disabled = array_map('trim', explode(',', ini_get('disable_functions')));
foreach ($wkPaths as $p) {
    if (file_exists($p) && is_executable($p)) {
        if (!in_array('exec', $disabled)) {
            $v = []; exec('HOME=/tmp '.$p.' --version 2>&1', $v, $vr);
            if ($vr === 0) { $wkBin = $p; break; }
        } else {
            $wkBin = $p; break;
        }
    }
}

$pdfGenerated = false;
$pdfData      = '';
$wkError      = '';
$safeId       = preg_replace('/[^a-zA-Z0-9_-]/', '_', $userId);
$ts           = date('YmdHis');
$htmlTmp      = '/tmp/repair_'.$safeId.'_'.$ts.'.html';
$pdfTmp       = '/tmp/repair_'.$safeId.'_'.$ts.'.pdf';

$htmlWritten = file_put_contents($htmlTmp, $pdfHtml);
if ($htmlWritten === false) {
    ob_clean();
    echo json_encode(['success'=>false,'message'=>'無法寫入暫存 HTML:'.$htmlTmp]);
    exit;
}

if (!$wkBin) {
    $wkError = 'wkhtmltopdf 未找到:'.implode(', ', $wkPaths);
} else {
    if (!in_array('exec', $disabled)) {
        $cmd = 'HOME=/tmp DISPLAY=:0 XAUTHORITY=/dev/null '.$wkBin
             . ' --encoding utf-8 --page-size A4'
             . ' --margin-top 8mm --margin-bottom 8mm --margin-left 10mm --margin-right 10mm'
             . ' --disable-smart-shrinking --load-error-handling ignore --quiet'
             . ' '.escapeshellarg($htmlTmp).' '.escapeshellarg($pdfTmp).' 2>&1';
        $out = []; exec($cmd, $out, $ret);
        if ($ret === 0 && file_exists($pdfTmp) && filesize($pdfTmp) > 500) {
            $pdfData = file_get_contents($pdfTmp);
            $pdfGenerated = true;
        } else {
            $wkError = 'exec ret='.$ret.': '.implode('|', $out);
        }
    } elseif (!in_array('proc_open', $disabled)) {
        $cmd = $wkBin.' --encoding utf-8 --page-size A4'
             . ' --margin-top 8mm --margin-bottom 8mm --margin-left 10mm --margin-right 10mm'
             . ' --disable-smart-shrinking --load-error-handling ignore --quiet'
             . ' '.escapeshellarg($htmlTmp).' '.escapeshellarg($pdfTmp);
        $proc = proc_open($cmd, [0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']], $pipes, null,
            ['HOME'=>'/tmp','DISPLAY'=>':0','XAUTHORITY'=>'/dev/null','PATH'=>'/usr/local/bin:/usr/bin:/bin']);
        if (is_resource($proc)) {
            fclose($pipes[0]);
            $se = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]);
            $ret = proc_close($proc);
            if ($ret === 0 && file_exists($pdfTmp) && filesize($pdfTmp) > 500) {
                $pdfData = file_get_contents($pdfTmp);
                $pdfGenerated = true;
            } else {
                $wkError = 'proc ret='.$ret.': '.$se;
            }
        } else {
            $wkError = 'proc_open失敗';
        }
    } else {
        $wkError = 'exec/proc_open 均被禁用';
    }
}
if (!$pdfGenerated) error_log('[repair_submit.php] PDF失敗: '.$wkError);
@unlink($htmlTmp);
@unlink($pdfTmp);

// ── Email ────────────────────────────
// 優先使用前端傳來的 final_email(使用者確認/修改過的),其次用原始 email,兩者皆空則不寄
$finalEmail = trim($_POST['final_email'] ?? '');
$origEmail  = $G['email'] ?? '';
$email = filter_var($finalEmail, FILTER_VALIDATE_EMAIL) ? $finalEmail
       : (filter_var($origEmail, FILTER_VALIDATE_EMAIL) ? $origEmail : '');

$emailSent = false;
if (!empty($email)) {
    $from        = 'service@ysnet.com.tw';
    $subject     = '=?UTF-8?B?'.base64_encode('亞訊寬頻派工維修單 - '.($G['user_name'] ?? '').'('.fmtD($repairDate).')').'?=';
    $boundary    = md5(uniqid('rp_'));
    $pdfFilename = $safeId.'_repair.pdf';
    $bodyHtml = '<!DOCTYPE html><html><body style="font-family:Microsoft JhengHei,sans-serif;color:#111">'
              . '<p>親愛的 <strong>'.htmlspecialchars($G['user_name'] ?? '').'</strong> 您好,</p>'
              . '<p>感謝您使用亞訊寬頻維修服務,'.($pdfGenerated ? '本次維修服務單 PDF 已附於此封信件。' : '您的維修紀錄已送出。').'</p>'
              . '<table style="border-collapse:collapse;max-width:480px;width:100%">'
              . '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">維修日期</td><td style="padding:6px;border:1px solid #ddd">'.fmtD($repairDate).'</td></tr>'
              . '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">施工工程師</td><td style="padding:6px;border:1px solid #ddd">'.htmlspecialchars($enname).'</td></tr>'
              . '</table>'
              . '<p style="margin-top:16px">如有任何問題,歡迎來電 <strong>03-358-5867</strong></p>'
              . '<p style="color:#666;font-size:12px">亞訊寬頻 客服中心 | service@ysnet.com.tw</p>'
              . '</body></html>';

    $headers = "From:{$from}\r\nReply-To:{$from}\r\nMIME-Version:1.0\r\n";
    if ($pdfGenerated && $pdfData) {
        $headers .= "Content-Type: multipart/mixed; boundary=\"{$boundary}\"\r\n";
        $body = "--{$boundary}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\n"
              . chunk_split(base64_encode($bodyHtml))
              . "--{$boundary}\r\nContent-Type: application/pdf; name=\"{$pdfFilename}\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"{$pdfFilename}\"\r\n\r\n"
              . chunk_split(base64_encode($pdfData))
              . "--{$boundary}--";
    } else {
        $headers .= "Content-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n";
        $body = base64_encode($bodyHtml);
    }
    $emailSent = mail($email, $subject, $body, $headers);
    if (!$emailSent) error_log('[repair_submit.php] mail() failed for: '.$email);
}

// ── Session 清理 ─────────────────────
unset($_SESSION['rp_user_id'], $_SESSION['rp_enname'], $_SESSION['rp_email']);
$_SESSION['repair_done_submitted'] = true;
$_SESSION['repair_done_email']     = $emailSent ? $email : '';

// ── 回傳 ─────────────────────────────
while (ob_get_level() > 1) ob_end_clean();
ob_clean();
echo json_encode([
    'success'       => true,
    'pdf_generated' => $pdfGenerated,
    'pdf_error'     => $wkError,
    'email_sent'    => $emailSent,
    'message'       => $emailSent
        ? '維修單已送出,PDF 已寄至 '.$email
        : ($pdfGenerated ? '送出成功,但 Email 發送失敗' : '送出成功,但 PDF 產生失敗:'.$wkError),
], JSON_UNESCAPED_UNICODE);