adding monthly email
This commit is contained in:
parent
699f776eee
commit
ad3177ebeb
26
admin/ajax_test_email.php
Normal file
26
admin/ajax_test_email.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once 'auth.php';
|
||||
require_once '../mail/MailService.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!is_logged_in() || !is_super_admin()) {
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$recipient = $_POST['recipient'] ?? '';
|
||||
|
||||
if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid recipient email address']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$subject = "SMTP Test Email - CharityHub";
|
||||
$htmlBody = "<h1>Test Successful!</h1><p>If you are reading this, your SMTP configuration is working correctly.</p><p>Sent at: " . date('Y-m-d H:i:s') . "</p>";
|
||||
$textBody = "SMTP Test Email - CharityHub\n\nTest Successful!\nIf you are reading this, your SMTP configuration is working correctly.\nSent at: " . date('Y-m-d H:i:s');
|
||||
|
||||
$result = MailService::sendMail($recipient, $subject, $htmlBody, $textBody);
|
||||
|
||||
echo json_encode($result);
|
||||
|
||||
@ -6,27 +6,76 @@ require_login();
|
||||
$user = get_user();
|
||||
$pdo = db();
|
||||
|
||||
// Fetch Categories for filter
|
||||
$stmt = $pdo->query("SELECT id, name_en FROM categories ORDER BY name_en ASC");
|
||||
$categories = $stmt->fetchAll();
|
||||
|
||||
// Filters
|
||||
$start_date = $_GET['start_date'] ?? '';
|
||||
$end_date = $_GET['end_date'] ?? '';
|
||||
$category_id = $_GET['category_id'] ?? '';
|
||||
$status_filter = $_GET['status'] ?? '';
|
||||
|
||||
// Build base where clauses for donations table
|
||||
$where_clauses = ["1=1"];
|
||||
$params = [];
|
||||
|
||||
if (!empty($start_date)) {
|
||||
$where_clauses[] = "d.created_at >= :start_date";
|
||||
$params[':start_date'] = $start_date . " 00:00:00";
|
||||
}
|
||||
if (!empty($end_date)) {
|
||||
$where_clauses[] = "d.created_at <= :end_date";
|
||||
$params[':end_date'] = $end_date . " 23:59:59";
|
||||
}
|
||||
if (!empty($status_filter)) {
|
||||
$where_clauses[] = "d.status = :status";
|
||||
$params[':status'] = $status_filter;
|
||||
}
|
||||
|
||||
// Category filter requires join with cases
|
||||
$category_join = "";
|
||||
if (!empty($category_id)) {
|
||||
$category_join = " JOIN cases cs_filter ON d.case_id = cs_filter.id ";
|
||||
$where_clauses[] = "cs_filter.category_id = :category_id";
|
||||
$params[':category_id'] = $category_id;
|
||||
}
|
||||
|
||||
$where_sql = implode(" AND ", $where_clauses);
|
||||
|
||||
// Basic Stats
|
||||
$stats = $pdo->query("
|
||||
$stats_stmt = $pdo->prepare("
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) as total_revenue,
|
||||
AVG(CASE WHEN status = 'completed' THEN amount ELSE NULL END) as avg_donation,
|
||||
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count,
|
||||
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count
|
||||
FROM donations
|
||||
")->fetch();
|
||||
SUM(CASE WHEN d.status = 'completed' THEN d.amount ELSE 0 END) as total_revenue,
|
||||
AVG(CASE WHEN d.status = 'completed' THEN d.amount ELSE NULL END) as avg_donation,
|
||||
SUM(CASE WHEN d.status = 'pending' THEN 1 ELSE 0 END) as pending_count,
|
||||
SUM(CASE WHEN d.status = 'completed' THEN 1 ELSE 0 END) as completed_count
|
||||
FROM donations d
|
||||
$category_join
|
||||
WHERE $where_sql
|
||||
");
|
||||
$stats_stmt->execute($params);
|
||||
$stats = $stats_stmt->fetch();
|
||||
|
||||
// Revenue by Category
|
||||
$category_revenue = $pdo->query("
|
||||
$cat_where = $where_sql;
|
||||
$cat_params = $params;
|
||||
if (empty($status_filter)) {
|
||||
$cat_where .= " AND d.status = 'completed'";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.name_en, SUM(d.amount) as total
|
||||
FROM categories c
|
||||
JOIN cases cs ON cs.category_id = c.id
|
||||
JOIN donations d ON d.case_id = cs.id
|
||||
WHERE d.status = 'completed'
|
||||
WHERE $cat_where
|
||||
GROUP BY c.id
|
||||
ORDER BY total DESC
|
||||
")->fetchAll();
|
||||
");
|
||||
$stmt->execute($cat_params);
|
||||
$category_revenue = $stmt->fetchAll();
|
||||
|
||||
$cat_labels = [];
|
||||
$cat_totals = [];
|
||||
@ -35,16 +84,29 @@ foreach ($category_revenue as $row) {
|
||||
$cat_totals[] = (float)$row['total'];
|
||||
}
|
||||
|
||||
// Monthly Revenue Trend (Last 12 Months)
|
||||
$monthly_trend = $pdo->query("
|
||||
// Monthly Revenue Trend
|
||||
$trend_where = $where_sql;
|
||||
$trend_params = $params;
|
||||
if (empty($status_filter)) {
|
||||
$trend_where .= " AND d.status = 'completed'";
|
||||
}
|
||||
|
||||
if (empty($start_date) && empty($end_date)) {
|
||||
$trend_where .= " AND d.created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
DATE_FORMAT(created_at, '%Y-%m') as month,
|
||||
SUM(amount) as total
|
||||
FROM donations
|
||||
WHERE status = 'completed' AND created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
|
||||
DATE_FORMAT(d.created_at, '%Y-%m') as month,
|
||||
SUM(d.amount) as total
|
||||
FROM donations d
|
||||
$category_join
|
||||
WHERE $trend_where
|
||||
GROUP BY month
|
||||
ORDER BY month ASC
|
||||
")->fetchAll();
|
||||
");
|
||||
$stmt->execute($trend_params);
|
||||
$monthly_trend = $stmt->fetchAll();
|
||||
|
||||
$trend_labels = [];
|
||||
$trend_totals = [];
|
||||
@ -54,26 +116,43 @@ foreach ($monthly_trend as $row) {
|
||||
}
|
||||
|
||||
// Top Cases by Revenue
|
||||
$top_cases = $pdo->query("
|
||||
$top_where = $where_sql;
|
||||
$top_params = $params;
|
||||
if (empty($status_filter)) {
|
||||
$top_where .= " AND d.status = 'completed'";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT cs.title_en, SUM(d.amount) as total, cs.goal
|
||||
FROM cases cs
|
||||
JOIN donations d ON d.case_id = cs.id
|
||||
WHERE d.status = 'completed'
|
||||
WHERE $top_where
|
||||
GROUP BY cs.id
|
||||
ORDER BY total DESC
|
||||
LIMIT 5
|
||||
")->fetchAll();
|
||||
");
|
||||
$stmt->execute($top_params);
|
||||
$top_cases = $stmt->fetchAll();
|
||||
|
||||
// Gift vs Regular
|
||||
$gift_stats = $pdo->query("
|
||||
$gift_where = $where_sql;
|
||||
$gift_params = $params;
|
||||
if (empty($status_filter)) {
|
||||
$gift_where .= " AND d.status = 'completed'";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
is_gift,
|
||||
d.is_gift,
|
||||
COUNT(*) as count,
|
||||
SUM(amount) as total
|
||||
FROM donations
|
||||
WHERE status = 'completed'
|
||||
GROUP BY is_gift
|
||||
")->fetchAll();
|
||||
SUM(d.amount) as total
|
||||
FROM donations d
|
||||
$category_join
|
||||
WHERE $gift_where
|
||||
GROUP BY d.is_gift
|
||||
");
|
||||
$stmt->execute($gift_params);
|
||||
$gift_stats = $stmt->fetchAll();
|
||||
|
||||
$gift_labels = ['Regular', 'Gift'];
|
||||
$gift_totals = [0, 0];
|
||||
@ -103,6 +182,34 @@ foreach ($gift_stats as $row) {
|
||||
.card { border: none; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.stat-card { padding: 1.5rem; border-left: 4px solid var(--primary-color); }
|
||||
.chart-container { position: relative; height: 250px; }
|
||||
.filter-section { background: #fff; padding: 1.25rem; border-radius: 12px; margin-bottom: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
|
||||
|
||||
@media print {
|
||||
.sidebar, .filter-section, .no-print {
|
||||
display: none !important;
|
||||
}
|
||||
.main-content {
|
||||
margin-left: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.card {
|
||||
box-shadow: none !important;
|
||||
border: 1px solid #eee !important;
|
||||
break-inside: avoid;
|
||||
}
|
||||
body {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
.stat-card {
|
||||
border-left: 4px solid var(--primary-color) !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
.progress-bar {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -111,10 +218,101 @@ foreach ($gift_stats as $row) {
|
||||
<div class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="mb-0">Financial Summary</h2>
|
||||
<h2 class="mb-0">Financial Summary Report</h2>
|
||||
<p class="text-muted mb-0">Detailed analysis of donations and revenue streams.</p>
|
||||
</div>
|
||||
<div class="text-muted"><?= date('l, F j, Y') ?></div>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold"><?= date('l, F j, Y') ?></div>
|
||||
<div class="text-muted small no-print">CharityHub Admin Panel</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filter-section">
|
||||
<form method="GET" class="row g-3 align-items-end">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small fw-bold text-muted">Start Date</label>
|
||||
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($start_date) ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small fw-bold text-muted">End Date</label>
|
||||
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($end_date) ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold text-muted">Category</label>
|
||||
<select name="category_id" class="form-select">
|
||||
<option value="">All Categories</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $category_id == $cat['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($cat['name_en']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small fw-bold text-muted">Status</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="">All (Default: Completed)</option>
|
||||
<option value="completed" <?= $status_filter == 'completed' ? 'selected' : '' ?>>Completed</option>
|
||||
<option value="pending" <?= $status_filter == 'pending' ? 'selected' : '' ?>>Pending</option>
|
||||
<option value="failed" <?= $status_filter == 'failed' ? 'selected' : '' ?>>Failed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-primary px-3">
|
||||
<i class="bi bi-filter"></i> Apply
|
||||
</button>
|
||||
<a href="financial_summary.php" class="btn btn-outline-secondary ms-1">Reset</a>
|
||||
<button type="button" onclick="window.print()" class="btn btn-outline-danger ms-1">
|
||||
<i class="bi bi-file-earmark-pdf"></i> Export PDF
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (!empty($start_date) || !empty($end_date) || !empty($category_id) || !empty($status_filter)): ?>
|
||||
<div class="mt-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<?php if (!empty($start_date) || !empty($end_date)): ?>
|
||||
<span class="badge bg-light text-dark border p-2">
|
||||
<i class="bi bi-calendar-check me-1"></i>
|
||||
<?= $start_date ?: 'Beginning' ?> - <?= $end_date ?: 'Today' ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($category_id)):
|
||||
$cat_name = "";
|
||||
foreach($categories as $c) if($c['id'] == $category_id) $cat_name = $c['name_en'];
|
||||
?>
|
||||
<span class="badge bg-light text-dark border p-2">
|
||||
<i class="bi bi-tag me-1"></i>
|
||||
Category: <?= htmlspecialchars($cat_name) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($status_filter)): ?>
|
||||
<span class="badge bg-light text-dark border p-2">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Status: <?= ucfirst(htmlspecialchars($status_filter)) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Print-only filter summary -->
|
||||
<div class="d-none d-print-block mb-4 p-3 bg-light border rounded">
|
||||
<h6 class="mb-2 fw-bold text-uppercase small text-muted">Report Filters</h6>
|
||||
<div class="row">
|
||||
<div class="col-3 small"><strong>Date Range:</strong> <?= ($start_date ?: 'All') . ' to ' . ($end_date ?: 'Today') ?></div>
|
||||
<div class="col-3 small"><strong>Category:</strong> <?php
|
||||
if($category_id) {
|
||||
foreach($categories as $c) if($c['id'] == $category_id) echo htmlspecialchars($c['name_en']);
|
||||
} else {
|
||||
echo "All Categories";
|
||||
}
|
||||
?></div>
|
||||
<div class="col-3 small"><strong>Status:</strong> <?= $status_filter ? ucfirst(htmlspecialchars($status_filter)) : 'Completed (Default)' ?></div>
|
||||
<div class="col-3 small text-end text-muted">Generated by: <?= htmlspecialchars($user['name']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
@ -135,7 +333,7 @@ foreach ($gift_stats as $row) {
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card" style="border-left-color: #f59e0b;">
|
||||
<div class="text-muted small">Pending Revenue</div>
|
||||
<div class="text-muted small">Pending Count</div>
|
||||
<div class="h3 mb-0"><?= $stats['pending_count'] ?></div>
|
||||
<div class="text-warning small">Awaiting payment</div>
|
||||
</div>
|
||||
@ -144,7 +342,7 @@ foreach ($gift_stats as $row) {
|
||||
<div class="card stat-card" style="border-left-color: #ec4899;">
|
||||
<div class="text-muted small">Total Donations</div>
|
||||
<div class="h3 mb-0"><?= $stats['total_count'] ?></div>
|
||||
<div class="text-danger small">All statuses included</div>
|
||||
<div class="text-danger small">Based on active filters</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -153,7 +351,7 @@ foreach ($gift_stats as $row) {
|
||||
<!-- Monthly Trend -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card p-4 h-100">
|
||||
<h5 class="mb-4">Monthly Revenue Trend (Last 12 Months)</h5>
|
||||
<h5 class="mb-4">Revenue Trend <?= empty($start_date) && empty($end_date) ? '(Last 12 Months)' : '' ?></h5>
|
||||
<div style="height: 300px;">
|
||||
<canvas id="trendChart"></canvas>
|
||||
</div>
|
||||
@ -176,21 +374,25 @@ foreach ($gift_stats as $row) {
|
||||
<div class="card p-4 h-100">
|
||||
<h5 class="mb-4">Top Performing Cases</h5>
|
||||
<div class="list-group list-group-flush">
|
||||
<?php foreach ($top_cases as $case): ?>
|
||||
<div class="list-group-item px-0 border-0 mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="fw-bold"><?= htmlspecialchars($case['title_en']) ?></span>
|
||||
<span>OMR <?= number_format($case['total'], 3) ?></span>
|
||||
<?php if (empty($top_cases)): ?>
|
||||
<p class="text-muted text-center py-5">No donations found for this selection.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($top_cases as $case): ?>
|
||||
<div class="list-group-item px-0 border-0 mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="fw-bold"><?= htmlspecialchars($case['title_en']) ?></span>
|
||||
<span>OMR <?= number_format($case['total'], 3) ?></span>
|
||||
</div>
|
||||
<?php
|
||||
$percent = $case['goal'] > 0 ? ($case['total'] / $case['goal']) * 100 : 0;
|
||||
?>
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: <?= min(100, $percent) ?>%"></div>
|
||||
</div>
|
||||
<div class="text-muted small mt-1"><?= number_format($percent, 1) ?>% of OMR <?= number_format($case['goal'], 0) ?> goal</div>
|
||||
</div>
|
||||
<?php
|
||||
$percent = $case['goal'] > 0 ? ($case['total'] / $case['goal']) * 100 : 0;
|
||||
?>
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: <?= min(100, $percent) ?>%"></div>
|
||||
</div>
|
||||
<div class="text-muted small mt-1"><?= number_format($percent, 1) ?>% of OMR <?= number_format($case['goal'], 0) ?> goal</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -217,6 +419,12 @@ foreach ($gift_stats as $row) {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Disable animations for cleaner printing
|
||||
const isPrinting = window.matchMedia('print');
|
||||
const animationConfig = {
|
||||
duration: isPrinting.matches ? 0 : 1000
|
||||
};
|
||||
|
||||
// Trend Chart
|
||||
new Chart(document.getElementById('trendChart'), {
|
||||
type: 'bar',
|
||||
@ -230,6 +438,7 @@ foreach ($gift_stats as $row) {
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
animation: animationConfig,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
@ -250,6 +459,7 @@ foreach ($gift_stats as $row) {
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
animation: animationConfig,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
@ -269,6 +479,7 @@ foreach ($gift_stats as $row) {
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
animation: animationConfig,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
@ -276,6 +487,14 @@ foreach ($gift_stats as $row) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for print event to disable animations if not already handled
|
||||
window.onbeforeprint = () => {
|
||||
Chart.instances.forEach(chart => {
|
||||
chart.options.animation = false;
|
||||
chart.update('none');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -12,8 +12,8 @@ $pdo = db();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
foreach ($_POST['settings'] as $key => $value) {
|
||||
$stmt = $pdo->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = ?");
|
||||
$stmt->execute([$value, $key]);
|
||||
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
|
||||
$stmt->execute([$key, $value]);
|
||||
}
|
||||
header('Location: settings.php?success=1');
|
||||
exit;
|
||||
@ -41,7 +41,9 @@ foreach ($settings_raw as $s) {
|
||||
.nav-link { color: #9ca3af; margin-bottom: 0.5rem; border-radius: 8px; }
|
||||
.nav-link:hover, .nav-link.active { color: #fff; background: #1f2937; }
|
||||
.nav-link.active { background: var(--primary-color); }
|
||||
.card { border: none; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.card { border: none; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 1.5rem; }
|
||||
.card-header { font-weight: 600; }
|
||||
.btn-test { font-size: 0.85rem; padding: 0.25rem 0.75rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -59,11 +61,101 @@ foreach ($settings_raw as $s) {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="test-alert-container"></div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="row g-4">
|
||||
<!-- Thawani Settings -->
|
||||
<div class="row">
|
||||
<!-- SMTP Settings -->
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-envelope me-2"></i> SMTP Configuration</h5>
|
||||
<button type="button" class="btn btn-outline-secondary btn-test" id="btn-test-smtp">
|
||||
<i class="bi bi-send me-1"></i> Test Email
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">SMTP Host</label>
|
||||
<input type="text" name="settings[mail_host]" class="form-control" value="<?= htmlspecialchars($settings['mail_host'] ?? '') ?>" placeholder="e.g. smtp.gmail.com">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">SMTP Port</label>
|
||||
<input type="text" name="settings[mail_port]" class="form-control" value="<?= htmlspecialchars($settings['mail_port'] ?? '587') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Encryption</label>
|
||||
<select name="settings[mail_encryption]" class="form-select">
|
||||
<option value="tls" <?= ($settings['mail_encryption'] ?? 'tls') === 'tls' ? 'selected' : '' ?>>TLS</option>
|
||||
<option value="ssl" <?= ($settings['mail_encryption'] ?? '') === 'ssl' ? 'selected' : '' ?>>SSL</option>
|
||||
<option value="none" <?= ($settings['mail_encryption'] ?? '') === 'none' ? 'selected' : '' ?>>None</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">SMTP Username</label>
|
||||
<input type="text" name="settings[mail_username]" class="form-control" value="<?= htmlspecialchars($settings['mail_username'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">SMTP Password</label>
|
||||
<input type="password" name="settings[mail_password]" class="form-control" value="<?= htmlspecialchars($settings['mail_password'] ?? '') ?>">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">From Address</label>
|
||||
<input type="email" name="settings[mail_from_address]" class="form-control" value="<?= htmlspecialchars($settings['mail_from_address'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">From Name</label>
|
||||
<input type="text" name="settings[mail_from_name]" class="form-control" value="<?= htmlspecialchars($settings['mail_from_name'] ?? 'CharityHub') ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Settings -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0"><i class="bi bi-file-earmark-bar-graph me-2"></i> Report Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Monthly Report Recipient Email</label>
|
||||
<input type="email" name="settings[report_recipient_email]" class="form-control" value="<?= htmlspecialchars($settings['report_recipient_email'] ?? '') ?>" placeholder="admin@example.com">
|
||||
<div class="form-text">The email address that will receive the automated monthly financial summary.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- POP3 Settings -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0"><i class="bi bi-mailbox me-2"></i> POP3 Configuration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">POP3 Host</label>
|
||||
<input type="text" name="settings[pop3_host]" class="form-control" value="<?= htmlspecialchars($settings['pop3_host'] ?? '') ?>" placeholder="e.g. pop.gmail.com">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">POP3 Port</label>
|
||||
<input type="text" name="settings[pop3_port]" class="form-control" value="<?= htmlspecialchars($settings['pop3_port'] ?? '995') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">POP3 Username</label>
|
||||
<input type="text" name="settings[pop3_username]" class="form-control" value="<?= htmlspecialchars($settings['pop3_username'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">POP3 Password</label>
|
||||
<input type="password" name="settings[pop3_password]" class="form-control" value="<?= htmlspecialchars($settings['pop3_password'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thawani Settings -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0"><i class="bi bi-credit-card me-2"></i> Thawani Payment Gateway</h5>
|
||||
</div>
|
||||
@ -71,12 +163,10 @@ foreach ($settings_raw as $s) {
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<input type="password" name="settings[thawani_secret_key]" class="form-control" value="<?= htmlspecialchars($settings['thawani_secret_key'] ?? '') ?>">
|
||||
<div class="form-text">Your Thawani Secret Key from merchant dashboard.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Publishable Key</label>
|
||||
<input type="text" name="settings[thawani_publishable_key]" class="form-control" value="<?= htmlspecialchars($settings['thawani_publishable_key'] ?? '') ?>">
|
||||
<div class="form-text">Your Thawani Publishable Key.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Environment</label>
|
||||
@ -87,11 +177,9 @@ foreach ($settings_raw as $s) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wablas Settings -->
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<!-- Wablas Settings -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0"><i class="bi bi-whatsapp me-2"></i> Wablas WhatsApp Gateway</h5>
|
||||
</div>
|
||||
@ -99,17 +187,14 @@ foreach ($settings_raw as $s) {
|
||||
<div class="mb-3">
|
||||
<label class="form-label">API Token</label>
|
||||
<input type="password" name="settings[wablas_api_token]" class="form-control" value="<?= htmlspecialchars($settings['wablas_api_token'] ?? '') ?>">
|
||||
<div class="form-text">Your Wablas API Token.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Security Key</label>
|
||||
<input type="password" name="settings[wablas_security_key]" class="form-control" value="<?= htmlspecialchars($settings['wablas_security_key'] ?? '') ?>">
|
||||
<div class="form-text">Optional security key if required by your Wablas account.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Server URL</label>
|
||||
<input type="url" name="settings[wablas_server_url]" class="form-control" value="<?= htmlspecialchars($settings['wablas_server_url'] ?? 'https://console.wablas.com') ?>">
|
||||
<div class="form-text">The server URL for Wablas API (e.g., https://console.wablas.com).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -122,6 +207,84 @@ foreach ($settings_raw as $s) {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Test Email Modal -->
|
||||
<div class="modal fade" id="testEmailModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Test SMTP Connection</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-muted small">Please save your settings before testing if you have made any changes.</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Recipient Email</label>
|
||||
<input type="email" id="test_recipient" class="form-control" placeholder="your-email@example.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="btn-send-test">Send Test Email</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const testEmailModal = new bootstrap.Modal(document.getElementById('testEmailModal'));
|
||||
const btnTestSmtp = document.getElementById('btn-test-smtp');
|
||||
const btnSendTest = document.getElementById('btn-send-test');
|
||||
const recipientInput = document.getElementById('test_recipient');
|
||||
const alertContainer = document.getElementById('test-alert-container');
|
||||
|
||||
btnTestSmtp.addEventListener('click', function() {
|
||||
testEmailModal.show();
|
||||
});
|
||||
|
||||
btnSendTest.addEventListener('click', function() {
|
||||
const recipient = recipientInput.value.trim();
|
||||
if (!recipient) {
|
||||
alert('Please enter a recipient email.');
|
||||
return;
|
||||
}
|
||||
|
||||
btnSendTest.disabled = true;
|
||||
btnSendTest.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Sending...';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('recipient', recipient);
|
||||
|
||||
fetch('ajax_test_email.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
btnSendTest.disabled = false;
|
||||
btnSendTest.innerHTML = 'Send Test Email';
|
||||
testEmailModal.hide();
|
||||
|
||||
const alertType = data.success ? 'success' : 'danger';
|
||||
const alertMsg = data.success ? 'Test email sent successfully! Please check your inbox.' : 'Failed to send test email: ' + (data.error || 'Unknown error');
|
||||
|
||||
const alertHtml = `
|
||||
<div class="alert alert-${alertType} alert-dismissible fade show">
|
||||
${alertMsg}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`;
|
||||
alertContainer.innerHTML = alertHtml;
|
||||
})
|
||||
.catch(error => {
|
||||
btnSendTest.disabled = false;
|
||||
btnSendTest.innerHTML = 'Send Test Email';
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while sending the test email.');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
13
db/migrations/20260213_add_email_settings.sql
Normal file
13
db/migrations/20260213_add_email_settings.sql
Normal file
@ -0,0 +1,13 @@
|
||||
INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
|
||||
('mail_transport', 'smtp'),
|
||||
('mail_host', ''),
|
||||
('mail_port', '587'),
|
||||
('mail_encryption', 'tls'),
|
||||
('mail_username', ''),
|
||||
('mail_password', ''),
|
||||
('mail_from_address', ''),
|
||||
('mail_from_name', 'CharityHub'),
|
||||
('pop3_host', ''),
|
||||
('pop3_port', '995'),
|
||||
('pop3_username', ''),
|
||||
('pop3_password', '');
|
||||
@ -1,15 +1,12 @@
|
||||
<?php
|
||||
// Mail configuration sourced from environment variables.
|
||||
// No secrets are stored here; the file just maps env -> config array for MailService.
|
||||
// Mail configuration sourced from environment variables and database settings.
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
function env_val(string $key, $default = null) {
|
||||
$v = getenv($key);
|
||||
return ($v === false || $v === null || $v === '') ? $default : $v;
|
||||
}
|
||||
|
||||
// Fallback: if critical vars are missing from process env, try to parse executor/.env
|
||||
// This helps in web/Apache contexts where .env is not exported.
|
||||
// Supports simple KEY=VALUE lines; ignores quotes and comments.
|
||||
function load_dotenv_if_needed(array $keys): void {
|
||||
$missing = array_filter($keys, fn($k) => getenv($k) === false || getenv($k) === '');
|
||||
if (empty($missing)) return;
|
||||
@ -22,9 +19,7 @@ function load_dotenv_if_needed(array $keys): void {
|
||||
if ($line[0] === '#' || trim($line) === '') continue;
|
||||
if (!str_contains($line, '=')) continue;
|
||||
[$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
// Strip potential surrounding quotes
|
||||
$v = trim($v, "\"' ");
|
||||
// Do not override existing env
|
||||
$v = trim($v, "' ");
|
||||
if ($k !== '' && (getenv($k) === false || getenv($k) === '')) {
|
||||
putenv("{$k}={$v}");
|
||||
}
|
||||
@ -35,42 +30,46 @@ function load_dotenv_if_needed(array $keys): void {
|
||||
|
||||
load_dotenv_if_needed([
|
||||
'MAIL_TRANSPORT','SMTP_HOST','SMTP_PORT','SMTP_SECURE','SMTP_USER','SMTP_PASS',
|
||||
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO',
|
||||
'DKIM_DOMAIN','DKIM_SELECTOR','DKIM_PRIVATE_KEY_PATH'
|
||||
'MAIL_FROM','MAIL_FROM_NAME','MAIL_REPLY_TO','MAIL_TO'
|
||||
]);
|
||||
|
||||
$transport = env_val('MAIL_TRANSPORT', 'smtp');
|
||||
$smtp_host = env_val('SMTP_HOST');
|
||||
$smtp_port = (int) env_val('SMTP_PORT', 587);
|
||||
$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null
|
||||
$smtp_user = env_val('SMTP_USER');
|
||||
$smtp_pass = env_val('SMTP_PASS');
|
||||
// Fetch from DB
|
||||
$db_settings = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key LIKE 'mail_%' OR setting_key LIKE 'pop3_%'");
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$db_settings[$row['setting_key']] = $row['setting_value'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Fallback to empty if DB fails
|
||||
}
|
||||
|
||||
$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
|
||||
$from_name = env_val('MAIL_FROM_NAME', 'App');
|
||||
$transport = env_val('MAIL_TRANSPORT', $db_settings['mail_transport'] ?? 'smtp');
|
||||
$smtp_host = env_val('SMTP_HOST', $db_settings['mail_host'] ?? '');
|
||||
$smtp_port = (int) env_val('SMTP_PORT', $db_settings['mail_port'] ?? 587);
|
||||
$smtp_secure = env_val('SMTP_SECURE', $db_settings['mail_encryption'] ?? 'tls');
|
||||
$smtp_user = env_val('SMTP_USER', $db_settings['mail_username'] ?? '');
|
||||
$smtp_pass = env_val('SMTP_PASS', $db_settings['mail_password'] ?? '');
|
||||
|
||||
$from_email = env_val('MAIL_FROM', $db_settings['mail_from_address'] ?? 'no-reply@localhost');
|
||||
$from_name = env_val('MAIL_FROM_NAME', $db_settings['mail_from_name'] ?? 'App');
|
||||
$reply_to = env_val('MAIL_REPLY_TO');
|
||||
|
||||
$dkim_domain = env_val('DKIM_DOMAIN');
|
||||
$dkim_selector = env_val('DKIM_SELECTOR');
|
||||
$dkim_private_key_path = env_val('DKIM_PRIVATE_KEY_PATH');
|
||||
|
||||
return [
|
||||
'transport' => $transport,
|
||||
|
||||
// SMTP
|
||||
'smtp_host' => $smtp_host,
|
||||
'smtp_port' => $smtp_port,
|
||||
'smtp_secure' => $smtp_secure,
|
||||
'smtp_user' => $smtp_user,
|
||||
'smtp_pass' => $smtp_pass,
|
||||
|
||||
// From / Reply-To
|
||||
'from_email' => $from_email,
|
||||
'from_name' => $from_name,
|
||||
'reply_to' => $reply_to,
|
||||
|
||||
// DKIM (optional)
|
||||
'dkim_domain' => $dkim_domain,
|
||||
'dkim_selector' => $dkim_selector,
|
||||
'dkim_private_key_path' => $dkim_private_key_path,
|
||||
];
|
||||
'pop3' => [
|
||||
'host' => $db_settings['pop3_host'] ?? '',
|
||||
'port' => $db_settings['pop3_port'] ?? '995',
|
||||
'user' => $db_settings['pop3_username'] ?? '',
|
||||
'pass' => $db_settings['pop3_password'] ?? '',
|
||||
]
|
||||
];
|
||||
157
scripts/monthly_report.php
Normal file
157
scripts/monthly_report.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* Monthly Financial Report Script
|
||||
* This script calculates financial statistics for the previous month and sends an email report.
|
||||
* Run via CLI: php scripts/monthly_report.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../mail/MailService.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Get settings
|
||||
$settings_raw = $pdo->query("SELECT * FROM settings")->fetchAll();
|
||||
$settings = [];
|
||||
foreach ($settings_raw as $s) {
|
||||
$settings[$s['setting_key']] = $s['setting_value'];
|
||||
}
|
||||
|
||||
$recipient = $settings['report_recipient_email'] ?? getenv('MAIL_TO');
|
||||
|
||||
if (empty($recipient)) {
|
||||
die("Error: No recipient email configured in settings or environment.\n");
|
||||
}
|
||||
|
||||
// Calculate last month date range
|
||||
$start_date = date('Y-m-01', strtotime('first day of last month'));
|
||||
$end_date = date('Y-m-t', strtotime('last month'));
|
||||
$month_name = date('F Y', strtotime('last month'));
|
||||
|
||||
// Fetch statistics for the period
|
||||
$stats = [];
|
||||
|
||||
// 1. Total Donations
|
||||
$stmt = $pdo->prepare("SELECT SUM(amount) as total, COUNT(*) as count FROM donations WHERE status = 'paid' AND created_at BETWEEN ? AND ?");
|
||||
$stmt->execute([$start_date . ' 00:00:00', $end_date . ' 23:59:59']);
|
||||
$donation_stats = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$stats['total_amount'] = $donation_stats['total'] ?? 0;
|
||||
$stats['total_count'] = $donation_stats['count'] ?? 0;
|
||||
|
||||
// 2. Donations by Category
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.name, SUM(d.amount) as total
|
||||
FROM donations d
|
||||
JOIN cases cs ON d.case_id = cs.id
|
||||
JOIN categories c ON cs.category_id = c.id
|
||||
WHERE d.status = 'paid' AND d.created_at BETWEEN ? AND ?
|
||||
GROUP BY c.id
|
||||
ORDER BY total DESC
|
||||
");
|
||||
$stmt->execute([$start_date . ' 00:00:00', $end_date . ' 23:59:59']);
|
||||
$stats['by_category'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 3. Top Cases
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT cs.title, SUM(d.amount) as total
|
||||
FROM donations d
|
||||
JOIN cases cs ON d.case_id = cs.id
|
||||
WHERE d.status = 'paid' AND d.created_at BETWEEN ? AND ?
|
||||
GROUP BY cs.id
|
||||
ORDER BY total DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute([$start_date . ' 00:00:00', $end_date . ' 23:59:59']);
|
||||
$stats['top_cases'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Build HTML Email
|
||||
$html = "
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e0e0e0; border-radius: 10px; }
|
||||
.header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid #059669; padding-bottom: 10px; }
|
||||
.header h1 { color: #059669; margin: 0; }
|
||||
.summary-box { background: #f9f9f9; padding: 20px; border-radius: 8px; margin-bottom: 20px; text-align: center; }
|
||||
.stat-value { font-size: 24px; font-weight: bold; color: #059669; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
|
||||
th { background: #f4f4f4; }
|
||||
.footer { margin-top: 30px; font-size: 12px; color: #777; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container'>
|
||||
<div class='header'>
|
||||
<h1>Monthly Financial Summary</h1>
|
||||
<p>Period: {$month_name}</p>
|
||||
</div>
|
||||
|
||||
<div class='summary-box'>
|
||||
<div style='display: inline-block; width: 45%;'>
|
||||
<p style='margin: 0; color: #666;'>Total Donations</p>
|
||||
<div class='stat-value'>$" . number_format($stats['total_amount'], 2) . "</div>
|
||||
</div>
|
||||
<div style='display: inline-block; width: 45%; border-left: 1px solid #ddd;'>
|
||||
<p style='margin: 0; color: #666;'>Donation Count</p>
|
||||
<div class='stat-value'>{$stats['total_count']}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Donations by Category</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
|
||||
foreach ($stats['by_category'] as $cat) {
|
||||
$html .= "<tr><td>" . htmlspecialchars($cat['name']) . "</td><td>$" . number_format($cat['total'], 2) . "</td></tr>";
|
||||
}
|
||||
|
||||
$html .= "
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Top 5 Performing Cases</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Case Title</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
|
||||
foreach ($stats['top_cases'] as $case) {
|
||||
$html .= "<tr><td>" . htmlspecialchars($case['title']) . "</td><td>$" . number_format($case['total'], 2) . "</td></tr>";
|
||||
}
|
||||
|
||||
$html .= "
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class='footer'>
|
||||
<p>This is an automated report from your CharityHub Admin Panel.</p>
|
||||
<p>© " . date('Y') . " CharityHub. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
// Send Email
|
||||
$subject = "Financial Summary Report - {$month_name}";
|
||||
$plain_text = "Monthly Financial Summary for {$month_name}\n\nTotal Amount: $" . number_format($stats['total_amount'], 2) . "\nTotal Count: {$stats['total_count']}\n\nPlease view the HTML version for detailed breakdown.";
|
||||
|
||||
$res = MailService::sendMail($recipient, $subject, $html, $plain_text);
|
||||
|
||||
if (!empty($res['success'])) {
|
||||
echo "Report sent successfully to {$recipient} for {$month_name}.\n";
|
||||
} else {
|
||||
echo "Failed to send report: " . ($res['error'] ?? 'Unknown error') . "\n";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user