From 5072ff8ba259bb6090b63cd4c5f1222732c867ac Mon Sep 17 00:00:00 2001 From: dicobaja <76435781+dicobaja@users.noreply.github.com> Date: Wed, 19 Mar 2025 21:44:30 +0700 Subject: [PATCH 01/24] fix: use `where` instead of `which` for windows host --- system/controllers/settings.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/controllers/settings.php b/system/controllers/settings.php index a1599e8b..4366a32f 100644 --- a/system/controllers/settings.php +++ b/system/controllers/settings.php @@ -146,7 +146,8 @@ switch ($action) { $r = ORM::for_table('tbl_routers')->find_many(); $ui->assign('r', $r); if (function_exists("shell_exec")) { - $php = trim(shell_exec('which php')); + $which = stripos(php_uname('s'), "Win") === 0 ? 'where' : 'which'; + $php = trim(shell_exec("$which php")); if (empty($php)) { $php = 'php'; } From dc28298d53fe2e22a03edcf9ed016f99b1994294 Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Sat, 22 Mar 2025 15:51:36 +0100 Subject: [PATCH 02/24] feat: add username field to customer query --- system/controllers/message.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/controllers/message.php b/system/controllers/message.php index b2df636d..078482a1 100644 --- a/system/controllers/message.php +++ b/system/controllers/message.php @@ -200,6 +200,7 @@ EOT; ['tbl_customers.phonenumber', 'phonenumber'], ['tbl_user_recharges.customer_id', 'customer_id'], ['tbl_customers.fullname', 'fullname'], + ['tbl_customers.username','username'], ]); $customers = $query->find_array(); } else { From 7bfbdb1efb9ac5210cc3de7c774a57e7c49ff1ec Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Sat, 22 Mar 2025 16:15:16 +0100 Subject: [PATCH 03/24] feat: add fullname field to activation report and update language file --- system/controllers/reports.php | 39 +++++++++++++++++++----------- ui/ui/admin/reports/activation.tpl | 2 ++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/system/controllers/reports.php b/system/controllers/reports.php index b24ce39c..4272a22c 100644 --- a/system/controllers/reports.php +++ b/system/controllers/reports.php @@ -58,7 +58,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); @@ -91,7 +91,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); @@ -161,7 +161,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); @@ -246,7 +246,7 @@ switch ($action) { $total += $v; $array['data'][] = $v; } - if($total>0){ + if ($total > 0) { $result['datas'][] = $array; } } @@ -258,18 +258,29 @@ switch ($action) { die(); case 'by-date': case 'activation': - $q = (_post('q') ? _post('q') : _get('q')); + $q = trim(_post('q') ?: _get('q')); $keep = _post('keep'); + if (!empty($keep)) { - ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))"); - r2(getUrl('logs/list/'), 's', "Delete logs older than $keep days"); + ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))", [$keep]); + r2(getUrl('reports/activation/'), 's', "Deleted logs older than $keep days"); } - if ($q != '') { - $query = ORM::for_table('tbl_transactions')->where_like('invoice', '%' . $q . '%')->order_by_desc('id'); + + $query = ORM::for_table('tbl_transactions') + ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); + + if ($q !== '') { + $query->where_like('invoice', "%$q%"); + } + + try { $d = Paginator::findMany($query, ['q' => $q]); - } else { - $query = ORM::for_table('tbl_transactions')->order_by_desc('id'); - $d = Paginator::findMany($query); + } catch (Exception $e) { + r2(getUrl('reports/activation/'), 'e','Database query failed: ' . $e->getMessage()); + $d = []; } $ui->assign('activation', $d); @@ -298,7 +309,7 @@ switch ($action) { $d->where_gte('recharged_on', $fdate); $d->where_lte('recharged_on', $tdate); $d->order_by_desc('id'); - $x = $d->find_many(); + $x = $d->find_many(); $dr = ORM::for_table('tbl_transactions'); if ($stype != '') { @@ -356,7 +367,7 @@ switch ($action) { $w = []; $v = []; foreach ($mts as $mt) { - $w[] ='method'; + $w[] = 'method'; $v[] = "$mt - %"; } $query->where_likes($w, $v); diff --git a/ui/ui/admin/reports/activation.tpl b/ui/ui/admin/reports/activation.tpl index 4ced97a9..1dc09a94 100644 --- a/ui/ui/admin/reports/activation.tpl +++ b/ui/ui/admin/reports/activation.tpl @@ -33,6 +33,7 @@ {Lang::T('Invoice')} {Lang::T('Username')} + {Lang::T('Fullname')} {Lang::T('Plan Name')} {Lang::T('Plan Price')} {Lang::T('Type')} @@ -48,6 +49,7 @@ style="cursor:pointer;">{$ds['invoice']} {$ds['username']} + {$ds['fullname']} {$ds['plan_name']} {Lang::moneyFormat($ds['price'])} {$ds['type']} From c573c49fb9d7ab20761f25d1b90e07c4f07e0c31 Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Sat, 22 Mar 2025 17:41:04 +0100 Subject: [PATCH 04/24] "Added exception for system/uploads/invoices/ directory to .gitignore" --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e9361f19..b26f81d5 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,5 @@ docs/** .idea !docs/insomnia.rest.json !system/uploads/paid.png +!system/uploads/invoices/ system/uploads/invoices/** From 11e5ebe1030fde8e94468456fb820b7bc645082b Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Sat, 22 Mar 2025 17:52:12 +0100 Subject: [PATCH 05/24] feat: update .gitignore to include invoices directory and add index.html --- .gitignore | 3 ++- system/uploads/invoices/index.html | 0 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 system/uploads/invoices/index.html diff --git a/.gitignore b/.gitignore index b26f81d5..3bd975cb 100644 --- a/.gitignore +++ b/.gitignore @@ -57,5 +57,6 @@ docs/** .idea !docs/insomnia.rest.json !system/uploads/paid.png -!system/uploads/invoices/ system/uploads/invoices/** +!system/uploads/invoices/ +!system/uploads/invoices/index.html \ No newline at end of file diff --git a/system/uploads/invoices/index.html b/system/uploads/invoices/index.html new file mode 100644 index 00000000..e69de29b From 43a92c5d3bb27b889da11aab6c43c7fade216946 Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Sat, 22 Mar 2025 18:34:51 +0100 Subject: [PATCH 06/24] feat: add fullname field to transaction reports, pdf export and update language file --- system/controllers/export.php | 8 +++++++- system/controllers/reports.php | 5 ++++- ui/ui/admin/reports/list.tpl | 7 ++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/system/controllers/export.php b/system/controllers/export.php index 154d188f..c814fda5 100644 --- a/system/controllers/export.php +++ b/system/controllers/export.php @@ -114,7 +114,10 @@ switch ($action) { $query = ORM::for_table('tbl_transactions') ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts")) ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te")) - ->order_by_desc('id'); + ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if (count($tps) > 0) { $query->where_in('type', $tps); } @@ -158,6 +161,7 @@ switch ($action) { + @@ -170,6 +174,7 @@ switch ($action) { foreach ($x as $value) { $username = $value['username']; + $fullname = $value['fullname']; $plan_name = $value['plan_name']; $type = $value['type']; $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); @@ -181,6 +186,7 @@ switch ($action) { $html .= "" . " + diff --git a/system/controllers/reports.php b/system/controllers/reports.php index 4272a22c..4562ab6e 100644 --- a/system/controllers/reports.php +++ b/system/controllers/reports.php @@ -359,7 +359,10 @@ switch ($action) { $query = ORM::for_table('tbl_transactions') ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts")) ->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te")) - ->order_by_desc('id'); + ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username') + ->select('tbl_transactions.*') + ->select('tbl_customers.fullname', 'fullname') + ->order_by_desc('tbl_transactions.id'); if (count($tps) > 0) { $query->where_in('type', $tps); } diff --git a/ui/ui/admin/reports/list.tpl b/ui/ui/admin/reports/list.tpl index deb249b6..356ebec0 100644 --- a/ui/ui/admin/reports/list.tpl +++ b/ui/ui/admin/reports/list.tpl @@ -94,10 +94,11 @@ - + + @@ -111,6 +112,7 @@ {foreach $d as $ds} + @@ -122,9 +124,8 @@ {/foreach} - - +
' . Lang::T('Username') . '' . Lang::T('Fullname') . ' ' . Lang::T('Plan Name') . ' ' . Lang::T('Type') . ' ' . Lang::T('Plan Price') . '$username$fullname $plan_name $type $price
{Lang::T('Username')}{Lang::T('Fullname')} {Lang::T('Type')} {Lang::T('Plan Name')} {Lang::T('Plan Price')}
{$ds['username']}{$ds['fullname']} {$ds['type']} {$ds['plan_name']} {Lang::moneyFormat($ds['price'])}
{Lang::T('Total')} {Lang::moneyFormat($dr)}
From bad0545be526d5407871dc12972c195630812a41 Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Mon, 24 Mar 2025 10:24:08 +0100 Subject: [PATCH 07/24] feat: enhance messaging system to support multiple channels including email and inbox --- system/autoload/Message.php | 4 +++- system/controllers/message.php | 44 +++++++++++++++++++++++++++------- system/lan/english.json | 6 ++++- ui/ui/admin/message/bulk.tpl | 7 ++++-- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/system/autoload/Message.php b/system/autoload/Message.php index 725d8f39..81236a2a 100644 --- a/system/autoload/Message.php +++ b/system/autoload/Message.php @@ -46,7 +46,7 @@ class Message $txts = str_split($txt, 160); try { foreach ($txts as $txt) { - self::sendSMS($config['sms_url'], $phone, $txt); + self::sendSMS( $phone, $txt); self::logMessage('SMS', $phone, $txt, 'Success'); } } catch (Throwable $e) { @@ -396,9 +396,11 @@ class Message $v->body = nl2br($body); $v->save(); self::logMessage("Inbox", $user->username, $body, "Success"); + return true; } catch (Throwable $e) { $errorMessage = Lang::T("Error adding message to inbox: " . $e->getMessage()); self::logMessage('Inbox', $user->username, $body, 'Error', $errorMessage); + return false; } } diff --git a/system/controllers/message.php b/system/controllers/message.php index 078482a1..4a6f3847 100644 --- a/system/controllers/message.php +++ b/system/controllers/message.php @@ -200,7 +200,9 @@ EOT; ['tbl_customers.phonenumber', 'phonenumber'], ['tbl_user_recharges.customer_id', 'customer_id'], ['tbl_customers.fullname', 'fullname'], - ['tbl_customers.username','username'], + ['tbl_customers.username', 'username'], + ['tbl_customers.email', 'email'], + ['tbl_customers.service_type','service_type'], ]); $customers = $query->find_array(); } else { @@ -288,7 +290,13 @@ EOT; $totalSMSFailed = 0; $totalWhatsappSent = 0; $totalWhatsappFailed = 0; + $totalEmailSent = 0; + $totalEmailFailed = 0; + $totalInboxSent = 0; + $totalInboxFailed = 0; $batchStatus = []; + $subject = Lang::T('Notification Message'); + $form = 'Admin'; foreach ($customers as $customer) { $currentMessage = str_replace( @@ -311,14 +319,14 @@ EOT; if ($test) { $batchStatus[] = [ 'name' => $customer['fullname'], - 'phone' => $customer['phonenumber'], + 'channel' => 'Test Channel', 'status' => 'Test Mode', 'message' => $currentMessage, 'service' => $service, 'router' => $routerName, ]; } else { - if ($via == 'sms' || $via == 'both') { + if ($via === 'sms' || $via === 'both' || $via === 'all') { if (Message::sendSMS($customer['phonenumber'], $currentMessage)) { $totalSMSSent++; $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Sent', 'message' => $currentMessage]; @@ -328,13 +336,33 @@ EOT; } } - if ($via == 'wa' || $via == 'both') { + if ($via === 'wa' || $via == 'both' || $via === 'all') { if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) { $totalWhatsappSent++; - $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage]; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage]; } else { $totalWhatsappFailed++; - $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage]; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage]; + } + } + + if ($via === 'email' || $via === 'all') { + if (Message::sendEmail($customer['email'], $subject, $currentMessage)) { + $totalEmailSent++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Sent', 'message' => $currentMessage]; + } else { + $totalEmailFailed++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Failed', 'message' => $currentMessage]; + } + } + + if ($via === 'inbox' || $via === 'all') { + if (Message::addToInbox($customer['customer_id'], $subject, $currentMessage, $form)) { + $totalInboxSent++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Sent', 'message' => $currentMessage]; + } else { + $totalInboxFailed++; + $batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Failed', 'message' => $currentMessage]; } } } @@ -349,8 +377,8 @@ EOT; 'page' => $page + 1, 'batchStatus' => $batchStatus, 'message' => $currentMessage, - 'totalSent' => $totalSMSSent + $totalWhatsappSent, - 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed, + 'totalSent' => $totalSMSSent + $totalWhatsappSent + $totalEmailSent + $totalInboxSent, + 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed + $totalEmailFailed + $totalInboxFailed, 'hasMore' => $hasMore, 'service' => $service, 'router' => $routerName, diff --git a/system/lan/english.json b/system/lan/english.json index f81d2f92..dced796a 100644 --- a/system/lan/english.json +++ b/system/lan/english.json @@ -381,5 +381,9 @@ "_Clear_old_logs_": " Clear old logs?", "Clean_up_Logs": "Clean up Logs", "ID": "ID", - "Date_Sent": "Date Sent" + "Date_Sent": "Date Sent", + "Notification_Message": "Notification Message", + "Share": "Share", + "Previous": "Previous", + "Email_not_sent__Mailer_Error__": "Email not sent, Mailer Error: " } \ No newline at end of file diff --git a/ui/ui/admin/message/bulk.tpl b/ui/ui/admin/message/bulk.tpl index a8a57dba..a0e6a903 100644 --- a/ui/ui/admin/message/bulk.tpl +++ b/ui/ui/admin/message/bulk.tpl @@ -50,6 +50,9 @@
- +
@@ -41,8 +42,10 @@ @@ -55,7 +58,8 @@ - + @@ -75,10 +79,21 @@ {Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')} +
+ +
+ +
+

+ {Lang::T('You can also use the below placeholders here too')}. +

+
- + {Lang::T('Testing [if checked no real message is sent]')}
@@ -96,7 +111,8 @@
- + {Lang::T('Cancel')}
@@ -129,6 +145,34 @@ + {literal} + {include file = "sections/footer.tpl" } \ No newline at end of file diff --git a/ui/ui/admin/message/single.tpl b/ui/ui/admin/message/single.tpl index 771b7150..434200d1 100644 --- a/ui/ui/admin/message/single.tpl +++ b/ui/ui/admin/message/single.tpl @@ -23,12 +23,26 @@
+ + + + + + +
+
+ +
+ +
+

+ {Lang::T('You can also use the below placeholders here too')}. +

+
@@ -64,6 +78,33 @@
+ {include file="sections/footer.tpl"} \ No newline at end of file From cf60c470b1bc34563d3c58029d4a491a61b8e61c Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Wed, 9 Apr 2025 12:50:30 +0100 Subject: [PATCH 21/24] feat: add subject validation for message types and display error if missing --- ui/ui/admin/customers/list.tpl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ui/ui/admin/customers/list.tpl b/ui/ui/admin/customers/list.tpl index bdbaf2a1..3d2ab3f0 100644 --- a/ui/ui/admin/customers/list.tpl +++ b/ui/ui/admin/customers/list.tpl @@ -274,6 +274,16 @@ return; } + if (messageType == 'all' || messageType == 'inbox' || messageType == 'email' && !subject) { + Swal.fire({ + title: 'Error!', + text: "{Lang::T('Please enter a subject for the message.')}", + icon: 'error', + confirmButtonText: 'OK' + }); + return; + } + // Disable the button and show loading text $(this).prop('disabled', true).text('{Lang::T('Sending...')}'); @@ -345,15 +355,15 @@ switch (messageType) { case 'all': subjectField.placeholder = 'Enter a subject for all channels'; - subjectField.required = true; + subjectField.required = true; break; case 'email': subjectField.placeholder = 'Enter a subject for email'; - subjectField.required = true; + subjectField.required = true; break; case 'inbox': subjectField.placeholder = 'Enter a subject for inbox'; - subjectField.required = true; + subjectField.required = true; break; default: subjectField.placeholder = 'Enter message subject here'; From e2f24c0cc62ef49314dc89bf29910040ba43371c Mon Sep 17 00:00:00 2001 From: Focuslinkstech <45756999+Focuslinkstech@users.noreply.github.com> Date: Wed, 9 Apr 2025 20:16:11 +0100 Subject: [PATCH 22/24] fix: correct subject field handling in message sending logic and update related IDs in bulk template --- system/controllers/message.php | 8 ++++---- ui/ui/admin/message/bulk.tpl | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/system/controllers/message.php b/system/controllers/message.php index 1df8144c..a1407698 100644 --- a/system/controllers/message.php +++ b/system/controllers/message.php @@ -58,7 +58,7 @@ EOT; } $id_customer = $_POST['id_customer'] ?? ''; - $message = $_POST['message']?? ''; + $message = $_POST['message'] ?? ''; $via = $_POST['via'] ?? ''; $subject = $_POST['subject'] ?? ''; @@ -156,7 +156,7 @@ EOT; $batch = $_REQUEST['batch'] ?? 100; $page = $_REQUEST['page'] ?? 0; $router = $_REQUEST['router'] ?? null; - $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? true : false; + $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on'; $service = $_REQUEST['service'] ?? ''; $subject = $_REQUEST['subject'] ?? ''; @@ -164,7 +164,7 @@ EOT; die(json_encode(['status' => 'error', 'message' => LANG::T('All fields are required')])); } - if ($via === 'all' || $via === 'email' || $via === 'inbox' && empty($subject)) { + if (in_array($via, ['all', 'email', 'inbox']) && empty($subject)) { die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.'])); } @@ -438,7 +438,7 @@ EOT; if ($via === 'all' || $via === 'email' || $via === 'inbox' && empty($subject)) { die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.'])); } - + // Prepare to send messages $sentCount = 0; $failedCount = 0; diff --git a/ui/ui/admin/message/bulk.tpl b/ui/ui/admin/message/bulk.tpl index 9610309d..7d7c677a 100644 --- a/ui/ui/admin/message/bulk.tpl +++ b/ui/ui/admin/message/bulk.tpl @@ -79,10 +79,10 @@ {Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')} -
+
-

@@ -148,8 +148,8 @@ {if isset($xfooter)} - {$xfooter} +{$xfooter} {/if} {if $_c['tawkto'] != ''} - - - - {/if} + (function () { + var s1 = document.createElement("script"), + s0 = document.getElementsByTagName("script")[0]; + s1.async = true; + s1.src = 'https://embed.tawk.to/{$_c['tawkto']}'; + s1.charset = 'UTF-8'; + s1.setAttribute('crossorigin', '*'); + s0.parentNode.insertBefore(s1, s0); + })(); + + +{/if} - + }); + {literal} - + return null; + } + {/literal}