diff --git a/system/controllers/logout.php b/system/controllers/logout.php
new file mode 100644
index 0000000..3db002b
--- /dev/null
+++ b/system/controllers/logout.php
@@ -0,0 +1,12 @@
+assign('_title', 'PHPNuxBill Logs');
+$ui->assign('_system_menu', 'logs');
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+}
+
+
+switch ($action) {
+ case 'list-csv':
+ $logs = ORM::for_table('tbl_logs')
+ ->select('id')
+ ->select('date')
+ ->select('type')
+ ->select('description')
+ ->select('userid')
+ ->select('ip')
+ ->order_by_asc('id')->find_array();
+ $h = false;
+ set_time_limit(-1);
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header("Content-type: text/csv");
+ header('Content-Disposition: attachment;filename="activity-logs_' . date('Y-m-d_H_i') . '.csv"');
+ header('Content-Transfer-Encoding: binary');
+ foreach ($logs as $log) {
+ $ks = [];
+ $vs = [];
+ foreach ($log as $k => $v) {
+ $ks[] = $k;
+ $vs[] = $v;
+ }
+ if (!$h) {
+ echo '"' . implode('";"', $ks) . "\"\n";
+ $h = true;
+ }
+ echo '"' . implode('";"', $vs) . "\"\n";
+ }
+ break;
+ case 'radius-csv':
+ $logs = ORM::for_table('radpostauth')
+ ->select('id')
+ ->select('username')
+ ->select('pass')
+ ->select('reply')
+ ->select('authdate')
+ ->order_by_asc('id')->find_array();
+ $h = false;
+ set_time_limit(-1);
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header("Content-type: text/csv");
+ header('Content-Disposition: attachment;filename="radius-logs_' . date('Y-m-d_H_i') . '.csv"');
+ header('Content-Transfer-Encoding: binary');
+ foreach ($logs as $log) {
+ $ks = [];
+ $vs = [];
+ foreach ($log as $k => $v) {
+ $ks[] = $k;
+ $vs[] = $v;
+ }
+ if (!$h) {
+ echo '"' . implode('";"', $ks) . "\"\n";
+ $h = true;
+ }
+ echo '"' . implode('";"', $vs) . "\"\n";
+ }
+ break;
+
+ case 'list':
+ $q = (_post('q') ? _post('q') : _get('q'));
+ $keep = _post('keep');
+ if (!empty($keep)) {
+ ORM::raw_execute("DELETE FROM tbl_logs WHERE UNIX_TIMESTAMP(date) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))");
+ r2(U . "logs/list/", 's', "Delete logs older than $keep days");
+ }
+ if ($q != '') {
+ $query = ORM::for_table('tbl_logs')->where_like('description', '%' . $q . '%')->order_by_desc('id');
+ $d = Paginator::findMany($query, ['q' => $q]);
+ } else {
+ $query = ORM::for_table('tbl_logs')->order_by_desc('id');
+ $d = Paginator::findMany($query);
+ }
+
+ $ui->assign('d', $d);
+ $ui->assign('q', $q);
+ $ui->display('logs.tpl');
+ break;
+ case 'radius':
+ $q = (_post('q') ? _post('q') : _get('q'));
+ $keep = _post('keep');
+ if (!empty($keep)) {
+ ORM::raw_execute("DELETE FROM radpostauth WHERE UNIX_TIMESTAMP(authdate) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))", [], 'radius');
+ r2(U . "logs/radius/", 's', "Delete logs older than $keep days");
+ }
+ if ($q != '') {
+ $query = ORM::for_table('radpostauth', 'radius')->where_like('username', '%' . $q . '%')->order_by_desc('id');
+ $d = Paginator::findMany($query, ['q' => $q]);
+ } else {
+ $query = ORM::for_table('radpostauth', 'radius')->order_by_desc('id');
+ $d = Paginator::findMany($query);
+ }
+
+ $ui->assign('d', $d);
+ $ui->assign('q', $q);
+ $ui->display('logs-radius.tpl');
+ break;
+
+
+ default:
+ r2(U . 'logs/list/', 's', '');
+}
diff --git a/system/controllers/map.php b/system/controllers/map.php
new file mode 100644
index 0000000..9ef0827
--- /dev/null
+++ b/system/controllers/map.php
@@ -0,0 +1,54 @@
+assign('_system_menu', 'map');
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+if (empty($action)) {
+ $action = 'customer';
+}
+
+switch ($action) {
+ case 'customer':
+ if(!empty(_req('search'))){
+ $search = _req('search');
+ $query = ORM::for_table('tbl_customers')->whereRaw("coordinates != '' AND fullname LIKE '%$search%' OR username LIKE '%$search%' OR email LIKE '%$search%' OR phonenumber LIKE '%$search%'")->order_by_desc('fullname');
+ $c = Paginator::findMany($query, ['search' => $search], 50);
+ }else{
+ $query = ORM::for_table('tbl_customers')->where_not_equal('coordinates','');
+ $c = Paginator::findMany($query, ['search'=>''], 50);
+ }
+ $customerData = [];
+
+ foreach ($c as $customer) {
+ if (!empty($customer->coordinates)) {
+ $customerData[] = [
+ 'id' => $customer->id,
+ 'name' => $customer->fullname,
+ 'balance' => $customer->balance,
+ 'address' => $customer->address,
+ 'direction' => $customer->coordinates,
+ 'info' => Lang::T("Username") . ": " . $customer->username . " - " . Lang::T("Full Name") . ": " . $customer->fullname . " - " . Lang::T("Email") . ": " . $customer->email . " - " . Lang::T("Phone") . ": " . $customer->phonenumber . " - " . Lang::T("Service Type") . ": " . $customer->service_type,
+ 'coordinates' => '[' . $customer->coordinates . ']',
+ ];
+ }
+ }
+ $ui->assign('search', $search);
+ $ui->assign('customers', $customerData);
+ $ui->assign('xheader', '');
+ $ui->assign('_title', Lang::T('Customer Geo Location Information'));
+ $ui->assign('xfooter', '');
+ $ui->display('customers-map.tpl');
+ break;
+
+ default:
+ r2(U . 'map/customer', 'e', 'action not defined');
+ break;
+}
diff --git a/system/controllers/message.php b/system/controllers/message.php
new file mode 100644
index 0000000..c0daae4
--- /dev/null
+++ b/system/controllers/message.php
@@ -0,0 +1,242 @@
+assign('_title', Lang::T('Send Message'));
+$ui->assign('_system_menu', 'message');
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+if (empty($action)) {
+ $action = 'send';
+}
+
+switch ($action) {
+ case 'send':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+
+ $select2_customer = <<
+document.addEventListener("DOMContentLoaded", function(event) {
+ $('#personSelect').select2({
+ theme: "bootstrap",
+ ajax: {
+ url: function(params) {
+ if(params.term != undefined){
+ return './index.php?_route=autoload/customer_select2&s='+params.term;
+ }else{
+ return './index.php?_route=autoload/customer_select2';
+ }
+ }
+ }
+ });
+});
+
+EOT;
+ if (isset($routes['2']) && !empty($routes['2'])) {
+ $ui->assign('cust', ORM::for_table('tbl_customers')->find_one($routes['2']));
+ }
+ $id = $routes['2'];
+ $ui->assign('id', $id);
+ $ui->assign('xfooter', $select2_customer);
+ $ui->display('message.tpl');
+ break;
+
+ case 'send-post':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+
+ // Get form data
+ $id_customer = $_POST['id_customer'];
+ $message = $_POST['message'];
+ $via = $_POST['via'];
+
+ // Check if fields are empty
+ if ($id_customer == '' or $message == '' or $via == '') {
+ r2(U . 'message/send', 'e', Lang::T('All field is required'));
+ } else {
+ // Get customer details from the database
+ $c = ORM::for_table('tbl_customers')->find_one($id_customer);
+
+ // Replace placeholders in the message with actual values
+ $message = str_replace('[[name]]', $c['fullname'], $message);
+ $message = str_replace('[[user_name]]', $c['username'], $message);
+ $message = str_replace('[[phone]]', $c['phonenumber'], $message);
+ $message = str_replace('[[company_name]]', $config['CompanyName'], $message);
+
+ // Send the message
+ if ($via == 'sms' || $via == 'both') {
+ $smsSent = Message::sendSMS($c['phonenumber'], $message);
+ }
+
+ if ($via == 'wa' || $via == 'both') {
+ $waSent = Message::sendWhatsapp($c['phonenumber'], $message);
+ }
+
+ if (isset($smsSent) || isset($waSent)) {
+ r2(U . 'message/send', 's', Lang::T('Message Sent Successfully'));
+ } else {
+ r2(U . 'message/send', 'e', Lang::T('Failed to send message'));
+ }
+ }
+ break;
+
+ case 'send_bulk':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+
+ // Get form data
+ $group = $_POST['group'];
+ $message = $_POST['message'];
+ $via = $_POST['via'];
+ $test = isset($_POST['test']) && $_POST['test'] === 'on' ? 'yes' : 'no';
+ $batch = $_POST['batch'];
+ $delay = $_POST['delay'];
+
+ // Initialize counters
+ $totalSMSSent = 0;
+ $totalSMSFailed = 0;
+ $totalWhatsappSent = 0;
+ $totalWhatsappFailed = 0;
+ $batchStatus = [];
+
+ if (_req('send') == 'now') {
+ // Check if fields are empty
+ if ($group == '' || $message == '' || $via == '') {
+ r2(U . 'message/send_bulk', 'e', Lang::T('All fields are required'));
+ } else {
+ // Get customer details from the database based on the selected group
+ if ($group == 'all') {
+ $customers = ORM::for_table('tbl_customers')->find_many()->as_array();
+ } elseif ($group == 'new') {
+ // Get customers created just a month ago
+ $customers = ORM::for_table('tbl_customers')->where_raw("DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)")->find_many()->as_array();
+ } elseif ($group == 'expired') {
+ // Get expired user recharges where status is 'off'
+ $expired = ORM::for_table('tbl_user_recharges')->where('status', 'off')->find_many();
+ $customer_ids = [];
+ foreach ($expired as $recharge) {
+ $customer_ids[] = $recharge->customer_id;
+ }
+ $customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_many()->as_array();
+ } elseif ($group == 'active') {
+ // Get active user recharges where status is 'on'
+ $active = ORM::for_table('tbl_user_recharges')->where('status', 'on')->find_many();
+ $customer_ids = [];
+ foreach ($active as $recharge) {
+ $customer_ids[] = $recharge->customer_id;
+ }
+ $customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_many()->as_array();
+ } elseif ($group == 'pppoe') {
+ // Get customers with PPPoE service type
+ $customers = ORM::for_table('tbl_customers')->where('service_type', 'PPPoE')->find_many()->as_array();
+ } elseif ($group == 'hotspot') {
+ // Get customers with Hotspot service type
+ $customers = ORM::for_table('tbl_customers')->where('service_type', 'Hotspot')->find_many()->as_array();
+ }
+
+ // Set the batch size
+ $batchSize = $batch;
+
+ // Calculate the number of batches
+ $totalCustomers = count($customers);
+ $totalBatches = ceil($totalCustomers / $batchSize);
+
+ // Loop through batches
+ for ($batchIndex = 0; $batchIndex < $totalBatches; $batchIndex++) {
+ // Get the starting and ending index for the current batch
+ $start = $batchIndex * $batchSize;
+ $end = min(($batchIndex + 1) * $batchSize, $totalCustomers);
+ $batchCustomers = array_slice($customers, $start, $end - $start);
+
+ // Loop through customers in the current batch and send messages
+ foreach ($batchCustomers as $customer) {
+ // Create a copy of the original message for each customer and save it as currentMessage
+ $currentMessage = $message;
+ $currentMessage = str_replace('[[name]]', $customer['fullname'], $currentMessage);
+ $currentMessage = str_replace('[[user_name]]', $customer['username'], $currentMessage);
+ $currentMessage = str_replace('[[phone]]', $customer['phonenumber'], $currentMessage);
+ $currentMessage = str_replace('[[company_name]]', $config['CompanyName'], $currentMessage);
+
+ // Send the message based on the selected method
+ if ($test === 'yes') {
+ // Only for testing, do not send messages to customers
+ $batchStatus[] = [
+ 'name' => $customer['fullname'],
+ 'phone' => $customer['phonenumber'],
+ 'message' => $currentMessage,
+ 'status' => 'Test Mode - Message not sent'
+ ];
+ } else {
+ // Send the actual messages
+ if ($via == 'sms' || $via == 'both') {
+ $smsSent = Message::sendSMS($customer['phonenumber'], $currentMessage);
+ if ($smsSent) {
+ $totalSMSSent++;
+ $batchStatus[] = [
+ 'name' => $customer['fullname'],
+ 'phone' => $customer['phonenumber'],
+ 'message' => $currentMessage,
+ 'status' => 'SMS Message Sent'
+ ];
+ } else {
+ $totalSMSFailed++;
+ $batchStatus[] = [
+ 'name' => $customer['fullname'],
+ 'phone' => $customer['phonenumber'],
+ 'message' => $currentMessage,
+ 'status' => 'SMS Message Failed'
+ ];
+ }
+ }
+
+ if ($via == 'wa' || $via == 'both') {
+ $waSent = Message::sendWhatsapp($customer['phonenumber'], $currentMessage);
+ if ($waSent) {
+ $totalWhatsappSent++;
+ $batchStatus[] = [
+ 'name' => $customer['fullname'],
+ 'phone' => $customer['phonenumber'],
+ 'message' => $currentMessage,
+ 'status' => 'WhatsApp Message Sent'
+ ];
+ } else {
+ $totalWhatsappFailed++;
+ $batchStatus[] = [
+ 'name' => $customer['fullname'],
+ 'phone' => $customer['phonenumber'],
+ 'message' => $currentMessage,
+ 'status' => 'WhatsApp Message Failed'
+ ];
+ }
+ }
+ }
+ }
+
+ // Introduce a delay between each batch
+ if ($batchIndex < $totalBatches - 1) {
+ sleep($delay);
+ }
+ }
+ }
+ }
+ $ui->assign('batchStatus', $batchStatus);
+ $ui->assign('totalSMSSent', $totalSMSSent);
+ $ui->assign('totalSMSFailed', $totalSMSFailed);
+ $ui->assign('totalWhatsappSent', $totalWhatsappSent);
+ $ui->assign('totalWhatsappFailed', $totalWhatsappFailed);
+ $ui->display('message-bulk.tpl');
+ break;
+
+ default:
+ r2(U . 'message/send_sms', 'e', 'action not defined');
+}
diff --git a/system/controllers/messages.php b/system/controllers/messages.php
new file mode 100644
index 0000000..3042f8d
--- /dev/null
+++ b/system/controllers/messages.php
@@ -0,0 +1,22 @@
+assign('_title', Lang::T('Messages'));
+$ui->assign('_system_menu', 'messages');
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+// Fetch all messages
+$msgs = ORM::for_table('tbl_message')
+ ->order_by_desc('date')
+ ->find_array();
+
+$ui->assign('messages', $msgs);
+$ui->display('message-list.tpl');
+
+?>