diff --git a/.gitignore b/.gitignore
index e9361f19..3bd975cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,5 @@ docs/**
!docs/insomnia.rest.json
!system/uploads/paid.png
system/uploads/invoices/**
+!system/uploads/invoices/
+!system/uploads/invoices/index.html
\ No newline at end of file
diff --git a/system/autoload/Invoice.php b/system/autoload/Invoice.php
index 96f558e6..8215559b 100644
--- a/system/autoload/Invoice.php
+++ b/system/autoload/Invoice.php
@@ -8,12 +8,12 @@ class Invoice
{
try {
if (empty($invoiceData['invoice'])) {
- throw new Exception("Invoice ID is required");
+ throw new Exception(Lang::T("Invoice No is required"));
}
$template = Lang::getNotifText('email_invoice');
if (!$template) {
- throw new Exception("Invoice template not found");
+ throw new Exception(Lang::T("Invoice template not found"));
}
if (strpos($template, '
Output($outputPath, 'F');
if (!file_exists($outputPath)) {
- throw new Exception("Failed to save PDF file");
+ throw new Exception(Lang::T("Failed to save PDF file"));
}
return $filename;
@@ -67,7 +71,7 @@ class Invoice
return preg_replace_callback('/\[\[(\w+)\]\]/', function ($matches) use ($invoiceData) {
$key = $matches[1];
if (!isset($invoiceData[$key])) {
- _log("Missing invoice key: $key");
+ _log(Lang::T("Missing invoice key: ") . $key);
return '';
}
@@ -80,92 +84,84 @@ class Invoice
}
if ($key === 'bill_rows') {
- return html_entity_decode($invoiceData[$key]);
+ return $invoiceData[$key];
}
-
return htmlspecialchars($invoiceData[$key] ?? '');
}, $template);
}
- public static function sendInvoice($userId, $status = "Unpaid")
+ /**
+ * Send invoice to user
+ *
+ * @param int $userId
+ * @param array $invoice
+ * @param array $bills
+ * @param string $status
+ * @param string $invoiceNo
+ * @return bool
+ */
+
+ public static function sendInvoice($userId, $invoice = null, $bills = [], $status = "Unpaid", $invoiceNo = null)
{
global $config, $root_path, $UPLOAD_PATH;
- if (empty($config['currency_code'])) {
- $config['currency_code'] = '$';
- }
+ // Set default currency code
+ $config['currency_code'] ??= '$';
$account = ORM::for_table('tbl_customers')->find_one($userId);
-
- if (!$account) {
- _log("Failed to send invoice: User not found");
- sendTelegram("Failed to send invoice: User not found");
- return false;
+ self::validateAccount($account);
+ if (!$invoiceNo) {
+ $invoiceNo = "INV-" . Package::_raid();
}
+ // Fetch invoice if not provided
+ if ($status === "Unpaid" && !$invoice) {
+ $data = ORM::for_table('tbl_user_recharges')->where('customer_id', $userId)
+ ->where('status', 'off')
+ ->left_outer_join('tbl_plans', 'tbl_user_recharges.namebp = tbl_plans.name_plan')
+ ->select('tbl_plans.price', 'price')
+ ->select('tbl_plans.name_plan', 'namebp')
+ ->find_one();
+ if (!$data) {
+ $data = ORM::for_table('tbl_user_recharges')->where('username', $account->username)
+ ->left_outer_join('tbl_plans', 'tbl_user_recharges.namebp = tbl_plans.name_plan')
+ ->select('tbl_plans.price', 'price')
+ ->select('tbl_plans.name_plan', 'namebp')
+ ->where('status', 'off')
+ ->find_one();
+ }
+ if (!$data) {
+ throw new Exception(Lang::T("No unpaid invoice found for username:") . $account->username);
+ }
+ $invoice = [
+ 'price' => $data->price,
+ 'plan_name' => $data->namebp,
+ 'routers' => $data->routers,
+ ];
- $invoice = ORM::for_table("tbl_transactions")->where("username", $account->username)->find_one();
-
+ } else if ($status === "Paid" && !$invoice) {
+ $invoice = ORM::for_table("tbl_transactions")->where("username", $account->username)->find_one();
+ }
if (!$invoice) {
- _log("Failed to send invoice: Transaction not found");
- sendTelegram("Failed to send invoice: Transaction not found");
- return false;
+ throw new Exception(Lang::T("Transaction not found for username: ") . $account->username);
}
- [$additionalBills, $add_cost] = User::getBills($account->id);
-
- $invoiceItems = [
- [
- 'description' => $invoice->plan_name,
- 'details' => 'Monthly Subscription',
- 'amount' => (float) $invoice->price
- ]
- ];
- $subtotal = (float) $invoice->price;
-
- if ($add_cost > 0 && $invoice->routers != 'balance') {
- foreach ($additionalBills as $description => $amount) {
- if (is_numeric($amount)) {
- $invoiceItems[] = [
- 'description' => $description,
- 'details' => 'Additional Bill',
- 'amount' => (float) $amount
- ];
- $subtotal += (float) $amount;
- } else {
- _log("Invalid bill amount for {$description}: {$amount}");
- }
- }
+ // Get additional bills if not provided
+ if (empty($bills)) {
+ [$bills, $add_cost] = User::getBills($account->id);
}
- $tax_rate = (float) ($config['tax_rate'] ?? 0);
+ $invoiceItems = self::generateInvoiceItems($invoice, $bills, $add_cost);
+ $subtotal = array_sum(array_column($invoiceItems, 'amount'));
$tax = $config['enable_tax'] ? Package::tax($subtotal) : 0;
- $total = ($tax > 0) ? $subtotal + $tax : $subtotal + $tax;
+ $tax_rate = $config['tax_rate'] ?? 0;
+ $total = $subtotal + $tax;
- $token = User::generateToken($account->id, 1);
- if (!empty($token['token'])) {
- $tur = ORM::for_table('tbl_user_recharges')
- ->where('customer_id', $account->id)
- ->where('namebp', $invoice->plan_name);
+ $payLink = self::generatePaymentLink($account, $invoice, $status);
+ $logo = self::getCompanyLogo($UPLOAD_PATH, $root_path);
- switch ($status) {
- case 'Paid':
- $tur->where('status', 'on');
- break;
- default:
- $tur->where('status', 'off');
- break;
- }
- $turResult = $tur->find_one();
- $payLink = $turResult ? '?_route=home&recharge=' . $turResult['id'] . '&uid=' . urlencode($token['token']) : '?_route=home';
- } else {
- $payLink = '?_route=home';
- }
-
- $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
- $logo = (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) ? $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png?' . time() : $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.default.png';
$invoiceData = [
- 'invoice' => "INV-" . Package::_raid(),
+ 'invoice' => $invoiceNo,
'fullname' => $account->fullname,
'email' => $account->email,
'address' => $account->address,
@@ -182,31 +178,90 @@ class Invoice
'payment_link' => $payLink
];
- if (!isset($invoiceData['bill_rows']) || empty($invoiceData['bill_rows'])) {
- _log("Invoice Error: Bill rows data is empty.");
+ if (empty($invoiceData['bill_rows'])) {
+ throw new Exception(Lang::T("Bill rows data is empty."));
}
$filename = self::generateInvoice($invoiceData);
-
- if ($filename) {
- $pdfPath = "system/uploads/invoices/{$filename}";
-
- try {
- Message::sendEmail(
- $account->email,
- "Invoice for Account {$account->fullname}",
- "Please find your invoice attached",
- $pdfPath
- );
- return true;
- } catch (\Exception $e) {
- _log("Failed to send invoice email: " . $e->getMessage());
- sendTelegram("Failed to send invoice email: " . $e->getMessage());
- return false;
- }
+ if (!$filename) {
+ throw new Exception(Lang::T("Failed to generate invoice PDF"));
}
- return false;
+ $pdfPath = "system/uploads/invoices/{$filename}";
+ self::saveToDatabase($filename, $account->id, $invoiceData, $total);
+
+ try {
+ Message::sendEmail(
+ $account->email,
+ Lang::T("Invoice for Account {$account->fullname}"),
+ Lang::T("Please find your invoice attached"),
+ $pdfPath
+ );
+ return true;
+ } catch (\Exception $e) {
+ throw new Exception(Lang::T("Failed to send email invoice to ") . $account->email . ". " . Lang::T("Reason: ") . $e->getMessage());
+ }
+ }
+
+ private static function validateAccount($account)
+ {
+ if (!$account) {
+ throw new Exception(Lang::T("User not found"));
+ }
+ if (!$account->email || !filter_var($account->email, FILTER_VALIDATE_EMAIL)) {
+ throw new Exception(Lang::T("Invalid user email"));
+ }
+ }
+
+ private static function generateInvoiceItems($invoice, $bills, $add_cost)
+ {
+ $items = [
+ [
+ 'description' => $invoice['plan_name'],
+ 'details' => Lang::T('Subscription'),
+ 'amount' => (float) $invoice['price']
+ ]
+ ];
+
+ if ($invoice->routers != 'balance') {
+ foreach ($bills as $description => $amount) {
+ if (is_numeric($amount)) {
+ $items[] = [
+ 'description' => $description,
+ 'details' => Lang::T('Additional Bill'),
+ 'amount' => (float) $amount
+ ];
+ } else {
+ _log(Lang::T("Invalid bill amount for {$description}: {$amount}"));
+ }
+ }
+ }
+ return $items;
+ }
+
+ private static function generatePaymentLink($account, $invoice, $status)
+ {
+ $token = User::generateToken($account->id, 1);
+ if (empty($token['token'])) {
+ return '?_route=home';
+ }
+
+ $tur = ORM::for_table('tbl_user_recharges')
+ ->where('customer_id', $account->id)
+ ->where('namebp', $invoice->plan_name);
+
+ $tur->where('status', $status === 'Paid' ? 'on' : 'off');
+ $turResult = $tur->find_one();
+
+ return $turResult ? '?_route=home&recharge=' . $turResult['id'] . '&uid=' . urlencode($token['token']) : '?_route=home';
+ }
+
+ private static function getCompanyLogo($UPLOAD_PATH, $root_path)
+ {
+ $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
+ return file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png') ?
+ $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png?' . time() :
+ $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.default.png';
}
private static function generateBillRows($items, $currency, $subtotal, $tax_rate, $tax, $total)
@@ -222,9 +277,11 @@ class Invoice
";
foreach ($items as $item) {
+ $desc = htmlspecialchars($item['description'], ENT_QUOTES);
+ $details = htmlspecialchars($item['details'], ENT_QUOTES);
$html .= "
- {$item['description']}
- {$item['details']}
+ {$desc}
+ {$details}
{$currency}" . number_format((float) $item['amount'], 2) . "
";
}
@@ -247,6 +304,44 @@ class Invoice
return $html;
}
+ private static function saveToDatabase($filename, $customer_id, $invoiceData, $total)
+ {
+ $invoice = ORM::for_table('tbl_invoices')->create();
+ $invoice->number = $invoiceData['invoice'];
+ $invoice->customer_id = $customer_id;
+ $invoice->fullname = $invoiceData['fullname'];
+ $invoice->email = $invoiceData['email'];
+ $invoice->address = $invoiceData['address'];
+ $invoice->status = $invoiceData['status'];
+ $invoice->due_date = $invoiceData['due_date'];
+ $invoice->filename = $filename;
+ $invoice->amount = $total;
+ $invoice->data = json_encode($invoiceData);
+ $invoice->created_at = date('Y-m-d H:i:s');
+ $invoice->save();
+ return $invoice->id;
+ }
+ public static function getAll()
+ {
+ return ORM::for_table('tbl_invoices')->order_by_desc('id')->find_many();
+ }
+ public static function getById($id)
+ {
+ return ORM::for_table('tbl_invoices')->find_one($id);
+ }
+ public static function getByNumber($number)
+ {
+ return ORM::for_table('tbl_invoices')->where('number', $number)->find_one();
+ }
+ public static function delete($id)
+ {
+ $invoice = ORM::for_table('tbl_invoices')->find_one($id);
+ if ($invoice) {
+ $invoice->delete();
+ return true;
+ }
+ return false;
+ }
}
diff --git a/system/autoload/Message.php b/system/autoload/Message.php
index 725d8f39..282a4513 100644
--- a/system/autoload/Message.php
+++ b/system/autoload/Message.php
@@ -46,7 +46,7 @@ class Message
$txts = str_split($txt, 160);
try {
foreach ($txts as $txt) {
- self::sendSMS($config['sms_url'], $phone, $txt);
+ self::sendSMS($phone, $txt);
self::logMessage('SMS', $phone, $txt, 'Success');
}
} catch (Throwable $e) {
@@ -140,6 +140,7 @@ class Message
}
mail($to, $subject, $body, $attr);
self::logMessage('Email', $to, $body, 'Success');
+ return true;
} else {
$mail = new PHPMailer();
$mail->isSMTP();
@@ -188,8 +189,10 @@ class Message
if (!$mail->send()) {
$errorMessage = Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo;
self::logMessage('Email', $to, $body, 'Error', $errorMessage);
+ return false;
} else {
self::logMessage('Email', $to, $body, 'Success');
+ return true;
}
//
@@ -396,9 +399,11 @@ class Message
$v->body = nl2br($body);
$v->save();
self::logMessage("Inbox", $user->username, $body, "Success");
+ return true;
} catch (Throwable $e) {
$errorMessage = Lang::T("Error adding message to inbox: " . $e->getMessage());
self::logMessage('Inbox', $user->username, $body, 'Error', $errorMessage);
+ return false;
}
}
diff --git a/system/controllers/export.php b/system/controllers/export.php
index 154d188f..a43a4d70 100644
--- a/system/controllers/export.php
+++ b/system/controllers/export.php
@@ -71,8 +71,8 @@ switch ($action) {
if (count($plns) > 0) {
$query->where_in('plan_name', $plns);
}
- $x = $query->find_array();
- $xy = $query->sum('price');
+ $x = $query->find_array();
+ $xy = $query->sum('price');
$ui->assign('sd', $sd);
$ui->assign('ed', $ed);
@@ -114,7 +114,10 @@ switch ($action) {
$query = ORM::for_table('tbl_transactions')
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts"))
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te"))
- ->order_by_desc('id');
+ ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
+ ->select('tbl_transactions.*')
+ ->select('tbl_customers.fullname', 'fullname')
+ ->order_by_desc('tbl_transactions.id');
if (count($tps) > 0) {
$query->where_in('type', $tps);
}
@@ -131,13 +134,13 @@ switch ($action) {
if (count($plns) > 0) {
$query->where_in('plan_name', $plns);
}
- $x = $query->find_array();
- $xy = $query->sum('price');
+ $x = $query->find_array();
+ $xy = $query->sum('price');
$title = ' Reports [' . $mdate . ']';
$title = str_replace('-', ' ', $title);
- $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
+ $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
if (file_exists($UPLOAD_PATH . '/logo.png')) {
$logo = $UPLOAD_URL_PATH . '/logo.png';
} else {
@@ -154,10 +157,11 @@ switch ($action) {
-
+
' . Lang::T('Username') . '
+ ' . Lang::T('Fullname') . '
' . Lang::T('Plan Name') . '
' . Lang::T('Type') . '
' . Lang::T('Plan Price') . '
@@ -170,6 +174,7 @@ switch ($action) {
foreach ($x as $value) {
$username = $value['username'];
+ $fullname = $value['fullname'];
$plan_name = $value['plan_name'];
$type = $value['type'];
$price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']);
@@ -181,6 +186,7 @@ switch ($action) {
$html .= " " . "
$username
+ $fullname
$plan_name
$type
$price
@@ -245,7 +251,7 @@ $style
$html
EOF;
$mpdf->WriteHTML($nhtml);
- $mpdf->Output('phpnuxbill_reports_'.date('Ymd_His') . '.pdf', 'D');
+ $mpdf->Output('phpnuxbill_reports_' . date('Ymd_His') . '.pdf', 'D');
} else {
echo 'No Data';
}
@@ -258,13 +264,17 @@ EOF;
$stype = _post('stype');
$d = ORM::for_table('tbl_transactions');
+ $d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
+ ->select('tbl_transactions.*')
+ ->select('tbl_customers.fullname', 'fullname')
+ ->order_by_desc('tbl_transactions.id');
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();
+ $x = $d->find_many();
$dr = ORM::for_table('tbl_transactions');
if ($stype != '') {
@@ -290,6 +300,10 @@ EOF;
$tdate = _post('tdate');
$stype = _post('stype');
$d = ORM::for_table('tbl_transactions');
+ $d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
+ ->select('tbl_transactions.*')
+ ->select('tbl_customers.fullname', 'fullname')
+ ->order_by_desc('tbl_transactions.id');
if ($stype != '') {
$d->where('type', $stype);
}
@@ -297,7 +311,7 @@ EOF;
$d->where_gte('recharged_on', $fdate);
$d->where_lte('recharged_on', $tdate);
$d->order_by_desc('id');
- $x = $d->find_many();
+ $x = $d->find_many();
$dr = ORM::for_table('tbl_transactions');
if ($stype != '') {
@@ -311,7 +325,7 @@ EOF;
$title = ' Reports [' . $mdate . ']';
$title = str_replace('-', ' ', $title);
- $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
+ $UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
if (file_exists($UPLOAD_PATH . '/logo.png')) {
$logo = $UPLOAD_URL_PATH . '/logo.png';
} else {
@@ -332,6 +346,7 @@ EOF;
' . Lang::T('Username') . '
+ ' . Lang::T('Fullname') . '
' . Lang::T('Plan Name') . '
' . Lang::T('Type') . '
' . Lang::T('Plan Price') . '
@@ -344,6 +359,7 @@ EOF;
foreach ($x as $value) {
$username = $value['username'];
+ $fullname = $value['fullname'];
$plan_name = $value['plan_name'];
$type = $value['type'];
$price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']);
@@ -355,7 +371,8 @@ EOF;
$html .= " " . "
$username
- $plan_name
+ $fullname
+ $plan_name
$type
$price
$recharged_on
diff --git a/system/controllers/invoices.php b/system/controllers/invoices.php
new file mode 100644
index 00000000..e072468c
--- /dev/null
+++ b/system/controllers/invoices.php
@@ -0,0 +1,27 @@
+assign('_title', Lang::T('Invoice Lists'));
+$ui->assign('_system_menu', 'reports');
+$action = $routes['1'];
+$ui->assign('_admin', $admin);
+
+if (empty($action)) {
+ $action = 'list';
+}
+switch ($action) {
+ case 'list':
+ $ui->assign('xheader', ' ');
+ $ui->assign('invoices', Invoice::getAll());
+ $ui->display('admin/invoices/list.tpl');
+ break;
+ default:
+ $ui->display('admin/404.tpl');
+}
diff --git a/system/controllers/message.php b/system/controllers/message.php
index b2df636d..a1407698 100644
--- a/system/controllers/message.php
+++ b/system/controllers/message.php
@@ -57,56 +57,79 @@ EOT;
_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'];
+ $id_customer = $_POST['id_customer'] ?? '';
+ $message = $_POST['message'] ?? '';
+ $via = $_POST['via'] ?? '';
+ $subject = $_POST['subject'] ?? '';
- // Check if fields are empty
- if ($id_customer == '' or $message == '' or $via == '') {
- r2(getUrl('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);
+ // Validate subject based on the selected channel
+ if (($via === 'all' || $via === 'email' || $via === 'inbox') && empty($subject)) {
+ r2(getUrl('message/send'), 'e', LANG::T('Subject is required to send message using') . ' ' . $via . '.');
+ }
- // 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);
- if (strpos($message, '[[payment_link]]') !== false) {
- // token only valid for 1 day, for security reason
- $token = User::generateToken($c['id'], 1);
- if (!empty($token['token'])) {
- $tur = ORM::for_table('tbl_user_recharges')
- ->where('customer_id', $c['id'])
- //->where('namebp', $package)
- ->find_one();
- if ($tur) {
- $url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']);
- $message = str_replace('[[payment_link]]', $url, $message);
- }
- } else {
- $message = str_replace('[[payment_link]]', '', $message);
+ if (empty($id_customer) || empty($message) || empty($via)) {
+ r2(getUrl('message/send'), 'e', Lang::T('Customer, Message, and Channel are required'));
+ }
+
+ $customer = ORM::for_table('tbl_customers')->find_one($id_customer);
+ if (!$customer) {
+ r2(getUrl('message/send'), 'e', Lang::T('Customer not found'));
+ }
+
+ // Replace placeholders in message and subject
+ $currentMessage = str_replace(
+ ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
+ [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
+ $message
+ );
+
+ $currentSubject = str_replace(
+ ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
+ [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
+ $subject
+ );
+
+ if (strpos($message, '[[payment_link]]') !== false) {
+ $token = User::generateToken($customer['id'], 1);
+ if (!empty($token['token'])) {
+ $tur = ORM::for_table('tbl_user_recharges')
+ ->where('customer_id', $customer['id'])
+ ->find_one();
+ if ($tur) {
+ $url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']);
+ $currentMessage = str_replace('[[payment_link]]', $url, $currentMessage);
}
- }
-
-
- //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(getUrl('message/send'), 's', Lang::T('Message Sent Successfully'));
} else {
- r2(getUrl('message/send'), 'e', Lang::T('Failed to send message'));
+ $currentMessage = str_replace('[[payment_link]]', '', $currentMessage);
}
}
+
+ // Send the message through the selected channels
+ $smsSent = $waSent = $emailSent = $inboxSent = false;
+
+ if ($via === 'sms' || $via === 'both' || $via === 'all') {
+ $smsSent = Message::sendSMS($customer['phonenumber'], $currentSubject);
+ }
+
+ if ($via === 'wa' || $via === 'both' || $via === 'all') {
+ $waSent = Message::sendWhatsapp($customer['phonenumber'], $currentSubject);
+ }
+
+ if ($via === 'email' || $via === 'all') {
+ $emailSent = Message::sendEmail($customer['email'], $currentSubject, $currentMessage);
+ }
+
+ if ($via === 'inbox' || $via === 'all') {
+ $inboxSent = Message::addToInbox($customer['id'], $currentSubject, $currentMessage, 'Admin');
+ }
+
+ // Check if any message was sent successfully
+ if ($smsSent || $waSent || $emailSent || $inboxSent) {
+ r2(getUrl('message/send'), 's', Lang::T('Message Sent Successfully'));
+ } else {
+ r2(getUrl('message/send'), 'e', Lang::T('Failed to send message'));
+ }
+
break;
case 'send_bulk':
@@ -133,11 +156,16 @@ EOT;
$batch = $_REQUEST['batch'] ?? 100;
$page = $_REQUEST['page'] ?? 0;
$router = $_REQUEST['router'] ?? null;
- $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? true : false;
+ $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on';
$service = $_REQUEST['service'] ?? '';
+ $subject = $_REQUEST['subject'] ?? '';
if (empty($group) || empty($message) || empty($via) || empty($service)) {
- die(json_encode(['status' => 'error', 'message' => 'All fields are required']));
+ die(json_encode(['status' => 'error', 'message' => LANG::T('All fields are required')]));
+ }
+
+ if (in_array($via, ['all', 'email', 'inbox']) && empty($subject)) {
+ die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.']));
}
// Get batch of customers based on group
@@ -153,7 +181,7 @@ EOT;
default:
$router = ORM::for_table('tbl_routers')->find_one($router);
if (!$router) {
- die(json_encode(['status' => 'error', 'message' => 'Invalid router']));
+ die(json_encode(['status' => 'error', 'message' => LANG::T('Invalid router')]));
}
$routerName = $router->name;
break;
@@ -200,6 +228,9 @@ EOT;
['tbl_customers.phonenumber', 'phonenumber'],
['tbl_user_recharges.customer_id', 'customer_id'],
['tbl_customers.fullname', 'fullname'],
+ ['tbl_customers.username', 'username'],
+ ['tbl_customers.email', 'email'],
+ ['tbl_customers.service_type', 'service_type'],
]);
$customers = $query->find_array();
} else {
@@ -287,7 +318,13 @@ EOT;
$totalSMSFailed = 0;
$totalWhatsappSent = 0;
$totalWhatsappFailed = 0;
+ $totalEmailSent = 0;
+ $totalEmailFailed = 0;
+ $totalInboxSent = 0;
+ $totalInboxFailed = 0;
$batchStatus = [];
+ //$subject = $config['CompanyName'] . ' ' . Lang::T('Notification Message');
+ $form = 'Admin';
foreach ($customers as $customer) {
$currentMessage = str_replace(
@@ -296,6 +333,12 @@ EOT;
$message
);
+ $currentSubject = str_replace(
+ ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
+ [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
+ $subject
+ );
+
$phoneNumber = preg_replace('/\D/', '', $customer['phonenumber']);
if (empty($phoneNumber)) {
@@ -310,14 +353,14 @@ EOT;
if ($test) {
$batchStatus[] = [
'name' => $customer['fullname'],
- 'phone' => $customer['phonenumber'],
+ 'channel' => 'Test Channel',
'status' => 'Test Mode',
'message' => $currentMessage,
'service' => $service,
'router' => $routerName,
];
} else {
- if ($via == 'sms' || $via == 'both') {
+ if ($via === 'sms' || $via === 'both' || $via === 'all') {
if (Message::sendSMS($customer['phonenumber'], $currentMessage)) {
$totalSMSSent++;
$batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Sent', 'message' => $currentMessage];
@@ -327,13 +370,33 @@ EOT;
}
}
- if ($via == 'wa' || $via == 'both') {
+ if ($via === 'wa' || $via == 'both' || $via === 'all') {
if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) {
$totalWhatsappSent++;
- $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage];
+ $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage];
} else {
$totalWhatsappFailed++;
- $batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage];
+ $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage];
+ }
+ }
+
+ if ($via === 'email' || $via === 'all') {
+ if (Message::sendEmail($customer['email'], $currentSubject, $currentMessage)) {
+ $totalEmailSent++;
+ $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Sent', 'message' => $currentMessage];
+ } else {
+ $totalEmailFailed++;
+ $batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Failed', 'message' => $currentMessage];
+ }
+ }
+
+ if ($via === 'inbox' || $via === 'all') {
+ if (Message::addToInbox($customer['customer_id'], $currentSubject, $currentMessage, $form)) {
+ $totalInboxSent++;
+ $batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Sent', 'message' => $currentMessage];
+ } else {
+ $totalInboxFailed++;
+ $batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Failed', 'message' => $currentMessage];
}
}
}
@@ -348,8 +411,8 @@ EOT;
'page' => $page + 1,
'batchStatus' => $batchStatus,
'message' => $currentMessage,
- 'totalSent' => $totalSMSSent + $totalWhatsappSent,
- 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed,
+ 'totalSent' => $totalSMSSent + $totalWhatsappSent + $totalEmailSent + $totalInboxSent,
+ 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed + $totalEmailFailed + $totalInboxFailed,
'hasMore' => $hasMore,
'service' => $service,
'router' => $routerName,
@@ -365,16 +428,20 @@ EOT;
// Get the posted data
$customerIds = $_POST['customer_ids'] ?? [];
$via = $_POST['message_type'] ?? '';
+ $subject = $_POST['subject'] ?? '';
$message = isset($_POST['message']) ? trim($_POST['message']) : '';
if (empty($customerIds) || empty($message) || empty($via)) {
echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid customer IDs, Message, or Message Type.')]);
exit;
}
+ if ($via === 'all' || $via === 'email' || $via === 'inbox' && empty($subject)) {
+ die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.']));
+ }
+
// Prepare to send messages
$sentCount = 0;
$failedCount = 0;
- $subject = Lang::T('Notification Message');
$form = 'Admin';
foreach ($customerIds as $customerId) {
diff --git a/system/controllers/reports.php b/system/controllers/reports.php
index b24ce39c..e18e1f87 100644
--- a/system/controllers/reports.php
+++ b/system/controllers/reports.php
@@ -58,7 +58,7 @@ switch ($action) {
$w = [];
$v = [];
foreach ($mts as $mt) {
- $w[] ='method';
+ $w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
@@ -91,7 +91,7 @@ switch ($action) {
$w = [];
$v = [];
foreach ($mts as $mt) {
- $w[] ='method';
+ $w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
@@ -161,7 +161,7 @@ switch ($action) {
$w = [];
$v = [];
foreach ($mts as $mt) {
- $w[] ='method';
+ $w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
@@ -246,7 +246,7 @@ switch ($action) {
$total += $v;
$array['data'][] = $v;
}
- if($total>0){
+ if ($total > 0) {
$result['datas'][] = $array;
}
}
@@ -258,18 +258,29 @@ switch ($action) {
die();
case 'by-date':
case 'activation':
- $q = (_post('q') ? _post('q') : _get('q'));
+ $q = trim(_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(getUrl('logs/list/'), 's', "Delete logs older than $keep days");
+ ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))", [$keep]);
+ r2(getUrl('reports/activation/'), 's', "Deleted logs older than $keep days");
}
- if ($q != '') {
- $query = ORM::for_table('tbl_transactions')->where_like('invoice', '%' . $q . '%')->order_by_desc('id');
+
+ $query = ORM::for_table('tbl_transactions')
+ ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
+ ->select('tbl_transactions.*')
+ ->select('tbl_customers.fullname', 'fullname')
+ ->order_by_desc('tbl_transactions.id');
+
+ if ($q !== '') {
+ $query->where_like('invoice', "%$q%");
+ }
+
+ try {
$d = Paginator::findMany($query, ['q' => $q]);
- } else {
- $query = ORM::for_table('tbl_transactions')->order_by_desc('id');
- $d = Paginator::findMany($query);
+ } catch (Exception $e) {
+ r2(getUrl('reports/activation/'), 'e', 'Database query failed: ' . $e->getMessage());
+ $d = [];
}
$ui->assign('activation', $d);
@@ -291,6 +302,10 @@ switch ($action) {
$stype = _post('stype');
$d = ORM::for_table('tbl_transactions');
+ $d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
+ ->select('tbl_transactions.*')
+ ->select('tbl_customers.fullname', 'fullname')
+ ->order_by_desc('tbl_transactions.id');
if ($stype != '') {
$d->where('type', $stype);
}
@@ -298,7 +313,7 @@ switch ($action) {
$d->where_gte('recharged_on', $fdate);
$d->where_lte('recharged_on', $tdate);
$d->order_by_desc('id');
- $x = $d->find_many();
+ $x = $d->find_many();
$dr = ORM::for_table('tbl_transactions');
if ($stype != '') {
@@ -348,7 +363,10 @@ switch ($action) {
$query = ORM::for_table('tbl_transactions')
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts"))
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te"))
- ->order_by_desc('id');
+ ->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
+ ->select('tbl_transactions.*')
+ ->select('tbl_customers.fullname', 'fullname')
+ ->order_by_desc('tbl_transactions.id');
if (count($tps) > 0) {
$query->where_in('type', $tps);
}
@@ -356,7 +374,7 @@ switch ($action) {
$w = [];
$v = [];
foreach ($mts as $mt) {
- $w[] ='method';
+ $w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
diff --git a/system/controllers/settings.php b/system/controllers/settings.php
index a1599e8b..4366a32f 100644
--- a/system/controllers/settings.php
+++ b/system/controllers/settings.php
@@ -146,7 +146,8 @@ switch ($action) {
$r = ORM::for_table('tbl_routers')->find_many();
$ui->assign('r', $r);
if (function_exists("shell_exec")) {
- $php = trim(shell_exec('which php'));
+ $which = stripos(php_uname('s'), "Win") === 0 ? 'where' : 'which';
+ $php = trim(shell_exec("$which php"));
if (empty($php)) {
$php = 'php';
}
diff --git a/system/cron_reminder.php b/system/cron_reminder.php
index ad4adbca..01092c80 100644
--- a/system/cron_reminder.php
+++ b/system/cron_reminder.php
@@ -49,7 +49,7 @@ foreach ($d as $ds) {
} else {
$price = $p['price'];
}
- if ($ds['expiration'] == $day7 && $config['notification_reminder_7day'] !== 'no') {
+ if ($ds['expiration'] == $day7 && $config['notification_reminder_7days'] !== 'no') {
try {
echo Message::sendPackageNotification(
$c,
@@ -61,7 +61,7 @@ foreach ($d as $ds) {
} catch (Exception $e) {
sendTelegram("Cron Reminder failed to send 7-day reminder to " . $ds['username'] . " Error: " . $e->getMessage());
}
- } else if ($ds['expiration'] == $day3 && $config['notification_reminder_3day'] !== 'no') {
+ } else if ($ds['expiration'] == $day3 && $config['notification_reminder_3days'] !== 'no') {
try {
echo Message::sendPackageNotification(
$c,
diff --git a/system/devices/MikrotikPppoe.php b/system/devices/MikrotikPppoe.php
index b3e5c9ec..9e10cfaa 100644
--- a/system/devices/MikrotikPppoe.php
+++ b/system/devices/MikrotikPppoe.php
@@ -244,7 +244,7 @@ class MikrotikPppoe
function add_pool($pool){
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$mikrotik = $this->info($pool['routers']);
@@ -259,7 +259,7 @@ class MikrotikPppoe
function update_pool($old_pool, $new_pool){
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$mikrotik = $this->info($new_pool['routers']);
@@ -284,7 +284,7 @@ class MikrotikPppoe
function remove_pool($pool){
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$mikrotik = $this->info($pool['routers']);
@@ -329,7 +329,7 @@ class MikrotikPppoe
function getClient($ip, $user, $pass)
{
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$iport = explode(":", $ip);
@@ -339,7 +339,7 @@ class MikrotikPppoe
function removePpoeUser($client, $username)
{
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request('/ppp/secret/print');
@@ -376,7 +376,7 @@ class MikrotikPppoe
function removePpoeActive($client, $username)
{
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$onlineRequest = new RouterOS\Request('/ppp/active/print');
@@ -392,7 +392,7 @@ class MikrotikPppoe
function getIpHotspotUser($client, $username)
{
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request(
@@ -405,7 +405,7 @@ class MikrotikPppoe
function addIpToAddressList($client, $ip, $listName, $comment = '')
{
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$addRequest = new RouterOS\Request('/ip/firewall/address-list/add');
@@ -420,7 +420,7 @@ class MikrotikPppoe
function removeIpFromAddressList($client, $ip)
{
global $_app_stage;
- if ($_app_stage == 'demo') {
+ if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request(
diff --git a/system/lan/english.json b/system/lan/english.json
index f81d2f92..1f97dbcb 100644
--- a/system/lan/english.json
+++ b/system/lan/english.json
@@ -381,5 +381,166 @@
"_Clear_old_logs_": " Clear old logs?",
"Clean_up_Logs": "Clean up Logs",
"ID": "ID",
- "Date_Sent": "Date Sent"
+ "Date_Sent": "Date Sent",
+ "Notification_Message": "Notification Message",
+ "Share": "Share",
+ "Previous": "Previous",
+ "Email_not_sent__Mailer_Error__": "Email not sent, Mailer Error: ",
+ "Invoice_Lists": "Invoice Lists",
+ "Invoices": "Invoices",
+ "Invoice_No": "Invoice No",
+ "Customer_Name": "Customer Name",
+ "Amount": "Amount",
+ "Due_Date": "Due Date",
+ "Actions": "Actions",
+ "Pending": "Pending",
+ "Send": "Send",
+ "": "",
+ "Commission": "Commission",
+ "Last_30_Days_Overall_Usage": "Last 30 Days Overall Usage",
+ "Last_30_Days_Total_Usage": "Last 30 Days Total Usage",
+ "Top_10_Downloaders": "Top 10 Downloaders",
+ "Rank": "Rank",
+ "Downloaded": "Downloaded",
+ "Upload_Data": "Upload Data",
+ "Download_Data": "Download Data",
+ "Total_Data": "Total Data",
+ "Service": "Service",
+ "Mac_Address": "Mac Address",
+ "Location": "Location",
+ "Last_Updated": "Last Updated",
+ "View_Details": "View Details",
+ "Successful_Payments": "Successful Payments",
+ "Failed_Payments": "Failed Payments",
+ "Pending_Payments": "Pending Payments",
+ "Cancelled_Payments": "Cancelled Payments",
+ "Refresh_Dashboard": "Refresh Dashboard",
+ "Daily_Sales": "Daily Sales",
+ "Monthly_Sales": "Monthly Sales",
+ "Weekly_Sales": "Weekly Sales",
+ "Hotspot_Vouchers_Overview": "Hotspot Vouchers Overview",
+ "Transaction_Ref": "Transaction Ref",
+ "Router_Name": "Router Name",
+ "Voucher_Code": "Voucher Code",
+ "Transaction_Status": "Transaction Status",
+ "Payment_Date": "Payment Date",
+ "Plan_Expiry_Date": "Plan Expiry Date",
+ "Action": "Action",
+ "Block_Mac_Address": "Block Mac Address",
+ "Unblock_Mac_Address": "Unblock Mac Address",
+ "Captive_Portal_Dashboard": "Captive Portal Dashboard",
+ "Manage_Router_NAS": "Manage Router\/NAS",
+ "Captive_Portal_Routers": "Captive Portal Routers",
+ "Add_New_Router": "Add New Router",
+ "Router_Details": "Router Details",
+ "MAP": "MAP",
+ "LANDING_PAGE": "LANDING PAGE",
+ "FILE": "FILE",
+ "EDIT": "EDIT",
+ "Edit_Router": "Edit Router",
+ "Description": "Description",
+ "Enter_Description": "Enter Description",
+ "Coordinate": "Coordinate",
+ "DELETE": "DELETE",
+ "Back_To_Dashboard": "Back To Dashboard",
+ "Device_Type": "Device Type",
+ "Mikrotik_API": "Mikrotik API",
+ "Choose_Router": "Choose Router",
+ "Choose": "Choose",
+ "Select": "Select",
+ "Add_Router": "Add Router",
+ "Router_Location": "Router Location",
+ "Go_Back": "Go Back",
+ "Preview": "Preview",
+ "Login_html": "Login.html",
+ "Branding": "Branding",
+ "Sliders": "Sliders",
+ "Advertisements": "Advertisements",
+ "Announcements": "Announcements",
+ "Integrations": "Integrations",
+ "Basic_Settings": "Basic Settings",
+ "Page_Title": "Page Title",
+ "Displayed_in_browser_title_bar": "Displayed in browser title bar",
+ "Page_description_browser_title_bar": "Page description browser title bar",
+ "Hotspot_Name": "Hotspot Name",
+ "Hotspot_Name_will_be_display_on_Login_Page_Nav_Bar_if_Logo_is_not_available": "Hotspot Name will be display on Login Page Nav Bar if Logo is not available",
+ "Footer_Text": "Footer Text",
+ "Allow_Free_Trial": "Allow Free Trial",
+ "Choose_No_if_you_dont_want_to_allow_Free_Trial": "Choose No if you dont want to allow Free Trial",
+ "Make_sure_you_enable_free_trial_in_Mikrotik_Router": "Make sure you enable free trial in Mikrotik Router",
+ "free_trial_button_wont_display_on_captive_portal_preview__but_will_work_if_you_connect_from_a_hotspot": "free trial button wont display on captive portal preview, but will work if you connect from a hotspot",
+ "Allow_Member_Login": "Allow Member Login",
+ "Choose_No_If_you_want_to_disable_Member_Login": "Choose No If you want to disable Member Login",
+ "Allow_Randomized_MAC": "Allow Randomized MAC",
+ "Choose_Yes_If_you_want_to_allow_Randomized_or_Private_MAC": "Choose Yes If you want to allow Randomized or Private MAC",
+ "Assign_Plan": "Assign Plan",
+ "Service_Plan": "Service Plan",
+ "Change": "Change",
+ "Select_Plans": "Select Plans",
+ "Choose_plan_that_will_that_your_users_will_be_assigned": "Choose plan that will that your users will be assigned",
+ "Restore_Default_Settings": "Restore Default Settings",
+ "Restore_Default": "Restore Default",
+ "Branding_Image": "Branding Image",
+ "Logo": "Logo",
+ "Backgrounds": "Backgrounds",
+ "Background_Type": "Background Type",
+ "Static_Color": "Static Color",
+ "Gradient_Color": "Gradient Color",
+ "Image": "Image",
+ "Static_Background_Color": "Static Background Color",
+ "Gradient_Background_Colors": "Gradient Background Colors",
+ "Gradient_Direction": "Gradient Direction",
+ "Left_to_Right": "Left to Right",
+ "Top_to_Bottom": "Top to Bottom",
+ "Right_to_Left": "Right to Left",
+ "Bottom_to_Top": "Bottom to Top",
+ "Choose_Image": "Choose Image",
+ "Buttons": "Buttons",
+ "Slider_Contact_Us_Button_BG_Color": "Slider Contact Us Button BG Color",
+ "Slider_Contact_Us_Button_Text_Color": "Slider Contact Us Button Text Color",
+ "Slider_Navigation_Button_BG_Color": "Slider Navigation Button BG Color",
+ "Slider_Navigation_Button_Icon_Color": "Slider Navigation Button Icon Color",
+ "Trial_Button_BG_Color": "Trial Button BG Color",
+ "Trial_Button_Text_Color": "Trial Button Text Color",
+ "Footer_Settings": "Footer Settings",
+ "Footer_Background_Color": "Footer Background Color",
+ "Misc": "Misc",
+ "Title": "Title",
+ "Link": "Link",
+ "Button": "Button",
+ "Add_New_Slider": "Add New Slider",
+ "Welcome_to_the_Captive_Portal": "Welcome to the Captive Portal",
+ "Button_Link": "Button Link",
+ "Button_Text": "Button Text",
+ "Click_Here": "Click Here",
+ "Target_Link": "Target Link",
+ "Create_New_Advert": "Create New Advert",
+ "Add_New_Advertisement": "Add New Advertisement",
+ "Content": "Content",
+ "Enter_Interstitial_Content": "Enter Interstitial Content",
+ "Upload_File": "Upload File",
+ "File_size_limit__5MB_for_images__100MB_for_videos": "File size limit: 5MB for images, 100MB for videos",
+ "Target_URL": "Target URL",
+ "Create_Advert": "Create Advert",
+ "You_cannot_perform_this_action_in_Demo_mode": "You cannot perform this action in Demo mode",
+ "Income_Today": "Income Today",
+ "Income_This_Month": "Income This Month",
+ "Customers": "Customers",
+ "Monthly_Registered_Customers": "Monthly Registered Customers",
+ "Cron_has_not_run_for_over_1_hour__Please_check_your_setup_": "Cron has not run for over 1 hour. Please check your setup.",
+ "Total_Monthly_Sales": "Total Monthly Sales",
+ "Customers_Expired__Today": "Customers Expired, Today",
+ "Phone": "Phone",
+ "Created___Expired": "Created \/ Expired",
+ "Internet_Package": "Internet Package",
+ "year": "year",
+ "month": "month",
+ "week": "week",
+ "day": "day",
+ "hour": "hour",
+ "minute": "minute",
+ "second": "second",
+ "ago": "ago",
+ "All_Users_Insights": "All Users Insights",
+ "Activity_Log": "Activity Log"
}
\ No newline at end of file
diff --git a/system/updates.json b/system/updates.json
index c0d490d9..8b17a0ed 100644
--- a/system/updates.json
+++ b/system/updates.json
@@ -1,210 +1,211 @@
{
- "2023.8.9": [
- "ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;",
- "CREATE TABLE `tbl_customers_meta` (`id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,`meta_key` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `meta_value` longtext COLLATE utf8mb4_general_ci) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
- "ALTER TABLE `tbl_customers_meta` ADD PRIMARY KEY (`id`);",
- "ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;"
- ],
- "2023.8.14": [
- "ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
- "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;"
- ],
- "2023.8.23": [
- "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
- ],
- "2023.8.28": [
- "ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;",
- "ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;"
- ],
- "2023.9.5": [
- "DROP TABLE `tbl_language`;",
- "ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;"
- ],
- "2023.9.27": [
- "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
- ],
- "2023.9.28": [
- "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
- ],
- "2023.10.1": [
- "ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; "
- ],
- "2023.10.24": [
- "ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;"
- ],
- "2023.12.15": [
- "ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;"
- ],
- "2024.1.11": [
- "ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;"
- ],
- "2024.2.7": [
- "ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;",
- "ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;"
- ],
- "2024.2.12": [
- "ALTER TABLE `tbl_users` CHANGE `user_type` `user_type` ENUM('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
- ],
- "2024.2.15": [
- "ALTER TABLE `tbl_users` CHANGE `password` `password` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "ALTER TABLE `tbl_users` ADD `phone` VARCHAR(32) NOT NULL DEFAULT '' AFTER `password`, ADD `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `phone`, ADD `city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kota' AFTER `email`, ADD `subdistrict` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kecamatan' AFTER `city`, ADD `ward` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kelurahan' AFTER `subdistrict`;"
- ],
- "2024.2.16": [
- "ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;"
- ],
- "2024.2.19": [
- "CREATE TABLE `tbl_customers_fields` (`id` INT PRIMARY KEY AUTO_INCREMENT, `customer_id` INT NOT NULL, `field_name` VARCHAR(255) NOT NULL, `field_value` VARCHAR(255) NOT NULL, FOREIGN KEY (customer_id) REFERENCES tbl_customers(id));"
- ],
- "2024.2.20": [
- "ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;",
- "ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;"
- ],
- "2024.2.20.1": [
- "DROP TABLE IF EXISTS `tbl_customers_meta`;"
- ],
- "2024.2.23": [
- "ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;",
- "ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;"
- ],
- "2024.3.3": [
- "ALTER TABLE `tbl_plans` CHANGE `validity_unit` `validity_unit` ENUM('Mins','Hrs','Days','Months','Period') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
- ],
- "2024.3.12": [
- "ALTER TABLE `tbl_plans` CHANGE `allow_purchase` `prepaid` ENUM('yes','no') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'yes' COMMENT 'is prepaid';"
- ],
- "2024.3.14": [
- "ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;"
- ],
- "2024.3.19": [
- "ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;"
- ],
- "2024.3.19.1": [
- "ALTER TABLE `tbl_customers` ADD `account_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' AFTER `coordinates`;"
- ],
- "2024.3.19.2": [
- "ALTER TABLE `tbl_plans` ADD `plan_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' ;"
- ],
- "2023.3.20": [
- "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
- ],
- "2024.4.5": [
- "ALTER TABLE `tbl_payment_gateway` ADD `trx_invoice` VARCHAR(25) NOT NULL DEFAULT '' COMMENT 'from tbl_transactions' AFTER `paid_date`;"
- ],
- "2024.5.17": [
- "ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;"
- ],
- "2024.5.18": [
- "ALTER TABLE `tbl_customers` CHANGE `status` `status` ENUM('Active','Banned','Disabled','Inactive','Limited','Suspended') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active';"
- ],
- "2024.5.20": [
- "ALTER TABLE `tbl_customers` ADD `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `address`, ADD `district` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `city`, ADD `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `district`, ADD `zip` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `state`;"
- ],
- "2024.6.5": [
- "ALTER TABLE `tbl_plans` ADD `price_old` VARCHAR(40) NOT NULL DEFAULT '' AFTER `price`;",
- "ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;"
- ],
- "2024.6.10": [
- "ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;"
- ],
- "2024.6.11": [
- "ALTER TABLE `tbl_plans` ADD `plan_expired` INT NOT NULL DEFAULT '0' AFTER `pool`;",
- "ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;"
- ],
- "2024.6.19": [
- "ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;"
- ],
- "2024.6.21": [
- "ALTER TABLE `tbl_plans` ADD `on_login` TEXT NULL DEFAULT NULL AFTER `device`;",
- "ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;"
- ],
- "2024.7.6": [
- "CREATE TABLE IF NOT EXISTS `rad_acct` ( `id` bigint NOT NULL, `acctsessionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `realm` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasid` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasportid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `nasporttype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `framedipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `macaddr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
- "ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);",
- "ALTER TABLE `rad_acct` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;"
- ],
- "2024.7.24": [
- "ALTER TABLE `tbl_voucher` ADD `used_date` DATETIME NULL DEFAULT NULL AFTER `status`;",
- "UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;"
- ],
- "2024.8.1": [
- "ALTER TABLE `tbl_payment_gateway` CHANGE `gateway_trx_id` `gateway_trx_id` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';",
- "ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';"
- ],
- "2024.8.2": [
- "CREATE TABLE IF NOT EXISTS `tbl_customers_inbox` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `customer_id` int NOT NULL, `date_created` datetime NOT NULL, `date_read` datetime DEFAULT NULL, `subject` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `body` TEXT NULL DEFAULT NULL, `from` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'System' COMMENT 'System or Admin or Else',`admin_id` int NOT NULL DEFAULT '0' COMMENT 'other than admin is 0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
- ],
- "2024.8.5": [
- "ALTER TABLE `tbl_customers` ADD `pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
- "ALTER TABLE `tbl_customers` ADD `pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `pppoe_password`;"
- ],
- "2024.8.5.1": [
- "ALTER TABLE `tbl_routers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' AFTER `description`;",
- "ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;"
- ],
- "2024.8.6": [
- "ALTER TABLE `rad_acct` ADD `acctinputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `framedipaddress`;",
- "ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;"
- ],
- "2024.8.7": [
- "ALTER TABLE `tbl_customers` CHANGE `coordinates` `coordinates` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates';"
- ],
- "2024.8.28": [
- "ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;",
- "ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;"
- ],
- "2024.9.13": [
- "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';",
- "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
- "CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
- ],
- "2024.10.10": [
- "ALTER TABLE `tbl_users` ADD `login_token` VARCHAR(40) AFTER `last_login`;"
- ],
- "2024.10.17": [
- "CREATE TABLE IF NOT EXISTS `tbl_meta` ( `id` int UNSIGNED NOT NULL AUTO_INCREMENT, `tbl` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Table name', `tbl_id` int NOT NULL COMMENT 'table value id', `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, `value` mediumtext COLLATE utf8mb4_general_ci, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='This Table to add additional data for any table';"
- ],
- "2024.10.30": [
- "ALTER TABLE `tbl_users` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png' AFTER `root`;",
- "ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;"
- ],
- "2024.10.31": [
- "ALTER TABLE `tbl_customers` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg' AFTER `password`;"
- ],
- "2024.12.5.1": [
- "ALTER TABLE `tbl_transactions` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;",
- "ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;"
- ],
- "2024.12.16": [
- "CREATE TABLE `tbl_coupons` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(50) NOT NULL UNIQUE, `type` ENUM('fixed', 'percent') NOT NULL, `value` DECIMAL(10,2) NOT NULL, `description` TEXT NOT NULL, `max_usage` INT NOT NULL DEFAULT 1,`usage_count` INT NOT NULL DEFAULT 0,`status` ENUM('active', 'inactive') NOT NULL, `min_order_amount` DECIMAL(10,2) NOT NULL, `max_discount_amount` DECIMAL(10,2) NOT NULL, `start_date` DATE NOT NULL,`end_date` DATE NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);"
- ],
- "2024.12.20": [
- "ALTER TABLE `tbl_voucher` ADD `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;"
- ],
- "2025.1.23": [
- "ALTER TABLE `rad_acct` ADD `acctsessiontime` BIGINT(12) NOT NULL DEFAULT '0' AFTER `framedipaddress`;"
- ],
- "2025.2.14": [
- "CREATE TABLE IF NOT EXISTS `tbl_widgets` ( `id` int NOT NULL AUTO_INCREMENT, `orders` int NOT NULL DEFAULT '99', `position` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1. top 2. left 3. right 4. bottom',`enabled` tinyint(1) NOT NULL DEFAULT '1', `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `widget` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `content` text COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
- ],
- "2025.2.17" : [
- "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `enabled`, `title`, `widget`, `content`) VALUES (1, 1, 1, 1, 'Top Widget', 'top_widget', ''),(2, 2, 1, 1, 'Default Info', 'default_info_row', ''),(3, 1, 2, 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),(4, 2, 2, 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),(5, 3, 2, 1, 'Voucher Stocks', 'voucher_stocks', ''),(6, 4, 2, 1, 'Customer Expired', 'customer_expired', ''),(7, 1, 3, 1, 'Cron Monitor', 'cron_monitor', ''),(8, 2, 3, 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),(9, 3, 3, 1, 'Info Payment Gateway', 'info_payment_gateway', ''),(10, 4, 3, 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(11, 5, 3, 1, 'Activity Log', 'activity_log', '');"
- ],
- "2025.2.19" : [
- "ALTER TABLE `tbl_widgets` ADD `user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin' AFTER `position`;"
- ],
- "2025.2.21" : [
- "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (60, 1, 2, 'Customer', 1, 'Account Info', 'account_info', ''),(61, 3, 1, 'Customer', 1, 'Active Internet Plan', 'active_internet_plan', ''),(62, 4, 1, 'Customer', 1, 'Balance Transfer', 'balance_transfer', ''),(63, 1, 1, 'Customer', 1, 'Unpaid Order', 'unpaid_order', ''),(64, 2, 1, 'Customer', 1, 'Announcement', 'announcement', ''),(65, 5, 1, 'Customer', 1, 'Recharge A Friend', 'recharge_a_friend', ''),(66, 2, 2, 'Customer', 1, 'Voucher Activation', 'voucher_activation', '');"
- ],
- "2025.2.25" : [
- "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (30, 1, 1, 'Agent', 1, 'Top Widget', 'top_widget', ''), (31, 2, 1, 'Agent', 1, 'Default Info', 'default_info_row', ''), (32, 1, 2, 'Agent', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (33, 2, 2, 'Agent', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (34, 3, 2, 'Agent', 1, 'Voucher Stocks', 'voucher_stocks', ''), (35, 4, 2, 'Agent', 1, 'Customer Expired', 'customer_expired', ''), (36, 1, 3, 'Agent', 1, 'Cron Monitor', 'cron_monitor', ''), (37, 2, 3, 'Agent', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (38, 3, 3, 'Agent', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (39, 4, 3, 'Agent', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(40, 5, 3, 'Agent', 1, 'Activity Log', 'activity_log', '');",
- "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (41, 1, 1, 'Sales', 1, 'Top Widget', 'top_widget', ''), (42, 2, 1, 'Sales', 1, 'Default Info', 'default_info_row', ''), (43, 1, 2, 'Sales', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (44, 2, 2, 'Sales', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (45, 3, 2, 'Sales', 1, 'Voucher Stocks', 'voucher_stocks', ''), (46, 4, 2, 'Sales', 1, 'Customer Expired', 'customer_expired', ''), (47, 1, 3, 'Sales', 1, 'Cron Monitor', 'cron_monitor', ''), (48, 2, 3, 'Sales', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (49, 3, 3, 'Sales', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (50, 4, 3, 'Sales', 1, 'Graph Customers Insight', 'graph_customers_insight', ''), (51, 5, 3, 'Sales', 1, 'Activity Log', 'activity_log', '');"
- ],
- "2025.3.5" : [
- "CREATE TABLE IF NOT EXISTS `tbl_message_logs` ( `id` SERIAL PRIMARY KEY, `message_type` VARCHAR(50), `recipient` VARCHAR(255), `message_content` TEXT, `status` VARCHAR(50), `error_message` TEXT, `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
- ]
-}
\ No newline at end of file
+ "2023.8.9": [
+ "ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;",
+ "CREATE TABLE `tbl_customers_meta` (`id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,`meta_key` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `meta_value` longtext COLLATE utf8mb4_general_ci) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
+ "ALTER TABLE `tbl_customers_meta` ADD PRIMARY KEY (`id`);",
+ "ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;"
+ ],
+ "2023.8.14": [
+ "ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
+ "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;"
+ ],
+ "2023.8.23": [
+ "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
+ ],
+ "2023.8.28": [
+ "ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;",
+ "ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;"
+ ],
+ "2023.9.5": [
+ "DROP TABLE `tbl_language`;",
+ "ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;"
+ ],
+ "2023.9.27": [
+ "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
+ ],
+ "2023.9.28": [
+ "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
+ ],
+ "2023.10.1": [
+ "ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; "
+ ],
+ "2023.10.24": [
+ "ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;"
+ ],
+ "2023.12.15": [
+ "ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;"
+ ],
+ "2024.1.11": [
+ "ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;"
+ ],
+ "2024.2.7": [
+ "ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;",
+ "ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;"
+ ],
+ "2024.2.12": [
+ "ALTER TABLE `tbl_users` CHANGE `user_type` `user_type` ENUM('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
+ ],
+ "2024.2.15": [
+ "ALTER TABLE `tbl_users` CHANGE `password` `password` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_users` ADD `phone` VARCHAR(32) NOT NULL DEFAULT '' AFTER `password`, ADD `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `phone`, ADD `city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kota' AFTER `email`, ADD `subdistrict` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kecamatan' AFTER `city`, ADD `ward` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kelurahan' AFTER `subdistrict`;"
+ ],
+ "2024.2.16": [
+ "ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;"
+ ],
+ "2024.2.19": [
+ "CREATE TABLE `tbl_customers_fields` (`id` INT PRIMARY KEY AUTO_INCREMENT, `customer_id` INT NOT NULL, `field_name` VARCHAR(255) NOT NULL, `field_value` VARCHAR(255) NOT NULL, FOREIGN KEY (customer_id) REFERENCES tbl_customers(id));"
+ ],
+ "2024.2.20": [
+ "ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;",
+ "ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;"
+ ],
+ "2024.2.20.1": ["DROP TABLE IF EXISTS `tbl_customers_meta`;"],
+ "2024.2.23": [
+ "ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;",
+ "ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;"
+ ],
+ "2024.3.3": [
+ "ALTER TABLE `tbl_plans` CHANGE `validity_unit` `validity_unit` ENUM('Mins','Hrs','Days','Months','Period') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
+ ],
+ "2024.3.12": [
+ "ALTER TABLE `tbl_plans` CHANGE `allow_purchase` `prepaid` ENUM('yes','no') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'yes' COMMENT 'is prepaid';"
+ ],
+ "2024.3.14": [
+ "ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;"
+ ],
+ "2024.3.19": [
+ "ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;"
+ ],
+ "2024.3.19.1": [
+ "ALTER TABLE `tbl_customers` ADD `account_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' AFTER `coordinates`;"
+ ],
+ "2024.3.19.2": [
+ "ALTER TABLE `tbl_plans` ADD `plan_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' ;"
+ ],
+ "2023.3.20": [
+ "ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
+ ],
+ "2024.4.5": [
+ "ALTER TABLE `tbl_payment_gateway` ADD `trx_invoice` VARCHAR(25) NOT NULL DEFAULT '' COMMENT 'from tbl_transactions' AFTER `paid_date`;"
+ ],
+ "2024.5.17": [
+ "ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;"
+ ],
+ "2024.5.18": [
+ "ALTER TABLE `tbl_customers` CHANGE `status` `status` ENUM('Active','Banned','Disabled','Inactive','Limited','Suspended') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active';"
+ ],
+ "2024.5.20": [
+ "ALTER TABLE `tbl_customers` ADD `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `address`, ADD `district` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `city`, ADD `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `district`, ADD `zip` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `state`;"
+ ],
+ "2024.6.5": [
+ "ALTER TABLE `tbl_plans` ADD `price_old` VARCHAR(40) NOT NULL DEFAULT '' AFTER `price`;",
+ "ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;"
+ ],
+ "2024.6.10": [
+ "ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;"
+ ],
+ "2024.6.11": [
+ "ALTER TABLE `tbl_plans` ADD `plan_expired` INT NOT NULL DEFAULT '0' AFTER `pool`;",
+ "ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;"
+ ],
+ "2024.6.19": [
+ "ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;"
+ ],
+ "2024.6.21": [
+ "ALTER TABLE `tbl_plans` ADD `on_login` TEXT NULL DEFAULT NULL AFTER `device`;",
+ "ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;"
+ ],
+ "2024.7.6": [
+ "CREATE TABLE IF NOT EXISTS `rad_acct` ( `id` bigint NOT NULL, `acctsessionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `realm` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasid` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasportid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `nasporttype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `framedipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `macaddr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
+ "ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);",
+ "ALTER TABLE `rad_acct` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;"
+ ],
+ "2024.7.24": [
+ "ALTER TABLE `tbl_voucher` ADD `used_date` DATETIME NULL DEFAULT NULL AFTER `status`;",
+ "UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;"
+ ],
+ "2024.8.1": [
+ "ALTER TABLE `tbl_payment_gateway` CHANGE `gateway_trx_id` `gateway_trx_id` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';",
+ "ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';"
+ ],
+ "2024.8.2": [
+ "CREATE TABLE IF NOT EXISTS `tbl_customers_inbox` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `customer_id` int NOT NULL, `date_created` datetime NOT NULL, `date_read` datetime DEFAULT NULL, `subject` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `body` TEXT NULL DEFAULT NULL, `from` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'System' COMMENT 'System or Admin or Else',`admin_id` int NOT NULL DEFAULT '0' COMMENT 'other than admin is 0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
+ ],
+ "2024.8.5": [
+ "ALTER TABLE `tbl_customers` ADD `pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
+ "ALTER TABLE `tbl_customers` ADD `pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `pppoe_password`;"
+ ],
+ "2024.8.5.1": [
+ "ALTER TABLE `tbl_routers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' AFTER `description`;",
+ "ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;"
+ ],
+ "2024.8.6": [
+ "ALTER TABLE `rad_acct` ADD `acctinputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `framedipaddress`;",
+ "ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;"
+ ],
+ "2024.8.7": [
+ "ALTER TABLE `tbl_customers` CHANGE `coordinates` `coordinates` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates';"
+ ],
+ "2024.8.28": [
+ "ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;",
+ "ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;"
+ ],
+ "2024.9.13": [
+ "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';",
+ "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
+ ],
+ "2024.10.10": [
+ "ALTER TABLE `tbl_users` ADD `login_token` VARCHAR(40) AFTER `last_login`;"
+ ],
+ "2024.10.17": [
+ "CREATE TABLE IF NOT EXISTS `tbl_meta` ( `id` int UNSIGNED NOT NULL AUTO_INCREMENT, `tbl` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Table name', `tbl_id` int NOT NULL COMMENT 'table value id', `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, `value` mediumtext COLLATE utf8mb4_general_ci, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='This Table to add additional data for any table';"
+ ],
+ "2024.10.30": [
+ "ALTER TABLE `tbl_users` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png' AFTER `root`;",
+ "ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;"
+ ],
+ "2024.10.31": [
+ "ALTER TABLE `tbl_customers` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg' AFTER `password`;"
+ ],
+ "2024.12.5.1": [
+ "ALTER TABLE `tbl_transactions` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;",
+ "ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;"
+ ],
+ "2024.12.16": [
+ "CREATE TABLE `tbl_coupons` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(50) NOT NULL UNIQUE, `type` ENUM('fixed', 'percent') NOT NULL, `value` DECIMAL(10,2) NOT NULL, `description` TEXT NOT NULL, `max_usage` INT NOT NULL DEFAULT 1,`usage_count` INT NOT NULL DEFAULT 0,`status` ENUM('active', 'inactive') NOT NULL, `min_order_amount` DECIMAL(10,2) NOT NULL, `max_discount_amount` DECIMAL(10,2) NOT NULL, `start_date` DATE NOT NULL,`end_date` DATE NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);"
+ ],
+ "2024.12.20": [
+ "ALTER TABLE `tbl_voucher` ADD `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;"
+ ],
+ "2025.1.23": [
+ "ALTER TABLE `rad_acct` ADD `acctsessiontime` BIGINT(12) NOT NULL DEFAULT '0' AFTER `framedipaddress`;"
+ ],
+ "2025.2.14": [
+ "CREATE TABLE IF NOT EXISTS `tbl_widgets` ( `id` int NOT NULL AUTO_INCREMENT, `orders` int NOT NULL DEFAULT '99', `position` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1. top 2. left 3. right 4. bottom',`enabled` tinyint(1) NOT NULL DEFAULT '1', `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `widget` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `content` text COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
+ ],
+ "2025.2.17": [
+ "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `enabled`, `title`, `widget`, `content`) VALUES (1, 1, 1, 1, 'Top Widget', 'top_widget', ''),(2, 2, 1, 1, 'Default Info', 'default_info_row', ''),(3, 1, 2, 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),(4, 2, 2, 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),(5, 3, 2, 1, 'Voucher Stocks', 'voucher_stocks', ''),(6, 4, 2, 1, 'Customer Expired', 'customer_expired', ''),(7, 1, 3, 1, 'Cron Monitor', 'cron_monitor', ''),(8, 2, 3, 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),(9, 3, 3, 1, 'Info Payment Gateway', 'info_payment_gateway', ''),(10, 4, 3, 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(11, 5, 3, 1, 'Activity Log', 'activity_log', '');"
+ ],
+ "2025.2.19": [
+ "ALTER TABLE `tbl_widgets` ADD `user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin' AFTER `position`;"
+ ],
+ "2025.2.21": [
+ "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (60, 1, 2, 'Customer', 1, 'Account Info', 'account_info', ''),(61, 3, 1, 'Customer', 1, 'Active Internet Plan', 'active_internet_plan', ''),(62, 4, 1, 'Customer', 1, 'Balance Transfer', 'balance_transfer', ''),(63, 1, 1, 'Customer', 1, 'Unpaid Order', 'unpaid_order', ''),(64, 2, 1, 'Customer', 1, 'Announcement', 'announcement', ''),(65, 5, 1, 'Customer', 1, 'Recharge A Friend', 'recharge_a_friend', ''),(66, 2, 2, 'Customer', 1, 'Voucher Activation', 'voucher_activation', '');"
+ ],
+ "2025.2.25": [
+ "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (30, 1, 1, 'Agent', 1, 'Top Widget', 'top_widget', ''), (31, 2, 1, 'Agent', 1, 'Default Info', 'default_info_row', ''), (32, 1, 2, 'Agent', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (33, 2, 2, 'Agent', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (34, 3, 2, 'Agent', 1, 'Voucher Stocks', 'voucher_stocks', ''), (35, 4, 2, 'Agent', 1, 'Customer Expired', 'customer_expired', ''), (36, 1, 3, 'Agent', 1, 'Cron Monitor', 'cron_monitor', ''), (37, 2, 3, 'Agent', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (38, 3, 3, 'Agent', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (39, 4, 3, 'Agent', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(40, 5, 3, 'Agent', 1, 'Activity Log', 'activity_log', '');",
+ "INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (41, 1, 1, 'Sales', 1, 'Top Widget', 'top_widget', ''), (42, 2, 1, 'Sales', 1, 'Default Info', 'default_info_row', ''), (43, 1, 2, 'Sales', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (44, 2, 2, 'Sales', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (45, 3, 2, 'Sales', 1, 'Voucher Stocks', 'voucher_stocks', ''), (46, 4, 2, 'Sales', 1, 'Customer Expired', 'customer_expired', ''), (47, 1, 3, 'Sales', 1, 'Cron Monitor', 'cron_monitor', ''), (48, 2, 3, 'Sales', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (49, 3, 3, 'Sales', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (50, 4, 3, 'Sales', 1, 'Graph Customers Insight', 'graph_customers_insight', ''), (51, 5, 3, 'Sales', 1, 'Activity Log', 'activity_log', '');"
+ ],
+ "2025.3.5": [
+ "CREATE TABLE IF NOT EXISTS `tbl_message_logs` ( `id` SERIAL PRIMARY KEY, `message_type` VARCHAR(50), `recipient` VARCHAR(255), `message_content` TEXT, `status` VARCHAR(50), `error_message` TEXT, `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
+ ],
+ "2025.3.10": [
+ "CREATE TABLE IF NOT EXISTS `tbl_invoices` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `number` VARCHAR(50) NOT NULL, `customer_id` INT NOT NULL, `fullname` VARCHAR(100) NOT NULL, `email` VARCHAR(100) NOT NULL, `address` TEXT, `status` ENUM('Unpaid', 'Paid', 'Cancelled') NOT NULL DEFAULT 'Unpaid', `due_date` DATETIME NOT NULL, `filename` VARCHAR(255), `amount` DECIMAL(10, 2) NOT NULL, `data` JSON NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);"
+ ]
+}
diff --git a/system/uploads/invoices/index.html b/system/uploads/invoices/index.html
new file mode 100644
index 00000000..e69de29b
diff --git a/ui/ui/admin/customers/list.tpl b/ui/ui/admin/customers/list.tpl
index 8906dd41..3d2ab3f0 100644
--- a/ui/ui/admin/customers/list.tpl
+++ b/ui/ui/admin/customers/list.tpl
@@ -18,9 +18,9 @@
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
CSV
+ href="{Text::url('customers/csv&token=', $csrf_token)}" onclick="return ask(this, '{Lang::T("
+ This will export to CSV")}?')">
CSV
{/if}
{Lang::T('Manage Contact')}
@@ -205,14 +205,15 @@
-
+
{Lang::T('All')}
{Lang::T('Email')}
{Lang::T('Inbox')}
{Lang::T('SMS')}
{Lang::T('WhatsApp')}
-
+
@@ -260,6 +261,8 @@
$('#sendMessageButton').on('click', function () {
const message = $('#messageContent').val().trim();
const messageType = $('#messageType').val();
+ const subject = $('#subject-content').val().trim();
+
if (!message) {
Swal.fire({
@@ -271,6 +274,16 @@
return;
}
+ if (messageType == 'all' || messageType == 'inbox' || messageType == 'email' && !subject) {
+ Swal.fire({
+ title: 'Error!',
+ text: "{Lang::T('Please enter a subject for the message.')}",
+ icon: 'error',
+ confirmButtonText: 'OK'
+ });
+ return;
+ }
+
// Disable the button and show loading text
$(this).prop('disabled', true).text('{Lang::T('Sending...')}');
@@ -332,4 +345,31 @@
});
});
+
{include file = "sections/footer.tpl" }
\ No newline at end of file
diff --git a/ui/ui/admin/header.tpl b/ui/ui/admin/header.tpl
index 9ab008c6..18ad53de 100644
--- a/ui/ui/admin/header.tpl
+++ b/ui/ui/admin/header.tpl
@@ -14,6 +14,7 @@
+
@@ -29,7 +30,7 @@
{if isset($xheader)}
- {$xheader}
+ {$xheader}
{/if}
@@ -77,8 +78,8 @@
@@ -395,11 +403,11 @@
{if $_c['maintenance_mode'] == 1}
-
-
{Lang::T('The website is currently in maintenance mode, this means that some or all functionality may be
+
+
{Lang::T('The website is currently in maintenance mode, this means that some or all functionality may be
unavailable to regular users during this time.')} {Lang::T('Turn Off')}
-
+ href="{Text::url('settings/maintenance')}">{Lang::T('Turn Off')}
+
{/if}
@@ -411,19 +419,19 @@
{if isset($notify)}
-
-{/if}
\ No newline at end of file
+
+ {/if}
\ No newline at end of file
diff --git a/ui/ui/admin/invoices/list.tpl b/ui/ui/admin/invoices/list.tpl
new file mode 100644
index 00000000..a2531310
--- /dev/null
+++ b/ui/ui/admin/invoices/list.tpl
@@ -0,0 +1,57 @@
+{include file="sections/header.tpl"}
+
+
+
+
{Lang::T('Invoices')}
+
+
+
+
+ {Lang::T('Invoice No')}
+ {Lang::T('Customer Name')}
+ {Lang::T('Email')}
+ {Lang::T('Address')}
+ {Lang::T('Amount')}
+ {Lang::T('Status')}
+ {Lang::T('Created Date')}
+ {Lang::T('Due Date')}
+ {Lang::T('Actions')}
+
+
+
+ {foreach $invoices as $invoice}
+
+ {$invoice->number}
+ {$invoice->fullname}
+ {$invoice->email}
+ {$invoice->address}
+ {$invoice->amount}
+
+ {if $invoice->status == 'paid'}
+ {Lang::T('Paid')}
+ {elseif $invoice->status == 'unpaid'}
+ {Lang::T('Unpaid')}
+ {else}
+ {Lang::T('Pending')}
+ {/if}
+
+ {$invoice->created_at}
+ {$invoice->due_date}
+
+ {Lang::T('View')}
+
+
+
+ {/foreach}
+
+
+
+
+
+
+
+
+{include file="sections/footer.tpl"}
\ No newline at end of file
diff --git a/ui/ui/admin/message/bulk.tpl b/ui/ui/admin/message/bulk.tpl
index a8a57dba..7d7c677a 100644
--- a/ui/ui/admin/message/bulk.tpl
+++ b/ui/ui/admin/message/bulk.tpl
@@ -30,7 +30,8 @@
{Lang::T('All')}
{Lang::T('PPPoE')}
- {Lang::T('Hotspot')}
+ {Lang::T('Hotspot')}
+
{Lang::T('VPN')}
@@ -41,8 +42,10 @@
{Lang::T('All Customers')}
{Lang::T('New Customers')}
- {Lang::T('Expired Customers')}
- {Lang::T('Active Customers')}
+ {Lang::T('Expired
+ Customers')}
+ {Lang::T('Active Customers')}
+
@@ -50,9 +53,13 @@
{Lang::T('Send Via')}
+ {Lang::T('All Channels')}
+ {Lang::T('Inbox')}
+ {Lang::T('Email')}
{Lang::T('SMS')}
{Lang::T('WhatsApp')}
- {Lang::T('SMS and WhatsApp')}
+ {Lang::T('SMS and WhatsApp')}
+
@@ -72,10 +79,21 @@
{Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')}
+
+
{Lang::T('Subject')}
+
+
+
+
+ {Lang::T('You can also use the below placeholders here too')}.
+
+
@@ -112,7 +131,7 @@
{Lang::T('Customer')}
- {Lang::T('Phone')}
+ {Lang::T('Channel')}
{Lang::T('Status')}
{Lang::T('Message')}
{Lang::T('Router')}
@@ -126,6 +145,34 @@
+
{literal}
{include file="sections/footer.tpl"}
\ No newline at end of file
diff --git a/ui/ui/admin/print/by-period.tpl b/ui/ui/admin/print/by-period.tpl
index 78f20828..01f7821f 100644
--- a/ui/ui/admin/print/by-period.tpl
+++ b/ui/ui/admin/print/by-period.tpl
@@ -26,6 +26,7 @@
{Lang::T('Username')}
+ {Lang::T('Fullname')}
{Lang::T('Plan Name')}
{Lang::T('Type')}
{Lang::T('Plan Price')}
@@ -36,6 +37,7 @@
{foreach $d as $ds}
{$ds['username']}
+ {$ds['fullname']}
{$ds['plan_name']}
{$ds['type']}
{Lang::moneyFormat($ds['price'])}
diff --git a/ui/ui/admin/reports/activation.tpl b/ui/ui/admin/reports/activation.tpl
index 4ced97a9..1dc09a94 100644
--- a/ui/ui/admin/reports/activation.tpl
+++ b/ui/ui/admin/reports/activation.tpl
@@ -33,6 +33,7 @@
{Lang::T('Invoice')}
{Lang::T('Username')}
+ {Lang::T('Fullname')}
{Lang::T('Plan Name')}
{Lang::T('Plan Price')}
{Lang::T('Type')}
@@ -48,6 +49,7 @@
style="cursor:pointer;">{$ds['invoice']}
{$ds['username']}
+ {$ds['fullname']}
{$ds['plan_name']}
{Lang::moneyFormat($ds['price'])}
{$ds['type']}
diff --git a/ui/ui/admin/reports/list.tpl b/ui/ui/admin/reports/list.tpl
index deb249b6..356ebec0 100644
--- a/ui/ui/admin/reports/list.tpl
+++ b/ui/ui/admin/reports/list.tpl
@@ -94,10 +94,11 @@
-
+
{Lang::T('Username')}
+ {Lang::T('Fullname')}
{Lang::T('Type')}
{Lang::T('Plan Name')}
{Lang::T('Plan Price')}
@@ -111,6 +112,7 @@
{foreach $d as $ds}
{$ds['username']}
+ {$ds['fullname']}
{$ds['type']}
{$ds['plan_name']}
{Lang::moneyFormat($ds['price'])}
@@ -122,9 +124,8 @@
{/foreach}
{Lang::T('Total')}
-
{Lang::moneyFormat($dr)}
-
+
diff --git a/ui/ui/admin/reports/period-view.tpl b/ui/ui/admin/reports/period-view.tpl
index d310248e..4283013c 100644
--- a/ui/ui/admin/reports/period-view.tpl
+++ b/ui/ui/admin/reports/period-view.tpl
@@ -37,6 +37,7 @@
{Lang::T('Username')}
+ {Lang::T('Fullname')}
{Lang::T('Type')}
{Lang::T('Plan Name')}
{Lang::T('Plan Price')}
@@ -50,6 +51,7 @@
{foreach $d as $ds}
{$ds['username']}
+ {$ds['fullname']}
{$ds['type']}
{$ds['plan_name']}
{Lang::moneyFormat($ds['price'])}
diff --git a/ui/ui/admin/settings/app.tpl b/ui/ui/admin/settings/app.tpl
index b5164781..ba359055 100644
--- a/ui/ui/admin/settings/app.tpl
+++ b/ui/ui/admin/settings/app.tpl
@@ -155,7 +155,7 @@
@@ -254,6 +254,37 @@
+
+
+
+
+
+
+ {Lang::T('Save Changes')}
+
+
+
+
+
diff --git a/ui/ui/customer/footer.tpl b/ui/ui/customer/footer.tpl
index 8d70ccee..1cdc6685 100644
--- a/ui/ui/customer/footer.tpl
+++ b/ui/ui/customer/footer.tpl
@@ -1,25 +1,25 @@
{if isset($_c['CompanyFooter'])}
-
- {$_c['CompanyFooter']}
-
-
+
+ {$_c['CompanyFooter']}
+
+
{else}
-
+
{/if}
@@ -50,156 +50,175 @@
{if isset($xfooter)}
- {$xfooter}
+{$xfooter}
{/if}
{if $_c['tawkto'] != ''}
-
-
-
- {/if}
+ (function () {
+ var s1 = document.createElement("script"),
+ s0 = document.getElementsByTagName("script")[0];
+ s1.async = true;
+ s1.src = 'https://embed.tawk.to/{$_c['tawkto']}';
+ s1.charset = 'UTF-8';
+ s1.setAttribute('crossorigin', '*');
+ s0.parentNode.insertBefore(s1, s0);
+ })();
+
+
+{/if}
-
+ });
+
{literal}
-
+ return null;
+ }
+
{/literal}