From 918a5ff12059b40f15cf66ea339b49c5c411b8e1 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Tue, 10 Mar 2026 13:09:30 +0200 Subject: [PATCH] =?UTF-8?q?Lis=C3=A4=C3=A4=20s=C3=A4hk=C3=B6postiallekirjo?= =?UTF-8?q?itus=20per=20k=C3=A4ytt=C3=A4j=C3=A4=20per=20postilaatikko?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Allekirjoitukset tallennetaan users.json:iin (signatures-objekti, avaimena mailbox_id) - Käyttäjälomakkeessa dynaamiset textareat jokaiselle postilaatikolle - Allekirjoitus liitetään automaattisesti sähköpostivastauksiin (ticket_reply) - Esikatselu näkyy tikettivastauslomakkeen alla - Muistiinpanoihin (ticket_note) ei lisätä allekirjoitusta - Uusi all_mailboxes endpoint palauttaa kaikki käyttäjän postilaatikot - check_auth ja login palauttavat nyt myös user_id ja signatures Co-Authored-By: Claude Opus 4.6 --- api.php | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++--- index.html | 6 +++++ script.js | 57 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 5 deletions(-) diff --git a/api.php b/api.php index 2c531be..d2871db 100644 --- a/api.php +++ b/api.php @@ -1067,6 +1067,7 @@ switch ($action) { 'role' => $u['role'], 'companies' => $companyList, 'company_id' => $_SESSION['company_id'], + 'signatures' => $u['signatures'] ?? [], ]); $found = true; break; @@ -1107,13 +1108,23 @@ switch ($action) { $companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']]; } } + // Hae allekirjoitukset + $userSignatures = []; + foreach ($users as $uu) { + if ($uu['id'] === $_SESSION['user_id']) { + $userSignatures = $uu['signatures'] ?? []; + break; + } + } echo json_encode([ 'authenticated' => true, + 'user_id' => $_SESSION['user_id'], 'username' => $_SESSION['username'], 'nimi' => $_SESSION['nimi'], 'role' => $_SESSION['role'], 'companies' => $companyList, 'company_id' => $_SESSION['company_id'] ?? '', + 'signatures' => $userSignatures, ]); } else { echo json_encode(['authenticated' => false]); @@ -1232,6 +1243,12 @@ switch ($action) { $allCompanies = loadCompanies(); $validIds = array_column($allCompanies, 'id'); $companies = array_values(array_filter($companies, fn($c) => in_array($c, $validIds))); + $signatures = []; + if (isset($input['signatures']) && is_array($input['signatures'])) { + foreach ($input['signatures'] as $mbId => $sig) { + $signatures[(string)$mbId] = (string)$sig; + } + } $newUser = [ 'id' => generateId(), 'username' => $username, @@ -1240,6 +1257,7 @@ switch ($action) { 'email' => $email, 'role' => $role, 'companies' => $companies, + 'signatures' => $signatures, 'luotu' => date('Y-m-d H:i:s'), ]; $users[] = $newUser; @@ -1269,6 +1287,13 @@ switch ($action) { if (!empty($input['password'])) { $u['password_hash'] = password_hash($input['password'], PASSWORD_DEFAULT); } + if (isset($input['signatures']) && is_array($input['signatures'])) { + $sigs = []; + foreach ($input['signatures'] as $mbId => $sig) { + $sigs[(string)$mbId] = (string)$sig; + } + $u['signatures'] = $sigs; + } $found = true; addLog('user_update', '', '', "Muokkasi käyttäjää: {$u['username']}"); // Päivitä sessio jos muokattiin kirjautunutta käyttäjää @@ -2022,8 +2047,20 @@ switch ($action) { $replyMailbox = $companyConf['mailboxes'][0]; } + // Hae käyttäjän allekirjoitus tälle postilaatikolle + $mailboxId = $t['mailbox_id'] ?? ''; + $signature = ''; + $usersForSig = loadUsers(); + foreach ($usersForSig as $sigUser) { + if ($sigUser['id'] === $_SESSION['user_id']) { + $signature = trim($sigUser['signatures'][$mailboxId] ?? ''); + break; + } + } + $emailBody = $signature ? $body . "\n\n-- \n" . $signature : $body; + $subject = 'Re: ' . $t['subject']; - $sent = sendTicketMail($t['from_email'], $subject, $body, $lastMsgId, trim($allRefs), $replyMailbox); + $sent = sendTicketMail($t['from_email'], $subject, $emailBody, $lastMsgId, trim($allRefs), $replyMailbox); if (!$sent) { http_response_code(500); @@ -2031,13 +2068,13 @@ switch ($action) { break 2; } - // Add reply to ticket + // Add reply to ticket (tallennetaan allekirjoituksen kanssa) $reply = [ 'id' => generateId(), 'type' => 'reply_out', 'from' => currentUser(), 'from_name' => $_SESSION['nimi'] ?? currentUser(), - 'body' => $body, + 'body' => $emailBody, 'timestamp' => date('Y-m-d H:i:s'), 'message_id' => '', ]; @@ -2399,6 +2436,30 @@ switch ($action) { echo json_encode(loadCompanies()); break; + case 'all_mailboxes': + requireAuth(); + // Palauttaa kaikki postilaatikot käyttäjän yrityksistä (allekirjoituksia varten) + $userCompanyIds = $_SESSION['companies'] ?? []; + $allCompanies = loadCompanies(); + $result = []; + foreach ($allCompanies as $comp) { + if (!in_array($comp['id'], $userCompanyIds)) continue; + $oldCompanyId = $_SESSION['company_id'] ?? ''; + $_SESSION['company_id'] = $comp['id']; + $conf = loadCompanyConfig(); + $_SESSION['company_id'] = $oldCompanyId; + foreach ($conf['mailboxes'] ?? [] as $mb) { + $result[] = [ + 'id' => $mb['id'], + 'nimi' => $mb['nimi'] ?? $mb['imap_user'] ?? '', + 'company_id' => $comp['id'], + 'company_nimi' => $comp['nimi'], + ]; + } + } + echo json_encode($result); + break; + case 'company_create': requireAdmin(); if ($method !== 'POST') break; diff --git a/index.html b/index.html index fb57a59..f5ae51f 100644 --- a/index.html +++ b/index.html @@ -331,6 +331,7 @@ +
@@ -735,6 +736,11 @@
+
diff --git a/script.js b/script.js index d85f443..d4d0242 100644 --- a/script.js +++ b/script.js @@ -7,6 +7,7 @@ let currentUser = { username: '', nimi: '', role: '' }; let currentCompany = null; // {id, nimi} let availableCompanies = []; // [{id, nimi}, ...] let currentTicketCompanyId = ''; // Avatun tiketin yritys (cross-company tuki) +let currentUserSignatures = {}; // {mailbox_id: "allekirjoitus teksti"} // Elements const loginScreen = document.getElementById('login-screen'); @@ -131,9 +132,10 @@ async function checkAuth() { try { const data = await apiCall('check_auth'); if (data.authenticated) { - currentUser = { username: data.username, nimi: data.nimi, role: data.role }; + currentUser = { username: data.username, nimi: data.nimi, role: data.role, id: data.user_id }; availableCompanies = data.companies || []; currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null; + currentUserSignatures = data.signatures || {}; showDashboard(); } } catch (e) { /* not logged in */ } @@ -147,9 +149,10 @@ loginForm.addEventListener('submit', async (e) => { try { const data = await apiCall('login', 'POST', { username, password, captcha: parseInt(captcha) }); loginError.style.display = 'none'; - currentUser = { username: data.username, nimi: data.nimi, role: data.role }; + currentUser = { username: data.username, nimi: data.nimi, role: data.role, id: data.user_id }; availableCompanies = data.companies || []; currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null; + currentUserSignatures = data.signatures || {}; showDashboard(); } catch (err) { loginError.textContent = err.message; @@ -976,6 +979,27 @@ function openUserForm(user = null) { ` ).join(''); }); + // Allekirjoitukset per postilaatikko + const sigSection = document.getElementById('user-signatures-section'); + const sigList = document.getElementById('user-signatures-list'); + const userSigs = user ? (user.signatures || {}) : {}; + apiCall('all_mailboxes').then(mailboxes => { + if (mailboxes.length === 0) { + sigSection.style.display = 'none'; + return; + } + sigSection.style.display = ''; + sigList.innerHTML = mailboxes.map(mb => + `
+ + +
` + ).join(''); + }).catch(() => { + sigSection.style.display = 'none'; + }); userModal.style.display = 'flex'; } @@ -999,12 +1023,20 @@ document.getElementById('user-form').addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('user-form-id').value; const companies = [...document.querySelectorAll('.user-company-cb:checked')].map(cb => cb.value); + // Kerää allekirjoitukset + const signatures = {}; + document.querySelectorAll('.sig-textarea').forEach(ta => { + const mbId = ta.dataset.mailboxId; + const val = ta.value.trim(); + if (val) signatures[mbId] = val; + }); const data = { username: document.getElementById('user-form-username').value, nimi: document.getElementById('user-form-nimi').value, email: document.getElementById('user-form-email').value, role: document.getElementById('user-form-role').value, companies, + signatures, }; const pw = document.getElementById('user-form-password').value; if (pw) data.password = pw; @@ -1014,6 +1046,12 @@ document.getElementById('user-form').addEventListener('submit', async (e) => { else { await apiCall('user_create', 'POST', data); } userModal.style.display = 'none'; loadUsers(); + // Päivitä omat allekirjoitukset (check_auth palauttaa tuoreet) + const auth = await apiCall('check_auth'); + if (auth.authenticated) { + currentUser = { username: auth.username, nimi: auth.nimi, role: auth.role, id: auth.user_id }; + currentUserSignatures = auth.signatures || {}; + } } catch (e) { alert(e.message); } }); @@ -1348,6 +1386,17 @@ async function showTicketDetail(id, companyId = '') { document.querySelector('.btn-reply-tab[data-reply-type="reply"]').classList.add('active'); document.getElementById('btn-send-reply').textContent = 'Lähetä vastaus'; + // Allekirjoituksen esikatselu + const sigPreview = document.getElementById('signature-preview'); + const mailboxId = ticket.mailbox_id || ''; + const sig = currentUserSignatures[mailboxId] || ''; + if (sig) { + sigPreview.textContent = '-- \n' + sig; + sigPreview.style.display = 'block'; + } else { + sigPreview.style.display = 'none'; + } + } catch (e) { alert(e.message); } } @@ -1376,12 +1425,16 @@ document.querySelectorAll('.btn-reply-tab').forEach(btn => { ticketReplyType = btn.dataset.replyType; const textarea = document.getElementById('ticket-reply-body'); const sendBtn = document.getElementById('btn-send-reply'); + const sigPrev = document.getElementById('signature-preview'); if (ticketReplyType === 'note') { textarea.placeholder = 'Kirjoita sisäinen muistiinpano...'; sendBtn.textContent = 'Tallenna muistiinpano'; + sigPrev.style.display = 'none'; } else { textarea.placeholder = 'Kirjoita vastaus...'; sendBtn.textContent = 'Lähetä vastaus'; + // Näytä allekirjoitus jos on asetettu + if (sigPrev.textContent.trim()) sigPrev.style.display = 'block'; } }); });