+
+
' . $config['CompanyName'] . '
+ ' . $config['address'] . '
+ ' . Lang::T('Phone Number') . ': ' . $config['phone'] . '
+
+
+
+
+ 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'] . '
+
+
+
+
+
+
+ | ' . 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') . ' |
+
';
+ $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 .= "" . "
+ | $username |
+ $plan_name |
+ $type |
+ $price |
+ $recharged_on |
+ $expiration $time |
+ $method |
+ $routers |
+
";
+ }
+ $html .= '
+ ' . 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');
+}