Autosave: 20260214-075612
This commit is contained in:
parent
29bb162fcd
commit
c1267b50a7
@ -5,10 +5,16 @@ require_once 'auth.php';
|
||||
require_login();
|
||||
require_once '../db/config.php';
|
||||
$pdo = db();
|
||||
require_once '../includes/fpdf/fpdf.php';
|
||||
|
||||
// Use tFPDF for Unicode/Arabic support
|
||||
require_once '../includes/fpdf/tfpdf.php';
|
||||
require_once '../includes/fpdf/font/unifont/ttfonts.php';
|
||||
require_once '../includes/I18N/Arabic.php';
|
||||
require_once '../mail/MailService.php';
|
||||
require_once 'i18n.php';
|
||||
|
||||
use I18N\Arabic;
|
||||
|
||||
// Get case ID from URL
|
||||
if (!isset($_GET['case_id']) || !is_numeric($_GET['case_id'])) {
|
||||
die('Invalid Case ID');
|
||||
@ -33,115 +39,60 @@ $logs_stmt = $pdo->prepare('SELECT al.*, u.email FROM audit_logs al LEFT JOIN us
|
||||
$logs_stmt->execute([$case_id]);
|
||||
$audit_logs = $logs_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// ####################################################################################
|
||||
// ## Fake Arabic Class for RTL text
|
||||
// ####################################################################################
|
||||
class PDF_Arabic extends FPDF {
|
||||
protected $CurrentFont;
|
||||
// PDF Generation Class
|
||||
class PDF extends tFPDF
|
||||
{
|
||||
private $arabic;
|
||||
|
||||
function __construct($orientation='P', $unit='mm', $size='A4') {
|
||||
parent::__construct($orientation, $unit, $size);
|
||||
$this->AddFont('Amiri','','amiri.php');
|
||||
$this->AddFont('Amiri','B','amirib.php');
|
||||
}
|
||||
|
||||
function Write($h, $txt, $link='', $fill=false, $align='') {
|
||||
$txt = $this->Convert_ar ($txt);
|
||||
parent::Write($h, $txt, $link, $fill, $align);
|
||||
}
|
||||
|
||||
function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='') {
|
||||
$txt = $this->Convert_ar ($txt);
|
||||
parent::Cell($w, $h, $txt, $border, $ln, $align, $fill, $link);
|
||||
}
|
||||
|
||||
function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false) {
|
||||
$txt = $this->Convert_ar ($txt);
|
||||
parent::MultiCell($w, $h, $txt, $border, $align, $fill);
|
||||
}
|
||||
|
||||
function Convert_ar($txt) {
|
||||
$p = $this->GetStringWidth($txt);
|
||||
$p = ($this->w - $p) /2;
|
||||
$nb = strlen($txt);
|
||||
$i = 0;
|
||||
$j = 0;
|
||||
$l = 0;
|
||||
$ns = 0;
|
||||
$sep = '-';
|
||||
$w = '';
|
||||
$len = 0;
|
||||
$h = $this->CurrentFont['h'];
|
||||
while ($i < $nb) {
|
||||
$c = $txt[$i];
|
||||
if ($c == '(') {
|
||||
$i++;
|
||||
$j = $i;
|
||||
while ($i < $nb && $txt[$i] != ')')
|
||||
$i++;
|
||||
$w .= substr($txt, $j, $i - $j) . ' ';
|
||||
$i++;
|
||||
continue;
|
||||
} elseif ($c == ')') {
|
||||
$i++;
|
||||
continue;
|
||||
}
|
||||
if (ord($c) < 192) {
|
||||
if ($c == ' ') {
|
||||
if ($l > 0) {
|
||||
$w .= $this->sub_str($txt, $ns, $l) . $sep;
|
||||
$ns = $i + 1;
|
||||
$l = 0;
|
||||
} else {
|
||||
$ns++;
|
||||
}
|
||||
} else
|
||||
$l++;
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$i += 2;
|
||||
$l += 2;
|
||||
}
|
||||
}
|
||||
if ($l > 0)
|
||||
$w .= $this->sub_str($txt, $ns, $l);
|
||||
$this->rtl = false;
|
||||
return $w;
|
||||
}
|
||||
|
||||
function sub_str($str, $from, $len) {
|
||||
$s = '';
|
||||
$i = $from;
|
||||
while ($i < $from + $len) {
|
||||
if (ord($str[$i]) < 128)
|
||||
$s .= $str[$i++];
|
||||
else {
|
||||
$s .= $str[$i++] . $str[$i++];
|
||||
}
|
||||
}
|
||||
return $s;
|
||||
function __construct($orientation='P', $unit='mm', $size='A4') {
|
||||
parent::__construct($orientation, $unit, $size);
|
||||
// Load Amiri font (Unicode)
|
||||
$this->AddFont('Amiri', '', 'amiri.ttf', true);
|
||||
$this->AddFont('Amiri', 'B', 'amiri.ttf', true); // Reuse regular for bold if bold TTF missing
|
||||
$this->arabic = new Arabic();
|
||||
}
|
||||
|
||||
}
|
||||
function shapeText($text) {
|
||||
// Apply Arabic shaping (Ligatures + Reversal for RTL)
|
||||
// This is necessary because PDF writes characters Left-to-Right.
|
||||
// For Arabic, we must reverse the string (Last char first) after shaping.
|
||||
if (preg_match('/\p{Arabic}/u', $text)) {
|
||||
return $this->arabic->utf8Glyphs($text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
function formatMoney($amount) {
|
||||
$currency = __('OMR');
|
||||
$formatted = number_format($amount, 3);
|
||||
|
||||
if (get_current_lang() === 'ar') {
|
||||
// For Arabic, we want the visual order to be: Number then Currency (Reading RTL: Currency Number)
|
||||
// But since we print LTR, we place Number on the Left, and reversed Currency on the Right.
|
||||
// E.g. "123 .ع.ر"
|
||||
return $formatted . ' ' . $this->shapeText($currency);
|
||||
}
|
||||
|
||||
// For English: "OMR 123"
|
||||
return $currency . ' ' . $formatted;
|
||||
}
|
||||
|
||||
// PDF Generation Class
|
||||
class PDF extends PDF_Arabic
|
||||
{
|
||||
function Header() {
|
||||
if (file_exists('../assets/images/logo_1770967720.jpg')) {
|
||||
$this->Image('../assets/images/logo_1770967720.jpg', 10, 6, 30);
|
||||
}
|
||||
$this->SetFont('Amiri', 'B', 15);
|
||||
$this->Cell(80);
|
||||
$this->Cell(30, 10, __('Case Report'), 0, 0, 'C');
|
||||
$title = $this->shapeText(__('Case Report'));
|
||||
$this->Cell(30, 10, $title, 0, 0, 'C');
|
||||
$this->Ln(20);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
$this->SetY(-15);
|
||||
$this->SetFont('Amiri', 'B', 8);
|
||||
$this->Cell(0, 10, __('Page') . ' ' . $this->PageNo() . '/{nb}', 0, 0, 'C');
|
||||
$pageTxt = $this->shapeText(__('Page') . ' ' . $this->PageNo() . '/{nb}');
|
||||
$this->Cell(0, 10, $pageTxt, 0, 0, 'C');
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,6 +103,8 @@ function generate_pdf($case, $donations, $audit_logs)
|
||||
$pdf->AddPage();
|
||||
$pdf->SetFont('Amiri', '', 12);
|
||||
|
||||
$is_ar = (get_current_lang() === 'ar');
|
||||
|
||||
// Calculate total donations
|
||||
$total_donations = 0;
|
||||
foreach ($donations as $donation) {
|
||||
@ -162,56 +115,96 @@ function generate_pdf($case, $donations, $audit_logs)
|
||||
|
||||
// Case Details
|
||||
$pdf->SetFont('Amiri', 'B', 16);
|
||||
$pdf->Cell(0, 10, __('Case Details'), 0, 1, 'R');
|
||||
$pdf->Cell(0, 10, $pdf->shapeText(__('Case Details')), 0, 1, 'R');
|
||||
|
||||
$pdf->SetFont('Amiri', '', 12);
|
||||
$pdf->Cell(40, 10, __('Case ID:'), 0, 0,'R'); $pdf->Cell(0, 10, $case['id'], 0, 1, 'R');
|
||||
$pdf->Cell(40, 10, __('Title:'), 0, 0, 'R'); $pdf->Cell(0, 10, (get_current_lang() === 'ar' ? $case['title_ar'] : $case['title_en']), 0, 1, 'R');
|
||||
$pdf->Cell(40, 10, __('Category:'), 0, 0,'R'); $pdf->Cell(0, 10, (get_current_lang() === 'ar' ? $case['category_name_ar'] : $case['category_name']), 0, 1, 'R');
|
||||
$pdf->Cell(40, 10, __('Goal Amount:'), 0, 0,'R'); $pdf->Cell(0, 10, __('OMR') . ' ' . number_format($case['goal'], 3), 0, 1, 'R');
|
||||
$pdf->Cell(40, 10, __('Raised Amount:'), 0, 0,'R'); $pdf->Cell(0, 10, __('OMR') . ' ' . number_format($case['raised'], 3), 0, 1, 'R');
|
||||
$pdf->Cell(40, 10, __('Total Collected:'), 0, 0,'R'); $pdf->Cell(0, 10, __('OMR') . ' ' . number_format($total_donations, 3), 0, 1, 'R');
|
||||
$pdf->Cell(40, 10, __('Status:'), 0, 0,'R'); $pdf->Cell(0, 10, __($case['status']), 0, 1, 'R');
|
||||
|
||||
$rows = [
|
||||
[__('Case ID:'), $case['id']],
|
||||
[__('Title:'), ($is_ar ? $case['title_ar'] : $case['title_en'])],
|
||||
[__('Category:'), ($is_ar ? $case['category_name_ar'] : $case['category_name'])],
|
||||
// Pass false as 3rd arg to skip re-shaping for formatted money
|
||||
[__('Goal Amount:'), $pdf->formatMoney($case['goal']), false],
|
||||
[__('Raised Amount:'), $pdf->formatMoney($case['raised']), false],
|
||||
[__('Total Collected:'), $pdf->formatMoney($total_donations), false],
|
||||
[__('Status:'), __($case['status'])],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$label = $pdf->shapeText($row[0]);
|
||||
$should_shape = isset($row[2]) ? $row[2] : true;
|
||||
$value = $should_shape ? $pdf->shapeText($row[1]) : $row[1];
|
||||
|
||||
if ($is_ar) {
|
||||
// RTL: Value (Left), Label (Right)
|
||||
$pdf->Cell(150, 10, $value, 0, 0, 'R');
|
||||
$pdf->Cell(40, 10, $label, 0, 1, 'R');
|
||||
} else {
|
||||
// LTR: Label (Left), Value (Right)
|
||||
$pdf->Cell(40, 10, $label, 0, 0, 'R');
|
||||
$pdf->Cell(0, 10, $value, 0, 1, 'R');
|
||||
}
|
||||
}
|
||||
$pdf->Ln(10);
|
||||
|
||||
// Donations
|
||||
$pdf->SetFont('Amiri', 'B', 16);
|
||||
$pdf->Cell(0, 10, __('Donations'), 0, 1, 'R');
|
||||
$pdf->Cell(0, 10, $pdf->shapeText(__('Donations')), 0, 1, 'R');
|
||||
|
||||
$pdf->SetFont('Amiri', 'B', 10);
|
||||
$pdf->Cell(25, 7, __('ID'), 1, 0, 'C');
|
||||
$pdf->Cell(50, 7, __('Donor'), 1, 0, 'C');
|
||||
$pdf->Cell(35, 7, __('Amount'), 1, 0, 'C');
|
||||
$pdf->Cell(40, 7, __('Date'), 1, 0, 'C');
|
||||
$pdf->Cell(30, 7, __('Status'), 1, 1, 'C');
|
||||
|
||||
$headers = [
|
||||
['w' => 25, 't' => __('ID')],
|
||||
['w' => 50, 't' => __('Donor')],
|
||||
['w' => 35, 't' => __('Amount')],
|
||||
['w' => 40, 't' => __('Date')],
|
||||
['w' => 30, 't' => __('Status')]
|
||||
];
|
||||
|
||||
foreach ($headers as $h) {
|
||||
$pdf->Cell($h['w'], 7, $pdf->shapeText($h['t']), 1, 0, 'C');
|
||||
}
|
||||
$pdf->Ln();
|
||||
|
||||
$pdf->SetFont('Amiri', '', 10);
|
||||
if (empty($donations)) {
|
||||
$pdf->Cell(180, 10, __('No donations for this case.'), 1, 1, 'C');
|
||||
$pdf->Cell(180, 10, $pdf->shapeText(__('No donations for this case.')), 1, 1, 'C');
|
||||
} else {
|
||||
foreach ($donations as $donation) {
|
||||
$pdf->Cell(25, 7, $donation['id'], 1, 0, 'C');
|
||||
$pdf->Cell(50, 7, htmlspecialchars($donation['donor_name']), 1, 0, 'R');
|
||||
$pdf->Cell(35, 7, __('OMR') . ' ' . number_format($donation['amount'], 3), 1, 0, 'R');
|
||||
$pdf->Cell(50, 7, $pdf->shapeText($donation['donor_name']), 1, 0, 'R');
|
||||
$pdf->Cell(35, 7, $pdf->formatMoney($donation['amount']), 1, 0, 'R');
|
||||
$pdf->Cell(40, 7, $donation['created_at'], 1, 0, 'C');
|
||||
$pdf->Cell(30, 7, __($donation['status']), 1, 1, 'C');
|
||||
$pdf->Cell(30, 7, $pdf->shapeText(__($donation['status'])), 1, 1, 'C');
|
||||
}
|
||||
}
|
||||
$pdf->Ln(10);
|
||||
|
||||
// History
|
||||
$pdf->SetFont('Amiri', 'B', 16);
|
||||
$pdf->Cell(0, 10, __('Case History'), 0, 1, 'R');
|
||||
$pdf->Cell(0, 10, $pdf->shapeText(__('Case History')), 0, 1, 'R');
|
||||
$pdf->SetFont('Amiri', 'B', 10);
|
||||
$pdf->Cell(20, 7, __('Log ID'), 1, 0, 'C');
|
||||
$pdf->Cell(30, 7, __('User'), 1, 0, 'C');
|
||||
$pdf->Cell(80, 7, __('Action'), 1, 0, 'C');
|
||||
$pdf->Cell(50, 7, __('Timestamp'), 1, 1, 'C');
|
||||
|
||||
$headers_log = [
|
||||
['w' => 20, 't' => __('Log ID')],
|
||||
['w' => 30, 't' => __('User')],
|
||||
['w' => 80, 't' => __('Action')],
|
||||
['w' => 50, 't' => __('Timestamp')]
|
||||
];
|
||||
|
||||
foreach ($headers_log as $h) {
|
||||
$pdf->Cell($h['w'], 7, $pdf->shapeText($h['t']), 1, 0, 'C');
|
||||
}
|
||||
$pdf->Ln();
|
||||
|
||||
$pdf->SetFont('Amiri', '', 9);
|
||||
if (empty($audit_logs)) {
|
||||
$pdf->Cell(180, 10, __('No history for this case.'), 1, 1, 'C');
|
||||
$pdf->Cell(180, 10, $pdf->shapeText(__('No history for this case.')), 1, 1, 'C');
|
||||
} else {
|
||||
foreach ($audit_logs as $log) {
|
||||
$pdf->Cell(20, 7, $log['id'], 1, 0, 'C');
|
||||
$pdf->Cell(30, 7, htmlspecialchars($log['email']), 1, 0, 'C');
|
||||
$pdf->Cell(80, 7, htmlspecialchars($log['action']), 1, 0, 'R');
|
||||
$pdf->Cell(30, 7, $pdf->shapeText($log['email']), 1, 0, 'C');
|
||||
$pdf->Cell(80, 7, $pdf->shapeText($log['action']), 1, 0, 'R');
|
||||
$pdf->Cell(50, 7, $log['created_at'], 1, 1, 'C');
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,6 +190,8 @@ $is_rtl = (get_current_lang() === 'ar');
|
||||
<span class="badge bg-success"><?= __('Active') ?></span>
|
||||
<?php elseif ($case['status'] === 'paused'): ?>
|
||||
<span class="badge bg-warning text-dark"><?= __('Paused') ?></span>
|
||||
<?php elseif ($case['status'] === 'completed'): ?>
|
||||
<span class="badge bg-primary"><?= __('Completed') ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-danger"><?= __('Disabled') ?></span>
|
||||
<?php endif; ?>
|
||||
@ -277,6 +279,7 @@ $is_rtl = (get_current_lang() === 'ar');
|
||||
<option value="active"><?= __('Active') ?></option>
|
||||
<option value="paused"><?= __('Paused') ?></option>
|
||||
<option value="disabled"><?= __('Disabled') ?></option>
|
||||
<option value="completed"><?= __('Completed') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
12
case.php
12
case.php
@ -35,6 +35,7 @@ $texts = [
|
||||
'admin_panel' => 'Admin Panel',
|
||||
'lang_name' => 'العربية',
|
||||
'lang_code' => 'ar',
|
||||
'goal_reached' => 'Goal Reached! Campaign Completed.',
|
||||
],
|
||||
'ar' => [
|
||||
'title' => 'تفاصيل الحالة',
|
||||
@ -63,6 +64,7 @@ $texts = [
|
||||
'admin_panel' => 'لوحة التحكم',
|
||||
'lang_name' => 'English',
|
||||
'lang_code' => 'en',
|
||||
'goal_reached' => 'تم تحقيق الهدف! الحملة مكتملة.',
|
||||
]
|
||||
];
|
||||
|
||||
@ -81,7 +83,7 @@ if ($case_id === 0) {
|
||||
$stmt = $pdo->prepare("SELECT c.*, cat.name_en as cat_name_en, cat.name_ar as cat_name_ar
|
||||
FROM cases c
|
||||
LEFT JOIN categories cat ON c.category_id = cat.id
|
||||
WHERE c.id = :id AND c.status = 'active'");
|
||||
WHERE c.id = :id AND (c.status = 'active' OR c.status = 'completed')");
|
||||
$stmt->execute(['id' => $case_id]);
|
||||
$case = $stmt->fetch();
|
||||
}
|
||||
@ -139,11 +141,18 @@ require_once 'includes/header.php';
|
||||
</div>
|
||||
|
||||
<div class="d-grid mt-4">
|
||||
<?php if ($case['status'] === 'active'): ?>
|
||||
<button class="btn btn-donate py-3 fs-5" data-bs-toggle="modal" data-bs-target="#donateModal"
|
||||
data-case-id="<?= $case['id'] ?>"
|
||||
data-case-title="<?= htmlspecialchars($lang === 'en' ? $case['title_en'] : $case['title_ar']) ?>">
|
||||
<?= $t['donate_now'] ?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-success text-center fw-bold py-3 fs-5 mb-0 rounded-4">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
<?= $t['goal_reached'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -168,6 +177,7 @@ require_once 'includes/header.php';
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<form action="checkout.php" method="POST">
|
||||
<input type="hidden" name="lang" value="<?= $lang ?>">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold"><?= $t['modal_title'] ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
|
||||
@ -17,21 +17,22 @@ $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';
|
||||
// Prefer Arabic name if available, otherwise English or default
|
||||
$orgName = $org['name_ar'] ?? ($org['name_en'] ?? 'المؤسسة');
|
||||
|
||||
// This is a simple HTML certificate that can be printed or saved as PDF by the user
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donation Certificate - <?= htmlspecialchars($orgName) ?></title>
|
||||
<title>شهادة تبرع - <?= 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">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Amiri:wght@400;700&family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f3f4f6; padding: 40px 20px; font-family: 'Inter', sans-serif; }
|
||||
body { background: #f3f4f6; padding: 40px 20px; font-family: 'Cairo', sans-serif; }
|
||||
.certificate-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
@ -49,13 +50,15 @@ $orgName = $org['name_en'] ?? 'Organization';
|
||||
border: 2px solid #059669;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cert-header { font-family: 'Cinzel', serif; font-size: 3rem; color: #111827; margin-bottom: 10px; }
|
||||
.cert-sub { font-size: 1.25rem; color: #059669; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 40px; }
|
||||
.cert-logo { margin-bottom: 20px; }
|
||||
.cert-logo img { max-height: 100px; }
|
||||
.cert-header { font-family: 'Amiri', serif; font-size: 3rem; color: #111827; margin-bottom: 10px; }
|
||||
.cert-sub { font-size: 1.25rem; color: #059669; font-weight: 700; margin-bottom: 40px; }
|
||||
.cert-body { font-size: 1.2rem; color: #4b5563; line-height: 1.8; margin-bottom: 40px; }
|
||||
.cert-name { font-family: 'Cinzel', serif; font-size: 2.5rem; color: #059669; margin: 20px 0; border-bottom: 2px solid #e5e7eb; display: inline-block; padding: 0 40px; }
|
||||
.cert-name { font-family: 'Amiri', serif; font-size: 2.5rem; color: #059669; margin: 20px 0; border-bottom: 2px solid #e5e7eb; display: inline-block; padding: 0 40px; }
|
||||
.cert-footer { margin-top: 60px; display: flex; justify-content: space-between; align-items: flex-end; }
|
||||
.signature { border-top: 1px solid #9ca3af; padding-top: 10px; width: 200px; font-style: italic; color: #6b7280; }
|
||||
.stamp { width: 120px; height: 120px; border: 4px double #059669; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #059669; font-weight: 800; transform: rotate(-15deg); font-size: 0.8rem; margin: 0 auto; }
|
||||
.signature { border-top: 1px solid #9ca3af; padding-top: 10px; width: 200px; font-weight: 600; color: #6b7280; }
|
||||
.stamp { width: 120px; height: 120px; border: 4px double #059669; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #059669; font-weight: 800; transform: rotate(-15deg); font-size: 0.8rem; margin: 0 auto; text-align: center; line-height: 1.4; }
|
||||
|
||||
@media print {
|
||||
body { background: #fff; padding: 0; }
|
||||
@ -64,43 +67,46 @@ $orgName = $org['name_en'] ?? 'Organization';
|
||||
}
|
||||
|
||||
.no-print { margin-top: 30px; text-align: center; }
|
||||
.btn-print { background: #059669; color: #fff; border: none; padding: 12px 30px; border-radius: 9999px; font-weight: 600; cursor: pointer; text-decoration: none; }
|
||||
.btn-print { background: #059669; color: #fff; border: none; padding: 12px 30px; border-radius: 9999px; font-weight: 600; cursor: pointer; text-decoration: none; font-family: 'Cairo', sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="certificate-container">
|
||||
<div class="cert-header">Certificate</div>
|
||||
<div class="cert-sub">of Appreciation</div>
|
||||
<div class="cert-logo">
|
||||
<img src="assets/images/logo_1770967720.jpg" alt="Logo">
|
||||
</div>
|
||||
<div class="cert-header">شهادة تقدير</div>
|
||||
<div class="cert-sub">شكر وعرفان</div>
|
||||
|
||||
<div class="cert-body">
|
||||
This certificate is proudly presented to
|
||||
يسرنا تقديم هذه الشهادة إلى
|
||||
<br>
|
||||
<div class="cert-name"><?= htmlspecialchars($don['donor_name'] ?: 'Valued Donor') ?></div>
|
||||
<div class="cert-name"><?= htmlspecialchars($don['donor_name'] ?: 'فاعل خير') ?></div>
|
||||
<br>
|
||||
In recognition of their generous donation of <strong>OMR <?= number_format($don['amount'], 3) ?></strong>
|
||||
تقديراً لتبرعكم الكريم بمبلغ <strong><?= number_format($don['amount'], 3) ?> ر.ع.</strong>
|
||||
<br>
|
||||
towards the cause: <strong><?= htmlspecialchars($don['case_title']) ?></strong>
|
||||
لدعم حالة: <strong><?= htmlspecialchars($don['case_title_ar'] ?: $don['case_title']) ?></strong>
|
||||
</div>
|
||||
|
||||
<div class="stamp">
|
||||
OFFICIAL<br>SEAL<br><?= strtoupper(htmlspecialchars($orgName)) ?>
|
||||
الختم<br>الرسمي<br><?= htmlspecialchars($orgName) ?>
|
||||
</div>
|
||||
|
||||
<div class="cert-footer">
|
||||
<div class="signature">
|
||||
Date: <?= date('M j, Y', strtotime($don['created_at'])) ?>
|
||||
التاريخ: <?= date('Y/m/d', strtotime($don['created_at'])) ?>
|
||||
</div>
|
||||
<div class="signature">
|
||||
Authorized Signature<br>
|
||||
التوقيع المعتمد<br>
|
||||
<?= htmlspecialchars($orgName) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-print">
|
||||
<button onclick="window.print()" class="btn-print">Print or Save as PDF</button>
|
||||
<a href="index.php" style="margin-left: 15px; color: #6b7280; text-decoration: none;">Back to Home</a>
|
||||
<button onclick="window.print()" class="btn-print">طباعة أو حفظ كملف PDF</button>
|
||||
<a href="index.php" style="margin-right: 15px; color: #6b7280; text-decoration: none;">العودة للرئيسية</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
18
checkout.php
18
checkout.php
@ -12,6 +12,7 @@ $amount = (float)$_POST['amount'];
|
||||
$donor_name = $_POST['donor_name'] ?? 'Anonymous';
|
||||
$donor_email = $_POST['donor_email'] ?? '';
|
||||
$donor_phone = $_POST['donor_phone'] ?? '';
|
||||
$lang = $_POST['lang'] ?? 'ar';
|
||||
|
||||
// Gift fields
|
||||
$is_gift = (int)($_POST['is_gift'] ?? 0);
|
||||
@ -30,8 +31,8 @@ $stmt = $pdo->prepare("SELECT * FROM cases WHERE id = ?");
|
||||
$stmt->execute([$case_id]);
|
||||
$case = $stmt->fetch();
|
||||
|
||||
if (!$case) {
|
||||
die("Case not found");
|
||||
if (!$case || $case['status'] !== 'active') {
|
||||
die("Case not found or not active");
|
||||
}
|
||||
|
||||
// Create pending donation
|
||||
@ -52,8 +53,8 @@ $payload = [
|
||||
'quantity' => 1
|
||||
]
|
||||
],
|
||||
'success_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/success.php?session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/index.php',
|
||||
'success_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/success.php?session_id={CHECKOUT_SESSION_ID}&lang=' . $lang,
|
||||
'cancel_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/index.php?lang=' . $lang,
|
||||
'metadata' => [
|
||||
'donation_id' => $donation_id,
|
||||
'case_id' => $case_id
|
||||
@ -65,7 +66,7 @@ if (THAWANI_SECRET_KEY === 'rRQ26GcsZ60u9Y9v9876543210' || empty(THAWANI_SECRET_
|
||||
// Simulation Mode
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="<?= htmlspecialchars($lang) ?>">
|
||||
<head><title>Simulating Thawani Checkout</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"></head>
|
||||
<body class="bg-light p-5 text-center">
|
||||
<div class="card mx-auto" style="max-width: 500px;">
|
||||
@ -81,9 +82,10 @@ if (THAWANI_SECRET_KEY === 'rRQ26GcsZ60u9Y9v9876543210' || empty(THAWANI_SECRET_
|
||||
<form action="success.php" method="GET">
|
||||
<input type="hidden" name="session_id" value="mock_session_<?= time() ?>">
|
||||
<input type="hidden" name="donation_id" value="<?= $donation_id ?>">
|
||||
<input type="hidden" name="lang" value="<?= htmlspecialchars($lang) ?>">
|
||||
<button type="submit" class="btn btn-success w-100">Simulate Success Payment</button>
|
||||
</form>
|
||||
<a href="index.php" class="btn btn-link mt-2">Cancel</a>
|
||||
<a href="index.php?lang=<?= htmlspecialchars($lang) ?>" class="btn btn-link mt-2">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@ -129,5 +131,5 @@ if (isset($data['success']) && $data['success'] === true && isset($data['data'][
|
||||
echo "<pre>";
|
||||
print_r($data);
|
||||
echo "</pre>";
|
||||
echo "<a href='index.php'>Go Back</a>";
|
||||
}
|
||||
echo "<a href='index.php?lang=" . htmlspecialchars($lang) . "'>Go Back</a>";
|
||||
}
|
||||
|
||||
174
includes/I18N/Arabic.php
Normal file
174
includes/I18N/Arabic.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace I18N;
|
||||
|
||||
class Arabic {
|
||||
private $presentation_forms = [
|
||||
'0621' => ['FE80', 'FE80', 'FE80', 'FE80'], // Hamza
|
||||
'0622' => ['FE81', 'FE82', 'FE81', 'FE82'], // Alef with Madda
|
||||
'0623' => ['FE83', 'FE84', 'FE83', 'FE84'], // Alef with Hamza Above
|
||||
'0624' => ['FE85', 'FE86', 'FE85', 'FE86'], // Waw with Hamza Above
|
||||
'0625' => ['FE87', 'FE88', 'FE87', 'FE88'], // Alef with Hamza Below
|
||||
'0626' => ['FE89', 'FE8A', 'FE8B', 'FE8C'], // Yeh with Hamza Above
|
||||
'0627' => ['FE8D', 'FE8E', 'FE8D', 'FE8E'], // Alef
|
||||
'0628' => ['FE8F', 'FE90', 'FE91', 'FE92'], // Beh
|
||||
'0629' => ['FE93', 'FE94', 'FE93', 'FE94'], // Teh Marbuta
|
||||
'062A' => ['FE95', 'FE96', 'FE97', 'FE98'], // Teh
|
||||
'062B' => ['FE99', 'FE9A', 'FE9B', 'FE9C'], // Theh
|
||||
'062C' => ['FE9D', 'FE9E', 'FE9F', 'FEA0'], // Jeem
|
||||
'062D' => ['FEA1', 'FEA2', 'FEA3', 'FEA4'], // Hah
|
||||
'062E' => ['FEA5', 'FEA6', 'FEA7', 'FEA8'], // Khah
|
||||
'062F' => ['FEA9', 'FEAA', 'FEA9', 'FEAA'], // Dal
|
||||
'0630' => ['FEAB', 'FEAC', 'FEAB', 'FEAC'], // Thal
|
||||
'0631' => ['FEAD', 'FEAE', 'FEAD', 'FEAE'], // Reh
|
||||
'0632' => ['FEAF', 'FEB0', 'FEAF', 'FEB0'], // Zain
|
||||
'0633' => ['FEB1', 'FEB2', 'FEB3', 'FEB4'], // Seen
|
||||
'0634' => ['FEB5', 'FEB6', 'FEB7', 'FEB8'], // Sheen
|
||||
'0635' => ['FEB9', 'FEBA', 'FEBB', 'FEBC'], // Sad
|
||||
'0636' => ['FEBD', 'FEBE', 'FEBF', 'FEC0'], // Dad
|
||||
'0637' => ['FEC1', 'FEC2', 'FEC3', 'FEC4'], // Tah
|
||||
'0638' => ['FEC5', 'FEC6', 'FEC7', 'FEC8'], // Zah
|
||||
'0639' => ['FEC9', 'FECA', 'FECB', 'FECC'], // Ain
|
||||
'063A' => ['FECD', 'FECE', 'FECF', 'FED0'], // Ghain
|
||||
'0640' => ['0640', '0640', '0640', '0640'], // Tatweel
|
||||
'0641' => ['FED1', 'FED2', 'FED3', 'FED4'], // Feh
|
||||
'0642' => ['FED5', 'FED6', 'FED7', 'FED8'], // Qaf
|
||||
'0643' => ['FED9', 'FEDA', 'FEDB', 'FEDC'], // Kaf
|
||||
'0644' => ['FEDD', 'FEDE', 'FEDF', 'FEE0'], // Lam
|
||||
'0645' => ['FEE1', 'FEE2', 'FEE3', 'FEE4'], // Meem
|
||||
'0646' => ['FEE5', 'FEE6', 'FEE7', 'FEE8'], // Noon
|
||||
'0647' => ['FEE9', 'FEEA', 'FEEB', 'FEEC'], // Heh
|
||||
'0648' => ['FEED', 'FEEE', 'FEED', 'FEEE'], // Waw
|
||||
'0649' => ['FEEF', 'FEF0', 'FEEF', 'FEF0'], // Alef Maksura
|
||||
'064A' => ['FEF1', 'FEF2', 'FEF3', 'FEF4'], // Yeh
|
||||
];
|
||||
|
||||
private $connects_before = [
|
||||
'0626','0628','0629','062A','062B','062C','062D','062E',
|
||||
'0633','0634','0635','0636','0637','0638','0639','063A','0640','0641','0642',
|
||||
'0643','0644','0645','0646','0647','064A'
|
||||
];
|
||||
|
||||
// Letters that prevent the *next* letter from connecting to *this* one (Right-joining only)
|
||||
private $disconnects_after = [
|
||||
'0621','0622','0623','0624','0625','0627','062F','0630','0631','0632','0648','0649'
|
||||
];
|
||||
|
||||
public function utf8Glyphs($str) {
|
||||
$hex = $this->utf8ToHexArray($str);
|
||||
$res = [];
|
||||
$len = count($hex);
|
||||
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$current = $hex[$i];
|
||||
|
||||
// Skip non-Arabic chars (or spaces) in shaping logic
|
||||
if (!isset($this->presentation_forms[$current])) {
|
||||
$res[] = $current;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for Lam-Alef Ligature
|
||||
if ($current == '0644' && $i < $len - 1) {
|
||||
$next = $hex[$i+1];
|
||||
$ligature = null;
|
||||
switch($next) {
|
||||
case '0622': $ligature = ['FEF5', 'FEF6', 'FEF5', 'FEF6']; break; // Lam-Alef Madda
|
||||
case '0623': $ligature = ['FEF7', 'FEF8', 'FEF7', 'FEF8']; break; // Lam-Alef Hamza Above
|
||||
case '0625': $ligature = ['FEF9', 'FEFA', 'FEF9', 'FEFA']; break; // Lam-Alef Hamza Below
|
||||
case '0627': $ligature = ['FEFB', 'FEFC', 'FEFB', 'FEFC']; break; // Lam-Alef
|
||||
}
|
||||
|
||||
if ($ligature) {
|
||||
// Determine connection from previous
|
||||
$prev = $i > 0 ? $hex[$i-1] : null;
|
||||
$connect_prev = $prev && $this->canConnect($prev);
|
||||
|
||||
if ($connect_prev) {
|
||||
$res[] = $ligature[1]; // Final form of ligature
|
||||
} else {
|
||||
$res[] = $ligature[0]; // Isolated form of ligature
|
||||
}
|
||||
$i++; // Skip the Alef
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Logic
|
||||
$prev = $i > 0 ? $hex[$i-1] : null;
|
||||
$next = $i < $len - 1 ? $hex[$i+1] : null;
|
||||
|
||||
$connect_prev = $prev && $this->canConnect($prev);
|
||||
|
||||
// For next connection, we check if current can connect left AND next can connect right
|
||||
$connect_next = $next && $this->canConnectLeft($current) && $this->isArabic($next);
|
||||
|
||||
// Refined Logic
|
||||
if ($connect_prev && $connect_next) {
|
||||
$form = 3; // Medial
|
||||
} elseif ($connect_prev) {
|
||||
$form = 1; // Final
|
||||
} elseif ($connect_next) {
|
||||
$form = 2; // Initial
|
||||
} else {
|
||||
$form = 0; // Isolated
|
||||
}
|
||||
|
||||
$res[] = $this->presentation_forms[$current][$form];
|
||||
}
|
||||
|
||||
$s = $this->hexArrayToUtf8($res);
|
||||
return $this->mb_strrev($s);
|
||||
}
|
||||
|
||||
private function canConnect($hex) {
|
||||
// Can $hex connect to the *next* letter?
|
||||
// True if it is Arabic and NOT in disconnects_after
|
||||
return isset($this->presentation_forms[$hex]) && !in_array($hex, $this->disconnects_after) && $hex != '0621'; // Hamza doesn't connect
|
||||
}
|
||||
|
||||
private function canConnectLeft($hex) {
|
||||
// Can $hex connect to the *next* letter? (Same as above)
|
||||
return $this->canConnect($hex);
|
||||
}
|
||||
|
||||
private function isArabic($hex) {
|
||||
return isset($this->presentation_forms[$hex]);
|
||||
}
|
||||
|
||||
private function utf8ToHexArray($str) {
|
||||
$out = [];
|
||||
$len = mb_strlen($str, 'UTF-8');
|
||||
for($i=0; $i<$len; $i++) {
|
||||
$c = mb_substr($str, $i, 1, 'UTF-8');
|
||||
$val = $this->uniord($c);
|
||||
$out[] = sprintf("%04X", $val);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function hexArrayToUtf8($arr) {
|
||||
$str = '';
|
||||
foreach ($arr as $hex) {
|
||||
$val = hexdec($hex);
|
||||
$str .= $this->unichr($val);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function uniord($u) {
|
||||
if (strlen($u) === 1) return ord($u);
|
||||
$k = mb_convert_encoding($u, 'UCS-2LE', 'UTF-8');
|
||||
$k1 = ord(substr($k, 0, 1));
|
||||
$k2 = ord(substr($k, 1, 1));
|
||||
return $k2 * 256 + $k1;
|
||||
}
|
||||
|
||||
private function unichr($u) {
|
||||
return mb_convert_encoding(pack("n", $u), 'UTF-8', 'UCS-2BE');
|
||||
}
|
||||
|
||||
private function mb_strrev($str){
|
||||
preg_match_all('/./us', $str, $ar);
|
||||
return join('', array_reverse($ar[0]));
|
||||
}
|
||||
}
|
||||
1
includes/I18N/Arabic/Glyphs.php
Normal file
1
includes/I18N/Arabic/Glyphs.php
Normal file
@ -0,0 +1 @@
|
||||
404: Not Found
|
||||
BIN
includes/fpdf/font/unifont/amiri.cw.dat
Normal file
BIN
includes/fpdf/font/unifont/amiri.cw.dat
Normal file
Binary file not shown.
120
includes/fpdf/font/unifont/amiri.cw127.php
Normal file
120
includes/fpdf/font/unifont/amiri.cw127.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
$rangeid=58;
|
||||
$prevcid=126;
|
||||
$prevwidth=556;
|
||||
$interval=false;
|
||||
$range=array (
|
||||
32 =>
|
||||
array (
|
||||
0 => 292,
|
||||
1 => 235,
|
||||
2 => 273,
|
||||
3 => 468,
|
||||
4 => 486,
|
||||
5 => 651,
|
||||
6 => 666,
|
||||
7 => 156,
|
||||
),
|
||||
40 =>
|
||||
array (
|
||||
0 => 458,
|
||||
1 => 458,
|
||||
'interval' => true,
|
||||
),
|
||||
42 =>
|
||||
array (
|
||||
0 => 432,
|
||||
1 => 529,
|
||||
2 => 196,
|
||||
3 => 368,
|
||||
4 => 317,
|
||||
5 => 344,
|
||||
),
|
||||
48 =>
|
||||
array (
|
||||
0 => 532,
|
||||
1 => 532,
|
||||
'interval' => true,
|
||||
2 => 532,
|
||||
3 => 532,
|
||||
4 => 532,
|
||||
5 => 532,
|
||||
6 => 532,
|
||||
7 => 532,
|
||||
8 => 532,
|
||||
9 => 532,
|
||||
),
|
||||
58 =>
|
||||
array (
|
||||
0 => 220,
|
||||
1 => 226,
|
||||
2 => 480,
|
||||
3 => 551,
|
||||
4 => 481,
|
||||
5 => 347,
|
||||
6 => 730,
|
||||
7 => 612,
|
||||
8 => 582,
|
||||
9 => 658,
|
||||
10 => 693,
|
||||
11 => 573,
|
||||
12 => 522,
|
||||
13 => 703,
|
||||
14 => 708,
|
||||
15 => 316,
|
||||
16 => 305,
|
||||
17 => 641,
|
||||
18 => 550,
|
||||
19 => 885,
|
||||
20 => 757,
|
||||
21 => 730,
|
||||
22 => 535,
|
||||
23 => 729,
|
||||
24 => 606,
|
||||
25 => 490,
|
||||
26 => 611,
|
||||
27 => 706,
|
||||
28 => 623,
|
||||
29 => 878,
|
||||
30 => 652,
|
||||
31 => 583,
|
||||
32 => 618,
|
||||
33 => 361,
|
||||
34 => 344,
|
||||
35 => 361,
|
||||
36 => 380,
|
||||
37 => 493,
|
||||
38 => 333,
|
||||
39 => 420,
|
||||
40 => 486,
|
||||
41 => 413,
|
||||
42 => 502,
|
||||
43 => 419,
|
||||
44 => 300,
|
||||
45 => 455,
|
||||
46 => 503,
|
||||
47 => 263,
|
||||
48 => 233,
|
||||
49 => 505,
|
||||
50 => 249,
|
||||
51 => 771,
|
||||
52 => 519,
|
||||
53 => 497,
|
||||
54 => 498,
|
||||
55 => 485,
|
||||
56 => 374,
|
||||
57 => 360,
|
||||
58 => 303,
|
||||
59 => 502,
|
||||
60 => 461,
|
||||
61 => 704,
|
||||
62 => 464,
|
||||
63 => 458,
|
||||
64 => 436,
|
||||
65 => 397,
|
||||
66 => 200,
|
||||
67 => 397,
|
||||
68 => 556,
|
||||
),
|
||||
);
|
||||
?>
|
||||
19
includes/fpdf/font/unifont/amiri.mtx.php
Normal file
19
includes/fpdf/font/unifont/amiri.mtx.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
$name='Amiri-Regular';
|
||||
$type='TTF';
|
||||
$desc=array (
|
||||
'Ascent' => 1124.0,
|
||||
'Descent' => -634.0,
|
||||
'CapHeight' => 646.0,
|
||||
'Flags' => 4,
|
||||
'FontBBox' => '[-438 -900 11467 1815]',
|
||||
'ItalicAngle' => 0.0,
|
||||
'StemV' => 87.0,
|
||||
'MissingWidth' => 364.0,
|
||||
);
|
||||
$up=-512;
|
||||
$ut=50;
|
||||
$ttffile='/home/ubuntu/executor/workspace/includes/fpdf/font/unifont/amiri.ttf';
|
||||
$originalsize=431116;
|
||||
$fontkey='amiri';
|
||||
?>
|
||||
1090
includes/fpdf/font/unifont/ttfonts.php
Normal file
1090
includes/fpdf/font/unifont/ttfonts.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,447 +1 @@
|
||||
<?php
|
||||
/*******************************************************************************
|
||||
* Utility to generate font definition files *
|
||||
* *
|
||||
* Version: 1.31 *
|
||||
* Date: 2019-12-07 *
|
||||
* Author: Olivier PLATHEY *
|
||||
*******************************************************************************/
|
||||
|
||||
require('ttfparser.php');
|
||||
|
||||
function Message($txt, $severity='')
|
||||
{
|
||||
if(PHP_SAPI=='cli')
|
||||
{
|
||||
if($severity)
|
||||
echo "$severity: ";
|
||||
echo "$txt\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
if($severity)
|
||||
echo "<b>$severity</b>: ";
|
||||
echo "$txt<br>";
|
||||
}
|
||||
}
|
||||
|
||||
function Notice($txt)
|
||||
{
|
||||
Message($txt, 'Notice');
|
||||
}
|
||||
|
||||
function Warning($txt)
|
||||
{
|
||||
Message($txt, 'Warning');
|
||||
}
|
||||
|
||||
function Error($txt)
|
||||
{
|
||||
Message($txt, 'Error');
|
||||
exit;
|
||||
}
|
||||
|
||||
function LoadMap($enc)
|
||||
{
|
||||
$file = dirname(__FILE__).'/'.strtolower($enc).'.map';
|
||||
$a = file($file);
|
||||
if(empty($a))
|
||||
Error('Encoding not found: '.$enc);
|
||||
$map = array_fill(0, 256, array('uv'=>-1, 'name'=>'.notdef'));
|
||||
foreach($a as $line)
|
||||
{
|
||||
$e = explode(' ', rtrim($line));
|
||||
$c = hexdec(substr($e[0],1));
|
||||
$uv = hexdec(substr($e[1],2));
|
||||
$name = $e[2];
|
||||
$map[$c] = array('uv'=>$uv, 'name'=>$name);
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
function GetInfoFromTrueType($file, $embed, $subset, $map)
|
||||
{
|
||||
// Return information from a TrueType font
|
||||
try
|
||||
{
|
||||
$ttf = new TTFParser($file);
|
||||
$ttf->Parse();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Error($e->getMessage());
|
||||
}
|
||||
if($embed)
|
||||
{
|
||||
if(!$ttf->embeddable)
|
||||
Error('Font license does not allow embedding');
|
||||
if($subset)
|
||||
{
|
||||
$chars = array();
|
||||
foreach($map as $v)
|
||||
{
|
||||
if($v['name']!='.notdef')
|
||||
$chars[] = $v['uv'];
|
||||
}
|
||||
$ttf->Subset($chars);
|
||||
$info['Data'] = $ttf->Build();
|
||||
}
|
||||
else
|
||||
$info['Data'] = file_get_contents($file);
|
||||
$info['OriginalSize'] = strlen($info['Data']);
|
||||
}
|
||||
$k = 1000/$ttf->unitsPerEm;
|
||||
$info['FontName'] = $ttf->postScriptName;
|
||||
$info['Bold'] = $ttf->bold;
|
||||
$info['ItalicAngle'] = $ttf->italicAngle;
|
||||
$info['IsFixedPitch'] = $ttf->isFixedPitch;
|
||||
$info['Ascender'] = round($k*$ttf->typoAscender);
|
||||
$info['Descender'] = round($k*$ttf->typoDescender);
|
||||
$info['UnderlineThickness'] = round($k*$ttf->underlineThickness);
|
||||
$info['UnderlinePosition'] = round($k*$ttf->underlinePosition);
|
||||
$info['FontBBox'] = array(round($k*$ttf->xMin), round($k*$ttf->yMin), round($k*$ttf->xMax), round($k*$ttf->yMax));
|
||||
$info['CapHeight'] = round($k*$ttf->capHeight);
|
||||
$info['MissingWidth'] = round($k*$ttf->glyphs[0]['w']);
|
||||
$widths = array_fill(0, 256, $info['MissingWidth']);
|
||||
foreach($map as $c=>$v)
|
||||
{
|
||||
if($v['name']!='.notdef')
|
||||
{
|
||||
if(isset($ttf->chars[$v['uv']]))
|
||||
{
|
||||
$id = $ttf->chars[$v['uv']];
|
||||
$w = $ttf->glyphs[$id]['w'];
|
||||
$widths[$c] = round($k*$w);
|
||||
}
|
||||
else
|
||||
Warning('Character '.$v['name'].' is missing');
|
||||
}
|
||||
}
|
||||
$info['Widths'] = $widths;
|
||||
return $info;
|
||||
}
|
||||
|
||||
function GetInfoFromType1($file, $embed, $map)
|
||||
{
|
||||
// Return information from a Type1 font
|
||||
if($embed)
|
||||
{
|
||||
$f = fopen($file, 'rb');
|
||||
if(!$f)
|
||||
Error('Can\'t open font file');
|
||||
// Read first segment
|
||||
$a = unpack('Cmarker/Ctype/Vsize', fread($f,6));
|
||||
if($a['marker']!=128)
|
||||
Error('Font file is not a valid binary Type1');
|
||||
$size1 = $a['size'];
|
||||
$data = fread($f, $size1);
|
||||
// Read second segment
|
||||
$a = unpack('Cmarker/Ctype/Vsize', fread($f,6));
|
||||
if($a['marker']!=128)
|
||||
Error('Font file is not a valid binary Type1');
|
||||
$size2 = $a['size'];
|
||||
$data .= fread($f, $size2);
|
||||
fclose($f);
|
||||
$info['Data'] = $data;
|
||||
$info['Size1'] = $size1;
|
||||
$info['Size2'] = $size2;
|
||||
}
|
||||
|
||||
$afm = substr($file, 0, -3).'afm';
|
||||
if(!file_exists($afm))
|
||||
Error('AFM font file not found: '.$afm);
|
||||
$a = file($afm);
|
||||
if(empty($a))
|
||||
Error('AFM file empty or not readable');
|
||||
foreach($a as $line)
|
||||
{
|
||||
$e = explode(' ', rtrim($line));
|
||||
if(count($e)<2)
|
||||
continue;
|
||||
$entry = $e[0];
|
||||
if($entry=='C')
|
||||
{
|
||||
$w = $e[4];
|
||||
$name = $e[7];
|
||||
$cw[$name] = $w;
|
||||
}
|
||||
elseif($entry=='FontName')
|
||||
$info['FontName'] = $e[1];
|
||||
elseif($entry=='Weight')
|
||||
$info['Weight'] = $e[1];
|
||||
elseif($entry=='ItalicAngle')
|
||||
$info['ItalicAngle'] = (int)$e[1];
|
||||
elseif($entry=='Ascender')
|
||||
$info['Ascender'] = (int)$e[1];
|
||||
elseif($entry=='Descender')
|
||||
$info['Descender'] = (int)$e[1];
|
||||
elseif($entry=='UnderlineThickness')
|
||||
$info['UnderlineThickness'] = (int)$e[1];
|
||||
elseif($entry=='UnderlinePosition')
|
||||
$info['UnderlinePosition'] = (int)$e[1];
|
||||
elseif($entry=='IsFixedPitch')
|
||||
$info['IsFixedPitch'] = ($e[1]=='true');
|
||||
elseif($entry=='FontBBox')
|
||||
$info['FontBBox'] = array((int)$e[1], (int)$e[2], (int)$e[3], (int)$e[4]);
|
||||
elseif($entry=='CapHeight')
|
||||
$info['CapHeight'] = (int)$e[1];
|
||||
elseif($entry=='StdVW')
|
||||
$info['StdVW'] = (int)$e[1];
|
||||
}
|
||||
|
||||
if(!isset($info['FontName']))
|
||||
Error('FontName missing in AFM file');
|
||||
if(!isset($info['Ascender']))
|
||||
$info['Ascender'] = $info['FontBBox'][3];
|
||||
if(!isset($info['Descender']))
|
||||
$info['Descender'] = $info['FontBBox'][1];
|
||||
$info['Bold'] = isset($info['Weight']) && preg_match('/bold|black/i', $info['Weight']);
|
||||
if(isset($cw['.notdef']))
|
||||
$info['MissingWidth'] = $cw['.notdef'];
|
||||
else
|
||||
$info['MissingWidth'] = 0;
|
||||
$widths = array_fill(0, 256, $info['MissingWidth']);
|
||||
foreach($map as $c=>$v)
|
||||
{
|
||||
if($v['name']!='.notdef')
|
||||
{
|
||||
if(isset($cw[$v['name']]))
|
||||
$widths[$c] = $cw[$v['name']];
|
||||
else
|
||||
Warning('Character '.$v['name'].' is missing');
|
||||
}
|
||||
}
|
||||
$info['Widths'] = $widths;
|
||||
return $info;
|
||||
}
|
||||
|
||||
function MakeFontDescriptor($info)
|
||||
{
|
||||
// Ascent
|
||||
$fd = "array('Ascent'=>".$info['Ascender'];
|
||||
// Descent
|
||||
$fd .= ",'Descent'=>".$info['Descender'];
|
||||
// CapHeight
|
||||
if(!empty($info['CapHeight']))
|
||||
$fd .= ",'CapHeight'=>".$info['CapHeight'];
|
||||
else
|
||||
$fd .= ",'CapHeight'=>".$info['Ascender'];
|
||||
// Flags
|
||||
$flags = 0;
|
||||
if($info['IsFixedPitch'])
|
||||
$flags += 1<<0;
|
||||
$flags += 1<<5;
|
||||
if($info['ItalicAngle']!=0)
|
||||
$flags += 1<<6;
|
||||
$fd .= ",'Flags'=>".$flags;
|
||||
// FontBBox
|
||||
$fbb = $info['FontBBox'];
|
||||
$fd .= ",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'";
|
||||
// ItalicAngle
|
||||
$fd .= ",'ItalicAngle'=>".$info['ItalicAngle'];
|
||||
// StemV
|
||||
if(isset($info['StdVW']))
|
||||
$stemv = $info['StdVW'];
|
||||
elseif($info['Bold'])
|
||||
$stemv = 120;
|
||||
else
|
||||
$stemv = 70;
|
||||
$fd .= ",'StemV'=>".$stemv;
|
||||
// MissingWidth
|
||||
$fd .= ",'MissingWidth'=>".$info['MissingWidth'].')';
|
||||
return $fd;
|
||||
}
|
||||
|
||||
function MakeWidthArray($widths)
|
||||
{
|
||||
$s = "array(\n\t";
|
||||
for($c=0;$c<=255;$c++)
|
||||
{
|
||||
if(chr($c)=="'")
|
||||
$s .= "'\\''";
|
||||
elseif(chr($c)=="\\")
|
||||
$s .= "'\\\\'";
|
||||
elseif($c>=32 && $c<=126)
|
||||
$s .= "'".chr($c)."'";
|
||||
else
|
||||
$s .= "chr($c)";
|
||||
$s .= '=>'.$widths[$c];
|
||||
if($c<255)
|
||||
$s .= ',';
|
||||
if(($c+1)%22==0)
|
||||
$s .= "\n\t";
|
||||
}
|
||||
$s .= ')';
|
||||
return $s;
|
||||
}
|
||||
|
||||
function MakeFontEncoding($map)
|
||||
{
|
||||
// Build differences from reference encoding
|
||||
$ref = LoadMap('cp1252');
|
||||
$s = '';
|
||||
$last = 0;
|
||||
for($c=32;$c<=255;$c++)
|
||||
{
|
||||
if($map[$c]['name']!=$ref[$c]['name'])
|
||||
{
|
||||
if($c!=$last+1)
|
||||
$s .= $c.' ';
|
||||
$last = $c;
|
||||
$s .= '/'.$map[$c]['name'].' ';
|
||||
}
|
||||
}
|
||||
return rtrim($s);
|
||||
}
|
||||
|
||||
function MakeUnicodeArray($map)
|
||||
{
|
||||
// Build mapping to Unicode values
|
||||
$ranges = array();
|
||||
foreach($map as $c=>$v)
|
||||
{
|
||||
$uv = $v['uv'];
|
||||
if($uv!=-1)
|
||||
{
|
||||
if(isset($range))
|
||||
{
|
||||
if($c==$range[1]+1 && $uv==$range[3]+1)
|
||||
{
|
||||
$range[1]++;
|
||||
$range[3]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$ranges[] = $range;
|
||||
$range = array($c, $c, $uv, $uv);
|
||||
}
|
||||
}
|
||||
else
|
||||
$range = array($c, $c, $uv, $uv);
|
||||
}
|
||||
}
|
||||
$ranges[] = $range;
|
||||
|
||||
foreach($ranges as $range)
|
||||
{
|
||||
if(isset($s))
|
||||
$s .= ',';
|
||||
else
|
||||
$s = 'array(';
|
||||
$s .= $range[0].'=>';
|
||||
$nb = $range[1]-$range[0]+1;
|
||||
if($nb>1)
|
||||
$s .= 'array('.$range[2].','.$nb.')';
|
||||
else
|
||||
$s .= $range[2];
|
||||
}
|
||||
$s .= ')';
|
||||
return $s;
|
||||
}
|
||||
|
||||
function SaveToFile($file, $s, $mode)
|
||||
{
|
||||
$f = fopen($file, 'w'.$mode);
|
||||
if(!$f)
|
||||
Error('Can\'t write to file '.$file);
|
||||
fwrite($f, $s);
|
||||
fclose($f);
|
||||
}
|
||||
|
||||
function MakeDefinitionFile($file, $type, $enc, $embed, $subset, $map, $info)
|
||||
{
|
||||
$s = "<?php\n";
|
||||
$s .= '$type = \''.$type."';\n";
|
||||
$s .= '$name = \''.$info['FontName']."';\n";
|
||||
$s .= '$desc = '.MakeFontDescriptor($info).";\n";
|
||||
$s .= '$up = '.$info['UnderlinePosition'].";\n";
|
||||
$s .= '$ut = '.$info['UnderlineThickness'].";\n";
|
||||
$s .= '$cw = '.MakeWidthArray($info['Widths']).";\n";
|
||||
$s .= '$enc = \''.$enc."';\n";
|
||||
$diff = MakeFontEncoding($map);
|
||||
if($diff)
|
||||
$s .= '$diff = \''.$diff."';\n";
|
||||
$s .= '$uv = '.MakeUnicodeArray($map).";\n";
|
||||
if($embed)
|
||||
{
|
||||
$s .= '$file = \''.$info['File']."';\n";
|
||||
if($type=='Type1')
|
||||
{
|
||||
$s .= '$size1 = '.$info['Size1'].";\n";
|
||||
$s .= '$size2 = '.$info['Size2'].";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$s .= '$originalsize = '.$info['OriginalSize'].";\n";
|
||||
if($subset)
|
||||
$s .= "\$subsetted = true;\n";
|
||||
}
|
||||
}
|
||||
$s .= "?>\n";
|
||||
SaveToFile($file, $s, 't');
|
||||
}
|
||||
|
||||
function MakeFont($fontfile, $enc='cp1252', $embed=true, $subset=true)
|
||||
{
|
||||
// Generate a font definition file
|
||||
if(!file_exists($fontfile))
|
||||
Error('Font file not found: '.$fontfile);
|
||||
$ext = strtolower(substr($fontfile,-3));
|
||||
if($ext=='ttf' || $ext=='otf')
|
||||
$type = 'TrueType';
|
||||
elseif($ext=='pfb')
|
||||
$type = 'Type1';
|
||||
else
|
||||
Error('Unrecognized font file extension: '.$ext);
|
||||
|
||||
$map = LoadMap($enc);
|
||||
|
||||
if($type=='TrueType')
|
||||
$info = GetInfoFromTrueType($fontfile, $embed, $subset, $map);
|
||||
else
|
||||
$info = GetInfoFromType1($fontfile, $embed, $map);
|
||||
|
||||
$basename = substr(basename($fontfile), 0, -4);
|
||||
if($embed)
|
||||
{
|
||||
if(function_exists('gzcompress'))
|
||||
{
|
||||
$file = $basename.'.z';
|
||||
SaveToFile($file, gzcompress($info['Data']), 'b');
|
||||
$info['File'] = $file;
|
||||
Message('Font file compressed: '.$file);
|
||||
}
|
||||
else
|
||||
{
|
||||
$info['File'] = basename($fontfile);
|
||||
$subset = false;
|
||||
Notice('Font file could not be compressed (zlib extension not available)');
|
||||
}
|
||||
}
|
||||
|
||||
MakeDefinitionFile($basename.'.php', $type, $enc, $embed, $subset, $map, $info);
|
||||
Message('Font definition file generated: '.$basename.'.php');
|
||||
}
|
||||
|
||||
if(PHP_SAPI=='cli')
|
||||
{
|
||||
// Command-line interface
|
||||
ini_set('log_errors', '0');
|
||||
if($argc==1)
|
||||
die("Usage: php makefont.php fontfile [encoding] [embed] [subset]\n");
|
||||
$fontfile = $argv[1];
|
||||
if($argc>=3)
|
||||
$enc = $argv[2];
|
||||
else
|
||||
$enc = 'cp1252';
|
||||
if($argc>=4)
|
||||
$embed = ($argv[3]=='true' || $argv[3]=='1');
|
||||
else
|
||||
$embed = true;
|
||||
if($argc>=5)
|
||||
$subset = ($argv[4]=='true' || $argv[4]=='1');
|
||||
else
|
||||
$subset = true;
|
||||
MakeFont($fontfile, $enc, $embed, $subset);
|
||||
}
|
||||
?>
|
||||
404: Not Found
|
||||
@ -1,714 +1 @@
|
||||
<?php
|
||||
/*******************************************************************************
|
||||
* Class to parse and subset TrueType fonts *
|
||||
* *
|
||||
* Version: 1.11 *
|
||||
* Date: 2021-04-18 *
|
||||
* Author: Olivier PLATHEY *
|
||||
*******************************************************************************/
|
||||
|
||||
class TTFParser
|
||||
{
|
||||
protected $f;
|
||||
protected $tables;
|
||||
protected $numberOfHMetrics;
|
||||
protected $numGlyphs;
|
||||
protected $glyphNames;
|
||||
protected $indexToLocFormat;
|
||||
protected $subsettedChars;
|
||||
protected $subsettedGlyphs;
|
||||
public $chars;
|
||||
public $glyphs;
|
||||
public $unitsPerEm;
|
||||
public $xMin, $yMin, $xMax, $yMax;
|
||||
public $postScriptName;
|
||||
public $embeddable;
|
||||
public $bold;
|
||||
public $typoAscender;
|
||||
public $typoDescender;
|
||||
public $capHeight;
|
||||
public $italicAngle;
|
||||
public $underlinePosition;
|
||||
public $underlineThickness;
|
||||
public $isFixedPitch;
|
||||
|
||||
function __construct($file)
|
||||
{
|
||||
$this->f = fopen($file, 'rb');
|
||||
if(!$this->f)
|
||||
$this->Error('Can\'t open file: '.$file);
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
if(is_resource($this->f))
|
||||
fclose($this->f);
|
||||
}
|
||||
|
||||
function Parse()
|
||||
{
|
||||
$this->ParseOffsetTable();
|
||||
$this->ParseHead();
|
||||
$this->ParseHhea();
|
||||
$this->ParseMaxp();
|
||||
$this->ParseHmtx();
|
||||
$this->ParseLoca();
|
||||
$this->ParseGlyf();
|
||||
$this->ParseCmap();
|
||||
$this->ParseName();
|
||||
$this->ParseOS2();
|
||||
$this->ParsePost();
|
||||
}
|
||||
|
||||
function ParseOffsetTable()
|
||||
{
|
||||
$version = $this->Read(4);
|
||||
if($version=='OTTO')
|
||||
$this->Error('OpenType fonts based on PostScript outlines are not supported');
|
||||
if($version!="\x00\x01\x00\x00")
|
||||
$this->Error('Unrecognized file format');
|
||||
$numTables = $this->ReadUShort();
|
||||
$this->Skip(3*2); // searchRange, entrySelector, rangeShift
|
||||
$this->tables = array();
|
||||
for($i=0;$i<$numTables;$i++)
|
||||
{
|
||||
$tag = $this->Read(4);
|
||||
$checkSum = $this->Read(4);
|
||||
$offset = $this->ReadULong();
|
||||
$length = $this->ReadULong();
|
||||
$this->tables[$tag] = array('offset'=>$offset, 'length'=>$length, 'checkSum'=>$checkSum);
|
||||
}
|
||||
}
|
||||
|
||||
function ParseHead()
|
||||
{
|
||||
$this->Seek('head');
|
||||
$this->Skip(3*4); // version, fontRevision, checkSumAdjustment
|
||||
$magicNumber = $this->ReadULong();
|
||||
if($magicNumber!=0x5F0F3CF5)
|
||||
$this->Error('Incorrect magic number');
|
||||
$this->Skip(2); // flags
|
||||
$this->unitsPerEm = $this->ReadUShort();
|
||||
$this->Skip(2*8); // created, modified
|
||||
$this->xMin = $this->ReadShort();
|
||||
$this->yMin = $this->ReadShort();
|
||||
$this->xMax = $this->ReadShort();
|
||||
$this->yMax = $this->ReadShort();
|
||||
$this->Skip(3*2); // macStyle, lowestRecPPEM, fontDirectionHint
|
||||
$this->indexToLocFormat = $this->ReadShort();
|
||||
}
|
||||
|
||||
function ParseHhea()
|
||||
{
|
||||
$this->Seek('hhea');
|
||||
$this->Skip(4+15*2);
|
||||
$this->numberOfHMetrics = $this->ReadUShort();
|
||||
}
|
||||
|
||||
function ParseMaxp()
|
||||
{
|
||||
$this->Seek('maxp');
|
||||
$this->Skip(4);
|
||||
$this->numGlyphs = $this->ReadUShort();
|
||||
}
|
||||
|
||||
function ParseHmtx()
|
||||
{
|
||||
$this->Seek('hmtx');
|
||||
$this->glyphs = array();
|
||||
for($i=0;$i<$this->numberOfHMetrics;$i++)
|
||||
{
|
||||
$advanceWidth = $this->ReadUShort();
|
||||
$lsb = $this->ReadShort();
|
||||
$this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb);
|
||||
}
|
||||
for($i=$this->numberOfHMetrics;$i<$this->numGlyphs;$i++)
|
||||
{
|
||||
$lsb = $this->ReadShort();
|
||||
$this->glyphs[$i] = array('w'=>$advanceWidth, 'lsb'=>$lsb);
|
||||
}
|
||||
}
|
||||
|
||||
function ParseLoca()
|
||||
{
|
||||
$this->Seek('loca');
|
||||
$offsets = array();
|
||||
if($this->indexToLocFormat==0)
|
||||
{
|
||||
// Short format
|
||||
for($i=0;$i<=$this->numGlyphs;$i++)
|
||||
$offsets[] = 2*$this->ReadUShort();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Long format
|
||||
for($i=0;$i<=$this->numGlyphs;$i++)
|
||||
$offsets[] = $this->ReadULong();
|
||||
}
|
||||
for($i=0;$i<$this->numGlyphs;$i++)
|
||||
{
|
||||
$this->glyphs[$i]['offset'] = $offsets[$i];
|
||||
$this->glyphs[$i]['length'] = $offsets[$i+1] - $offsets[$i];
|
||||
}
|
||||
}
|
||||
|
||||
function ParseGlyf()
|
||||
{
|
||||
$tableOffset = $this->tables['glyf']['offset'];
|
||||
foreach($this->glyphs as &$glyph)
|
||||
{
|
||||
if($glyph['length']>0)
|
||||
{
|
||||
fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET);
|
||||
if($this->ReadShort()<0)
|
||||
{
|
||||
// Composite glyph
|
||||
$this->Skip(4*2); // xMin, yMin, xMax, yMax
|
||||
$offset = 5*2;
|
||||
$a = array();
|
||||
do
|
||||
{
|
||||
$flags = $this->ReadUShort();
|
||||
$index = $this->ReadUShort();
|
||||
$a[$offset+2] = $index;
|
||||
if($flags & 1) // ARG_1_AND_2_ARE_WORDS
|
||||
$skip = 2*2;
|
||||
else
|
||||
$skip = 2;
|
||||
if($flags & 8) // WE_HAVE_A_SCALE
|
||||
$skip += 2;
|
||||
elseif($flags & 64) // WE_HAVE_AN_X_AND_Y_SCALE
|
||||
$skip += 2*2;
|
||||
elseif($flags & 128) // WE_HAVE_A_TWO_BY_TWO
|
||||
$skip += 4*2;
|
||||
$this->Skip($skip);
|
||||
$offset += 2*2 + $skip;
|
||||
}
|
||||
while($flags & 32); // MORE_COMPONENTS
|
||||
$glyph['components'] = $a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ParseCmap()
|
||||
{
|
||||
$this->Seek('cmap');
|
||||
$this->Skip(2); // version
|
||||
$numTables = $this->ReadUShort();
|
||||
$offset31 = 0;
|
||||
for($i=0;$i<$numTables;$i++)
|
||||
{
|
||||
$platformID = $this->ReadUShort();
|
||||
$encodingID = $this->ReadUShort();
|
||||
$offset = $this->ReadULong();
|
||||
if($platformID==3 && $encodingID==1)
|
||||
$offset31 = $offset;
|
||||
}
|
||||
if($offset31==0)
|
||||
$this->Error('No Unicode encoding found');
|
||||
|
||||
$startCount = array();
|
||||
$endCount = array();
|
||||
$idDelta = array();
|
||||
$idRangeOffset = array();
|
||||
$this->chars = array();
|
||||
fseek($this->f, $this->tables['cmap']['offset']+$offset31, SEEK_SET);
|
||||
$format = $this->ReadUShort();
|
||||
if($format!=4)
|
||||
$this->Error('Unexpected subtable format: '.$format);
|
||||
$this->Skip(2*2); // length, language
|
||||
$segCount = $this->ReadUShort()/2;
|
||||
$this->Skip(3*2); // searchRange, entrySelector, rangeShift
|
||||
for($i=0;$i<$segCount;$i++)
|
||||
$endCount[$i] = $this->ReadUShort();
|
||||
$this->Skip(2); // reservedPad
|
||||
for($i=0;$i<$segCount;$i++)
|
||||
$startCount[$i] = $this->ReadUShort();
|
||||
for($i=0;$i<$segCount;$i++)
|
||||
$idDelta[$i] = $this->ReadShort();
|
||||
$offset = ftell($this->f);
|
||||
for($i=0;$i<$segCount;$i++)
|
||||
$idRangeOffset[$i] = $this->ReadUShort();
|
||||
|
||||
for($i=0;$i<$segCount;$i++)
|
||||
{
|
||||
$c1 = $startCount[$i];
|
||||
$c2 = $endCount[$i];
|
||||
$d = $idDelta[$i];
|
||||
$ro = $idRangeOffset[$i];
|
||||
if($ro>0)
|
||||
fseek($this->f, $offset+2*$i+$ro, SEEK_SET);
|
||||
for($c=$c1;$c<=$c2;$c++)
|
||||
{
|
||||
if($c==0xFFFF)
|
||||
break;
|
||||
if($ro>0)
|
||||
{
|
||||
$gid = $this->ReadUShort();
|
||||
if($gid>0)
|
||||
$gid += $d;
|
||||
}
|
||||
else
|
||||
$gid = $c+$d;
|
||||
if($gid>=65536)
|
||||
$gid -= 65536;
|
||||
if($gid>0)
|
||||
$this->chars[$c] = $gid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ParseName()
|
||||
{
|
||||
$this->Seek('name');
|
||||
$tableOffset = $this->tables['name']['offset'];
|
||||
$this->postScriptName = '';
|
||||
$this->Skip(2); // format
|
||||
$count = $this->ReadUShort();
|
||||
$stringOffset = $this->ReadUShort();
|
||||
for($i=0;$i<$count;$i++)
|
||||
{
|
||||
$this->Skip(3*2); // platformID, encodingID, languageID
|
||||
$nameID = $this->ReadUShort();
|
||||
$length = $this->ReadUShort();
|
||||
$offset = $this->ReadUShort();
|
||||
if($nameID==6)
|
||||
{
|
||||
// PostScript name
|
||||
fseek($this->f, $tableOffset+$stringOffset+$offset, SEEK_SET);
|
||||
$s = $this->Read($length);
|
||||
$s = str_replace(chr(0), '', $s);
|
||||
$s = preg_replace('|[ \[\](){}<>/%]|', '', $s);
|
||||
$this->postScriptName = $s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($this->postScriptName=='')
|
||||
$this->Error('PostScript name not found');
|
||||
}
|
||||
|
||||
function ParseOS2()
|
||||
{
|
||||
$this->Seek('OS/2');
|
||||
$version = $this->ReadUShort();
|
||||
$this->Skip(3*2); // xAvgCharWidth, usWeightClass, usWidthClass
|
||||
$fsType = $this->ReadUShort();
|
||||
$this->embeddable = ($fsType!=2) && ($fsType & 0x200)==0;
|
||||
$this->Skip(11*2+10+4*4+4);
|
||||
$fsSelection = $this->ReadUShort();
|
||||
$this->bold = ($fsSelection & 32)!=0;
|
||||
$this->Skip(2*2); // usFirstCharIndex, usLastCharIndex
|
||||
$this->typoAscender = $this->ReadShort();
|
||||
$this->typoDescender = $this->ReadShort();
|
||||
if($version>=2)
|
||||
{
|
||||
$this->Skip(3*2+2*4+2);
|
||||
$this->capHeight = $this->ReadShort();
|
||||
}
|
||||
else
|
||||
$this->capHeight = 0;
|
||||
}
|
||||
|
||||
function ParsePost()
|
||||
{
|
||||
$this->Seek('post');
|
||||
$version = $this->ReadULong();
|
||||
$this->italicAngle = $this->ReadShort();
|
||||
$this->Skip(2); // Skip decimal part
|
||||
$this->underlinePosition = $this->ReadShort();
|
||||
$this->underlineThickness = $this->ReadShort();
|
||||
$this->isFixedPitch = ($this->ReadULong()!=0);
|
||||
if($version==0x20000)
|
||||
{
|
||||
// Extract glyph names
|
||||
$this->Skip(4*4); // min/max usage
|
||||
$this->Skip(2); // numberOfGlyphs
|
||||
$glyphNameIndex = array();
|
||||
$names = array();
|
||||
$numNames = 0;
|
||||
for($i=0;$i<$this->numGlyphs;$i++)
|
||||
{
|
||||
$index = $this->ReadUShort();
|
||||
$glyphNameIndex[] = $index;
|
||||
if($index>=258 && $index-257>$numNames)
|
||||
$numNames = $index-257;
|
||||
}
|
||||
for($i=0;$i<$numNames;$i++)
|
||||
{
|
||||
$len = ord($this->Read(1));
|
||||
$names[] = $this->Read($len);
|
||||
}
|
||||
foreach($glyphNameIndex as $i=>$index)
|
||||
{
|
||||
if($index>=258)
|
||||
$this->glyphs[$i]['name'] = $names[$index-258];
|
||||
else
|
||||
$this->glyphs[$i]['name'] = $index;
|
||||
}
|
||||
$this->glyphNames = true;
|
||||
}
|
||||
else
|
||||
$this->glyphNames = false;
|
||||
}
|
||||
|
||||
function Subset($chars)
|
||||
{
|
||||
$this->subsettedGlyphs = array();
|
||||
$this->AddGlyph(0);
|
||||
$this->subsettedChars = array();
|
||||
foreach($chars as $char)
|
||||
{
|
||||
if(isset($this->chars[$char]))
|
||||
{
|
||||
$this->subsettedChars[] = $char;
|
||||
$this->AddGlyph($this->chars[$char]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function AddGlyph($id)
|
||||
{
|
||||
if(!isset($this->glyphs[$id]['ssid']))
|
||||
{
|
||||
$this->glyphs[$id]['ssid'] = count($this->subsettedGlyphs);
|
||||
$this->subsettedGlyphs[] = $id;
|
||||
if(isset($this->glyphs[$id]['components']))
|
||||
{
|
||||
foreach($this->glyphs[$id]['components'] as $cid)
|
||||
$this->AddGlyph($cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Build()
|
||||
{
|
||||
$this->BuildCmap();
|
||||
$this->BuildHhea();
|
||||
$this->BuildHmtx();
|
||||
$this->BuildLoca();
|
||||
$this->BuildGlyf();
|
||||
$this->BuildMaxp();
|
||||
$this->BuildPost();
|
||||
return $this->BuildFont();
|
||||
}
|
||||
|
||||
function BuildCmap()
|
||||
{
|
||||
if(!isset($this->subsettedChars))
|
||||
return;
|
||||
|
||||
// Divide charset in contiguous segments
|
||||
$chars = $this->subsettedChars;
|
||||
sort($chars);
|
||||
$segments = array();
|
||||
$segment = array($chars[0], $chars[0]);
|
||||
for($i=1;$i<count($chars);$i++)
|
||||
{
|
||||
if($chars[$i]>$segment[1]+1)
|
||||
{
|
||||
$segments[] = $segment;
|
||||
$segment = array($chars[$i], $chars[$i]);
|
||||
}
|
||||
else
|
||||
$segment[1]++;
|
||||
}
|
||||
$segments[] = $segment;
|
||||
$segments[] = array(0xFFFF, 0xFFFF);
|
||||
$segCount = count($segments);
|
||||
|
||||
// Build a Format 4 subtable
|
||||
$startCount = array();
|
||||
$endCount = array();
|
||||
$idDelta = array();
|
||||
$idRangeOffset = array();
|
||||
$glyphIdArray = '';
|
||||
for($i=0;$i<$segCount;$i++)
|
||||
{
|
||||
list($start, $end) = $segments[$i];
|
||||
$startCount[] = $start;
|
||||
$endCount[] = $end;
|
||||
if($start!=$end)
|
||||
{
|
||||
// Segment with multiple chars
|
||||
$idDelta[] = 0;
|
||||
$idRangeOffset[] = strlen($glyphIdArray) + ($segCount-$i)*2;
|
||||
for($c=$start;$c<=$end;$c++)
|
||||
{
|
||||
$ssid = $this->glyphs[$this->chars[$c]]['ssid'];
|
||||
$glyphIdArray .= pack('n', $ssid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Segment with a single char
|
||||
if($start<0xFFFF)
|
||||
$ssid = $this->glyphs[$this->chars[$start]]['ssid'];
|
||||
else
|
||||
$ssid = 0;
|
||||
$idDelta[] = $ssid - $start;
|
||||
$idRangeOffset[] = 0;
|
||||
}
|
||||
}
|
||||
$entrySelector = 0;
|
||||
$n = $segCount;
|
||||
while($n!=1)
|
||||
{
|
||||
$n = $n>>1;
|
||||
$entrySelector++;
|
||||
}
|
||||
$searchRange = (1<<$entrySelector)*2;
|
||||
$rangeShift = 2*$segCount - $searchRange;
|
||||
$cmap = pack('nnnn', 2*$segCount, $searchRange, $entrySelector, $rangeShift);
|
||||
foreach($endCount as $val)
|
||||
$cmap .= pack('n', $val);
|
||||
$cmap .= pack('n', 0); // reservedPad
|
||||
foreach($startCount as $val)
|
||||
$cmap .= pack('n', $val);
|
||||
foreach($idDelta as $val)
|
||||
$cmap .= pack('n', $val);
|
||||
foreach($idRangeOffset as $val)
|
||||
$cmap .= pack('n', $val);
|
||||
$cmap .= $glyphIdArray;
|
||||
|
||||
$data = pack('nn', 0, 1); // version, numTables
|
||||
$data .= pack('nnN', 3, 1, 12); // platformID, encodingID, offset
|
||||
$data .= pack('nnn', 4, 6+strlen($cmap), 0); // format, length, language
|
||||
$data .= $cmap;
|
||||
$this->SetTable('cmap', $data);
|
||||
}
|
||||
|
||||
function BuildHhea()
|
||||
{
|
||||
$this->LoadTable('hhea');
|
||||
$numberOfHMetrics = count($this->subsettedGlyphs);
|
||||
$data = substr_replace($this->tables['hhea']['data'], pack('n',$numberOfHMetrics), 4+15*2, 2);
|
||||
$this->SetTable('hhea', $data);
|
||||
}
|
||||
|
||||
function BuildHmtx()
|
||||
{
|
||||
$data = '';
|
||||
foreach($this->subsettedGlyphs as $id)
|
||||
{
|
||||
$glyph = $this->glyphs[$id];
|
||||
$data .= pack('nn', $glyph['w'], $glyph['lsb']);
|
||||
}
|
||||
$this->SetTable('hmtx', $data);
|
||||
}
|
||||
|
||||
function BuildLoca()
|
||||
{
|
||||
$data = '';
|
||||
$offset = 0;
|
||||
foreach($this->subsettedGlyphs as $id)
|
||||
{
|
||||
if($this->indexToLocFormat==0)
|
||||
$data .= pack('n', $offset/2);
|
||||
else
|
||||
$data .= pack('N', $offset);
|
||||
$offset += $this->glyphs[$id]['length'];
|
||||
}
|
||||
if($this->indexToLocFormat==0)
|
||||
$data .= pack('n', $offset/2);
|
||||
else
|
||||
$data .= pack('N', $offset);
|
||||
$this->SetTable('loca', $data);
|
||||
}
|
||||
|
||||
function BuildGlyf()
|
||||
{
|
||||
$tableOffset = $this->tables['glyf']['offset'];
|
||||
$data = '';
|
||||
foreach($this->subsettedGlyphs as $id)
|
||||
{
|
||||
$glyph = $this->glyphs[$id];
|
||||
fseek($this->f, $tableOffset+$glyph['offset'], SEEK_SET);
|
||||
$glyph_data = $this->Read($glyph['length']);
|
||||
if(isset($glyph['components']))
|
||||
{
|
||||
// Composite glyph
|
||||
foreach($glyph['components'] as $offset=>$cid)
|
||||
{
|
||||
$ssid = $this->glyphs[$cid]['ssid'];
|
||||
$glyph_data = substr_replace($glyph_data, pack('n',$ssid), $offset, 2);
|
||||
}
|
||||
}
|
||||
$data .= $glyph_data;
|
||||
}
|
||||
$this->SetTable('glyf', $data);
|
||||
}
|
||||
|
||||
function BuildMaxp()
|
||||
{
|
||||
$this->LoadTable('maxp');
|
||||
$numGlyphs = count($this->subsettedGlyphs);
|
||||
$data = substr_replace($this->tables['maxp']['data'], pack('n',$numGlyphs), 4, 2);
|
||||
$this->SetTable('maxp', $data);
|
||||
}
|
||||
|
||||
function BuildPost()
|
||||
{
|
||||
$this->Seek('post');
|
||||
if($this->glyphNames)
|
||||
{
|
||||
// Version 2.0
|
||||
$numberOfGlyphs = count($this->subsettedGlyphs);
|
||||
$numNames = 0;
|
||||
$names = '';
|
||||
$data = $this->Read(2*4+2*2+5*4);
|
||||
$data .= pack('n', $numberOfGlyphs);
|
||||
foreach($this->subsettedGlyphs as $id)
|
||||
{
|
||||
$name = $this->glyphs[$id]['name'];
|
||||
if(is_string($name))
|
||||
{
|
||||
$data .= pack('n', 258+$numNames);
|
||||
$names .= chr(strlen($name)).$name;
|
||||
$numNames++;
|
||||
}
|
||||
else
|
||||
$data .= pack('n', $name);
|
||||
}
|
||||
$data .= $names;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Version 3.0
|
||||
$this->Skip(4);
|
||||
$data = "\x00\x03\x00\x00";
|
||||
$data .= $this->Read(4+2*2+5*4);
|
||||
}
|
||||
$this->SetTable('post', $data);
|
||||
}
|
||||
|
||||
function BuildFont()
|
||||
{
|
||||
$tags = array();
|
||||
foreach(array('cmap', 'cvt ', 'fpgm', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'prep') as $tag)
|
||||
{
|
||||
if(isset($this->tables[$tag]))
|
||||
$tags[] = $tag;
|
||||
}
|
||||
$numTables = count($tags);
|
||||
$offset = 12 + 16*$numTables;
|
||||
foreach($tags as $tag)
|
||||
{
|
||||
if(!isset($this->tables[$tag]['data']))
|
||||
$this->LoadTable($tag);
|
||||
$this->tables[$tag]['offset'] = $offset;
|
||||
$offset += strlen($this->tables[$tag]['data']);
|
||||
}
|
||||
|
||||
// Build offset table
|
||||
$entrySelector = 0;
|
||||
$n = $numTables;
|
||||
while($n!=1)
|
||||
{
|
||||
$n = $n>>1;
|
||||
$entrySelector++;
|
||||
}
|
||||
$searchRange = 16*(1<<$entrySelector);
|
||||
$rangeShift = 16*$numTables - $searchRange;
|
||||
$offsetTable = pack('nnnnnn', 1, 0, $numTables, $searchRange, $entrySelector, $rangeShift);
|
||||
foreach($tags as $tag)
|
||||
{
|
||||
$table = $this->tables[$tag];
|
||||
$offsetTable .= $tag.$table['checkSum'].pack('NN', $table['offset'], $table['length']);
|
||||
}
|
||||
|
||||
// Compute checkSumAdjustment (0xB1B0AFBA - font checkSum)
|
||||
$s = $this->CheckSum($offsetTable);
|
||||
foreach($tags as $tag)
|
||||
$s .= $this->tables[$tag]['checkSum'];
|
||||
$a = unpack('n2', $this->CheckSum($s));
|
||||
$high = 0xB1B0 + ($a[1]^0xFFFF);
|
||||
$low = 0xAFBA + ($a[2]^0xFFFF) + 1;
|
||||
$checkSumAdjustment = pack('nn', $high+($low>>16), $low);
|
||||
$this->tables['head']['data'] = substr_replace($this->tables['head']['data'], $checkSumAdjustment, 8, 4);
|
||||
|
||||
$font = $offsetTable;
|
||||
foreach($tags as $tag)
|
||||
$font .= $this->tables[$tag]['data'];
|
||||
|
||||
return $font;
|
||||
}
|
||||
|
||||
function LoadTable($tag)
|
||||
{
|
||||
$this->Seek($tag);
|
||||
$length = $this->tables[$tag]['length'];
|
||||
$n = $length % 4;
|
||||
if($n>0)
|
||||
$length += 4 - $n;
|
||||
$this->tables[$tag]['data'] = $this->Read($length);
|
||||
}
|
||||
|
||||
function SetTable($tag, $data)
|
||||
{
|
||||
$length = strlen($data);
|
||||
$n = $length % 4;
|
||||
if($n>0)
|
||||
$data = str_pad($data, $length+4-$n, "\x00");
|
||||
$this->tables[$tag]['data'] = $data;
|
||||
$this->tables[$tag]['length'] = $length;
|
||||
$this->tables[$tag]['checkSum'] = $this->CheckSum($data);
|
||||
}
|
||||
|
||||
function Seek($tag)
|
||||
{
|
||||
if(!isset($this->tables[$tag]))
|
||||
$this->Error('Table not found: '.$tag);
|
||||
fseek($this->f, $this->tables[$tag]['offset'], SEEK_SET);
|
||||
}
|
||||
|
||||
function Skip($n)
|
||||
{
|
||||
fseek($this->f, $n, SEEK_CUR);
|
||||
}
|
||||
|
||||
function Read($n)
|
||||
{
|
||||
return $n>0 ? fread($this->f, $n) : '';
|
||||
}
|
||||
|
||||
function ReadUShort()
|
||||
{
|
||||
$a = unpack('nn', fread($this->f,2));
|
||||
return $a['n'];
|
||||
}
|
||||
|
||||
function ReadShort()
|
||||
{
|
||||
$a = unpack('nn', fread($this->f,2));
|
||||
$v = $a['n'];
|
||||
if($v>=0x8000)
|
||||
$v -= 65536;
|
||||
return $v;
|
||||
}
|
||||
|
||||
function ReadULong()
|
||||
{
|
||||
$a = unpack('NN', fread($this->f,4));
|
||||
return $a['N'];
|
||||
}
|
||||
|
||||
function CheckSum($s)
|
||||
{
|
||||
$n = strlen($s);
|
||||
$high = 0;
|
||||
$low = 0;
|
||||
for($i=0;$i<$n;$i+=4)
|
||||
{
|
||||
$high += (ord($s[$i])<<8) + ord($s[$i+1]);
|
||||
$low += (ord($s[$i+2])<<8) + ord($s[$i+3]);
|
||||
}
|
||||
return pack('nn', $high+($low>>16), $low);
|
||||
}
|
||||
|
||||
function Error($msg)
|
||||
{
|
||||
throw new Exception($msg);
|
||||
}
|
||||
}
|
||||
?>
|
||||
404: Not Found
|
||||
2371
includes/fpdf/tfpdf.php
Normal file
2371
includes/fpdf/tfpdf.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -499,6 +499,7 @@ require_once 'includes/header.php';
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<form action="checkout.php" method="POST">
|
||||
<input type="hidden" name="lang" value="<?= $lang ?>">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold"><?= $t['modal_title'] ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
|
||||
93
success.php
93
success.php
@ -5,9 +5,45 @@ require_once 'mail/WablasService.php';
|
||||
|
||||
$session_id = $_GET['session_id'] ?? null;
|
||||
$donation_id = $_GET['donation_id'] ?? null; // For simulation
|
||||
$lang = $_GET['lang'] ?? 'ar';
|
||||
if (!in_array($lang, ['en', 'ar'])) $lang = 'ar';
|
||||
$dir = ($lang === 'ar') ? 'rtl' : 'ltr';
|
||||
|
||||
// UI Text
|
||||
$texts = [
|
||||
'en' => [
|
||||
'title' => 'Donation Successful',
|
||||
'thank_you' => 'Thank You!',
|
||||
'success_msg' => 'Your donation has been successfully processed. Your generosity helps us continue our mission.',
|
||||
'download_cert' => 'Download Certificate',
|
||||
'return_home' => 'Return to Home',
|
||||
'trans_id' => 'Transaction ID',
|
||||
'status' => 'Status',
|
||||
'completed' => 'Completed',
|
||||
'conf_sent' => 'Confirmation messages have been sent to the relevant parties.',
|
||||
'failed_title' => 'Payment Verification Failed',
|
||||
'failed_msg' => 'We couldn\'t verify your payment. If you believe this is an error, please contact support.',
|
||||
'back_home' => 'Back to Home'
|
||||
],
|
||||
'ar' => [
|
||||
'title' => 'تم التبرع بنجاح',
|
||||
'thank_you' => 'شكراً لك!',
|
||||
'success_msg' => 'تمت معالجة تبرعك بنجاح. سخاؤك يساعدنا في الاستمرار في مهمتنا.',
|
||||
'download_cert' => 'تحميل الشهادة',
|
||||
'return_home' => 'العودة للرئيسية',
|
||||
'trans_id' => 'رقم المعاملة',
|
||||
'status' => 'الحالة',
|
||||
'completed' => 'مكتمل',
|
||||
'conf_sent' => 'تم إرسال رسائل التأكيد إلى الأطراف المعنية.',
|
||||
'failed_title' => 'فشل التحقق من الدفع',
|
||||
'failed_msg' => 'لم نتمكن من التحقق من دفعتك. إذا كنت تعتقد أن هذا خطأ، يرجى الاتصال بالدعم.',
|
||||
'back_home' => 'العودة للرئيسية'
|
||||
]
|
||||
];
|
||||
$t = $texts[$lang];
|
||||
|
||||
if (!$session_id) {
|
||||
header('Location: index.php');
|
||||
header('Location: index.php?lang=' . $lang);
|
||||
exit;
|
||||
}
|
||||
|
||||
@ -51,6 +87,16 @@ if ($donation) {
|
||||
// Update case raised amount
|
||||
$pdo->prepare("UPDATE cases SET raised = raised + ? WHERE id = ?")
|
||||
->execute([$donation['amount'], $donation['case_id']]);
|
||||
|
||||
// Check if goal reached and mark as completed if so
|
||||
$stmt = $pdo->prepare("SELECT raised, goal FROM cases WHERE id = ?");
|
||||
$stmt->execute([$donation['case_id']]);
|
||||
$updatedCase = $stmt->fetch();
|
||||
|
||||
if ($updatedCase && $updatedCase['raised'] >= $updatedCase['goal']) {
|
||||
$pdo->prepare("UPDATE cases SET status = 'completed' WHERE id = ?")
|
||||
->execute([$donation['case_id']]);
|
||||
}
|
||||
|
||||
// Refresh donation data to get name/phone/gift info
|
||||
$stmt = $pdo->prepare("SELECT * FROM donations WHERE id = ?");
|
||||
@ -60,7 +106,7 @@ if ($donation) {
|
||||
// Send WhatsApp notification to donor
|
||||
WablasService::sendThankYou($fullDonation);
|
||||
|
||||
// Send WhatsApp notification to recipient if it's a gift
|
||||
// Send WhatsApp notification to recipient if it\'s a gift
|
||||
if ($fullDonation['is_gift']) {
|
||||
WablasService::sendGiftNotification($fullDonation);
|
||||
}
|
||||
@ -79,21 +125,28 @@ if ($donation) {
|
||||
}
|
||||
|
||||
$org = $pdo->query("SELECT * FROM org_profile LIMIT 1")->fetch();
|
||||
$orgName = $org['name_en'] ?? 'Organization';
|
||||
$orgName = ($lang === 'en') ? ($org['name_en'] ?? 'Organization') : ($org['name_ar'] ?? 'المؤسسة');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="<?= $lang ?>" dir="<?= $dir ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Donation Successful - <?= htmlspecialchars($orgName) ?></title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<title><?= $t['title'] ?> - <?= htmlspecialchars($orgName) ?></title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<?php if ($lang === 'ar'): ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
|
||||
<?php else: ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<?php endif; ?>
|
||||
<style>
|
||||
body { background-color: #f8fafc; font-family: 'Inter', sans-serif; }
|
||||
body { background-color: #f8fafc; font-family: 'Inter', system-ui, -apple-system, sans-serif; }
|
||||
<?php if ($lang === 'ar'): ?>
|
||||
body { font-family: 'Amiri', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
||||
<?php endif; ?>
|
||||
.success-card { max-width: 600px; border-radius: 24px; border: none; overflow: hidden; }
|
||||
.success-icon { background: #ecfdf5; color: #10b981; width: 100px; height: 100px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto; }
|
||||
.btn-home { background: #059669; color: white; border: none; padding: 12px 40px; border-radius: 12px; font-weight: 600; transition: all 0.3s; }
|
||||
.btn-home { background: #059669; color: white; border: none; padding: 12px 40px; border-radius: 12px; font-weight: 600; transition: all 0.3s; text-decoration: none; }
|
||||
.btn-home:hover { background: #047857; color: white; transform: translateY(-2px); }
|
||||
.btn-cert { background: #fff; color: #059669; border: 2px solid #059669; padding: 12px 40px; border-radius: 12px; font-weight: 600; transition: all 0.3s; text-decoration: none; display: inline-block; }
|
||||
.btn-cert:hover { background: #ecfdf5; color: #047857; }
|
||||
@ -110,27 +163,27 @@ $orgName = $org['name_en'] ?? 'Organization';
|
||||
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.42-6.447a.733.733 0 0 1 0 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="fw-bold mb-3">Thank You!</h1>
|
||||
<p class="text-muted fs-5 mb-4">Your donation has been successfully processed. Your generosity helps us continue our mission.</p>
|
||||
<h1 class="fw-bold mb-3"><?= $t['thank_you'] ?></h1>
|
||||
<p class="text-muted fs-5 mb-4"><?= $t['success_msg'] ?></p>
|
||||
|
||||
<div class="d-flex flex-column flex-sm-row gap-3 justify-content-center mb-5">
|
||||
<a href="certificate.php?id=<?= $final_donation_id ?>" target="_blank" class="btn-cert">
|
||||
<i class="bi bi-patch-check me-2"></i>Download Certificate
|
||||
<a href="certificate.php?id=<?= $final_donation_id ?>&lang=<?= $lang ?>" target="_blank" class="btn-cert">
|
||||
<i class="bi bi-patch-check me-2"></i><?= $t['download_cert'] ?>
|
||||
</a>
|
||||
<a href="index.php" class="btn btn-home">Return to Home</a>
|
||||
<a href="index.php?lang=<?= $lang ?>" class="btn btn-home"><?= $t['return_home'] ?></a>
|
||||
</div>
|
||||
|
||||
<div class="bg-light p-4 rounded-4 mb-4 text-start">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Transaction ID</span>
|
||||
<span class="text-muted"><?= $t['trans_id'] ?></span>
|
||||
<span class="fw-medium text-break"><?= htmlspecialchars($session_id) ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted">Status</span>
|
||||
<span class="badge bg-success rounded-pill px-3">Completed</span>
|
||||
<span class="text-muted"><?= $t['status'] ?></span>
|
||||
<span class="badge bg-success rounded-pill px-3"><?= $t['completed'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small text-muted mb-0">Confirmation messages have been sent to the relevant parties.</p>
|
||||
<p class="small text-muted mb-0"><?= $t['conf_sent'] ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card border-0 shadow-lg p-5 rounded-4">
|
||||
@ -139,9 +192,9 @@ $orgName = $org['name_en'] ?? 'Organization';
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="fw-bold">Payment Verification Failed</h2>
|
||||
<p class="text-muted">We couldn't verify your payment. If you believe this is an error, please contact support.</p>
|
||||
<a href="index.php" class="btn btn-secondary rounded-pill px-4 mt-3">Back to Home</a>
|
||||
<h2 class="fw-bold"><?= $t['failed_title'] ?></h2>
|
||||
<p class="text-muted"><?= $t['failed_msg'] ?></p>
|
||||
<a href="index.php?lang=<?= $lang ?>" class="btn btn-secondary rounded-pill px-4 mt-3"><?= $t['back_home'] ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
1
tmp/ar-php-master.zip
Normal file
1
tmp/ar-php-master.zip
Normal file
@ -0,0 +1 @@
|
||||
Not Found
|
||||
BIN
tmp/test_currency.pdf
Normal file
BIN
tmp/test_currency.pdf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user