diff --git a/system/controllers/callback.php b/system/controllers/callback.php
new file mode 100644
index 0000000..352f2eb
--- /dev/null
+++ b/system/controllers/callback.php
@@ -0,0 +1,22 @@
+assign('_title', 'CodeCanyon.net');
+$ui->assign('_system_menu', 'settings');
+
+$plugin_repository = 'https://hotspotbilling.github.io/Plugin-Repository/repository.json';
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+$cache = File::pathFixer($CACHE_PATH . '/codecanyon.json');
+
+if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+}
+if (empty($config['envato_token'])) {
+ r2(U . 'settings/app', 'w', 'Envato Personal Access Token is not set');
+}
+
+switch ($action) {
+
+ case 'install':
+ if (!is_writeable(File::pathFixer($CACHE_PATH . '/'))) {
+ r2(U . "codecanyon", 'e', 'Folder system/cache/ is not writable');
+ }
+ if (!is_writeable($PLUGIN_PATH)) {
+ r2(U . "codecanyon", 'e', 'Folder plugin/ is not writable');
+ }
+ if (!is_writeable($PAYMENTGATEWAY_PATH)) {
+ r2(U . "codecanyon", 'e', 'Folder paymentgateway/ is not writable');
+ }
+ set_time_limit(-1);
+ $item_id = $routes['2'];
+ $tipe = $routes['3'];
+ $result = Http::getData('https://api.envato.com/v3/market/buyer/download?item_id=' . $item_id, ['Authorization: Bearer ' . $config['envato_token']]);
+ $json = json_decode($result, true);
+ if (!isset($json['download_url'])) {
+ r2(U . 'codecanyon', 'e', 'Failed to get download url. ' . $json['description']);
+ }
+ $file = File::pathFixer($CACHE_PATH . '/codecanyon/');
+ if (!file_exists($file)) {
+ mkdir($file);
+ }
+ $file .= $item_id . '.zip';
+ if (file_exists($file))
+ unlink($file);
+ //download
+ $fp = fopen($file, 'w+');
+ $ch = curl_init($json['download_url']);
+ curl_setopt($ch, CURLOPT_POST, 0);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 120);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 120);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_exec($ch);
+ curl_close($ch);
+ fclose($fp);
+ //extract
+ $target = File::pathFixer($CACHE_PATH . '/codecanyon/' . $item_id . '/');
+ $zip = new ZipArchive();
+ $zip->open($file);
+ $zip->extractTo($target);
+ $zip->close();
+ //moving
+ if (file_exists($target . 'plugin')) {
+ File::copyFolder($target . 'plugin', $PLUGIN_PATH . DIRECTORY_SEPARATOR);
+ } else if (file_exists($target . 'paymentgateway')) {
+ File::copyFolder($target . 'paymentgateway', $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR);
+ } else if (file_exists($target . 'theme')) {
+ File::copyFolder($target . 'theme', File::pathFixer('ui/themes/'));
+ }
+ //Cleaning
+ File::deleteFolder($target);
+ unlink($file);
+ r2(U . "codecanyon", 's', 'Installation success');
+ case 'reload':
+ if (file_exists($cache))
+ unlink($cache);
+ default:
+ if (class_exists('ZipArchive')) {
+ $zipExt = true;
+ } else {
+ $zipExt = false;
+ }
+ $ui->assign('zipExt', $zipExt);
+
+ if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) {
+ $txt = file_get_contents($cache);
+ $plugins = json_decode($txt, true);
+ $ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60)));
+ if (count($plugins) == 0) {
+ unlink($cache);
+ r2(U . 'codecanyon');
+ }
+ } else {
+ $plugins = [];
+ $page = _get('page', 1);
+ back:
+ $result = Http::getData('https://api.envato.com/v3/market/buyer/list-purchases?&page=' . $page, ['Authorization: Bearer ' . $config['envato_token']]);
+ $items = json_decode($result, true);
+ if ($items && count($items['results']) > 0) {
+ foreach ($items['results'] as $item) {
+ $name = strtolower($item['item']['name']);
+ if (strpos($name, 'phpnuxbill') !== false) {
+ $plugins[] = $item;
+ }
+ }
+ $page++;
+ goto back;
+ }
+ if (count($plugins) > 0) {
+ file_put_contents($cache, json_encode($plugins));
+ if (file_exists($cache)) {
+ $ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60)));
+ }
+ }
+ }
+ $ui->assign('plugins', $plugins);
+ $ui->display('codecanyon.tpl');
+}
diff --git a/system/controllers/community.php b/system/controllers/community.php
new file mode 100644
index 0000000..df60c08
--- /dev/null
+++ b/system/controllers/community.php
@@ -0,0 +1,26 @@
+assign('_title', 'Community');
+$ui->assign('_system_menu', 'community');
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+switch ($action) {
+ case 'rollback':
+ $ui->assign('_title', 'Rollback Update');
+ $masters = json_decode(Http::getData("https://api.github.com/repos/hotspotbilling/phpnuxbill/commits?per_page=100",['User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0']), true);
+ $devs = json_decode(Http::getData("https://api.github.com/repos/hotspotbilling/phpnuxbill/commits?sha=Development&per_page=100",['User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0']), true);
+
+ $ui->assign('masters', $masters);
+ $ui->assign('devs', $devs);
+ $ui->display('community-rollback.tpl');
+ break;
+ default:
+ $ui->display('community.tpl');
+}
\ No newline at end of file
diff --git a/system/controllers/customers.php b/system/controllers/customers.php
new file mode 100644
index 0000000..93477f8
--- /dev/null
+++ b/system/controllers/customers.php
@@ -0,0 +1,705 @@
+assign('_title', Lang::T('Customer'));
+$ui->assign('_system_menu', 'customers');
+
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+if (empty($action)) {
+ $action = 'list';
+}
+
+$leafletpickerHeader = <<
+EOT;
+
+switch ($action) {
+ case 'csv':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+
+ $cs = ORM::for_table('tbl_customers')
+ ->select('tbl_customers.id', 'id')
+ ->select('tbl_customers.username', 'username')
+ ->select('fullname')
+ ->select('address')
+ ->select('phonenumber')
+ ->select('email')
+ ->select('balance')
+ ->select('service_type')
+ ->order_by_asc('tbl_customers.id')
+ ->find_array();
+
+ $h = false;
+ set_time_limit(-1);
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header("Content-type: text/csv");
+ header('Content-Disposition: attachment;filename="phpnuxbill_customers_' . date('Y-m-d_H_i') . '.csv"');
+ header('Content-Transfer-Encoding: binary');
+
+ $headers = [
+ 'id',
+ 'username',
+ 'fullname',
+ 'address',
+ 'phonenumber',
+ 'email',
+ 'balance',
+ 'service_type',
+ ];
+
+ if (!$h) {
+ echo '"' . implode('","', $headers) . "\"\n";
+ $h = true;
+ }
+
+ foreach ($cs as $c) {
+ $row = [
+ $c['id'],
+ $c['username'],
+ $c['fullname'],
+ $c['address'],
+ $c['phonenumber'],
+ $c['email'],
+ $c['balance'],
+ $c['service_type'],
+ ];
+ echo '"' . implode('","', $row) . "\"\n";
+ }
+ break;
+ //case csv-prepaid can be moved later to (plan.php) php file dealing with prepaid users
+ case 'csv-prepaid':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+
+ $cs = ORM::for_table('tbl_customers')
+ ->select('tbl_customers.id', 'id')
+ ->select('tbl_customers.username', 'username')
+ ->select('fullname')
+ ->select('address')
+ ->select('phonenumber')
+ ->select('email')
+ ->select('balance')
+ ->select('service_type')
+ ->select('namebp')
+ ->select('routers')
+ ->select('status')
+ ->select('method', 'Payment')
+ ->left_outer_join('tbl_user_recharges', array('tbl_customers.id', '=', 'tbl_user_recharges.customer_id'))
+ ->order_by_asc('tbl_customers.id')
+ ->find_array();
+
+ $h = false;
+ set_time_limit(-1);
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header("Content-type: text/csv");
+ header('Content-Disposition: attachment;filename="phpnuxbill_prepaid_users' . date('Y-m-d_H_i') . '.csv"');
+ header('Content-Transfer-Encoding: binary');
+
+ $headers = [
+ 'id',
+ 'username',
+ 'fullname',
+ 'address',
+ 'phonenumber',
+ 'email',
+ 'balance',
+ 'service_type',
+ 'namebp',
+ 'routers',
+ 'status',
+ 'Payment'
+ ];
+
+ if (!$h) {
+ echo '"' . implode('","', $headers) . "\"\n";
+ $h = true;
+ }
+
+ foreach ($cs as $c) {
+ $row = [
+ $c['id'],
+ $c['username'],
+ $c['fullname'],
+ $c['address'],
+ $c['phonenumber'],
+ $c['email'],
+ $c['balance'],
+ $c['service_type'],
+ $c['namebp'],
+ $c['routers'],
+ $c['status'],
+ $c['Payment']
+ ];
+ echo '"' . implode('","', $row) . "\"\n";
+ }
+ break;
+ case 'add':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+ $ui->assign('xheader', $leafletpickerHeader);
+ run_hook('view_add_customer'); #HOOK
+ $ui->display('customers-add.tpl');
+ break;
+ case 'recharge':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+ $id_customer = $routes['2'];
+ $plan_id = $routes['3'];
+ $b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one();
+ if ($b) {
+ $gateway = 'Recharge';
+ $channel = $admin['fullname'];
+ $cust = User::_info($id_customer);
+ $plan = ORM::for_table('tbl_plans')->find_one($b['plan_id']);
+ list($bills, $add_cost) = User::getBills($id_customer);
+ if ($using == 'balance' && $config['enable_balance'] == 'yes') {
+ if (!$cust) {
+ r2(U . 'plan/recharge', 'e', Lang::T('Customer not found'));
+ }
+ if (!$plan) {
+ r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
+ }
+ if ($cust['balance'] < ($plan['price'] + $add_cost)) {
+ r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
+ }
+ $gateway = 'Recharge Balance';
+ }
+ if ($using == 'zero') {
+ $zero = 1;
+ $gateway = 'Recharge Zero';
+ }
+ $usings = explode(',', $config['payment_usings']);
+ $usings = array_filter(array_unique($usings));
+ if (count($usings) == 0) {
+ $usings[] = Lang::T('Cash');
+ }
+ $ui->assign('usings', $usings);
+ $ui->assign('bills', $bills);
+ $ui->assign('add_cost', $add_cost);
+ $ui->assign('cust', $cust);
+ $ui->assign('gateway', $gateway);
+ $ui->assign('channel', $channel);
+ $ui->assign('server', $b['routers']);
+ $ui->assign('plan', $plan);
+ $ui->display('recharge-confirm.tpl');
+ } else {
+ r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
+ }
+ break;
+ case 'deactivate':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+ $id_customer = $routes['2'];
+ $plan_id = $routes['3'];
+ $b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one();
+ if ($b) {
+ $p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->find_one();
+ if ($p) {
+ if ($p['is_radius']) {
+ Radius::customerDeactivate($b['username']);
+ } else {
+ $mikrotik = Mikrotik::info($b['routers']);
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ if ($b['type'] == 'Hotspot') {
+ Mikrotik::removeHotspotUser($client, $b['username']);
+ Mikrotik::removeHotspotActiveUser($client, $b['username']);
+ } else if ($b['type'] == 'PPPOE') {
+ Mikrotik::removePpoeUser($client, $b['username']);
+ Mikrotik::removePpoeActive($client, $b['username']);
+ }
+ }
+ $b->status = 'off';
+ $b->expiration = date('Y-m-d');
+ $b->time = date('H:i:s');
+ $b->save();
+ _log('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for ' . $b['username'], 'User', $b['customer_id']);
+ Message::sendTelegram('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for u' . $b['username']);
+ r2(U . 'customers/view/' . $id_customer, 's', 'Success deactivate customer to Mikrotik');
+ }
+ }
+ r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
+ break;
+ case 'sync':
+ $id_customer = $routes['2'];
+ $bs = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('status', 'on')->findMany();
+ if ($bs) {
+ $routers = [];
+ foreach ($bs as $b) {
+ $c = ORM::for_table('tbl_customers')->find_one($id_customer);
+ $p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->where('enabled', '1')->find_one();
+ if ($p) {
+ $routers[] = $b['routers'];
+ if ($p['is_radius']) {
+ Radius::customerAddPlan($c, $p, $p['expiration'] . ' ' . $p['time']);
+ } else {
+ $mikrotik = Mikrotik::info($b['routers']);
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ if ($b['type'] == 'Hotspot') {
+ Mikrotik::addHotspotUser($client, $p, $c);
+ } else if ($b['type'] == 'PPPOE') {
+ Mikrotik::addPpoeUser($client, $p, $c);
+ }
+ }
+ }
+ }
+ r2(U . 'customers/view/' . $id_customer, 's', 'Sync success to ' . implode(", ", $routers));
+ }
+ r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
+ break;
+ case 'viewu':
+ $customer = ORM::for_table('tbl_customers')->where('username', $routes['2'])->find_one();
+ case 'view':
+ $id = $routes['2'];
+ run_hook('view_customer'); #HOOK
+ if (!$customer) {
+ $customer = ORM::for_table('tbl_customers')->find_one($id);
+ }
+ if ($customer) {
+
+
+ // Fetch the Customers Attributes values from the tbl_customer_custom_fields table
+ $customFields = ORM::for_table('tbl_customers_fields')
+ ->where('customer_id', $customer['id'])
+ ->find_many();
+ $v = $routes['3'];
+ if (empty($v)) {
+ $v = 'activation';
+ }
+ if ($v == 'order') {
+ $v = 'order';
+ $query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id');
+ $order = Paginator::findMany($query);
+ $ui->assign('order', $order);
+ } else if ($v == 'activation') {
+ $query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id');
+ $activation = Paginator::findMany($query);
+ $ui->assign('activation', $activation);
+ }
+ $ui->assign('packages', User::_billing($customer['id']));
+ $ui->assign('v', $v);
+ $ui->assign('d', $customer);
+ $ui->assign('customFields', $customFields);
+ $ui->assign('xheader', $leafletpickerHeader);
+ $ui->display('customers-view.tpl');
+ } else {
+ r2(U . 'customers/list', 'e', Lang::T('Account Not Found'));
+ }
+ break;
+ case 'edit':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+ $id = $routes['2'];
+ run_hook('edit_customer'); #HOOK
+ $d = ORM::for_table('tbl_customers')->find_one($id);
+ // Fetch the Customers Attributes values from the tbl_customers_fields table
+ $customFields = ORM::for_table('tbl_customers_fields')
+ ->where('customer_id', $id)
+ ->find_many();
+ if ($d) {
+ $ui->assign('d', $d);
+ $ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status"));
+ $ui->assign('customFields', $customFields);
+ $ui->assign('xheader', $leafletpickerHeader);
+ $ui->display('customers-edit.tpl');
+ } else {
+ r2(U . 'customers/list', 'e', Lang::T('Account Not Found'));
+ }
+ break;
+
+ case 'delete':
+ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
+ _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
+ }
+ $id = $routes['2'];
+ run_hook('delete_customer'); #HOOK
+ $d = ORM::for_table('tbl_customers')->find_one($id);
+ if ($d) {
+ // Delete the associated Customers Attributes records from tbl_customer_custom_fields table
+ ORM::for_table('tbl_customers_fields')->where('customer_id', $id)->delete_many();
+ $c = ORM::for_table('tbl_user_recharges')->where('username', $d['username'])->find_one();
+ if ($c) {
+ $p = ORM::for_table('tbl_plans')->find_one($c['plan_id']);
+ if ($p['is_radius']) {
+ Radius::customerDelete($d['username']);
+ } else {
+ $mikrotik = Mikrotik::info($c['routers']);
+ if ($c['type'] == 'Hotspot') {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removeHotspotUser($client, $d['username']);
+ Mikrotik::removeHotspotActiveUser($client, $d['username']);
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::removePpoeUser($client, $d['username']);
+ Mikrotik::removePpoeActive($client, $d['username']);
+ }
+ try {
+ $d->delete();
+ } catch (Exception $e) {
+ } catch (Throwable $e) {
+ }
+ try {
+ $c->delete();
+ } catch (Exception $e) {
+ }
+ }
+ } else {
+ try {
+ $d->delete();
+ } catch (Exception $e) {
+ } catch (Throwable $e) {
+ }
+ try {
+ if ($c)
+ $c->delete();
+ } catch (Exception $e) {
+ } catch (Throwable $e) {
+ }
+ }
+
+ r2(U . 'customers/list', 's', Lang::T('User deleted Successfully'));
+ }
+ break;
+
+ case 'add-post':
+ $username = _post('username');
+ $fullname = _post('fullname');
+ $password = _post('password');
+ $pppoe_password = _post('pppoe_password');
+ $email = _post('email');
+ $address = _post('address');
+ $phonenumber = _post('phonenumber');
+ $service_type = _post('service_type');
+ $account_type = _post('account_type');
+ $coordinates = _post('coordinates');
+ //post Customers Attributes
+ $custom_field_names = (array) $_POST['custom_field_name'];
+ $custom_field_values = (array) $_POST['custom_field_value'];
+ //additional information
+ $city = _post('city');
+ $district = _post('district');
+ $state = _post('state');
+ $zip = _post('zip');
+
+ run_hook('add_customer'); #HOOK
+ $msg = '';
+ if (Validator::Length($username, 35, 2) == false) {
+ $msg .= 'Username should be between 3 to 55 characters' . '
';
+ }
+ if (Validator::Length($fullname, 36, 2) == false) {
+ $msg .= 'Full Name should be between 3 to 25 characters' . '
';
+ }
+ if (!Validator::Length($password, 36, 2)) {
+ $msg .= 'Password should be between 3 to 35 characters' . '
';
+ }
+
+ $d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
+ if ($d) {
+ $msg .= Lang::T('Account already axist') . '
';
+ }
+
+ if ($msg == '') {
+ $d = ORM::for_table('tbl_customers')->create();
+ $d->username = Lang::phoneFormat($username);
+ $d->password = $password;
+ $d->pppoe_password = $pppoe_password;
+ $d->email = $email;
+ $d->account_type = $account_type;
+ $d->fullname = $fullname;
+ $d->address = $address;
+ $d->created_by = $admin['id'];
+ $d->phonenumber = Lang::phoneFormat($phonenumber);
+ $d->service_type = $service_type;
+ $d->coordinates = $coordinates;
+ $d->city = $city;
+ $d->district = $district;
+ $d->state = $state;
+ $d->zip = $zip;
+ $d->save();
+
+ // Retrieve the customer ID of the newly created customer
+ $customerId = $d->id();
+ // Save Customers Attributes details
+ if (!empty($custom_field_names) && !empty($custom_field_values)) {
+ $totalFields = min(count($custom_field_names), count($custom_field_values));
+ for ($i = 0; $i < $totalFields; $i++) {
+ $name = $custom_field_names[$i];
+ $value = $custom_field_values[$i];
+
+ if (!empty($name)) {
+ $customField = ORM::for_table('tbl_customers_fields')->create();
+ $customField->customer_id = $customerId;
+ $customField->field_name = $name;
+ $customField->field_value = $value;
+ $customField->save();
+ }
+ }
+ }
+ r2(U . 'customers/list', 's', Lang::T('Account Created Successfully'));
+ } else {
+ r2(U . 'customers/add', 'e', $msg);
+ }
+ break;
+
+ case 'edit-post':
+ $username = Lang::phoneFormat(_post('username'));
+ $fullname = _post('fullname');
+ $account_type = _post('account_type');
+ $password = _post('password');
+ $pppoe_password = _post('pppoe_password');
+ $email = _post('email');
+ $address = _post('address');
+ $phonenumber = Lang::phoneFormat(_post('phonenumber'));
+ $service_type = _post('service_type');
+ $coordinates = _post('coordinates');
+ $status = _post('status');
+ //additional information
+ $city = _post('city');
+ $district = _post('district');
+ $state = _post('state');
+ $zip = _post('zip');
+ run_hook('edit_customer'); #HOOK
+ $msg = '';
+ if (Validator::Length($username, 35, 2) == false) {
+ $msg .= 'Username should be between 3 to 15 characters' . '
';
+ }
+ if (Validator::Length($fullname, 36, 1) == false) {
+ $msg .= 'Full Name should be between 2 to 25 characters' . '
';
+ }
+ if ($password != '') {
+ if (!Validator::Length($password, 36, 2)) {
+ $msg .= 'Password should be between 3 to 15 characters' . '
';
+ }
+ }
+
+ $id = _post('id');
+ $d = ORM::for_table('tbl_customers')->find_one($id);
+
+ //lets find user Customers Attributes using id
+ $customFields = ORM::for_table('tbl_customers_fields')
+ ->where('customer_id', $id)
+ ->find_many();
+
+ if (!$d) {
+ $msg .= Lang::T('Data Not Found') . '
';
+ }
+
+ $oldusername = $d['username'];
+ $oldPppoePassword = $d['password'];
+ $oldPassPassword = $d['pppoe_password'];
+ $userDiff = false;
+ $pppoeDiff = false;
+ $passDiff = false;
+ if ($oldusername != $username) {
+ $c = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
+ if ($c) {
+ $msg .= Lang::T('Account already exist') . '
';
+ }
+ $userDiff = true;
+ }
+ if ($oldPppoePassword != $pppoe_password) {
+ $pppoeDiff = true;
+ }
+ if ($password != '' && $oldPassPassword != $password) {
+ $passDiff = true;
+ }
+
+ if ($msg == '') {
+ if ($userDiff) {
+ $d->username = $username;
+ }
+ if ($password != '') {
+ $d->password = $password;
+ }
+ $d->pppoe_password = $pppoe_password;
+ $d->fullname = $fullname;
+ $d->email = $email;
+ $d->account_type = $account_type;
+ $d->address = $address;
+ $d->status = $status;
+ $d->phonenumber = $phonenumber;
+ $d->service_type = $service_type;
+ $d->coordinates = $coordinates;
+ $d->city = $city;
+ $d->district = $district;
+ $d->state = $state;
+ $d->zip = $zip;
+ $d->save();
+
+
+ // Update Customers Attributes values in tbl_customers_fields table
+ foreach ($customFields as $customField) {
+ $fieldName = $customField['field_name'];
+ if (isset($_POST['custom_fields'][$fieldName])) {
+ $customFieldValue = $_POST['custom_fields'][$fieldName];
+ $customField->set('field_value', $customFieldValue);
+ $customField->save();
+ }
+ }
+
+ // Add new Customers Attributess
+ if (isset($_POST['custom_field_name']) && isset($_POST['custom_field_value'])) {
+ $newCustomFieldNames = $_POST['custom_field_name'];
+ $newCustomFieldValues = $_POST['custom_field_value'];
+
+ // Check if the number of field names and values match
+ if (count($newCustomFieldNames) == count($newCustomFieldValues)) {
+ $numNewFields = count($newCustomFieldNames);
+
+ for ($i = 0; $i < $numNewFields; $i++) {
+ $fieldName = $newCustomFieldNames[$i];
+ $fieldValue = $newCustomFieldValues[$i];
+
+ // Insert the new Customers Attributes
+ $newCustomField = ORM::for_table('tbl_customers_fields')->create();
+ $newCustomField->set('customer_id', $id);
+ $newCustomField->set('field_name', $fieldName);
+ $newCustomField->set('field_value', $fieldValue);
+ $newCustomField->save();
+ }
+ }
+ }
+
+ // Delete Customers Attributess
+ if (isset($_POST['delete_custom_fields'])) {
+ $fieldsToDelete = $_POST['delete_custom_fields'];
+ foreach ($fieldsToDelete as $fieldName) {
+ // Delete the Customers Attributes with the given field name
+ ORM::for_table('tbl_customers_fields')
+ ->where('field_name', $fieldName)
+ ->where('customer_id', $id)
+ ->delete_many();
+ }
+ }
+
+ if ($userDiff || $pppoeDiff || $passDiff) {
+ $c = ORM::for_table('tbl_user_recharges')->where('username', ($userDiff) ? $oldusername : $username)->find_one();
+ if ($c) {
+ $c->username = $username;
+ $c->save();
+ $p = ORM::for_table('tbl_plans')->find_one($c['plan_id']);
+ if ($p['is_radius']) {
+ if ($userDiff) {
+ Radius::customerChangeUsername($oldusername, $username);
+ }
+ Radius::customerAddPlan($d, $p, $p['expiration'] . ' ' . $p['time']);
+ } else {
+ $mikrotik = Mikrotik::info($c['routers']);
+ if ($c['type'] == 'Hotspot') {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ Mikrotik::setHotspotUser($client, $c['username'], $password);
+ Mikrotik::removeHotspotActiveUser($client, $d['username']);
+ } else {
+ $client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
+ if (!empty($d['pppoe_password'])) {
+ Mikrotik::setPpoeUser($client, $c['username'], $d['pppoe_password']);
+ } else {
+ Mikrotik::setPpoeUser($client, $c['username'], $password);
+ }
+ Mikrotik::removePpoeActive($client, $d['username']);
+ }
+ }
+ }
+ }
+ r2(U . 'customers/view/' . $id, 's', 'User Updated Successfully');
+ } else {
+ r2(U . 'customers/edit/' . $id, 'e', $msg);
+ }
+ break;
+
+ default:
+ run_hook('list_customers'); #HOOK
+ $search = _post('search');
+ $order = _post('order', 'username');
+ $filter = _post('filter', 'Active');
+ $orderby = _post('orderby', 'asc');
+ $order_pos = [
+ 'username' => 0,
+ 'created_at' => 8,
+ 'balance' => 3,
+ 'status' => 7
+ ];
+
+ if ($search != '') {
+ $query = ORM::for_table('tbl_customers')
+ ->whereRaw("username LIKE '%$search%' OR fullname LIKE '%$search%' OR address LIKE '%$search%' " .
+ "OR phonenumber LIKE '%$search%' OR email LIKE '%$search%' AND status='$filter'");
+ } else {
+ $query = ORM::for_table('tbl_customers');
+ $query->where("status", $filter);
+ }
+ if ($orderby == 'asc') {
+ $query->order_by_asc($order);
+ } else {
+ $query->order_by_desc($order);
+ }
+ $d = $query->findMany();
+ if (_post('export', '') == 'csv') {
+ $h = false;
+ set_time_limit(-1);
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header("Content-type: text/csv");
+ header('Content-Disposition: attachment;filename="phpnuxbill_customers_' . $filter . '_' . date('Y-m-d_H_i') . '.csv"');
+ header('Content-Transfer-Encoding: binary');
+
+ $headers = [
+ 'id',
+ 'username',
+ 'fullname',
+ 'address',
+ 'phonenumber',
+ 'email',
+ 'balance',
+ 'service_type',
+ ];
+ $fp = fopen('php://output', 'wb');
+ if (!$h) {
+ fputcsv($fp, $headers, ";");
+ $h = true;
+ }
+ foreach ($d as $c) {
+ $row = [
+ $c['id'],
+ $c['username'],
+ $c['fullname'],
+ str_replace("\n", " ", $c['address']),
+ $c['phonenumber'],
+ $c['email'],
+ $c['balance'],
+ $c['service_type'],
+ ];
+ fputcsv($fp, $row, ";");
+ }
+ fclose($fp);
+ die();
+ }
+ $ui->assign('xheader', '');
+ $ui->assign('d', $d);
+ $ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status"));
+ $ui->assign('filter', $filter);
+ $ui->assign('search', $search);
+ $ui->assign('order', $order);
+ $ui->assign('order_pos', $order_pos[$order]);
+ $ui->assign('orderby', $orderby);
+ $ui->display('customers.tpl');
+ break;
+}
diff --git a/system/controllers/dashboard.php b/system/controllers/dashboard.php
new file mode 100644
index 0000000..ad8afe3
--- /dev/null
+++ b/system/controllers/dashboard.php
@@ -0,0 +1,204 @@
+assign('_title', Lang::T('Dashboard'));
+$ui->assign('_admin', $admin);
+
+if(isset($_GET['refresh'])){
+ $files = scandir($CACHE_PATH);
+ foreach ($files as $file) {
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') {
+ unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file);
+ }
+ }
+ r2(U . 'dashboard', 's', 'Data Refreshed');
+}
+
+$fdate = date('Y-m-01');
+$tdate = date('Y-m-t');
+//first day of month
+$first_day_month = date('Y-m-01');
+$mdate = date('Y-m-d');
+$month_n = date('n');
+
+$iday = ORM::for_table('tbl_transactions')
+ ->where('recharged_on', $mdate)
+ ->where_not_equal('method', 'Customer - Balance')
+ ->where_not_equal('method', 'Recharge Balance - Administrator')
+ ->sum('price');
+
+if ($iday == '') {
+ $iday = '0.00';
+}
+$ui->assign('iday', $iday);
+
+$imonth = ORM::for_table('tbl_transactions')->where_not_equal('method', 'Customer - Balance')->where_not_equal('method', 'Recharge Balance - Administrator')->where_gte('recharged_on', $first_day_month)->where_lte('recharged_on', $mdate)->sum('price');
+if ($imonth == '') {
+ $imonth = '0.00';
+}
+$ui->assign('imonth', $imonth);
+
+$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count();
+if (empty($u_act)) {
+ $u_act = '0';
+}
+$ui->assign('u_act', $u_act);
+
+$u_all = ORM::for_table('tbl_user_recharges')->count();
+if (empty($u_all)) {
+ $u_all = '0';
+}
+$ui->assign('u_all', $u_all);
+
+
+$c_all = ORM::for_table('tbl_customers')->count();
+if (empty($c_all)) {
+ $c_all = '0';
+}
+$ui->assign('c_all', $c_all);
+
+if ($config['hide_uet'] != 'yes') {
+ //user expire
+ $query = ORM::for_table('tbl_user_recharges')
+ ->where_lte('expiration', $mdate)
+ ->order_by_desc('expiration');
+ $expire = Paginator::findMany($query);
+
+ // Get the total count of expired records for pagination
+ $totalCount = ORM::for_table('tbl_user_recharges')
+ ->where_lte('expiration', $mdate)
+ ->count();
+
+ // Pass the total count and current page to the paginator
+ $paginator['total_count'] = $totalCount;
+
+ // Assign the pagination HTML to the template variable
+ $ui->assign('expire', $expire);
+}
+
+//activity log
+$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->find_many();
+$ui->assign('dlog', $dlog);
+$log = ORM::for_table('tbl_logs')->count();
+$ui->assign('log', $log);
+
+
+if ($config['hide_vs'] != 'yes') {
+ $cacheStocksfile = $CACHE_PATH . File::pathFixer('/VoucherStocks.temp');
+ $cachePlanfile = $CACHE_PATH . File::pathFixer('/VoucherPlans.temp');
+ //Cache for 5 minutes
+ if (file_exists($cacheStocksfile) && time() - filemtime($cacheStocksfile) < 600) {
+ $stocks = json_decode(file_get_contents($cacheStocksfile), true);
+ $plans = json_decode(file_get_contents($cachePlanfile), true);
+ } else {
+ // Count stock
+ $tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many();
+ $plans = array();
+ $stocks = array("used" => 0, "unused" => 0);
+ $n = 0;
+ foreach ($tmp as $plan) {
+ $unused = ORM::for_table('tbl_voucher')
+ ->where('id_plan', $plan['id'])
+ ->where('status', 0)->count();
+ $used = ORM::for_table('tbl_voucher')
+ ->where('id_plan', $plan['id'])
+ ->where('status', 1)->count();
+ if ($unused > 0 || $used > 0) {
+ $plans[$n]['name_plan'] = $plan['name_plan'];
+ $plans[$n]['unused'] = $unused;
+ $plans[$n]['used'] = $used;
+ $stocks["unused"] += $unused;
+ $stocks["used"] += $used;
+ $n++;
+ }
+ }
+ file_put_contents($cacheStocksfile, json_encode($stocks));
+ file_put_contents($cachePlanfile, json_encode($plans));
+ }
+}
+
+$cacheMRfile = File::pathFixer('/monthlyRegistered.temp');
+//Cache for 1 hour
+if (file_exists($cacheMRfile) && time() - filemtime($cacheMRfile) < 3600) {
+ $monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true);
+} else {
+ //Monthly Registered Customers
+ $result = ORM::for_table('tbl_customers')
+ ->select_expr('MONTH(created_at)', 'month')
+ ->select_expr('COUNT(*)', 'count')
+ ->where_raw('YEAR(created_at) = YEAR(NOW())')
+ ->group_by_expr('MONTH(created_at)')
+ ->find_many();
+
+ $monthlyRegistered = [];
+ foreach ($result as $row) {
+ $monthlyRegistered[] = [
+ 'date' => $row->month,
+ 'count' => $row->count
+ ];
+ }
+ file_put_contents($cacheMRfile, json_encode($monthlyRegistered));
+}
+
+$cacheMSfile = $CACHE_PATH . File::pathFixer('/monthlySales.temp');
+//Cache for 12 hours
+if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) {
+ $monthlySales = json_decode(file_get_contents($cacheMSfile), true);
+} else {
+ // Query to retrieve monthly data
+ $results = ORM::for_table('tbl_transactions')
+ ->select_expr('MONTH(recharged_on)', 'month')
+ ->select_expr('SUM(price)', 'total')
+ ->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year
+ ->where_not_equal('method', 'Customer - Balance')
+ ->where_not_equal('method', 'Recharge Balance - Administrator')
+ ->group_by_expr('MONTH(recharged_on)')
+ ->find_many();
+
+ // Create an array to hold the monthly sales data
+ $monthlySales = array();
+
+ // Iterate over the results and populate the array
+ foreach ($results as $result) {
+ $month = $result->month;
+ $totalSales = $result->total;
+
+ $monthlySales[$month] = array(
+ 'month' => $month,
+ 'totalSales' => $totalSales
+ );
+ }
+
+ // Fill in missing months with zero sales
+ for ($month = 1; $month <= 12; $month++) {
+ if (!isset($monthlySales[$month])) {
+ $monthlySales[$month] = array(
+ 'month' => $month,
+ 'totalSales' => 0
+ );
+ }
+ }
+
+ // Sort the array by month
+ ksort($monthlySales);
+
+ // Reindex the array
+ $monthlySales = array_values($monthlySales);
+ file_put_contents($cacheMSfile, json_encode($monthlySales));
+}
+
+// Assign the monthly sales data to Smarty
+$ui->assign('monthlySales', $monthlySales);
+$ui->assign('xfooter', '');
+$ui->assign('monthlyRegistered', $monthlyRegistered);
+$ui->assign('stocks', $stocks);
+$ui->assign('plans', $plans);
+
+run_hook('view_dashboard'); #HOOK
+$ui->display('dashboard.tpl');