installation

This commit is contained in:
Flatlogic Bot 2026-02-28 07:59:16 +00:00
parent 26c455a51e
commit c6cb25129c
5 changed files with 107 additions and 33 deletions

View File

@ -70,6 +70,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? 0; $id = $_POST['id'] ?? 0;
if ($ref_no && $subject) { if ($ref_no && $subject) {
$should_notify = false;
try { try {
db()->beginTransaction(); db()->beginTransaction();
if ($action === 'add') { if ($action === 'add') {
@ -78,7 +79,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$mail_id = db()->lastInsertId(); $mail_id = db()->lastInsertId();
if ($assigned_to) { if ($assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject); $should_notify = true;
} }
$_SESSION['success'] = 'تمت إضافة البريد بنجاح'; $_SESSION['success'] = 'تمت إضافة البريد بنجاح';
@ -93,7 +94,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt->execute([$ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $id]); $stmt->execute([$ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $id]);
if ($assigned_to && $assigned_to != $old_assigned_to) { if ($assigned_to && $assigned_to != $old_assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject); $should_notify = true;
} }
$_SESSION['success'] = 'تم تحديث البيانات بنجاح'; $_SESSION['success'] = 'تم تحديث البيانات بنجاح';
@ -117,9 +118,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
db()->commit(); db()->commit();
// Notify after commit to avoid holding locks during email sending
if ($should_notify) {
sendAssignmentNotification($assigned_to, $ref_no, $subject);
}
redirect('inbound.php'); redirect('inbound.php');
} catch (PDOException $e) { } catch (PDOException $e) {
db()->rollBack(); if (db()->inTransaction()) db()->rollBack();
if ($e->getCode() == 23000) { if ($e->getCode() == 23000) {
$error = 'رقم القيد مستخدم مسبقاً'; $error = 'رقم القيد مستخدم مسبقاً';
} else { } else {
@ -404,7 +411,7 @@ function getStatusBadgeInList($mail) {
</div> </div>
<div class="col-12"> <div class="col-12">
<label class="form-label fw-bold">الوصف / ملاحظات</label> <label class="form-label fw-bold">الوصف / ملاحظات</label>
<textarea name="description" id="modalDescription" class="form-control" rows="3"></textarea> <textarea name="description" id="modalDescription" class="form-control" rows="5"></textarea>
</div> </div>
<div class="col-12"> <div class="col-12">
<label class="form-label fw-bold">المرفقات</label> <label class="form-label fw-bold">المرفقات</label>
@ -445,8 +452,27 @@ function getStatusBadgeInList($mail) {
</div> </div>
<script> <script>
let descriptionEditor;
let mailModal; let mailModal;
function initEditors() {
if (typeof ClassicEditor === 'undefined') {
console.error('CKEditor not loaded');
return Promise.resolve();
}
const config = {
language: 'ar',
toolbar: ['heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'undo', 'redo']
};
return ClassicEditor.create(document.querySelector('#modalDescription'), config)
.then(editor => { descriptionEditor = editor; })
.catch(err => {
console.error('CKEditor Init Error:', err);
});
}
function openMailModal(action, data = null) { function openMailModal(action, data = null) {
if (!mailModal) { if (!mailModal) {
const modalEl = document.getElementById('mailModal'); const modalEl = document.getElementById('mailModal');
@ -469,7 +495,6 @@ function openMailModal(action, data = null) {
sender: document.getElementById('modalSender'), sender: document.getElementById('modalSender'),
recipient: document.getElementById('modalRecipient'), recipient: document.getElementById('modalRecipient'),
subject: document.getElementById('modalSubject'), subject: document.getElementById('modalSubject'),
description: document.getElementById('modalDescription'),
status_id: document.getElementById('modalStatusId'), status_id: document.getElementById('modalStatusId'),
assigned_to: document.getElementById('modalAssignedTo') assigned_to: document.getElementById('modalAssignedTo')
}; };
@ -487,37 +512,49 @@ function openMailModal(action, data = null) {
else fields[key].value = ''; else fields[key].value = '';
} }
}); });
if (descriptionEditor) descriptionEditor.setData('');
else document.getElementById('modalDescription').value = '';
} else { } else {
label.textContent = 'تعديل البريد الوارد'; label.textContent = 'تعديل البريد الوارد';
modalId.value = data.id; modalId.value = data.id;
Object.keys(fields).forEach(key => { Object.keys(fields).forEach(key => {
if (fields[key]) fields[key].value = data[key] || ''; if (fields[key]) fields[key].value = data[key] || '';
}); });
if (descriptionEditor) descriptionEditor.setData(data.description || '');
else document.getElementById('modalDescription').value = data.description || '';
} }
mailModal.show(); mailModal.show();
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
<?php if ($deepLinkData): ?> initEditors().finally(() => {
openMailModal('edit', <?= json_encode($deepLinkData) ?>); <?php if ($deepLinkData): ?>
<?php elseif ($error && isset($_POST['action'])): ?> openMailModal('edit', <?= json_encode($deepLinkData) ?>);
const errorData = <?= json_encode([ <?php elseif ($error && isset($_POST['action'])): ?>
'id' => $_POST['id'] ?? 0, const errorData = <?= json_encode([
'ref_no' => $_POST['ref_no'] ?? '', 'id' => $_POST['id'] ?? 0,
'date_registered' => $_POST['date_registered'] ?? date('Y-m-d'), 'ref_no' => $_POST['ref_no'] ?? '',
'due_date' => $_POST['due_date'] ?? '', 'date_registered' => $_POST['date_registered'] ?? date('Y-m-d'),
'sender' => $_POST['sender'] ?? '', 'due_date' => $_POST['due_date'] ?? '',
'recipient' => $_POST['recipient'] ?? '', 'sender' => $_POST['sender'] ?? '',
'subject' => $_POST['subject'] ?? '', 'recipient' => $_POST['recipient'] ?? '',
'description' => $_POST['description'] ?? '', 'subject' => $_POST['subject'] ?? '',
'status_id' => $_POST['status_id'] ?? $default_status_id, 'description' => $_POST['description'] ?? '',
'assigned_to' => $_POST['assigned_to'] ?? '' 'status_id' => $_POST['status_id'] ?? $default_status_id,
]) ?>; 'assigned_to' => $_POST['assigned_to'] ?? ''
openMailModal('<?= $_POST['action'] ?>', errorData); ]) ?>;
<?php elseif (isset($_GET['action']) && $_GET['action'] === 'add'): ?> openMailModal('<?= $_POST['action'] ?>', errorData);
openMailModal('add'); <?php elseif (isset($_GET['action']) && $_GET['action'] === 'add'): ?>
<?php endif; ?> openMailModal('add');
<?php endif; ?>
});
document.getElementById('mailForm').addEventListener('submit', function() {
if (descriptionEditor) descriptionEditor.updateSourceElement();
});
}); });
function confirmDelete(id) { function confirmDelete(id) {
@ -546,6 +583,11 @@ function confirmDelete(id) {
<?php endif; ?> <?php endif; ?>
<style> <style>
.ck-editor__editable_inline {
min-height: 250px;
direction: rtl;
text-align: right;
}
.modal-content { .modal-content {
border-radius: 15px; border-radius: 15px;
overflow: hidden; overflow: hidden;

View File

@ -38,7 +38,13 @@
} }
</style> </style>
<!-- Bootstrap Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- CKEditor -->
<script src="https://cdn.ckeditor.com/ckeditor5/40.0.0/classic/ckeditor.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Main App JS -->
<script src="assets/js/main.js?v=<?= time() ?>"></script> <script src="assets/js/main.js?v=<?= time() ?>"></script>
<script> <script>
// Sidebar Toggle for Mobile // Sidebar Toggle for Mobile

View File

@ -24,6 +24,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
$default_status_id = db()->query("SELECT id FROM mailbox_statuses WHERE is_default = 1 LIMIT 1")->fetchColumn() ?: 1; $default_status_id = db()->query("SELECT id FROM mailbox_statuses WHERE is_default = 1 LIMIT 1")->fetchColumn() ?: 1;
if ($recipient_id && $subject) { if ($recipient_id && $subject) {
$should_notify = false;
$recipient_email = '';
try { try {
db()->beginTransaction(); db()->beginTransaction();
$stmt = db()->prepare("INSERT INTO mailbox (type, ref_no, date_registered, subject, description, status_id, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt = db()->prepare("INSERT INTO mailbox (type, ref_no, date_registered, subject, description, status_id, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
@ -47,12 +49,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
} }
} }
// Notify recipient // Get recipient info for notification
$stmt_recp = db()->prepare("SELECT full_name, email FROM users WHERE id = ?"); $stmt_recp = db()->prepare("SELECT full_name, email FROM users WHERE id = ?");
$stmt_recp->execute([$recipient_id]); $stmt_recp->execute([$recipient_id]);
$recipient = $stmt_recp->fetch(); $recipient = $stmt_recp->fetch();
if ($recipient && !empty($recipient['email'])) { if ($recipient && !empty($recipient['email'])) {
$should_notify = true;
$recipient_email = $recipient['email'];
}
db()->commit();
// Notify after commit
if ($should_notify) {
$email_subject = "رسالة داخلية جديدة من " . $_SESSION['name']; $email_subject = "رسالة داخلية جديدة من " . $_SESSION['name'];
$htmlBody = " $htmlBody = "
<div dir='rtl' style='font-family: Arial, sans-serif;'> <div dir='rtl' style='font-family: Arial, sans-serif;'>
@ -65,14 +75,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
<p>يمكنك الرد من خلال النظام.</p> <p>يمكنك الرد من خلال النظام.</p>
</div> </div>
"; ";
MailService::sendMail($recipient['email'], $email_subject, $htmlBody); MailService::sendMail($recipient_email, $email_subject, $htmlBody);
} }
db()->commit();
$_SESSION['success'] = 'تم إرسال الرسالة بنجاح'; $_SESSION['success'] = 'تم إرسال الرسالة بنجاح';
redirect('internal_outbox.php'); redirect('internal_outbox.php');
} catch (PDOException $e) { } catch (PDOException $e) {
db()->rollBack(); if (db()->inTransaction()) db()->rollBack();
$error = 'حدث خطأ: ' . $e->getMessage(); $error = 'حدث خطأ: ' . $e->getMessage();
} }
} else { } else {
@ -271,7 +280,7 @@ function getStatusBadgeInternal($mail) {
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-bold">إلى <span class="text-danger">*</span></label> <label class="form-label fw-bold">إلى <span class="text-danger">*</span></label>
<select name="recipient_id" class="form-select border-2" required> <select name="recipient_id" class="form-select border-2" required>
<option value="">-- اختر המوظف --</option> <option value="">-- اختر الموظف --</option>
<?php foreach ($users_list as $u): ?> <?php foreach ($users_list as $u): ?>
<option value="<?= $u['id'] ?>"><?= htmlspecialchars($u['full_name'] . ' (@' . $u['username'] . ')') ?></option> <option value="<?= $u['id'] ?>"><?= htmlspecialchars($u['full_name'] . ' (@' . $u['username'] . ')') ?></option>
<?php endforeach; ?> <?php endforeach; ?>
@ -301,6 +310,7 @@ function getStatusBadgeInternal($mail) {
</div> </div>
<script> <script>
let composeEditor;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (typeof ClassicEditor !== 'undefined') { if (typeof ClassicEditor !== 'undefined') {
ClassicEditor ClassicEditor
@ -310,6 +320,7 @@ document.addEventListener('DOMContentLoaded', function() {
direction: 'rtl' direction: 'rtl'
}) })
.then(editor => { .then(editor => {
composeEditor = editor;
// Force RTL style on editable area // Force RTL style on editable area
editor.ui.view.editable.element.style.direction = 'rtl'; editor.ui.view.editable.element.style.direction = 'rtl';
editor.ui.view.editable.element.style.textAlign = 'right'; editor.ui.view.editable.element.style.textAlign = 'right';
@ -319,6 +330,10 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
} }
document.getElementById('composeForm').addEventListener('submit', function() {
if (composeEditor) composeEditor.updateSourceElement();
});
<?php if (isset($_GET['action']) && $_GET['action'] === 'compose'): ?> <?php if (isset($_GET['action']) && $_GET['action'] === 'compose'): ?>
var myModal = new bootstrap.Modal(document.getElementById('composeModal')); var myModal = new bootstrap.Modal(document.getElementById('composeModal'));
myModal.show(); myModal.show();

View File

@ -61,6 +61,10 @@ class MailService
$mail->Username = $cfg['smtp_user'] ?? ''; $mail->Username = $cfg['smtp_user'] ?? '';
$mail->Password = $cfg['smtp_pass'] ?? ''; $mail->Password = $cfg['smtp_pass'] ?? '';
// Set timeout to 10 seconds to prevent long hangs
$mail->Timeout = 10;
$mail->SMTPKeepAlive = false;
$fromEmail = $opts['from_email'] ?? ($cfg['from_email'] ?? 'no-reply@localhost'); $fromEmail = $opts['from_email'] ?? ($cfg['from_email'] ?? 'no-reply@localhost');
$fromName = $opts['from_name'] ?? ($cfg['from_name'] ?? 'App'); $fromName = $opts['from_name'] ?? ($cfg['from_name'] ?? 'App');
$mail->setFrom($fromEmail, $fromName); $mail->setFrom($fromEmail, $fromName);

View File

@ -82,6 +82,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? 0; $id = $_POST['id'] ?? 0;
if ($ref_no && $subject) { if ($ref_no && $subject) {
$should_notify = false;
try { try {
db()->beginTransaction(); db()->beginTransaction();
if ($action === 'add') { if ($action === 'add') {
@ -90,7 +91,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$mail_id = db()->lastInsertId(); $mail_id = db()->lastInsertId();
if ($assigned_to) { if ($assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject); $should_notify = true;
} }
$_SESSION['success'] = 'تمت إضافة البريد الصادر بنجاح'; $_SESSION['success'] = 'تمت إضافة البريد الصادر بنجاح';
@ -106,7 +107,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt->execute([$ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $mail_id]); $stmt->execute([$ref_no, $date_registered, $due_date, $sender, $recipient, $subject, $description, $status_id, $assigned_to, $mail_id]);
if ($assigned_to && $assigned_to != $old_assigned_to) { if ($assigned_to && $assigned_to != $old_assigned_to) {
sendAssignmentNotification($assigned_to, $ref_no, $subject); $should_notify = true;
} }
$_SESSION['success'] = 'تم تحديث البيانات بنجاح'; $_SESSION['success'] = 'تم تحديث البيانات بنجاح';
@ -130,9 +131,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
db()->commit(); db()->commit();
// Notify after commit
if ($should_notify) {
sendAssignmentNotification($assigned_to, $ref_no, $subject);
}
redirect('outbound.php'); redirect('outbound.php');
} catch (PDOException $e) { } catch (PDOException $e) {
db()->rollBack(); if (db()->inTransaction()) db()->rollBack();
if ($e->getCode() == 23000) { if ($e->getCode() == 23000) {
$error = 'رقم القيد مستخدم مسبقاً'; $error = 'رقم القيد مستخدم مسبقاً';
} else { } else {