Merge pull request 'Development' (#2) from Development into master

Reviewed-on: #2
This commit is contained in:
kevinowino869 2025-04-14 11:00:04 +02:00
commit 915af6fc03
24 changed files with 1410 additions and 765 deletions

2
.gitignore vendored
View File

@ -58,3 +58,5 @@ docs/**
!docs/insomnia.rest.json !docs/insomnia.rest.json
!system/uploads/paid.png !system/uploads/paid.png
system/uploads/invoices/** system/uploads/invoices/**
!system/uploads/invoices/
!system/uploads/invoices/index.html

View File

@ -8,12 +8,12 @@ class Invoice
{ {
try { try {
if (empty($invoiceData['invoice'])) { 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'); $template = Lang::getNotifText('email_invoice');
if (!$template) { if (!$template) {
throw new Exception("Invoice template not found"); throw new Exception(Lang::T("Invoice template not found"));
} }
if (strpos($template, '<body') === false) { if (strpos($template, '<body') === false) {
@ -47,10 +47,14 @@ class Invoice
// Save PDF // Save PDF
$filename = "invoice_{$invoiceData['invoice']}.pdf"; $filename = "invoice_{$invoiceData['invoice']}.pdf";
$outputPath = "system/uploads/invoices/{$filename}"; $outputPath = "system/uploads/invoices/{$filename}";
$dir = dirname($outputPath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$mpdf->Output($outputPath, 'F'); $mpdf->Output($outputPath, 'F');
if (!file_exists($outputPath)) { if (!file_exists($outputPath)) {
throw new Exception("Failed to save PDF file"); throw new Exception(Lang::T("Failed to save PDF file"));
} }
return $filename; return $filename;
@ -67,7 +71,7 @@ class Invoice
return preg_replace_callback('/\[\[(\w+)\]\]/', function ($matches) use ($invoiceData) { return preg_replace_callback('/\[\[(\w+)\]\]/', function ($matches) use ($invoiceData) {
$key = $matches[1]; $key = $matches[1];
if (!isset($invoiceData[$key])) { if (!isset($invoiceData[$key])) {
_log("Missing invoice key: $key"); _log(Lang::T("Missing invoice key: ") . $key);
return ''; return '';
} }
@ -80,92 +84,84 @@ class Invoice
} }
if ($key === 'bill_rows') { if ($key === 'bill_rows') {
return html_entity_decode($invoiceData[$key]); return $invoiceData[$key];
} }
return htmlspecialchars($invoiceData[$key] ?? ''); return htmlspecialchars($invoiceData[$key] ?? '');
}, $template); }, $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; global $config, $root_path, $UPLOAD_PATH;
if (empty($config['currency_code'])) { // Set default currency code
$config['currency_code'] = '$'; $config['currency_code'] ??= '$';
}
$account = ORM::for_table('tbl_customers')->find_one($userId); $account = ORM::for_table('tbl_customers')->find_one($userId);
self::validateAccount($account);
if (!$account) { if (!$invoiceNo) {
_log("Failed to send invoice: User not found"); $invoiceNo = "INV-" . Package::_raid();
sendTelegram("Failed to send invoice: User not found");
return false;
} }
// 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) { if (!$invoice) {
_log("Failed to send invoice: Transaction not found"); throw new Exception(Lang::T("Transaction not found for username: ") . $account->username);
sendTelegram("Failed to send invoice: Transaction not found");
return false;
} }
[$additionalBills, $add_cost] = User::getBills($account->id); // Get additional bills if not provided
if (empty($bills)) {
$invoiceItems = [ [$bills, $add_cost] = User::getBills($account->id);
[
'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}");
}
}
} }
$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; $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); $payLink = self::generatePaymentLink($account, $invoice, $status);
if (!empty($token['token'])) { $logo = self::getCompanyLogo($UPLOAD_PATH, $root_path);
$tur = ORM::for_table('tbl_user_recharges')
->where('customer_id', $account->id)
->where('namebp', $invoice->plan_name);
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 = [ $invoiceData = [
'invoice' => "INV-" . Package::_raid(), 'invoice' => $invoiceNo,
'fullname' => $account->fullname, 'fullname' => $account->fullname,
'email' => $account->email, 'email' => $account->email,
'address' => $account->address, 'address' => $account->address,
@ -182,31 +178,90 @@ class Invoice
'payment_link' => $payLink 'payment_link' => $payLink
]; ];
if (!isset($invoiceData['bill_rows']) || empty($invoiceData['bill_rows'])) { if (empty($invoiceData['bill_rows'])) {
_log("Invoice Error: Bill rows data is empty."); throw new Exception(Lang::T("Bill rows data is empty."));
} }
$filename = self::generateInvoice($invoiceData); $filename = self::generateInvoice($invoiceData);
if (!$filename) {
if ($filename) { throw new Exception(Lang::T("Failed to generate invoice PDF"));
$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;
}
} }
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) private static function generateBillRows($items, $currency, $subtotal, $tax_rate, $tax, $total)
@ -222,9 +277,11 @@ class Invoice
<tbody>"; <tbody>";
foreach ($items as $item) { foreach ($items as $item) {
$desc = htmlspecialchars($item['description'], ENT_QUOTES);
$details = htmlspecialchars($item['details'], ENT_QUOTES);
$html .= "<tr> $html .= "<tr>
<td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$item['description']}</td> <td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$desc}</td>
<td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$item['details']}</td> <td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$details}</td>
<td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$currency}" . number_format((float) $item['amount'], 2) . "</td> <td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$currency}" . number_format((float) $item['amount'], 2) . "</td>
</tr>"; </tr>";
} }
@ -247,6 +304,44 @@ class Invoice
return $html; 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;
}
} }

View File

@ -46,7 +46,7 @@ class Message
$txts = str_split($txt, 160); $txts = str_split($txt, 160);
try { try {
foreach ($txts as $txt) { foreach ($txts as $txt) {
self::sendSMS($config['sms_url'], $phone, $txt); self::sendSMS($phone, $txt);
self::logMessage('SMS', $phone, $txt, 'Success'); self::logMessage('SMS', $phone, $txt, 'Success');
} }
} catch (Throwable $e) { } catch (Throwable $e) {
@ -140,6 +140,7 @@ class Message
} }
mail($to, $subject, $body, $attr); mail($to, $subject, $body, $attr);
self::logMessage('Email', $to, $body, 'Success'); self::logMessage('Email', $to, $body, 'Success');
return true;
} else { } else {
$mail = new PHPMailer(); $mail = new PHPMailer();
$mail->isSMTP(); $mail->isSMTP();
@ -188,8 +189,10 @@ class Message
if (!$mail->send()) { if (!$mail->send()) {
$errorMessage = Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo; $errorMessage = Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo;
self::logMessage('Email', $to, $body, 'Error', $errorMessage); self::logMessage('Email', $to, $body, 'Error', $errorMessage);
return false;
} else { } else {
self::logMessage('Email', $to, $body, 'Success'); self::logMessage('Email', $to, $body, 'Success');
return true;
} }
//<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;"> //<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">
@ -396,9 +399,11 @@ class Message
$v->body = nl2br($body); $v->body = nl2br($body);
$v->save(); $v->save();
self::logMessage("Inbox", $user->username, $body, "Success"); self::logMessage("Inbox", $user->username, $body, "Success");
return true;
} catch (Throwable $e) { } catch (Throwable $e) {
$errorMessage = Lang::T("Error adding message to inbox: " . $e->getMessage()); $errorMessage = Lang::T("Error adding message to inbox: " . $e->getMessage());
self::logMessage('Inbox', $user->username, $body, 'Error', $errorMessage); self::logMessage('Inbox', $user->username, $body, 'Error', $errorMessage);
return false;
} }
} }

View File

@ -71,8 +71,8 @@ switch ($action) {
if (count($plns) > 0) { if (count($plns) > 0) {
$query->where_in('plan_name', $plns); $query->where_in('plan_name', $plns);
} }
$x = $query->find_array(); $x = $query->find_array();
$xy = $query->sum('price'); $xy = $query->sum('price');
$ui->assign('sd', $sd); $ui->assign('sd', $sd);
$ui->assign('ed', $ed); $ui->assign('ed', $ed);
@ -114,7 +114,10 @@ switch ($action) {
$query = ORM::for_table('tbl_transactions') $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("$sd $ts"))
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te")) ->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) { if (count($tps) > 0) {
$query->where_in('type', $tps); $query->where_in('type', $tps);
} }
@ -131,13 +134,13 @@ switch ($action) {
if (count($plns) > 0) { if (count($plns) > 0) {
$query->where_in('plan_name', $plns); $query->where_in('plan_name', $plns);
} }
$x = $query->find_array(); $x = $query->find_array();
$xy = $query->sum('price'); $xy = $query->sum('price');
$title = ' Reports [' . $mdate . ']'; $title = ' Reports [' . $mdate . ']';
$title = str_replace('-', ' ', $title); $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')) { if (file_exists($UPLOAD_PATH . '/logo.png')) {
$logo = $UPLOAD_URL_PATH . '/logo.png'; $logo = $UPLOAD_URL_PATH . '/logo.png';
} else { } else {
@ -154,10 +157,11 @@ switch ($action) {
</div> </div>
<div id="logo"><img id="image" src="' . $logo . '" alt="logo" /></div> <div id="logo"><img id="image" src="' . $logo . '" alt="logo" /></div>
</div> </div>
<div id="header">' . Lang::T('All Transactions at Date') . ': ' . Lang::dateAndTimeFormat($sd, $ts) .' - '. Lang::dateAndTimeFormat($ed, $te) . '</div> <div id="header">' . Lang::T('All Transactions at Date') . ': ' . Lang::dateAndTimeFormat($sd, $ts) . ' - ' . Lang::dateAndTimeFormat($ed, $te) . '</div>
<table id="customers"> <table id="customers">
<tr> <tr>
<th>' . Lang::T('Username') . '</th> <th>' . Lang::T('Username') . '</th>
<th>' . Lang::T('Fullname') . '</th>
<th>' . Lang::T('Plan Name') . '</th> <th>' . Lang::T('Plan Name') . '</th>
<th>' . Lang::T('Type') . '</th> <th>' . Lang::T('Type') . '</th>
<th>' . Lang::T('Plan Price') . '</th> <th>' . Lang::T('Plan Price') . '</th>
@ -170,6 +174,7 @@ switch ($action) {
foreach ($x as $value) { foreach ($x as $value) {
$username = $value['username']; $username = $value['username'];
$fullname = $value['fullname'];
$plan_name = $value['plan_name']; $plan_name = $value['plan_name'];
$type = $value['type']; $type = $value['type'];
$price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']);
@ -181,6 +186,7 @@ switch ($action) {
$html .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . " $html .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . "
<td>$username</td> <td>$username</td>
<td>$fullname</td>
<td>$plan_name</td> <td>$plan_name</td>
<td>$type</td> <td>$type</td>
<td align='right'>$price</td> <td align='right'>$price</td>
@ -245,7 +251,7 @@ $style
$html $html
EOF; EOF;
$mpdf->WriteHTML($nhtml); $mpdf->WriteHTML($nhtml);
$mpdf->Output('phpnuxbill_reports_'.date('Ymd_His') . '.pdf', 'D'); $mpdf->Output('phpnuxbill_reports_' . date('Ymd_His') . '.pdf', 'D');
} else { } else {
echo 'No Data'; echo 'No Data';
} }
@ -258,13 +264,17 @@ EOF;
$stype = _post('stype'); $stype = _post('stype');
$d = ORM::for_table('tbl_transactions'); $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 != '') { if ($stype != '') {
$d->where('type', $stype); $d->where('type', $stype);
} }
$d->where_gte('recharged_on', $fdate); $d->where_gte('recharged_on', $fdate);
$d->where_lte('recharged_on', $tdate); $d->where_lte('recharged_on', $tdate);
$d->order_by_desc('id'); $d->order_by_desc('id');
$x = $d->find_many(); $x = $d->find_many();
$dr = ORM::for_table('tbl_transactions'); $dr = ORM::for_table('tbl_transactions');
if ($stype != '') { if ($stype != '') {
@ -290,6 +300,10 @@ EOF;
$tdate = _post('tdate'); $tdate = _post('tdate');
$stype = _post('stype'); $stype = _post('stype');
$d = ORM::for_table('tbl_transactions'); $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 != '') { if ($stype != '') {
$d->where('type', $stype); $d->where('type', $stype);
} }
@ -297,7 +311,7 @@ EOF;
$d->where_gte('recharged_on', $fdate); $d->where_gte('recharged_on', $fdate);
$d->where_lte('recharged_on', $tdate); $d->where_lte('recharged_on', $tdate);
$d->order_by_desc('id'); $d->order_by_desc('id');
$x = $d->find_many(); $x = $d->find_many();
$dr = ORM::for_table('tbl_transactions'); $dr = ORM::for_table('tbl_transactions');
if ($stype != '') { if ($stype != '') {
@ -311,7 +325,7 @@ EOF;
$title = ' Reports [' . $mdate . ']'; $title = ' Reports [' . $mdate . ']';
$title = str_replace('-', ' ', $title); $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')) { if (file_exists($UPLOAD_PATH . '/logo.png')) {
$logo = $UPLOAD_URL_PATH . '/logo.png'; $logo = $UPLOAD_URL_PATH . '/logo.png';
} else { } else {
@ -332,6 +346,7 @@ EOF;
<table id="customers"> <table id="customers">
<tr> <tr>
<th>' . Lang::T('Username') . '</th> <th>' . Lang::T('Username') . '</th>
<th>' . Lang::T('Fullname') . '</th>
<th>' . Lang::T('Plan Name') . '</th> <th>' . Lang::T('Plan Name') . '</th>
<th>' . Lang::T('Type') . '</th> <th>' . Lang::T('Type') . '</th>
<th>' . Lang::T('Plan Price') . '</th> <th>' . Lang::T('Plan Price') . '</th>
@ -344,6 +359,7 @@ EOF;
foreach ($x as $value) { foreach ($x as $value) {
$username = $value['username']; $username = $value['username'];
$fullname = $value['fullname'];
$plan_name = $value['plan_name']; $plan_name = $value['plan_name'];
$type = $value['type']; $type = $value['type'];
$price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']); $price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']);
@ -355,7 +371,8 @@ EOF;
$html .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . " $html .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . "
<td>$username</td> <td>$username</td>
<td>$plan_name</td> <td>$fullname</td>
<td>$plan_name</td>
<td>$type</td> <td>$type</td>
<td align='right'>$price</td> <td align='right'>$price</td>
<td>$recharged_on </td> <td>$recharged_on </td>

View File

@ -0,0 +1,27 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
*
**/
_admin();
$ui->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', '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css">');
$ui->assign('invoices', Invoice::getAll());
$ui->display('admin/invoices/list.tpl');
break;
default:
$ui->display('admin/404.tpl');
}

View File

@ -57,56 +57,79 @@ EOT;
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
} }
// Get form data $id_customer = $_POST['id_customer'] ?? '';
$id_customer = $_POST['id_customer']; $message = $_POST['message'] ?? '';
$message = $_POST['message']; $via = $_POST['via'] ?? '';
$via = $_POST['via']; $subject = $_POST['subject'] ?? '';
// Check if fields are empty // Validate subject based on the selected channel
if ($id_customer == '' or $message == '' or $via == '') { if (($via === 'all' || $via === 'email' || $via === 'inbox') && empty($subject)) {
r2(getUrl('message/send'), 'e', Lang::T('All field is required')); r2(getUrl('message/send'), 'e', LANG::T('Subject is required to send message using') . ' ' . $via . '.');
} else { }
// Get customer details from the database
$c = ORM::for_table('tbl_customers')->find_one($id_customer);
// Replace placeholders in the message with actual values if (empty($id_customer) || empty($message) || empty($via)) {
$message = str_replace('[[name]]', $c['fullname'], $message); r2(getUrl('message/send'), 'e', Lang::T('Customer, Message, and Channel are required'));
$message = str_replace('[[user_name]]', $c['username'], $message); }
$message = str_replace('[[phone]]', $c['phonenumber'], $message);
$message = str_replace('[[company_name]]', $config['CompanyName'], $message); $customer = ORM::for_table('tbl_customers')->find_one($id_customer);
if (strpos($message, '[[payment_link]]') !== false) { if (!$customer) {
// token only valid for 1 day, for security reason r2(getUrl('message/send'), 'e', Lang::T('Customer not found'));
$token = User::generateToken($c['id'], 1); }
if (!empty($token['token'])) {
$tur = ORM::for_table('tbl_user_recharges') // Replace placeholders in message and subject
->where('customer_id', $c['id']) $currentMessage = str_replace(
//->where('namebp', $package) ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
->find_one(); [$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
if ($tur) { $message
$url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']); );
$message = str_replace('[[payment_link]]', $url, $message);
} $currentSubject = str_replace(
} else { ['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
$message = str_replace('[[payment_link]]', '', $message); [$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 { } 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; break;
case 'send_bulk': case 'send_bulk':
@ -133,11 +156,16 @@ EOT;
$batch = $_REQUEST['batch'] ?? 100; $batch = $_REQUEST['batch'] ?? 100;
$page = $_REQUEST['page'] ?? 0; $page = $_REQUEST['page'] ?? 0;
$router = $_REQUEST['router'] ?? null; $router = $_REQUEST['router'] ?? null;
$test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? true : false; $test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on';
$service = $_REQUEST['service'] ?? ''; $service = $_REQUEST['service'] ?? '';
$subject = $_REQUEST['subject'] ?? '';
if (empty($group) || empty($message) || empty($via) || empty($service)) { 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 // Get batch of customers based on group
@ -153,7 +181,7 @@ EOT;
default: default:
$router = ORM::for_table('tbl_routers')->find_one($router); $router = ORM::for_table('tbl_routers')->find_one($router);
if (!$router) { if (!$router) {
die(json_encode(['status' => 'error', 'message' => 'Invalid router'])); die(json_encode(['status' => 'error', 'message' => LANG::T('Invalid router')]));
} }
$routerName = $router->name; $routerName = $router->name;
break; break;
@ -200,6 +228,9 @@ EOT;
['tbl_customers.phonenumber', 'phonenumber'], ['tbl_customers.phonenumber', 'phonenumber'],
['tbl_user_recharges.customer_id', 'customer_id'], ['tbl_user_recharges.customer_id', 'customer_id'],
['tbl_customers.fullname', 'fullname'], ['tbl_customers.fullname', 'fullname'],
['tbl_customers.username', 'username'],
['tbl_customers.email', 'email'],
['tbl_customers.service_type', 'service_type'],
]); ]);
$customers = $query->find_array(); $customers = $query->find_array();
} else { } else {
@ -287,7 +318,13 @@ EOT;
$totalSMSFailed = 0; $totalSMSFailed = 0;
$totalWhatsappSent = 0; $totalWhatsappSent = 0;
$totalWhatsappFailed = 0; $totalWhatsappFailed = 0;
$totalEmailSent = 0;
$totalEmailFailed = 0;
$totalInboxSent = 0;
$totalInboxFailed = 0;
$batchStatus = []; $batchStatus = [];
//$subject = $config['CompanyName'] . ' ' . Lang::T('Notification Message');
$form = 'Admin';
foreach ($customers as $customer) { foreach ($customers as $customer) {
$currentMessage = str_replace( $currentMessage = str_replace(
@ -296,6 +333,12 @@ EOT;
$message $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']); $phoneNumber = preg_replace('/\D/', '', $customer['phonenumber']);
if (empty($phoneNumber)) { if (empty($phoneNumber)) {
@ -310,14 +353,14 @@ EOT;
if ($test) { if ($test) {
$batchStatus[] = [ $batchStatus[] = [
'name' => $customer['fullname'], 'name' => $customer['fullname'],
'phone' => $customer['phonenumber'], 'channel' => 'Test Channel',
'status' => 'Test Mode', 'status' => 'Test Mode',
'message' => $currentMessage, 'message' => $currentMessage,
'service' => $service, 'service' => $service,
'router' => $routerName, 'router' => $routerName,
]; ];
} else { } else {
if ($via == 'sms' || $via == 'both') { if ($via === 'sms' || $via === 'both' || $via === 'all') {
if (Message::sendSMS($customer['phonenumber'], $currentMessage)) { if (Message::sendSMS($customer['phonenumber'], $currentMessage)) {
$totalSMSSent++; $totalSMSSent++;
$batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Sent', 'message' => $currentMessage]; $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)) { if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) {
$totalWhatsappSent++; $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 { } else {
$totalWhatsappFailed++; $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, 'page' => $page + 1,
'batchStatus' => $batchStatus, 'batchStatus' => $batchStatus,
'message' => $currentMessage, 'message' => $currentMessage,
'totalSent' => $totalSMSSent + $totalWhatsappSent, 'totalSent' => $totalSMSSent + $totalWhatsappSent + $totalEmailSent + $totalInboxSent,
'totalFailed' => $totalSMSFailed + $totalWhatsappFailed, 'totalFailed' => $totalSMSFailed + $totalWhatsappFailed + $totalEmailFailed + $totalInboxFailed,
'hasMore' => $hasMore, 'hasMore' => $hasMore,
'service' => $service, 'service' => $service,
'router' => $routerName, 'router' => $routerName,
@ -365,16 +428,20 @@ EOT;
// Get the posted data // Get the posted data
$customerIds = $_POST['customer_ids'] ?? []; $customerIds = $_POST['customer_ids'] ?? [];
$via = $_POST['message_type'] ?? ''; $via = $_POST['message_type'] ?? '';
$subject = $_POST['subject'] ?? '';
$message = isset($_POST['message']) ? trim($_POST['message']) : ''; $message = isset($_POST['message']) ? trim($_POST['message']) : '';
if (empty($customerIds) || empty($message) || empty($via)) { if (empty($customerIds) || empty($message) || empty($via)) {
echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid customer IDs, Message, or Message Type.')]); echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid customer IDs, Message, or Message Type.')]);
exit; 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 // Prepare to send messages
$sentCount = 0; $sentCount = 0;
$failedCount = 0; $failedCount = 0;
$subject = Lang::T('Notification Message');
$form = 'Admin'; $form = 'Admin';
foreach ($customerIds as $customerId) { foreach ($customerIds as $customerId) {

View File

@ -58,7 +58,7 @@ switch ($action) {
$w = []; $w = [];
$v = []; $v = [];
foreach ($mts as $mt) { foreach ($mts as $mt) {
$w[] ='method'; $w[] = 'method';
$v[] = "$mt - %"; $v[] = "$mt - %";
} }
$query->where_likes($w, $v); $query->where_likes($w, $v);
@ -91,7 +91,7 @@ switch ($action) {
$w = []; $w = [];
$v = []; $v = [];
foreach ($mts as $mt) { foreach ($mts as $mt) {
$w[] ='method'; $w[] = 'method';
$v[] = "$mt - %"; $v[] = "$mt - %";
} }
$query->where_likes($w, $v); $query->where_likes($w, $v);
@ -161,7 +161,7 @@ switch ($action) {
$w = []; $w = [];
$v = []; $v = [];
foreach ($mts as $mt) { foreach ($mts as $mt) {
$w[] ='method'; $w[] = 'method';
$v[] = "$mt - %"; $v[] = "$mt - %";
} }
$query->where_likes($w, $v); $query->where_likes($w, $v);
@ -246,7 +246,7 @@ switch ($action) {
$total += $v; $total += $v;
$array['data'][] = $v; $array['data'][] = $v;
} }
if($total>0){ if ($total > 0) {
$result['datas'][] = $array; $result['datas'][] = $array;
} }
} }
@ -258,18 +258,29 @@ switch ($action) {
die(); die();
case 'by-date': case 'by-date':
case 'activation': case 'activation':
$q = (_post('q') ? _post('q') : _get('q')); $q = trim(_post('q') ?: _get('q'));
$keep = _post('keep'); $keep = _post('keep');
if (!empty($keep)) { if (!empty($keep)) {
ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))"); ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))", [$keep]);
r2(getUrl('logs/list/'), 's', "Delete logs older than $keep days"); 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]); $d = Paginator::findMany($query, ['q' => $q]);
} else { } catch (Exception $e) {
$query = ORM::for_table('tbl_transactions')->order_by_desc('id'); r2(getUrl('reports/activation/'), 'e', 'Database query failed: ' . $e->getMessage());
$d = Paginator::findMany($query); $d = [];
} }
$ui->assign('activation', $d); $ui->assign('activation', $d);
@ -291,6 +302,10 @@ switch ($action) {
$stype = _post('stype'); $stype = _post('stype');
$d = ORM::for_table('tbl_transactions'); $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 != '') { if ($stype != '') {
$d->where('type', $stype); $d->where('type', $stype);
} }
@ -298,7 +313,7 @@ switch ($action) {
$d->where_gte('recharged_on', $fdate); $d->where_gte('recharged_on', $fdate);
$d->where_lte('recharged_on', $tdate); $d->where_lte('recharged_on', $tdate);
$d->order_by_desc('id'); $d->order_by_desc('id');
$x = $d->find_many(); $x = $d->find_many();
$dr = ORM::for_table('tbl_transactions'); $dr = ORM::for_table('tbl_transactions');
if ($stype != '') { if ($stype != '') {
@ -348,7 +363,10 @@ switch ($action) {
$query = ORM::for_table('tbl_transactions') $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("$sd $ts"))
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te")) ->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) { if (count($tps) > 0) {
$query->where_in('type', $tps); $query->where_in('type', $tps);
} }
@ -356,7 +374,7 @@ switch ($action) {
$w = []; $w = [];
$v = []; $v = [];
foreach ($mts as $mt) { foreach ($mts as $mt) {
$w[] ='method'; $w[] = 'method';
$v[] = "$mt - %"; $v[] = "$mt - %";
} }
$query->where_likes($w, $v); $query->where_likes($w, $v);

View File

@ -146,7 +146,8 @@ switch ($action) {
$r = ORM::for_table('tbl_routers')->find_many(); $r = ORM::for_table('tbl_routers')->find_many();
$ui->assign('r', $r); $ui->assign('r', $r);
if (function_exists("shell_exec")) { 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)) { if (empty($php)) {
$php = 'php'; $php = 'php';
} }

View File

@ -49,7 +49,7 @@ foreach ($d as $ds) {
} else { } else {
$price = $p['price']; $price = $p['price'];
} }
if ($ds['expiration'] == $day7 && $config['notification_reminder_7day'] !== 'no') { if ($ds['expiration'] == $day7 && $config['notification_reminder_7days'] !== 'no') {
try { try {
echo Message::sendPackageNotification( echo Message::sendPackageNotification(
$c, $c,
@ -61,7 +61,7 @@ foreach ($d as $ds) {
} catch (Exception $e) { } catch (Exception $e) {
sendTelegram("Cron Reminder failed to send 7-day reminder to " . $ds['username'] . " Error: " . $e->getMessage()); 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 { try {
echo Message::sendPackageNotification( echo Message::sendPackageNotification(
$c, $c,

View File

@ -244,7 +244,7 @@ class MikrotikPppoe
function add_pool($pool){ function add_pool($pool){
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$mikrotik = $this->info($pool['routers']); $mikrotik = $this->info($pool['routers']);
@ -259,7 +259,7 @@ class MikrotikPppoe
function update_pool($old_pool, $new_pool){ function update_pool($old_pool, $new_pool){
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$mikrotik = $this->info($new_pool['routers']); $mikrotik = $this->info($new_pool['routers']);
@ -284,7 +284,7 @@ class MikrotikPppoe
function remove_pool($pool){ function remove_pool($pool){
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$mikrotik = $this->info($pool['routers']); $mikrotik = $this->info($pool['routers']);
@ -329,7 +329,7 @@ class MikrotikPppoe
function getClient($ip, $user, $pass) function getClient($ip, $user, $pass)
{ {
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$iport = explode(":", $ip); $iport = explode(":", $ip);
@ -339,7 +339,7 @@ class MikrotikPppoe
function removePpoeUser($client, $username) function removePpoeUser($client, $username)
{ {
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$printRequest = new RouterOS\Request('/ppp/secret/print'); $printRequest = new RouterOS\Request('/ppp/secret/print');
@ -376,7 +376,7 @@ class MikrotikPppoe
function removePpoeActive($client, $username) function removePpoeActive($client, $username)
{ {
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$onlineRequest = new RouterOS\Request('/ppp/active/print'); $onlineRequest = new RouterOS\Request('/ppp/active/print');
@ -392,7 +392,7 @@ class MikrotikPppoe
function getIpHotspotUser($client, $username) function getIpHotspotUser($client, $username)
{ {
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$printRequest = new RouterOS\Request( $printRequest = new RouterOS\Request(
@ -405,7 +405,7 @@ class MikrotikPppoe
function addIpToAddressList($client, $ip, $listName, $comment = '') function addIpToAddressList($client, $ip, $listName, $comment = '')
{ {
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$addRequest = new RouterOS\Request('/ip/firewall/address-list/add'); $addRequest = new RouterOS\Request('/ip/firewall/address-list/add');
@ -420,7 +420,7 @@ class MikrotikPppoe
function removeIpFromAddressList($client, $ip) function removeIpFromAddressList($client, $ip)
{ {
global $_app_stage; global $_app_stage;
if ($_app_stage == 'demo') { if ($_app_stage == 'Demo') {
return null; return null;
} }
$printRequest = new RouterOS\Request( $printRequest = new RouterOS\Request(

View File

@ -381,5 +381,166 @@
"_Clear_old_logs_": " Clear old logs?", "_Clear_old_logs_": " Clear old logs?",
"Clean_up_Logs": "Clean up Logs", "Clean_up_Logs": "Clean up Logs",
"ID": "ID", "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"
} }

View File

@ -1,210 +1,211 @@
{ {
"2023.8.9": [ "2023.8.9": [
"ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;", "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;", "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` ADD PRIMARY KEY (`id`);",
"ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;" "ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;"
], ],
"2023.8.14": [ "2023.8.14": [
"ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;", "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_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_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`;" "ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;"
], ],
"2023.8.23": [ "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';" "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": [ "2023.8.28": [
"ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;", "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`;" "ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;"
], ],
"2023.9.5": [ "2023.9.5": [
"DROP TABLE `tbl_language`;", "DROP TABLE `tbl_language`;",
"ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;" "ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;"
], ],
"2023.9.27": [ "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_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;" "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
], ],
"2023.9.28": [ "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_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_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
], ],
"2023.10.1": [ "2023.10.1": [
"ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; " "ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; "
], ],
"2023.10.24": [ "2023.10.24": [
"ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;" "ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;"
], ],
"2023.12.15": [ "2023.12.15": [
"ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;" "ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;"
], ],
"2024.1.11": [ "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`;" "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": [ "2024.2.7": [
"ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;", "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`;" "ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;"
], ],
"2024.2.12": [ "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;" "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": [ "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` 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`;" "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": [ "2024.2.16": [
"ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;" "ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;"
], ],
"2024.2.19": [ "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));" "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": [ "2024.2.20": [
"ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;", "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`;" "ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;"
], ],
"2024.2.20.1": [ "2024.2.20.1": ["DROP TABLE IF EXISTS `tbl_customers_meta`;"],
"DROP TABLE IF EXISTS `tbl_customers_meta`;" "2024.2.23": [
], "ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;",
"2024.2.23": [ "ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;"
"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.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.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.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": [ ],
"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.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' ;"
"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';"
"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.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.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.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.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`;",
"2024.6.5": [ "ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;"
"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.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`;",
"2024.6.11": [ "ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;"
"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.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`;",
"2024.6.21": [ "ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;"
"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;",
"2024.7.6": [ "ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);",
"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` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;"
"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`;",
"2024.7.24": [ "UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;"
"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 '';",
"2024.8.1": [ "ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';"
"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.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`;",
"2024.8.5": [ "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`;"
"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`;",
"2024.8.5.1": [ "ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;"
"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`;",
"2024.8.6": [ "ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;"
"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.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`;",
"2024.8.28": [ "ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;"
"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;",
"2024.9.13": [ "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_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;", "ALTER TABLE `tbl_transactions` 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';", "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;"
"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.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.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`;",
"2024.10.30": [ "ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;"
"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.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`;",
"2024.12.5.1": [ "ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;"
"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.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`;"
"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.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.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.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.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.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', '');",
"2025.2.25" : [ "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', '');"
"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.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);"
} ]
}

View File

View File

@ -18,9 +18,9 @@
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<a class="btn btn-primary btn-xs" title="save" <a class="btn btn-primary btn-xs" title="save"
href="{Text::url('customers/csv&token=', $csrf_token)}" href="{Text::url('customers/csv&token=', $csrf_token)}" onclick="return ask(this, '{Lang::T("
onclick="return ask(this, '{Lang::T("This will export to CSV")}?')"><span This will export to CSV")}?')"><span class="glyphicon glyphicon-download"
class="glyphicon glyphicon-download" aria-hidden="true"></span> CSV</a> aria-hidden="true"></span> CSV</a>
</div> </div>
{/if} {/if}
{Lang::T('Manage Contact')} {Lang::T('Manage Contact')}
@ -205,14 +205,15 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<select id="messageType" class="form-control"> <select style="margin-bottom: 10px;" id="messageType" class="form-control">
<option value="all">{Lang::T('All')}</option> <option value="all">{Lang::T('All')}</option>
<option value="email">{Lang::T('Email')}</option> <option value="email">{Lang::T('Email')}</option>
<option value="inbox">{Lang::T('Inbox')}</option> <option value="inbox">{Lang::T('Inbox')}</option>
<option value="sms">{Lang::T('SMS')}</option> <option value="sms">{Lang::T('SMS')}</option>
<option value="wa">{Lang::T('WhatsApp')}</option> <option value="wa">{Lang::T('WhatsApp')}</option>
</select> </select>
<br> <input type="text" style="margin-bottom: 10px;" class="form-control" id="subject-content" value=""
placeholder="{Lang::T('Enter message subject here')}">
<textarea id="messageContent" class="form-control" rows="4" <textarea id="messageContent" class="form-control" rows="4"
placeholder="{Lang::T('Enter your message here...')}"></textarea> placeholder="{Lang::T('Enter your message here...')}"></textarea>
</div> </div>
@ -260,6 +261,8 @@
$('#sendMessageButton').on('click', function () { $('#sendMessageButton').on('click', function () {
const message = $('#messageContent').val().trim(); const message = $('#messageContent').val().trim();
const messageType = $('#messageType').val(); const messageType = $('#messageType').val();
const subject = $('#subject-content').val().trim();
if (!message) { if (!message) {
Swal.fire({ Swal.fire({
@ -271,6 +274,16 @@
return; 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 // Disable the button and show loading text
$(this).prop('disabled', true).text('{Lang::T('Sending...')}'); $(this).prop('disabled', true).text('{Lang::T('Sending...')}');
@ -332,4 +345,31 @@
}); });
}); });
</script> </script>
<script>
document.getElementById('messageType').addEventListener('change', function () {
const messageType = this.value;
const subjectField = document.getElementById('subject-content');
subjectField.style.display = (messageType === 'all' || messageType === 'email' || messageType === 'inbox') ? 'block' : 'none';
switch (messageType) {
case 'all':
subjectField.placeholder = 'Enter a subject for all channels';
subjectField.required = true;
break;
case 'email':
subjectField.placeholder = 'Enter a subject for email';
subjectField.required = true;
break;
case 'inbox':
subjectField.placeholder = 'Enter a subject for inbox';
subjectField.required = true;
break;
default:
subjectField.placeholder = 'Enter message subject here';
subjectField.required = false;
break;
}
});
</script>
{include file = "sections/footer.tpl" } {include file = "sections/footer.tpl" }

View File

@ -14,6 +14,7 @@
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/bootstrap.min.css"> <link rel="stylesheet" href="{$app_url}/ui/ui/styles/bootstrap.min.css">
<link rel="stylesheet" href="{$app_url}/ui/ui/fonts/ionicons/css/ionicons.min.css"> <link rel="stylesheet" href="{$app_url}/ui/ui/fonts/ionicons/css/ionicons.min.css">
<link rel="stylesheet" href="{$app_url}/ui/ui/fonts/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="{$app_url}/ui/ui/fonts/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/modern-AdminLTE.min.css"> <link rel="stylesheet" href="{$app_url}/ui/ui/styles/modern-AdminLTE.min.css">
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/select2.min.css" /> <link rel="stylesheet" href="{$app_url}/ui/ui/styles/select2.min.css" />
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/select2-bootstrap.min.css" /> <link rel="stylesheet" href="{$app_url}/ui/ui/styles/select2-bootstrap.min.css" />
@ -29,7 +30,7 @@
</style> </style>
{if isset($xheader)} {if isset($xheader)}
{$xheader} {$xheader}
{/if} {/if}
</head> </head>
@ -77,8 +78,8 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li class="user-header"> <li class="user-header">
<img src="{$app_url}/{$UPLOAD_PATH}{$_admin['photo']}.thumb.jpg" <img src="{$app_url}/{$UPLOAD_PATH}{$_admin['photo']}.thumb.jpg"
onerror="this.src='{$app_url}/{$UPLOAD_PATH}/admin.default.png'" class="img-circle" onerror="this.src='{$app_url}/{$UPLOAD_PATH}/admin.default.png'"
alt="Avatar"> class="img-circle" alt="Avatar">
<p> <p>
{$_admin['fullname']} {$_admin['fullname']}
<small>{Lang::T($_admin['user_type'])}</small> <small>{Lang::T($_admin['user_type'])}</small>
@ -127,63 +128,64 @@
</li> </li>
{$_MENU_AFTER_CUSTOMERS} {$_MENU_AFTER_CUSTOMERS}
{if !in_array($_admin['user_type'],['Report'])} {if !in_array($_admin['user_type'],['Report'])}
<li class="{if $_routes[0] eq 'plan' || $_routes[0] eq 'coupons'}active{/if} treeview"> <li class="{if $_routes[0] eq 'plan' || $_routes[0] eq 'coupons'}active{/if} treeview">
<a href="#"> <a href="#">
<i class="fa fa-ticket"></i> <span>{Lang::T('Services')}</span> <i class="fa fa-ticket"></i> <span>{Lang::T('Services')}</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li {if $_routes[1] eq 'list' }class="active" {/if}><a <li {if $_routes[1] eq 'list' }class="active" {/if}><a
href="{Text::url('plan/list')}">{Lang::T('Active Customers')}</a></li> href="{Text::url('plan/list')}">{Lang::T('Active Customers')}</a></li>
{if $_c['disable_voucher'] != 'yes'} {if $_c['disable_voucher'] != 'yes'}
<li {if $_routes[1] eq 'refill' }class="active" {/if}><a <li {if $_routes[1] eq 'refill' }class="active" {/if}><a
href="{Text::url('plan/refill')}">{Lang::T('Refill Customer')}</a></li> href="{Text::url('plan/refill')}">{Lang::T('Refill Customer')}</a></li>
{/if} {/if}
{if $_c['disable_voucher'] != 'yes'} {if $_c['disable_voucher'] != 'yes'}
<li {if $_routes[1] eq 'voucher' }class="active" {/if}><a <li {if $_routes[1] eq 'voucher' }class="active" {/if}><a
href="{Text::url('plan/voucher')}">{Lang::T('Vouchers')}</a></li> href="{Text::url('plan/voucher')}">{Lang::T('Vouchers')}</a></li>
{/if} {/if}
{if $_c['enable_coupons'] == 'yes'} {if $_c['enable_coupons'] == 'yes'}
<li {if $_routes[0] eq 'coupons' }class="active" {/if}><a <li {if $_routes[0] eq 'coupons' }class="active" {/if}><a
href="{Text::url('coupons')}">{Lang::T('Coupons')}</a></li> href="{Text::url('coupons')}">{Lang::T('Coupons')}</a></li>
{/if} {/if}
<li {if $_routes[1] eq 'recharge' }class="active" {/if}><a <li {if $_routes[1] eq 'recharge' }class="active" {/if}><a
href="{Text::url('plan/recharge')}">{Lang::T('Recharge Customer')}</a></li> href="{Text::url('plan/recharge')}">{Lang::T('Recharge Customer')}</a></li>
{if $_c['enable_balance'] == 'yes'} {if $_c['enable_balance'] == 'yes'}
<li {if $_routes[1] eq 'deposit' }class="active" {/if}><a <li {if $_routes[1] eq 'deposit' }class="active" {/if}><a
href="{Text::url('plan/deposit')}">{Lang::T('Refill Balance')}</a></li> href="{Text::url('plan/deposit')}">{Lang::T('Refill Balance')}</a></li>
{/if} {/if}
{$_MENU_SERVICES} {$_MENU_SERVICES}
</ul> </ul>
</li> </li>
{/if} {/if}
{$_MENU_AFTER_SERVICES} {$_MENU_AFTER_SERVICES}
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<li class="{if $_system_menu eq 'services'}active{/if} treeview"> <li class="{if $_system_menu eq 'services'}active{/if} treeview">
<a href="#"> <a href="#">
<i class="ion ion-cube"></i> <span>{Lang::T('Internet Plan')}</span> <i class="ion ion-cube"></i> <span>{Lang::T('Internet Plan')}</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li {if $_routes[1] eq 'hotspot' }class="active" {/if}><a <li {if $_routes[1] eq 'hotspot' }class="active" {/if}><a
href="{Text::url('services/hotspot')}">Hotspot</a></li> href="{Text::url('services/hotspot')}">Hotspot</a></li>
<li {if $_routes[1] eq 'pppoe' }class="active" {/if}><a <li {if $_routes[1] eq 'pppoe' }class="active" {/if}><a
href="{Text::url('services/pppoe')}">PPPOE</a></li> href="{Text::url('services/pppoe')}">PPPOE</a></li>
<li {if $_routes[1] eq 'vpn' }class="active" {/if}><a href="{Text::url('services/vpn')}">VPN</a> <li {if $_routes[1] eq 'vpn' }class="active" {/if}><a
</li> href="{Text::url('services/vpn')}">VPN</a>
<li {if $_routes[1] eq 'list' }class="active" {/if}><a </li>
href="{Text::url('bandwidth/list')}">Bandwidth</a></li> <li {if $_routes[1] eq 'list' }class="active" {/if}><a
{if $_c['enable_balance'] == 'yes'} href="{Text::url('bandwidth/list')}">Bandwidth</a></li>
<li {if $_routes[1] eq 'balance' }class="active" {/if}><a {if $_c['enable_balance'] == 'yes'}
href="{Text::url('services/balance')}">{Lang::T('Customer Balance')}</a></li> <li {if $_routes[1] eq 'balance' }class="active" {/if}><a
{/if} href="{Text::url('services/balance')}">{Lang::T('Customer Balance')}</a></li>
{$_MENU_PLANS} {/if}
</ul> {$_MENU_PLANS}
</li> </ul>
</li>
{/if} {/if}
{$_MENU_AFTER_PLANS} {$_MENU_AFTER_PLANS}
<li class="{if in_array($_routes[0], ['maps'])}active{/if} treeview"> <li class="{if in_array($_routes[0], ['maps'])}active{/if} treeview">
@ -203,18 +205,20 @@
</li> </li>
<li class="{if $_system_menu eq 'reports'}active{/if} treeview"> <li class="{if $_system_menu eq 'reports'}active{/if} treeview">
{if in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])}
<a href="#"> <a href="#">
<i class="ion ion-clipboard"></i> <span>{Lang::T('Reports')}</span> <i class="ion ion-clipboard"></i> <span>{Lang::T('Reports')}</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
{/if} {/if}
<ul class="treeview-menu"> <ul class="treeview-menu">
<li {if $_routes[1] eq 'reports' }class="active" {/if}><a <li {if $_routes[1] eq 'reports' }class="active" {/if}><a
href="{Text::url('reports')}">{Lang::T('Daily Reports')}</a></li> href="{Text::url('reports')}">{Lang::T('Daily Reports')}</a></li>
<li {if $_routes[1] eq 'activation' }class="active" {/if}><a <li {if $_routes[1] eq 'activation' }class="active" {/if}><a
href="{Text::url('reports/activation')}">{Lang::T('Activation History')}</a></li> href="{Text::url('reports/activation')}">{Lang::T('Activation History')}</a></li>
{* <li {if $_routes[0] eq 'invoices' }class="active" {/if}><a
href="{Text::url('invoices')}">{Lang::T('Invoices')}</a></li> *}
{$_MENU_REPORTS} {$_MENU_REPORTS}
</ul> </ul>
</li> </li>
@ -236,68 +240,71 @@
</li> </li>
{$_MENU_AFTER_MESSAGE} {$_MENU_AFTER_MESSAGE}
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<li class="{if $_system_menu eq 'network'}active{/if} treeview"> <li class="{if $_system_menu eq 'network'}active{/if} treeview">
<a href="#"> <a href="#">
<i class="ion ion-network"></i> <span>{Lang::T('Network')}</span> <i class="ion ion-network"></i> <span>{Lang::T('Network')}</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li {if $_routes[0] eq 'routers' and $_routes[1] eq '' }class="active" {/if}><a <li {if $_routes[0] eq 'routers' and $_routes[1] eq '' }class="active" {/if}><a
href="{Text::url('routers')}">Routers</a></li> href="{Text::url('routers')}">Routers</a></li>
<li {if $_routes[0] eq 'pool' and $_routes[1] eq 'list' }class="active" {/if}><a <li {if $_routes[0] eq 'pool' and $_routes[1] eq 'list' }class="active" {/if}><a
href="{Text::url('pool/list')}">IP Pool</a></li> href="{Text::url('pool/list')}">IP Pool</a></li>
<li {if $_routes[0] eq 'pool' and $_routes[1] eq 'port' }class="active" {/if}><a <li {if $_routes[0] eq 'pool' and $_routes[1] eq 'port' }class="active" {/if}><a
href="{Text::url('pool/port')}">Port Pool</a></li> href="{Text::url('pool/port')}">Port Pool</a></li>
{$_MENU_NETWORK} {$_MENU_NETWORK}
</ul> </ul>
</li> </li>
{$_MENU_AFTER_NETWORKS} {$_MENU_AFTER_NETWORKS}
{if $_c['radius_enable']} {if $_c['radius_enable']}
<li class="{if $_system_menu eq 'radius'}active{/if} treeview"> <li class="{if $_system_menu eq 'radius'}active{/if} treeview">
<a href="#"> <a href="#">
<i class="fa fa-database"></i> <span>{Lang::T('Radius')}</span> <i class="fa fa-database"></i> <span>{Lang::T('Radius')}</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li {if $_routes[0] eq 'radius' and $_routes[1] eq 'nas-list' }class="active" {/if}><a <li {if $_routes[0] eq 'radius' and $_routes[1] eq 'nas-list' }class="active" {/if}><a
href="{Text::url('radius/nas-list')}">{Lang::T('Radius NAS')}</a></li> href="{Text::url('radius/nas-list')}">{Lang::T('Radius NAS')}</a></li>
{$_MENU_RADIUS} {$_MENU_RADIUS}
</ul> </ul>
</li>
{/if}
{$_MENU_AFTER_RADIUS}
<li class="{if $_system_menu eq 'pages'}active{/if} treeview">
<a href="#">
<i class="ion ion-document"></i> <span>{Lang::T("Static Pages")}</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li {if $_routes[1] eq 'Order_Voucher' }class="active" {/if}><a
href="{Text::url('pages/Order_Voucher')}">{Lang::T('Order Voucher')}</a></li>
<li {if $_routes[1] eq 'Voucher' }class="active" {/if}><a
href="{Text::url('pages/Voucher')}">{Lang::T('Theme Voucher')}</a></li>
<li {if $_routes[1] eq 'Announcement' }class="active" {/if}><a
href="{Text::url('pages/Announcement')}">{Lang::T('Announcement')}</a></li>
<li {if $_routes[1] eq 'Announcement_Customer' }class="active" {/if}><a
href="{Text::url('pages/Announcement_Customer')}">{Lang::T('Customer
Announcement')}</a>
</li> </li>
{/if} <li {if $_routes[1] eq 'Registration_Info' }class="active" {/if}><a
{$_MENU_AFTER_RADIUS} href="{Text::url('pages/Registration_Info')}">{Lang::T('Registration Info')}</a>
<li class="{if $_system_menu eq 'pages'}active{/if} treeview"> </li>
<a href="#"> <li {if $_routes[1] eq 'Payment_Info' }class="active" {/if}><a
<i class="ion ion-document"></i> <span>{Lang::T("Static Pages")}</span> href="{Text::url('pages/Payment_Info')}">{Lang::T('Payment Info')}</a></li>
<span class="pull-right-container"> <li {if $_routes[1] eq 'Privacy_Policy' }class="active" {/if}><a
<i class="fa fa-angle-left pull-right"></i> href="{Text::url('pages/Privacy_Policy')}">{Lang::T('Privacy Policy')}</a></li>
</span> <li {if $_routes[1] eq 'Terms_and_Conditions' }class="active" {/if}><a
</a> href="{Text::url('pages/Terms_and_Conditions')}">{Lang::T('Terms and
<ul class="treeview-menu"> Conditions')}</a></li>
<li {if $_routes[1] eq 'Order_Voucher' }class="active" {/if}><a {$_MENU_PAGES}
href="{Text::url('pages/Order_Voucher')}">{Lang::T('Order Voucher')}</a></li> </ul>
<li {if $_routes[1] eq 'Voucher' }class="active" {/if}><a </li>
href="{Text::url('pages/Voucher')}">{Lang::T('Theme Voucher')}</a></li>
<li {if $_routes[1] eq 'Announcement' }class="active" {/if}><a
href="{Text::url('pages/Announcement')}">{Lang::T('Announcement')}</a></li>
<li {if $_routes[1] eq 'Announcement_Customer' }class="active" {/if}><a
href="{Text::url('pages/Announcement_Customer')}">{Lang::T('Customer Announcement')}</a>
</li>
<li {if $_routes[1] eq 'Registration_Info' }class="active" {/if}><a
href="{Text::url('pages/Registration_Info')}">{Lang::T('Registration Info')}</a></li>
<li {if $_routes[1] eq 'Payment_Info' }class="active" {/if}><a
href="{Text::url('pages/Payment_Info')}">{Lang::T('Payment Info')}</a></li>
<li {if $_routes[1] eq 'Privacy_Policy' }class="active" {/if}><a
href="{Text::url('pages/Privacy_Policy')}">{Lang::T('Privacy Policy')}</a></li>
<li {if $_routes[1] eq 'Terms_and_Conditions' }class="active" {/if}><a
href="{Text::url('pages/Terms_and_Conditions')}">{Lang::T('Terms and Conditions')}</a></li>
{$_MENU_PAGES}
</ul>
</li>
{/if} {/if}
{$_MENU_AFTER_PAGES} {$_MENU_AFTER_PAGES}
<li <li
@ -310,84 +317,85 @@
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<li {if $_routes[1] eq 'app' }class="active" {/if}><a <li {if $_routes[1] eq 'app' }class="active" {/if}><a
href="{Text::url('settings/app')}">{Lang::T('General Settings')}</a></li> href="{Text::url('settings/app')}">{Lang::T('General Settings')}</a></li>
<li {if $_routes[1] eq 'localisation' }class="active" {/if}><a <li {if $_routes[1] eq 'localisation' }class="active" {/if}><a
href="{Text::url('settings/localisation')}">{Lang::T('Localisation')}</a></li> href="{Text::url('settings/localisation')}">{Lang::T('Localisation')}</a></li>
<li {if $_routes[0] eq 'customfield' }class="active" {/if}><a <li {if $_routes[0] eq 'customfield' }class="active" {/if}><a
href="{Text::url('customfield')}">{Lang::T('Custom Fields')}</a></li> href="{Text::url('customfield')}">{Lang::T('Custom Fields')}</a></li>
<li {if $_routes[1] eq 'miscellaneous' }class="active" {/if}><a <li {if $_routes[1] eq 'miscellaneous' }class="active" {/if}><a
href="{Text::url('settings/miscellaneous')}">{Lang::T('Miscellaneous')}</a></li> href="{Text::url('settings/miscellaneous')}">{Lang::T('Miscellaneous')}</a></li>
<li {if $_routes[1] eq 'maintenance' }class="active" {/if}><a <li {if $_routes[1] eq 'maintenance' }class="active" {/if}><a
href="{Text::url('settings/maintenance')}">{Lang::T('Maintenance Mode')}</a></li> href="{Text::url('settings/maintenance')}">{Lang::T('Maintenance Mode')}</a></li>
<li {if $_routes[0] eq 'widgets' }class="active" {/if}><a <li {if $_routes[0] eq 'widgets' }class="active" {/if}><a
href="{Text::url('widgets')}">{Lang::T('Widgets')}</a></li> href="{Text::url('widgets')}">{Lang::T('Widgets')}</a></li>
<li {if $_routes[1] eq 'notifications' }class="active" {/if}><a <li {if $_routes[1] eq 'notifications' }class="active" {/if}><a
href="{Text::url('settings/notifications')}">{Lang::T('User Notification')}</a></li> href="{Text::url('settings/notifications')}">{Lang::T('User Notification')}</a></li>
<li {if $_routes[1] eq 'devices' }class="active" {/if}><a <li {if $_routes[1] eq 'devices' }class="active" {/if}><a
href="{Text::url('settings/devices')}">{Lang::T('Devices')}</a></li> href="{Text::url('settings/devices')}">{Lang::T('Devices')}</a></li>
{/if} {/if}
{if in_array($_admin['user_type'],['SuperAdmin','Admin','Agent'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin','Agent'])}
<li {if $_routes[1] eq 'users' }class="active" {/if}><a <li {if $_routes[1] eq 'users' }class="active" {/if}><a
href="{Text::url('settings/users')}">{Lang::T('Administrator Users')}</a></li> href="{Text::url('settings/users')}">{Lang::T('Administrator Users')}</a></li>
{/if} {/if}
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<li {if $_routes[1] eq 'dbstatus' }class="active" {/if}><a <li {if $_routes[1] eq 'dbstatus' }class="active" {/if}><a
href="{Text::url('settings/dbstatus')}">{Lang::T('Backup/Restore')}</a></li> href="{Text::url('settings/dbstatus')}">{Lang::T('Backup/Restore')}</a></li>
<li {if $_system_menu eq 'paymentgateway' }class="active" {/if}> <li {if $_system_menu eq 'paymentgateway' }class="active" {/if}>
<a href="{Text::url('paymentgateway')}"> <a href="{Text::url('paymentgateway')}">
<span class="text">{Lang::T('Payment Gateway')}</span> <span class="text">{Lang::T('Payment Gateway')}</span>
</a> </a>
</li> </li>
{$_MENU_SETTINGS} {$_MENU_SETTINGS}
<li {if $_routes[0] eq 'pluginmanager' }class="active" {/if}> <li {if $_routes[0] eq 'pluginmanager' }class="active" {/if}>
<a href="{Text::url('pluginmanager')}"><i class="glyphicon glyphicon-tasks"></i> <a href="{Text::url('pluginmanager')}"><i class="glyphicon glyphicon-tasks"></i>
{Lang::T('Plugin Manager')}</a> {Lang::T('Plugin Manager')}</a>
</li> </li>
{/if} {/if}
</ul> </ul>
</li> </li>
{$_MENU_AFTER_SETTINGS} {$_MENU_AFTER_SETTINGS}
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<li class="{if $_system_menu eq 'logs' }active{/if} treeview"> <li class="{if $_system_menu eq 'logs' }active{/if} treeview">
<a href="#"> <a href="#">
<i class="ion ion-clock"></i> <span>{Lang::T('Logs')}</span> <i class="ion ion-clock"></i> <span>{Lang::T('Logs')}</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li {if $_routes[1] eq 'list' }class="active" {/if}><a <li {if $_routes[1] eq 'list' }class="active" {/if}><a
href="{Text::url('logs/phpnuxbill')}">PhpNuxBill</a></li> href="{Text::url('logs/phpnuxbill')}">PhpNuxBill</a></li>
{if $_c['radius_enable']} {if $_c['radius_enable']}
<li {if $_routes[1] eq 'radius' }class="active" {/if}><a <li {if $_routes[1] eq 'radius' }class="active" {/if}><a
href="{Text::url('logs/radius')}">Radius</a> href="{Text::url('logs/radius')}">Radius</a>
</li> </li>
{/if} {/if}
<li {if $_routes[1] eq 'message' }class="active" {/if}><a <li {if $_routes[1] eq 'message' }class="active" {/if}><a
href="{Text::url('logs/message')}">Message</a></li> href="{Text::url('logs/message')}">Message</a></li>
{$_MENU_LOGS} {$_MENU_LOGS}
</ul> </ul>
</li> </li>
{/if} {/if}
{$_MENU_AFTER_LOGS} {$_MENU_AFTER_LOGS}
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])} {if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<li {if $_routes[1] eq 'docs' }class="active" {/if}> <li {if $_routes[1] eq 'docs' }class="active" {/if}>
<a href="{if $_c['docs_clicked'] != 'yes'}{Text::url('settings/docs')}{else}{$app_url}/docs{/if}"> <a
<i class="ion ion-ios-bookmarks"></i> href="{if $_c['docs_clicked'] != 'yes'}{Text::url('settings/docs')}{else}{$app_url}/docs{/if}">
<span class="text">{Lang::T('Documentation')}</span> <i class="ion ion-ios-bookmarks"></i>
{if $_c['docs_clicked'] != 'yes'} <span class="text">{Lang::T('Documentation')}</span>
<span class="pull-right-container"><small {if $_c['docs_clicked'] != 'yes'}
class="label pull-right bg-green">New</small></span> <span class="pull-right-container"><small
{/if} class="label pull-right bg-green">New</small></span>
</a> {/if}
</li> </a>
<li {if $_system_menu eq 'community' }class="active" {/if}> </li>
<a href="{Text::url('community')}"> <li {if $_system_menu eq 'community' }class="active" {/if}>
<i class="ion ion-chatboxes"></i> <a href="{Text::url('community')}">
<span class="text">Community</span> <i class="ion ion-chatboxes"></i>
</a> <span class="text">Community</span>
</li> </a>
</li>
{/if} {/if}
{$_MENU_AFTER_COMMUNITY} {$_MENU_AFTER_COMMUNITY}
</ul> </ul>
@ -395,11 +403,11 @@
</aside> </aside>
{if $_c['maintenance_mode'] == 1} {if $_c['maintenance_mode'] == 1}
<div class="notification-top-bar"> <div class="notification-top-bar">
<p>{Lang::T('The website is currently in maintenance mode, this means that some or all functionality may be <p>{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.')}<small> &nbsp;&nbsp;<a unavailable to regular users during this time.')}<small> &nbsp;&nbsp;<a
href="{Text::url('settings/maintenance')}">{Lang::T('Turn Off')}</a></small></p> href="{Text::url('settings/maintenance')}">{Lang::T('Turn Off')}</a></small></p>
</div> </div>
{/if} {/if}
<div class="content-wrapper"> <div class="content-wrapper">
@ -411,19 +419,19 @@
<section class="content"> <section class="content">
{if isset($notify)} {if isset($notify)}
<script> <script>
// Display SweetAlert toast notification // Display SweetAlert toast notification
Swal.fire({ Swal.fire({
icon: '{if $notify_t == "s"}success{else}error{/if}', icon: '{if $notify_t == "s"}success{else}error{/if}',
title: '{$notify}', title: '{$notify}',
position: 'top-end', position: 'top-end',
showConfirmButton: false, showConfirmButton: false,
timer: 5000, timer: 5000,
timerProgressBar: true, timerProgressBar: true,
didOpen: (toast) => { didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer) toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer) toast.addEventListener('mouseleave', Swal.resumeTimer)
} }
}); });
</script> </script>
{/if} {/if}

View File

@ -0,0 +1,57 @@
{include file="sections/header.tpl"}
<!-- Add a Table for Sent History -->
<div class="panel panel-default">
<div class="panel-heading">{Lang::T('Invoices')}</div>
<div class="panel-body" style="overflow: auto;">
<table class="table table-bordered" id="invoiceTable" style="width:100%">
<thead>
<tr>
<th>{Lang::T('Invoice No')}</th>
<th>{Lang::T('Customer Name')}</th>
<th>{Lang::T('Email')}</th>
<th>{Lang::T('Address')}</th>
<th>{Lang::T('Amount')}</th>
<th>{Lang::T('Status')}</th>
<th>{Lang::T('Created Date')}</th>
<th>{Lang::T('Due Date')}</th>
<th>{Lang::T('Actions')}</th>
</tr>
</thead>
<tbody>
{foreach $invoices as $invoice}
<tr>
<td>{$invoice->number}</td>
<td>{$invoice->fullname}</td>
<td>{$invoice->email}</td>
<td>{$invoice->address}</td>
<td>{$invoice->amount}</td>
<td>
{if $invoice->status == 'paid'}
<span class="label label-success">{Lang::T('Paid')}</span>
{elseif $invoice->status == 'unpaid'}
<span class="label label-danger">{Lang::T('Unpaid')}</span>
{else}
<span class="label label-warning">{Lang::T('Pending')}</span>
{/if}
</td>
<td>{$invoice->created_at}</td>
<td>{$invoice->due_date}</td>
<td>
<a href="{$app_url}/system/uploads/invoices/{$invoice->filename}" class="btn btn-primary btn-xs">{Lang::T('View')}</a>
<!-- <a href="javascript:void(0);" class="btn btn-danger btn-xs" onclick="deleteInvoice({$invoice->id});">{Lang::T('Delete')}</a>
<a href="javascript:void(0);" class="btn btn-success btn-xs" onclick="sendInvoice({$invoice->id});">{Lang::T('Send')}</a> -->
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script>
new DataTable('#invoiceTable');
</script>
{include file="sections/footer.tpl"}

View File

@ -30,7 +30,8 @@
<select class="form-control" name="service" id="service"> <select class="form-control" name="service" id="service">
<option value="all" {if $group=='all' }selected{/if}>{Lang::T('All')}</option> <option value="all" {if $group=='all' }selected{/if}>{Lang::T('All')}</option>
<option value="PPPoE" {if $service=='PPPoE' }selected{/if}>{Lang::T('PPPoE')}</option> <option value="PPPoE" {if $service=='PPPoE' }selected{/if}>{Lang::T('PPPoE')}</option>
<option value="Hotspot" {if $service=='Hotspot' }selected{/if}>{Lang::T('Hotspot')}</option> <option value="Hotspot" {if $service=='Hotspot' }selected{/if}>{Lang::T('Hotspot')}
</option>
<option value="VPN" {if $service=='VPN' }selected{/if}>{Lang::T('VPN')}</option> <option value="VPN" {if $service=='VPN' }selected{/if}>{Lang::T('VPN')}</option>
</select> </select>
</div> </div>
@ -41,8 +42,10 @@
<select class="form-control" name="group" id="group"> <select class="form-control" name="group" id="group">
<option value="all" {if $group=='all' }selected{/if}>{Lang::T('All Customers')}</option> <option value="all" {if $group=='all' }selected{/if}>{Lang::T('All Customers')}</option>
<option value="new" {if $group=='new' }selected{/if}>{Lang::T('New Customers')}</option> <option value="new" {if $group=='new' }selected{/if}>{Lang::T('New Customers')}</option>
<option value="expired" {if $group=='expired' }selected{/if}>{Lang::T('Expired Customers')}</option> <option value="expired" {if $group=='expired' }selected{/if}>{Lang::T('Expired
<option value="active" {if $group=='active' }selected{/if}>{Lang::T('Active Customers')}</option> Customers')}</option>
<option value="active" {if $group=='active' }selected{/if}>{Lang::T('Active Customers')}
</option>
</select> </select>
</div> </div>
</div> </div>
@ -50,9 +53,13 @@
<label class="col-md-2 control-label">{Lang::T('Send Via')}</label> <label class="col-md-2 control-label">{Lang::T('Send Via')}</label>
<div class="col-md-6"> <div class="col-md-6">
<select class="form-control" name="via" id="via"> <select class="form-control" name="via" id="via">
<option value="all" {if $via=='all' }selected{/if}>{Lang::T('All Channels')}</option>
<option value="inbox" {if $via=='inbox' }selected{/if}>{Lang::T('Inbox')}</option>
<option value="email" {if $via=='email' }selected{/if}>{Lang::T('Email')}</option>
<option value="sms" {if $via=='sms' }selected{/if}>{Lang::T('SMS')}</option> <option value="sms" {if $via=='sms' }selected{/if}>{Lang::T('SMS')}</option>
<option value="wa" {if $via=='wa' }selected{/if}>{Lang::T('WhatsApp')}</option> <option value="wa" {if $via=='wa' }selected{/if}>{Lang::T('WhatsApp')}</option>
<option value="both" {if $via=='both' }selected{/if}>{Lang::T('SMS and WhatsApp')}</option> <option value="both" {if $via=='both' }selected{/if}>{Lang::T('SMS and WhatsApp')}
</option>
</select> </select>
</div> </div>
</div> </div>
@ -72,10 +79,21 @@
{Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')} {Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')}
</div> </div>
</div> </div>
<div class="form-group" id="subject-content">
<label class="col-md-2 control-label">{Lang::T('Subject')}</label>
<div class="col-md-6">
<input type="text" class="form-control" name="subject" id="subject" value=""
placeholder="{Lang::T('Enter message subject here')}">
</div>
<p class="help-block col-md-4">
{Lang::T('You can also use the below placeholders here too')}.
</p>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Message')}</label> <label class="col-md-2 control-label">{Lang::T('Message')}</label>
<div class="col-md-6"> <div class="col-md-6">
<textarea class="form-control" id="message" name="message" required placeholder="{Lang::T('Compose your message...')}" rows="5">{$message}</textarea> <textarea class="form-control" id="message" name="message" required
placeholder="{Lang::T('Compose your message...')}" rows="5">{$message}</textarea>
<input name="test" id="test" type="checkbox"> <input name="test" id="test" type="checkbox">
{Lang::T('Testing [if checked no real message is sent]')} {Lang::T('Testing [if checked no real message is sent]')}
</div> </div>
@ -93,7 +111,8 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-lg-offset-2 col-lg-10"> <div class="col-lg-offset-2 col-lg-10">
<button type="button" id="startBulk" class="btn btn-primary">{Lang::T('Start Bulk Messaging')}</button> <button type="button" id="startBulk" class="btn btn-primary">{Lang::T('Start Bulk
Messaging')}</button>
<a href="{Text::url('dashboard')}" class="btn btn-default">{Lang::T('Cancel')}</a> <a href="{Text::url('dashboard')}" class="btn btn-default">{Lang::T('Cancel')}</a>
</div> </div>
</div> </div>
@ -112,7 +131,7 @@
<thead> <thead>
<tr> <tr>
<th>{Lang::T('Customer')}</th> <th>{Lang::T('Customer')}</th>
<th>{Lang::T('Phone')}</th> <th>{Lang::T('Channel')}</th>
<th>{Lang::T('Status')}</th> <th>{Lang::T('Status')}</th>
<th>{Lang::T('Message')}</th> <th>{Lang::T('Message')}</th>
<th>{Lang::T('Router')}</th> <th>{Lang::T('Router')}</th>
@ -126,6 +145,34 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script> <script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script>
document.getElementById('via').addEventListener('change', function () {
const via = this.value;
const subject = document.getElementById('subject-content');
const subjectField = document.getElementById('subject');
subject.style.display = (via === 'all' || via === 'email' || via === 'inbox') ? 'block' : 'none';
switch (via) {
case 'all':
subjectField.placeholder = 'Enter a subject for all channels';
subjectField.required = true;
break;
case 'email':
subjectField.placeholder = 'Enter a subject for email';
subjectField.required = true;
break;
case 'inbox':
subjectField.placeholder = 'Enter a subject for inbox';
subjectField.required = true;
break;
default:
subjectField.placeholder = 'Enter message subject here';
subjectField.required = false;
break;
}
});
</script>
{literal} {literal}
<script> <script>
let page = 0; let page = 0;
@ -158,6 +205,7 @@
page: page, page: page,
test: $('#test').is(':checked') ? 'on' : 'off', test: $('#test').is(':checked') ? 'on' : 'off',
service: $('#service').val(), service: $('#service').val(),
subject: $('#subject').val(),
}, },
dataType: 'json', dataType: 'json',
beforeSend: function () { beforeSend: function () {
@ -186,10 +234,10 @@
let statusClass = msg.status.includes('Failed') ? 'danger' : 'success'; let statusClass = msg.status.includes('Failed') ? 'danger' : 'success';
historyTable.row.add([ historyTable.row.add([
msg.name, msg.name,
msg.phone, msg.channel,
`<span class="text-${statusClass}">${msg.status}</span>`, `<span class="text-${statusClass}">${msg.status}</span>`,
msg.message || 'No message', msg.message || 'No message',
msg.router ? msg.router : 'All Router', msg.router ? msg.router : 'All Router',
msg.service == 'all' ? 'All Service' : (msg.service || 'No Service') msg.service == 'all' ? 'All Service' : (msg.service || 'No Service')
]).draw(false); // Add row without redrawing the table ]).draw(false); // Add row without redrawing the table
}); });
@ -207,7 +255,7 @@
console.error("Unexpected response format:", response); console.error("Unexpected response format:", response);
$('#status').html(` $('#status').html(`
<div class="alert alert-danger"> <div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i> Error: Unexpected response format. <i class="fas fa-exclamation-circle"></i> Error: ${response.message}
</div> </div>
`); `);
} }

View File

@ -23,12 +23,26 @@
<label class="col-md-2 control-label">{Lang::T('Send Via')}</label> <label class="col-md-2 control-label">{Lang::T('Send Via')}</label>
<div class="col-md-6"> <div class="col-md-6">
<select class="form-control" name="via" id="via"> <select class="form-control" name="via" id="via">
<option value="sms" selected> {Lang::T('via SMS')}</option> <option value="all" {if $via=='all' }selected{/if}>{Lang::T('All Channels')}</option>
<option value="wa"> {Lang::T('Via WhatsApp')}</option> <option value="inbox" {if $via=='inbox' }selected{/if}>{Lang::T('Inbox')}</option>
<option value="both"> {Lang::T('Via WhatsApp and SMS')}</option> <option value="email" {if $via=='email' }selected{/if}>{Lang::T('Email')}</option>
</select> <option value="sms" {if $via=='sms' }selected{/if}>{Lang::T('SMS')}</option>
<option value="wa" {if $via=='wa' }selected{/if}>{Lang::T('WhatsApp')}</option>
<option value="both" {if $via=='both' }selected{/if}>{Lang::T('SMS and WhatsApp')}
</option>
</select>
</div> </div>
</div> </div>
<div class="form-group" id="subject">
<label class="col-md-2 control-label">{Lang::T('Subject')}</label>
<div class="col-md-6">
<input type="text" class="form-control" name="subject" id="subject-content" value=""
placeholder="{Lang::T('Enter message subject here')}">
</div>
<p class="help-block col-md-4">
{Lang::T('You can also use the below placeholders here too')}.
</p>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Message')}</label> <label class="col-md-2 control-label">{Lang::T('Message')}</label>
<div class="col-md-6"> <div class="col-md-6">
@ -64,6 +78,33 @@
</div> </div>
</div> </div>
</div> </div>
<script>
document.getElementById('via').addEventListener('change', function () {
const via = this.value;
const subject = document.getElementById('subject');
const subjectField = document.getElementById('subject-content');
subject.style.display = (via === 'all' || via === 'email' || via === 'inbox') ? 'block' : 'none';
switch (via) {
case 'all':
subjectField.placeholder = 'Enter a subject for all channels';
subjectField.required = true;
break;
case 'email':
subjectField.placeholder = 'Enter a subject for email';
subjectField.required = true;
break;
case 'inbox':
subjectField.placeholder = 'Enter a subject for inbox';
subjectField.required = true;
break;
default:
subjectField.placeholder = 'Enter message subject here';
subjectField.required = false;
break;
}
});
</script>
{include file="sections/footer.tpl"} {include file="sections/footer.tpl"}

View File

@ -26,6 +26,7 @@
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-bordered table-condensed table-striped " style="background: #ffffff"> <table class="table table-bordered table-condensed table-striped " style="background: #ffffff">
<th class="text-center">{Lang::T('Username')}</th> <th class="text-center">{Lang::T('Username')}</th>
<th class="text-center">{Lang::T('Fullname')}</th>
<th class="text-center">{Lang::T('Plan Name')}</th> <th class="text-center">{Lang::T('Plan Name')}</th>
<th class="text-center">{Lang::T('Type')}</th> <th class="text-center">{Lang::T('Type')}</th>
<th class="text-center">{Lang::T('Plan Price')}</th> <th class="text-center">{Lang::T('Plan Price')}</th>
@ -36,6 +37,7 @@
{foreach $d as $ds} {foreach $d as $ds}
<tr> <tr>
<td>{$ds['username']}</td> <td>{$ds['username']}</td>
<td>{$ds['fullname']}</td>
<td class="text-center">{$ds['plan_name']}</td> <td class="text-center">{$ds['plan_name']}</td>
<td class="text-center">{$ds['type']}</td> <td class="text-center">{$ds['type']}</td>
<td class="text-right">{Lang::moneyFormat($ds['price'])}</td> <td class="text-right">{Lang::moneyFormat($ds['price'])}</td>

View File

@ -33,6 +33,7 @@
<tr> <tr>
<th>{Lang::T('Invoice')}</th> <th>{Lang::T('Invoice')}</th>
<th>{Lang::T('Username')}</th> <th>{Lang::T('Username')}</th>
<th>{Lang::T('Fullname')}</th>
<th>{Lang::T('Plan Name')}</th> <th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th> <th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Type')}</th> <th>{Lang::T('Type')}</th>
@ -48,6 +49,7 @@
style="cursor:pointer;">{$ds['invoice']}</td> style="cursor:pointer;">{$ds['invoice']}</td>
<td onclick="window.location.href = '{Text::url('')}customers/viewu/{$ds['username']}'" <td onclick="window.location.href = '{Text::url('')}customers/viewu/{$ds['username']}'"
style="cursor:pointer;">{$ds['username']}</td> style="cursor:pointer;">{$ds['username']}</td>
<td>{$ds['fullname']}</td>
<td>{$ds['plan_name']}</td> <td>{$ds['plan_name']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td> <td>{Lang::moneyFormat($ds['price'])}</td>
<td>{$ds['type']}</td> <td>{$ds['type']}</td>

View File

@ -94,10 +94,11 @@
<a href="{Text::url('export/pdf-by-date&')}{$filter}" class="btn btn-default"><i <a href="{Text::url('export/pdf-by-date&')}{$filter}" class="btn btn-default"><i
class="fa fa-file-pdf-o"></i></a> class="fa fa-file-pdf-o"></i></a>
</th> </th>
<th colspan="7"></th> <th colspan="8"></th>
</tr> </tr>
<tr> <tr>
<th>{Lang::T('Username')}</th> <th>{Lang::T('Username')}</th>
<th>{Lang::T('Fullname')}</th>
<th>{Lang::T('Type')}</th> <th>{Lang::T('Type')}</th>
<th>{Lang::T('Plan Name')}</th> <th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th> <th>{Lang::T('Plan Price')}</th>
@ -111,6 +112,7 @@
{foreach $d as $ds} {foreach $d as $ds}
<tr> <tr>
<td>{$ds['username']}</td> <td>{$ds['username']}</td>
<td>{$ds['fullname']}</td>
<td>{$ds['type']}</td> <td>{$ds['type']}</td>
<td>{$ds['plan_name']}</td> <td>{$ds['plan_name']}</td>
<td class="text-right">{Lang::moneyFormat($ds['price'])}</td> <td class="text-right">{Lang::moneyFormat($ds['price'])}</td>
@ -122,9 +124,8 @@
{/foreach} {/foreach}
<tr> <tr>
<th>{Lang::T('Total')}</th> <th>{Lang::T('Total')}</th>
<td colspan="2"></td>
<th class="text-right">{Lang::moneyFormat($dr)}</th> <th class="text-right">{Lang::moneyFormat($dr)}</th>
<td colspan="4"></td> <td colspan="7"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -37,6 +37,7 @@
<thead> <thead>
<tr> <tr>
<th>{Lang::T('Username')}</th> <th>{Lang::T('Username')}</th>
<th>{Lang::T('Fullname')}</th>
<th>{Lang::T('Type')}</th> <th>{Lang::T('Type')}</th>
<th>{Lang::T('Plan Name')}</th> <th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th> <th>{Lang::T('Plan Price')}</th>
@ -50,6 +51,7 @@
{foreach $d as $ds} {foreach $d as $ds}
<tr> <tr>
<td>{$ds['username']}</td> <td>{$ds['username']}</td>
<td>{$ds['fullname']}</td>
<td>{$ds['type']}</td> <td>{$ds['type']}</td>
<td>{$ds['plan_name']}</td> <td>{$ds['plan_name']}</td>
<td class="text-right">{Lang::moneyFormat($ds['price'])}</td> <td class="text-right">{Lang::moneyFormat($ds['price'])}</td>

View File

@ -155,7 +155,7 @@
<h3 class="panel-title"> <h3 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseLoginPage" <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseLoginPage"
aria-expanded="true" aria-controls="collapseLoginPage"> aria-expanded="true" aria-controls="collapseLoginPage">
{Lang::T('Customer Login Page Settings')} {Lang::T('Customer Login Page')}
</a> </a>
</h3> </h3>
</div> </div>
@ -254,6 +254,37 @@
</div> </div>
</div> </div>
<div class="panel">
<div class="panel-heading" role="tab" id="Coupon">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
href="#collapseCoupon" aria-expanded="false" aria-controls="collapseCoupon">
{Lang::T('Coupons')}
</a>
</h4>
</div>
<div id="collapseCoupon" class="panel-collapse collapse" role="tabpanel">
<div class="panel-body">
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Enable Coupon')}</label>
<div class="col-md-5">
<select name="enable_coupons" id="enable_coupons" class="form-control text-muted">
<option value="no">{Lang::T('No')}</option>
<option value="yes" {if $_c['enable_coupons'] == 'yes'}selected="selected" {/if}>{Lang::T('Yes')}
</option>
</select>
</div>
<p class="help-block col-md-4">
<small>{Lang::T('Enable or disable coupons')}</small>
</p>
</div>
<button class="btn btn-success btn-block" type="submit">
{Lang::T('Save Changes')}
</button>
</div>
</div>
</div>
<div class="panel"> <div class="panel">
<div class="panel-heading" role="tab" id="Registration"> <div class="panel-heading" role="tab" id="Registration">
<h4 class="panel-title"> <h4 class="panel-title">

View File

@ -1,25 +1,25 @@
</section> </section>
</div> </div>
{if isset($_c['CompanyFooter'])} {if isset($_c['CompanyFooter'])}
<footer class="main-footer"> <footer class="main-footer">
{$_c['CompanyFooter']} {$_c['CompanyFooter']}
<div class="pull-right"> <div class="pull-right">
<a href="javascript:showPrivacy()">Privacy</a> <a href="javascript:showPrivacy()">Privacy</a>
&bull; &bull;
<a href="javascript:showTaC()">T &amp; C</a> <a href="javascript:showTaC()">T &amp; C</a>
</div> </div>
</footer> </footer>
{else} {else}
<footer class="main-footer"> <footer class="main-footer">
PHPNuxBill by <a href="https://github.com/hotspotbilling/phpnuxbill" rel="nofollow noreferrer noopener" PHPNuxBill by <a href="https://github.com/hotspotbilling/phpnuxbill" rel="nofollow noreferrer noopener"
target="_blank">iBNuX</a>, Theme by <a href="https://adminlte.io/" rel="nofollow noreferrer noopener" target="_blank">iBNuX</a>, Theme by <a href="https://adminlte.io/" rel="nofollow noreferrer noopener"
target="_blank">AdminLTE</a> target="_blank">AdminLTE</a>
<div class="pull-right"> <div class="pull-right">
<a href="javascript:showPrivacy()">Privacy</a> <a href="javascript:showPrivacy()">Privacy</a>
&bull; &bull;
<a href="javascript:showTaC()">T &amp; C</a> <a href="javascript:showTaC()">T &amp; C</a>
</div> </div>
</footer> </footer>
{/if} {/if}
</div> </div>
@ -50,156 +50,175 @@
<script src="{$app_url}/ui/ui/scripts/custom.js?2025.2.5"></script> <script src="{$app_url}/ui/ui/scripts/custom.js?2025.2.5"></script>
{if isset($xfooter)} {if isset($xfooter)}
{$xfooter} {$xfooter}
{/if} {/if}
{if $_c['tawkto'] != ''} {if $_c['tawkto'] != ''}
<!--Start of Tawk.to Script--> <!--Start of Tawk.to Script-->
<script type="text/javascript"> <script type="text/javascript">
var isLoggedIn = false; var isLoggedIn = false;
var Tawk_API = { var Tawk_API = {
onLoad: function() { onLoad: function () {
Tawk_API.setAttributes({ Tawk_API.setAttributes({
'username' : '{$_user['username']}', 'username': '{$_user['username']}',
'service' : '{$_user['service_type']}', 'service': '{$_user['service_type']}',
'balance' : '{$_user['balance']}', 'balance': '{$_user['balance']}',
'account' : '{$_user['account_type']}', 'account': '{$_user['account_type']}',
'phone' : '{$_user['phonenumber']}' 'phone': '{$_user['phonenumber']}'
}, function(error) { }, function (error) {
console.log(error) console.log(error)
}); });
} }
};
var Tawk_LoadStart = new Date();
Tawk_API.visitor = {
name: '{$_user['fullname']}',
email: '{$_user['email']}',
phone: '{$_user['phonenumber']}'
}; };
var Tawk_LoadStart = new Date(); (function () {
Tawk_API.visitor = { var s1 = document.createElement("script"),
name: '{$_user['fullname']}', s0 = document.getElementsByTagName("script")[0];
email: '{$_user['email']}', s1.async = true;
phone: '{$_user['phonenumber']}' s1.src = 'https://embed.tawk.to/{$_c['tawkto']}';
}; s1.charset = 'UTF-8';
(function() { s1.setAttribute('crossorigin', '*');
var s1 = document.createElement("script"), s0.parentNode.insertBefore(s1, s0);
s0 = document.getElementsByTagName("script")[0]; })();
s1.async = true; </script>
s1.src = 'https://embed.tawk.to/{$_c['tawkto']}'; <!--End of Tawk.to Script-->
s1.charset = 'UTF-8'; {/if}
s1.setAttribute('crossorigin', '*');
s0.parentNode.insertBefore(s1, s0);
})();
</script>
<!--End of Tawk.to Script-->
{/if}
<script> <script>
const toggleIcon = document.getElementById('toggleIcon'); const toggleIcon = document.getElementById('toggleIcon');
const body = document.body; const body = document.body;
const savedMode = localStorage.getItem('mode'); const savedMode = localStorage.getItem('mode');
if (savedMode === 'dark') { if (savedMode === 'dark') {
body.classList.add('dark-mode');
toggleIcon.textContent = '🌞';
}
function setMode(mode) {
if (mode === 'dark') {
body.classList.add('dark-mode'); body.classList.add('dark-mode');
toggleIcon.textContent = '🌞'; toggleIcon.textContent = '🌞';
} else {
body.classList.remove('dark-mode');
toggleIcon.textContent = '🌜';
} }
}
function setMode(mode) {
if (mode === 'dark') { toggleIcon.addEventListener('click', () => {
body.classList.add('dark-mode'); if (body.classList.contains('dark-mode')) {
toggleIcon.textContent = '🌞'; setMode('light');
} else { localStorage.setItem('mode', 'light');
body.classList.remove('dark-mode'); } else {
toggleIcon.textContent = '🌜'; setMode('dark');
} localStorage.setItem('mode', 'dark');
} }
});
toggleIcon.addEventListener('click', () => { </script>
if (body.classList.contains('dark-mode')) {
setMode('light');
localStorage.setItem('mode', 'light');
} else {
setMode('dark');
localStorage.setItem('mode', 'dark');
}
});
</script>
{literal} {literal}
<script> <script>
var listAtts = document.querySelectorAll(`[api-get-text]`); var listAtts = document.querySelectorAll(`[api-get-text]`);
listAtts.forEach(function(el) { listAtts.forEach(function (el) {
$.get(el.getAttribute('api-get-text'), function(data) { $.get(el.getAttribute('api-get-text'), function (data) {
el.innerHTML = data; el.innerHTML = data;
});
}); });
$(document).ready(function() { });
var listAtts = document.querySelectorAll(`button[type="submit"]`); $(document).ready(function () {
listAtts.forEach(function(el) { var listAtts = document.querySelectorAll(`button[type="submit"]`);
if (el.addEventListener) { // all browsers except IE before version 9 listAtts.forEach(function (el) {
el.addEventListener("click", function() { if (el.addEventListener) { // all browsers except IE before version 9
el.addEventListener("click", function () {
$(this).html(
`<span class="loading"></span>`
);
setTimeout(() => {
$(this).prop("disabled", true);
}, 100);
}, false);
} else {
if (el.attachEvent) { // IE before version 9
el.attachEvent("click", function () {
$(this).html( $(this).html(
`<span class="loading"></span>` `<span class="loading"></span>`
); );
setTimeout(() => { setTimeout(() => {
$(this).prop("disabled", true); $(this).prop("disabled", true);
}, 100); }, 100);
}, false); });
} else { }
if (el.attachEvent) { // IE before version 9 }
el.attachEvent("click", function() { $(function () {
$(this).html( $('[data-toggle="tooltip"]').tooltip()
`<span class="loading"></span>` })
); });
setTimeout(() => { });
$(this).prop("disabled", true);
}, 100); function ask(field, text) {
}); const txt = field.innerHTML;
field.innerHTML = `<span class="loading"></span>`;
field.setAttribute("disabled", true);
Swal.fire({
title: 'Are you sure?',
text: text,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, proceed',
cancelButtonText: 'Cancel',
}).then((result) => {
let delay = result.isConfirmed ? 400 : 500;
setTimeout(() => {
field.innerHTML = txt;
field.removeAttribute("disabled");
if (result.isConfirmed) {
const form = field.closest('form');
if (form) {
form.submit(); // manually submit the form
} else {
//fallback if not in a form
const href = field.getAttribute("href") || field.dataset.href;
if (href) window.location.href = href;
} }
} }
$(function() { }, delay);
$('[data-toggle="tooltip"]').tooltip()
})
});
}); });
function ask(field, text){ return false;
var txt = field.innerHTML; }
if (confirm(text)) {
setTimeout(() => {
field.innerHTML = field.innerHTML.replace(`<span class="loading"></span>`, txt);
field.removeAttribute("disabled");
}, 5000);
return true;
} else {
setTimeout(() => {
field.innerHTML = field.innerHTML.replace(`<span class="loading"></span>`, txt);
field.removeAttribute("disabled");
}, 500);
return false;
}
}
function setCookie(name, value, days) { function setCookie(name, value, days) {
var expires = ""; var expires = "";
if (days) { if (days) {
var date = new Date(); var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString(); expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
} }
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
function getCookie(name) { function getCookie(name) {
var nameEQ = name + "="; var nameEQ = name + "=";
var ca = document.cookie.split(';'); var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) { for (var i = 0; i < ca.length; i++) {
var c = ca[i]; var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length); while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
} }
</script> return null;
}
</script>
{/literal} {/literal}
<script> <script>
setCookie('user_language', '{$user_language}', 365); setCookie('user_language', '{$user_language}', 365);
</script> </script>
</body> </body>