From c1267b50a70e353360a9436bdbee78ddf1015cbe Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 14 Feb 2026 07:56:12 +0000 Subject: [PATCH] Autosave: 20260214-075612 --- admin/case_report.php | 233 +- admin/cases.php | 3 + case.php | 12 +- certificate.php | 50 +- checkout.php | 18 +- includes/I18N/Arabic.php | 174 ++ includes/I18N/Arabic/Glyphs.php | 1 + includes/fpdf/font/unifont/amiri.cw.dat | Bin 0 -> 131072 bytes includes/fpdf/font/unifont/amiri.cw127.php | 120 + includes/fpdf/font/unifont/amiri.mtx.php | 19 + {tmp => includes/fpdf/font/unifont}/amiri.ttf | Bin includes/fpdf/font/unifont/ttfonts.php | 1090 ++++++++ includes/fpdf/makefont/makefont.php | 448 +--- includes/fpdf/makefont/ttfparser.php | 715 +---- includes/fpdf/tfpdf.php | 2371 +++++++++++++++++ index.php | 1 + success.php | 93 +- tmp/ar-php-master.zip | 1 + tmp/test_currency.pdf | Bin 0 -> 8872 bytes 19 files changed, 4017 insertions(+), 1332 deletions(-) create mode 100644 includes/I18N/Arabic.php create mode 100644 includes/I18N/Arabic/Glyphs.php create mode 100644 includes/fpdf/font/unifont/amiri.cw.dat create mode 100644 includes/fpdf/font/unifont/amiri.cw127.php create mode 100644 includes/fpdf/font/unifont/amiri.mtx.php rename {tmp => includes/fpdf/font/unifont}/amiri.ttf (100%) create mode 100644 includes/fpdf/font/unifont/ttfonts.php create mode 100644 includes/fpdf/tfpdf.php create mode 100644 tmp/ar-php-master.zip create mode 100644 tmp/test_currency.pdf diff --git a/admin/case_report.php b/admin/case_report.php index 8b4a823..149ff22 100644 --- a/admin/case_report.php +++ b/admin/case_report.php @@ -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'); } } diff --git a/admin/cases.php b/admin/cases.php index fb25e4b..f3f6f4b 100644 --- a/admin/cases.php +++ b/admin/cases.php @@ -190,6 +190,8 @@ $is_rtl = (get_current_lang() === 'ar'); + + @@ -277,6 +279,7 @@ $is_rtl = (get_current_lang() === 'ar'); + diff --git a/case.php b/case.php index 725ca46..d0da2bd 100644 --- a/case.php +++ b/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';
+ + +
+ + +
+
@@ -168,6 +177,7 @@ require_once 'includes/header.php';