From 0a3205915f5245673f5437960a2252d96cdb1068 Mon Sep 17 00:00:00 2001 From: Focuslinks Digital Solutions Date: Sun, 9 Feb 2025 16:06:59 +0100 Subject: [PATCH] Enhancement and Improvements Refactor CSRF class: improve token handling and update session variable names Replace bulk message with ajax based message sending. Added support for multiple recipients in bulk message, and also router based filtering. Add support for multiple recipients in bulk message from customer list as requested by one of our Member. you can now send messages to multiple recipients at once from customer list. Added Exception for CRON but not tested yet. i dont have multiple routers. Added notify to know if cron has been executed or not. --- system/autoload/Csrf.php | 127 ++++++---- system/controllers/message.php | 418 +++++++++++++++++++++------------ system/cron.php | 164 +++++++------ ui/ui/admin/customers/list.tpl | 280 +++++++++++++++++----- ui/ui/admin/message/bulk.tpl | 367 ++++++++++++++++------------- 5 files changed, 863 insertions(+), 493 deletions(-) diff --git a/system/autoload/Csrf.php b/system/autoload/Csrf.php index 57752a0e..b7c7a170 100644 --- a/system/autoload/Csrf.php +++ b/system/autoload/Csrf.php @@ -6,50 +6,83 @@ **/ -class Csrf -{ - private static $tokenExpiration = 1800; // 30 minutes - - public static function generateToken($length = 16) - { - return bin2hex(random_bytes($length)); - } - - public static function validateToken($token, $storedToken) - { - return hash_equals($token, $storedToken); - } - - public static function check($token) - { - global $config; - if($config['csrf_enabled'] == 'yes') { - if (isset($_SESSION['csrf_token'], $_SESSION['csrf_token_time'], $token)) { - $storedToken = $_SESSION['csrf_token']; - $tokenTime = $_SESSION['csrf_token_time']; - - if (time() - $tokenTime > self::$tokenExpiration) { - self::clearToken(); - return false; - } - - return self::validateToken($token, $storedToken); - } - return false; - } - return true; - } - - public static function generateAndStoreToken() - { - $token = self::generateToken(); - $_SESSION['csrf_token'] = $token; - $_SESSION['csrf_token_time'] = time(); - return $token; - } - - public static function clearToken() - { - unset($_SESSION['csrf_token'], $_SESSION['csrf_token_time']); - } -} + class Csrf + { + private const int TOKEN_LENGTH = 16; + private const int TOKEN_EXPIRATION = 1800; + + /** + * Generate a CSRF token. + * + * @param int $length + * @return string + */ + public static function generateToken(int $length = self::TOKEN_LENGTH): string + { + return bin2hex(random_bytes($length)); + } + + /** + * Validate the provided CSRF token against the stored token. + * + * @param string $token + * @param string $storedToken + * @return bool + */ + public static function validateToken(string $token, string $storedToken): bool + { + return hash_equals($token, $storedToken); + } + + /** + * Check if the CSRF token is valid. + * + * @param string|null $token + * @return bool + */ + public static function check(?string $token): bool + { + global $config; + + if ($config['csrf_enabled'] === 'yes') { + if (isset($_SESSION['nux_csrf_token'], $_SESSION['nux_csrf_token_time'], $token)) { + $storedToken = $_SESSION['nux_csrf_token']; + $tokenTime = $_SESSION['nux_csrf_token_time']; + + if (time() - $tokenTime > self::TOKEN_EXPIRATION) { + self::clearToken(); + return false; + } + + return self::validateToken($token, $storedToken); + } + + return false; + } + + return true; // CSRF is disabled + } + + /** + * Generate and store a new CSRF token in the session. + * + * @return string + */ + public static function generateAndStoreToken(): string + { + $token = self::generateToken(); + $_SESSION['nux_csrf_token'] = $token; + $_SESSION['nux_csrf_token_time'] = time(); + return $token; + } + + /** + * Clear the stored CSRF token from the session. + * + * @return void + */ + public static function clearToken(): void + { + unset($_SESSION['nux_csrf_token'], $_SESSION['nux_csrf_token_time']); + } + } \ No newline at end of file diff --git a/system/controllers/message.php b/system/controllers/message.php index 55774d9f..ec06ea1b 100644 --- a/system/controllers/message.php +++ b/system/controllers/message.php @@ -22,7 +22,7 @@ switch ($action) { _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); } -$appUrl = APP_URL; + $appUrl = APP_URL; $select2_customer = << @@ -74,22 +74,22 @@ EOT; $message = str_replace('[[user_name]]', $c['username'], $message); $message = str_replace('[[phone]]', $c['phonenumber'], $message); $message = str_replace('[[company_name]]', $config['CompanyName'], $message); - if (strpos($message, '[[payment_link]]') !== false) { - // token only valid for 1 day, for security reason - $token = User::generateToken($c['id'], 1); - if (!empty($token['token'])) { - $tur = ORM::for_table('tbl_user_recharges') - ->where('customer_id', $c['id']) - //->where('namebp', $package) - ->find_one(); - if ($tur) { - $url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']); - $message = str_replace('[[payment_link]]', $url, $message); - } - } else { - $message = str_replace('[[payment_link]]', '', $message); - } - } + if (strpos($message, '[[payment_link]]') !== false) { + // token only valid for 1 day, for security reason + $token = User::generateToken($c['id'], 1); + if (!empty($token['token'])) { + $tur = ORM::for_table('tbl_user_recharges') + ->where('customer_id', $c['id']) + //->where('namebp', $package) + ->find_one(); + if ($tur) { + $url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']); + $message = str_replace('[[payment_link]]', $url, $message); + } + } else { + $message = str_replace('[[payment_link]]', '', $message); + } + } //Send the message @@ -113,158 +113,274 @@ EOT; if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); } + + $ui->assign('routers', ORM::forTable('tbl_routers')->where('enabled', '1')->find_many()); + $ui->display('admin/message/bulk.tpl'); + break; + + case 'send_bulk_ajax': + // Check user permissions + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + die(json_encode(['status' => 'error', 'message' => 'Permission denied'])); + } + set_time_limit(0); - // Initialize counters + + // Get request parameters + $group = $_REQUEST['group'] ?? ''; + $message = $_REQUEST['message'] ?? ''; + $via = $_REQUEST['via'] ?? ''; + $batch = $_REQUEST['batch'] ?? 100; + $page = $_REQUEST['page'] ?? 0; + $router = $_REQUEST['router'] ?? null; + $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? true : false; + + if (empty($group) || empty($message) || empty($via)) { + die(json_encode(['status' => 'error', 'message' => 'All fields are required'])); + } + + // Get batch of customers based on group + $startpoint = $page * $batch; + $customers = []; + + if (isset($router) && !empty($router)) { + $router = ORM::for_table('tbl_routers')->find_one($router); + if (!$router) { + die(json_encode(['status' => 'error', 'message' => 'Invalid router'])); + } + + $query = ORM::for_table('tbl_user_recharges') + ->left_outer_join('tbl_customers', 'tbl_user_recharges.customer_id = tbl_customers.id') + ->where('tbl_user_recharges.routers', $router->name) + ->offset($startpoint) + ->limit($batch); + + switch ($group) { + case 'all': + // No additional conditions needed + break; + case 'new': + $query->where_raw("DATE(recharged_on) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"); + break; + case 'expired': + $query->where('tbl_user_recharges.status', 'off'); + break; + case 'active': + $query->where('tbl_user_recharges.status', 'on'); + break; + } + + $query->selects([ + ['tbl_customers.phonenumber', 'phonenumber'], + ['tbl_user_recharges.customer_id', 'customer_id'], + ['tbl_customers.fullname', 'fullname'], + ]); + $customers = $query->find_array(); + } else { + switch ($group) { + case 'all': + $customers = ORM::for_table('tbl_customers')->offset($startpoint)->limit($batch)->find_array(); + break; + case 'new': + $customers = ORM::for_table('tbl_customers') + ->where_raw("DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)") + ->offset($startpoint)->limit($batch)->find_array(); + break; + case 'expired': + $customers = ORM::for_table('tbl_user_recharges')->where('status', 'off') + ->select('customer_id')->offset($startpoint)->limit($batch)->find_array(); + break; + case 'active': + $customers = ORM::for_table('tbl_user_recharges')->where('status', 'on') + ->select('customer_id')->offset($startpoint)->limit($batch)->find_array(); + break; + } + } + + // Ensure $customers is always an array + if (!$customers) { + $customers = []; + } + + // Calculate total customers for the group + $totalCustomers = 0; + if ($router) { + switch ($group) { + case 'all': + $totalCustomers = ORM::for_table('tbl_user_recharges')->where('routers', $router->routers)->count(); + break; + case 'new': + $totalCustomers = ORM::for_table('tbl_user_recharges') + ->where_raw("DATE(recharged_on) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)") + ->where('routers', $router->routers) + ->count(); + break; + case 'expired': + $totalCustomers = ORM::for_table('tbl_user_recharges')->where('status', 'off')->where('routers', $router->routers)->count(); + break; + case 'active': + $totalCustomers = ORM::for_table('tbl_user_recharges')->where('status', 'on')->where('routers', $router->routers)->count(); + break; + } + } else { + switch ($group) { + case 'all': + $totalCustomers = ORM::for_table('tbl_customers')->count(); + break; + case 'new': + $totalCustomers = ORM::for_table('tbl_customers') + ->where_raw("DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)") + ->count(); + break; + case 'expired': + $totalCustomers = ORM::for_table('tbl_user_recharges')->where('status', 'off')->count(); + break; + case 'active': + $totalCustomers = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count(); + break; + } + } + + // Send messages $totalSMSSent = 0; $totalSMSFailed = 0; $totalWhatsappSent = 0; $totalWhatsappFailed = 0; - $totalCustomers = 0; - $batchStatus = $_SESSION['batchStatus']; - $page = _req('page', -1); + $batchStatus = []; - if (_req('send') == 'now') { - // Get form data - $group = $_REQUEST['group']; - $message = $_REQUEST['message']; - $via = $_REQUEST['via']; - $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? 'yes' : 'no'; - $batch = $_REQUEST['batch']; - $delay = $_REQUEST['delay']; + foreach ($customers as $customer) { + $currentMessage = str_replace( + ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'], + [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']], + $message + ); - $ui->assign('group', $group); - $ui->assign('message', $message); - $ui->assign('via', $via); - $ui->assign('test', $test); - $ui->assign('batch', $batch); - $ui->assign('delay', $delay); - if($page<0){ - $batchStatus = []; - $page = 0; + $phoneNumber = preg_replace('/\D/', '', $customer['phonenumber']); + + if (empty($phoneNumber)) { + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => '', + 'status' => 'No Phone Number' + ]; + continue; } - $startpoint = $page * $batch; - $page++; - // Check if fields are empty - if ($group == '' || $message == '' || $via == '') { - r2(getUrl('message/send_bulk'), 'e', Lang::T('All fields are required')); + + if ($test) { + $batchStatus[] = [ + 'name' => $customer['fullname'], + 'phone' => $customer['phonenumber'], + 'status' => 'Test Mode', + 'message' => $currentMessage + ]; } else { - // Get customer details from the database based on the selected group - if ($group == 'all') { - $customers = ORM::for_table('tbl_customers') - ->offset($startpoint) - ->limit($batch)->find_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)") - ->offset($startpoint)->limit($batch) - ->find_array(); - } elseif ($group == 'expired') { - // Get expired user recharges where status is 'off' - $expired = ORM::for_table('tbl_user_recharges')->select('customer_id')->where('status', 'off') - ->offset($startpoint)->limit($batch) - ->find_array(); - $customer_ids = array_column($expired, 'customer_id'); - $customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_array(); - } elseif ($group == 'active') { - // Get active user recharges where status is 'on' - $active = ORM::for_table('tbl_user_recharges')->select('customer_id')->where('status', 'on') - ->offset($startpoint)->limit($batch) - ->find_array(); - $customer_ids = array_column($active, 'customer_id'); - $customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_array(); + if ($via == 'sms' || $via == 'both') { + if (Message::sendSMS($customer['phonenumber'], $currentMessage)) { + $totalSMSSent++; + $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Sent', 'message' => $currentMessage]; + } else { + $totalSMSFailed++; + $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Failed', 'message' => $currentMessage]; + } } - // Set the batch size - $batchSize = $batch; - - // Calculate the number of batches - $totalCustomers = count($customers); - $totalBatches = ceil($totalCustomers / $batchSize); - - // Loop through customers in the current batch and send messages - foreach ($customers 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); - - if(empty($customer['phonenumber'])){ - $batchStatus[] = [ - 'name' => $customer['fullname'], - 'phone' => $customer['phonenumber'], - 'message' => $currentMessage, - 'status' => 'No Phone Number' - ]; - }else - // 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' - ]; + if ($via == 'wa' || $via == 'both') { + if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) { + $totalWhatsappSent++; + $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage]; } 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' - ]; - } - } + $totalWhatsappFailed++; + $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage]; } } } } - $ui->assign('page', $page); - $ui->assign('totalCustomers', $totalCustomers); - $_SESSION['batchStatus'] = $batchStatus; - $ui->assign('batchStatus', $batchStatus); - $ui->assign('totalSMSSent', $totalSMSSent); - $ui->assign('totalSMSFailed', $totalSMSFailed); - $ui->assign('totalWhatsappSent', $totalWhatsappSent); - $ui->assign('totalWhatsappFailed', $totalWhatsappFailed); - $ui->display('admin/message/bulk.tpl'); + + // Calculate if there are more customers to process + $hasMore = ($startpoint + $batch) < $totalCustomers; + + // Return JSON response + echo json_encode([ + 'status' => 'success', + 'page' => $page + 1, + 'batchStatus' => $batchStatus, + 'message' => $currentMessage, + 'totalSent' => $totalSMSSent + $totalWhatsappSent, + 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed, + 'hasMore' => $hasMore + ]); break; + case 'send_bulk_selected': + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Set headers + header('Content-Type: application/json'); + header('Cache-Control: no-cache, no-store, must-revalidate'); + + // Get the posted data + $customerIds = $_POST['customer_ids'] ?? []; + $via = $_POST['message_type'] ?? ''; + $message = isset($_POST['message']) ? trim($_POST['message']) : ''; + if (empty($customerIds) || empty($message) || empty($via)) { + echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid customer IDs, Message, or Message Type.')]); + exit; + } + + // Prepare to send messages + $sentCount = 0; + $failedCount = 0; + $subject = Lang::T('Notification Message'); + $form = 'Admin'; + + foreach ($customerIds as $customerId) { + $customer = ORM::for_table('tbl_customers')->where('id', $customerId)->find_one(); + if ($customer) { + $messageSent = false; + + // Check the message type and send accordingly + try { + if ($via === 'sms' || $via === 'all') { + $messageSent = Message::sendSMS($customer['phonenumber'], $message); + } + if (!$messageSent && ($via === 'wa' || $via === 'all')) { + $messageSent = Message::sendWhatsapp($customer['phonenumber'], $message); + } + if (!$messageSent && ($via === 'inbox' || $via === 'all')) { + Message::addToInbox($customer['id'], $subject, $message, $form); + $messageSent = true; + } + if (!$messageSent && ($via === 'email' || $via === 'all')) { + $messageSent = Message::sendEmail($customer['email'], $subject, $message); + } + } catch (Throwable $e) { + $messageSent = false; + $failedCount++; + sendTelegram('Failed to send message to ' . $e->getMessage()); + _log('Failed to send message to ' . $customer['fullname'] . ': ' . $e->getMessage()); + continue; + } + + if ($messageSent) { + $sentCount++; + } else { + $failedCount++; + } + } else { + $failedCount++; + } + } + + // Prepare the response + echo json_encode([ + 'status' => 'success', + 'totalSent' => $sentCount, + 'totalFailed' => $failedCount + ]); + } else { + header('Content-Type: application/json'); + echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid request method.')]); + } + break; default: r2(getUrl('message/send_sms'), 'e', 'action not defined'); } diff --git a/system/cron.php b/system/cron.php index 16bef18a..6165c734 100644 --- a/system/cron.php +++ b/system/cron.php @@ -30,7 +30,7 @@ if (php_sapi_name() !== 'cli') { echo "PHP Time\t" . date('Y-m-d H:i:s') . "\n"; $res = ORM::raw_execute('SELECT NOW() AS WAKTU;'); $statement = ORM::get_last_statement(); -$rows = array(); +$rows = []; while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { echo "MYSQL Time\t" . $row['WAKTU'] . "\n"; } @@ -45,80 +45,111 @@ echo "Found " . count($d) . " user(s)\n"; run_hook('cronjob'); #HOOK foreach ($d as $ds) { - $date_now = strtotime(date("Y-m-d H:i:s")); - $expiration = strtotime($ds['expiration'] . ' ' . $ds['time']); - echo $ds['expiration'] . " : " . (($isCli) ? $ds['username'] : Lang::maskText($ds['username'])); - if ($date_now >= $expiration) { - echo " : EXPIRED \r\n"; - $u = ORM::for_table('tbl_user_recharges')->where('id', $ds['id'])->find_one(); - $c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one(); - $p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one(); - if (empty($c)) { - $c = $u; - } - $dvc = Package::getDevice($p); - if ($_app_stage != 'demo') { - if (file_exists($dvc)) { - require_once $dvc; - (new $p['device'])->remove_customer($c, $p); - } else { - echo "Cron error Devices $p[device] not found, cannot disconnect $c[username]"; - Message::sendTelegram("Cron error Devices $p[device] not found, cannot disconnect $c[username]"); - } - } - echo Message::sendPackageNotification($c, $u['namebp'], $p['price'], $textExpired, $config['user_notification_expired']) . "\n"; - //update database user dengan status off - $u->status = 'off'; - $u->save(); + try { + $date_now = strtotime(date("Y-m-d H:i:s")); + $expiration = strtotime($ds['expiration'] . ' ' . $ds['time']); + echo $ds['expiration'] . " : " . ($isCli ? $ds['username'] : Lang::maskText($ds['username'])); - // autorenewal from deposit - if ($config['enable_balance'] == 'yes' && $c['auto_renewal']) { - list($bills, $add_cost) = User::getBills($ds['customer_id']); - if ($add_cost != 0) { - if (!empty($add_cost)) { + if ($date_now >= $expiration) { + echo " : EXPIRED \r\n"; + + // Fetch user recharge details + $u = ORM::for_table('tbl_user_recharges')->where('id', $ds['id'])->find_one(); + if (!$u) { + throw new Exception("User recharge record not found for ID: " . $ds['id']); + } + + // Fetch customer details + $c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one(); + if (!$c) { + $c = $u; + } + + // Fetch plan details + $p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one(); + if (!$p) { + throw new Exception("Plan not found for ID: " . $u['plan_id']); + } + + $dvc = Package::getDevice($p); + if ($_app_stage != 'demo') { + if (file_exists($dvc)) { + require_once $dvc; + try { + (new $p['device'])->remove_customer($c, $p); + } catch (Throwable $e) { + _log($e->getMessage()); + sendTelegram($e->getMessage()); + echo "Error: " . $e->getMessage() . "\n"; + } + } else { + throw new Exception("Cron error: Devices " . $p['device'] . "not found, cannot disconnect ".$c['username']."\n"); + } + } + + // Send notification and update user status + try { + echo Message::sendPackageNotification($c, $u['namebp'], $p['price'], $textExpired, $config['user_notification_expired']) . "\n"; + $u->status = 'off'; + $u->save(); + } catch (Throwable $e) { + _log($e->getMessage()); + sendTelegram($e->getMessage()); + echo "Error: " . $e->getMessage() . "\n"; + } + + // Auto-renewal from deposit + if ($config['enable_balance'] == 'yes' && $c['auto_renewal']) { + [$bills, $add_cost] = User::getBills($ds['customer_id']); + if ($add_cost != 0) { $p['price'] += $add_cost; } - } - if ($p && $c['balance'] >= $p['price']) { - if (Package::rechargeUser($ds['customer_id'], $ds['routers'], $p['id'], 'Customer', 'Balance')) { - // if success, then get the balance - Balance::min($ds['customer_id'], $p['price']); - echo "plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n"; - echo "auto renewall Success\n"; + + if ($p && $c['balance'] >= $p['price']) { + if (Package::rechargeUser($ds['customer_id'], $ds['routers'], $p['id'], 'Customer', 'Balance')) { + Balance::min($ds['customer_id'], $p['price']); + echo "plan enabled: " . (string) $p['enabled'] . " | User balance: " . (string) $c['balance'] . " | price " . (string) $p['price'] . "\n"; + echo "auto renewal Success\n"; + } else { + echo "plan enabled: " . $p['enabled'] . " | User balance: " . $c['balance'] . " | price " . $p['price'] . "\n"; + echo "auto renewal Failed\n"; + Message::sendTelegram("FAILED RENEWAL #cron\n\n#u." . $c['username'] . " #buy #Hotspot \n" . $p['name_plan'] . + "\nRouter: " . $p['routers'] . + "\nPrice: " . $p['price']); + } } else { - echo "plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n"; - echo "auto renewall Failed\n"; - Message::sendTelegram("FAILED RENEWAL #cron\n\n#u$c[username] #buy #Hotspot \n" . $p['name_plan'] . - "\nRouter: " . $p['routers'] . - "\nPrice: " . $p['price']); + echo "no renewal | plan enabled: " . (string) $p['enabled'] . " | User balance: " . (string) $c['balance'] . " | price " . (string) $p['price'] . "\n"; } } else { - echo "no renewall | plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n"; + echo "no renewal | balance" . $config['enable_balance'] . " auto_renewal " . $c['auto_renewal'] . "\n"; } } else { - echo "no renewall | balance $config[enable_balance] auto_renewal $c[auto_renewal]\n"; + echo " : ACTIVE \r\n"; } - } else { - echo " : ACTIVE \r\n"; + } catch (Throwable $e) { + // Catch any unexpected errors + _log($e->getMessage()); + sendTelegram($e->getMessage()); + echo "Unexpected Error: " . $e->getMessage() . "\n"; } } - //Cek interim-update radiusrest - if ($config['frrest_interim_update'] != 0) { +//Cek interim-update radiusrest +if ($config['frrest_interim_update'] != 0) { $r_a = ORM::for_table('rad_acct') - ->whereRaw("BINARY acctstatustype = 'Start' OR acctstatustype = 'Interim-Update'") - ->where_lte('dateAdded', date("Y-m-d H:i:s"))->find_many(); + ->whereRaw("BINARY acctstatustype = 'Start' OR acctstatustype = 'Interim-Update'") + ->where_lte('dateAdded', date("Y-m-d H:i:s"))->find_many(); - foreach ($r_a as $ra) { - $interval = $_c['frrest_interim_update']*60; - $timeUpdate = strtotime($ra['dateAdded'])+$interval; - $timeNow = strtotime(date("Y-m-d H:i:s")); - if ($timeNow >= $timeUpdate) { - $ra->acctstatustype = 'Stop'; - $ra->save(); - } - } + foreach ($r_a as $ra) { + $interval = $_c['frrest_interim_update'] * 60; + $timeUpdate = strtotime($ra['dateAdded']) + $interval; + $timeNow = strtotime(date("Y-m-d H:i:s")); + if ($timeNow >= $timeUpdate) { + $ra->acctstatustype = 'Stop'; + $ra->save(); + } + } } if ($config['router_check']) { @@ -137,7 +168,7 @@ if ($config['router_check']) { foreach ($routers as $router) { // check if custom port - if (strpos($router->ip_address, ':') === false){ + if (strpos($router->ip_address, ':') === false) { $ip = $router->ip_address; $port = 8728; } else { @@ -207,14 +238,7 @@ if ($config['router_check']) { Message::SendEmail($adminEmail, $subject, $message); sendTelegram($message); } - echo "Router monitoring finished\n"; -} - - -if (defined('PHP_SAPI') && PHP_SAPI === 'cli') { - echo "Cronjob finished\n"; -} else { - echo ""; + echo "Router monitoring finished checking.\n"; } flock($lock, LOCK_UN); @@ -224,5 +248,5 @@ unlink($lockFile); $timestampFile = "$UPLOAD_PATH/cron_last_run.txt"; file_put_contents($timestampFile, time()); - run_hook('cronjob_end'); #HOOK +echo "Cron job finished and completed successfully.\n"; \ No newline at end of file diff --git a/ui/ui/admin/customers/list.tpl b/ui/ui/admin/customers/list.tpl index 9e260443..9299de5f 100644 --- a/ui/ui/admin/customers/list.tpl +++ b/ui/ui/admin/customers/list.tpl @@ -16,12 +16,12 @@
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} -
- CSV -
+
+ CSV +
{/if} {Lang::T('Manage Contact')}
@@ -65,8 +65,8 @@ Status
@@ -97,6 +97,7 @@ + @@ -113,65 +114,222 @@ {foreach $d as $ds} - - - - - - - - - - - - - - + + + + + + + + + + + + + + + {/foreach}
{Lang::T('Username')} Photo {Lang::T('Account Type')}
{$ds['username']} - - - - {$ds['account_type']}{$ds['fullname']}{Lang::moneyFormat($ds['balance'])} - {if $ds['phonenumber']} - - {/if} - {if $ds['email']} - - {/if} - {if $ds['coordinates']} - - {/if} - - - {$ds['service_type']} - {$ds['pppoe_username']} - {if !empty($ds['pppoe_username']) && !empty($ds['pppoe_ip'])}:{/if} - {$ds['pppoe_ip']} - {Lang::T($ds['status'])}{Lang::dateTimeFormat($ds['created_at'])} -   {Lang::T('View')}   -   {Lang::T('Edit')}   -   {Lang::T('Sync')}   - {Lang::T('Recharge')} -
{$ds['username']} + + + + {$ds['account_type']}{$ds['fullname']}{Lang::moneyFormat($ds['balance'])} + {if $ds['phonenumber']} + + {/if} + {if $ds['email']} + + {/if} + {if $ds['coordinates']} + + {/if} + + + {$ds['service_type']} + {$ds['pppoe_username']} + {if !empty($ds['pppoe_username']) && !empty($ds['pppoe_ip'])}:{/if} + {$ds['pppoe_ip']} + {Lang::T($ds['status'])}{Lang::dateTimeFormat($ds['created_at'])} +   {Lang::T('View')}   +   {Lang::T('Edit')}   +   {Lang::T('Sync')}   + {Lang::T('Recharge')} +
+
+
+
+ +
+ +
+
+
+
{include file="pagination.tpl"} -{include file="sections/footer.tpl"} \ No newline at end of file + + + + +{include file = "sections/footer.tpl" } \ No newline at end of file diff --git a/ui/ui/admin/message/bulk.tpl b/ui/ui/admin/message/bulk.tpl index ea77b4dd..936c6d6e 100644 --- a/ui/ui/admin/message/bulk.tpl +++ b/ui/ui/admin/message/bulk.tpl @@ -1,180 +1,219 @@ {include file="sections/header.tpl"} - +
-
- {if $page>0 && $totalCustomers>0} - - {/if} -
-
{Lang::T('Send Bulk Message')}
-
-
- -
- -
- -
-
-
- -
- -
-
-
- -
- {Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')} -
-
-
- -
- {Lang::T('Use at least 5 secs if you are sending to all customers to avoid being banned by your message provider')} -
-
-
- -
- - - {Lang::T('Testing [if checked no real message is sent]')} -
-

- {Lang::T('Use placeholders:')} -
- [[name]] - {Lang::T('Customer Name')} -
- [[user_name]] - {Lang::T('Customer Username')} -
- [[phone]] - {Lang::T('Customer Phone')} -
- [[company_name]] - {Lang::T('Your Company Name')} -

-
-
-
- {if $page >= 0} - - {else} - - {/if} - {Lang::T('Cancel')} -
-
-
- -
-
-
+
+
+
+
{Lang::T('Send Bulk Message')}
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + {Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')} +
+
+
+ +
+ + + {Lang::T('Testing [if checked no real message is sent]')} +
+

+ {Lang::T('Use placeholders:')} +
+ [[name]] - {Lang::T('Customer Name')} +
+ [[user_name]] - {Lang::T('Customer Username')} +
+ [[phone]] - {Lang::T('Customer Phone')} +
+ [[company_name]] - {Lang::T('Your Company Name')} +

+
+
+
+ + {Lang::T('Cancel')} +
+
+
+
+
+
-{if $batchStatus} -

{Lang::T('Total SMS Sent')}: {$totalSMSSent} {Lang::T('Total SMS - Failed')}: {$totalSMSFailed} {Lang::T('Total WhatsApp Sent')}: - {$totalWhatsappSent} {Lang::T('Total WhatsApp Failed')}: - {$totalWhatsappFailed}

-{/if} -
-
-

{Lang::T('Message Results')}

-
- -
- - - - - - - - - - - {foreach $batchStatus as $customer} - - - - - - - {/foreach} - -
{Lang::T('Name')}{Lang::T('Phone')}{Lang::T('Message')}{Lang::T('Status')}
{$customer.name}{$customer.phone}{$customer.message}{$customer.status}
-
- + +
+
{Lang::T('Message Sending History')}
+
+
+ + + + + + + + + + +
{Lang::T('Customer')}{Lang::T('Phone')}{Lang::T('Status')}{Lang::T('Message')}
+
- +{literal} - - - +{/literal} {include file="sections/footer.tpl"} \ No newline at end of file