diff --git a/system/controllers/accounts.php b/system/controllers/accounts.php new file mode 100644 index 0000000..ca46741 --- /dev/null +++ b/system/controllers/accounts.php @@ -0,0 +1,254 @@ +assign('_title', Lang::T('My Account')); +$ui->assign('_system_menu', 'accounts'); + +$action = $routes['1']; +$user = User::_info(); +$ui->assign('_user', $user); + +switch ($action) { + + case 'change-password': + run_hook('customer_view_change_password'); #HOOK + $ui->display('user-change-password.tpl'); + break; + + case 'change-password-post': + $password = _post('password'); + run_hook('customer_change_password'); #HOOK + if ($password != '') { + $d = ORM::for_table('tbl_customers')->where('username', $user['username'])->find_one(); + if ($d) { + $d_pass = $d['password']; + $npass = _post('npass'); + $cnpass = _post('cnpass'); + + if (Password::_uverify($password, $d_pass) == true) { + if (!Validator::Length($npass, 15, 2)) { + r2(U . 'accounts/change-password', 'e', 'New Password must be 3 to 14 character'); + } + if ($npass != $cnpass) { + r2(U . 'accounts/change-password', 'e', 'Both Password should be same'); + } + + $c = ORM::for_table('tbl_user_recharges')->where('username', $user['username'])->find_one(); + if ($c) { + $p = ORM::for_table('tbl_plans')->where('id', $c['plan_id'])->find_one(); + if ($p['is_radius']) { + if ($c['type'] == 'Hotspot' || ($c['type'] == 'PPPOE' && empty($d['pppoe_password']))) { + Radius::customerUpsert($d, $p); + } + } else { + $mikrotik = Mikrotik::info($c['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($c['type'] == 'Hotspot') { + Mikrotik::setHotspotUser($client, $c['username'], $npass); + Mikrotik::removeHotspotActiveUser($client, $user['username']); + } else if (empty($d['pppoe_password'])) { + // only change when pppoe_password empty + Mikrotik::setPpoeUser($client, $c['username'], $npass); + Mikrotik::removePpoeActive($client, $user['username']); + } + } + } + $d->password = $npass; + $d->save(); + + _msglog('s', Lang::T('Password changed successfully, Please login again')); + _log('[' . $user['username'] . ']: Password changed successfully', 'User', $user['id']); + + r2(U . 'login'); + } else { + r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password')); + } + } else { + r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password')); + } + } else { + r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password')); + } + break; + + case 'profile': + $d = ORM::for_table('tbl_customers')->find_one($user['id']); + if ($d) { + run_hook('customer_view_edit_profile'); #HOOK + $ui->assign('d', $d); + $ui->display('user-profile.tpl'); + } else { + r2(U . 'home', 'e', Lang::T('Account Not Found')); + } + break; + + case 'edit-profile-post': + $fullname = _post('fullname'); + $address = _post('address'); + $email = _post('email'); + $phonenumber = _post('phonenumber'); + run_hook('customer_edit_profile'); #HOOK + $msg = ''; + if (Validator::Length($fullname, 31, 2) == false) { + $msg .= 'Full Name should be between 3 to 30 characters' . '
'; + } + if (Validator::UnsignedNumber($phonenumber) == false) { + $msg .= 'Phone Number must be a number' . '
'; + } + + $d = ORM::for_table('tbl_customers')->find_one($user['id']); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + + if ($msg == '') { + $d->fullname = $fullname; + $d->address = $address; + $d->email = $email; + $d->phonenumber = $phonenumber; + $d->save(); + + _log('[' . $user['username'] . ']: ' . Lang::T('User Updated Successfully'), 'User', $user['id']); + r2(U . 'accounts/profile', 's', Lang::T('User Updated Successfully')); + } else { + r2(U . 'accounts/profile', 'e', $msg); + } + break; + + + case 'phone-update': + + $d = ORM::for_table('tbl_customers')->find_one($user['id']); + if ($d) { + //run_hook('customer_view_edit_profile'); #HOOK + $ui->assign('d', $d); + $ui->display('user-phone-update.tpl'); + } else { + r2(U . 'home', 'e', Lang::T('Account Not Found')); + } + break; + + case 'phone-update-otp': + $phone = _post('phone'); + $username = $user['username']; + $otpPath = $CACHE_PATH . '/sms/'; + + // Validate the phone number format + if (!preg_match('/^[0-9]{10,}$/', $phone)) { + r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid phone number format')); + } + + if (empty($config['sms_url'])) { + r2(U . 'accounts/phone-update', 'e', Lang::T('SMS server not Available, Please try again later')); + } + + if (!empty($config['sms_url'])) { + if (!empty($phone)) { + $d = ORM::for_table('tbl_customers')->where('username', $username)->where('phonenumber', $phone)->find_one(); + if ($d) { + r2(U . 'accounts/phone-update', 'e', Lang::T('You cannot use your current phone number')); + } + if (!file_exists($otpPath)) { + mkdir($otpPath); + touch($otpPath . 'index.html'); + } + $otpFile = $otpPath . sha1($username . $db_password) . ".txt"; + $phoneFile = $otpPath . sha1($username . $db_password) . "_phone.txt"; + + // expired 10 minutes + if (file_exists($otpFile) && time() - filemtime($otpFile) < 1200) { + r2(U . 'accounts/phone-update', 'e', Lang::T('Please wait ' . (1200 - (time() - filemtime($otpFile))) . ' seconds before sending another SMS')); + } else { + $otp = rand(100000, 999999); + file_put_contents($otpFile, $otp); + file_put_contents($phoneFile, $phone); + // send send OTP to user + if ($config['phone_otp_type'] === 'sms') { + Message::sendSMS($phone, $config['CompanyName'] . "\n Your Verification code is: $otp"); + } elseif ($config['phone_otp_type'] === 'whatsapp') { + Message::sendWhatsapp($phone, $config['CompanyName'] . "\n Your Verification code is: $otp"); + } elseif ($config['phone_otp_type'] === 'both') { + Message::sendSMS($phone, $config['CompanyName'] . "\n Your Verification code is: $otp"); + Message::sendWhatsapp($phone, $config['CompanyName'] . "\n Your Verification code is: $otp"); + } + //redirect after sending OTP + r2(U . 'accounts/phone-update', 'e', Lang::T('Verification code has been sent to your phone')); + } + } + } + + break; + + case 'phone-update-post': + $phone = _post('phone'); + $otp_code = _post('otp'); + $username = $user['username']; + $otpPath = $CACHE_PATH . '/sms/'; + + // Validate the phone number format + if (!preg_match('/^[0-9]{10,}$/', $phone)) { + r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid phone number format')); + exit(); + } + + if (!empty($config['sms_url'])) { + $otpFile = $otpPath . sha1($username . $db_password) . ".txt"; + $phoneFile = $otpPath . sha1($username . $db_password) . "_phone.txt"; + + // Check if OTP file exists + if (!file_exists($otpFile)) { + r2(U . 'accounts/phone-update', 'e', Lang::T('Please request OTP first')); + exit(); + } + + // expired 10 minutes + if (time() - filemtime($otpFile) > 1200) { + unlink($otpFile); + unlink($phoneFile); + r2(U . 'accounts/phone-update', 'e', Lang::T('Verification code expired')); + exit(); + } else { + $code = file_get_contents($otpFile); + + // Check if OTP code matches + if ($code != $otp_code) { + r2(U . 'accounts/phone-update', 'e', Lang::T('Wrong Verification code')); + exit(); + } + + // Check if the phone number matches the one that requested the OTP + $savedPhone = file_get_contents($phoneFile); + if ($savedPhone !== $phone) { + r2(U . 'accounts/phone-update', 'e', Lang::T('The phone number does not match the one that requested the OTP')); + exit(); + } + + // OTP verification successful, delete OTP and phone number files + unlink($otpFile); + unlink($phoneFile); + } + } else { + r2(U . 'accounts/phone-update', 'e', Lang::T('SMS server not available')); + exit(); + } + + // Update the phone number in the database + $d = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($d) { + $d->phonenumber = Lang::phoneFormat($phone); + $d->save(); + } + + r2(U . 'accounts/profile', 's', Lang::T('Phone number updated successfully')); + break; + + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/admin.php b/system/controllers/admin.php new file mode 100644 index 0000000..e8064bd --- /dev/null +++ b/system/controllers/admin.php @@ -0,0 +1,57 @@ +where('username', $username)->find_one(); + if ($d) { + $d_pass = $d['password']; + if (Password::_verify($password, $d_pass) == true) { + $_SESSION['aid'] = $d['id']; + $token = Admin::setCookie($d['id']); + $d->last_login = date('Y-m-d H:i:s'); + $d->save(); + _log($username . ' ' . Lang::T('Login Successful'), $d['user_type'], $d['id']); + if ($isApi) { + if ($token) { + showResult(true, Lang::T('Login Successful'), ['token' => "a.".$token]); + } else { + showResult(false, Lang::T('Invalid Username or Password')); + } + } + _alert(Lang::T('Login Successful'),'success', "dashboard"); + } else { + _log($username . ' ' . Lang::T('Failed Login'), $d['user_type']); + _alert(Lang::T('Invalid Username or Password').".",'danger', "admin"); + } + } else { + _alert(Lang::T('Invalid Username or Password')."..",'danger', "admin"); + } + } else { + _alert(Lang::T('Invalid Username or Password')."...",'danger', "admin"); + } + + break; + default: + run_hook('view_login'); #HOOK + $ui->display('admin-login.tpl'); + break; +} diff --git a/system/controllers/autoload.php b/system/controllers/autoload.php new file mode 100644 index 0000000..3ab1df6 --- /dev/null +++ b/system/controllers/autoload.php @@ -0,0 +1,119 @@ +assign('_title', Lang::T('Network')); +$ui->assign('_system_menu', 'network'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +switch ($action) { + case 'pool': + $routers = _get('routers'); + if(empty($routers)){ + $d = ORM::for_table('tbl_pool')->find_many(); + }else{ + $d = ORM::for_table('tbl_pool')->where('routers', $routers)->find_many(); + } + $ui->assign('routers', $routers); + $ui->assign('d', $d); + $ui->display('autoload-pool.tpl'); + break; + + case 'server': + $d = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many(); + $ui->assign('d', $d); + + $ui->display('autoload-server.tpl'); + break; + + case 'plan': + $server = _post('server'); + $jenis = _post('jenis'); + if(in_array($admin['user_type'], array('SuperAdmin', 'Admin'))){ + if($server=='radius'){ + $d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->find_many(); + }else{ + $d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->find_many(); + } + }else{ + if($server=='radius'){ + $d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->where('enabled', '1')->find_many(); + }else{ + $d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->where('enabled', '1')->find_many(); + } + } + $ui->assign('d', $d); + + $ui->display('autoload.tpl'); + break; + case 'customer_is_active': + $d = ORM::for_table('tbl_user_recharges')->where('customer_id', $routes['2'])->findOne(); + if ($d) { + if ($d['status'] == 'on') { + die(''.$d['namebp'].''); + } else { + die(''.$d['namebp'].''); + } + } else { + die(''); + } + break; + case 'customer_select2': + + $s = addslashes(_get('s')); + if (empty($s)) { + $c = ORM::for_table('tbl_customers')->limit(30)->find_many(); + } else { + $c = ORM::for_table('tbl_customers')->where_raw("(`username` LIKE '%$s%' OR `fullname` LIKE '%$s%' OR `phonenumber` LIKE '%$s%' OR `email` LIKE '%$s%')")->limit(30)->find_many(); + } + header('Content-Type: application/json'); + foreach ($c as $cust) { + $json[] = [ + 'id' => $cust['id'], + 'text' => $cust['username'] . ' - ' . $cust['fullname'] . ' - ' . $cust['email'] + ]; + } + echo json_encode(['results' => $json]); + die(); + case 'customer_info': + $customerId = _post('id'); + + if (empty($customerId)) { + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'message' => 'Customer ID is required']); + die(); + } + + $customer = ORM::for_table('tbl_customers')->find_one($customerId); + if (!$customer) { + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'message' => 'Customer not found']); + die(); + } + + header('Content-Type: application/json'); + echo json_encode([ + 'success' => true, + 'data' => [ + 'id' => $customer['id'], + 'username' => $customer['username'], + 'fullname' => $customer['fullname'], + 'phonenumber' => $customer['phonenumber'], + 'email' => $customer['email'], + 'address' => $customer['address'], + 'balance' => $customer['balance'] + ] + ]); + die(); + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/autoload_user.php b/system/controllers/autoload_user.php new file mode 100644 index 0000000..8e50773 --- /dev/null +++ b/system/controllers/autoload_user.php @@ -0,0 +1,37 @@ +where('id', $routes['2'])->where('username', $user['username'])->findOne(); + if ($bill['type'] == 'Hotspot' && $bill['status'] == 'on') { + $m = Mikrotik::info($bill['routers']); + $client = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + if (Mikrotik::isUserLogin($client, $user['username'])) { + die(''.Lang::T('You are Online, Logout?').''); + } else { + if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + die(''.Lang::T('Not Online, Login now?').''); + }else{ + die(Lang::T('Your account not connected to internet')); + } + } + } else { + die('--'); + } + break; + default: + $ui->display('404.tpl'); +} diff --git a/system/controllers/bandwidth.php b/system/controllers/bandwidth.php new file mode 100644 index 0000000..2d49061 --- /dev/null +++ b/system/controllers/bandwidth.php @@ -0,0 +1,190 @@ +assign('_title', Lang::T('Bandwidth Plans')); +$ui->assign('_system_menu', 'services'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + r2(U . "dashboard", 'e', Lang::T('You do not have permission to access this page')); +} + +switch ($action) { + case 'list': + $ui->assign('xfooter', ''); + run_hook('view_list_bandwidth'); #HOOK + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_bandwidth')->where_like('name_bw', '%' . $name . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_bandwidth')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + $ui->display('bandwidth.tpl'); + break; + + case 'add': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + run_hook('view_add_bandwidth'); #HOOK + $ui->display('bandwidth-add.tpl'); + break; + + case 'edit': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('view_edit_bandwith'); #HOOK + $d = ORM::for_table('tbl_bandwidth')->find_one($id); + if ($d) { + $ui->assign('burst', explode(" ", $d['burst'])); + $ui->assign('d', $d); + $ui->display('bandwidth-edit.tpl'); + } else { + r2(U . 'bandwidth/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('delete_bandwidth'); #HOOK + $d = ORM::for_table('tbl_bandwidth')->find_one($id); + if ($d) { + $d->delete(); + r2(U . 'bandwidth/list', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'add-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $name = _post('name'); + $rate_down = _post('rate_down'); + $rate_down_unit = _post('rate_down_unit'); + $rate_up = _post('rate_up'); + $rate_up_unit = _post('rate_up_unit'); + run_hook('add_bandwidth'); #HOOK + $isBurst = true; + $burst = ""; + if (isset($_POST['burst'])) { + foreach ($_POST['burst'] as $b) { + if (empty($b)) { + $isBurst = false; + } + } + if ($isBurst) { + $burst = implode(' ', $_POST['burst']); + }; + } + $msg = ''; + if (Validator::Length($name, 16, 4) == false) { + $msg .= 'Name should be between 5 to 15 characters' . '
'; + } + + if ($rate_down_unit == 'Kbps') { + $unit_rate_down = $rate_down * 1024; + } else { + $unit_rate_down = $rate_down * 1048576; + } + if ($rate_up_unit == 'Kbps') { + $unit_rate_up = $min_up * 1024; + } else { + $unit_rate_up = $min_up * 1048576; + } + + $d = ORM::for_table('tbl_bandwidth')->where('name_bw', $name)->find_one(); + if ($d) { + $msg .= Lang::T('Name Bandwidth Already Exist') . '
'; + } + + if ($msg == '') { + $d = ORM::for_table('tbl_bandwidth')->create(); + $d->name_bw = $name; + $d->rate_down = $rate_down; + $d->rate_down_unit = $rate_down_unit; + $d->rate_up = $rate_up; + $d->rate_up_unit = $rate_up_unit; + $d->burst = $burst; + $d->save(); + + r2(U . 'bandwidth/list', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'bandwidth/add', 'e', $msg); + } + break; + + case 'edit-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $name = _post('name'); + $rate_down = _post('rate_down'); + $rate_down_unit = _post('rate_down_unit'); + $rate_up = _post('rate_up'); + $rate_up_unit = _post('rate_up_unit'); + run_hook('edit_bandwidth'); #HOOK + $isBurst = true; + $burst = ""; + if (isset($_POST['burst'])) { + foreach ($_POST['burst'] as $b) { + if (empty($b)) { + $isBurst = false; + } + } + if ($isBurst) { + $burst = implode(' ', $_POST['burst']); + }; + } + $msg = ''; + if (Validator::Length($name, 16, 4) == false) { + $msg .= 'Name should be between 5 to 15 characters' . '
'; + } + + $id = _post('id'); + $d = ORM::for_table('tbl_bandwidth')->find_one($id); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + + if ($d['name_bw'] != $name) { + $c = ORM::for_table('tbl_bandwidth')->where('name_bw', $name)->find_one(); + if ($c) { + $msg .= Lang::T('Name Bandwidth Already Exist') . '
'; + } + } + + if ($msg == '') { + $d->name_bw = $name; + $d->rate_down = $rate_down; + $d->rate_down_unit = $rate_down_unit; + $d->rate_up = $rate_up; + $d->rate_up_unit = $rate_up_unit; + $d->burst = $burst; + $d->save(); + + r2(U . 'bandwidth/list', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'bandwidth/edit/' . $id, 'e', $msg); + } + break; + + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/banks.php b/system/controllers/banks.php new file mode 100644 index 0000000..a614c5d --- /dev/null +++ b/system/controllers/banks.php @@ -0,0 +1,481 @@ +assign('_title', Lang::T('Bank Management')); +$ui->assign('_system_menu', 'services'); +$ui->assign('_admin', $admin); + +// Check permissions +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + r2(U . "dashboard", 'e', Lang::T('You do not have permission to access this page')); +} + +$action = $routes['1'] ?? 'list'; + +switch ($action) { + case 'list': + // List all banks + $ui->assign('xfooter', ''); + + // Search functionality + $search = _post('search'); + $filter_stk = _post('filter_stk'); + $filter_active = _post('filter_active'); + + // Alternative approach: Use direct PDO query for better performance + $use_direct_query = true; // Set to true for direct query, false for ORM + + if ($use_direct_query) { + // Direct PDO approach - faster and more reliable + $pdo = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_password); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $sql = "SELECT * FROM tbl_banks WHERE 1=1"; + $params = []; + + if (!empty($search)) { + $sql .= " AND (name LIKE ? OR account_number LIKE ? OR account_name LIKE ?)"; + $search_term = '%' . $search . '%'; + $params[] = $search_term; + $params[] = $search_term; + $params[] = $search_term; + } + + if ($filter_stk !== '') { + $sql .= " AND supports_stk_push = ?"; + $params[] = $filter_stk; + } + + if ($filter_active !== '') { + $sql .= " AND is_active = ?"; + $params[] = $filter_active; + } + + $sql .= " ORDER BY is_default DESC, name ASC"; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $banks = $stmt->fetchAll(PDO::FETCH_OBJ); + } else { + // Original ORM approach + $query = ORM::for_table('tbl_banks'); + + if (!empty($search)) { + $query->where_raw('(name LIKE ? OR account_number LIKE ? OR account_name LIKE ?)', + ['%' . $search . '%', '%' . $search . '%', '%' . $search . '%']); + } + + if ($filter_stk !== '') { + $query->where('supports_stk_push', $filter_stk); + } + + if ($filter_active !== '') { + $query->where('is_active', $filter_active); + } + + $query->order_by_desc('is_default')->order_by_asc('name'); + $banks = Paginator::findMany($query, [ + 'search' => $search, + 'filter_stk' => $filter_stk, + 'filter_active' => $filter_active + ]); + + // Convert ORM object to array for template compatibility + if ($banks && method_exists($banks, 'as_array')) { + $banks = $banks->as_array(); + } elseif ($banks && is_object($banks)) { + $banks_array = []; + foreach ($banks as $bank) { + $banks_array[] = $bank; + } + $banks = $banks_array; + } + + // Fallback: If banks is still not an array, use direct database query + if (!is_array($banks) || empty($banks)) { + $pdo = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_password); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $sql = "SELECT * FROM tbl_banks WHERE 1=1"; + $params = []; + + if (!empty($search)) { + $sql .= " AND (name LIKE ? OR account_number LIKE ? OR account_name LIKE ?)"; + $search_term = '%' . $search . '%'; + $params[] = $search_term; + $params[] = $search_term; + $params[] = $search_term; + } + + if ($filter_stk !== '') { + $sql .= " AND supports_stk_push = ?"; + $params[] = $filter_stk; + } + + if ($filter_active !== '') { + $sql .= " AND is_active = ?"; + $params[] = $filter_active; + } + + $sql .= " ORDER BY is_default DESC, name ASC"; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $banks = $stmt->fetchAll(PDO::FETCH_OBJ); + } + } + + $ui->assign('banks', $banks); + $ui->assign('search', $search); + $ui->assign('filter_stk', $filter_stk); + $ui->assign('filter_active', $filter_active); + + run_hook('view_list_banks'); + $ui->display('banks_basic.tpl'); + break; + + case 'add': + // Show add bank form + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + // Add JavaScript for bank add form + $xfooter = ' + '; + + $ui->assign('xfooter', $xfooter); + run_hook('view_add_bank'); + $ui->display('banks-add.tpl'); + break; + + case 'edit': + // Show edit bank form + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $id = $routes['2'] ?? 0; + if (!$id) { + r2(U . 'banks/list', 'e', Lang::T('Invalid Bank ID')); + } + + $bank = ORM::for_table('tbl_banks')->find_one($id); + if (!$bank) { + r2(U . 'banks/list', 'e', Lang::T('Bank Not Found')); + } + + // Add JavaScript for bank edit form + $xfooter = ' + '; + + $ui->assign('bank', $bank); + $ui->assign('xfooter', $xfooter); + run_hook('view_edit_bank'); + $ui->display('banks-edit.tpl'); + break; + + case 'save': + // Save bank data (add or edit) + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $id = _post('id'); + $name = _post('name'); + $account_number = _post('account_number'); + $account_name = _post('account_name'); + $bank_code = _post('bank_code'); + $paybill = _post('paybill'); + $supports_stk_push = _post('supports_stk_push') ? 1 : 0; + $is_active = _post('is_active') ? 1 : 0; + $is_default = _post('is_default') ? 1 : 0; + + // Validation + $msg = ''; + if (empty($name)) { + $msg .= Lang::T('Bank name is required') . '
'; + } + if (empty($account_number)) { + $msg .= Lang::T('Account number is required') . '
'; + } + if (empty($account_name)) { + $msg .= Lang::T('Account name is required') . '
'; + } + + // Check for duplicate account number + $duplicate_query = ORM::for_table('tbl_banks')->where('account_number', $account_number); + if ($id) { + $duplicate_query->where_not_equal('id', $id); + } + $duplicate = $duplicate_query->find_one(); + + if ($duplicate) { + $msg .= Lang::T('Account number already exists') . '
'; + } + + // If setting as default, unset other defaults + if ($is_default) { + ORM::for_table('tbl_banks')->where('is_default', 1)->find_result_set()->set('is_default', 0)->save(); + } + + if ($msg) { + $redirect_url = $id ? U . 'banks/edit/' . $id : U . 'banks/add'; + r2($redirect_url, 'e', $msg); + } + + // Save bank + if ($id) { + $bank = ORM::for_table('tbl_banks')->find_one($id); + if (!$bank) { + r2(U . 'banks/list', 'e', Lang::T('Bank Not Found')); + } + } else { + $bank = ORM::for_table('tbl_banks')->create(); + } + + $bank->name = $name; + $bank->account_number = $account_number; + $bank->account_name = $account_name; + $bank->bank_code = $bank_code; + $bank->paybill = $paybill; + $bank->supports_stk_push = $supports_stk_push; + $bank->is_active = $is_active; + $bank->is_default = $is_default; + $bank->save(); + + $action_text = $id ? 'updated' : 'created'; + _log('[' . $admin['username'] . ']: Bank ' . $action_text . ' - ' . $name, 'Admin', $admin['id']); + + r2(U . 'banks/list', 's', Lang::T('Bank ' . ucfirst($action_text) . ' Successfully')); + break; + + case 'delete': + // Delete bank + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $id = $routes['2'] ?? 0; + if (!$id) { + r2(U . 'banks/list', 'e', Lang::T('Invalid Bank ID')); + } + + $bank = ORM::for_table('tbl_banks')->find_one($id); + if (!$bank) { + r2(U . 'banks/list', 'e', Lang::T('Bank Not Found')); + } + + // Check if bank is being used in appconfig + $stk_config = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankacc')->find_one(); + if ($stk_config && $stk_config->value == $id) { + r2(U . 'banks/list', 'e', Lang::T('Cannot delete bank that is currently selected for STK Push')); + } + + $bank_name = $bank->name; + $bank->delete(); + + _log('[' . $admin['username'] . ']: Bank deleted - ' . $bank_name, 'Admin', $admin['id']); + r2(U . 'banks/list', 's', Lang::T('Bank Deleted Successfully')); + break; + + case 'toggle-status': + // Toggle bank active status + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $id = $routes['2'] ?? 0; + if (!$id) { + r2(U . 'banks/list', 'e', Lang::T('Invalid Bank ID')); + } + + $bank = ORM::for_table('tbl_banks')->find_one($id); + if (!$bank) { + r2(U . 'banks/list', 'e', Lang::T('Bank Not Found')); + } + + $bank->is_active = $bank->is_active ? 0 : 1; + $bank->save(); + + $status_text = $bank->is_active ? 'activated' : 'deactivated'; + _log('[' . $admin['username'] . ']: Bank ' . $status_text . ' - ' . $bank->name, 'Admin', $admin['id']); + + r2(U . 'banks/list', 's', Lang::T('Bank ' . ucfirst($status_text) . ' Successfully')); + break; + + case 'set-default': + // Set bank as default for STK Push + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $id = $routes['2'] ?? 0; + if (!$id) { + r2(U . 'banks/list', 'e', Lang::T('Invalid Bank ID')); + } + + $bank = ORM::for_table('tbl_banks')->find_one($id); + if (!$bank) { + r2(U . 'banks/list', 'e', Lang::T('Bank Not Found')); + } + + if (!$bank->supports_stk_push) { + r2(U . 'banks/list', 'e', Lang::T('Bank does not support STK Push')); + } + + // Unset other defaults + ORM::for_table('tbl_banks')->where('is_default', 1)->find_result_set()->set('is_default', 0)->save(); + + // Set this bank as default + $bank->is_default = 1; + $bank->save(); + + // Update appconfig + $stk_config = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankacc')->find_one(); + if ($stk_config) { + $stk_config->value = $bank->id; + $stk_config->save(); + } + + $stk_name_config = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankname')->find_one(); + if ($stk_name_config) { + $stk_name_config->value = $bank->name; + $stk_name_config->save(); + } + + _log('[' . $admin['username'] . ']: Bank set as default for STK Push - ' . $bank->name, 'Admin', $admin['id']); + r2(U . 'banks/list', 's', Lang::T('Bank Set as Default Successfully')); + break; + + case 'api': + // API endpoint for AJAX requests + $api_action = $routes['2'] ?? ''; + + switch ($api_action) { + case 'get-active-banks': + // Get banks that support STK Push and are active + $banks = ORM::for_table('tbl_banks') + ->where('supports_stk_push', 1) + ->where('is_active', 1) + ->order_by_desc('is_default') + ->order_by_asc('name') + ->find_many(); + + $result = []; + foreach ($banks as $bank) { + $result[] = [ + 'id' => $bank->id, + 'name' => $bank->name, + 'account_number' => $bank->account_number, + 'account_name' => $bank->account_name, + 'is_default' => $bank->is_default + ]; + } + + header('Content-Type: application/json'); + echo json_encode($result); + exit; + + case 'get-bank-details': + // Get specific bank details + $bank_id = $_GET['bank_id'] ?? 0; + $bank = ORM::for_table('tbl_banks')->find_one($bank_id); + + if ($bank) { + $result = [ + 'id' => $bank->id, + 'name' => $bank->name, + 'account_number' => $bank->account_number, + 'account_name' => $bank->account_name, + 'bank_code' => $bank->bank_code, + 'paybill' => $bank->paybill, + 'supports_stk_push' => $bank->supports_stk_push, + 'is_active' => $bank->is_active, + 'is_default' => $bank->is_default + ]; + } else { + $result = ['error' => 'Bank not found']; + } + + header('Content-Type: application/json'); + echo json_encode($result); + exit; + } + + r2(U . 'banks/list', 'e', Lang::T('Invalid API Action')); + break; + + default: + $ui->display('a404.tpl'); +} +?> diff --git a/system/controllers/callback.php b/system/controllers/callback.php new file mode 100644 index 0000000..352f2eb --- /dev/null +++ b/system/controllers/callback.php @@ -0,0 +1,22 @@ +assign('_title', 'CodeCanyon.net'); +$ui->assign('_system_menu', 'settings'); + +$plugin_repository = 'https://hotspotbilling.github.io/Plugin-Repository/repository.json'; + +$action = $routes['1']; +$ui->assign('_admin', $admin); +$cache = File::pathFixer($CACHE_PATH . '/codecanyon.json'); + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} +if (empty($config['envato_token'])) { + r2(U . 'settings/app', 'w', 'Envato Personal Access Token is not set'); +} + +switch ($action) { + + case 'install': + if (!is_writeable(File::pathFixer($CACHE_PATH . '/'))) { + r2(U . "codecanyon", 'e', 'Folder system/cache/ is not writable'); + } + if (!is_writeable($PLUGIN_PATH)) { + r2(U . "codecanyon", 'e', 'Folder plugin/ is not writable'); + } + if (!is_writeable($PAYMENTGATEWAY_PATH)) { + r2(U . "codecanyon", 'e', 'Folder paymentgateway/ is not writable'); + } + set_time_limit(-1); + $item_id = $routes['2']; + $tipe = $routes['3']; + $result = Http::getData('https://api.envato.com/v3/market/buyer/download?item_id=' . $item_id, ['Authorization: Bearer ' . $config['envato_token']]); + $json = json_decode($result, true); + if (!isset($json['download_url'])) { + r2(U . 'codecanyon', 'e', 'Failed to get download url. ' . $json['description']); + } + $file = File::pathFixer($CACHE_PATH . '/codecanyon/'); + if (!file_exists($file)) { + mkdir($file); + } + $file .= $item_id . '.zip'; + if (file_exists($file)) + unlink($file); + //download + $fp = fopen($file, 'w+'); + $ch = curl_init($json['download_url']); + curl_setopt($ch, CURLOPT_POST, 0); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 120); + curl_setopt($ch, CURLOPT_TIMEOUT, 120); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + curl_close($ch); + fclose($fp); + //extract + $target = File::pathFixer($CACHE_PATH . '/codecanyon/' . $item_id . '/'); + $zip = new ZipArchive(); + $zip->open($file); + $zip->extractTo($target); + $zip->close(); + //moving + if (file_exists($target . 'plugin')) { + File::copyFolder($target . 'plugin', $PLUGIN_PATH . DIRECTORY_SEPARATOR); + } else if (file_exists($target . 'paymentgateway')) { + File::copyFolder($target . 'paymentgateway', $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR); + } else if (file_exists($target . 'theme')) { + File::copyFolder($target . 'theme', File::pathFixer('ui/themes/')); + } + //Cleaning + File::deleteFolder($target); + unlink($file); + r2(U . "codecanyon", 's', 'Installation success'); + case 'reload': + if (file_exists($cache)) + unlink($cache); + default: + if (class_exists('ZipArchive')) { + $zipExt = true; + } else { + $zipExt = false; + } + $ui->assign('zipExt', $zipExt); + + if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) { + $txt = file_get_contents($cache); + $plugins = json_decode($txt, true); + $ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60))); + if (count($plugins) == 0) { + unlink($cache); + r2(U . 'codecanyon'); + } + } else { + $plugins = []; + $page = _get('page', 1); + back: + $result = Http::getData('https://api.envato.com/v3/market/buyer/list-purchases?&page=' . $page, ['Authorization: Bearer ' . $config['envato_token']]); + $items = json_decode($result, true); + if ($items && count($items['results']) > 0) { + foreach ($items['results'] as $item) { + $name = strtolower($item['item']['name']); + if (strpos($name, 'phpnuxbill') !== false) { + $plugins[] = $item; + } + } + $page++; + goto back; + } + if (count($plugins) > 0) { + file_put_contents($cache, json_encode($plugins)); + if (file_exists($cache)) { + $ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60))); + } + } + } + $ui->assign('plugins', $plugins); + $ui->display('codecanyon.tpl'); +} diff --git a/system/controllers/community.php b/system/controllers/community.php new file mode 100644 index 0000000..df60c08 --- /dev/null +++ b/system/controllers/community.php @@ -0,0 +1,26 @@ +assign('_title', 'Community'); +$ui->assign('_system_menu', 'community'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +switch ($action) { + case 'rollback': + $ui->assign('_title', 'Rollback Update'); + $masters = json_decode(Http::getData("https://api.github.com/repos/hotspotbilling/phpnuxbill/commits?per_page=100",['User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0']), true); + $devs = json_decode(Http::getData("https://api.github.com/repos/hotspotbilling/phpnuxbill/commits?sha=Development&per_page=100",['User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0']), true); + + $ui->assign('masters', $masters); + $ui->assign('devs', $devs); + $ui->display('community-rollback.tpl'); + break; + default: + $ui->display('community.tpl'); +} \ No newline at end of file diff --git a/system/controllers/customers.php b/system/controllers/customers.php new file mode 100644 index 0000000..af5c536 --- /dev/null +++ b/system/controllers/customers.php @@ -0,0 +1,739 @@ +assign('_title', Lang::T('Customer')); +$ui->assign('_system_menu', 'customers'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + + +if (empty($action)) { + $action = 'list'; +} + +$leafletpickerHeader = << +EOT; + +switch ($action) { + case 'csv': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $cs = ORM::for_table('tbl_customers') + ->select('tbl_customers.id', 'id') + ->select('tbl_customers.username', 'username') + ->select('fullname') + ->select('address') + ->select('phonenumber') + ->select('email') + ->select('balance') + ->select('service_type') + ->order_by_asc('tbl_customers.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="phpnuxbill_customers_' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + + $headers = [ + 'id', + 'username', + 'fullname', + 'address', + 'phonenumber', + 'email', + 'balance', + 'service_type', + ]; + + if (!$h) { + echo '"' . implode('","', $headers) . "\"\n"; + $h = true; + } + + foreach ($cs as $c) { + $row = [ + $c['id'], + $c['username'], + $c['fullname'], + $c['address'], + $c['phonenumber'], + $c['email'], + $c['balance'], + $c['service_type'], + ]; + echo '"' . implode('","', $row) . "\"\n"; + } + break; + //case csv-prepaid can be moved later to (plan.php) php file dealing with prepaid users + case 'csv-prepaid': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $cs = ORM::for_table('tbl_customers') + ->select('tbl_customers.id', 'id') + ->select('tbl_customers.username', 'username') + ->select('fullname') + ->select('address') + ->select('phonenumber') + ->select('email') + ->select('balance') + ->select('service_type') + ->select('namebp') + ->select('routers') + ->select('status') + ->select('method', 'Payment') + ->left_outer_join('tbl_user_recharges', array('tbl_customers.id', '=', 'tbl_user_recharges.customer_id')) + ->order_by_asc('tbl_customers.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="phpnuxbill_prepaid_users' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + + $headers = [ + 'id', + 'username', + 'fullname', + 'address', + 'phonenumber', + 'email', + 'balance', + 'service_type', + 'namebp', + 'routers', + 'status', + 'Payment' + ]; + + if (!$h) { + echo '"' . implode('","', $headers) . "\"\n"; + $h = true; + } + + foreach ($cs as $c) { + $row = [ + $c['id'], + $c['username'], + $c['fullname'], + $c['address'], + $c['phonenumber'], + $c['email'], + $c['balance'], + $c['service_type'], + $c['namebp'], + $c['routers'], + $c['status'], + $c['Payment'] + ]; + echo '"' . implode('","', $row) . "\"\n"; + } + break; + case 'add': + 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('xheader', $leafletpickerHeader); + run_hook('view_add_customer'); #HOOK + $ui->display('customers-add.tpl'); + break; + case 'recharge': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id_customer = $routes['2']; + $plan_id = $routes['3']; + $b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one(); + if ($b) { + $gateway = 'Recharge'; + $channel = $admin['fullname']; + $cust = User::_info($id_customer); + $plan = ORM::for_table('tbl_plans')->find_one($b['plan_id']); + list($bills, $add_cost) = User::getBills($id_customer); + if ($using == 'balance' && $config['enable_balance'] == 'yes') { + if (!$cust) { + r2(U . 'plan/recharge', 'e', Lang::T('Customer not found')); + } + if (!$plan) { + r2(U . 'plan/recharge', 'e', Lang::T('Plan not found')); + } + if ($cust['balance'] < ($plan['price'] + $add_cost)) { + r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance')); + } + $gateway = 'Recharge Balance'; + } + if ($using == 'zero') { + $zero = 1; + $gateway = 'Recharge Zero'; + } + $usings = explode(',', $config['payment_usings']); + $usings = array_filter(array_unique($usings)); + if (count($usings) == 0) { + $usings[] = Lang::T('Cash'); + } + $ui->assign('usings', $usings); + $ui->assign('bills', $bills); + $ui->assign('add_cost', $add_cost); + $ui->assign('cust', $cust); + $ui->assign('gateway', $gateway); + $ui->assign('channel', $channel); + $ui->assign('server', $b['routers']); + $ui->assign('plan', $plan); + $ui->display('recharge-confirm.tpl'); + } else { + r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan'); + } + break; + case 'deactivate': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id_customer = $routes['2']; + $plan_id = $routes['3']; + $b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one(); + if ($b) { + $p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->find_one(); + if ($p) { + if ($p['is_radius']) { + Radius::customerDeactivate($b['username']); + } else { + $mikrotik = Mikrotik::info($b['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($b['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $b['username']); + Mikrotik::removeHotspotActiveUser($client, $b['username']); + } else if ($b['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $b['username']); + Mikrotik::removePpoeActive($client, $b['username']); + } + } + $b->status = 'off'; + $b->expiration = date('Y-m-d'); + $b->time = date('H:i:s'); + $b->save(); + _log('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for ' . $b['username'], 'User', $b['customer_id']); + Message::sendTelegram('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for u' . $b['username']); + r2(U . 'customers/view/' . $id_customer, 's', 'Success deactivate customer to Mikrotik'); + } + } + r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan'); + break; + case 'sync': + $id_customer = $routes['2']; + $bs = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('status', 'on')->findMany(); + if ($bs) { + $routers = []; + foreach ($bs as $b) { + $c = ORM::for_table('tbl_customers')->find_one($id_customer); + $p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->where('enabled', '1')->find_one(); + if ($p) { + $routers[] = $b['routers']; + if ($p['is_radius']) { + Radius::customerAddPlan($c, $p, $p['expiration'] . ' ' . $p['time']); + } else { + $mikrotik = Mikrotik::info($b['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($b['type'] == 'Hotspot') { + Mikrotik::addHotspotUser($client, $p, $c); + } else if ($b['type'] == 'PPPOE') { + Mikrotik::addPpoeUser($client, $p, $c); + } + } + } + } + r2(U . 'customers/view/' . $id_customer, 's', 'Sync success to ' . implode(", ", $routers)); + } + r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan'); + break; + case 'viewu': + $customer = ORM::for_table('tbl_customers')->where('username', $routes['2'])->find_one(); + case 'view': + $id = $routes['2']; + run_hook('view_customer'); #HOOK + if (!$customer) { + $customer = ORM::for_table('tbl_customers')->find_one($id); + } + if ($customer) { + + + // Fetch the Customers Attributes values from the tbl_customer_custom_fields table + $customFields = ORM::for_table('tbl_customers_fields') + ->where('customer_id', $customer['id']) + ->find_many(); + $v = $routes['3']; + if (empty($v)) { + $v = 'activation'; + } + if ($v == 'order') { + $v = 'order'; + $query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id'); + $order = Paginator::findMany($query); + $ui->assign('order', $order); + } else if ($v == 'activation') { + $query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id'); + $activation = Paginator::findMany($query); + $ui->assign('activation', $activation); + } + $ui->assign('packages', User::_billing($customer['id'])); + $ui->assign('v', $v); + $ui->assign('d', $customer); + $ui->assign('customFields', $customFields); + $ui->assign('xheader', $leafletpickerHeader); + $ui->display('customers-view.tpl'); + } else { + r2(U . 'customers/list', 'e', Lang::T('Account Not Found')); + } + break; + case 'edit': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('edit_customer'); #HOOK + $d = ORM::for_table('tbl_customers')->find_one($id); + // Fetch the Customers Attributes values from the tbl_customers_fields table + $customFields = ORM::for_table('tbl_customers_fields') + ->where('customer_id', $id) + ->find_many(); + if ($d) { + $ui->assign('d', $d); + $ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status")); + $ui->assign('customFields', $customFields); + $ui->assign('xheader', $leafletpickerHeader); + $ui->display('customers-edit.tpl'); + } else { + r2(U . 'customers/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('delete_customer'); #HOOK + $d = ORM::for_table('tbl_customers')->find_one($id); + if ($d) { + // Delete the associated Customers Attributes records from tbl_customer_custom_fields table + ORM::for_table('tbl_customers_fields')->where('customer_id', $id)->delete_many(); + $c = ORM::for_table('tbl_user_recharges')->where('username', $d['username'])->find_one(); + if ($c) { + $p = ORM::for_table('tbl_plans')->find_one($c['plan_id']); + if ($p['is_radius']) { + Radius::customerDelete($d['username']); + } else { + $mikrotik = Mikrotik::info($c['routers']); + if ($c['type'] == 'Hotspot') { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removeHotspotUser($client, $d['username']); + Mikrotik::removeHotspotActiveUser($client, $d['username']); + } else { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removePpoeUser($client, $d['username']); + Mikrotik::removePpoeActive($client, $d['username']); + } + try { + $d->delete(); + } catch (Exception $e) { + } catch (Throwable $e) { + } + try { + $c->delete(); + } catch (Exception $e) { + } + } + } else { + try { + $d->delete(); + } catch (Exception $e) { + } catch (Throwable $e) { + } + try { + if ($c) + $c->delete(); + } catch (Exception $e) { + } catch (Throwable $e) { + } + } + + r2(U . 'customers/list', 's', Lang::T('User deleted Successfully')); + } + break; + + case 'add-post': + $username = _post('username'); + $fullname = _post('fullname'); + $password = _post('password'); + $pppoe_password = _post('pppoe_password'); + $email = _post('email'); + $address = _post('address'); + $phonenumber = _post('phonenumber'); + $service_type = _post('service_type'); + $account_type = _post('account_type'); + $status = _post('status'); + // Validate status value and set default if invalid + $validStatuses = ['Active', 'Banned', 'Disabled', 'Inactive', 'Limited', 'Suspended']; + if (!in_array($status, $validStatuses)) { + $status = 'Active'; // Default status + } + $coordinates = _post('coordinates'); + //post Customers Attributes + $custom_field_names = (array) $_POST['custom_field_name']; + $custom_field_values = (array) $_POST['custom_field_value']; + //additional information + $city = _post('city'); + $district = _post('district'); + $state = _post('state'); + $zip = _post('zip'); + + run_hook('add_customer'); #HOOK + $msg = ''; + if (Validator::Length($username, 35, 2) == false) { + $msg .= 'Username should be between 3 to 55 characters' . '
'; + } + if (Validator::Length($fullname, 36, 2) == false) { + $msg .= 'Full Name should be between 3 to 25 characters' . '
'; + } + if (!Validator::Length($password, 36, 2)) { + $msg .= 'Password should be between 3 to 35 characters' . '
'; + } + + $d = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($d) { + $msg .= Lang::T('Account already axist') . '
'; + } + + if ($msg == '') { + $d = ORM::for_table('tbl_customers')->create(); + $d->username = Lang::phoneFormat($username); + $d->password = $password; + $d->pppoe_password = $pppoe_password; + $d->email = $email; + $d->account_type = $account_type; + $d->status = $status; + $d->fullname = $fullname; + $d->address = $address; + $d->created_by = $admin['id']; + $d->phonenumber = Lang::phoneFormat($phonenumber); + $d->service_type = $service_type; + $d->coordinates = $coordinates; + $d->city = $city; + $d->district = $district; + $d->state = $state; + $d->zip = $zip; + $d->save(); + + // Retrieve the customer ID of the newly created customer + $customerId = $d->id(); + // Save Customers Attributes details + if (!empty($custom_field_names) && !empty($custom_field_values)) { + $totalFields = min(count($custom_field_names), count($custom_field_values)); + for ($i = 0; $i < $totalFields; $i++) { + $name = $custom_field_names[$i]; + $value = $custom_field_values[$i]; + + if (!empty($name)) { + $customField = ORM::for_table('tbl_customers_fields')->create(); + $customField->customer_id = $customerId; + $customField->field_name = $name; + $customField->field_value = $value; + $customField->save(); + } + } + } + + // Send registration notification SMS/WhatsApp + if (!empty($phonenumber) && strlen($phonenumber) > 5) { + $formattedPhone = Lang::phoneFormat($phonenumber); + $customerData = [ + 'fullname' => $fullname, + 'username' => Lang::phoneFormat($username), + 'password' => $password, + 'phonenumber' => $formattedPhone, + 'service_type' => $service_type + ]; + + Message::sendRegistrationNotification($customerData); + } + + r2(U . 'customers/list', 's', Lang::T('Account Created Successfully')); + } else { + r2(U . 'customers/add', 'e', $msg); + } + break; + + + case 'edit-post': + $username = Lang::phoneFormat(_post('username')); + $fullname = _post('fullname'); + $account_type = _post('account_type'); + $password = _post('password'); + $pppoe_password = _post('pppoe_password'); + $email = _post('email'); + $address = _post('address'); + $phonenumber = Lang::phoneFormat(_post('phonenumber')); + $service_type = _post('service_type'); + $coordinates = _post('coordinates'); + $status = _post('status'); + // Validate status value and set default if invalid + $validStatuses = ['Active', 'Banned', 'Disabled', 'Inactive', 'Limited', 'Suspended']; + if (!in_array($status, $validStatuses)) { + $status = 'Active'; // Default status + } + //additional information + $city = _post('city'); + $district = _post('district'); + $state = _post('state'); + $zip = _post('zip'); + run_hook('edit_customer'); #HOOK + $msg = ''; + if (Validator::Length($username, 35, 2) == false) { + $msg .= 'Username should be between 3 to 15 characters' . '
'; + } + if (Validator::Length($fullname, 36, 1) == false) { + $msg .= 'Full Name should be between 2 to 25 characters' . '
'; + } + if ($password != '') { + if (!Validator::Length($password, 36, 2)) { + $msg .= 'Password should be between 3 to 15 characters' . '
'; + } + } + + $id = _post('id'); + $d = ORM::for_table('tbl_customers')->find_one($id); + + //lets find user Customers Attributes using id + $customFields = ORM::for_table('tbl_customers_fields') + ->where('customer_id', $id) + ->find_many(); + + if (!$d) { + $msg .= Lang::T('Data Not Found') . '
'; + } + + $oldusername = $d['username']; + $oldPppoePassword = $d['password']; + $oldPassPassword = $d['pppoe_password']; + $userDiff = false; + $pppoeDiff = false; + $passDiff = false; + if ($oldusername != $username) { + $c = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($c) { + $msg .= Lang::T('Account already exist') . '
'; + } + $userDiff = true; + } + if ($oldPppoePassword != $pppoe_password) { + $pppoeDiff = true; + } + if ($password != '' && $oldPassPassword != $password) { + $passDiff = true; + } + + if ($msg == '') { + if ($userDiff) { + $d->username = $username; + } + if ($password != '') { + $d->password = $password; + } + $d->pppoe_password = $pppoe_password; + $d->fullname = $fullname; + $d->email = $email; + $d->account_type = $account_type; + $d->address = $address; + $d->status = $status; + $d->phonenumber = $phonenumber; + $d->service_type = $service_type; + $d->coordinates = $coordinates; + $d->city = $city; + $d->district = $district; + $d->state = $state; + $d->zip = $zip; + $d->save(); + + + // Update Customers Attributes values in tbl_customers_fields table + foreach ($customFields as $customField) { + $fieldName = $customField['field_name']; + if (isset($_POST['custom_fields'][$fieldName])) { + $customFieldValue = $_POST['custom_fields'][$fieldName]; + $customField->set('field_value', $customFieldValue); + $customField->save(); + } + } + + // Add new Customers Attributess + if (isset($_POST['custom_field_name']) && isset($_POST['custom_field_value'])) { + $newCustomFieldNames = $_POST['custom_field_name']; + $newCustomFieldValues = $_POST['custom_field_value']; + + // Check if the number of field names and values match + if (count($newCustomFieldNames) == count($newCustomFieldValues)) { + $numNewFields = count($newCustomFieldNames); + + for ($i = 0; $i < $numNewFields; $i++) { + $fieldName = $newCustomFieldNames[$i]; + $fieldValue = $newCustomFieldValues[$i]; + + // Insert the new Customers Attributes + $newCustomField = ORM::for_table('tbl_customers_fields')->create(); + $newCustomField->set('customer_id', $id); + $newCustomField->set('field_name', $fieldName); + $newCustomField->set('field_value', $fieldValue); + $newCustomField->save(); + } + } + } + + // Delete Customers Attributess + if (isset($_POST['delete_custom_fields'])) { + $fieldsToDelete = $_POST['delete_custom_fields']; + foreach ($fieldsToDelete as $fieldName) { + // Delete the Customers Attributes with the given field name + ORM::for_table('tbl_customers_fields') + ->where('field_name', $fieldName) + ->where('customer_id', $id) + ->delete_many(); + } + } + + if ($userDiff || $pppoeDiff || $passDiff) { + $c = ORM::for_table('tbl_user_recharges')->where('username', ($userDiff) ? $oldusername : $username)->find_one(); + if ($c) { + $c->username = $username; + $c->save(); + $p = ORM::for_table('tbl_plans')->find_one($c['plan_id']); + if ($p['is_radius']) { + if ($userDiff) { + Radius::customerChangeUsername($oldusername, $username); + } + Radius::customerAddPlan($d, $p, $p['expiration'] . ' ' . $p['time']); + } else { + $mikrotik = Mikrotik::info($c['routers']); + if ($c['type'] == 'Hotspot') { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::setHotspotUser($client, $c['username'], $password); + Mikrotik::removeHotspotActiveUser($client, $d['username']); + } else { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if (!empty($d['pppoe_password'])) { + Mikrotik::setPpoeUser($client, $c['username'], $d['pppoe_password']); + } else { + Mikrotik::setPpoeUser($client, $c['username'], $password); + } + Mikrotik::removePpoeActive($client, $d['username']); + } + } + } + } + r2(U . 'customers/view/' . $id, 's', 'User Updated Successfully'); + } else { + r2(U . 'customers/edit/' . $id, 'e', $msg); + } + break; + + default: + run_hook('list_customers'); #HOOK + $search = _post('search'); + $order = _post('order', 'username'); + $filter = _post('filter', 'All'); + $orderby = _post('orderby', 'asc'); + $order_pos = [ + 'username' => 0, + 'created_at' => 8, + 'balance' => 3, + 'status' => 7 + ]; + + if ($search != '') { + $query = ORM::for_table('tbl_customers') + ->whereRaw("(username LIKE '%$search%' OR fullname LIKE '%$search%' OR address LIKE '%$search%' " . + "OR phonenumber LIKE '%$search%' OR email LIKE '%$search%')"); + if ($filter != 'All') { + $query->where("status", $filter); + } + } else { + $query = ORM::for_table('tbl_customers'); + if ($filter != 'All') { + $query->where("status", $filter); + } + } + if ($orderby == 'asc') { + $query->order_by_asc($order); + } else { + $query->order_by_desc($order); + } + $d = $query->findMany(); + if (_post('export', '') == 'csv') { + $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="phpnuxbill_customers_' . $filter . '_' . date('Y-m-d_H_i') . '.csv"'); + header('Content-Transfer-Encoding: binary'); + + $headers = [ + 'id', + 'username', + 'fullname', + 'address', + 'phonenumber', + 'email', + 'balance', + 'service_type', + ]; + $fp = fopen('php://output', 'wb'); + if (!$h) { + fputcsv($fp, $headers, ";"); + $h = true; + } + foreach ($d as $c) { + $row = [ + $c['id'], + $c['username'], + $c['fullname'], + str_replace("\n", " ", $c['address']), + $c['phonenumber'], + $c['email'], + $c['balance'], + $c['service_type'], + ]; + fputcsv($fp, $row, ";"); + } + fclose($fp); + die(); + } + $ui->assign('xheader', ''); + $ui->assign('d', $d); + $ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status")); + $ui->assign('filter', $filter); + $ui->assign('search', $search); + $ui->assign('order', $order); + $ui->assign('order_pos', $order_pos[$order]); + $ui->assign('orderby', $orderby); + $ui->display('customers.tpl'); + break; +} diff --git a/system/controllers/dashboard.php b/system/controllers/dashboard.php new file mode 100644 index 0000000..373edc9 --- /dev/null +++ b/system/controllers/dashboard.php @@ -0,0 +1,207 @@ +assign('_title', Lang::T('Dashboard')); +$ui->assign('_admin', $admin); + +if(isset($_GET['refresh'])){ + $files = scandir($CACHE_PATH); + foreach ($files as $file) { + $ext = pathinfo($file, PATHINFO_EXTENSION); + if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') { + unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file); + } + } + r2(U . 'dashboard', 's', 'Data Refreshed'); +} + +$fdate = date('Y-m-01'); +$tdate = date('Y-m-t'); +//first day of month +$first_day_month = date('Y-m-01'); +$mdate = date('Y-m-d'); +$month_n = date('n'); + +$iday = ORM::for_table('tbl_transactions') + ->where('recharged_on', $mdate) + ->where_not_equal('method', 'Customer - Balance') + ->where_not_equal('method', 'Recharge Balance - Administrator') + ->sum('price'); + +if ($iday == '') { + $iday = '0.00'; +} +$ui->assign('iday', $iday); + +$imonth = ORM::for_table('tbl_transactions')->where_not_equal('method', 'Customer - Balance')->where_not_equal('method', 'Recharge Balance - Administrator')->where_gte('recharged_on', $first_day_month)->where_lte('recharged_on', $mdate)->sum('price'); +if ($imonth == '') { + $imonth = '0.00'; +} +$ui->assign('imonth', $imonth); + +$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count(); +if (empty($u_act)) { + $u_act = '0'; +} +$ui->assign('u_act', $u_act); + +$u_all = ORM::for_table('tbl_user_recharges')->count(); +if (empty($u_all)) { + $u_all = '0'; +} +$ui->assign('u_all', $u_all); + + +$c_all = ORM::for_table('tbl_customers')->count(); +if (empty($c_all)) { + $c_all = '0'; +} +$ui->assign('c_all', $c_all); + +if ($config['hide_uet'] != 'yes') { + //user expire + $query = ORM::for_table('tbl_user_recharges') + ->select('tbl_user_recharges.*') + ->select('tbl_customers.fullname') + ->left_outer_join('tbl_customers', array('tbl_customers.id', '=', 'tbl_user_recharges.customer_id')) + ->where_lte('tbl_user_recharges.expiration', $mdate) + ->order_by_desc('tbl_user_recharges.expiration'); + $expire = Paginator::findMany($query); + + // Get the total count of expired records for pagination + $totalCount = ORM::for_table('tbl_user_recharges') + ->where_lte('expiration', $mdate) + ->count(); + + // Pass the total count and current page to the paginator + $paginator['total_count'] = $totalCount; + + // Assign the pagination HTML to the template variable + $ui->assign('expire', $expire); +} + +//activity log +$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->find_many(); +$ui->assign('dlog', $dlog); +$log = ORM::for_table('tbl_logs')->count(); +$ui->assign('log', $log); + + +if ($config['hide_vs'] != 'yes') { + $cacheStocksfile = $CACHE_PATH . File::pathFixer('/VoucherStocks.temp'); + $cachePlanfile = $CACHE_PATH . File::pathFixer('/VoucherPlans.temp'); + //Cache for 5 minutes + if (file_exists($cacheStocksfile) && time() - filemtime($cacheStocksfile) < 600) { + $stocks = json_decode(file_get_contents($cacheStocksfile), true); + $plans = json_decode(file_get_contents($cachePlanfile), true); + } else { + // Count stock + $tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many(); + $plans = array(); + $stocks = array("used" => 0, "unused" => 0); + $n = 0; + foreach ($tmp as $plan) { + $unused = ORM::for_table('tbl_voucher') + ->where('id_plan', $plan['id']) + ->where('status', 0)->count(); + $used = ORM::for_table('tbl_voucher') + ->where('id_plan', $plan['id']) + ->where('status', 1)->count(); + if ($unused > 0 || $used > 0) { + $plans[$n]['name_plan'] = $plan['name_plan']; + $plans[$n]['unused'] = $unused; + $plans[$n]['used'] = $used; + $stocks["unused"] += $unused; + $stocks["used"] += $used; + $n++; + } + } + file_put_contents($cacheStocksfile, json_encode($stocks)); + file_put_contents($cachePlanfile, json_encode($plans)); + } +} + +$cacheMRfile = File::pathFixer('/monthlyRegistered.temp'); +//Cache for 1 hour +if (file_exists($cacheMRfile) && time() - filemtime($cacheMRfile) < 3600) { + $monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true); +} else { + //Monthly Registered Customers + $result = ORM::for_table('tbl_customers') + ->select_expr('MONTH(created_at)', 'month') + ->select_expr('COUNT(*)', 'count') + ->where_raw('YEAR(created_at) = YEAR(NOW())') + ->group_by_expr('MONTH(created_at)') + ->find_many(); + + $monthlyRegistered = []; + foreach ($result as $row) { + $monthlyRegistered[] = [ + 'date' => $row->month, + 'count' => $row->count + ]; + } + file_put_contents($cacheMRfile, json_encode($monthlyRegistered)); +} + +$cacheMSfile = $CACHE_PATH . File::pathFixer('/monthlySales.temp'); +//Cache for 12 hours +if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) { + $monthlySales = json_decode(file_get_contents($cacheMSfile), true); +} else { + // Query to retrieve monthly data + $results = ORM::for_table('tbl_transactions') + ->select_expr('MONTH(recharged_on)', 'month') + ->select_expr('SUM(price)', 'total') + ->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year + ->where_not_equal('method', 'Customer - Balance') + ->where_not_equal('method', 'Recharge Balance - Administrator') + ->group_by_expr('MONTH(recharged_on)') + ->find_many(); + + // Create an array to hold the monthly sales data + $monthlySales = array(); + + // Iterate over the results and populate the array + foreach ($results as $result) { + $month = $result->month; + $totalSales = $result->total; + + $monthlySales[$month] = array( + 'month' => $month, + 'totalSales' => $totalSales + ); + } + + // Fill in missing months with zero sales + for ($month = 1; $month <= 12; $month++) { + if (!isset($monthlySales[$month])) { + $monthlySales[$month] = array( + 'month' => $month, + 'totalSales' => 0 + ); + } + } + + // Sort the array by month + ksort($monthlySales); + + // Reindex the array + $monthlySales = array_values($monthlySales); + file_put_contents($cacheMSfile, json_encode($monthlySales)); +} + +// Assign the monthly sales data to Smarty +$ui->assign('monthlySales', $monthlySales); +$ui->assign('xfooter', ''); +$ui->assign('monthlyRegistered', $monthlyRegistered); +$ui->assign('stocks', $stocks); +$ui->assign('plans', $plans); + +run_hook('view_dashboard'); #HOOK +$ui->display('dashboard.tpl'); diff --git a/system/controllers/default.php b/system/controllers/default.php new file mode 100644 index 0000000..d32f8ce --- /dev/null +++ b/system/controllers/default.php @@ -0,0 +1,13 @@ +assign('_title', Lang::T('Reports')); +$ui->assign('_sysfrm_menu', 'reports'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +$mdate = date('Y-m-d'); +$tdate = date('Y-m-d', strtotime('today - 30 days')); + +//first day of month +$first_day_month = date('Y-m-01'); +// +$this_week_start = date('Y-m-d', strtotime('previous sunday')); +// 30 days before +$before_30_days = date('Y-m-d', strtotime('today - 30 days')); +//this month +$month_n = date('n'); + +switch ($action) { + + case 'print-by-date': + $mdate = date('Y-m-d'); + $d = ORM::for_table('tbl_transactions'); + $d->where('recharged_on', $mdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + $dr->where('recharged_on', $mdate); + $dr->order_by_desc('id'); + $xy = $dr->sum('price'); + + $ui->assign('d', $x); + $ui->assign('dr', $xy); + $ui->assign('mdate', $mdate); + $ui->assign('recharged_on', $mdate); + run_hook('print_by_date'); #HOOK + $ui->display('print-by-date.tpl'); + break; + + case 'pdf-by-date': + $mdate = date('Y-m-d'); + + $d = ORM::for_table('tbl_transactions'); + $d->where('recharged_on', $mdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + $dr->where('recharged_on', $mdate); + $dr->order_by_desc('id'); + $xy = $dr->sum('price'); + + $title = ' Reports [' . $mdate . ']'; + $title = str_replace('-', ' ', $title); + + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + if (file_exists($UPLOAD_PATH . '/logo.png')) { + $logo = $UPLOAD_URL_PATH . '/logo.png'; + } else { + $logo = $UPLOAD_URL_PATH . '/logo.default.png'; + } + + if ($x) { + $html = ' +
+
+

' . $config['CompanyName'] . '

+ ' . $config['address'] . '
+ ' . Lang::T('Phone Number') . ': ' . $config['phone'] . '
+
+ +
+ + + + + + + + + + + + '; + $c = true; + foreach ($x as $value) { + + $username = $value['username']; + $plan_name = $value['plan_name']; + $type = $value['type']; + $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); + $recharged_on = date($config['date_format'], strtotime($value['recharged_on'])); + $expiration = date($config['date_format'], strtotime($value['expiration'])); + $time = $value['time']; + $method = $value['method']; + $routers = $value['routers']; + + $html .= "" . " + + + + + + + + + "; + } + $html .= '
' . Lang::T('Username') . '' . Lang::T('Plan Name') . '' . Lang::T('Type') . '' . Lang::T('Plan Price') . '' . Lang::T('Created On') . '' . Lang::T('Expires On') . '' . Lang::T('Method') . '' . Lang::T('Routers') . '
$username$plan_name$type$price$recharged_on$expiration $time $method$routers
+

' . Lang::T('Total Income') . ':

+

' . $config['currency_code'] . ' ' . number_format($xy, 2, $config['dec_point'], $config['thousands_sep']) . '

'; + run_hook('print_pdf_by_date'); #HOOK + + $mpdf = new \Mpdf\Mpdf(); + $mpdf->SetProtection(array('print')); + $mpdf->SetTitle($config['CompanyName'] . ' Reports'); + $mpdf->SetAuthor($config['CompanyName']); + $mpdf->SetWatermarkText($d['price']); + $mpdf->showWatermarkText = true; + $mpdf->watermark_font = 'Helvetica'; + $mpdf->watermarkTextAlpha = 0.1; + $mpdf->SetDisplayMode('fullpage'); + + $style = ''; + + $nhtml = <<WriteHTML($nhtml); + $mpdf->Output(date('Ymd_His') . '.pdf', 'D'); + } else { + echo 'No Data'; + } + + break; + + case 'print-by-period': + $fdate = _post('fdate'); + $tdate = _post('tdate'); + $stype = _post('stype'); + + $d = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $d->where('type', $stype); + } + $d->where_gte('recharged_on', $fdate); + $d->where_lte('recharged_on', $tdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $dr->where('type', $stype); + } + + $dr->where_gte('recharged_on', $fdate); + $dr->where_lte('recharged_on', $tdate); + $xy = $dr->sum('price'); + + $ui->assign('d', $x); + $ui->assign('dr', $xy); + $ui->assign('fdate', $fdate); + $ui->assign('tdate', $tdate); + $ui->assign('stype', $stype); + run_hook('print_by_period'); #HOOK + $ui->display('print-by-period.tpl'); + break; + + + case 'pdf-by-period': + $fdate = _post('fdate'); + $tdate = _post('tdate'); + $stype = _post('stype'); + $d = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $d->where('type', $stype); + } + + $d->where_gte('recharged_on', $fdate); + $d->where_lte('recharged_on', $tdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $dr->where('type', $stype); + } + + $dr->where_gte('recharged_on', $fdate); + $dr->where_lte('recharged_on', $tdate); + $xy = $dr->sum('price'); + + $title = ' Reports [' . $mdate . ']'; + $title = str_replace('-', ' ', $title); + + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + if (file_exists($UPLOAD_PATH . '/logo.png')) { + $logo = $UPLOAD_URL_PATH . '/logo.png'; + } else { + $logo = $UPLOAD_URL_PATH . '/logo.default.png'; + } + + if ($x) { + $html = ' +
+
+

' . $config['CompanyName'] . '

+ ' . $config['address'] . '
+ ' . Lang::T('Phone Number') . ': ' . $config['phone'] . '
+
+ +
+ + + + + + + + + + + + '; + $c = true; + foreach ($x as $value) { + + $username = $value['username']; + $plan_name = $value['plan_name']; + $type = $value['type']; + $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); + $recharged_on = date($config['date_format'], strtotime($value['recharged_on'])); + $expiration = date($config['date_format'], strtotime($value['expiration'])); + $time = $value['time']; + $method = $value['method']; + $routers = $value['routers']; + + $html .= "" . " + + + + + + + + + "; + } + $html .= '
' . Lang::T('Username') . '' . Lang::T('Plan Name') . '' . Lang::T('Type') . '' . Lang::T('Plan Price') . '' . Lang::T('Created On') . '' . Lang::T('Expires On') . '' . Lang::T('Method') . '' . Lang::T('Routers') . '
$username$plan_name$type$price$recharged_on $expiration $time $method$routers
+

' . Lang::T('Total Income') . ':

+

' . $config['currency_code'] . ' ' . number_format($xy, 2, $config['dec_point'], $config['thousands_sep']) . '

'; + + run_hook('pdf_by_period'); #HOOK + $mpdf = new \Mpdf\Mpdf(); + $mpdf->SetProtection(array('print')); + $mpdf->SetTitle($config['CompanyName'] . ' Reports'); + $mpdf->SetAuthor($config['CompanyName']); + $mpdf->SetWatermarkText($d['price']); + $mpdf->showWatermarkText = true; + $mpdf->watermark_font = 'Helvetica'; + $mpdf->watermarkTextAlpha = 0.1; + $mpdf->SetDisplayMode('fullpage'); + + $style = ''; + + $nhtml = <<WriteHTML($nhtml); + $mpdf->Output(date('Ymd_His') . '.pdf', 'D'); + } else { + echo 'No Data'; + } + + break; + + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/home.php b/system/controllers/home.php new file mode 100644 index 0000000..7640d05 --- /dev/null +++ b/system/controllers/home.php @@ -0,0 +1,252 @@ +assign('_title', Lang::T('Dashboard')); + +$user = User::_info(); +$ui->assign('_user', $user); + +if (isset($_GET['renewal'])) { + $user->auto_renewal = $_GET['renewal']; + $user->save(); +} + +if (_post('send') == 'balance') { + if ($config['enable_balance'] == 'yes' && $config['allow_balance_transfer'] == 'yes') { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $target = ORM::for_table('tbl_customers')->where('username', _post('username'))->find_one(); + if (!$target) { + r2(U . 'home', 'd', Lang::T('Username not found')); + } + $username = _post('username'); + $balance = _post('balance'); + if ($user['balance'] < $balance) { + r2(U . 'home', 'd', Lang::T('insufficient balance')); + } + if (!empty($config['minimum_transfer']) && intval($balance) < intval($config['minimum_transfer'])) { + r2(U . 'home', 'd', Lang::T('Minimum Transfer') . ' ' . Lang::moneyFormat($config['minimum_transfer'])); + } + if ($user['username'] == $target['username']) { + r2(U . 'home', 'd', Lang::T('Cannot send to yourself')); + } + if (Balance::transfer($user['id'], $username, $balance)) { + //sender + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $user['username']; + $d->gateway = $target['username']; + $d->plan_id = 0; + $d->plan_name = 'Send Balance'; + $d->routers_id = 0; + $d->routers = 'balance'; + $d->price = $balance; + $d->payment_method = "Customer"; + $d->payment_channel = "Balance"; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->status = 2; + $d->save(); + //receiver + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $target['username']; + $d->gateway = $user['username']; + $d->plan_id = 0; + $d->plan_name = 'Receive Balance'; + $d->routers_id = 0; + $d->routers = 'balance'; + $d->payment_method = "Customer"; + $d->payment_channel = "Balance"; + $d->price = $balance; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->status = 2; + $d->save(); + Message::sendBalanceNotification($user['phonenumber'], $target['fullname'] . ' (' . $target['username'] . ')', $balance, ($user['balance'] - $balance), Lang::getNotifText('balance_send'), $config['user_notification_payment']); + Message::sendBalanceNotification($target['phonenumber'], $user['fullname'] . ' (' . $user['username'] . ')', $balance, ($target['balance'] + $balance), Lang::getNotifText('balance_received'), $config['user_notification_payment']); + Message::sendTelegram("#u$user[username] send balance to #u$target[username] \n" . Lang::moneyFormat($balance)); + r2(U . 'home', 's', Lang::T('Sending balance success')); + } + } else { + r2(U . 'home', 'd', Lang::T('Failed, balance is not available')); + } +} else if (_post('send') == 'plan') { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $actives = ORM::for_table('tbl_user_recharges') + ->where('username', _post('username')) + ->find_many(); + foreach ($actives as $active) { + $router = ORM::for_table('tbl_routers')->where('name', $active['routers'])->find_one(); + if ($router) { + r2(U . "order/send/$router[id]/$active[plan_id]&u=" . trim(_post('username')), 's', Lang::T('Review package before recharge')); + } + } + r2(U . 'home', 'w', Lang::T('Your friend do not have active package')); +} + +$ui->assign('_bills', User::_billing()); + +if (isset($_GET['recharge']) && !empty($_GET['recharge'])) { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + if (!empty(App::getTokenValue(_get('stoken')))) { + r2(U . "voucher/invoice/"); + die(); + } + $bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['recharge'])->where('username', $user['username'])->findOne(); + if ($bill) { + if ($bill['routers'] == 'radius') { + $router = 'radius'; + } else { + $routers = ORM::for_table('tbl_routers')->where('name', $bill['routers'])->find_one(); + $router = $routers['id']; + } + if ($config['enable_balance'] == 'yes') { + $plan = ORM::for_table('tbl_plans')->find_one($bill['plan_id']); + if (!$plan['enabled']) { + r2(U . "home", 'e', 'Plan is not exists'); + } + if ($user['balance'] > $plan['price']) { + r2(U . "order/pay/$router/$bill[plan_id]&stoken=" . _get('stoken'), 'e', 'Order Plan'); + } else { + r2(U . "order/buy/$router/$bill[plan_id]", 'e', 'Order Plan'); + } + } else { + r2(U . "order/buy/$router/$bill[plan_id]", 'e', 'Order Plan'); + } + } +} else if (!empty(_get('extend'))) { + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + if (!$config['extend_expired']) { + r2(U . 'home', 'e', "cannot extend"); + } + if (!empty(App::getTokenValue(_get('stoken')))) { + r2(U . 'home', 'e', "You already extend"); + } + $id = _get('extend'); + $tur = ORM::for_table('tbl_user_recharges')->where('customer_id', $user['id'])->where('id', $id)->find_one(); + if ($tur) { + $m = date("m"); + $path = $CACHE_PATH . DIRECTORY_SEPARATOR . "extends" . DIRECTORY_SEPARATOR; + if (!file_exists($path)) { + mkdir($path); + } + $path .= $user['id'] . ".txt"; + if (file_exists($path)) { + // is already extend + $last = file_get_contents($path); + if ($last == $m) { + r2(U . 'home', 'e', "You already extend for this month"); + } + } + if ($tur['status'] != 'on') { + if ($tur['routers'] != 'radius') { + $mikrotik = Mikrotik::info($tur['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $router = $tur['routers']; + } + $p = ORM::for_table('tbl_plans')->findOne($tur['plan_id']); + if (!$p) { + r2(U . 'home', '3', "Plan Not Found"); + } + if ($tur['routers'] == 'radius') { + Radius::customerAddPlan($user, $p, $tur['expiration'] . ' ' . $tur['time']); + } else { + if ($tur['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $user['username']); + Mikrotik::addHotspotUser($client, $p, $user); + } else if ($tur['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $user['username']); + Mikrotik::addPpoeUser($client, $p, $user); + } + } + // make customer cannot extend again + $days = $config['extend_days']; + $expiration = date('Y-m-d', strtotime(" +$days day")); + $tur->expiration = $expiration; + $tur->status = "on"; + $tur->save(); + App::setToken(_get('stoken'), $id); + file_put_contents($path, $m); + _log("Customer $tur[customer_id] $tur[username] extend for $days days", "Customer", $user['id']); + Message::sendTelegram("#u$user[username] #extend #" . $p['type'] . " \n" . $p['name_plan'] . + "\nLocation: " . $p['routers'] . + "\nCustomer: " . $user['fullname'] . + "\nNew Expired: " . Lang::dateAndTimeFormat($expiration, $tur['time'])); + r2(U . 'home', 's', "Extend until $expiration"); + } else { + r2(U . 'home', 'e', "Plan is not expired"); + } + } else { + r2(U . 'home', 'e', "Plan Not Found or Not Active"); + } +} else if (isset($_GET['deactivate']) && !empty($_GET['deactivate'])) { + $bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['deactivate'])->where('username', $user['username'])->findOne(); + if ($bill) { + $p = ORM::for_table('tbl_plans')->where('id', $bill['plan_id'])->find_one(); + if ($p['is_radius']) { + Radius::customerDeactivate($user['username']); + } else { + try { + $mikrotik = Mikrotik::info($bill['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($bill['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $bill['username']); + Mikrotik::removeHotspotActiveUser($client, $bill['username']); + } else if ($bill['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $bill['username']); + Mikrotik::removePpoeActive($client, $bill['username']); + } + } catch (Exception $e) { + //ignore it maybe mikrotik has been deleted + } + } + $bill->status = 'off'; + $bill->expiration = date('Y-m-d'); + $bill->time = date('H:i:s'); + $bill->save(); + _log('User ' . $bill['username'] . ' Deactivate ' . $bill['namebp'], 'Customer', $bill['customer_id']); + Message::sendTelegram('User u' . $bill['username'] . ' Deactivate ' . $bill['namebp']); + r2(U . 'home', 's', 'Success deactivate ' . $bill['namebp']); + } else { + r2(U . 'home', 'e', 'No Active Plan'); + } +} + +if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + $ui->assign('nux_mac', $_SESSION['nux-mac']); + $ui->assign('nux_ip', $_SESSION['nux-ip']); + $bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['id'])->where('username', $user['username'])->findOne(); + if ($_GET['mikrotik'] == 'login') { + $m = Mikrotik::info($bill['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeIn($c, $user['username'], $user['password'], $_SESSION['nux-ip'], $_SESSION['nux-mac']); + r2(U . 'home', 's', Lang::T('Login Request successfully')); + } else if ($_GET['mikrotik'] == 'logout') { + $m = Mikrotik::info($bill['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeOut($c, $user['username']); + r2(U . 'home', 's', Lang::T('Logout Request successfully')); + } +} + +$ui->assign('unpaid', ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->where('status', 1) + ->find_one()); +run_hook('view_customer_dashboard'); #HOOK +$ui->display('user-dashboard.tpl'); diff --git a/system/controllers/index.html b/system/controllers/index.html new file mode 100644 index 0000000..9757970 --- /dev/null +++ b/system/controllers/index.html @@ -0,0 +1,8 @@ + + + 403 Forbidden + + +

Directory access is forbidden.

+ + \ No newline at end of file diff --git a/system/controllers/login.php b/system/controllers/login.php new file mode 100644 index 0000000..449b6cc --- /dev/null +++ b/system/controllers/login.php @@ -0,0 +1,195 @@ +where('username', $username)->find_one(); + if ($d) { + $d_pass = $d['password']; + if ($d['status'] == 'Banned') { + echo '
' . Lang::T('This account status') . ': ' . Lang::T($d['status']) . '
'; + } + if (Password::_uverify($password, $d_pass) == true) { + $_SESSION['uid'] = $d['id']; + User::setCookie($d['id']); + $d->last_login = date('Y-m-d H:i:s'); + $d->save(); + _log($username . ' ' . Lang::T('Login Successful'), 'User', $d['id']); + echo '
' . Lang::T('Login Successful') . '
'; + r2(U . 'home'); + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + _log($username . ' ' . Lang::T('Failed Login'), 'User'); + r2(U . 'login'); + } + } else { + $d = ORM::for_table('tbl_users')->where('username', $username)->find_one(); + if ($d) { + $d_pass = $d['password']; + if (Password::_verify($password, $d_pass) == true) { + $_SESSION['aid'] = $d['id']; + $token = Admin::setCookie($d['id']); + $d->last_login = date('Y-m-d H:i:s'); + $d->save(); + _log($username . ' ' . Lang::T('Login Successful'), $d['user_type'], $d['id']); + echo '
' . Lang::T('Login Successful') . '
'; + r2(U . 'dashboard'); + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + _log($username . ' ' . Lang::T('Failed Login'), $d['user_type']); + r2(U . 'login'); + } + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + r2(U . 'login'); + } + } + } else { + echo '
' . Lang::T('Invalid Username or Password') . '
'; + r2(U . 'login'); + } + + break; + + case 'activation': + $voucher = _post('voucher'); + $username = _post('username'); + $v1 = ORM::for_table('tbl_voucher')->where('code', $voucher)->find_one(); + if ($v1) { + // voucher exists, check customer exists or not + $user = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if (!$user) { + $d = ORM::for_table('tbl_customers')->create(); + $d->username = alphanumeric($username, "+_."); + $d->password = $voucher; + $d->fullname = ''; + $d->address = ''; + $d->email = ''; + $d->phonenumber = (strlen($username) < 21) ? $username : ''; + if ($d->save()) { + $user = ORM::for_table('tbl_customers')->where('username', $username)->find_one($d->id()); + if (!$user) { + r2(U . 'login', 'e', Lang::T('Voucher activation failed')); + } + } else { + _alert(Lang::T('Login Successful'), 'success', "dashboard"); + r2(U . 'login', 'e', Lang::T('Voucher activation failed') . '.'); + } + } + if ($v1['status'] == 0) { + $oldPass = $user['password']; + // change customer password to voucher code + $user->password = $voucher; + $user->save(); + // voucher activation + if (Package::rechargeUser($user['id'], $v1['routers'], $v1['id_plan'], "Voucher", $voucher)) { + $v1->status = "1"; + $v1->user = $user['username']; + $v1->save(); + $user->last_login = date('Y-m-d H:i:s'); + $user->save(); + // add customer to mikrotik + if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + try { + $m = Mikrotik::info($v1['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeIn($c, $user['username'], $user['password'], $_SESSION['nux-ip'], $_SESSION['nux-mac']); + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, you are connected to internet")); + } + } catch (Exception $e) { + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } + } + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } else { + // if failed to recharge, restore old password + $user->password = $oldPass; + $user->save(); + r2(U . 'login', 'e', Lang::T("Failed to activate voucher")); + } + } else { + // used voucher + // check if voucher used by this username + if ($v1['user'] == $user['username']) { + $user->last_login = date('Y-m-d H:i:s'); + $user->save(); + if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) { + try { + $m = Mikrotik::info($v1['routers']); + $c = Mikrotik::getClient($m['ip_address'], $m['username'], $m['password']); + Mikrotik::logMeIn($c, $user['username'], $user['password'], $_SESSION['nux-ip'], $_SESSION['nux-mac']); + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } catch (Exception $e) { + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } + } else { + if (!empty($config['voucher_redirect'])) { + r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet")); + } else { + r2(U . "login", 's', Lang::T("Voucher activation success, now you can login")); + } + } + } else { + // voucher used by other customer + r2(U . 'login', 'e', Lang::T('Voucher Not Valid')); + } + } + } else { + _msglog('e', Lang::T('Invalid Username or Password')); + r2(U . 'login'); + } + default: + run_hook('customer_view_login'); #HOOK + if ($config['disable_registration'] == 'yes') { + $ui->display('user-login-noreg.tpl'); + } else { + $ui->display('user-login.tpl'); + } + break; +} 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->assign('_title', 'Radius Logs'); + $ui->display('logs-radius.tpl'); + break; + + case 'cron': + $q = (_post('q') ? _post('q') : _get('q')); + $cron_type = (_post('cron_type') ? _post('cron_type') : _get('cron_type')); + $status = (_post('status') ? _post('status') : _get('status')); + + // Build query + $query = ORM::for_table('tbl_cron_logs'); + + if (!empty($q)) { + $query->where_like('error_message', '%' . $q . '%'); + } + + if (!empty($cron_type)) { + $query->where('cron_type', $cron_type); + } + + if (!empty($status)) { + $query->where('status', $status); + } + + $query->order_by_desc('started_at'); + $d = Paginator::findMany($query, ['q' => $q, 'cron_type' => $cron_type, 'status' => $status]); + + // Get statistics + $stats = CronLog::getStats(7); + + $ui->assign('d', $d); + $ui->assign('q', $q); + $ui->assign('cron_type', $cron_type); + $ui->assign('status', $status); + $ui->assign('stats', $stats); + $ui->assign('_title', 'Cron Job Logs'); + $ui->display('logs-cron.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'); + +?> diff --git a/system/controllers/monitoring.php b/system/controllers/monitoring.php new file mode 100644 index 0000000..7610be3 --- /dev/null +++ b/system/controllers/monitoring.php @@ -0,0 +1,212 @@ +assign('_title', Lang::T('Router Monitoring')); +$ui->assign('_system_menu', 'monitoring'); + +$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 'dashboard': + $ui->assign('xfooter', ''); + $ui->display('monitoring.tpl'); + break; + + case 'get_router_status': + $routerId = $_GET['router_id'] ?? null; + $hours = $_GET['hours'] ?? 24; + + if ($routerId) { + $data = getRouterMonitoringData($routerId, $hours); + } else { + $data = getAllRoutersMonitoringData($hours); + } + + header('Content-Type: application/json'); + echo json_encode($data); + exit; + break; + + case 'get_router_history': + $routerId = $_GET['router_id'] ?? null; + $days = $_GET['days'] ?? 7; + + if (!$routerId) { + http_response_code(400); + echo json_encode(['error' => 'Router ID required']); + exit; + } + + $data = getRouterHistoryData($routerId, $days); + header('Content-Type: application/json'); + echo json_encode($data); + exit; + break; + + case 'get_alerts': + $hours = $_GET['hours'] ?? 24; + $data = getRecentAlerts($hours); + header('Content-Type: application/json'); + echo json_encode($data); + exit; + break; + + default: + r2(U . 'monitoring/dashboard/', 's', ''); +} + +/** + * Get monitoring data for specific router + */ +function getRouterMonitoringData($routerId, $hours = 24) { + $cutoffTime = date('Y-m-d H:i:s', strtotime("-$hours hours")); + + $router = ORM::for_table('tbl_routers')->find_one($routerId); + if (!$router) { + return ['error' => 'Router not found']; + } + + $monitoringData = ORM::for_table('tbl_router_monitoring') + ->where('router_id', $routerId) + ->where_gte('timestamp', $cutoffTime) + ->order_by_desc('timestamp') + ->find_many(); + + $data = [ + 'router' => [ + 'id' => $router['id'], + 'name' => $router['name'], + 'ip_address' => $router['ip_address'], + 'status' => $router['status'], + 'last_check' => $router['last_check'], + 'last_error' => $router['last_error'] + ], + 'monitoring' => [] + ]; + + foreach ($monitoringData as $record) { + $data['monitoring'][] = [ + 'timestamp' => $record['timestamp'], + 'ping_status' => (bool)$record['ping_status'], + 'api_status' => (bool)$record['api_status'], + 'uptime' => $record['uptime'], + 'free_memory' => (int)$record['free_memory'], + 'total_memory' => (int)$record['total_memory'], + 'cpu_load' => (int)$record['cpu_load'], + 'temperature' => $record['temperature'], + 'voltage' => $record['voltage'], + 'error' => $record['error'] + ]; + } + + return $data; +} + +/** + * Get monitoring data for all routers + */ +function getAllRoutersMonitoringData($hours = 24) { + $cutoffTime = date('Y-m-d H:i:s', strtotime("-$hours hours")); + + $routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many(); + $data = []; + + foreach ($routers as $router) { + $latestData = ORM::for_table('tbl_router_monitoring') + ->where('router_id', $router['id']) + ->where_gte('timestamp', $cutoffTime) + ->order_by_desc('timestamp') + ->find_one(); + + $data[] = [ + 'router' => [ + 'id' => $router['id'], + 'name' => $router['name'], + 'ip_address' => $router['ip_address'], + 'status' => $router['status'], + 'last_check' => $router['last_check'], + 'last_error' => $router['last_error'] + ], + 'latest_data' => $latestData ? [ + 'timestamp' => $latestData['timestamp'], + 'ping_status' => (bool)$latestData['ping_status'], + 'api_status' => (bool)$latestData['api_status'], + 'uptime' => $latestData['uptime'], + 'free_memory' => (int)$latestData['free_memory'], + 'total_memory' => (int)$latestData['total_memory'], + 'cpu_load' => (int)$latestData['cpu_load'], + 'temperature' => $latestData['temperature'], + 'voltage' => $latestData['voltage'], + 'error' => $latestData['error'] + ] : null + ]; + } + + return $data; +} + +/** + * Get historical data for router + */ +function getRouterHistoryData($routerId, $days = 7) { + $cutoffTime = date('Y-m-d H:i:s', strtotime("-$days days")); + + $data = ORM::for_table('tbl_router_monitoring') + ->where('router_id', $routerId) + ->where_gte('timestamp', $cutoffTime) + ->order_by_asc('timestamp') + ->find_many(); + + $result = []; + foreach ($data as $record) { + $result[] = [ + 'timestamp' => $record['timestamp'], + 'ping_status' => (bool)$record['ping_status'], + 'api_status' => (bool)$record['api_status'], + 'cpu_load' => (int)$record['cpu_load'], + 'memory_usage' => $record['total_memory'] ? + round((($record['total_memory'] - $record['free_memory']) / $record['total_memory']) * 100, 1) : 0, + 'temperature' => $record['temperature'], + 'voltage' => $record['voltage'] + ]; + } + + return $result; +} + +/** + * Get recent alerts + */ +function getRecentAlerts($hours = 24) { + $cutoffTime = date('Y-m-d H:i:s', strtotime("-$hours hours")); + + $alerts = ORM::for_table('tbl_router_monitoring') + ->where_gte('timestamp', $cutoffTime) + ->where_not_null('error') + ->order_by_desc('timestamp') + ->find_many(); + + $result = []; + foreach ($alerts as $alert) { + $router = ORM::for_table('tbl_routers')->find_one($alert['router_id']); + $result[] = [ + 'timestamp' => $alert['timestamp'], + 'router_name' => $router ? $router['name'] : 'Unknown', + 'error' => $alert['error'], + 'cpu_load' => $alert['cpu_load'], + 'temperature' => $alert['temperature'] + ]; + } + + return $result; +} +?> diff --git a/system/controllers/onlinehotspot.php b/system/controllers/onlinehotspot.php new file mode 100644 index 0000000..4b4fd04 --- /dev/null +++ b/system/controllers/onlinehotspot.php @@ -0,0 +1,130 @@ +assign('_title', Lang::T('online')); +$ui->assign('_system_menu', 'onlineusers'); +$ui->assign('onlineusers', $online); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +use PEAR2\Net\RouterOS; + +function handle_action($action) +{ + switch ($action) { + case 'get_hotspot_online_users': + mikrotik_get_hotspot_online_users(); + break; + case 'disconnect_online_user': + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + mikrotik_disconnect_online_user($_POST['router'], $_POST['username'], $_POST['userType']); + } + break; + case 'list': + // Assuming you have a function to fetch the data for the online hotspot users list + $onlineHotspotUsers = fetch_online_hotspot_users(); + $ui->assign('onlineHotspotUsers', $onlineHotspotUsers); + $ui->display('onlinehotspot.tpl'); + break; + default: + // Handle default case, maybe return an error or redirect + break; + } +} + +function mikrotik_get_hotspot_online_users() +{ + global $routes; + $router = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + $hotspotList = []; + + foreach ($hotspotActive as $hotspot) { + $username = $hotspot->getProperty('user'); + $address = $hotspot->getProperty('address'); + $uptime = $hotspot->getProperty('uptime'); + $server = $hotspot->getProperty('server'); + $mac = $hotspot->getProperty('mac-address'); + $sessionTime = $hotspot->getProperty('session-time-left'); + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + $hotspotList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'server' => $server, + 'mac' => $mac, + 'session_time' => $sessionTime, + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($txBytes + $rxBytes), + ]; + } + + // Return the Hotspot online user list as JSON + header('Content-Type: application/json'); + echo json_encode($hotspotList); +} + +function mikrotik_disconnect_online_user($router, $username, $userType) +{ + // Check if the form was submitted + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Retrieve the form data + $router = $_POST['router']; + $username = $_POST['username']; + $userType = $_POST['userType']; + + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + if (!$mikrotik) { + // Handle the error response or redirection + return; + } + + try { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($userType == 'hotspot') { + Mikrotik::removeHotspotActiveUser($client, $username); + // Handle the success response or redirection + } elseif ($userType == 'pppoe') { + Mikrotik::removePpoeActive($client, $username); + // Handle the success response or redirection + } else { + // Handle the error response or redirection + return; + } + } catch (Exception $e) { + // Handle the error response or redirection + } finally { + // Disconnect from the MikroTik router + if (isset($client)) { + $client->disconnect(); + } + } + } +} + +// Helper function to format bytes +function mikrotik_formatBytes($bytes) +{ + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $factor = floor((strlen($bytes) - 1) / 3); + return sprintf("%.2f %s", $bytes / pow(1024, $factor), @$units[$factor]); +} + +// Call the main function with the action provided in the URL +$action = $routes['1'] ?? ''; // Assuming $routes is defined elsewhere +handle_action($action); + +$ui->assign('onlineusers', $online); + +$ui->display('onlinehotspot.tpl'); +?> diff --git a/system/controllers/onlineusers.php b/system/controllers/onlineusers.php new file mode 100644 index 0000000..95bba44 --- /dev/null +++ b/system/controllers/onlineusers.php @@ -0,0 +1,347 @@ +assign('_title', Lang::T('Online Users')); +$ui->assign('_system_menu', 'onlineusers'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +use PEAR2\Net\RouterOS; + +require_once 'system/autoload/PEAR2/Autoload.php'; + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} + +// Handle cases for hotspot users and PPP users +switch ($action) { + case 'hotspot': + $ui->display('hotspot_users.tpl'); + break; +case 'hotspot_users': + $hotspotUsers = mikrotik_get_hotspot_online_users(); + + // Filter out entries where all values are null + $filteredHotspotUsers = array_filter($hotspotUsers, function($user) { + // Check if all specified fields are null + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['server']) && + is_null($user['mac']) && + is_null($user['session_time']) && + $user['rx_bytes'] === '0 B' && + $user['tx_bytes'] === '0 B' && + $user['total'] === '0 B' + ); + }); + + header('Content-Type: application/json'); + echo json_encode($filteredHotspotUsers); + exit; + break; + +case 'pppoe': + $ui->display('ppp_users.tpl'); + break; + +case 'ppp_users': + $pppUsers = mikrotik_get_ppp_online_users(); + header('Content-Type: application/json'); + echo json_encode($pppUsers); + exit; + break; + + case 'disconnect': + $routerId = $routes['2']; + $username = $routes['3']; + $userType = $routes['4']; + mikrotik_disconnect_online_user($routerId, $username, $userType); + // Redirect or handle the response as needed + break; + + case 'summary': + // Fetch summary of online users and total bytes used + $summary = mikrotik_get_online_users_summary(); + header('Content-Type: application/json'); + echo json_encode($summary); + exit; + break; + + default: + // Handle default case or invalid action + break; +} + +// Function to round the value and append the appropriate unit +function mikrotik_formatBytes($bytes, $precision = 2) +{ +$units = array('B', 'KB', 'MB', 'GB', 'TB'); + + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + + $bytes /= pow(1024, $pow); + + return round($bytes, $precision) . ' ' . $units[$pow]; +} + +function filter_null_users($users) { + return array_filter($users, function($user) { + return array_reduce($user, function($carry, $value) { + return $carry || $value !== null; + }, false); + }); +} + +function mikrotik_get_hotspot_online_users() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + + $hotspotList = []; + foreach ($hotspotActive as $hotspot) { + $username = $hotspot->getProperty('user'); + $address = $hotspot->getProperty('address'); + $uptime = $hotspot->getProperty('uptime'); + $server = $hotspot->getProperty('server'); + $mac = $hotspot->getProperty('mac-address'); + $sessionTime = $hotspot->getProperty('session-time-left'); + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + + $hotspotList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'server' => $server, + 'mac' => $mac, + 'session_time' => $sessionTime, + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($rxBytes + $txBytes), + ]; + } + + // Filter out users with all null properties + $filteredHotspotList = filter_null_users($hotspotList); + + // Return an empty array if no users are left after filtering + return empty($filteredHotspotList) ? [] : $filteredHotspotList; +} + + + +function mikrotik_get_ppp_online_users() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print')); + + $userList = []; + foreach ($pppUsers as $pppUser) { + $username = $pppUser->getProperty('name'); + $address = $pppUser->getProperty('address'); + $uptime = $pppUser->getProperty('uptime'); + $service = $pppUser->getProperty('service'); + $callerid = $pppUser->getProperty('caller-id'); + $bytes_in = $pppUser->getProperty('limit-bytes-in'); + $bytes_out = $pppUser->getProperty('limit-bytes-out'); + + $userList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'service' => $service, + 'caller_id' => $callerid, + 'bytes_in' => $bytes_in, + 'bytes_out' => $bytes_out, + ]; + } + + // Filter out users with all null properties + return filter_null_users($userList); +} + +function save_data_usage($username, $bytes_in, $bytes_out, $connection_type) { + if (!$username) { + error_log("Error: Missing username in save_data_usage()"); + return; + } + + $currentTime = date('Y-m-d H:i:s'); + $currentDate = date('Y-m-d'); + + // Check if there's an existing record for this user today + $existingRecord = ORM::for_table('tbl_user_data_usage') + ->where('username', $username) + ->where('connection_type', $connection_type) + ->where_raw('DATE(timestamp) = ?', [$currentDate]) + ->find_one(); + + if ($existingRecord) { + // Update existing record for today + $existingRecord->bytes_in = ($bytes_in ?: 0); + $existingRecord->bytes_out = ($bytes_out ?: 0); + $existingRecord->last_updated = $currentTime; + $existingRecord->save(); + } else { + // Create new record for today + $newRecord = ORM::for_table('tbl_user_data_usage')->create(); + $newRecord->username = $username; + $newRecord->bytes_in = ($bytes_in ?: 0); + $newRecord->bytes_out = ($bytes_out ?: 0); + $newRecord->connection_type = $connection_type; + $newRecord->timestamp = $currentTime; + $newRecord->last_updated = $currentTime; + $newRecord->save(); + } +} + +function mikrotik_get_online_users_summary() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + // Get Hotspot users + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + $hotspotList = []; + $totalHotspotUsage = 0; + foreach ($hotspotActive as $hotspot) { + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + $totalHotspotUsage += $rxBytes + $txBytes; + $username = $hotspot->getProperty('user'); + save_data_usage($username, $rxBytes, $txBytes, 'hotspot'); + + $hotspotList[] = [ + 'username' => $username, + 'address' => $hotspot->getProperty('address'), + 'uptime' => $hotspot->getProperty('uptime'), + 'server' => $hotspot->getProperty('server'), + 'mac' => $hotspot->getProperty('mac-address'), + 'session_time' => $hotspot->getProperty('session-time-left'), + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($rxBytes + $txBytes), + ]; + } + + // Filter out null hotspot users + $hotspotList = array_filter($hotspotList, function($user) { + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['server']) && + is_null($user['mac']) && + is_null($user['session_time']) && + $user['rx_bytes'] === '0 B' && + $user['tx_bytes'] === '0 B' && + $user['total'] === '0 B' + ); + }); + + // Get PPPoE users + $pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print')); + $pppoeList = []; + $totalPPPoEUsage = 0; + foreach ($pppUsers as $pppUser) { + $bytes_in = $pppUser->getProperty('limit-bytes-in'); + $bytes_out = $pppUser->getProperty('limit-bytes-out'); + $totalPPPoEUsage += $bytes_in + $bytes_out; + $username = $pppUser->getProperty('name'); + save_data_usage($username, $bytes_in, $bytes_out, 'pppoe'); + + $pppoeList[] = [ + 'username' => $username, + 'address' => $pppUser->getProperty('address'), + 'uptime' => $pppUser->getProperty('uptime'), + 'service' => $pppUser->getProperty('service'), + 'caller_id' => $pppUser->getProperty('caller-id'), + 'bytes_in' => mikrotik_formatBytes($bytes_in), + 'bytes_out' => mikrotik_formatBytes($bytes_out), + 'total' => mikrotik_formatBytes($bytes_in + $bytes_out), + ]; + } + + // Filter out null PPPoE users + $pppoeList = array_filter($pppoeList, function($user) { + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['service']) && + is_null($user['caller_id']) && + $user['bytes_in'] === '0 B' && + $user['bytes_out'] === '0 B' && + $user['total'] === '0 B' + ); + }); + // Calculate total data usage + $totalDataUsage = $totalHotspotUsage + $totalPPPoEUsage; + + // Calculate total users + $totalHotspotUsers = count($hotspotList); + $totalPPPoEUsers = count($pppoeList); + $totalUsers = $totalHotspotUsers + $totalPPPoEUsers; + + return [ + 'hotspot_users' => $totalHotspotUsers, + 'ppp_users' => $totalPPPoEUsers, + 'total_users' => $totalUsers, + 'total_bytes' => mikrotik_formatBytes($totalDataUsage), + ]; +} + +function mikrotik_disconnect_online_user($router, $username, $userType) +{ + // Check if the form was submitted + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Retrieve the form data + $router = $_POST['router']; + $username = $_POST['username']; + $userType = $_POST['userType']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + if (!$mikrotik) { + // Handle the error response or redirection + return; + } + try { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($userType == 'hotspot') { + Mikrotik::removeHotspotActiveUser($client, $username); + // Handle the success response or redirection + } elseif ($userType == 'pppoe') { + Mikrotik::removePpoeActive($client, $username); + // Handle the success response or redirection + } else { + // Handle the error response or redirection + return; + } + } catch (Exception $e) { + // Handle the error response or redirection + } finally { + // Disconnect from the MikroTik router + if (isset($client)) { + $client->disconnect(); + } + } + } +} + +?> diff --git a/system/controllers/onlineusers_backup.php b/system/controllers/onlineusers_backup.php new file mode 100644 index 0000000..95bba44 --- /dev/null +++ b/system/controllers/onlineusers_backup.php @@ -0,0 +1,347 @@ +assign('_title', Lang::T('Online Users')); +$ui->assign('_system_menu', 'onlineusers'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +use PEAR2\Net\RouterOS; + +require_once 'system/autoload/PEAR2/Autoload.php'; + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} + +// Handle cases for hotspot users and PPP users +switch ($action) { + case 'hotspot': + $ui->display('hotspot_users.tpl'); + break; +case 'hotspot_users': + $hotspotUsers = mikrotik_get_hotspot_online_users(); + + // Filter out entries where all values are null + $filteredHotspotUsers = array_filter($hotspotUsers, function($user) { + // Check if all specified fields are null + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['server']) && + is_null($user['mac']) && + is_null($user['session_time']) && + $user['rx_bytes'] === '0 B' && + $user['tx_bytes'] === '0 B' && + $user['total'] === '0 B' + ); + }); + + header('Content-Type: application/json'); + echo json_encode($filteredHotspotUsers); + exit; + break; + +case 'pppoe': + $ui->display('ppp_users.tpl'); + break; + +case 'ppp_users': + $pppUsers = mikrotik_get_ppp_online_users(); + header('Content-Type: application/json'); + echo json_encode($pppUsers); + exit; + break; + + case 'disconnect': + $routerId = $routes['2']; + $username = $routes['3']; + $userType = $routes['4']; + mikrotik_disconnect_online_user($routerId, $username, $userType); + // Redirect or handle the response as needed + break; + + case 'summary': + // Fetch summary of online users and total bytes used + $summary = mikrotik_get_online_users_summary(); + header('Content-Type: application/json'); + echo json_encode($summary); + exit; + break; + + default: + // Handle default case or invalid action + break; +} + +// Function to round the value and append the appropriate unit +function mikrotik_formatBytes($bytes, $precision = 2) +{ +$units = array('B', 'KB', 'MB', 'GB', 'TB'); + + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + + $bytes /= pow(1024, $pow); + + return round($bytes, $precision) . ' ' . $units[$pow]; +} + +function filter_null_users($users) { + return array_filter($users, function($user) { + return array_reduce($user, function($carry, $value) { + return $carry || $value !== null; + }, false); + }); +} + +function mikrotik_get_hotspot_online_users() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + + $hotspotList = []; + foreach ($hotspotActive as $hotspot) { + $username = $hotspot->getProperty('user'); + $address = $hotspot->getProperty('address'); + $uptime = $hotspot->getProperty('uptime'); + $server = $hotspot->getProperty('server'); + $mac = $hotspot->getProperty('mac-address'); + $sessionTime = $hotspot->getProperty('session-time-left'); + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + + $hotspotList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'server' => $server, + 'mac' => $mac, + 'session_time' => $sessionTime, + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($rxBytes + $txBytes), + ]; + } + + // Filter out users with all null properties + $filteredHotspotList = filter_null_users($hotspotList); + + // Return an empty array if no users are left after filtering + return empty($filteredHotspotList) ? [] : $filteredHotspotList; +} + + + +function mikrotik_get_ppp_online_users() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print')); + + $userList = []; + foreach ($pppUsers as $pppUser) { + $username = $pppUser->getProperty('name'); + $address = $pppUser->getProperty('address'); + $uptime = $pppUser->getProperty('uptime'); + $service = $pppUser->getProperty('service'); + $callerid = $pppUser->getProperty('caller-id'); + $bytes_in = $pppUser->getProperty('limit-bytes-in'); + $bytes_out = $pppUser->getProperty('limit-bytes-out'); + + $userList[] = [ + 'username' => $username, + 'address' => $address, + 'uptime' => $uptime, + 'service' => $service, + 'caller_id' => $callerid, + 'bytes_in' => $bytes_in, + 'bytes_out' => $bytes_out, + ]; + } + + // Filter out users with all null properties + return filter_null_users($userList); +} + +function save_data_usage($username, $bytes_in, $bytes_out, $connection_type) { + if (!$username) { + error_log("Error: Missing username in save_data_usage()"); + return; + } + + $currentTime = date('Y-m-d H:i:s'); + $currentDate = date('Y-m-d'); + + // Check if there's an existing record for this user today + $existingRecord = ORM::for_table('tbl_user_data_usage') + ->where('username', $username) + ->where('connection_type', $connection_type) + ->where_raw('DATE(timestamp) = ?', [$currentDate]) + ->find_one(); + + if ($existingRecord) { + // Update existing record for today + $existingRecord->bytes_in = ($bytes_in ?: 0); + $existingRecord->bytes_out = ($bytes_out ?: 0); + $existingRecord->last_updated = $currentTime; + $existingRecord->save(); + } else { + // Create new record for today + $newRecord = ORM::for_table('tbl_user_data_usage')->create(); + $newRecord->username = $username; + $newRecord->bytes_in = ($bytes_in ?: 0); + $newRecord->bytes_out = ($bytes_out ?: 0); + $newRecord->connection_type = $connection_type; + $newRecord->timestamp = $currentTime; + $newRecord->last_updated = $currentTime; + $newRecord->save(); + } +} + +function mikrotik_get_online_users_summary() +{ + global $routes; + $routerId = $routes['2']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + // Get Hotspot users + $hotspotActive = $client->sendSync(new RouterOS\Request('/ip/hotspot/active/print')); + $hotspotList = []; + $totalHotspotUsage = 0; + foreach ($hotspotActive as $hotspot) { + $rxBytes = $hotspot->getProperty('bytes-in'); + $txBytes = $hotspot->getProperty('bytes-out'); + $totalHotspotUsage += $rxBytes + $txBytes; + $username = $hotspot->getProperty('user'); + save_data_usage($username, $rxBytes, $txBytes, 'hotspot'); + + $hotspotList[] = [ + 'username' => $username, + 'address' => $hotspot->getProperty('address'), + 'uptime' => $hotspot->getProperty('uptime'), + 'server' => $hotspot->getProperty('server'), + 'mac' => $hotspot->getProperty('mac-address'), + 'session_time' => $hotspot->getProperty('session-time-left'), + 'rx_bytes' => mikrotik_formatBytes($rxBytes), + 'tx_bytes' => mikrotik_formatBytes($txBytes), + 'total' => mikrotik_formatBytes($rxBytes + $txBytes), + ]; + } + + // Filter out null hotspot users + $hotspotList = array_filter($hotspotList, function($user) { + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['server']) && + is_null($user['mac']) && + is_null($user['session_time']) && + $user['rx_bytes'] === '0 B' && + $user['tx_bytes'] === '0 B' && + $user['total'] === '0 B' + ); + }); + + // Get PPPoE users + $pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print')); + $pppoeList = []; + $totalPPPoEUsage = 0; + foreach ($pppUsers as $pppUser) { + $bytes_in = $pppUser->getProperty('limit-bytes-in'); + $bytes_out = $pppUser->getProperty('limit-bytes-out'); + $totalPPPoEUsage += $bytes_in + $bytes_out; + $username = $pppUser->getProperty('name'); + save_data_usage($username, $bytes_in, $bytes_out, 'pppoe'); + + $pppoeList[] = [ + 'username' => $username, + 'address' => $pppUser->getProperty('address'), + 'uptime' => $pppUser->getProperty('uptime'), + 'service' => $pppUser->getProperty('service'), + 'caller_id' => $pppUser->getProperty('caller-id'), + 'bytes_in' => mikrotik_formatBytes($bytes_in), + 'bytes_out' => mikrotik_formatBytes($bytes_out), + 'total' => mikrotik_formatBytes($bytes_in + $bytes_out), + ]; + } + + // Filter out null PPPoE users + $pppoeList = array_filter($pppoeList, function($user) { + return !( + is_null($user['username']) && + is_null($user['address']) && + is_null($user['uptime']) && + is_null($user['service']) && + is_null($user['caller_id']) && + $user['bytes_in'] === '0 B' && + $user['bytes_out'] === '0 B' && + $user['total'] === '0 B' + ); + }); + // Calculate total data usage + $totalDataUsage = $totalHotspotUsage + $totalPPPoEUsage; + + // Calculate total users + $totalHotspotUsers = count($hotspotList); + $totalPPPoEUsers = count($pppoeList); + $totalUsers = $totalHotspotUsers + $totalPPPoEUsers; + + return [ + 'hotspot_users' => $totalHotspotUsers, + 'ppp_users' => $totalPPPoEUsers, + 'total_users' => $totalUsers, + 'total_bytes' => mikrotik_formatBytes($totalDataUsage), + ]; +} + +function mikrotik_disconnect_online_user($router, $username, $userType) +{ + // Check if the form was submitted + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // Retrieve the form data + $router = $_POST['router']; + $username = $_POST['username']; + $userType = $_POST['userType']; + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router); + if (!$mikrotik) { + // Handle the error response or redirection + return; + } + try { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + if ($userType == 'hotspot') { + Mikrotik::removeHotspotActiveUser($client, $username); + // Handle the success response or redirection + } elseif ($userType == 'pppoe') { + Mikrotik::removePpoeActive($client, $username); + // Handle the success response or redirection + } else { + // Handle the error response or redirection + return; + } + } catch (Exception $e) { + // Handle the error response or redirection + } finally { + // Disconnect from the MikroTik router + if (isset($client)) { + $client->disconnect(); + } + } + } +} + +?> diff --git a/system/controllers/order.php b/system/controllers/order.php new file mode 100644 index 0000000..bc07be1 --- /dev/null +++ b/system/controllers/order.php @@ -0,0 +1,493 @@ +assign('_user', $user); + +switch ($action) { + case 'voucher': + $ui->assign('_system_menu', 'voucher'); + $ui->assign('_title', Lang::T('Order Voucher')); + run_hook('customer_view_order'); #HOOK + $ui->display('user-order.tpl'); + break; + case 'history': + $ui->assign('_system_menu', 'history'); + $query = ORM::for_table('tbl_payment_gateway')->where('username', $user['username'])->order_by_desc('id'); + $d = Paginator::findMany($query); + $ui->assign('d', $d); + $ui->assign('_title', Lang::T('Order History')); + run_hook('customer_view_order_history'); #HOOK + $ui->display('user-orderHistory.tpl'); + break; + case 'balance': + if (strpos($user['email'], '@') === false) { + r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address")); + } + $ui->assign('_title', 'Top Up'); + $ui->assign('_system_menu', 'balance'); + $plans_balance = ORM::for_table('tbl_plans')->where('enabled', '1')->where('type', 'Balance')->where('prepaid', 'yes')->find_many(); + $ui->assign('plans_balance', $plans_balance); + $ui->display('user-orderBalance.tpl'); + break; + case 'package': + if (strpos($user['email'], '@') === false) { + r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address")); + } + $ui->assign('_title', 'Order Plan'); + $ui->assign('_system_menu', 'package'); + $account_type = $user['account_type']; + if (empty($account_type)) { + $account_type = 'Personal'; + } + if (!empty($_SESSION['nux-router'])) { + if ($_SESSION['nux-router'] == 'radius') { + $radius_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $radius_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + } else { + $routers = ORM::for_table('tbl_routers')->where('id', $_SESSION['nux-router'])->find_many(); + $rs = []; + foreach ($routers as $r) { + $rs[] = $r['name']; + } + $plans_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where_in('routers', $rs)->where('is_radius', 0)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $plans_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where_in('routers', $rs)->where('is_radius', 0)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + } + } else { + $radius_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $radius_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 1)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + + $routers = ORM::for_table('tbl_routers')->find_many(); + $plans_pppoe = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 0)->where('type', 'PPPOE')->where('prepaid', 'yes')->find_many(); + $plans_hotspot = ORM::for_table('tbl_plans')->where('plan_type', $account_type)->where('enabled', '1')->where('is_radius', 0)->where('type', 'Hotspot')->where('prepaid', 'yes')->find_many(); + } + $ui->assign('routers', $routers); + $ui->assign('radius_pppoe', $radius_pppoe); + $ui->assign('radius_hotspot', $radius_hotspot); + $ui->assign('plans_pppoe', $plans_pppoe); + $ui->assign('plans_hotspot', $plans_hotspot); + run_hook('customer_view_order_plan'); #HOOK + $ui->display('user-orderPlan.tpl'); + break; + case 'unpaid': + $d = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->where('status', 1) + ->find_one(); + run_hook('custome + r_find_unpaid'); #HOOK + if ($d) { + if (empty($d['pg_url_payment'])) { + r2(U . "order/buy/" . $trx['routers_id'] . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment")); + } else { + r2(U . "order/view/" . $d['id'] . '/check/', 's', Lang::T("You have unpaid transaction")); + } + } else { + r2(U . "order/package/", 's', Lang::T("You have no unpaid transaction")); + } + break; + case 'view': + $trxid = $routes['2']; + $trx = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->find_one($trxid); + run_hook('customer_view_payment'); #HOOK + // jika tidak ditemukan, berarti punya orang lain + if (empty($trx)) { + r2(U . "order/package", 'w', Lang::T("Payment not found")); + } + // jika url kosong, balikin ke buy, kecuali cancel + if (empty($trx['pg_url_payment']) && $routes['3'] != 'cancel') { + r2(U . "order/buy/" . (($trx['routers_id'] == 0) ? $trx['routers'] : $trx['routers_id']) . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment")); + } + if ($routes['3'] == 'check') { + if (!file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $trx['gateway'] . '.php')) { + r2(U . 'order/view/' . $trxid, 'e', Lang::T("No Payment Gateway Available")); + } + run_hook('customer_check_payment_status'); #HOOK + include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $trx['gateway'] . '.php'; + call_user_func($trx['gateway'] . '_validate_config'); + call_user_func($trx['gateway'] . '_get_status', $trx, $user); + } else if ($routes['3'] == 'cancel') { + run_hook('customer_cancel_payment'); #HOOK + $trx->pg_paid_response = '{}'; + $trx->status = 4; + $trx->paid_date = date('Y-m-d H:i:s'); + $trx->save(); + $trx = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->find_one($trxid); + } + if (empty($trx)) { + r2(U . "order/package", 'e', Lang::T("Transaction Not found")); + } + + $router = Mikrotik::info($trx['routers']); + $plan = ORM::for_table('tbl_plans')->find_one($trx['plan_id']); + $bandw = ORM::for_table('tbl_bandwidth')->find_one($plan['id_bw']); + $invoice = ORM::for_table('tbl_transactions')->where("invoice", $trx['trx_invoice'])->find_one(); + $ui->assign('invoice', $invoice); + $ui->assign('trx', $trx); + $ui->assign('router', $router); + $ui->assign('plan', $plan); + $ui->assign('bandw', $bandw); + $ui->assign('_title', 'TRX #' . $trxid); + $ui->display('user-orderView.tpl'); + break; + case 'pay': + if ($config['enable_balance'] != 'yes') { + r2(U . "order/package", 'e', Lang::T("Balance not enabled")); + } + if (!empty(App::getTokenValue($_GET['stoken']))) { + r2(U . "voucher/invoice/"); + die(); + } + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']); + if (empty($plan)) { + r2(U . "order/package", 'e', Lang::T("Plan Not found")); + } + if (!$plan['enabled']) { + r2(U . "home", 'e', 'Plan is not exists'); + } + if ($routes['2'] == 'radius') { + $router_name = 'radius'; + } else { + $router_name = $plan['routers']; + } + + list($bills, $add_cost) = User::getBills($id_customer); + + // Tax calculation start + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + + if ($tax_enable === 'yes') { + $tax = Package::tax($plan['price'], $tax_rate); + } else { + $tax = 0; + } + // Tax calculation stop + + if ($plan && $plan['enabled'] && $user['balance'] >= $plan['price'] + $tax) { + if (Package::rechargeUser($user['id'], $router_name, $plan['id'], 'Customer', 'Balance')) { + // if success, then get the balance + Balance::min($user['id'], $plan['price'] + $add_cost + $tax); + App::setToken($_GET['stoken'], "success"); + r2(U . "voucher/invoice/", 's', Lang::T("Success to buy package")); + } else { + r2(U . "order/package", 'e', Lang::T("Failed to buy package")); + Message::sendTelegram("Buy Package with Balance Failed\n\n#u$c[username] #buy \n" . $plan['name_plan'] . + "\nRouter: " . $router_name . + "\nPrice: " . $plan['price'] + $tax); + } + } else { + r2(U . "home", 'e', 'Plan is not exists'); + } + break; + + case 'send': + if ($config['enable_balance'] != 'yes') { + r2(U . "order/package", 'e', Lang::T("Balance not enabled")); + } + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + $ui->assign('_title', Lang::T('Buy for friend')); + $ui->assign('_system_menu', 'package'); + $plan = ORM::for_table('tbl_plans')->find_one($routes['3']); + if (empty($plan)) { + r2(U . "order/package", 'e', Lang::T("Plan Not found")); + } + if (!$plan['enabled']) { + r2(U . "home", 'e', 'Plan is not exists'); + } + if ($routes['2'] == 'radius') { + $router_name = 'radius'; + } else { + $router_name = $plan['routers']; + } + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + + if ($tax_enable === 'yes') { + $tax = Package::tax($plan['price'], $tax_rate); + $ui->assign('tax', $tax); + } else { + $tax = 0; + } + + // Add tax to plan price + $plan['price'] += $tax; + + if (isset($_POST['send']) && $_POST['send'] == 'plan') { + $target = ORM::for_table('tbl_customers')->where('username', _post('username'))->find_one(); + list($bills, $add_cost) = User::getBills($target['id']); + if (!empty($add_cost)) { + $ui->assign('bills', $bills); + $ui->assign('add_cost', $add_cost); + $plan['price'] += $add_cost; + } + + if (!$target) { + r2(U . 'home', 'd', Lang::T('Username not found')); + } + if ($user['balance'] < $plan['price']) { + r2(U . 'home', 'd', Lang::T('insufficient balance')); + } + if ($user['username'] == $target['username']) { + r2(U . "order/pay/$routes[2]/$routes[3]", 's', '^_^ v'); + } + $active = ORM::for_table('tbl_user_recharges') + ->where('username', _post('username')) + ->where('status', 'on') + ->find_one(); + + if ($active && $active['plan_id'] != $plan['id']) { + r2(U . "order/package", 'e', Lang::T("Target has active plan, different with current plant.") . " [ $active[namebp] ]"); + } + $result = Package::rechargeUser($target['id'], $router_name, $plan['id'], $user['username'], 'Balance'); + if (!empty($result)) { + // if success, then get the balance + Balance::min($user['id'], $plan['price']); + //sender + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $user['username']; + $d->gateway = $target['username']; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $routes['2']; + $d->routers = $router_name; + $d->price = $plan['price']; + $d->payment_method = "Balance"; + $d->payment_channel = "Send Plan"; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->trx_invoice = $result; + $d->status = 2; + $d->save(); + $trx_id = $d->id(); + //receiver + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $target['username']; + $d->gateway = $user['username']; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $routes['2']; + $d->routers = $router_name; + $d->price = $plan['price']; + $d->payment_method = "Balance"; + $d->payment_channel = "Received Plan"; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = 'balance'; + $d->trx_invoice = $result; + $d->status = 2; + $d->save(); + r2(U . "order/view/$trx_id", 's', Lang::T("Success to send package")); + } else { + $errorMessage = "Send Package with Balance Failed\n\n#u$user[username] #send \n" . $plan['name_plan'] . + "\nRouter: " . $router_name . + "\nPrice: " . $plan['price']; + + if ($tax_enable === 'yes') { + $errorMessage .= "\nTax: " . $tax; + } + + r2(U . "order/package", 'e', Lang::T("Failed to Send package")); + Message::sendTelegram($errorMessage); + } + } + $ui->assign('username', $_GET['u']); + $ui->assign('router', $router_name); + $ui->assign('plan', $plan); + $ui->assign('tax', $tax); + $ui->display('user-sendPlan.tpl'); + break; + case 'gateway': + $ui->assign('_title', Lang::T('Select Payment Gateway')); + $ui->assign('_system_menu', 'package'); + if (strpos($user['email'], '@') === false) { + r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address")); + } + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + $plan = ORM::for_table('tbl_plans')->find_one($routes['3']); + $tax = Package::tax($plan['price'], $tax_rate); + $pgs = array_values(explode(',', $config['payment_gateway'])); + if (count($pgs) == 0) { + sendTelegram("Payment Gateway not set, please set it in Settings"); + _log(Lang::T("Payment Gateway not set, please set it in Settings")); + r2(U . "home", 'e', Lang::T("Failed to create Transaction..")); + } + if (count($pgs) > 1) { + $ui->assign('pgs', $pgs); + if ($tax_enable === 'yes') { + $ui->assign('tax', $tax); + } + $ui->assign('route2', $routes[2]); + $ui->assign('route3', $routes[3]); + $ui->assign('plan', $plan); + $ui->display('user-selectGateway.tpl'); + break; + } else { + if (empty($pgs[0])) { + sendTelegram("Payment Gateway not set, please set it in Settings"); + _log(Lang::T("Payment Gateway not set, please set it in Settings")); + r2(U . "home", 'e', Lang::T("Failed to create Transaction..")); + } else { + $_POST['gateway'] = $pgs[0]; + } + } + case 'buy': + $gateway = _post('gateway'); + if (empty($gateway) && !empty($_SESSION['gateway'])) { + $gateway = $_SESSION['gateway']; + } else if (!empty($gateway)) { + $_SESSION['gateway'] = $gateway; + } + if ($user['status'] != 'Active') { + _alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', ""); + } + if (empty($gateway)) { + r2(U . 'order/gateway/' . $routes[2] . '/' . $routes[3], 'w', Lang::T("Please select Payment Gateway")); + } + run_hook('customer_buy_plan'); #HOOK + include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $gateway . '.php'; + call_user_func($gateway . '_validate_config'); + + if ($routes['2'] == 'radius') { + $router['id'] = 0; + $router['name'] = 'radius'; + } else if ($routes['2'] > 0) { + $router = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routes['2']); + } else { + $router['id'] = 0; + $router['name'] = 'balance'; + } + $plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']); + if (empty($router) || empty($plan)) { + r2(U . "order/package", 'e', Lang::T("Plan Not found")); + } + $d = ORM::for_table('tbl_payment_gateway') + ->where('username', $user['username']) + ->where('status', 1) + ->find_one(); + if ($d) { + if ($d['pg_url_payment']) { + r2(U . "order/view/" . $d['id'], 'w', Lang::T("You already have unpaid transaction, cancel it or pay it.")); + } else { + if ($gateway == $d['gateway']) { + $id = $d['id']; + } else { + $d->status = 4; + $d->save(); + } + } + } + $add_cost = 0; + $tax = 0; + if ($router['name'] != 'balance') { + list($bills, $add_cost) = User::getBills($id_customer); + } + // Tax calculation start + $tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no'; + $tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null; + $custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null; + if ($tax_rate_setting === 'custom') { + $tax_rate = $custom_tax_rate; + } else { + $tax_rate = $tax_rate_setting; + } + if ($tax_enable === 'yes') { + $tax = Package::tax($plan['price'], $tax_rate); + } + // Tax calculation stop + if (empty($id)) { + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $user['username']; + $d->gateway = $gateway; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $router['id']; + $d->routers = $router['name']; + if ($plan['validity_unit'] == 'Period') { + // Postpaid price from field + $add_inv = User::getAttribute("Invoice", $id_customer); + if (empty($add_inv) or $add_inv == 0) { + $d->price = ($plan['price'] + $add_cost + $tax); + } else { + $d->price = ($add_inv + $add_cost + $tax); + } + } else { + $d->price = ($plan['price'] + $add_cost + $tax); + } + //$d->price = ($plan['price'] + $add_cost); + $d->created_date = date('Y-m-d H:i:s'); + $d->status = 1; + $d->save(); + $id = $d->id(); + } else { + $d->username = $user['username']; + $d->gateway = $gateway; + $d->plan_id = $plan['id']; + $d->plan_name = $plan['name_plan']; + $d->routers_id = $router['id']; + $d->routers = $router['name']; + if ($plan['validity_unit'] == 'Period') { + // Postpaid price from field + $add_inv = User::getAttribute("Invoice", $id_customer); + if (empty($add_inv) or $add_inv == 0) { + $d->price = ($plan['price'] + $add_cost + $tax); + } else { + $d->price = ($add_inv + $add_cost + $tax); + } + } else { + $d->price = ($plan['price'] + $add_cost + $tax); + } + //$d->price = ($plan['price'] + $add_cost); + $d->created_date = date('Y-m-d H:i:s'); + $d->status = 1; + $d->save(); + } + if (!$id) { + r2(U . "order/package/" . $d['id'], 'e', Lang::T("Failed to create Transaction..")); + } else { + call_user_func($gateway . '_create_transaction', $d, $user); + } + break; + default: + r2(U . "order/package/", 's', ''); +} diff --git a/system/controllers/page.php b/system/controllers/page.php new file mode 100644 index 0000000..d1bd9ad --- /dev/null +++ b/system/controllers/page.php @@ -0,0 +1,21 @@ +assign('_title', Lang::T('Order Voucher')); +$ui->assign('_system_menu', 'order'); + +$action = $routes['1']; +$user = User::_info(); +$ui->assign('_user', $user); + +if(file_exists(__DIR__."/../../pages/".str_replace(".","",$action).".html")){ + $ui->assign("PageFile",$action); + $ui->assign("pageHeader",$action); + run_hook('customer_view_page'); #HOOK + $ui->display('user-pages.tpl'); +}else + $ui->display('404.tpl'); \ No newline at end of file diff --git a/system/controllers/pages.php b/system/controllers/pages.php new file mode 100644 index 0000000..1d60dfa --- /dev/null +++ b/system/controllers/pages.php @@ -0,0 +1,71 @@ +assign('_title', 'Pages'); +$ui->assign('_system_menu', 'pages'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +if(strpos($action,"-reset")!==false){ + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'),'danger', "dashboard"); + } + $action = str_replace("-reset","",$action); + $path = "pages/".str_replace(".","",$action).".html"; + $temp = "pages_template/".str_replace(".","",$action).".html"; + if(file_exists($temp)){ + if(!copy($temp, $path)){ + file_put_contents($path, Http::getData('https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/pages_template/'.$action.'.html')); + } + }else{ + file_put_contents($path, Http::getData('https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/pages_template/'.$action.'.html')); + } + r2(U . 'pages/'.$action); +}else if(strpos($action,"-post")===false){ + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'),'danger', "dashboard"); + } + $path = "pages/".str_replace(".","",$action).".html"; + //echo $path; + run_hook('view_edit_pages'); #HOOK + if(!file_exists($path)){ + $temp = "pages_template/".str_replace(".","",$action).".html"; + if(file_exists($temp)){ + if(!copy($temp, $path)){ + touch($path); + } + }else{ + touch($path); + } + } + if(file_exists($path)){ + $html = file_get_contents($path); + $ui->assign("htmls",str_replace([""],"",$html)); + $ui->assign("writeable",is_writable($path)); + $ui->assign("pageHeader",str_replace('_', ' ', $action)); + $ui->assign("PageFile",$action); + $ui->display('page-edit.tpl'); + }else + $ui->display('a404.tpl'); +}else{ + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'),'danger', "dashboard"); + } + $action = str_replace("-post","",$action); + $path = "pages/".str_replace(".","",$action).".html"; + if(file_exists($path)){ + $html = _post("html"); + run_hook('save_pages'); #HOOK + if(file_put_contents($path, str_replace([""],"",$html))){ + r2(U . 'pages/'.$action, 's', Lang::T("Saving page success")); + }else{ + r2(U . 'pages/'.$action, 'e', Lang::T("Failed to save page, make sure i can write to folder pages, chmod 664 pages/*.html")); + } + }else + $ui->display('a404.tpl'); +} \ No newline at end of file diff --git a/system/controllers/paymentgateway.php b/system/controllers/paymentgateway.php new file mode 100644 index 0000000..7621b11 --- /dev/null +++ b/system/controllers/paymentgateway.php @@ -0,0 +1,83 @@ +assign('_system_menu', 'paymentgateway'); + +$action = alphanumeric($routes[1]); +$ui->assign('_admin', $admin); + +if ($action == 'delete') { + $pg = alphanumeric($routes[2]); + if (file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $pg . '.php')) { + deleteFile($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR, $pg); + } + r2(U . 'paymentgateway', 's', Lang::T('Payment Gateway Deleted')); +} + +if (_post('save') == 'actives') { + $pgs = ''; + if(is_array($_POST['pgs'])){ + $pgs = implode(',', $_POST['pgs']); + } + $d = ORM::for_table('tbl_appconfig')->where('setting', 'payment_gateway')->find_one(); + if ($d) { + $d->value = $pgs; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'payment_gateway'; + $d->value = $pgs; + $d->save(); + } + r2(U . 'paymentgateway', 's', Lang::T('Payment Gateway saved successfully')); +} + +if (file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $action . '.php')) { + include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $action . '.php'; + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (function_exists($action . '_save_config')) { + call_user_func($action . '_save_config'); + } else { + $ui->display('a404.tpl'); + } + } else { + if (function_exists($action . '_show_config')) { + call_user_func($action . '_show_config'); + } else { + $ui->display('a404.tpl'); + } + } +} else { + if (!empty($action)) { + r2(U . 'paymentgateway', 'w', Lang::T('Payment Gateway Not Found')); + } else { + $files = scandir($PAYMENTGATEWAY_PATH); + foreach ($files as $file) { + if (pathinfo($file, PATHINFO_EXTENSION) == 'php') { + $pgs[] = str_replace('.php', '', $file); + } + } + $ui->assign('_title', 'Payment Gateway Settings'); + $ui->assign('pgs', $pgs); + $ui->assign('actives', explode(',', $config['payment_gateway'])); + $ui->display('paymentgateway.tpl'); + } +} + + +function deleteFile($path, $name) +{ + $files = scandir($path); + foreach ($files as $file) { + if (is_file($path . $file) && strpos($file, $name) !== false) { + unlink($path . $file); + } else if (is_dir($path . $file) && !in_array($file, ['.', '..'])) { + deleteFile($path . $file . DIRECTORY_SEPARATOR, $name); + } + } +} diff --git a/system/controllers/plan.php b/system/controllers/plan.php new file mode 100644 index 0000000..0577b2a --- /dev/null +++ b/system/controllers/plan.php @@ -0,0 +1,1042 @@ +assign('_title', Lang::T('Recharge Account')); +$ui->assign('_system_menu', 'plan'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + + +switch ($action) { + case 'sync': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + set_time_limit(-1); + $plans = ORM::for_table('tbl_user_recharges')->where('status', 'on')->find_many(); + $log = ''; + $router = ''; + foreach ($plans as $plan) { + if ($router != $plan['routers'] && $plan['routers'] != 'radius') { + $mikrotik = Mikrotik::info($plan['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $router = $plan['routers']; + } + $p = ORM::for_table('tbl_plans')->findOne($plan['plan_id']); + $c = ORM::for_table('tbl_customers')->findOne($plan['customer_id']); + if ($plan['routers'] == 'radius') { + Radius::customerAddPlan($c, $p, $plan['expiration'] . ' ' . $plan['time']); + } else { + if ($plan['type'] == 'Hotspot') { + Mikrotik::addHotspotUser($client, $p, $c); + } else if ($plan['type'] == 'PPPOE') { + Mikrotik::addPpoeUser($client, $p, $c); + } + } + $log .= "DONE : $plan[username], $plan[namebp], $plan[type], $plan[routers]
"; + } + r2(U . 'plan/list', 's', $log); + case 'recharge': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + if (isset($routes['2']) && !empty($routes['2'])) { + $ui->assign('cust', ORM::for_table('tbl_customers')->find_one($routes['2'])); + } + $usings = explode(',', $config['payment_usings']); + $usings = array_filter(array_unique($usings)); + if(count($usings)==0){ + $usings[] = Lang::T('Cash'); + } + $ui->assign('usings', $usings); + run_hook('view_recharge'); #HOOK + $ui->display('recharge.tpl'); + break; + + case 'recharge-confirm': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id_customer = _post('id_customer'); + $server = _post('server'); + $planId = _post('plan'); + $using = _post('using'); + + $msg = ''; + if ($id_customer == '' or $server == '' or $planId == '' or $using == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + if ($msg == '') { + $gateway = 'Recharge'; + $channel = $admin['fullname']; + $cust = User::_info($id_customer); + $plan = ORM::for_table('tbl_plans')->find_one($planId); + list($bills, $add_cost) = User::getBills($id_customer); + if ($using == 'balance' && $config['enable_balance'] == 'yes') { + if (!$cust) { + r2(U . 'plan/recharge', 'e', Lang::T('Customer not found')); + } + if (!$plan) { + r2(U . 'plan/recharge', 'e', Lang::T('Plan not found')); + } + if ($cust['balance'] < ($plan['price'] + $add_cost)) { + r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance')); + } + $gateway = 'Recharge Balance'; + } + if ($using == 'zero') { + $zero = 1; + $gateway = 'Recharge Zero'; + } + $usings = explode(',', $config['payment_usings']); + $usings = array_filter(array_unique($usings)); + if(count($usings)==0){ + $usings[] = Lang::T('Cash'); + } + $ui->assign('usings', $usings); + $ui->assign('bills', $bills); + $ui->assign('add_cost', $add_cost); + $ui->assign('cust', $cust); + $ui->assign('gateway', $gateway); + $ui->assign('channel', $channel); + $ui->assign('server', $server); + $ui->assign('using', $using); + $ui->assign('plan', $plan); + $ui->display('recharge-confirm.tpl'); + } else { + r2(U . 'plan/recharge', 'e', $msg); + } + break; + + case 'recharge-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"); + } + $id_customer = _post('id_customer'); + $server = _post('server'); + $planId = _post('plan'); + $using = _post('using'); + $stoken = _post('stoken'); + + if (!empty(App::getTokenValue($stoken))) { + $username = App::getTokenValue($stoken); + $in = ORM::for_table('tbl_transactions')->where('username', $username)->order_by_desc('id')->find_one(); + Package::createInvoice($in); + $ui->display('invoice.tpl'); + die(); + } + + $msg = ''; + if ($id_customer == '' or $server == '' or $planId == '' or $using == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + if ($msg == '') { + $gateway = ucwords($using); + $channel = $admin['fullname']; + $cust = User::_info($id_customer); + list($bills, $add_cost) = User::getBills($id_customer); + if ($using == 'balance' && $config['enable_balance'] == 'yes') { + $plan = ORM::for_table('tbl_plans')->find_one($planId); + if (!$cust) { + r2(U . 'plan/recharge', 'e', Lang::T('Customer not found')); + } + if (!$plan) { + r2(U . 'plan/recharge', 'e', Lang::T('Plan not found')); + } + if ($cust['balance'] < ($plan['price'] + $add_cost)) { + r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance')); + } + $gateway = 'Recharge Balance'; + } + if ($using == 'zero') { + $add_cost = 0; + $zero = 1; + $gateway = 'Recharge Zero'; + } + if (Package::rechargeUser($id_customer, $server, $planId, $gateway, $channel)) { + if ($using == 'balance') { + Balance::min($cust['id'], $plan['price'] + $add_cost); + } + $in = ORM::for_table('tbl_transactions')->where('username', $cust['username'])->order_by_desc('id')->find_one(); + Package::createInvoice($in); + App::setToken($stoken, $cust['username']); + $ui->display('invoice.tpl'); + _log('[' . $admin['username'] . ']: ' . 'Recharge ' . $cust['username'] . ' [' . $in['plan_name'] . '][' . Lang::moneyFormat($in['price']) . ']', $admin['user_type'], $admin['id']); + } else { + r2(U . 'plan/recharge', 'e', "Failed to recharge account"); + } + } else { + r2(U . 'plan/recharge', 'e', $msg); + } + break; + + case 'view': + $id = $routes['2']; + $in = ORM::for_table('tbl_transactions')->where('id', $id)->find_one(); + $ui->assign('in', $in); + if (!empty($routes['3']) && $routes['3'] == 'send') { + $c = ORM::for_table('tbl_customers')->where('username', $in['username'])->find_one(); + if ($c) { + Message::sendInvoice($c, $in); + r2(U . 'plan/view/' . $id, 's', "Success send to customer"); + } + r2(U . 'plan/view/' . $id, 'd', "Customer not found"); + } + Package::createInvoice($in); + $ui->assign('_title', 'View Invoice'); + $ui->display('invoice.tpl'); + break; + + + case 'print': + $content = $_POST['content']; + if (!empty($content)) { + if ($_POST['nux'] == 'print') { + //header("Location: nux://print?text=".urlencode($content)); + $ui->assign('nuxprint', "nux://print?text=" . urlencode($content)); + } + $ui->assign('content', $content); + } else { + $id = _post('id'); + $d = ORM::for_table('tbl_transactions')->where('id', $id)->find_one(); + $ui->assign('in', $d); + $ui->assign('date', Lang::dateAndTimeFormat($d['recharged_on'], $d['recharged_time'])); + } + + run_hook('print_invoice'); #HOOK + $ui->display('invoice-print.tpl'); + break; + + case 'edit': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + $d = ORM::for_table('tbl_user_recharges')->find_one($id); + if ($d) { + $ui->assign('d', $d); + if (in_array($admin['user_type'], array('SuperAdmin', 'Admin'))) { + $p = ORM::for_table('tbl_plans')->where_not_equal('type', 'Balance')->find_many(); + } else { + $p = ORM::for_table('tbl_plans')->where('enabled', '1')->where_not_equal('type', 'Balance')->find_many(); + } + $ui->assign('p', $p); + run_hook('view_edit_customer_plan'); #HOOK + $ui->assign('_title', 'Edit Plan'); + $ui->display('plan-edit.tpl'); + } else { + r2(U . 'plan/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + $d = ORM::for_table('tbl_user_recharges')->find_one($id); + if ($d) { + run_hook('delete_customer_active_plan'); #HOOK + $p = ORM::for_table('tbl_plans')->find_one($d['plan_id']); + if ($p['is_radius']) { + Radius::customerDeactivate($d['username']); + } else { + $mikrotik = Mikrotik::info($d['routers']); + if ($d['type'] == 'Hotspot') { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removeHotspotUser($client, $d['username']); + Mikrotik::removeHotspotActiveUser($client, $d['username']); + } else { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removePpoeUser($client, $d['username']); + Mikrotik::removePpoeActive($client, $d['username']); + } + } + $d->delete(); + _log('[' . $admin['username'] . ']: ' . 'Delete Plan for Customer ' . $c['username'] . ' [' . $in['plan_name'] . '][' . Lang::moneyFormat($in['price']) . ']', $admin['user_type'], $admin['id']); + r2(U . 'plan/list', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'edit-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $username = _post('username'); + $id_plan = _post('id_plan'); + $recharged_on = _post('recharged_on'); + $expiration = _post('expiration'); + $time = _post('time'); + + $id = _post('id'); + $d = ORM::for_table('tbl_user_recharges')->find_one($id); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + $p = ORM::for_table('tbl_plans')->where('id', $id_plan)->where('enabled', '1')->find_one(); + if ($d) { + } else { + $msg .= ' Plan Not Found
'; + } + if ($msg == '') { + run_hook('edit_customer_plan'); #HOOK + $d->username = $username; + $d->plan_id = $id_plan; + $d->namebp = $p['name_plan']; + //$d->recharged_on = $recharged_on; + $d->expiration = $expiration; + $d->time = $time; + if ($d['status'] == 'off') { + if (strtotime($expiration . ' ' . $time) > time()) { + $d->status = 'on'; + } + } + if ($p['is_radius']) { + $d->routers = 'radius'; + } else { + $d->routers = $p['routers']; + } + $d->save(); + if ($d['status'] == 'on') { + Package::changeTo($username, $id_plan, $id); + } + _log('[' . $admin['username'] . ']: ' . 'Edit Plan for Customer ' . $d['username'] . ' to [' . $d['namebp'] . '][' . Lang::moneyFormat($p['price']) . ']', $admin['user_type'], $admin['id']); + r2(U . 'plan/list', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'plan/edit/' . $id, 'e', $msg); + } + break; + + case 'voucher': + $ui->assign('_title', Lang::T('Vouchers')); + $search = _req('search'); + if ($search != '') { + if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + $query = ORM::for_table('tbl_plans')->where('enabled', '1') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where_like('tbl_voucher.code', '%' . $search . '%'); + $d = Paginator::findMany($query, ["search" => $search]); + } else if ($admin['user_type'] == 'Agent') { + $sales = []; + $sls = ORM::for_table('tbl_users')->select('id')->where('root', $admin['id'])->findArray(); + foreach ($sls as $s) { + $sales[] = $s['id']; + } + $sales[] = $admin['id']; + $query = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where_in('generated_by', $sales) + ->where_like('tbl_voucher.code', '%' . $search . '%'); + $d = Paginator::findMany($query, ["search" => $search]); + } + } else { + if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + $query = ORM::for_table('tbl_plans')->where('enabled', '1') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')); + $d = Paginator::findMany($query); + } else if ($admin['user_type'] == 'Agent') { + $sales = []; + $sls = ORM::for_table('tbl_users')->select('id')->where('root', $admin['id'])->findArray(); + foreach ($sls as $s) { + $sales[] = $s['id']; + } + $sales[] = $admin['id']; + $query = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where_in('generated_by', $sales); + $d = Paginator::findMany($query); + } + } + // extract admin + $admins = []; + foreach ($d as $k) { + if (!empty($k['generated_by'])) { + $admins[] = $k['generated_by']; + } + } + if (count($admins) > 0) { + $adms = ORM::for_table('tbl_users')->where_in('id', $admins)->find_many(); + unset($admins); + foreach ($adms as $adm) { + $tipe = $adm['user_type']; + if ($tipe == 'Sales') { + $tipe = ' [S]'; + } else if ($tipe == 'Agent') { + $tipe = ' [A]'; + } else { + $tipe == ''; + } + $admins[$adm['id']] = $adm['fullname'] . $tipe; + } + } + $ui->assign('admins', $admins); + $ui->assign('d', $d); + $ui->assign('search', $search); + $ui->assign('page', $page); + run_hook('view_list_voucher'); #HOOK + $ui->display('voucher.tpl'); + break; + + case 'add-voucher': + 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('_title', Lang::T('Add Vouchers')); + $c = ORM::for_table('tbl_customers')->find_many(); + $ui->assign('c', $c); + $p = ORM::for_table('tbl_plans')->where('enabled', '1')->find_many(); + $ui->assign('p', $p); + $r = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many(); + $ui->assign('r', $r); + run_hook('view_add_voucher'); #HOOK + $ui->display('voucher-add.tpl'); + break; + + case 'remove-voucher': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $d = ORM::for_table('tbl_voucher')->where_equal('status', '1')->findMany(); + if ($d) { + $jml = 0; + foreach ($d as $v) { + if (!ORM::for_table('tbl_user_recharges')->where_equal("method", 'Voucher - ' . $v['code'])->findOne()) { + $v->delete(); + $jml++; + } + } + r2(U . 'plan/voucher', 's', "$jml " . Lang::T('Data Deleted Successfully')); + } + case 'print-voucher': + $from_id = _post('from_id'); + $planid = _post('planid'); + $pagebreak = _post('pagebreak'); + $limit = _post('limit'); + $vpl = _post('vpl'); + if (empty($vpl)) { + $vpl = 3; + } + if ($pagebreak < 1) $pagebreak = 12; + + if ($limit < 1) $limit = $pagebreak * 2; + if (empty($from_id)) { + $from_id = 0; + } + + if ($from_id > 0 && $planid > 0) { + $v = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->where('tbl_plans.id', $planid) + ->where_gt('tbl_voucher.id', $from_id) + ->limit($limit); + $vc = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->where('tbl_plans.id', $planid) + ->where_gt('tbl_voucher.id', $from_id); + } else if ($from_id == 0 && $planid > 0) { + $v = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->where('tbl_plans.id', $planid) + ->limit($limit); + $vc = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->where('tbl_plans.id', $planid); + } else if ($from_id > 0 && $planid == 0) { + $v = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->where_gt('tbl_voucher.id', $from_id) + ->limit($limit); + $vc = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->where_gt('tbl_voucher.id', $from_id); + } else { + $v = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0') + ->limit($limit); + $vc = ORM::for_table('tbl_plans') + ->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan')) + ->where('tbl_voucher.status', '0'); + } + if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + $v = $v->find_many(); + $vc = $vc->count(); + } else { + $sales = []; + $sls = ORM::for_table('tbl_users')->select('id')->where('root', $admin['id'])->findArray(); + foreach ($sls as $s) { + $sales[] = $s['id']; + } + $sales[] = $admin['id']; + $v = $v->where_in('generated_by', $sales)->find_many(); + $vc = $vc->where_in('generated_by', $sales)->count(); + } + $template = file_get_contents("pages/Voucher.html"); + $template = str_replace('[[company_name]]', $config['CompanyName'], $template); + + $ui->assign('_title', Lang::T('Hotspot Voucher')); + $ui->assign('from_id', $from_id); + $ui->assign('vpl', $vpl); + $ui->assign('pagebreak', $pagebreak); + + $plans = ORM::for_table('tbl_plans')->find_many(); + $ui->assign('plans', $plans); + $ui->assign('limit', $limit); + $ui->assign('planid', $planid); + + $voucher = []; + $n = 1; + foreach ($v as $vs) { + $temp = $template; + $temp = str_replace('[[qrcode]]', '', $temp); + $temp = str_replace('[[price]]', Lang::moneyFormat($vs['price']), $temp); + $temp = str_replace('[[voucher_code]]', $vs['code'], $temp); + $temp = str_replace('[[plan]]', $vs['name_plan'], $temp); + $temp = str_replace('[[counter]]', $n, $temp); + $voucher[] = $temp; + $n++; + } + + $ui->assign('voucher', $voucher); + $ui->assign('vc', $vc); + + //for counting pagebreak + $ui->assign('jml', 0); + run_hook('view_print_voucher'); #HOOK + $ui->display('print-voucher.tpl'); + break; + case 'voucher-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"); + } + $type = _post('type'); + $plan = _post('plan'); + $voucher_format = _post('voucher_format'); + $prefix = _post('prefix'); + $server = _post('server'); + $numbervoucher = _post('numbervoucher'); + $lengthcode = _post('lengthcode'); + + $msg = ''; + if ($type == '' or $plan == '' or $server == '' or $numbervoucher == '' or $lengthcode == '') { + $msg .= Lang::T('All field is required') . '
'; + } + if (Validator::UnsignedNumber($numbervoucher) == false) { + $msg .= 'The Number of Vouchers must be a number' . '
'; + } + if (Validator::UnsignedNumber($lengthcode) == false) { + $msg .= 'The Length Code must be a number' . '
'; + } + if ($msg == '') { + if (!empty($prefix)) { + $d = ORM::for_table('tbl_appconfig')->where('setting', 'voucher_prefix')->find_one(); + if ($d) { + $d->value = $prefix; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'voucher_prefix'; + $d->value = $prefix; + $d->save(); + } + } + run_hook('create_voucher'); #HOOK + $vouchers = []; + if($voucher_format == 'numbers'){ + if (strlen($lengthcode)<6) { + $msg .= 'The Length Code must be a more than 6 for numbers' . '
'; + } + $vouchers = generateUniqueNumericVouchers($numbervoucher, $lengthcode); + } + else { + for ($i = 0; $i < $numbervoucher; $i++) { + $code = strtoupper(substr(md5(time() . rand(10000, 99999)), 0, $lengthcode)); + if ($voucher_format == 'low') { + $code = strtolower($code); + } else if ($voucher_format == 'rand') { + $code = Lang::randomUpLowCase($code); + } + $vouchers[] = $code; + + } + } + + foreach($vouchers as $code){ + $d = ORM::for_table('tbl_voucher')->create(); + $d->type = $type; + $d->routers = $server; + $d->id_plan = $plan; + $d->code = $prefix . $code; + $d->user = '0'; + $d->status = '0'; + $d->generated_by = $admin['id']; + $d->save(); + } + if ($numbervoucher == 1) { + r2(U . 'plan/voucher-view/' . $d->id(), 's', Lang::T('Create Vouchers Successfully')); + } + + r2(U . 'plan/voucher', 's', Lang::T('Create Vouchers Successfully')); + } else { + r2(U . 'plan/add-voucher/' . $id, 'e', $msg); + } + break; + + case 'voucher-view': + $id = $routes[2]; + if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + $voucher = ORM::for_table('tbl_voucher')->find_one($id); + } else { + $sales = []; + $sls = ORM::for_table('tbl_users')->select('id')->where('root', $admin['id'])->findArray(); + foreach ($sls as $s) { + $sales[] = $s['id']; + } + $sales[] = $admin['id']; + $voucher = ORM::for_table('tbl_voucher') + ->find_one($id); + if (!in_array($voucher['generated_by'], $sales)) { + r2(U . 'plan/voucher/', 'e', Lang::T('Voucher Not Found')); + } + } + if (!$voucher) { + r2(U . 'plan/voucher/', 'e', Lang::T('Voucher Not Found')); + } + $plan = ORM::for_table('tbl_plans')->find_one($voucher['id_plan']); + if ($voucher && $plan) { + $content = Lang::pad($config['CompanyName'], ' ', 2) . "\n"; + $content .= Lang::pad($config['address'], ' ', 2) . "\n"; + $content .= Lang::pad($config['phone'], ' ', 2) . "\n"; + $content .= Lang::pad("", '=') . "\n"; + $content .= Lang::pads('ID', $voucher['id'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Code'), $voucher['code'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Plan Name'), $plan['name_plan'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Type'), $voucher['type'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Plan Price'), Lang::moneyFormat($plan['price']), ' ') . "\n"; + $content .= Lang::pads(Lang::T('Sales'), $admin['fullname'] . ' #' . $admin['id'], ' ') . "\n"; + $content .= Lang::pad("", '=') . "\n"; + $content .= Lang::pad($config['note'], ' ', 2) . "\n"; + $ui->assign('print', $content); + $config['printer_cols'] = 30; + $content = Lang::pad($config['CompanyName'], ' ', 2) . "\n"; + $content .= Lang::pad($config['address'], ' ', 2) . "\n"; + $content .= Lang::pad($config['phone'], ' ', 2) . "\n"; + $content .= Lang::pad("", '=') . "\n"; + $content .= Lang::pads('ID', $voucher['id'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Code'), $voucher['code'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Plan Name'), $plan['name_plan'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Type'), $voucher['type'], ' ') . "\n"; + $content .= Lang::pads(Lang::T('Plan Price'), Lang::moneyFormat($plan['price']), ' ') . "\n"; + $content .= Lang::pads(Lang::T('Sales'), $admin['fullname'] . ' #' . $admin['id'], ' ') . "\n"; + $content .= Lang::pad("", '=') . "\n"; + $content .= Lang::pad($config['note'], ' ', 2) . "\n"; + $ui->assign('_title', Lang::T('View')); + $ui->assign('whatsapp', urlencode("```$content```")); + $ui->display('voucher-view.tpl'); + } else { + r2(U . 'plan/voucher/', 'e', Lang::T('Voucher Not Found')); + } + break; + case 'voucher-delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + run_hook('delete_voucher'); #HOOK + $d = ORM::for_table('tbl_voucher')->find_one($id); + if ($d) { + $d->delete(); + r2(U . 'plan/voucher', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'refill': + 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('_title', Lang::T('Refill Account')); + run_hook('view_refill'); #HOOK + $ui->display('refill.tpl'); + + break; + + case 'refill-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"); + } + $code = _post('code'); + $user = ORM::for_table('tbl_customers')->where('id', _post('id_customer'))->find_one(); + $v1 = ORM::for_table('tbl_voucher')->where('code', $code)->where('status', 0)->find_one(); + + run_hook('refill_customer'); #HOOK + if ($v1) { + if (Package::rechargeUser($user['id'], $v1['routers'], $v1['id_plan'], "Voucher", $code)) { + $v1->status = "1"; + $v1->user = $user['username']; + $v1->save(); + $in = ORM::for_table('tbl_transactions')->where('username', $user['username'])->order_by_desc('id')->find_one(); + Package::createInvoice($in); + $ui->display('invoice.tpl'); + } else { + r2(U . 'plan/refill', 'e', "Failed to refill account"); + } + } else { + r2(U . 'plan/refill', 'e', Lang::T('Voucher Not Valid')); + } + break; + case 'deposit': + 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('_title', Lang::T('Refill Balance')); + if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + $ui->assign('p', ORM::for_table('tbl_plans')->where('type', 'Balance')->find_many()); + } else { + $ui->assign('p', ORM::for_table('tbl_plans')->where('enabled', '1')->where('type', 'Balance')->find_many()); + } + run_hook('view_deposit'); #HOOK + $ui->display('deposit.tpl'); + break; + case 'deposit-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"); + } + $user = _post('id_customer'); + $plan = _post('id_plan'); + $stoken = _req('stoken'); + if (App::getTokenValue($stoken)) { + $c = ORM::for_table('tbl_customers')->where('id', $user)->find_one(); + $in = ORM::for_table('tbl_transactions')->where('username', $c['username'])->order_by_desc('id')->find_one(); + Package::createInvoice($in); + $ui->display('invoice.tpl'); + die(); + } + + run_hook('deposit_customer'); #HOOK + if (!empty($user) && !empty($plan)) { + if (Package::rechargeUser($user, 'balance', $plan, "Deposit", $admin['fullname'])) { + $c = ORM::for_table('tbl_customers')->where('id', $user)->find_one(); + $in = ORM::for_table('tbl_transactions')->where('username', $c['username'])->order_by_desc('id')->find_one(); + Package::createInvoice($in); + if(!empty($stoken)){ + App::setToken($stoken, $in['id']); + } + $ui->display('invoice.tpl'); + } else { + r2(U . 'plan/refill', 'e', "Failed to refill account"); + } + } else { + r2(U . 'plan/refill', 'e', "All field is required"); + } + break; + case 'extend': + $id = $routes[2]; + $days = $routes[3]; + $stoken = $_GET['stoken']; + if (App::getTokenValue($stoken)) { + r2(U . 'plan', 's', "Extend already done"); + } + $tur = ORM::for_table('tbl_user_recharges')->find_one($id); + $status = $tur['status']; + if ($status == 'off') { + if (strtotime($tur['expiration'] . ' ' . $tur['time']) > time()) { + // not expired + $expiration = date('Y-m-d', strtotime($tur['expiration'] . " +$days day")); + } else { + //expired + $expiration = date('Y-m-d', strtotime(" +$days day")); + } + $tur->expiration = $expiration; + $tur->status = "on"; + $tur->save(); + App::setToken($stoken, $id); + if ($tur['routers'] != 'radius') { + $mikrotik = Mikrotik::info($tur['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $router = $tur['routers']; + } + $p = ORM::for_table('tbl_plans')->findOne($tur['plan_id']); + $c = ORM::for_table('tbl_customers')->findOne($tur['customer_id']); + if ($tur['routers'] == 'radius') { + Radius::customerAddPlan($c, $p, $tur['expiration'] . ' ' . $tur['time']); + } else { + if ($tur['type'] == 'Hotspot') { + Mikrotik::removeHotspotUser($client, $c['username']); + Mikrotik::removeHotspotActiveUser($client, $c['username']); + Mikrotik::addHotspotUser($client, $p, $c); + } else if ($tur['type'] == 'PPPOE') { + Mikrotik::removePpoeUser($client, $c['username']); + Mikrotik::removePpoeActive($client, $c['username']); + Mikrotik::addPpoeUser($client, $p, $c); + } + } + _log("$admin[fullname] extend Customer $tur[customer_id] $tur[username] for $days days", $admin['user_type'], $admin['id']); + r2(U . 'plan', 's', "Extend until $expiration"); + }else{ + r2(U . 'plan', 's', "Customer is not expired yet"); + } + break; + case 'suspend': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = $routes['2']; + $tur = ORM::for_table('tbl_user_recharges')->find_one($id); + if ($tur) { + // Check if customer is already inactive or suspended + $current_time = time(); + $expiry_timestamp = strtotime($tur['expiration'] . ' ' . $tur['time']); + $is_expired = ($current_time >= $expiry_timestamp); + $is_inactive = ($tur['status'] == 'off' || $is_expired); + $is_suspended = ($tur['status'] == 'suspended' && $is_expired); // Only consider truly suspended if expired + + if ($is_inactive) { + r2(U . 'plan/list', 'e', "Cannot suspend inactive customer: $tur[username]"); + } elseif ($is_suspended) { + r2(U . 'plan/list', 'e', "Customer $tur[username] is already suspended"); + } else { + $c = ORM::for_table('tbl_customers')->find_one($tur['customer_id']); + $p = ORM::for_table('tbl_plans')->find_one($tur['plan_id']); + + // Set suspension expiry to 10 seconds from now + $suspension_expiry = date('Y-m-d H:i:s', strtotime('+10 seconds')); + $expiry_date = date('Y-m-d', strtotime($suspension_expiry)); + $expiry_time = date('H:i:s', strtotime($suspension_expiry)); + + // Update the user recharge record with suspension expiry + $tur->expiration = $expiry_date; + $tur->time = $expiry_time; + $tur->status = 'suspended'; // New status for suspended users + $tur->save(); + + // Deactivate user on Mikrotik/Router (like manual edit) + if ($p['is_radius']) { + // Handle Radius users + if (empty($p['pool_expired'])) { + Radius::customerDeactivate($c['username']); + } else { + Radius::upsertCustomerAttr($c['username'], 'Framed-Pool', $p['pool_expired'], ':='); + Radius::disconnectCustomer($c['username']); + } + } else { + // Handle Mikrotik users + $mikrotik = Mikrotik::info($tur['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + if ($tur['type'] == 'Hotspot') { + if (!empty($p['pool_expired'])) { + Mikrotik::setHotspotUserPlan($client, $c['username'], 'EXPIRED NUXBILL ' . $p['pool_expired']); + } else { + Mikrotik::removeHotspotUser($client, $c['username']); + } + Mikrotik::removeHotspotActiveUser($client, $c['username']); + } else if ($tur['type'] == 'PPPOE') { + if (!empty($p['pool_expired'])) { + Mikrotik::setPpoeUserPlan($client, $c['username'], 'EXPIRED NUXBILL ' . $p['pool_expired']); + } else { + Mikrotik::removePpoeUser($client, $c['username']); + } + Mikrotik::removePpoeActive($client, $c['username']); + } + } + + // Log the suspension + _log("$admin[fullname] suspended Customer $tur[customer_id] $tur[username] until $expiry_date $expiry_time", $admin['user_type'], $admin['id']); + + r2(U . 'plan/list', 's', "Customer $tur[username] will be suspended at $expiry_date $expiry_time"); + } + } else { + r2(U . 'plan/list', 'e', "Customer not found"); + } + break; + case 'suspend-confirm': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id = _post('id'); + $suspension_date = _post('suspension_date'); + $suspension_time = _post('suspension_time'); + + $tur = ORM::for_table('tbl_user_recharges')->find_one($id); + if ($tur) { + // Check if customer is already inactive or suspended + $current_time = time(); + $expiry_timestamp = strtotime($tur['expiration'] . ' ' . $tur['time']); + $is_expired = ($current_time >= $expiry_timestamp); + $is_inactive = ($tur['status'] == 'off' || $is_expired); + $is_suspended = ($tur['status'] == 'suspended' && $is_expired); // Only consider truly suspended if expired + + if ($is_inactive) { + r2(U . 'plan/list', 'e', "Cannot suspend inactive customer: $tur[username]"); + } elseif ($is_suspended) { + r2(U . 'plan/list', 'e', "Customer $tur[username] is already suspended"); + } else { + $c = ORM::for_table('tbl_customers')->find_one($tur['customer_id']); + $p = ORM::for_table('tbl_plans')->find_one($tur['plan_id']); + + // Update the user recharge record with custom suspension expiry + $tur->expiration = $suspension_date; + $tur->time = $suspension_time; + $tur->status = 'suspended'; // New status for suspended users + $tur->save(); + + // Deactivate user on Mikrotik/Router (like manual edit) + if ($p['is_radius']) { + // Handle Radius users + if (empty($p['pool_expired'])) { + Radius::customerDeactivate($c['username']); + } else { + Radius::upsertCustomerAttr($c['username'], 'Framed-Pool', $p['pool_expired'], ':='); + Radius::disconnectCustomer($c['username']); + } + } else { + // Handle Mikrotik users + $mikrotik = Mikrotik::info($tur['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + if ($tur['type'] == 'Hotspot') { + if (!empty($p['pool_expired'])) { + Mikrotik::setHotspotUserPlan($client, $c['username'], 'EXPIRED NUXBILL ' . $p['pool_expired']); + } else { + Mikrotik::removeHotspotUser($client, $c['username']); + } + Mikrotik::removeHotspotActiveUser($client, $c['username']); + } else if ($tur['type'] == 'PPPOE') { + if (!empty($p['pool_expired'])) { + Mikrotik::setPpoeUserPlan($client, $c['username'], 'EXPIRED NUXBILL ' . $p['pool_expired']); + } else { + Mikrotik::removePpoeUser($client, $c['username']); + } + Mikrotik::removePpoeActive($client, $c['username']); + } + } + + // Log the suspension + _log("$admin[fullname] suspended Customer $tur[customer_id] $tur[username] until $suspension_date $suspension_time", $admin['user_type'], $admin['id']); + + r2(U . 'plan/list', 's', "Customer $tur[username] will be suspended at $suspension_date $suspension_time"); + } + } else { + r2(U . 'plan/list', 'e', "Customer not found"); + } + break; + case 'recharge-modal': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $id_customer = _post('id_customer'); + $server = _post('server'); + $planId = _post('plan'); + $using = _post('using'); + $channel = _post('channel'); + $zero = 0; + $msg = ''; + + if ($id_customer == '') { + $msg .= Lang::T('Please select customer') . '
'; + } + if ($server == '') { + $msg .= Lang::T('Please select router') . '
'; + } + if ($planId == '') { + $msg .= Lang::T('Please select plan') . '
'; + } + if ($using == '') { + $msg .= Lang::T('Please select payment method') . '
'; + } + + if ($msg == '') { + $cust = User::_info($id_customer); + $plan = ORM::for_table('tbl_plans')->find_one($planId); + list($bills, $add_cost) = User::getBills($id_customer); + if ($using == 'balance' && $config['enable_balance'] == 'yes') { + if (!$cust) { + r2(U . 'plan/list', 'e', Lang::T('Customer not found')); + } + if (!$plan) { + r2(U . 'plan/list', 'e', Lang::T('Plan not found')); + } + if ($cust['balance'] < ($plan['price'] + $add_cost)) { + r2(U . 'plan/list', 'e', Lang::T('insufficient balance')); + } + $gateway = 'Recharge Balance'; + } + if ($using == 'zero') { + $add_cost = 0; + $zero = 1; + $gateway = 'Recharge Zero'; + } + if (Package::rechargeUser($id_customer, $server, $planId, $gateway, $channel)) { + if ($using == 'balance') { + Balance::min($cust['id'], $plan['price'] + $add_cost); + } + $in = ORM::for_table('tbl_transactions')->where('username', $cust['username'])->order_by_desc('id')->find_one(); + Package::createInvoice($in); + App::setToken($stoken, $cust['username']); + _log('[' . $admin['username'] . ']: ' . 'Recharge ' . $cust['username'] . ' [' . $in['plan_name'] . '][' . Lang::moneyFormat($in['price']) . ']', $admin['user_type'], $admin['id']); + r2(U . 'plan/list', 's', "Customer $cust[username] recharged successfully with plan $in[plan_name]"); + } else { + r2(U . 'plan/list', 'e', "Failed to recharge account"); + } + } else { + r2(U . 'plan/list', 'e', $msg); + } + break; + default: + $ui->assign('xfooter', ''); + $ui->assign('_title', Lang::T('Customer')); + $search = _post('search'); + if ($search != '') { + $query = ORM::for_table('tbl_user_recharges') + ->whereRaw("username LIKE '%$search%' OR namebp LIKE '%$search%' OR method LIKE '%$search%' OR routers LIKE '%$search%' OR type LIKE '%$search%'") + ->order_by_desc('id'); + $d = Paginator::findMany($query, ['search' => $search]); + } else { + $query = ORM::for_table('tbl_user_recharges')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + // Process each record to determine current status + foreach ($d as $key => $record) { + $current_time = time(); + $expiry_timestamp = strtotime($record['expiration'] . ' ' . $record['time']); + $is_expired = ($current_time >= $expiry_timestamp); + + // Update status based on current time vs expiry + if ($record['status'] == 'suspended' && $is_expired) { + // Suspended user has reached suspension time, mark as off + $updateRecord = ORM::for_table('tbl_user_recharges')->find_one($record['id']); + if ($updateRecord) { + $updateRecord->status = 'off'; + $updateRecord->save(); + $d[$key]['status'] = 'off'; + } + } + + // Calculate status variables for template + $d[$key]['is_expired'] = $is_expired; + $d[$key]['is_suspended'] = ($record['status'] == 'suspended'); + $d[$key]['is_active'] = ($record['status'] == 'on' && !$is_expired); + $d[$key]['is_off'] = ($record['status'] == 'off' || $is_expired); + } + run_hook('view_list_billing'); #HOOK + $ui->assign('d', $d); + $ui->assign('search', $search); + $ui->display('plan.tpl'); + break; +} diff --git a/system/controllers/plugin.php b/system/controllers/plugin.php new file mode 100644 index 0000000..900ab4a --- /dev/null +++ b/system/controllers/plugin.php @@ -0,0 +1,11 @@ +assign('_title', 'Plugin Manager'); +$ui->assign('_system_menu', 'settings'); + +$plugin_repository = 'https://hotspotbilling.github.io/Plugin-Repository/repository.json'; + +$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"); +} + +$cache = $CACHE_PATH . File::pathFixer('/plugin_repository.json'); +if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) { + $txt = file_get_contents($cache); + $json = json_decode($txt, true); + if (empty($json['plugins']) && empty($json['payment_gateway'])) { + unlink($cache); + r2(U . 'dashboard', 'd', $txt); + } +} else { + $data = Http::getData($plugin_repository); + file_put_contents($cache, $data); + $json = json_decode($data, true); +} +switch ($action) { + case 'delete': + if (!is_writeable($CACHE_PATH)) { + r2(U . "pluginmanager", 'e', 'Folder cache/ is not writable'); + } + if (!is_writeable($PLUGIN_PATH)) { + r2(U . "pluginmanager", 'e', 'Folder plugin/ is not writable'); + } + set_time_limit(-1); + $tipe = $routes['2']; + $plugin = $routes['3']; + $file = $CACHE_PATH . DIRECTORY_SEPARATOR . $plugin . '.zip'; + if (file_exists($file)) unlink($file); + if ($tipe == 'plugin') { + foreach ($json['plugins'] as $plg) { + if ($plg['id'] == $plugin) { + $fp = fopen($file, 'w+'); + $ch = curl_init($plg['github'] . '/archive/refs/heads/master.zip'); + curl_setopt($ch, CURLOPT_POST, 0); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + curl_close($ch); + fclose($fp); + + $zip = new ZipArchive(); + $zip->open($file); + $zip->extractTo($CACHE_PATH); + $zip->close(); + $folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-main/'); + if (!file_exists($folder)) { + $folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/'); + } + if (!file_exists($folder)) { + r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown'); + } + scanAndRemovePath($folder, $PLUGIN_PATH . DIRECTORY_SEPARATOR); + File::deleteFolder($folder); + unlink($file); + r2(U . "pluginmanager", 's', 'Plugin ' . $plugin . ' has been deleted'); + break; + } + } + break; + } + break; + case 'install': + if (!is_writeable($CACHE_PATH)) { + r2(U . "pluginmanager", 'e', 'Folder cache/ is not writable'); + } + if (!is_writeable($PLUGIN_PATH)) { + r2(U . "pluginmanager", 'e', 'Folder plugin/ is not writable'); + } + set_time_limit(-1); + $tipe = $routes['2']; + $plugin = $routes['3']; + $file = $CACHE_PATH . DIRECTORY_SEPARATOR . $plugin . '.zip'; + if (file_exists($file)) unlink($file); + if ($tipe == 'plugin') { + foreach ($json['plugins'] as $plg) { + if ($plg['id'] == $plugin) { + $fp = fopen($file, 'w+'); + $ch = curl_init($plg['github'] . '/archive/refs/heads/master.zip'); + curl_setopt($ch, CURLOPT_POST, 0); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + curl_close($ch); + fclose($fp); + + $zip = new ZipArchive(); + $zip->open($file); + $zip->extractTo($CACHE_PATH); + $zip->close(); + $folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-main/'); + if (!file_exists($folder)) { + $folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/'); + } + if (!file_exists($folder)) { + r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown'); + } + File::copyFolder($folder, $PLUGIN_PATH . DIRECTORY_SEPARATOR, ['README.md', 'LICENSE']); + File::deleteFolder($folder); + unlink($file); + r2(U . "pluginmanager", 's', 'Plugin ' . $plugin . ' has been installed'); + break; + } + } + break; + } else if ($tipe == 'payment') { + foreach ($json['payment_gateway'] as $plg) { + if ($plg['id'] == $plugin) { + $fp = fopen($file, 'w+'); + $ch = curl_init($plg['github'] . '/archive/refs/heads/master.zip'); + curl_setopt($ch, CURLOPT_POST, 0); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + curl_close($ch); + fclose($fp); + + $zip = new ZipArchive(); + $zip->open($file); + $zip->extractTo($CACHE_PATH); + $zip->close(); + $folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-main/'); + if (!file_exists($folder)) { + $folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/'); + } + if (!file_exists($folder)) { + r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown'); + } + File::copyFolder($folder, $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR, ['README.md', 'LICENSE']); + File::deleteFolder($folder); + unlink($file); + r2(U . "paymentgateway", 's', 'Payment Gateway ' . $plugin . ' has been installed'); + break; + } + } + break; + } + default: + if (class_exists('ZipArchive')) { + $zipExt = true; + } else { + $zipExt = false; + } + $ui->assign('zipExt', $zipExt); + $ui->assign('plugins', $json['plugins']); + $ui->assign('pgs', $json['payment_gateway']); + $ui->display('plugin-manager.tpl'); +} + + +function scanAndRemovePath($source, $target) +{ + $files = scandir($source); + foreach ($files as $file) { + if (is_file($source . $file)) { + if(file_exists($target.$file)){ + unlink($target . $file); + } + } else if (is_dir($source . $file) && !in_array($file, ['.', '..'])) { + scanAndRemovePath($source. $file. DIRECTORY_SEPARATOR, $target. $file. DIRECTORY_SEPARATOR); + if(file_exists($target.$file)){ + rmdir($target . $file); + } + } + } + if(file_exists($target)){ + rmdir($target); + } +} \ No newline at end of file diff --git a/system/controllers/pool.php b/system/controllers/pool.php new file mode 100644 index 0000000..980b089 --- /dev/null +++ b/system/controllers/pool.php @@ -0,0 +1,165 @@ +assign('_title', Lang::T('Network')); +$ui->assign('_system_menu', 'network'); + +$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': + $ui->assign('xfooter', ''); + + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_pool')->where_like('pool_name', '%' . $name . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_pool')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + run_hook('view_pool'); #HOOK + $ui->display('pool.tpl'); + break; + + case 'add': + $r = ORM::for_table('tbl_routers')->find_many(); + $ui->assign('r', $r); + run_hook('view_add_pool'); #HOOK + $ui->display('pool-add.tpl'); + break; + + case 'edit': + $id = $routes['2']; + $d = ORM::for_table('tbl_pool')->find_one($id); + if ($d) { + $ui->assign('d', $d); + run_hook('view_edit_pool'); #HOOK + $ui->display('pool-edit.tpl'); + } else { + r2(U . 'pool/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + $id = $routes['2']; + run_hook('delete_pool'); #HOOK + $d = ORM::for_table('tbl_pool')->find_one($id); + if ($d) { + if ($d['routers'] != 'radius') { + try { + $mikrotik = Mikrotik::info($d['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removePool($client, $d['pool_name']); + } catch (Exception $e) { + //ignore exception, it means router has already deleted + } catch(Throwable $e){ + //ignore exception, it means router has already deleted + } + } + $d->delete(); + + r2(U . 'pool/list', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'sync': + $pools = ORM::for_table('tbl_pool')->find_many(); + $log = ''; + foreach ($pools as $pool) { + if ($pool['routers'] != 'radius') { + $mikrotik = Mikrotik::info($pool['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::addPool($client, $pool['pool_name'], $pool['range_ip']); + $log .= 'DONE: ' . $pool['pool_name'] . ': ' . $pool['range_ip'] . '
'; + } + } + r2(U . 'pool/list', 's', $log); + break; + case 'add-post': + $name = _post('name'); + $ip_address = _post('ip_address'); + $routers = _post('routers'); + run_hook('add_pool'); #HOOK + $msg = ''; + if (Validator::Length($name, 30, 2) == false) { + $msg .= 'Name should be between 3 to 30 characters' . '
'; + } + if ($ip_address == '' or $routers == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $d = ORM::for_table('tbl_pool')->where('pool_name', $name)->find_one(); + if ($d) { + $msg .= Lang::T('Pool Name Already Exist') . '
'; + } + if ($msg == '') { + if ($routers != 'radius') { + $mikrotik = Mikrotik::info($routers); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::addPool($client, $name, $ip_address); + } + + $b = ORM::for_table('tbl_pool')->create(); + $b->pool_name = $name; + $b->range_ip = $ip_address; + $b->routers = $routers; + $b->save(); + + r2(U . 'pool/list', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'pool/add', 'e', $msg); + } + break; + + + case 'edit-post': + $ip_address = _post('ip_address'); + $routers = _post('routers'); + run_hook('edit_pool'); #HOOK + $msg = ''; + + if ($ip_address == '' or $routers == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $id = _post('id'); + $d = ORM::for_table('tbl_pool')->find_one($id); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + + if ($msg == '') { + if ($routers != 'radius') { + $mikrotik = Mikrotik::info($routers); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::setPool($client, $d['pool_name'], $ip_address); + } + + $d->range_ip = $ip_address; + $d->routers = $routers; + $d->save(); + + r2(U . 'pool/list', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'pool/edit/' . $id, 'e', $msg); + } + break; + + default: + r2(U . 'pool/list/', 's', ''); +} diff --git a/system/controllers/radius.php b/system/controllers/radius.php new file mode 100644 index 0000000..3e1357f --- /dev/null +++ b/system/controllers/radius.php @@ -0,0 +1,150 @@ +assign('_title', Lang::T('Plugin Manager')); +$ui->assign('_system_menu', 'settings'); + +$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 'nas-add': + $ui->assign('_system_menu', 'radius'); + $ui->assign('_title', "Network Access Server"); + $ui->assign('routers', ORM::for_table('tbl_routers')->find_many()); + $ui->display('radius-nas-add.tpl'); + break; + case 'nas-add-post': + $shortname = _post('shortname'); + $nasname = _post('nasname'); + $secret = _post('secret'); + $ports = _post('ports', null); + $type = _post('type', 'other'); + $server = _post('server', null); + $community = _post('community', null); + $description = _post('description'); + $routers = _post('routers'); + $msg = ''; + + if (Validator::Length($shortname, 30, 2) == false) { + $msg .= 'Name should be between 3 to 30 characters' . '
'; + } + if (empty($ports)) { + $ports = null; + } + if (empty($server)) { + $server = null; + } + if (empty($community)) { + $community = null; + } + if (empty($type)) { + $type = null; + } + $d = ORM::for_table('nas', 'radius')->where('nasname', $nasname)->find_one(); + if ($d) { + $msg .= 'NAS IP Exists
'; + } + if ($msg == '') { + $id = Radius::nasAdd($shortname, $nasname, $ports, $secret, $routers, $description, $type, $server, $community); + if ($id > 0) { + r2(U . 'radius/nas-list/', 's', "NAS Added"); + } else { + r2(U . 'radius/nas-add/', 'e', "NAS Added Failed"); + } + } else { + r2(U . 'radius/nas-add', 'e', $msg); + } + break; + case 'nas-edit': + $ui->assign('_system_menu', 'radius'); + $ui->assign('_title', "Network Access Server"); + + $id = $routes['2']; + $d = ORM::for_table('nas', 'radius')->find_one($id); + if (!$d) { + $d = ORM::for_table('nas', 'radius')->where_equal('shortname', _get('name'))->find_one(); + } + if ($d) { + $ui->assign('routers', ORM::for_table('tbl_routers')->find_many()); + $ui->assign('d', $d); + $ui->display('radius-nas-edit.tpl'); + } else { + r2(U . 'radius/list', 'e', Lang::T('Account Not Found')); + } + + break; + case 'nas-edit-post': + $id = $routes['2']; + $shortname = _post('shortname'); + $nasname = _post('nasname'); + $secret = _post('secret'); + $ports = _post('ports', null); + $type = _post('type', 'other'); + $server = _post('server', null); + $community = _post('community', null); + $description = _post('description'); + $routers = _post('routers'); + $msg = ''; + + if (Validator::Length($shortname, 30, 2) == false) { + $msg .= 'Name should be between 3 to 30 characters' . '
'; + } + if (empty($ports)) { + $ports = null; + } + if (empty($server)) { + $server = null; + } + if (empty($community)) { + $community = null; + } + if (empty($type)) { + $type = null; + } + if ($msg == '') { + if (Radius::nasUpdate($id, $shortname, $nasname, $ports, $secret, $routers, $description, $type, $server, $community)) { + r2(U . 'radius/list/', 's', "NAS Saved"); + } else { + r2(U . 'radius/nas-add', 'e', 'NAS NOT Exists'); + } + } else { + r2(U . 'radius/nas-add', 'e', $msg); + } + break; + case 'nas-delete': + $id = $routes['2']; + $d = ORM::for_table('nas', 'radius')->find_one($id); + if ($d) { + $d->delete(); + } else { + r2(U . 'radius/nas-list', 'e', 'NAS Not found'); + } + default: + $ui->assign('_system_menu', 'radius'); + $ui->assign('_title', "Network Access Server"); + $name = _post('name'); + if (empty($name)) { + $query = ORM::for_table('nas', 'radius'); + $nas = Paginator::findMany($query); + } else { + $query = ORM::for_table('nas', 'radius') + ->where_like('nasname', $search) + ->where_like('shortname', $search) + ->where_like('description', $search); + $nas = Paginator::findMany($query, ['name' => $name]); + } + $ui->assign('name', $name); + $ui->assign('nas', $nas); + $ui->display('radius-nas.tpl'); +} diff --git a/system/controllers/register.php b/system/controllers/register.php new file mode 100644 index 0000000..40792a8 --- /dev/null +++ b/system/controllers/register.php @@ -0,0 +1,169 @@ +'; + } + if (Validator::Length($fullname, 36, 2) == false) { + $msg .= 'Full Name should be between 3 to 25 characters' . '
'; + } + if (!Validator::Length($password, 35, 2)) { + $msg .= 'Password should be between 3 to 35 characters' . '
'; + } + if (!Validator::Email($email)) { + $msg .= 'Email is not Valid
'; + } + if ($password != $cpassword) { + $msg .= Lang::T('Passwords does not match') . '
'; + } + + if (!empty($config['sms_url'])) { + $otpPath .= sha1($username . $db_password) . ".txt"; + run_hook('validate_otp'); #HOOK + //expired 10 minutes + if (file_exists($otpPath) && time() - filemtime($otpPath) > 1200) { + unlink($otpPath); + r2(U . 'register', 's', 'Verification code expired'); + } else if (file_exists($otpPath)) { + $code = file_get_contents($otpPath); + if ($code != $otp_code) { + $ui->assign('username', $username); + $ui->assign('fullname', $fullname); + $ui->assign('address', $address); + $ui->assign('email', $email); + $ui->assign('phonenumber', $phonenumber); + $ui->assign('notify', 'Wrong Verification code'); + $ui->assign('notify_t', 'd'); + $ui->display('register-otp.tpl'); + exit(); + } else { + unlink($otpPath); + } + } else { + r2(U . 'register', 's', 'No Verification code'); + } + } + $d = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($d) { + $msg .= Lang::T('Account already axist') . '
'; + } + if ($msg == '') { + run_hook('register_user'); #HOOK + $d = ORM::for_table('tbl_customers')->create(); + $d->username = alphanumeric($username, "+_."); + $d->password = $password; + $d->fullname = $fullname; + $d->address = $address; + $d->email = $email; + $d->phonenumber = $phonenumber; + if ($d->save()) { + $user = $d->id(); + + // Send registration notification SMS/WhatsApp + if (!empty($phonenumber) && strlen($phonenumber) > 5) { + $customerData = [ + 'fullname' => $fullname, + 'username' => alphanumeric($username, "+_."), + 'password' => $password, + 'phonenumber' => $phonenumber, + 'service_type' => 'Hotspot' // Default for public registration + ]; + + Message::sendRegistrationNotification($customerData); + } + + r2(U . 'login', 's', Lang::T('Register Success! You can login now')); + } else { + $ui->assign('username', $username); + $ui->assign('fullname', $fullname); + $ui->assign('address', $address); + $ui->assign('email', $email); + $ui->assign('phonenumber', $phonenumber); + $ui->assign('notify', 'Failed to register'); + $ui->assign('notify_t', 'd'); + run_hook('view_otp_register'); #HOOK + $ui->display('register-rotp.tpl'); + } + } else { + $ui->assign('username', $username); + $ui->assign('fullname', $fullname); + $ui->assign('address', $address); + $ui->assign('email', $email); + $ui->assign('phonenumber', $phonenumber); + $ui->assign('notify', $msg); + $ui->assign('notify_t', 'd'); + $ui->display('register.tpl'); + } + break; + + default: + if (!empty($config['sms_url'])) { + $username = _post('username'); + if (!empty($username)) { + $d = ORM::for_table('tbl_customers')->where('username', $username)->find_one(); + if ($d) { + r2(U . 'register', 's', Lang::T('Account already axist')); + } + if (!file_exists($otpPath)) { + mkdir($otpPath); + touch($otpPath . 'index.html'); + } + $otpPath .= sha1($username . $db_password) . ".txt"; + //expired 10 minutes + if (file_exists($otpPath) && time() - filemtime($otpPath) < 1200) { + $ui->assign('username', $username); + $ui->assign('notify', 'Please wait ' . (1200 - (time() - filemtime($otpPath))) . ' seconds before sending another SMS'); + $ui->assign('notify_t', 'd'); + $ui->display('register-otp.tpl'); + } else { + $otp = rand(100000, 999999); + file_put_contents($otpPath, $otp); + Message::sendSMS($username, $config['CompanyName'] . "\nYour Verification code are: $otp"); + $ui->assign('username', $username); + $ui->assign('notify', 'Verification code has been sent to your phone'); + $ui->assign('notify_t', 's'); + $ui->display('register-otp.tpl'); + } + } else { + run_hook('view_otp_register'); #HOOK + $ui->display('register-rotp.tpl'); + } + } else { + $ui->assign('username', ""); + $ui->assign('fullname', ""); + $ui->assign('address', ""); + $ui->assign('email', ""); + $ui->assign('otp', false); + run_hook('view_register'); #HOOK + $ui->display('register.tpl'); + } + break; +} diff --git a/system/controllers/reports.php b/system/controllers/reports.php new file mode 100644 index 0000000..2983fc1 --- /dev/null +++ b/system/controllers/reports.php @@ -0,0 +1,100 @@ +assign('_title', Lang::T('Reports')); +$ui->assign('_system_menu', 'reports'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +$mdate = date('Y-m-d'); +$mtime = date('H:i:s'); +$tdate = date('Y-m-d', strtotime('today - 30 days')); +$firs_day_month = date('Y-m-01'); +$this_week_start = date('Y-m-d', strtotime('previous sunday')); +$before_30_days = date('Y-m-d', strtotime('today - 30 days')); +$month_n = date('n'); + +switch ($action) { + case 'by-date': + case 'activation': + $q = (_post('q') ? _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(U . "logs/list/", 's', "Delete logs older than $keep days"); + } + if ($q != '') { + $query = ORM::for_table('tbl_transactions')->where_like('invoice', '%' . $q . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['q' => $q]); + } else { + $query = ORM::for_table('tbl_transactions')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('activation', $d); + $ui->assign('q', $q); + $ui->display('reports-activation.tpl'); + break; + case 'daily-report': + $query = ORM::for_table('tbl_transactions')->where('recharged_on', $mdate)->order_by_desc('id'); + $d = Paginator::findMany($query); + $dr = $query->sum('price'); + + $ui->assign('d', $d); + $ui->assign('dr', $dr); + $ui->assign('mdate', $mdate); + $ui->assign('mtime', $mtime); + run_hook('view_daily_reports'); #HOOK + $ui->display('reports-daily.tpl'); + break; + + case 'by-period': + $ui->assign('mdate', $mdate); + $ui->assign('mtime', $mtime); + $ui->assign('tdate', $tdate); + run_hook('view_reports_by_period'); #HOOK + $ui->display('reports-period.tpl'); + break; + + case 'period-view': + $fdate = _post('fdate'); + $tdate = _post('tdate'); + $stype = _post('stype'); + + $d = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $d->where('type', $stype); + } + + $d->where_gte('recharged_on', $fdate); + $d->where_lte('recharged_on', $tdate); + $d->order_by_desc('id'); + $x = $d->find_many(); + + $dr = ORM::for_table('tbl_transactions'); + if ($stype != '') { + $dr->where('type', $stype); + } + + $dr->where_gte('recharged_on', $fdate); + $dr->where_lte('recharged_on', $tdate); + $xy = $dr->sum('price'); + + $ui->assign('d', $x); + $ui->assign('dr', $xy); + $ui->assign('fdate', $fdate); + $ui->assign('tdate', $tdate); + $ui->assign('stype', $stype); + run_hook('view_reports_period'); #HOOK + $ui->display('reports-period-view.tpl'); + break; + + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/routers.php b/system/controllers/routers.php new file mode 100644 index 0000000..0d9724f --- /dev/null +++ b/system/controllers/routers.php @@ -0,0 +1,400 @@ +assign('_title', Lang::T('Network')); +$ui->assign('_system_menu', 'network'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +use PEAR2\Net\RouterOS; + +require_once 'system/autoload/PEAR2/Autoload.php'; + +if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); +} + +use RouterOS\Exceptions\Socket\TimeoutException; +use RouterOS\Exceptions\Socket\ConnectionException; + +function mikrotik_get_resources($routerId) +{ + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + + if (!$mikrotik) { + return [ + 'status' => 'Offline', + 'error' => 'Router not found or disabled' + ]; + } + + // Check cache first (30 second cache for successful connections) + $cacheKey = 'router_resources_' . $routerId; + $cachedData = null; + if (function_exists('apcu_fetch')) { + $cachedData = apcu_fetch($cacheKey); + if ($cachedData && (time() - $cachedData['timestamp']) < 30) { + return array_merge($cachedData, ['cached' => true]); + } + } + + // Check if router is in circuit breaker state (too many recent failures) + $circuitBreakerKey = 'router_failures_' . $routerId; + $failureCount = 0; + $lastFailure = 0; + if (function_exists('apcu_fetch')) { + $failureCount = apcu_fetch($circuitBreakerKey) ?: 0; + $lastFailure = apcu_fetch($circuitBreakerKey . '_time') ?: 0; + } + + // If more than 3 failures in the last 5 minutes, skip connection attempt + if ($failureCount >= 3 && (time() - $lastFailure) < 300) { + return [ + 'status' => 'Offline', + 'error' => 'Router temporarily unavailable (circuit breaker)', + 'cached' => true + ]; + } + + try { + // Set a shorter timeout for the connection + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + if (!$client) { + throw new Exception('Failed to create RouterOS client'); + } + + // Set timeout for individual requests (5 seconds each) + $health = $client->sendSync(new RouterOS\Request('/system health print'), 5); + $res = $client->sendSync(new RouterOS\Request('/system resource print'), 5); + + $resourceData = $res->getAllOfType(RouterOS\Response::TYPE_DATA)[0]; + $uptime = $resourceData->getProperty('uptime'); + $freeMemory = $resourceData->getProperty('free-memory'); + $totalMemory = $resourceData->getProperty('total-memory'); + $cpuLoad = $resourceData->getProperty('cpu-load'); + + $status = ($uptime !== null && $freeMemory !== null && $totalMemory !== null && $cpuLoad !== null) ? 'Online' : 'Offline'; + + $result = [ + 'status' => $status, + 'uptime' => $uptime, + 'freeMemory' => mikrotik_formatSize($freeMemory), + 'totalMemory' => mikrotik_formatSize($totalMemory), + 'cpuLoad' => $cpuLoad . '%', + 'timestamp' => time() + ]; + + // Cache successful results + if (function_exists('apcu_store') && $status === 'Online') { + apcu_store($cacheKey, $result, 30); + } + + return $result; + } catch (TimeoutException $e) { + // Increment failure count for circuit breaker + if (function_exists('apcu_store')) { + apcu_store($circuitBreakerKey, $failureCount + 1, 300); + apcu_store($circuitBreakerKey . '_time', time(), 300); + } + + return [ + 'status' => 'Offline', + 'error' => 'Connection timeout', + 'timeout' => true + ]; + } catch (ConnectionException $e) { + // Increment failure count for circuit breaker + if (function_exists('apcu_store')) { + apcu_store($circuitBreakerKey, $failureCount + 1, 300); + apcu_store($circuitBreakerKey . '_time', time(), 300); + } + + return [ + 'status' => 'Offline', + 'error' => 'Connection failed', + 'connection_error' => true + ]; + } catch (Exception $e) { + // Increment failure count for circuit breaker + if (function_exists('apcu_store')) { + apcu_store($circuitBreakerKey, $failureCount + 1, 300); + apcu_store($circuitBreakerKey . '_time', time(), 300); + } + + return [ + 'status' => 'Offline', + 'error' => 'Unknown error: ' . $e->getMessage() + ]; + } +} + +function mikrotik_formatSize($size) +{ + $units = ['B', 'KB', 'MB', 'GB']; + $unitIndex = 0; + while ($size >= 1024 && $unitIndex < count($units) - 1) { + $size /= 1024; + $unitIndex++; + } + return round($size, 2) . ' ' . $units[$unitIndex]; +} + +// Function to reboot MikroTik router +function mikrotik_reboot($routerId) +{ + $mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId); + + if (!$mikrotik) { + return [ + 'status' => 'Offline', + 'message' => 'Router not found' + ]; + } + + try { + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + + // Send the reboot command + $client->sendSync(new RouterOS\Request('/system reboot')); + + return [ + 'status' => 'Rebooting', + 'message' => 'Router is rebooting' + ]; + } catch (TimeoutException | ConnectionException $e) { + return [ + 'status' => 'Error', + 'message' => 'Failed to connect to the router' + ]; + } +} + +switch ($action) { + case 'list': + $ui->assign('xfooter', ''); + + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_routers')->where_like('name', '%' . $name . '%')->order_by_desc('id'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_routers')->order_by_desc('id'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + run_hook('view_list_routers'); #HOOK + $ui->display('routers.tpl'); + break; + + case 'get_resources': + if (isset($_GET['router_id'])) { + $routerId = $_GET['router_id']; + $resources = mikrotik_get_resources($routerId); + echo json_encode($resources); + exit; + } + break; + + case 'get_all_resources': + // Get all enabled routers + $routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many(); + $results = []; + + foreach ($routers as $router) { + $resources = mikrotik_get_resources($router['id']); + $results[$router['id']] = $resources; + } + + echo json_encode($results); + exit; + break; + + case 'reboot': + if (isset($_GET['router_id'])) { + $routerId = $_GET['router_id']; + $result = mikrotik_reboot($routerId); + echo json_encode($result); + exit; + } + break; + + case 'add': + run_hook('view_add_routers'); #HOOK + $ui->display('routers-add.tpl'); + break; + + case 'edit': + $id = $routes['2']; + $d = ORM::for_table('tbl_routers')->find_one($id); + if (!$d) { + $d = ORM::for_table('tbl_routers')->where_equal('name', _get('name'))->find_one(); + } + if ($d) { + $ui->assign('d', $d); + run_hook('view_router_edit'); #HOOK + $ui->display('routers-edit.tpl'); + } else { + r2(U . 'routers/list', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + $id = $routes['2']; + run_hook('router_delete'); #HOOK + $d = ORM::for_table('tbl_routers')->find_one($id); + if ($d) { + $d->delete(); + r2(U . 'routers/list', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'add-post': + $name = _post('name'); + $ip_address = _post('ip_address'); + $username = _post('username'); + $password = _post('password'); + $description = _post('description'); + $enabled = _post('enabled'); + + $msg = ''; + if (Validator::Length($name, 30, 4) == false) { + $msg .= 'Name should be between 5 to 30 characters' . '
'; + } + if ($ip_address == '' or $username == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $d = ORM::for_table('tbl_routers')->where('ip_address', $ip_address)->find_one(); + if ($d) { + $msg .= Lang::T('IP Router Already Exist') . '
'; + } + if (strtolower($name) == 'radius') { + $msg .= 'Radius name is reserved
'; + } + + if ($msg == '') { + Mikrotik::getClient($ip_address, $username, $password); + run_hook('add_router'); #HOOK + $d = ORM::for_table('tbl_routers')->create(); + $d->name = $name; + $d->ip_address = $ip_address; + $d->username = $username; + $d->password = $password; + $d->description = $description; + $d->enabled = $enabled; + $d->save(); + + r2(U . 'routers/list', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'routers/add', 'e', $msg); + } + break; + + case 'download': + $routerId = _post('router_id'); + $routerName = _post('router_name'); + + if ($routerId && $routerName) { + $updateRouterIdStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_id WHERE setting = 'router_id'"); + $updateRouterIdStmt->execute(['router_id' => $routerId]); + + $updateRouterNameStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_name WHERE setting = 'router_name'"); + $updateRouterNameStmt->execute(['router_name' => $routerName]); + + header("Location: {$app_url}/system/plugin/download.php?download=1"); + exit; + } else { + r2(U . 'routers/list', 'e', Lang::T('Invalid router ID or name')); + } + break; + + case 'edit-post': + $name = _post('name'); + $ip_address = _post('ip_address'); + $username = _post('username'); + $password = _post('password'); + $description = _post('description'); + $enabled = $_POST['enabled']; + $msg = ''; + if (Validator::Length($name, 30, 4) == false) { + $msg .= 'Name should be between 5 to 30 characters' . '
'; + } + if ($ip_address == '' or $username == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $id = _post('id'); + $d = ORM::for_table('tbl_routers')->find_one($id); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + + if ($d['name'] != $name) { + $c = ORM::for_table('tbl_routers')->where('name', $name)->where_not_equal('id', $id)->find_one(); + if ($c) { + $msg .= 'Name Already Exists
'; + } + } + $oldname = $d['name']; + + if ($d['ip_address'] != $ip_address) { + $c = ORM::for_table('tbl_routers')->where('ip_address', $ip_address)->where_not_equal('id', $id)->find_one(); + if ($c) { + $msg .= 'IP Already Exists
'; + } + } + + if (strtolower($name) == 'radius') { + $msg .= 'Radius name is reserved
'; + } + + if ($msg == '') { + Mikrotik::getClient($ip_address, $username, $password); + run_hook('router_edit'); #HOOK + $d->name = $name; + $d->ip_address = $ip_address; + $d->username = $username; + $d->password = $password; + $d->description = $description; + $d->enabled = $enabled; + $d->save(); + if ($name != $oldname) { + $p = ORM::for_table('tbl_plans')->where('routers', $oldname)->find_result_set(); + $p->set('routers', $name); + $p->save(); + $p = ORM::for_table('tbl_payment_gateway')->where('routers', $oldname)->find_result_set(); + $p->set('routers', $name); + $p->save(); + $p = ORM::for_table('tbl_pool')->where('routers', $oldname)->find_result_set(); + $p->set('routers', $name); + $p->save(); + $p = ORM::for_table('tbl_transactions')->where('routers', $oldname)->find_result_set(); + $p->set('routers', $name); + $p->save(); + $p = ORM::for_table('tbl_user_recharges')->where('routers', $oldname)->find_result_set(); + $p->set('routers', $name); + $p->save(); + $p = ORM::for_table('tbl_voucher')->where('routers', $oldname)->find_result_set(); + $p->set('routers', $name); + $p->save(); + } + r2(U . 'routers/list', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'routers/edit/' . $id, 'e', $msg); + } + break; + + default: + r2(U . 'routers/list/', 's', ''); +} + +?> diff --git a/system/controllers/services.php b/system/controllers/services.php new file mode 100644 index 0000000..f5de7f0 --- /dev/null +++ b/system/controllers/services.php @@ -0,0 +1,750 @@ +assign('_title', Lang::T('Hotspot Plans')); +$ui->assign('_system_menu', 'services'); + +$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"); +} + +use PEAR2\Net\RouterOS; + +require_once 'system/autoload/PEAR2/Autoload.php'; + +switch ($action) { + case 'sync': + set_time_limit(-1); + if ($routes['2'] == 'hotspot') { + $plans = ORM::for_table('tbl_bandwidth')->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))->where('tbl_plans.type', 'Hotspot')->where('tbl_plans.enabled', '1')->find_many(); + $log = ''; + $router = ''; + foreach ($plans as $plan) { + if ($plan['is_radius']) { + if ($b['rate_down_unit'] == 'Kbps') { + $raddown = '000'; + } else { + $raddown = '000000'; + } + if ($b['rate_up_unit'] == 'Kbps') { + $radup = '000'; + } else { + $radup = '000000'; + } + $radiusRate = $plan['rate_up'] . $radup . '/' . $plan['rate_down'] . $raddown . '/' . $plan['burst']; + Radius::planUpSert($plan['id'], $radiusRate); + $log .= "DONE : Radius $plan[name_plan], $plan[shared_users], $radiusRate
"; + } else { + if ($router != $plan['routers']) { + $mikrotik = Mikrotik::info($plan['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $router = $plan['routers']; + } + if ($plan['rate_down_unit'] == 'Kbps') { + $unitdown = 'K'; + } else { + $unitdown = 'M'; + } + if ($plan['rate_up_unit'] == 'Kbps') { + $unitup = 'K'; + } else { + $unitup = 'M'; + } + $rate = $plan['rate_up'] . $unitup . "/" . $plan['rate_down'] . $unitdown; + Mikrotik::addHotspotPlan($client, $plan['name_plan'], $plan['shared_users'], $rate); + $log .= "DONE : $plan[name_plan], $plan[shared_users], $rate
"; + if (!empty($plan['pool_expired'])) { + Mikrotik::setHotspotExpiredPlan($client, 'EXPIRED NUXBILL ' . $plan['pool_expired'], $plan['pool_expired']); + $log .= "DONE Expired : EXPIRED NUXBILL $plan[pool_expired]
"; + } + } + } + r2(U . 'services/hotspot', 's', $log); + } else if ($routes['2'] == 'pppoe') { + $plans = ORM::for_table('tbl_bandwidth')->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))->where('tbl_plans.type', 'PPPOE')->where('tbl_plans.enabled', '1')->find_many(); + $log = ''; + $router = ''; + foreach ($plans as $plan) { + if ($plan['is_radius']) { + if ($b['rate_down_unit'] == 'Kbps') { + $raddown = '000'; + } else { + $raddown = '000000'; + } + if ($b['rate_up_unit'] == 'Kbps') { + $radup = '000'; + } else { + $radup = '000000'; + } + $radiusRate = $plan['rate_up'] . $radup . '/' . $plan['rate_down'] . $raddown . '/' . $plan['burst']; + Radius::planUpSert($plan['id'], $radiusRate, $plan['pool']); + $log .= "DONE : RADIUS $plan[name_plan], $plan[pool], $rate
"; + } else { + if ($router != $plan['routers']) { + $mikrotik = Mikrotik::info($plan['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + $router = $plan['routers']; + } + if ($plan['rate_down_unit'] == 'Kbps') { + $unitdown = 'K'; + } else { + $unitdown = 'M'; + } + if ($plan['rate_up_unit'] == 'Kbps') { + $unitup = 'K'; + } else { + $unitup = 'M'; + } + $rate = $plan['rate_up'] . $unitup . "/" . $plan['rate_down'] . $unitdown; + Mikrotik::addPpoePlan($client, $plan['name_plan'], $plan['pool'], $rate); + $log .= "DONE : $plan[name_plan], $plan[pool], $rate
"; + if (!empty($plan['pool_expired'])) { + Mikrotik::setPpoePlan($client, 'EXPIRED NUXBILL ' . $plan['pool_expired'], $plan['pool_expired'], '512K/512K'); + $log .= "DONE Expired : EXPIRED NUXBILL $plan[pool_expired]
"; + } + } + } + r2(U . 'services/pppoe', 's', $log); + } + r2(U . 'services/hotspot', 'w', 'Unknown command'); + case 'hotspot': + $ui->assign('xfooter', ''); + + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_bandwidth')->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))->where('tbl_plans.type', 'Hotspot')->where_like('tbl_plans.name_plan', '%' . $name . '%'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_bandwidth')->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))->where('tbl_plans.type', 'Hotspot'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + run_hook('view_list_plans'); #HOOK + $ui->display('hotspot.tpl'); + break; + + case 'add': + $d = ORM::for_table('tbl_bandwidth')->find_many(); + $ui->assign('d', $d); + $r = ORM::for_table('tbl_routers')->find_many(); + $ui->assign('r', $r); + run_hook('view_add_plan'); #HOOK + $ui->display('hotspot-add.tpl'); + break; + + case 'edit': + $id = $routes['2']; + $d = ORM::for_table('tbl_plans')->find_one($id); + if ($d) { + $ui->assign('d', $d); + $p = ORM::for_table('tbl_pool')->where('routers', $d['routers'])->find_many(); + $ui->assign('p', $p); + $b = ORM::for_table('tbl_bandwidth')->find_many(); + $ui->assign('b', $b); + run_hook('view_edit_plan'); #HOOK + $ui->display('hotspot-edit.tpl'); + } else { + r2(U . 'services/hotspot', 'e', Lang::T('Account Not Found')); + } + break; + + case 'delete': + $id = $routes['2']; + + $d = ORM::for_table('tbl_plans')->find_one($id); + if ($d) { + run_hook('delete_plan'); #HOOK + if ($d['is_radius']) { + Radius::planDelete($d['id']); + } else { + try { + $mikrotik = Mikrotik::info($d['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removeHotspotPlan($client, $d['name_plan']); + } catch (Exception $e) { + //ignore exception, it means router has already deleted + } catch (Throwable $e) { + //ignore exception, it means router has already deleted + } + } + + $d->delete(); + + r2(U . 'services/hotspot', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'add-post': + $name = _post('name'); + $plan_type = _post('plan_type'); //Personal / Business + $radius = _post('radius'); + $typebp = _post('typebp'); + $limit_type = _post('limit_type'); + $time_limit = _post('time_limit'); + $time_unit = _post('time_unit'); + $data_limit = _post('data_limit'); + $data_unit = _post('data_unit'); + $id_bw = _post('id_bw'); + $price = _post('price'); + $sharedusers = _post('sharedusers'); + $validity = _post('validity'); + $validity_unit = _post('validity_unit'); + $routers = _post('routers'); + $pool_expired = _post('pool_expired'); + $list_expired = _post('list_expired'); + $enabled = _post('enabled'); + $prepaid = _post('prepaid'); + + $msg = ''; + if (Validator::UnsignedNumber($validity) == false) { + $msg .= 'The validity must be a number' . '
'; + } + if (Validator::UnsignedNumber($price) == false) { + $msg .= 'The price must be a number' . '
'; + } + if ($name == '' or $id_bw == '' or $price == '' or $validity == '') { + $msg .= Lang::T('All field is required') . '
'; + } + if (empty($radius)) { + if ($routers == '') { + $msg .= Lang::T('All field is required') . '
'; + } + } + $d = ORM::for_table('tbl_plans')->where('name_plan', $name)->where('type', 'Hotspot')->find_one(); + if ($d) { + $msg .= Lang::T('Name Plan Already Exist') . '
'; + } + + run_hook('add_plan'); #HOOK + + if ($msg == '') { + $b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one(); + if ($b['rate_down_unit'] == 'Kbps') { + $unitdown = 'K'; + $raddown = '000'; + } else { + $unitdown = 'M'; + $raddown = '000000'; + } + if ($b['rate_up_unit'] == 'Kbps') { + $unitup = 'K'; + $radup = '000'; + } else { + $unitup = 'M'; + $radup = '000000'; + } + $rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown; + $radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst']; + $rate = trim($rate . " " . $b['burst']); + // Create new plan + $d = ORM::for_table('tbl_plans')->create(); + $d->name_plan = $name; + $d->id_bw = $id_bw; + $d->price = $price; // Set price with or without tax based on configuration + $d->type = 'Hotspot'; + $d->typebp = $typebp; + $d->plan_type = $plan_type; + $d->limit_type = $limit_type; + $d->time_limit = $time_limit; + $d->time_unit = $time_unit; + $d->data_limit = $data_limit; + $d->data_unit = $data_unit; + $d->validity = $validity; + $d->validity_unit = $validity_unit; + $d->shared_users = $sharedusers; + if (!empty($radius)) { + $d->is_radius = 1; + $d->routers = ''; + } else { + $d->is_radius = 0; + $d->routers = $routers; + } + $d->pool_expired = $pool_expired; + $d->list_expired = $list_expired; + $d->enabled = $enabled; + $d->prepaid = $prepaid; + $d->save(); + $plan_id = $d->id(); + + if ($d['is_radius']) { + Radius::planUpSert($plan_id, $radiusRate); + } else { + $mikrotik = Mikrotik::info($routers); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::addHotspotPlan($client, $name, $sharedusers, $rate); + if (!empty($pool_expired)) { + Mikrotik::setHotspotExpiredPlan($client, 'EXPIRED NUXBILL ' . $pool_expired, $pool_expired); + } + } + + + r2(U . 'services/hotspot', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'services/add', 'e', $msg); + } + break; + + + case 'edit-post': + $id = _post('id'); + $name = _post('name'); + $plan_type = _post('plan_type'); + $id_bw = _post('id_bw'); + $typebp = _post('typebp'); + $price = _post('price'); + $limit_type = _post('limit_type'); + $time_limit = _post('time_limit'); + $time_unit = _post('time_unit'); + $data_limit = _post('data_limit'); + $data_unit = _post('data_unit'); + $sharedusers = _post('sharedusers'); + $validity = _post('validity'); + $validity_unit = _post('validity_unit'); + $pool_expired = _post('pool_expired'); + $list_expired = _post('list_expired'); + $enabled = _post('enabled'); + $prepaid = _post('prepaid'); + $routers = _post('routers'); + $msg = ''; + if (Validator::UnsignedNumber($validity) == false) { + $msg .= 'The validity must be a number' . '
'; + } + if (Validator::UnsignedNumber($price) == false) { + $msg .= 'The price must be a number' . '
'; + } + if ($name == '' or $id_bw == '' or $price == '' or $validity == '') { + $msg .= Lang::T('All field is required') . '
'; + } + $d = ORM::for_table('tbl_plans')->where('id', $id)->find_one(); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + run_hook('edit_plan'); #HOOK + if ($msg == '') { + $b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one(); + if ($b['rate_down_unit'] == 'Kbps') { + $unitdown = 'K'; + $raddown = '000'; + } else { + $unitdown = 'M'; + $raddown = '000000'; + } + if ($b['rate_up_unit'] == 'Kbps') { + $unitup = 'K'; + $radup = '000'; + } else { + $unitup = 'M'; + $radup = '000000'; + } + $rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown; + $radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst']; + + $rate = trim($rate . " " . $b['burst']); + + if ($d['is_radius']) { + Radius::planUpSert($id, $radiusRate); + } else { + $mikrotik = Mikrotik::info($routers); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::setHotspotPlan($client, $name, $sharedusers, $rate); + if (!empty($pool_expired)) { + Mikrotik::setHotspotExpiredPlan($client, 'EXPIRED NUXBILL ' . $pool_expired, $pool_expired); + } + } + $d->name_plan = $name; + $d->id_bw = $id_bw; + $d->price = $price; // Set price with or without tax based on configuration + $d->typebp = $typebp; + $d->limit_type = $limit_type; + $d->time_limit = $time_limit; + $d->time_unit = $time_unit; + $d->data_limit = $data_limit; + $d->plan_type = $plan_type; + $d->data_unit = $data_unit; + $d->validity = $validity; + $d->validity_unit = $validity_unit; + $d->shared_users = $sharedusers; + $d->pool_expired = $pool_expired; + $d->list_expired = $list_expired; + $d->enabled = $enabled; + $d->prepaid = $prepaid; + $d->save(); + + r2(U . 'services/hotspot', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'services/edit/' . $id, 'e', $msg); + } + break; + + case 'pppoe': + $ui->assign('_title', Lang::T('PPPOE Plans')); + $ui->assign('xfooter', ''); + + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_bandwidth')->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))->where('tbl_plans.type', 'PPPOE')->where_like('tbl_plans.name_plan', '%' . $name . '%'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_bandwidth')->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))->where('tbl_plans.type', 'PPPOE'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + run_hook('view_list_ppoe'); #HOOK + $ui->display('pppoe.tpl'); + break; + + case 'pppoe-add': + $ui->assign('_title', Lang::T('PPPOE Plans')); + $d = ORM::for_table('tbl_bandwidth')->find_many(); + $ui->assign('d', $d); + $r = ORM::for_table('tbl_routers')->find_many(); + $ui->assign('r', $r); + run_hook('view_add_ppoe'); #HOOK + $ui->display('pppoe-add.tpl'); + break; + + case 'pppoe-edit': + $ui->assign('_title', Lang::T('PPPOE Plans')); + $id = $routes['2']; + $d = ORM::for_table('tbl_plans')->find_one($id); + if ($d) { + $ui->assign('d', $d); + $p = ORM::for_table('tbl_pool')->where('routers', ($d['is_radius']) ? 'radius' : $d['routers'])->find_many(); + $ui->assign('p', $p); + $b = ORM::for_table('tbl_bandwidth')->find_many(); + $ui->assign('b', $b); + $r = []; + if ($d['is_radius']) { + $r = ORM::for_table('tbl_routers')->find_many(); + } + $ui->assign('r', $r); + run_hook('view_edit_ppoe'); #HOOK + $ui->display('pppoe-edit.tpl'); + } else { + r2(U . 'services/pppoe', 'e', Lang::T('Account Not Found')); + } + break; + + case 'pppoe-delete': + $id = $routes['2']; + + $d = ORM::for_table('tbl_plans')->find_one($id); + if ($d) { + run_hook('delete_ppoe'); #HOOK + if ($d['is_radius']) { + Radius::planDelete($d['id']); + } else { + try { + $mikrotik = Mikrotik::info($d['routers']); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::removePpoePlan($client, $d['name_plan']); + } catch (Exception $e) { + //ignore exception, it means router has already deleted + } catch (Throwable $e) { + //ignore exception, it means router has already deleted + } + } + $d->delete(); + + r2(U . 'services/pppoe', 's', Lang::T('Data Deleted Successfully')); + } + break; + + case 'pppoe-add-post': + $name = _post('name_plan'); + $plan_type = _post('plan_type'); + $radius = _post('radius'); + $id_bw = _post('id_bw'); + $price = _post('price'); + $validity = _post('validity'); + $validity_unit = _post('validity_unit'); + $routers = _post('routers'); + $pool = _post('pool_name'); + $pool_expired = _post('pool_expired'); + $list_expired = _post('list_expired'); + $enabled = _post('enabled'); + $prepaid = _post('prepaid'); + + + $msg = ''; + if (Validator::UnsignedNumber($validity) == false) { + $msg .= 'The validity must be a number' . '
'; + } + if (Validator::UnsignedNumber($price) == false) { + $msg .= 'The price must be a number' . '
'; + } + if ($name == '' or $id_bw == '' or $price == '' or $validity == '' or $pool == '') { + $msg .= Lang::T('All field is required') . '
'; + } + if (empty($radius)) { + if ($routers == '') { + $msg .= Lang::T('All field is required') . '
'; + } + } + + $d = ORM::for_table('tbl_plans')->where('name_plan', $name)->find_one(); + if ($d) { + $msg .= Lang::T('Name Plan Already Exist') . '
'; + } + run_hook('add_ppoe'); #HOOK + if ($msg == '') { + $b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one(); + if ($b['rate_down_unit'] == 'Kbps') { + $unitdown = 'K'; + $raddown = '000'; + } else { + $unitdown = 'M'; + $raddown = '000000'; + } + if ($b['rate_up_unit'] == 'Kbps') { + $unitup = 'K'; + $radup = '000'; + } else { + $unitup = 'M'; + $radup = '000000'; + } + $rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown; + $radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst']; + $rate = trim($rate . " " . $b['burst']); + $d = ORM::for_table('tbl_plans')->create(); + $d->type = 'PPPOE'; + $d->name_plan = $name; + $d->id_bw = $id_bw; + $d->price = $price; + $d->plan_type = $plan_type; + $d->validity = $validity; + $d->validity_unit = $validity_unit; + $d->pool = $pool; + if (!empty($radius)) { + $d->is_radius = 1; + $d->routers = ''; + } else { + $d->is_radius = 0; + $d->routers = $routers; + } + $d->pool_expired = $pool_expired; + $d->list_expired = $list_expired; + $d->enabled = $enabled; + $d->prepaid = $prepaid; + $d->save(); + $plan_id = $d->id(); + + if ($d['is_radius']) { + Radius::planUpSert($plan_id, $radiusRate, $pool); + } else { + $mikrotik = Mikrotik::info($routers); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::addPpoePlan($client, $name, $pool, $rate); + if (!empty($pool_expired)) { + Mikrotik::setPpoePlan($client, 'EXPIRED NUXBILL ' . $pool_expired, $pool_expired, '512K/512K'); + } + } + + r2(U . 'services/pppoe', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'services/pppoe-add', 'e', $msg); + } + break; + + case 'edit-pppoe-post': + $id = _post('id'); + $plan_type = _post('plan_type'); + $name = _post('name_plan'); + $id_bw = _post('id_bw'); + $price = _post('price'); + $validity = _post('validity'); + $validity_unit = _post('validity_unit'); + $routers = _post('routers'); + $pool = _post('pool_name'); + $pool_expired = _post('pool_expired'); + $list_expired = _post('list_expired'); + $enabled = _post('enabled'); + $prepaid = _post('prepaid'); + + $msg = ''; + if (Validator::UnsignedNumber($validity) == false) { + $msg .= 'The validity must be a number' . '
'; + } + if (Validator::UnsignedNumber($price) == false) { + $msg .= 'The price must be a number' . '
'; + } + if ($name == '' or $id_bw == '' or $price == '' or $validity == '' or $pool == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $d = ORM::for_table('tbl_plans')->where('id', $id)->find_one(); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + run_hook('edit_ppoe'); #HOOK + if ($msg == '') { + $b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one(); + if ($b['rate_down_unit'] == 'Kbps') { + $unitdown = 'K'; + $raddown = '000'; + } else { + $unitdown = 'M'; + $raddown = '000000'; + } + if ($b['rate_up_unit'] == 'Kbps') { + $unitup = 'K'; + $radup = '000'; + } else { + $unitup = 'M'; + $radup = '000000'; + } + $rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown; + $radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst']; + $rate = trim($rate . " " . $b['burst']); + + if ($d['is_radius']) { + Radius::planUpSert($id, $radiusRate, $pool); + } else { + $mikrotik = Mikrotik::info($routers); + $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']); + Mikrotik::setPpoePlan($client, $name, $pool, $rate); + if (!empty($pool_expired)) { + Mikrotik::setPpoePlan($client, 'EXPIRED NUXBILL ' . $pool_expired, $pool_expired, '512K/512K'); + } + } + $d->name_plan = $name; + $d->id_bw = $id_bw; + $d->price = $price; + $d->plan_type = $plan_type; + $d->validity = $validity; + $d->validity_unit = $validity_unit; + $d->routers = $routers; + $d->pool = $pool; + $d->pool_expired = $pool_expired; + $d->list_expired = $list_expired; + $d->enabled = $enabled; + $d->prepaid = $prepaid; + $d->save(); + + r2(U . 'services/pppoe', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'services/pppoe-edit/' . $id, 'e', $msg); + } + break; + case 'balance': + $ui->assign('_title', Lang::T('Balance Plans')); + $name = _post('name'); + if ($name != '') { + $query = ORM::for_table('tbl_plans')->where('tbl_plans.type', 'Balance')->where_like('tbl_plans.name_plan', '%' . $name . '%'); + $d = Paginator::findMany($query, ['name' => $name]); + } else { + $query = ORM::for_table('tbl_plans')->where('tbl_plans.type', 'Balance'); + $d = Paginator::findMany($query); + } + + $ui->assign('d', $d); + run_hook('view_list_balance'); #HOOK + $ui->display('balance.tpl'); + break; + case 'balance-add': + $ui->assign('_title', Lang::T('Balance Plans')); + run_hook('view_add_balance'); #HOOK + $ui->display('balance-add.tpl'); + break; + case 'balance-edit': + $ui->assign('_title', Lang::T('Balance Plans')); + $id = $routes['2']; + $d = ORM::for_table('tbl_plans')->find_one($id); + $ui->assign('d', $d); + run_hook('view_edit_balance'); #HOOK + $ui->display('balance-edit.tpl'); + break; + case 'balance-delete': + $id = $routes['2']; + + $d = ORM::for_table('tbl_plans')->find_one($id); + if ($d) { + run_hook('delete_balance'); #HOOK + $d->delete(); + r2(U . 'services/balance', 's', Lang::T('Data Deleted Successfully')); + } + break; + case 'balance-edit-post': + $id = _post('id'); + $name = _post('name'); + $price = _post('price'); + $enabled = _post('enabled'); + $prepaid = _post('prepaid'); + + $msg = ''; + if (Validator::UnsignedNumber($price) == false) { + $msg .= 'The price must be a number' . '
'; + } + if ($name == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $d = ORM::for_table('tbl_plans')->where('id', $id)->find_one(); + if ($d) { + } else { + $msg .= Lang::T('Data Not Found') . '
'; + } + run_hook('edit_ppoe'); #HOOK + if ($msg == '') { + $d->name_plan = $name; + $d->price = $price; + $d->enabled = $enabled; + $d->prepaid = 'yes'; + $d->save(); + + r2(U . 'services/balance', 's', Lang::T('Data Updated Successfully')); + } else { + r2(U . 'services/balance-edit/' . $id, 'e', $msg); + } + break; + case 'balance-add-post': + $name = _post('name'); + $price = _post('price'); + $enabled = _post('enabled'); + + $msg = ''; + if (Validator::UnsignedNumber($price) == false) { + $msg .= 'The price must be a number' . '
'; + } + if ($name == '') { + $msg .= Lang::T('All field is required') . '
'; + } + + $d = ORM::for_table('tbl_plans')->where('name_plan', $name)->find_one(); + if ($d) { + $msg .= Lang::T('Name Plan Already Exist') . '
'; + } + run_hook('add_ppoe'); #HOOK + if ($msg == '') { + $d = ORM::for_table('tbl_plans')->create(); + $d->type = 'Balance'; + $d->name_plan = $name; + $d->id_bw = 0; + $d->price = $price; + $d->validity = 0; + $d->validity_unit = 'Months'; + $d->routers = ''; + $d->pool = ''; + $d->enabled = $enabled; + $d->prepaid = 'yes'; + $d->save(); + + r2(U . 'services/balance', 's', Lang::T('Data Created Successfully')); + } else { + r2(U . 'services/balance-add', 'e', $msg); + } + break; + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/settings.php b/system/controllers/settings.php new file mode 100644 index 0000000..7a3db1b --- /dev/null +++ b/system/controllers/settings.php @@ -0,0 +1,1013 @@ +assign('_title', Lang::T('Settings')); +$ui->assign('_system_menu', 'settings'); + +$action = $routes['1']; +$ui->assign('_admin', $admin); + +/** + * Handle test notifications for various communication channels + */ +function _handleTestNotifications() { + global $config, $U; + + if (!empty(_get('testWa'))) { + $test_message_wa = !empty($config['test_message_wa']) ? $config['test_message_wa'] : 'Hello this is a test Whatsapp Courtesy of Smart-tech'; + $result = Message::sendWhatsapp(_get('testWa'), $test_message_wa); + r2(U . "settings/app", 's', 'Test Whatsapp has been send
Result: ' . $result); + } + + if (!empty(_get('testSms'))) { + $test_message_sms = !empty($config['test_message_sms']) ? $config['test_message_sms'] : 'Hello this is a test SMS Courtesy of Smart-tech'; + $result = Message::sendSMS(_get('testSms'), $test_message_sms); + r2(U . "settings/app", 's', 'Test SMS has been send
Result: ' . $result); + } + + if (!empty(_get('testEmail'))) { + Message::sendEmail(_get('testEmail'), 'PHPNuxBill Test Email', 'PHPNuxBill Test Email Body'); + r2(U . "settings/app", 's', 'Test Email has been send'); + } + + if (!empty(_get('testTg'))) { + $result = Message::sendTelegram('PHPNuxBill Test Telegram'); + r2(U . "settings/app", 's', 'Test Telegram has been send
Result: ' . $result); + } +} + +/** + * Prepare all data needed for the app settings page + */ +function _prepareAppSettingsData() { + global $ui, $config, $UPLOAD_PATH, $root_path, $admin; + + // Prepare logo + $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH); + if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) { + $logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png?' . time(); + } else { + $logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.default.png'; + } + $ui->assign('logo', $logo); + + // Handle Radius client if enabled + if ($config['radius_enable'] && empty($config['radius_client'])) { + try { + $config['radius_client'] = Radius::getClient(); + } catch (Exception $e) { + // Ignore radius errors + } + } + + // Get available themes + $themes = []; + $files = scandir('ui/themes/'); + foreach ($files as $file) { + if (is_dir('ui/themes/' . $file) && !in_array($file, ['.', '..'])) { + $themes[] = $file; + } + } + + // Get routers for SMS configuration + $r = ORM::for_table('tbl_routers')->find_many(); + $ui->assign('r', $r); + + // Get PHP executable path + if (function_exists("shell_exec")) { + $php = trim(shell_exec('which php')); + if (empty($php)) { + $php = 'php'; + } + } else { + $php = 'php'; + } + + // Generate API key if not exists + if (empty($config['api_key'])) { + $config['api_key'] = sha1(uniqid(rand(), true)); + $d = ORM::for_table('tbl_appconfig')->where('setting', 'api_key')->find_one(); + if ($d) { + $d->value = $config['api_key']; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'api_key'; + $d->value = $config['api_key']; + $d->save(); + } + } + + // Assign all data to template + $ui->assign('_c', $config); + $ui->assign('php', $php); + $ui->assign('dir', str_replace('controllers', '', __DIR__)); + $ui->assign('themes', $themes); +} + +switch ($action) { + case 'app': + // Check admin permissions + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + // Handle test notifications + _handleTestNotifications(); + + // Prepare application data + _prepareAppSettingsData(); + + // Display the settings page + run_hook('view_app_settings'); #HOOK + $ui->display('app-settings.tpl'); + break; + + case 'app-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $company = _post('CompanyName'); + run_hook('save_settings'); #HOOK + + + if (!empty($_FILES['logo']['name'])) { + if (function_exists('imagecreatetruecolor')) { + if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) unlink($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png'); + File::resizeCropImage($_FILES['logo']['tmp_name'], $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png', 1078, 200, 100); + if (file_exists($_FILES['logo']['tmp_name'])) unlink($_FILES['logo']['tmp_name']); + } else { + r2(U . 'settings/app', 'e', 'PHP GD is not installed'); + } + } + if ($company == '') { + r2(U . 'settings/app', 'e', Lang::T('All field is required')); + } else { + if ($radius_enable) { + try { + Radius::getTableNas()->find_many(); + } catch (Exception $e) { + $ui->assign("error_title", "RADIUS Error"); + $ui->assign("error_message", "Radius table not found.

" . + $e->getMessage() . + "

Download here or here and import it to database.

Check config.php for radius connection details"); + $ui->display('router-error.tpl'); + die(); + } + } + // Save all settings including tax system + foreach ($_POST as $key => $value) { + $d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one(); + if ($d) { + $d->value = $value; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = $key; + $d->value = $value; + $d->save(); + } + } + //checkbox + $checks = ['hide_mrc', 'hide_tms', 'hide_aui', 'hide_al', 'hide_uet', 'hide_vs', 'hide_pg']; + foreach ($checks as $check) { + if (!isset($_POST[$check])) { + $d = ORM::for_table('tbl_appconfig')->where('setting', $check)->find_one(); + if ($d) { + $d->value = 'no'; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = $check; + $d->value = 'no'; + $d->save(); + } + } + } + + _log('[' . $admin['username'] . ']: ' . Lang::T('Settings Saved Successfully'), $admin['user_type'], $admin['id']); + + r2(U . 'settings/app', 's', Lang::T('Settings Saved Successfully')); + } + break; + + case 'localisation': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $folders = []; + $files = scandir('system/lan/'); + foreach ($files as $file) { + if (is_file('system/lan/' . $file) && !in_array($file, ['index.html', 'country.json', '.DS_Store'])) { + $file = str_replace(".json", "", $file); + $folders[$file] = ''; + } + } + $ui->assign('lani', $folders); + $lans = Lang::getIsoLang(); + foreach ($lans as $lan => $val) { + if (isset($folders[$lan])) { + unset($lans[$lan]); + } + } + $ui->assign('lan', $lans); + $timezonelist = Timezone::timezoneList(); + $ui->assign('tlist', $timezonelist); + $ui->assign('xjq', ' $("#tzone").select2(); '); + run_hook('view_localisation'); #HOOK + $ui->display('app-localisation.tpl'); + break; + + case 'localisation-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $tzone = _post('tzone'); + $date_format = _post('date_format'); + $country_code_phone = _post('country_code_phone'); + $lan = _post('lan'); + run_hook('save_localisation'); #HOOK + if ($tzone == '' or $date_format == '' or $lan == '') { + r2(U . 'settings/app', 'e', Lang::T('All field is required')); + } else { + $d = ORM::for_table('tbl_appconfig')->where('setting', 'timezone')->find_one(); + $d->value = $tzone; + $d->save(); + + $d = ORM::for_table('tbl_appconfig')->where('setting', 'date_format')->find_one(); + $d->value = $date_format; + $d->save(); + + $dec_point = $_POST['dec_point']; + if (strlen($dec_point) == '1') { + $d = ORM::for_table('tbl_appconfig')->where('setting', 'dec_point')->find_one(); + $d->value = $dec_point; + $d->save(); + } + + $thousands_sep = $_POST['thousands_sep']; + if (strlen($thousands_sep) == '1') { + $d = ORM::for_table('tbl_appconfig')->where('setting', 'thousands_sep')->find_one(); + $d->value = $thousands_sep; + $d->save(); + } + + $d = ORM::for_table('tbl_appconfig')->where('setting', 'country_code_phone')->find_one(); + if ($d) { + $d->value = $country_code_phone; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'country_code_phone'; + $d->value = $country_code_phone; + $d->save(); + } + + $d = ORM::for_table('tbl_appconfig')->where('setting', 'radius_plan')->find_one(); + if ($d) { + $d->value = _post('radius_plan'); + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'radius_plan'; + $d->value = _post('radius_plan'); + $d->save(); + } + $d = ORM::for_table('tbl_appconfig')->where('setting', 'hotspot_plan')->find_one(); + if ($d) { + $d->value = _post('hotspot_plan'); + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'hotspot_plan'; + $d->value = _post('hotspot_plan'); + $d->save(); + } + $d = ORM::for_table('tbl_appconfig')->where('setting', 'pppoe_plan')->find_one(); + if ($d) { + $d->value = _post('pppoe_plan'); + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'pppoe_plan'; + $d->value = _post('pppoe_plan'); + $d->save(); + } + + $currency_code = $_POST['currency_code']; + $d = ORM::for_table('tbl_appconfig')->where('setting', 'currency_code')->find_one(); + $d->value = $currency_code; + $d->save(); + + $d = ORM::for_table('tbl_appconfig')->where('setting', 'language')->find_one(); + $d->value = $lan; + $d->save(); + unset($_SESSION['Lang']); + _log('[' . $admin['username'] . ']: ' . Lang::T('Settings Saved Successfully'), $admin['user_type'], $admin['id']); + r2(U . 'settings/localisation', 's', Lang::T('Settings Saved Successfully')); + } + break; + + case 'users': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $search = _req('search'); + if ($search != '') { + if ($admin['user_type'] == 'SuperAdmin') { + $query = ORM::for_table('tbl_users') + ->where_like('username', '%' . $search . '%') + ->order_by_asc('id'); + $d = Paginator::findMany($query, ['search' => $search]); + } else if ($admin['user_type'] == 'Admin') { + $query = ORM::for_table('tbl_users') + ->where_like('username', '%' . $search . '%')->where_any_is([ + ['user_type' => 'Report'], + ['user_type' => 'Agent'], + ['user_type' => 'Sales'], + ['id' => $admin['id']] + ])->order_by_asc('id'); + $d = Paginator::findMany($query, ['search' => $search]); + } else { + $query = ORM::for_table('tbl_users') + ->where_like('username', '%' . $search . '%') + ->where_any_is([ + ['id' => $admin['id']], + ['root' => $admin['id']] + ])->order_by_asc('id'); + $d = Paginator::findMany($query, ['search' => $search]); + } + } else { + if ($admin['user_type'] == 'SuperAdmin') { + $query = ORM::for_table('tbl_users')->order_by_asc('id'); + $d = Paginator::findMany($query); + } else if ($admin['user_type'] == 'Admin') { + $query = ORM::for_table('tbl_users')->where_any_is([ + ['user_type' => 'Report'], + ['user_type' => 'Agent'], + ['user_type' => 'Sales'], + ['id' => $admin['id']] + ])->order_by_asc('id'); + $d = Paginator::findMany($query); + } else { + $query = ORM::for_table('tbl_users') + ->where_any_is([ + ['id' => $admin['id']], + ['root' => $admin['id']] + ])->order_by_asc('id'); + $d = Paginator::findMany($query); + } + } + $admins = []; + foreach ($d as $k) { + if (!empty($k['root'])) { + $admins[] = $k['root']; + } + } + if (count($admins) > 0) { + $adms = ORM::for_table('tbl_users')->where_in('id', $admins)->findArray(); + unset($admins); + foreach ($adms as $adm) { + $admins[$adm['id']] = $adm['fullname']; + } + } + $ui->assign('admins', $admins); + $ui->assign('d', $d); + $ui->assign('search', $search); + run_hook('view_list_admin'); #HOOK + $ui->display('users.tpl'); + break; + + case 'users-add': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $ui->assign('_title', Lang::T('Add User')); + $ui->assign('agents', ORM::for_table('tbl_users')->where('user_type', 'Agent')->find_many()); + $ui->display('users-add.tpl'); + break; + case 'users-view': + $ui->assign('_title', Lang::T('Edit User')); + $id = $routes['2']; + if (empty($id)) { + $id = $admin['id']; + } + //allow see himself + if ($admin['id'] == $id) { + $d = ORM::for_table('tbl_users')->where('id', $id)->find_array($id)[0]; + } else { + if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + // Super Admin can see anyone + $d = ORM::for_table('tbl_users')->where('id', $id)->find_array()[0]; + } else if ($admin['user_type'] == 'Agent') { + // Agent can see Sales + $d = ORM::for_table('tbl_users')->where_any_is([['root' => $admin['id']], ['id' => $id]])->find_array()[0]; + } + } + if ($d) { + run_hook('view_edit_admin'); #HOOK + if ($d['user_type'] == 'Sales') { + $ui->assign('agent', ORM::for_table('tbl_users')->where('id', $d['root'])->find_array()[0]); + } + $ui->assign('d', $d); + $ui->assign('_title', $d['username']); + $ui->display('users-view.tpl'); + } else { + r2(U . 'settings/users', 'e', Lang::T('Account Not Found')); + } + break; + case 'users-edit': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $ui->assign('_title', Lang::T('Edit User')); + $id = $routes['2']; + if (empty($id)) { + $id = $admin['id']; + } + if ($admin['id'] == $id) { + $d = ORM::for_table('tbl_users')->find_one($id); + } else { + if ($admin['user_type'] == 'SuperAdmin') { + $d = ORM::for_table('tbl_users')->find_one($id); + $ui->assign('agents', ORM::for_table('tbl_users')->where('user_type', 'Agent')->find_many()); + } else if ($admin['user_type'] == 'Admin') { + $d = ORM::for_table('tbl_users')->where_any_is([ + ['user_type' => 'Report'], + ['user_type' => 'Agent'], + ['user_type' => 'Sales'] + ])->find_one($id); + $ui->assign('agents', ORM::for_table('tbl_users')->where('user_type', 'Agent')->find_many()); + } else { + // Agent cannot move Sales to other Agent + $ui->assign('agents', ORM::for_table('tbl_users')->where('id', $admin['id'])->find_many()); + $d = ORM::for_table('tbl_users')->where('root', $admin['id'])->find_one($id); + } + } + if ($d) { + $ui->assign('id', $id); + $ui->assign('d', $d); + run_hook('view_edit_admin'); #HOOK + $ui->display('users-edit.tpl'); + } else { + r2(U . 'settings/users', 'e', Lang::T('Account Not Found')); + } + break; + + case 'users-delete': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $id = $routes['2']; + if (($admin['id']) == $id) { + r2(U . 'settings/users', 'e', 'Sorry You can\'t delete yourself'); + } + $d = ORM::for_table('tbl_users')->find_one($id); + if ($d) { + run_hook('delete_admin'); #HOOK + $d->delete(); + r2(U . 'settings/users', 's', Lang::T('User deleted Successfully')); + } else { + r2(U . 'settings/users', 'e', Lang::T('Account Not Found')); + } + break; + + case 'users-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $username = _post('username'); + $fullname = _post('fullname'); + $password = _post('password'); + $user_type = _post('user_type'); + $phone = _post('phone'); + $email = _post('email'); + $city = _post('city'); + $subdistrict = _post('subdistrict'); + $ward = _post('ward'); + $send_notif = _post('send_notif'); + $root = _post('root'); + $msg = ''; + if (Validator::Length($username, 45, 2) == false) { + $msg .= Lang::T('Username should be between 3 to 45 characters') . '
'; + } + if (Validator::Length($fullname, 45, 2) == false) { + $msg .= Lang::T('Full Name should be between 3 to 45 characters') . '
'; + } + if (!Validator::Length($password, 1000, 5)) { + $msg .= Lang::T('Password should be minimum 6 characters') . '
'; + } + + $d = ORM::for_table('tbl_users')->where('username', $username)->find_one(); + if ($d) { + $msg .= Lang::T('Account already axist') . '
'; + } + $date_now = date("Y-m-d H:i:s"); + run_hook('add_admin'); #HOOK + if ($msg == '') { + $passwordC = Password::_crypt($password); + $d = ORM::for_table('tbl_users')->create(); + $d->username = $username; + $d->fullname = $fullname; + $d->password = $passwordC; + $d->user_type = $user_type; + $d->phone = $phone; + $d->email = $email; + $d->city = $city; + $d->subdistrict = $subdistrict; + $d->ward = $ward; + $d->status = 'Active'; + $d->creationdate = $date_now; + if ($admin['user_type'] == 'Agent') { + // Prevent hacking from form + $d->root = $admin['id']; + } else if ($user_type == 'Sales') { + $d->root = $root; + } + $d->save(); + + if ($send_notif == 'wa') { + Message::sendWhatsapp(Lang::phoneFormat($phone), Lang::T('Hello, Your account has been created successfully Portal $username.smartisp.co.ke.') . "\nUsername: $username\nPassword: $password\n\n" . $config['CompanyName']); + } else if ($send_notif == 'sms') { + Message::sendSMS($phone, Lang::T('Hello, Your account has been created successfully.') . "\nUsername: $username\nPassword: $password\n\n" . $config['CompanyName']); + } + + _log('[' . $admin['username'] . ']: ' . "Created $user_type $username", $admin['user_type'], $admin['id']); + r2(U . 'settings/users', 's', Lang::T('Account Created Successfully')); + } else { + r2(U . 'settings/users-add', 'e', $msg); + } + break; + + case 'users-edit-post': + $username = _post('username'); + $fullname = _post('fullname'); + $password = _post('password'); + $cpassword = _post('cpassword'); + $user_type = _post('user_type'); + $phone = _post('phone'); + $email = _post('email'); + $city = _post('city'); + $subdistrict = _post('subdistrict'); + $ward = _post('ward'); + $status = _post('status'); + $root = _post('root'); + $msg = ''; + if (Validator::Length($username, 45, 2) == false) { + $msg .= Lang::T('Username should be between 3 to 45 characters') . '
'; + } + if (Validator::Length($fullname, 45, 2) == false) { + $msg .= Lang::T('Full Name should be between 3 to 45 characters') . '
'; + } + if ($password != '') { + if (!Validator::Length($password, 1000, 5)) { + $msg .= Lang::T('Password should be minimum 6 characters') . '
'; + } + if ($password != $cpassword) { + $msg .= Lang::T('Passwords does not match') . '
'; + } + } + + $id = _post('id'); + if ($admin['id'] == $id) { + $d = ORM::for_table('tbl_users')->find_one($id); + } else { + if ($admin['user_type'] == 'SuperAdmin') { + $d = ORM::for_table('tbl_users')->find_one($id); + } else if ($admin['user_type'] == 'Admin') { + $d = ORM::for_table('tbl_users')->where_any_is([ + ['user_type' => 'Report'], + ['user_type' => 'Agent'], + ['user_type' => 'Sales'] + ])->find_one($id); + } else { + $d = ORM::for_table('tbl_users')->where('root', $admin['id'])->find_one($id); + } + } + if (!$d) { + $msg .= Lang::T('Data Not Found') . '
'; + } + + if ($d['username'] != $username) { + $c = ORM::for_table('tbl_users')->where('username', $username)->find_one(); + if ($c) { + $msg .= "$username " . Lang::T('Account already axist') . '
'; + } + } + run_hook('edit_admin'); #HOOK + if ($msg == '') { + $d->username = $username; + if ($password != '') { + $password = Password::_crypt($password); + $d->password = $password; + } + + $d->fullname = $fullname; + if (($admin['id']) != $id) { + $user_type = _post('user_type'); + $d->user_type = $user_type; + } + $d->phone = $phone; + $d->email = $email; + $d->city = $city; + $d->subdistrict = $subdistrict; + $d->ward = $ward; + if (isset($_POST['status'])) { + $d->status = $status; + } + + if ($admin['user_type'] == 'Agent') { + // Prevent hacking from form + $d->root = $admin['id']; + } else if ($user_type == 'Sales') { + $d->root = $root; + } + + $d->save(); + + _log('[' . $admin['username'] . ']: $username ' . Lang::T('User Updated Successfully'), $admin['user_type'], $admin['id']); + r2(U . 'settings/users', 's', 'User Updated Successfully'); + } else { + r2(U . 'settings/users-edit/' . $id, 'e', $msg); + } + break; + + case 'change-password': + run_hook('view_change_password'); #HOOK + $ui->display('change-password.tpl'); + break; + + case 'change-password-post': + $password = _post('password'); + if ($password != '') { + $d = ORM::for_table('tbl_users')->where('username', $admin['username'])->find_one(); + run_hook('change_password'); #HOOK + if ($d) { + $d_pass = $d['password']; + if (Password::_verify($password, $d_pass) == true) { + $npass = _post('npass'); + $cnpass = _post('cnpass'); + if (!Validator::Length($npass, 15, 5)) { + r2(U . 'settings/change-password', 'e', 'New Password must be 6 to 14 character'); + } + if ($npass != $cnpass) { + r2(U . 'settings/change-password', 'e', 'Both Password should be same'); + } + + $npass = Password::_crypt($npass); + $d->password = $npass; + $d->save(); + + _msglog('s', Lang::T('Password changed successfully, Please login again')); + _log('[' . $admin['username'] . ']: Password changed successfully', $admin['user_type'], $admin['id']); + + r2(U . 'admin'); + } else { + r2(U . 'settings/change-password', 'e', Lang::T('Incorrect Current Password')); + } + } else { + r2(U . 'settings/change-password', 'e', Lang::T('Incorrect Current Password')); + } + } else { + r2(U . 'settings/change-password', 'e', Lang::T('Incorrect Current Password')); + } + break; + + case 'notifications': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + run_hook('view_notifications'); #HOOK + if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . "notifications.json")) { + $ui->assign('_json', json_decode(file_get_contents($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'notifications.json'), true)); + } else { + $ui->assign('_json', json_decode(file_get_contents($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'notifications.default.json'), true)); + } + $ui->assign('_default', json_decode(file_get_contents($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'notifications.default.json'), true)); + $ui->display('app-notifications.tpl'); + break; + case 'notifications-post': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + // Process the new plan-type-specific template structure + $notifications = []; + + // Handle plan-type-specific templates + $plan_specific_keys = ['expired', 'reminder_7_day', 'reminder_3_day', 'reminder_1_day']; + + foreach ($plan_specific_keys as $key) { + if (isset($_POST[$key]) && is_array($_POST[$key])) { + $notifications[$key] = $_POST[$key]; + } else { + // Fallback to old structure for backward compatibility + $notifications[$key] = isset($_POST[$key]) ? $_POST[$key] : ''; + } + } + + // Handle other notification types (balance_send, balance_received, etc.) + $other_keys = ['balance_send', 'balance_received', 'invoice_paid', 'invoice_balance', 'user_registration']; + foreach ($other_keys as $key) { + if (isset($_POST[$key])) { + $notifications[$key] = $_POST[$key]; + } + } + + file_put_contents($UPLOAD_PATH . "/notifications.json", json_encode($notifications, JSON_PRETTY_PRINT)); + r2(U . 'settings/notifications', 's', Lang::T('Settings Saved Successfully')); + break; + + case 'test-notification': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + http_response_code(403); + echo 'Access denied'; + exit; + } + + // Get test parameters + $phone = _post('phone'); + $customer_name = _post('customer_name', 'Test User'); + $package_name = _post('package_name', 'Test Package'); + $price = _post('price', '1000'); + $notification_type = _post('notification_type', 'sms'); + $test_type = _post('test_type', 'expired'); + $plan_type = _post('plan_type', 'hotspot'); + + if (empty($phone)) { + echo 'Error: Phone number is required'; + exit; + } + + // Create test customer data + $test_customer = [ + 'fullname' => $customer_name, + 'username' => 'testuser', + 'phonenumber' => $phone, + 'service_type' => ucfirst($plan_type) + ]; + + // Get the appropriate template + $template = Message::getPlanSpecificTemplate($test_type, $plan_type); + + // Send test notification + $result = Message::sendPackageNotification( + $test_customer, + $package_name, + $price, + $template, + $notification_type, + $plan_type + ); + + echo "Test notification sent successfully!\n"; + echo "Type: " . $test_type . " (" . $plan_type . ")\n"; + echo "Method: " . $notification_type . "\n"; + echo "Phone: " . $phone . "\n"; + echo "Result: " . $result; + break; + + case 'test-cron': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + http_response_code(403); + echo 'Access denied'; + exit; + } + + echo "=== CRON JOB TEST ===\n\n"; + + // Test 1: Check if cron file exists and is readable + $cron_file = $root_path . 'system/cron.php'; + if (file_exists($cron_file)) { + echo "✓ Cron file exists: $cron_file\n"; + } else { + echo "✗ Cron file not found: $cron_file\n"; + } + + // Test 2: Check notification settings + echo "\n=== NOTIFICATION SETTINGS ===\n"; + echo "Expired Notification: " . ($config['user_notification_expired'] ?? 'Not Set') . "\n"; + echo "Reminder Notification: " . ($config['user_notification_reminder'] ?? 'Not Set') . "\n"; + echo "SMS URL: " . ($config['sms_url'] ?? 'Not Set') . "\n"; + echo "WhatsApp URL: " . ($config['wa_url'] ?? 'Not Set') . "\n"; + + // Test 3: Check for expired users + echo "\n=== EXPIRED USERS CHECK ===\n"; + $expired_users = ORM::for_table('tbl_user_recharges') + ->where('status', 'on') + ->where_lt('expiration', date('Y-m-d')) + ->find_many(); + + echo "Found " . count($expired_users) . " expired users\n"; + + if (count($expired_users) > 0) { + echo "\nExpired users:\n"; + foreach ($expired_users as $user) { + $customer = ORM::for_table('tbl_customers')->where('id', $user['customer_id'])->find_one(); + $plan = ORM::for_table('tbl_plans')->where('id', $user['plan_id'])->find_one(); + echo "- " . $customer['fullname'] . " (" . $customer['username'] . ") - " . $plan['name_plan'] . " - Expired: " . $user['expiration'] . "\n"; + } + } + + // Test 4: Check notification templates + echo "\n=== NOTIFICATION TEMPLATES ===\n"; + $notifications_file = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'notifications.json'; + if (file_exists($notifications_file)) { + $notifications = json_decode(file_get_contents($notifications_file), true); + echo "✓ Notifications file exists\n"; + + if (isset($notifications['expired'])) { + if (is_array($notifications['expired'])) { + echo "✓ Expired templates (new format):\n"; + echo " - Hotspot: " . (isset($notifications['expired']['hotspot']) ? 'Set' : 'Missing') . "\n"; + echo " - PPPoE: " . (isset($notifications['expired']['pppoe']) ? 'Set' : 'Missing') . "\n"; + } else { + echo "✓ Expired template (old format): " . (strlen($notifications['expired']) > 0 ? 'Set' : 'Empty') . "\n"; + } + } else { + echo "✗ No expired template found\n"; + } + } else { + echo "✗ Notifications file not found\n"; + } + + // Test 5: Simulate cron execution (dry run) + echo "\n=== SIMULATING CRON EXECUTION ===\n"; + echo "Note: This is a dry run - no actual notifications will be sent\n"; + + $hotspot_expired = ORM::for_table('tbl_user_recharges') + ->where('status', 'on') + ->where_lt('expiration', date('Y-m-d')) + ->find_many(); + + $processed = 0; + foreach ($hotspot_expired as $ds) { + $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 ($c && $p) { + echo "Would process: " . $c['fullname'] . " (" . $c['username'] . ") - " . $p['name_plan'] . " (" . $p['type'] . ")\n"; + echo " Phone: " . $c['phonenumber'] . "\n"; + echo " Notification method: " . ($config['user_notification_expired'] ?? 'Not Set') . "\n"; + $processed++; + } + } + + echo "\nTotal users that would be processed: $processed\n"; + echo "\n=== TEST COMPLETE ===\n"; + break; + case 'dbstatus': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + $dbc = new mysqli($db_host, $db_user, $db_password, $db_name); + if ($result = $dbc->query('SHOW TABLE STATUS')) { + $tables = array(); + while ($row = $result->fetch_array()) { + $tables[$row['Name']]['rows'] = ORM::for_table($row["Name"])->count(); + $tables[$row['Name']]['name'] = $row["Name"]; + } + $ui->assign('tables', $tables); + run_hook('view_database'); #HOOK + $ui->display('dbstatus.tpl'); + } + break; + + case 'dbbackup': + if (!in_array($admin['user_type'], ['SuperAdmin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + $tables = $_POST['tables']; + set_time_limit(-1); + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Type: application/force-download'); + header('Content-Type: application/octet-stream'); + header('Content-Type: application/download'); + header('Content-Disposition: attachment;filename="phpnuxbill_' . count($tables) . '_tables_' . date('Y-m-d_H_i') . '.json"'); + header('Content-Transfer-Encoding: binary'); + $array = []; + foreach ($tables as $table) { + $array[$table] = ORM::for_table($table)->find_array(); + } + echo json_encode($array); + break; + case 'dbrestore': + if (!in_array($admin['user_type'], ['SuperAdmin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + if (file_exists($_FILES['json']['tmp_name'])) { + $suc = 0; + $fal = 0; + $json = json_decode(file_get_contents($_FILES['json']['tmp_name']), true); + try { + ORM::raw_execute("SET FOREIGN_KEY_CHECKS=0;"); + } catch (Throwable $e) { + } catch (Exception $e) { + } + try { + ORM::raw_execute("SET GLOBAL FOREIGN_KEY_CHECKS=0;"); + } catch (Throwable $e) { + } catch (Exception $e) { + } + foreach ($json as $table => $records) { + ORM::raw_execute("TRUNCATE $table;"); + foreach ($records as $rec) { + try { + $t = ORM::for_table($table)->create(); + foreach ($rec as $k => $v) { + $t->set($k, $v); + } + if ($t->save()) { + $suc++; + } else { + $fal++; + } + } catch (Throwable $e) { + $fal++; + } catch (Exception $e) { + $fal++; + } + } + } + try { + ORM::raw_execute("SET FOREIGN_KEY_CHECKS=1;"); + } catch (Throwable $e) { + } catch (Exception $e) { + } + try { + ORM::raw_execute("SET GLOBAL FOREIGN_KEY_CHECKS=1;"); + } catch (Throwable $e) { + } catch (Exception $e) { + } + if (file_exists($_FILES['json']['tmp_name'])) unlink($_FILES['json']['tmp_name']); + r2(U . "settings/dbstatus", 's', "Restored $suc success $fal failed"); + } else { + r2(U . "settings/dbstatus", 'e', 'Upload failed'); + } + break; + case 'language': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + run_hook('view_add_language'); #HOOK + if (file_exists($lan_file)) { + $ui->assign('langs', json_decode(file_get_contents($lan_file), true)); + } else { + $ui->assign('langs', []); + } + $ui->display('language-add.tpl'); + break; + + case 'lang-post': + file_put_contents($lan_file, json_encode($_POST, JSON_PRETTY_PRINT)); + r2(U . 'settings/language', 's', Lang::T('Translation saved Successfully')); + break; + + case 'maintenance': + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + exit; + } + if (_post('save') == 'save') { + $status = isset($_POST['maintenance_mode']) ? 1 : 0; // Checkbox returns 1 if checked, otherwise 0 + $force_logout = isset($_POST['maintenance_mode_logout']) ? 1 : 0; // Checkbox returns 1 if checked, otherwise 0 + $date = isset($_POST['maintenance_date']) ? $_POST['maintenance_date'] : null; + + $settings = [ + 'maintenance_mode' => $status, + 'maintenance_mode_logout' => $force_logout, + 'maintenance_date' => $date + ]; + + foreach ($settings as $key => $value) { + $d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one(); + if ($d) { + $d->value = $value; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = $key; + $d->value = $value; + $d->save(); + } + } + + r2(U . "settings/maintenance", 's', Lang::T('Settings Saved Successfully')); + } + $ui->assign('_c', $config); + $ui->assign('_title', Lang::T('Maintenance Mode Settings')); + $ui->display('maintenance-mode.tpl'); + break; + + default: + $ui->display('a404.tpl'); +} diff --git a/system/controllers/sms.php b/system/controllers/sms.php new file mode 100644 index 0000000..c0eb06d --- /dev/null +++ b/system/controllers/sms.php @@ -0,0 +1,26 @@ +assign('_title', Lang::T('SMS')); +$ui->assign('_system_menu', 'sms'); +$ui->assign('_admin', $admin); + +// Fetch balance from the database +$balance_row = ORM::for_table('balance')->find_one(); +$balance = $balance_row ? $balance_row->amount : 0; + +// Set balance in template +$ui->assign('balance', $balance); + +// Display the SMS rate +$sms_rate = 0.40; // Set SMS rate +$ui->assign('sms_rate', $sms_rate); + + // Display the template + $ui->display('sms.tpl'); +} +?> diff --git a/system/controllers/update_balance.php b/system/controllers/update_balance.php new file mode 100644 index 0000000..2944e5f --- /dev/null +++ b/system/controllers/update_balance.php @@ -0,0 +1,50 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Get the paid amount from the POST request + $amount = isset($_POST['amount']) ? $_POST['amount'] : 0; + + // Update the balance in the database + $stmt = $db->prepare("UPDATE balance SET sms_balance = sms_balance + :amount"); + $stmt->bindParam(':amount', $amount, PDO::PARAM_INT); + $stmt->execute(); + + // Check if the update was successful + $rowCount = $stmt->rowCount(); + if ($rowCount > 0) { + // Success message + $response['status'] = 'success'; + $response['message'] = "Balance updated successfully. Paid amount: {$amount}"; + } else { + // Error message if no rows were affected + $response['status'] = 'error'; + $response['message'] = "Failed to update balance."; + } +} catch (PDOException $e) { + // Display any database errors + $response['status'] = 'error'; + $response['message'] = "Error: " . $e->getMessage(); +} + +// Close the database connection +$db = null; + +// Return JSON response +echo json_encode($response); +?> \ No newline at end of file diff --git a/system/controllers/verify_payment.php b/system/controllers/verify_payment.php new file mode 100644 index 0000000..18a57e7 --- /dev/null +++ b/system/controllers/verify_payment.php @@ -0,0 +1,70 @@ +assign('_title', Lang::T('Payment Verification')); +$ui->assign('_system_menu', 'sms'); +$ui->assign('_admin', $admin); + +if (isset($_GET['reference'])) { + $reference = $_GET['reference']; + + $paystack_secret_key = 'sk_live_96e0fc93ffc118bef55d81438d569f9f704d1199'; // Replace with your Paystack secret key + + $curl = curl_init(); + curl_setopt_array($curl, [ + CURLOPT_URL => "https://api.paystack.co/transaction/verify/" . rawurlencode($reference), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + "authorization: Bearer $paystack_secret_key", + "content-type: application/json", + "cache-control: no-cache" + ], + ]); + + $response = curl_exec($curl); + $err = curl_error($curl); + curl_close($curl); + + if ($err) { + r2(U . 'sms.php', 'e', Lang::T('Curl returned error: ' . $err)); + } else { + $tranx = json_decode($response, true); + + if (!$tranx['status']) { + r2(U . 'sms.php', 'e', Lang::T('API returned error: ' . $tranx['message'])); + } else { + if ($tranx['data']['status'] == 'success') { + // Payment was successful + $email = $tranx['data']['customer']['email']; + $amount = $tranx['data']['amount'] / 100; // Amount in KES + $phone = $_POST['phone']; // Retrieve phone number from session or form data + + // Update balance in the database + $balance_row = ORM::for_table('balance')->find_one(); + $balance_row->amount += $amount; + $balance_row->save(); + + // Send SMS notification + $message = "Your SMS top-up of " . $amount . " KES was successful. Thank you!"; + $url = "https://api.netguru.co.ke/bytewave.php?message=" . urlencode($message) . "&phone=" . urlencode($phone) . "&senderid=Lineserve&api=60|wMNX8WcxrIuRAal5LseGW8ixwGlMljl4nIrMQpN5"; + + $smsResponse = file_get_contents($url); + + if ($smsResponse) { + r2(U . 'sms.php', 's', Lang::T('Payment Successful! Your SMS balance has been updated successfully!')); + } else { + r2(U . 'sms.php', 'e', Lang::T('Failed to send SMS. Please try again later.')); + } + } else { + r2(U . 'sms.php', 'e', Lang::T('Payment verification failed.')); + } + } + } +} else { + r2(U . 'sms.php', 'e', Lang::T('No reference supplied.')); +} +?> diff --git a/system/controllers/voucher.php b/system/controllers/voucher.php new file mode 100644 index 0000000..8abcd23 --- /dev/null +++ b/system/controllers/voucher.php @@ -0,0 +1,67 @@ +assign('_title', Lang::T('Voucher')); +$ui->assign('_system_menu', 'voucher'); + +$action = $routes['1']; +$user = User::_info(); +$ui->assign('_user', $user); + +require_once 'system/autoload/PEAR2/Autoload.php'; + +switch ($action) { + + case 'activation': + run_hook('view_activate_voucher'); #HOOK + $ui->display('user-activation.tpl'); + break; + + case 'activation-post': + $code = _post('code'); + $v1 = ORM::for_table('tbl_voucher')->where('code', $code)->where('status', 0)->find_one(); + run_hook('customer_activate_voucher'); #HOOK + if ($v1) { + if (Package::rechargeUser($user['id'], $v1['routers'], $v1['id_plan'], "Voucher", $code)) { + $v1->status = "1"; + $v1->user = $user['username']; + $v1->save(); + r2(U . "voucher/list-activated", 's', Lang::T('Activation Vouchers Successfully')); + } else { + r2(U . 'voucher/activation', 'e', "Failed to refill account"); + } + } else { + r2(U . 'voucher/activation', 'e', Lang::T('Voucher Not Valid')); + } + break; + + case 'list-activated': + $ui->assign('_system_menu', 'list-activated'); + $query = ORM::for_table('tbl_transactions')->where('username', $user['username'])->order_by_desc('id'); + $d = Paginator::findMany($query); + + $ui->assign('d', $d); + run_hook('customer_view_activation_list'); #HOOK + $ui->display('user-activation-list.tpl'); + + break; + case 'invoice': + $id = $routes[2]; + if(empty($id)){ + $in = ORM::for_table('tbl_transactions')->where('username', $user['username'])->order_by_desc('id')->find_one(); + }else{ + $in = ORM::for_table('tbl_transactions')->where('username', $user['username'])->where('id', $id)->find_one(); + } + if($in){ + Package::createInvoice($in); + $ui->display('invoice-customer.tpl'); + }else{ + r2(U . 'voucher/list-activated', 'e', Lang::T('Not Found')); + } + break; + default: + $ui->display('a404.tpl'); +}