adding favicon
This commit is contained in:
parent
0649f8fffd
commit
6d57961985
@ -21,6 +21,9 @@ $logs = $pdo->query("
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audit Logs - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
131
admin/auth.php
131
admin/auth.php
@ -1,60 +1,64 @@
|
||||
<?php
|
||||
// admin/auth.php
|
||||
|
||||
// Detect HTTPS even behind a proxy
|
||||
$is_https = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
|
||||
|
||||
// Ensure session is started with basic secure defaults
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_name('CHARITYHUB_SESS');
|
||||
|
||||
// Explicitly set session cookie parameters
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => $is_https,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
ini_set('session.use_strict_mode', 1); // Re-enabling strict mode for security now that login works
|
||||
|
||||
session_name('ORG_ADMIN_SESS');
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
/**
|
||||
* Check if the user is logged in
|
||||
*/
|
||||
function is_logged_in() {
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Require login for a page
|
||||
*/
|
||||
function require_login() {
|
||||
if (!is_logged_in()) {
|
||||
header('Location: login.php?auth_error=1');
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current logged in user
|
||||
*/
|
||||
function get_user() {
|
||||
return $_SESSION['user'] ?? null;
|
||||
}
|
||||
|
||||
function is_super_admin() {
|
||||
$user = get_user();
|
||||
return $user && $user['role'] === 'super_admin';
|
||||
if (!isset($_SESSION['user_id'])) return null;
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
return $stmt->fetch();
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an administrative action to the audit_logs table
|
||||
* Check if user is super admin
|
||||
*/
|
||||
function log_action($action, $details = null) {
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
$userId = $_SESSION['user_id'] ?? null;
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO audit_logs (user_id, action, details) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$userId, $action, $details]);
|
||||
function is_super_admin() {
|
||||
$user = get_user();
|
||||
return $user && isset($user['role']) && $user['role'] === 'super_admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an action to audit_logs table
|
||||
*/
|
||||
function log_action($action, $details = '') {
|
||||
try {
|
||||
$pdo = db();
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
$stmt = $pdo->prepare("INSERT INTO audit_logs (user_id, action, details) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$user_id, $action, $details]);
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to log action: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,14 +69,57 @@ function get_org_name() {
|
||||
return $_SESSION['org_name'];
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
try {
|
||||
$pdo = db();
|
||||
$profile = $pdo->query("SELECT name_en FROM org_profile LIMIT 1")->fetch();
|
||||
$name = $profile['name_en'] ?? 'CharityHub';
|
||||
$_SESSION['org_name'] = $name;
|
||||
return $name;
|
||||
if ($profile && !empty($profile['name_en'])) {
|
||||
$_SESSION['org_name'] = $profile['name_en'];
|
||||
return $profile['name_en'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return 'CharityHub';
|
||||
}
|
||||
|
||||
return 'Organization';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organization favicon URL
|
||||
*/
|
||||
function get_favicon_url() {
|
||||
if (isset($_SESSION['favicon_url']) && !empty($_SESSION['favicon_url'])) {
|
||||
return $_SESSION['favicon_url'];
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$profile = $pdo->query("SELECT favicon_url FROM org_profile LIMIT 1")->fetch();
|
||||
if ($profile && !empty($profile['favicon_url'])) {
|
||||
$_SESSION['favicon_url'] = $profile['favicon_url'];
|
||||
return $profile['favicon_url'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organization logo URL
|
||||
*/
|
||||
function get_logo_url() {
|
||||
if (isset($_SESSION['logo_url']) && !empty($_SESSION['logo_url'])) {
|
||||
return $_SESSION['logo_url'];
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$profile = $pdo->query("SELECT logo_url FROM org_profile LIMIT 1")->fetch();
|
||||
if ($profile && !empty($profile['logo_url'])) {
|
||||
$_SESSION['logo_url'] = $profile['logo_url'];
|
||||
return $profile['logo_url'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
@ -112,6 +112,9 @@ if (isset($_GET['edit'])) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Manage Cases - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
@ -51,6 +51,9 @@ if (isset($_GET['edit'])) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Manage Categories - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
@ -19,6 +19,9 @@ $donations = $pdo->query("SELECT d.*, c.title_en as case_title, cat.name_en as c
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donations - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
@ -27,6 +27,9 @@ $donors = $pdo->query("
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donors CRM - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
@ -171,6 +171,9 @@ foreach ($gift_stats as $row) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Financial Summary - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
@ -311,7 +314,7 @@ foreach ($gift_stats as $row) {
|
||||
}
|
||||
?></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 class="col-3 small text-end text-muted">Generated by: <?= htmlspecialchars($user['email']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -42,6 +42,9 @@ $recent_donations = $pdo->query("
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
@ -68,7 +71,7 @@ $recent_donations = $pdo->query("
|
||||
<div class="main-content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="mb-0">Welcome, <?= htmlspecialchars($user['name'] ?? 'Admin') ?></h2>
|
||||
<h2 class="mb-0">Welcome, <?= htmlspecialchars($user['email'] ?? 'Admin') ?></h2>
|
||||
<p class="text-muted mb-0">Manage your charity activities and donations.</p>
|
||||
</div>
|
||||
<div class="text-muted"><?= date('l, F j, Y') ?></div>
|
||||
|
||||
@ -51,6 +51,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Login - <?= htmlspecialchars(get_org_name()) ?></title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
@ -20,12 +20,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$phone = $_POST['phone'];
|
||||
$address = $_POST['address'];
|
||||
$logo_url = $profile['logo_url'] ?? '';
|
||||
$favicon_url = $profile['favicon_url'] ?? '';
|
||||
|
||||
$upload_dir = '../assets/images/';
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
|
||||
|
||||
// Handle Logo Upload
|
||||
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
|
||||
$upload_dir = '../assets/images/';
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
|
||||
|
||||
$file_ext = pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION);
|
||||
$file_name = 'logo_' . time() . '.' . $file_ext;
|
||||
$target_file = $upload_dir . $file_name;
|
||||
@ -35,15 +36,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Favicon Upload
|
||||
if (isset($_FILES['favicon']) && $_FILES['favicon']['error'] === UPLOAD_ERR_OK) {
|
||||
$file_ext = pathinfo($_FILES['favicon']['name'], PATHINFO_EXTENSION);
|
||||
$file_name = 'favicon_' . time() . '.' . $file_ext;
|
||||
$target_file = $upload_dir . $file_name;
|
||||
|
||||
if (move_uploaded_file($_FILES['favicon']['tmp_name'], $target_file)) {
|
||||
$favicon_url = 'assets/images/' . $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
if ($profile) {
|
||||
$stmt = $pdo->prepare("UPDATE org_profile SET name_en=?, name_ar=?, description_en=?, description_ar=?, email=?, phone=?, address=?, logo_url=? WHERE id=?");
|
||||
$stmt->execute([$name_en, $name_ar, $description_en, $description_ar, $email, $phone, $address, $logo_url, $profile['id']]);
|
||||
$stmt = $pdo->prepare("UPDATE org_profile SET name_en=?, name_ar=?, description_en=?, description_ar=?, email=?, phone=?, address=?, logo_url=?, favicon_url=? WHERE id=?");
|
||||
$stmt->execute([$name_en, $name_ar, $description_en, $description_ar, $email, $phone, $address, $logo_url, $favicon_url, $profile['id']]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO org_profile (name_en, name_ar, description_en, description_ar, email, phone, address, logo_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$name_en, $name_ar, $description_en, $description_ar, $email, $phone, $address, $logo_url]);
|
||||
$stmt = $pdo->prepare("INSERT INTO org_profile (name_en, name_ar, description_en, description_ar, email, phone, address, logo_url, favicon_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$name_en, $name_ar, $description_en, $description_ar, $email, $phone, $address, $logo_url, $favicon_url]);
|
||||
}
|
||||
|
||||
$_SESSION['org_name'] = $name_en;
|
||||
$_SESSION['logo_url'] = $logo_url;
|
||||
$_SESSION['favicon_url'] = $favicon_url;
|
||||
|
||||
header('Location: profile.php?success=1');
|
||||
exit;
|
||||
}
|
||||
@ -54,6 +69,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Organization Profile - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
@ -120,7 +138,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Organization Logo</label>
|
||||
<div class="mb-2">
|
||||
<?php if (!empty($profile['logo_url'])): ?>
|
||||
@ -133,6 +151,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
</div>
|
||||
<input type="file" name="logo" class="form-control" accept="image/*">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Favicon (Small icon for browser tabs)</label>
|
||||
<div class="mb-2">
|
||||
<?php if (!empty($profile['favicon_url'])): ?>
|
||||
<img src="../<?= htmlspecialchars($profile['favicon_url']) ?>" class="img-thumbnail" style="max-height: 64px;">
|
||||
<?php else: ?>
|
||||
<div class="bg-light d-flex align-items-center justify-content-center border rounded" style="height: 64px; width: 64px;">
|
||||
<i class="bi bi-app text-muted fs-3"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<input type="file" name="favicon" class="form-control" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
@ -145,4 +176,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -31,6 +31,9 @@ foreach ($settings_raw as $s) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gateway Settings - <?= htmlspecialchars(get_org_name()) ?> Admin</title>
|
||||
<?php if ($favicon = get_favicon_url()): ?>
|
||||
<link rel="icon" type="image/x-icon" href="../<?= htmlspecialchars($favicon) ?>">
|
||||
<?php endif; ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
BIN
assets/images/favicon_1770977716.jpg
Normal file
BIN
assets/images/favicon_1770977716.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
@ -17,6 +17,7 @@ $don = $stmt->fetch();
|
||||
if (!$don) exit('Donation not found or not completed.');
|
||||
|
||||
$org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
$orgName = $org['name_en'] ?? 'Organization';
|
||||
|
||||
// This is a simple HTML certificate that can be printed or saved as PDF by the user
|
||||
?>
|
||||
@ -25,7 +26,7 @@ $org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donation Certificate - CharityHub</title>
|
||||
<title>Donation Certificate - <?= htmlspecialchars($orgName) ?></title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
@ -83,7 +84,7 @@ $org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
</div>
|
||||
|
||||
<div class="stamp">
|
||||
OFFICIAL<br>SEAL<br>CHARITYHUB
|
||||
OFFICIAL<br>SEAL<br><?= strtoupper(htmlspecialchars($orgName)) ?>
|
||||
</div>
|
||||
|
||||
<div class="cert-footer">
|
||||
@ -92,7 +93,7 @@ $org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
</div>
|
||||
<div class="signature">
|
||||
Authorized Signature<br>
|
||||
<?= htmlspecialchars($org['name_en'] ?? 'CharityHub') ?>
|
||||
<?= htmlspecialchars($orgName) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,8 +6,8 @@ INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
|
||||
('mail_username', ''),
|
||||
('mail_password', ''),
|
||||
('mail_from_address', ''),
|
||||
('mail_from_name', 'CharityHub'),
|
||||
('mail_from_name', ''),
|
||||
('pop3_host', ''),
|
||||
('pop3_port', '995'),
|
||||
('pop3_username', ''),
|
||||
('pop3_password', '');
|
||||
('pop3_password', '');
|
||||
2
db/migrations/20260213_add_favicon_url.sql
Normal file
2
db/migrations/20260213_add_favicon_url.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add favicon_url column to org_profile table
|
||||
ALTER TABLE org_profile ADD COLUMN favicon_url VARCHAR(255) DEFAULT NULL AFTER logo_url;
|
||||
@ -151,6 +151,9 @@ function safe_truncate($text, $limit = 120) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= ($lang === 'en' ? ($profile['name_en'] ?? 'Organization') : ($profile['name_ar'] ?? 'المؤسسة')) . ' - ' . $t['title'] ?></title>
|
||||
<?php if (!empty($profile["favicon_url"])): ?>
|
||||
<link rel="icon" href="<?= htmlspecialchars($profile["favicon_url"]) ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Meta Tags -->
|
||||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
|
||||
|
||||
@ -13,31 +13,32 @@ class MailService
|
||||
public static function sendMail($to, string $subject, string $htmlBody, ?string $textBody = null, array $opts = [])
|
||||
{
|
||||
$cfg = self::loadConfig();
|
||||
$orgName = $cfg['org_name'] ?? 'Organization';
|
||||
|
||||
$autoload = __DIR__ . '/../vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer not available' ];
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ class MailService
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$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'] ?? $orgName);
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
if (!empty($opts['reply_to']) && filter_var($opts['reply_to'], FILTER_VALIDATE_EMAIL)) {
|
||||
$mail->addReplyTo($opts['reply_to']);
|
||||
@ -91,11 +92,22 @@ class MailService
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
|
||||
// Append footer if not explicitly disabled
|
||||
if (!isset($opts['no_footer']) || !$opts['no_footer']) {
|
||||
$footerHtml = "<br><br>Best regards,<br>The <strong>{$orgName}</strong> Team";
|
||||
$footerText = "\n\nBest regards,\nThe {$orgName} Team";
|
||||
$htmlBody .= $footerHtml;
|
||||
if ($textBody) {
|
||||
$textBody .= $footerText;
|
||||
}
|
||||
}
|
||||
|
||||
$mail->Body = $htmlBody;
|
||||
$mail->AltBody = $textBody ?? strip_tags($htmlBody);
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
@ -124,21 +136,21 @@ class MailService
|
||||
require_once $autoload;
|
||||
}
|
||||
// Fallback to system-wide PHPMailer (installed via apt: libphp-phpmailer)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
// Debian/Ubuntu package layout (libphp-phpmailer)
|
||||
@require_once 'libphp-phpmailer/autoload.php';
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'libphp-phpmailer/src/Exception.php';
|
||||
@require_once 'libphp-phpmailer/src/SMTP.php';
|
||||
@require_once 'libphp-phpmailer/src/PHPMailer.php';
|
||||
}
|
||||
// Alternative layout (older PHPMailer package names)
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'PHPMailer/src/Exception.php';
|
||||
@require_once 'PHPMailer/src/SMTP.php';
|
||||
@require_once 'PHPMailer/src/PHPMailer.php';
|
||||
}
|
||||
if (!class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if (!class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
@require_once 'PHPMailer/Exception.php';
|
||||
@require_once 'PHPMailer/SMTP.php';
|
||||
@require_once 'PHPMailer/PHPMailer.php';
|
||||
@ -146,7 +158,7 @@ class MailService
|
||||
}
|
||||
|
||||
$transport = $cfg['transport'] ?? 'smtp';
|
||||
if ($transport === 'smtp' && class_exists('PHPMailer\\PHPMailer\\PHPMailer')) {
|
||||
if ($transport === 'smtp' && class_exists('PHPMailer\PHPMailer\PHPMailer')) {
|
||||
return self::sendViaPHPMailer($cfg, $name, $email, $message, $to, $subject);
|
||||
}
|
||||
|
||||
@ -156,6 +168,7 @@ class MailService
|
||||
|
||||
private static function sendViaPHPMailer(array $cfg, string $name, string $email, string $body, $to, string $subject)
|
||||
{
|
||||
$orgName = $cfg['org_name'] ?? 'Organization';
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
@ -170,7 +183,7 @@ class MailService
|
||||
$mail->Password = $cfg['smtp_pass'] ?? '';
|
||||
|
||||
$fromEmail = $cfg['from_email'] ?? 'no-reply@localhost';
|
||||
$fromName = $cfg['from_name'] ?? 'App';
|
||||
$fromName = $cfg['from_name'] ?? $orgName;
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
|
||||
// Use Reply-To for the user's email to avoid spoofing From
|
||||
@ -216,12 +229,15 @@ class MailService
|
||||
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeEmail = htmlspecialchars($email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$safeBody = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
$mail->Body = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}";
|
||||
$mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}";
|
||||
|
||||
$footer = "<hr><p><small>This message was sent via the <strong>{$orgName}</strong> contact form.</small></p>";
|
||||
|
||||
$mail->Body = "<p><strong>Name:</strong> {$safeName}</p><p><strong>Email:</strong> {$safeEmail}</p><hr>{$safeBody}{$footer}";
|
||||
$mail->AltBody = "Name: {$name}\nEmail: {$email}\n\n{$body}\n\n---\nThis message was sent via the {$orgName} contact form.";
|
||||
|
||||
$ok = $mail->send();
|
||||
return [ 'success' => $ok ];
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\throwable $e) {
|
||||
return [ 'success' => false, 'error' => 'PHPMailer error: ' . $e->getMessage() ];
|
||||
}
|
||||
}
|
||||
@ -232,4 +248,4 @@ class MailService
|
||||
$html = nl2br(htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
return self::sendMail($to, $subject, $html, $body, $opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,9 +62,16 @@ class WablasService {
|
||||
return ['success' => false, 'error' => $result];
|
||||
}
|
||||
|
||||
public static function getOrgName() {
|
||||
$pdo = db();
|
||||
$org = $pdo->query("SELECT name_en FROM org_profile LIMIT 1")->fetch();
|
||||
return $org['name_en'] ?? 'Organization';
|
||||
}
|
||||
|
||||
public static function sendThankYou($donation) {
|
||||
$name = !empty($donation['donor_name']) ? $donation['donor_name'] : 'Donor';
|
||||
$amount = number_format($donation['amount'], 3);
|
||||
$orgName = self::getOrgName();
|
||||
|
||||
// Fetch case title
|
||||
$pdo = db();
|
||||
@ -73,7 +80,7 @@ class WablasService {
|
||||
$case = $stmt->fetch();
|
||||
$caseTitle = $case ? $case['title_en'] : 'a cause';
|
||||
|
||||
$message = "Dear $name,\n\nThank you for your generous donation of OMR $amount to \"$caseTitle\".\n\nYour support makes a real difference! ❤️\n\nCharityHub Team";
|
||||
$message = "Dear $name,\n\nThank you for your generous donation of OMR $amount to \"$caseTitle\".\n\nYour support makes a real difference! ❤️\n\n$orgName Team";
|
||||
|
||||
if (!empty($donation['donor_phone'])) {
|
||||
return self::sendMessage($donation['donor_phone'], $message);
|
||||
@ -90,6 +97,7 @@ class WablasService {
|
||||
$donorName = !empty($donation['donor_name']) ? $donation['donor_name'] : 'A generous donor';
|
||||
$recipientName = !empty($donation['gift_recipient_name']) ? $donation['gift_recipient_name'] : 'Friend';
|
||||
$giftMessage = !empty($donation['gift_message']) ? "\n\nMessage: \"" . $donation['gift_message'] . "\"" : "";
|
||||
$orgName = self::getOrgName();
|
||||
|
||||
// Fetch case title
|
||||
$pdo = db();
|
||||
@ -98,7 +106,7 @@ class WablasService {
|
||||
$case = $stmt->fetch();
|
||||
$caseTitle = $case ? $case['title_en'] : 'a charitable cause';
|
||||
|
||||
$message = "Hello $recipientName! ✨\n\n$donorName has made a donation to \"$caseTitle\" in your name as a special gift.$giftMessage\n\nMay this kindness bring joy to your day! ❤️\n\nCharityHub Team";
|
||||
$message = "Hello $recipientName! ✨\n\n$donorName has made a donation to \"$caseTitle\" in your name as a special gift.$giftMessage\n\nMay this kindness bring joy to your day! ❤️\n\n$orgName Team";
|
||||
|
||||
return self::sendMessage($donation['gift_recipient_phone'], $message);
|
||||
}
|
||||
|
||||
@ -35,14 +35,21 @@ load_dotenv_if_needed([
|
||||
|
||||
// Fetch from DB
|
||||
$db_settings = [];
|
||||
$org_name = 'Organization';
|
||||
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'];
|
||||
}
|
||||
|
||||
// Also fetch org name for default
|
||||
$org = $pdo->query("SELECT name_en FROM org_profile LIMIT 1")->fetch();
|
||||
if ($org && !empty($org['name_en'])) {
|
||||
$org_name = $org['name_en'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Fallback to empty if DB fails
|
||||
// Fallback if DB fails
|
||||
}
|
||||
|
||||
$transport = env_val('MAIL_TRANSPORT', $db_settings['mail_transport'] ?? 'smtp');
|
||||
@ -53,7 +60,14 @@ $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');
|
||||
|
||||
// Ensure from_name falls back to org_name if DB setting is empty
|
||||
$db_from_name = $db_settings['mail_from_name'] ?? '';
|
||||
if (empty($db_from_name)) {
|
||||
$db_from_name = $org_name;
|
||||
}
|
||||
$from_name = env_val('MAIL_FROM_NAME', $db_from_name);
|
||||
|
||||
$reply_to = env_val('MAIL_REPLY_TO');
|
||||
|
||||
return [
|
||||
@ -66,6 +80,7 @@ return [
|
||||
'from_email' => $from_email,
|
||||
'from_name' => $from_name,
|
||||
'reply_to' => $reply_to,
|
||||
'org_name' => $org_name,
|
||||
'pop3' => [
|
||||
'host' => $db_settings['pop3_host'] ?? '',
|
||||
'port' => $db_settings['pop3_port'] ?? '995',
|
||||
|
||||
@ -1,157 +1,160 @@
|
||||
<?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";
|
||||
}
|
||||
|
||||
<?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");
|
||||
}
|
||||
|
||||
// Get org profile
|
||||
$org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
$orgName = $org['name_en'] ?? 'Organization';
|
||||
|
||||
// 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 " . htmlspecialchars($orgName) . " Admin Panel.</p>
|
||||
<p>© " . date('Y') . " " . htmlspecialchars($orgName) . ". 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";
|
||||
}
|
||||
@ -77,13 +77,16 @@ if ($donation) {
|
||||
$final_donation_id = $existing['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
$orgName = $org['name_en'] ?? 'Organization';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donation Successful - CharityHub</title>
|
||||
<title>Donation Successful - <?= htmlspecialchars($orgName) ?></title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user