Compare commits

...

51 Commits

Author SHA1 Message Date
66432eda56 2024.2.7 2024-02-07 13:35:23 +07:00
92eee8245d timeElapsed 2024-02-07 13:32:33 +07:00
f62f07d102 add Hide Dashboard Content 2024-02-07 12:02:39 +07:00
671154d146 add sub sales 2024-02-07 10:11:30 +07:00
ac84e4b235 settimezone 2024-02-07 09:39:49 +07:00
db7c6014dc Fix invoice keyword 2024-02-06 17:41:45 +07:00
21b57ef471 Fix price for logging 2024-02-06 16:54:57 +07:00
79e5c72ca2 2024.2.6 2024-02-06 16:48:59 +07:00
5921fef67e cache Voucher stocks for 5 Minutes 2024-02-06 16:46:50 +07:00
1e0b246d74 Cache for 12 hours 2024-02-06 16:43:06 +07:00
009c890ab6 Fix router_name for log 2024-02-06 16:41:41 +07:00
f3d7687cdb cache dashboard graph 2024-02-06 16:41:26 +07:00
80cecabfb0 Fix Calculation 2024-02-05 14:25:23 +07:00
00ac91903f Merge pull request #103 from Focuslinkstech/master
Dashboard Updates
2024-02-05 14:19:37 +07:00
500f3de6a9 Update dashboard.tpl
fixed inactive users in graph
2024-02-05 08:17:10 +01:00
6c2658bf03 Update dashboard.tpl
change all users to total users
2024-02-05 07:41:15 +01:00
59de353353 Merge branch 'hotspotbilling:master' into master 2024-02-05 07:33:47 +01:00
d5ea56d078 Update dashboard.tpl
change active users to All Users Insight
add All users to graph
2024-02-05 07:24:32 +01:00
7cc8034b8c 2024.2.5 2024-02-05 10:08:10 +07:00
c3a76bab90 Language add 2024-02-05 10:05:41 +07:00
8f32a7cfa9 Merge pull request #102 from Focuslinkstech/master
Dashboard update
2024-02-05 09:45:37 +07:00
cbe2602b69 Dashboard update
add monthly Sales Graph
add Monthly Registered Customers Graph
add Active Users Graph
2024-02-04 20:25:31 +01:00
771bc9d8d9 Fix edit plan for user 2024-02-02 13:40:08 +07:00
788e558171 activate plan when its on 2024-02-02 13:39:03 +07:00
4ab32bc68d Fix Edit Plan 2024-02-02 13:38:22 +07:00
bba09ca647 activate customer when edit Expired 2024-01-29 11:34:07 +07:00
adbac642ca 2024.1.24 2024-01-24 15:06:36 +07:00
7e7b70ba75 Add test SMS, WA and Telegram 2024-01-24 14:02:58 +07:00
72fbc27f97 2024.1.19 2024-01-19 09:27:22 +07:00
d386dc5eec Sell your own plugin 2024-01-19 09:24:24 +07:00
41481880ab support full package 2024-01-19 09:22:43 +07:00
f8a879dc0f paymentgateway to paymentgateway folder 2024-01-19 09:09:07 +07:00
da44e3f6da codecanyon theme install 2024-01-19 09:08:49 +07:00
afdd7edafa themes support 2024-01-19 09:07:48 +07:00
4e1a10d814 Envato Personal Token 2024-01-18 17:24:59 +07:00
7046aa5ed1 CodeCanyon integration fix 2024-01-18 17:24:21 +07:00
d81ba5d5fb codecanyon integration 2024-01-18 15:41:24 +07:00
a35506db1b 2024.1.18 2024-01-18 13:33:22 +07:00
96945ab813 Merge pull request #99 from axmad386/patch-1
fix(mikrotik): set pool $poolId always empty
2024-01-18 12:00:51 +07:00
2b8ca5fd85 fix(mikrotik): set pool $poolId always empty 2024-01-18 01:55:23 +07:00
2d095aef08 fix delete logs 2024-01-17 13:42:07 +07:00
c906d47674 minor change, for plugin, menu can have notifications 2024-01-17 10:56:39 +07:00
5dd430f9b2 Formatting code 2024-01-17 10:54:31 +07:00
7c88be8865 Merge pull request #98 from Focuslinkstech/master
Nav menu label added
2024-01-17 10:39:25 +07:00
b45f5a5587 Merge branch 'hotspotbilling:master' into master 2024-01-16 22:26:57 +01:00
10e788e9a2 update
Nav label added
2024-01-16 22:19:38 +01:00
7ceb883826 fix notifications 2024-01-16 15:16:59 +07:00
80e78d9796 Fix sendPackageNotification 2024-01-16 15:08:26 +07:00
534d62d944 2024.1.16.1 2024-01-16 11:41:25 +07:00
1857c145d1 fix print 2024-01-16 11:41:12 +07:00
12cdef4f66 remove debug 2024-01-16 10:36:55 +07:00
32 changed files with 984 additions and 185 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ config.php
ui/compiled/*.php
ui/cache/*.php
test.php
sms.php
pages/
system/cache/**
system/plugin/*

View File

@ -2,6 +2,42 @@
# CHANGELOG
## 2024.2.7
- Hide Dashboard content
## 2024.2.6
- Cache graph for faster opening graph
## 2024.2.5
- Admin Dashboard Update
- Add Monthly Registered Customers
- Total Monthly Sales
- Active Users
## 2024.2.2
- Fix edit plan for user
## 2024.1.24
- Add Send test for SMS, Whatsapp and Telegram
## 2024.1.19
- Paid Plugin, Theme, and payment gateway marketplace using codecanyon.net
- Fix Plugin manager List
## 2024.1.18
- fix(mikrotik): set pool $poolId always empty
## 2024.1.17
- Add minor change, for plugin, menu can have notifications by @Focuslinkstech
## 2024.1.16
- Add yellow color to table for plan not allowed to purchase

View File

@ -15,7 +15,7 @@
- Hotspot & PPPOE
- Easy Installation
- Multi Language
- Payment Gateway Midtrans, Xendit and Tripay
- Payment Gateway
- SMS validation for login
- Whatsapp Notification to Consumer
- Telegram Notification for Admin

View File

@ -188,6 +188,8 @@ CREATE TABLE `tb_languages` (
`id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
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_appconfig`
ADD PRIMARY KEY (`id`);

View File

@ -16,8 +16,10 @@ $menu_registered = array();
* Admin/Sales menu: AFTER_DASHBOARD, CUSTOMERS, PREPAID, SERVICES, REPORTS, VOUCHER, AFTER_ORDER, NETWORK, SETTINGS, AFTER_PAYMENTGATEWAY
* | Customer menu: AFTER_DASHBOARD, ORDER, HISTORY, ACCOUNTS
* @param string icon from ion icon, ion-person, only for AFTER_
* @param string label for showing label or number of notification or update
* @param string color Label color
*/
function register_menu($name, $admin, $function, $position, $icon = '')
function register_menu($name, $admin, $function, $position, $icon = '', $label = '', $color = 'success')
{
global $menu_registered;
$menu_registered[] = [
@ -25,7 +27,9 @@ function register_menu($name, $admin, $function, $position, $icon = '')
"admin" => $admin,
"position" => $position,
"icon" => $icon,
"function" => $function
"function" => $function,
"label" => $label,
"color" => $color
];
}
@ -48,4 +52,3 @@ function run_hook($action){
}
}
}

View File

@ -56,6 +56,24 @@ class Lang
return date($config['date_format'] . ' H:i', strtotime("$date $time"));
}
public static function timeElapsed($time){
$s = $time%60;
$m = floor(($time%3600)/60);
$h = floor(($time%86400)/3600);
$d = floor(($time%2592000)/86400);
$M = floor($time/2592000);
$result = '';
if($M>0){
$result = $M.'m ';
}
if($d>0){
$result .= $d.'d ';
}else if($M>0){
$result .= '0d ';
}
return "$result$h:$m:$s";
}
public static function nl2br($text)
{
return nl2br($text);
@ -112,7 +130,17 @@ class Lang
if($config['printer_cols']){
$cols = $config['printer_cols'];
}
return str_pad($text, $cols, $pad_string, $pad_type);
$text = trim($text);
$texts = explode("\n", $text);
if(count($texts)>1){
$text = '';
foreach($texts as $t){
$text.= self::pad(trim($t), $pad_string, $pad_type)."\n";
}
return $text;
}else{
return str_pad(trim($text), $cols, $pad_string, $pad_type);
}
}
public static function pads($textLeft, $textRight, $pad_string = ' '){

View File

@ -14,7 +14,7 @@ class Message
global $config;
run_hook('send_telegram'); #HOOK
if (!empty($config['telegram_bot']) && !empty($config['telegram_target_id'])) {
Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?chat_id=' . $config['telegram_target_id'] . '&text=' . urlencode($txt));
return Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?chat_id=' . $config['telegram_target_id'] . '&text=' . urlencode($txt));
}
}
@ -27,17 +27,17 @@ class Message
if (strlen($config['sms_url']) > 4 && substr($config['sms_url'], 0, 4) != "http") {
if (strlen($txt) > 160) {
$txts = str_split($txt, 160);
foreach ($txts as $txt) {
try {
$mikrotik = Mikrotik::info($config['sms_url']);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
try {
$mikrotik = Mikrotik::info($config['sms_url']);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
foreach ($txts as $txt) {
Mikrotik::sendSMS($client, $phone, $txt);
} catch (Exception $e) {
// ignore, add to logs
_log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
}
} catch (Exception $e) {
// ignore, add to logs
_log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
}
}else{
} else {
try {
$mikrotik = Mikrotik::info($config['sms_url']);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
@ -50,7 +50,7 @@ class Message
} else {
$smsurl = str_replace('[number]', urlencode($phone), $config['sms_url']);
$smsurl = str_replace('[text]', urlencode($txt), $smsurl);
Http::getData($smsurl);
return Http::getData($smsurl);
}
}
}
@ -62,15 +62,15 @@ class Message
if (!empty($config['wa_url'])) {
$waurl = str_replace('[number]', urlencode($phone), $config['wa_url']);
$waurl = str_replace('[text]', urlencode($txt), $waurl);
Http::getData($waurl);
return Http::getData($waurl);
}
}
public static function sendPackageNotification($phone, $name, $package, $price, $message, $via)
{
$msg = str_replace('[[name]]', "$name", $message);
$msg = str_replace('[[package]]', "$package", $msg);
$msg = str_replace('[[price]]', "$price", $msg);
$msg = str_replace('[[name]]', $name, $message);
$msg = str_replace('[[package]]', $package, $msg);
$msg = str_replace('[[price]]', $price, $msg);
if (
!empty($phone) && strlen($phone) > 5
&& !empty($message) && in_array($via, ['sms', 'wa'])
@ -86,7 +86,7 @@ class Message
public static function sendBalanceNotification($phone, $name, $balance, $balance_now, $message, $via)
{
$msg = str_replace('[[name]]', "$name", $message);
$msg = str_replace('[[name]]', $name, $message);
$msg = str_replace('[[current_balance]]', Lang::moneyFormat($balance_now), $msg);
$msg = str_replace('[[balance]]', Lang::moneyFormat($balance), $msg);
if (
@ -111,8 +111,9 @@ class Message
$textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $trx['invoice'], $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateAndTimeFormat($trx['recharged_on'], $trx['recharged_time']), $textInvoice);
$textInvoice = str_replace('[[payment_gateway]]', $config['gateway'], $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', $config['channel'], $textInvoice);
$gc = explode("-", $trx['method']);
$textInvoice = str_replace('[[payment_gateway]]', trim($gc[0]), $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', trim($gc[1]), $textInvoice);
$textInvoice = str_replace('[[type]]', $trx['type'], $textInvoice);
$textInvoice = str_replace('[[plan_name]]', $trx['plan_name'], $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($trx['price']), $textInvoice);

View File

@ -130,7 +130,6 @@ class Mikrotik
->setArgument('address-pool', $pool)
->setArgument('rate-limit', '512K/512K')
);
die("| $profileID | $name | $pool |");
} else {
$setRequest = new RouterOS\Request('/ip/hotspot/user/profile/set');
$client->sendSync(
@ -428,7 +427,7 @@ class Mikrotik
'/ip pool print .proplist=.id',
RouterOS\Query::where('name', $name)
);
$poolID = $client->sendSync($printRequest)->getProperty('id');
$poolID = $client->sendSync($printRequest)->getProperty('.id');
if (empty($poolID)) {
self::addPool($client, $name, $ip_address);
@ -515,4 +514,37 @@ class Mikrotik
->setArgument('message', $message);
$client->sendSync($smsRequest);
}
public static function addIpToAddressList($client, $ip, $listName, $comment = '')
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$addRequest = new RouterOS\Request('/ip/firewall/address-list/add');
$client->sendSync(
$addRequest
->setArgument('address', $ip)
->setArgument('comment', $comment)
->setArgument('list', $listName)
);
}
public static function removeIpFromAddressList($client, $ip)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$printRequest = new RouterOS\Request(
'/ip firewall address-list print .proplist=.id',
RouterOS\Query::where('address', $ip)
);
$id = $client->sendSync($printRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ip/firewall/address-list/remove');
$client->sendSync(
$removeRequest
->setArgument('numbers', $id)
);
}
}

View File

@ -59,8 +59,8 @@ class Package
$textInvoice = str_replace('[[phone]]', $_c['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $inv, $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateTimeFormat($date_now), $textInvoice);
$textInvoice = str_replace('[[payment_gateway]]', $_c['gateway'], $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', $_c['channel'], $textInvoice);
$textInvoice = str_replace('[[payment_gateway]]', $gateway, $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', $channel, $textInvoice);
$textInvoice = str_replace('[[type]]', 'Balance', $textInvoice);
$textInvoice = str_replace('[[plan_name]]', $p['name_plan'], $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($p['price']), $textInvoice);
@ -316,8 +316,7 @@ class Package
"\nPrice: " . Lang::moneyFormat($p['price']));
}
$in = ORM::for_table('tbl_transactions')->where('username', $c['username'])->order_by_desc('id')->find_one();
Message::sendInvoice($c, $in);
Message::sendInvoice($c, $t);
return true;
}

View File

@ -20,7 +20,7 @@ class Validator
* @param array $hits
* @return void
*/
private static function textHit($string, $exclude = "")
public static function textHit($string, $exclude = "")
{
if (empty($exclude)) return false;
if (is_array($exclude)) {

View File

@ -108,6 +108,21 @@ try {
ORM::configure('driver_options', array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'), 'radius');
ORM::configure('return_result_sets', true, 'radius');
}
} catch (Throwable $e) {
$ui = new Smarty();
$ui->setTemplateDir(['custom' => File::pathFixer('ui/ui_custom/'), 'default' => File::pathFixer('ui/ui/')]);
$ui->assign('_url', APP_URL . '/index.php?_route=');
$ui->setCompileDir(File::pathFixer('ui/compiled/'));
$ui->setConfigDir(File::pathFixer('ui/conf/'));
$ui->setCacheDir(File::pathFixer('ui/cache/'));
$ui->assign("error_title", "PHPNuxBill Crash");
if (isset($_SESSION['uid'])) {
$ui->assign("error_message", $e->getMessage() . '<br>');
} else {
$ui->assign("error_message", $e->getMessage() . '<br><pre>' . $e->getTraceAsString() . '</pre>');
}
$ui->display('router-error.tpl');
die();
} catch (Exception $e) {
$ui = new Smarty();
$ui->setTemplateDir(['custom' => File::pathFixer('ui/ui_custom/'), 'default' => File::pathFixer('ui/ui/')]);
@ -227,7 +242,13 @@ function _admin($login = true)
function _log($description, $type = '', $userid = '0')
{
Log::put($type, $description, $userid);
$d = ORM::for_table('tbl_logs')->create();
$d->date = date('Y-m-d H:i:s');
$d->type = $type;
$d->description = $description;
$d->userid = $userid;
$d->ip = $_SERVER["REMOTE_ADDR"];
$d->save();
}
function Lang($key)
@ -311,7 +332,6 @@ if ($handler == '') {
$handler = 'default';
}
try {
$sys_render = File::pathFixer('system/controllers/' . $handler . '.php');
if (file_exists($sys_render)) {
$menus = array();
@ -326,12 +346,20 @@ try {
if (!empty($menu['icon'])) {
$menus[$menu['position']] .= '<i class="' . $menu['icon'] . '"></i>';
}
if (!empty($menu['label'])) {
$menus[$menu['position']] .= '<span class="pull-right-container">';
$menus[$menu['position']] .= '<small class="label pull-right bg-' . $menu['color'] . '">' . $menu['label'] . '</small></span>';
}
$menus[$menu['position']] .= '<span class="text">' . $menu['name'] . '</span></a></li>';
} else if (!$menu['admin'] && _auth(false)) {
$menus[$menu['position']] .= '<li' . (($routes[1] == $menu['function']) ? ' class="active"' : '') . '><a href="' . U . 'plugin/' . $menu['function'] . '">';
if (!empty($menu['icon'])) {
$menus[$menu['position']] .= '<i class="' . $menu['icon'] . '"></i>';
}
if (!empty($menu['label'])) {
$menus[$menu['position']] .= '<span class="pull-right-container">';
$menus[$menu['position']] .= '<small class="label pull-right bg-' . $menu['color'] . '">' . $menu['label'] . '</small></span>';
}
$menus[$menu['position']] .= '<span class="text">' . $menu['name'] . '</span></a></li>';
}
}
@ -343,6 +371,14 @@ try {
} else {
r2(U . 'dashboard', 'e', 'not found');
}
} catch (Throwable $e) {
if (!isset($_SESSION['aid']) || empty($_SESSION['aid'])) {
r2(U . 'home', 'e', $e->getMessage());
}
$ui->assign("error_message", $e->getMessage() . '<br><pre>' . $e->getTraceAsString() . '</pre>');
$ui->assign("error_title", "PHPNuxBill Crash");
$ui->display('router-error.tpl');
die();
} catch (Exception $e) {
if (!isset($_SESSION['aid']) || empty($_SESSION['aid'])) {
r2(U . 'home', 'e', $e->getMessage());

View File

@ -0,0 +1,126 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', 'CodeCanyon.net');
$ui->assign('_system_menu', 'settings');
$plugin_repository = 'https://hotspotbilling.github.io/Plugin-Repository/repository.json';
$action = $routes['1'];
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$cache = File::pathFixer('system/cache/codecanyon.json');
if ($admin['user_type'] != 'Admin') {
r2(U . "dashboard", 'e', $_L['Do_Not_Access']);
}
if (empty($config['envato_token'])) {
r2(U . 'settings/app', 'w', '<a href="' . U . 'settings/app#envato' . '">Envato Personal Access Token</a> is not set');
}
switch ($action) {
case 'install':
if (!is_writeable(File::pathFixer('system/cache/'))) {
r2(U . "codecanyon", 'e', 'Folder system/cache/ is not writable');
}
if (!is_writeable(File::pathFixer('system/plugin/'))) {
r2(U . "codecanyon", 'e', 'Folder system/plugin/ is not writable');
}
if (!is_writeable(File::pathFixer('system/paymentgateway/'))) {
r2(U . "codecanyon", 'e', 'Folder system/paymentgateway/ is not writable');
}
set_time_limit(-1);
$item_id = $routes['2'];
$tipe = $routes['3'];
$result = Http::getData('https://api.envato.com/v3/market/buyer/download?item_id=' . $item_id, ['Authorization: Bearer ' . $config['envato_token']]);
$json = json_decode($result, true);
if (!isset($json['download_url'])) {
r2(U . 'codecanyon', 'e', 'Failed to get download url. ' . $json['description']);
}
$file = File::pathFixer('system/cache/codecanyon/');
if (!file_exists($file)) {
mkdir($file);
}
$file .= $item_id . '.zip';
if (file_exists($file))
unlink($file);
//download
$fp = fopen($file, 'w+');
$ch = curl_init($json['download_url']);
curl_setopt($ch, CURLOPT_POST, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 120);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_exec($ch);
curl_close($ch);
fclose($fp);
//extract
$target = File::pathFixer('system/cache/codecanyon/' . $item_id . '/');
$zip = new ZipArchive();
$zip->open($file);
$zip->extractTo($target);
$zip->close();
//moving
if (file_exists($target . 'plugin')) {
File::copyFolder($target . 'plugin', File::pathFixer('system/plugin/'));
} else if (file_exists($target . 'paymentgateway')) {
File::copyFolder($target . 'paymentgateway', File::pathFixer('system/paymentgateway/'));
} else if (file_exists($target . 'theme')) {
File::copyFolder($target . 'theme', File::pathFixer('ui/themes/'));
}
//Cleaning
File::deleteFolder($target);
unlink($file);
r2(U . "codecanyon", 's', 'Installation success');
case 'reload':
if (file_exists($cache))
unlink($cache);
default:
if (class_exists('ZipArchive')) {
$zipExt = true;
} else {
$zipExt = false;
}
$ui->assign('zipExt', $zipExt);
if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) {
$txt = file_get_contents($cache);
$plugins = json_decode($txt, true);
$ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60)));
if (count($plugins) == 0) {
unlink($cache);
r2(U . 'codecanyon');
}
} else {
$plugins = [];
$page = _get('page', 1);
back:
$result = Http::getData('https://api.envato.com/v3/market/buyer/list-purchases?&page=' . $page, ['Authorization: Bearer ' . $config['envato_token']]);
$items = json_decode($result, true);
if ($items && count($items['results']) > 0) {
foreach ($items['results'] as $item) {
$name = strtolower($item['item']['name']);
if (strpos($name, 'phpnuxbill') !== false) {
$plugins[] = $item;
}
}
$page++;
goto back;
}
if (count($plugins) > 0) {
file_put_contents($cache, json_encode($plugins));
if (file_exists($cache)) {
$ui->assign('chached_until', date($config['date_format'] . ' H:i', filemtime($cache) + (24 * 60 * 60)));
}
}
}
$ui->assign('plugins', $plugins);
$ui->display('codecanyon.tpl');
}

View File

@ -51,26 +51,28 @@ if (empty($c_all)) {
}
$ui->assign('c_all', $c_all);
//user expire
$paginator = Paginator::build(ORM::for_table('tbl_user_recharges'));
$expire = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $mdate)
->offset($paginator['startpoint'])
->limit($paginator['limit'])
->order_by_desc('expiration')
->find_many();
if($config['hide_uet'] != 'yes'){
//user expire
$paginator = Paginator::build(ORM::for_table('tbl_user_recharges'));
$expire = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $mdate)
->offset($paginator['startpoint'])
->limit($paginator['limit'])
->order_by_desc('expiration')
->find_many();
// Get the total count of expired records for pagination
$totalCount = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $mdate)
->count();
// Get the total count of expired records for pagination
$totalCount = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $mdate)
->count();
// Pass the total count and current page to the paginator
$paginator['total_count'] = $totalCount;
// Pass the total count and current page to the paginator
$paginator['total_count'] = $totalCount;
// Assign the pagination HTML to the template variable
$ui->assign('paginator', $paginator);
$ui->assign('expire', $expire);
// Assign the pagination HTML to the template variable
$ui->assign('paginator', $paginator);
$ui->assign('expire', $expire);
}
//activity log
$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->find_many();
@ -78,28 +80,113 @@ $ui->assign('dlog', $dlog);
$log = ORM::for_table('tbl_logs')->count();
$ui->assign('log', $log);
// Count stock
$tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many();
$plans = array();
$stocks = array("used" => 0, "unused" => 0);
$n = 0;
foreach ($tmp as $plan) {
$unused = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 0)->count();
$used = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 1)->count();
if($unused>0 || $used>0){
$plans[$n]['name_plan'] = $plan['name_plan'];
$plans[$n]['unused'] = $unused;
$plans[$n]['used'] = $used;
$stocks["unused"] += $unused;
$stocks["used"] += $used;
$n++;
if($config['hide_vs'] != 'yes'){
$cacheStocksfile = File::pathFixer('system/cache/VoucherStocks.temp');
$cachePlanfile = File::pathFixer('system/cache/VoucherPlans.temp');
//Cache for 5 minutes
if(file_exists($cacheStocksfile) && time()- filemtime($cacheStocksfile) < 600){
$stocks = json_decode(file_get_contents($cacheStocksfile), true);
$plans = json_decode(file_get_contents($cachePlanfile), true);
}else{
// Count stock
$tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many();
$plans = array();
$stocks = array("used" => 0, "unused" => 0);
$n = 0;
foreach ($tmp as $plan) {
$unused = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 0)->count();
$used = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 1)->count();
if ($unused > 0 || $used > 0) {
$plans[$n]['name_plan'] = $plan['name_plan'];
$plans[$n]['unused'] = $unused;
$plans[$n]['used'] = $used;
$stocks["unused"] += $unused;
$stocks["used"] += $used;
$n++;
}
}
file_put_contents($cacheStocksfile, json_encode($stocks));
file_put_contents($cachePlanfile, json_encode($plans));
}
}
$cacheMRfile = File::pathFixer('system/cache/monthlyRegistered.temp');
//Cache for 1 hour
if(file_exists($cacheMRfile) && time()- filemtime($cacheMRfile) < 3600){
$monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true);
}else{
//Monthly Registered Customers
$result = ORM::for_table('tbl_customers')
->select_expr('MONTH(created_at)', 'month')
->select_expr('COUNT(*)', 'count')
->where_raw('YEAR(created_at) = YEAR(NOW())')
->group_by_expr('MONTH(created_at)')
->find_many();
$monthlyRegistered = [];
foreach ($result as $row) {
$monthlyRegistered[] = [
'date' => $row->month,
'count' => $row->count
];
}
file_put_contents($cacheMRfile, json_encode($monthlyRegistered));
}
$cacheMSfile = File::pathFixer('system/cache/monthlySales.temp');
//Cache for 12 hours
if(file_exists($cacheMSfile) && time()- filemtime($cacheMSfile) < 43200){
$monthlySales = json_decode(file_get_contents($cacheMSfile), true);
}else{
// Query to retrieve monthly data
$results = ORM::for_table('tbl_transactions')
->select_expr('MONTH(recharged_on)', 'month')
->select_expr('SUM(price)', 'total')
->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year
->group_by_expr('MONTH(recharged_on)')
->find_many();
// Create an array to hold the monthly sales data
$monthlySales = array();
// Iterate over the results and populate the array
foreach ($results as $result) {
$month = $result->month;
$totalSales = $result->total;
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => $totalSales
);
}
// Fill in missing months with zero sales
for ($month = 1; $month <= 12; $month++) {
if (!isset($monthlySales[$month])) {
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => 0
);
}
}
// Sort the array by month
ksort($monthlySales);
// Reindex the array
$monthlySales = array_values($monthlySales);
file_put_contents($cacheMSfile, json_encode($monthlySales));
}
// Assign the monthly sales data to Smarty
$ui->assign('monthlySales', $monthlySales);
$ui->assign('xfooter', '');
$ui->assign('monthlyRegistered', $monthlyRegistered);
$ui->assign('stocks', $stocks);
$ui->assign('plans', $plans);

View File

@ -23,7 +23,7 @@ switch ($action) {
$q = (_post('q') ? _post('q') : _get('q'));
$keep = _post('keep');
if (!empty($keep)) {
ORM::raw_execute("DELETE FROM tbl_logs WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))");
ORM::raw_execute("DELETE FROM tbl_logs WHERE UNIX_TIMESTAMP(date) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))");
r2(U . "logs/list/", 's', "Delete logs older than $keep days");
}
if ($q != '') {

View File

@ -5,7 +5,7 @@
**/
_admin();
$ui->assign('_title', $_L['Plugin Manager']);
$ui->assign('_title', 'Plugin Manager');
$ui->assign('_system_menu', 'settings');
$plugin_repository = 'https://hotspotbilling.github.io/Plugin-Repository/repository.json';

View File

@ -159,7 +159,7 @@ switch ($action) {
case 'print':
$id = _post('id');
$d = ORM::for_table('tbl_transactions')->where('id', $id)->find_one();
$ui->assign('d', $d);
$ui->assign('in', $d);
$ui->assign('date', Lang::dateAndTimeFormat($d['recharged_on'], $d['recharged_time']));
run_hook('print_invoice'); #HOOK
@ -174,6 +174,7 @@ switch ($action) {
$p = ORM::for_table('tbl_plans')->where('enabled', '1')->where_not_equal('type', 'Balance')->find_many();
$ui->assign('p', $p);
run_hook('view_edit_customer_plan'); #HOOK
$ui->assign('_title', 'Edit Plan');
$ui->display('prepaid-edit.tpl');
} else {
r2(U . 'services/list', 'e', $_L['Account_Not_Found']);
@ -228,17 +229,25 @@ switch ($action) {
run_hook('edit_customer_plan'); #HOOK
$d->username = $username;
$d->plan_id = $id_plan;
$d->namebp = $p['name_plan'];
//$d->recharged_on = $recharged_on;
$d->expiration = $expiration;
$d->time = $time;
if($d['status'] == 'off'){
if(strtotime($expiration.' '.$time) > time()){
$d->status = 'on';
}
}
if($p['is_radius']){
$d->routers = 'radius';
}else{
$d->routers = $p['routers'];
}
$d->save();
Package::changeTo($username, $id_plan, $id);
_log('[' . $admin['username'] . ']: ' . 'Edit Plan for Customer ' . $d['username'] . ' to [' . $d['plan_name'] . '][' . Lang::moneyFormat($d['price']) . ']', 'Admin', $admin['id']);
if($d['status'] == 'on'){
Package::changeTo($username, $id_plan, $id);
}
_log('[' . $admin['username'] . ']: ' . 'Edit Plan for Customer ' . $d['username'] . ' to [' . $d['namebp'] . '][' . Lang::moneyFormat($p['price']) . ']', 'Admin', $admin['id']);
r2(U . 'prepaid/list', 's', $_L['Updated_Successfully']);
} else {
r2(U . 'prepaid/edit/' . $id, 'e', $msg);
@ -443,6 +452,7 @@ switch ($action) {
$d->code = $prefix.$code;
$d->user = '0';
$d->status = '0';
$d->generated_by = $admin['id'];
$d->save();
}

View File

@ -17,6 +17,20 @@ switch ($action) {
if ($admin['user_type'] != 'Admin') {
r2(U . "dashboard", 'e', $_L['Do_Not_Access']);
}
if (!empty(_get('testWa'))) {
$result = Message::sendWhatsapp(_get('testWa'), 'PHPNuxBill Test Whatsapp');
r2(U . "settings/app", 's', 'Test Whatsapp has been send<br>Result: ' . $result);
}
if (!empty(_get('testSms'))) {
$result = Message::sendSMS(_get('testSms'), 'PHPNuxBill Test SMS');
r2(U . "settings/app", 's', 'Test SMS has been send<br>Result: ' . $result);
}
if (!empty(_get('testTg'))) {
$result = Message::sendTelegram('PHPNuxBill Test Telegram');
r2(U . "settings/app", 's', 'Test Telegram has been send<br>Result: ' . $result);
}
if (file_exists('system/uploads/logo.png')) {
$logo = 'system/uploads/logo.png?' . time();
} else {
@ -272,7 +286,7 @@ switch ($action) {
}
}
// save all settings
foreach($_POST as $key => $value) {
foreach ($_POST as $key => $value) {
$d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one();
if ($d) {
$d->value = $value;
@ -285,6 +299,23 @@ switch ($action) {
}
}
//checkbox
$checks = ['hide_mrc','hide_tms','hide_aui','hide_al','hide_uet','hide_vs','hide_pg'];
foreach ($checks as $check) {
if(!isset($_POST[$check])){
$d = ORM::for_table('tbl_appconfig')->where('setting', $check)->find_one();
if ($d) {
$d->value = 'no';
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = $check;
$d->value = 'no';
$d->save();
}
}
}
_log('[' . $admin['username'] . ']: ' . $_L['Settings_Saved_Successfully'], 'Admin', $admin['id']);
r2(U . 'settings/app', 's', $_L['Settings_Saved_Successfully']);

View File

@ -81,6 +81,7 @@ $result = ORM::for_table('tbl_appconfig')->find_many();
foreach ($result as $value) {
$config[$value['setting']] = $value['value'];
}
date_default_timezone_set($config['timezone']);
if (!empty($radius_user) && $config['radius_enable']) {
ORM::configure("mysql:host=$radius_host;dbname=$radius_name", null, 'radius');
@ -100,7 +101,6 @@ while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$_c = $config;
date_default_timezone_set($config['timezone']);
$textExpired = Lang::getNotifText('expired');
@ -119,7 +119,7 @@ foreach ($d as $ds) {
$c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one();
$m = Mikrotik::info($ds['routers']);
$p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one();
$price = Lang::moneyFormat($p['price']);
if ($p['is_radius']) {
if (empty($p['pool_expired'])) {
print_r(Radius::customerDeactivate($c['username']));
@ -136,7 +136,7 @@ foreach ($d as $ds) {
}
Mikrotik::removeHotspotActiveUser($client, $c['username']);
}
Message::sendPackageNotification($c['phonenumber'], $c['fullname'], $u['namebp'], $textExpired, $config['user_notification_expired']);
echo Message::sendPackageNotification($c['phonenumber'], $c['fullname'], $u['namebp'], $price, $textExpired, $config['user_notification_expired'])."\n";
//update database user dengan status off
$u->status = 'off';
$u->save();
@ -153,7 +153,7 @@ foreach ($d as $ds) {
echo "plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n";
echo "auto renewall Failed\n";
Message::sendTelegram("FAILED RENEWAL #cron\n\n#u$c[username] #buy #Hotspot \n" . $p['name_plan'] .
"\nRouter: " . $router_name .
"\nRouter: " . $p['routers'] .
"\nPrice: " . $p['price']);
}
} else {
@ -174,7 +174,7 @@ foreach ($d as $ds) {
$c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one();
$m = ORM::for_table('tbl_routers')->where('name', $ds['routers'])->find_one();
$p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one();
$price = Lang::moneyFormat($p['price']);
if ($p['is_radius']) {
if (empty($p['pool_expired'])) {
print_r(Radius::customerDeactivate($c['username']));
@ -191,7 +191,7 @@ foreach ($d as $ds) {
}
Mikrotik::removePpoeActive($client, $c['username']);
}
Message::sendPackageNotification($c['phonenumber'], $c['fullname'], $u['namebp'], $textExpired, $config['user_notification_expired']);
echo Message::sendPackageNotification($c['phonenumber'], $c['fullname'], $u['namebp'], $price, $textExpired, $config['user_notification_expired'])."\n";
$u->status = 'off';
$u->save();
@ -208,7 +208,7 @@ foreach ($d as $ds) {
echo "plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n";
echo "auto renewall Failed\n";
Message::sendTelegram("FAILED RENEWAL #cron\n\n#u$c[username] #buy #PPPOE \n" . $p['name_plan'] .
"\nRouter: " . $router_name .
"\nRouter: " . $p['routers'] .
"\nPrice: " . $p['price']);
}
}

View File

@ -418,3 +418,13 @@ $_L['Voucher_activation_success_now_you_can_login'] = 'Voucher activation succes
$_L['Client_Can_Purchase'] = 'Client Can Purchase';
$_L['Buy_this_your_active_package_will_be_overwritten'] = 'Buy this? your active package will be overwritten';
$_L['Pay_this_with_Balance_your_active_package_will_be_overwritten'] = 'Pay this with Balance? your active package will be overwritten';
$_L['Buy_this_your_active_package_will_be_overwrite'] = 'Buy this? your active package will be overwrite';
$_L['Buy_this_your_active_package_will_be_overwrite'] = 'Buy this? your active package will be overwrite';
$_L['Buy_this_your_active_package_will_be_overwrite'] = 'Buy this? your active package will be overwrite';
$_L['Buy_this_your_active_package_will_be_overwrite'] = 'Buy this? your active package will be overwrite';
$_L['Buy_this_your_active_package_will_be_overwrite'] = 'Buy this? your active package will be overwrite';
$_L['Buy_this_your_active_package_will_be_overwrite'] = 'Buy this? your active package will be overwrite';
$_L['Monthly_Registered_Customers'] = 'Monthly Registered Customers';
$_L['Total_Monthly_Sales'] = 'Total Monthly Sales';
$_L['Active_Users'] = 'Active Users';
$_L['All_Users_Insights'] = 'All Users Insights';

View File

@ -408,4 +408,7 @@ $_L['Resend_To_Customer'] = 'Kirim Ulang Ke Pelanggan';
$_L['Service_Type'] = 'Service Type';
$_L['Others'] = 'Lainnya';
$_L['PPPoE'] = 'PPPoE';
$_L['Hotspot'] = 'Hotspot';
$_L['Hotspot'] = 'Hotspot';
$_L['Monthly_Registered_Customers'] = 'Pendaftaran Pelanggan perbulan';
$_L['Total_Monthly_Sales'] = 'Total penjualan Perbulan';
$_L['Active_Users'] = 'Pelanggan Aktif';

View File

@ -405,4 +405,7 @@ $_L['Voucher_Format'] = 'Voucher Format';
$_L['Service_Type'] = 'Service Type';
$_L['Others'] = 'Others';
$_L['PPPoE'] = 'PPPoE';
$_L['Hotspot'] = 'Hotspot';
$_L['Hotspot'] = 'Hotspot';
$_L['Monthly_Registered_Customers'] = 'Monthly Registered Customers';
$_L['Total_Monthly_Sales'] = 'Total Monthly Sales';
$_L['Active_Users'] = 'Active Users';

View File

@ -382,4 +382,7 @@ $_L['Voucher_Format'] = 'Voucher Format';
$_L['Service_Type'] = 'Service Type';
$_L['Others'] = 'Others';
$_L['PPPoE'] = 'PPPoE';
$_L['Hotspot'] = 'Hotspot';
$_L['Hotspot'] = 'Hotspot';
$_L['Monthly_Registered_Customers'] = 'Monthly Registered Customers';
$_L['Total_Monthly_Sales'] = 'Total Monthly Sales';
$_L['Active_Users'] = 'Active Users';

View File

@ -41,5 +41,9 @@
],
"2024.1.11": [
"ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;"
],
"2024.2.7": [
"ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;",
"ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;"
]
}

View File

@ -54,10 +54,11 @@
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label"><i class="glyphicon glyphicon-print"></i> Print Max Char</label>
<label class="col-md-2 control-label"><i class="glyphicon glyphicon-print"></i> Print Max
Char</label>
<div class="col-md-6">
<input type="number" required class="form-control" id="printer_cols" placeholder="37" name="printer_cols"
value="{$_c['printer_cols']}">
<input type="number" required class="form-control" id="printer_cols" placeholder="37"
name="printer_cols" value="{$_c['printer_cols']}">
</div>
<span class="help-block col-md-4">For invoice print using Thermal Printer</span>
</div>
@ -85,6 +86,24 @@
<p class="help-block col-md-4">edit at config.php</p>
</div>
</div>
<div class="panel-heading">
<div class="btn-group pull-right">
<button class="btn btn-primary btn-xs" title="save" type="submit"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
</div>
Hide Dashboard Content
</div>
<div class="panel-body">
<div class="form-group">
<label class="col-md-3 control-label"><input type="checkbox" name="hide_mrc" value="yes" {if $_c['hide_mrc'] eq 'yes'}checked{/if}> {Lang::T('Monthly Registered Customers')}</label>
<label class="col-md-2 control-label"><input type="checkbox" name="hide_tms" value="yes" {if $_c['hide_tms'] eq 'yes'}checked{/if}> {Lang::T('Total Monthly Sales')}</label>
<label class="col-md-2 control-label"><input type="checkbox" name="hide_aui" value="yes" {if $_c['hide_aui'] eq 'yes'}checked{/if}> {Lang::T('All Users Insights')}</label>
<label class="col-md-2 control-label"><input type="checkbox" name="hide_al" value="yes" {if $_c['hide_al'] eq 'yes'}checked{/if}> {$_L['Activity_Log']}</label>
<label class="col-md-2 control-label"><input type="checkbox" name="hide_uet" value="yes" {if $_c['hide_uet'] eq 'yes'}checked{/if}> {$_L['User_Expired_Today']}</label>
<label class="col-md-2 control-label"><input type="checkbox" name="hide_vs" value="yes" {if $_c['hide_vs'] eq 'yes'}checked{/if}> Vouchers Stock</label>
<label class="col-md-2 control-label"><input type="checkbox" name="hide_pg" value="yes" {if $_c['hide_pg'] eq 'yes'}checked{/if}> Payment Gateway</label>
</div>
</div>
<div class="panel-heading">
<div class="btn-group pull-right">
<button class="btn btn-primary btn-xs" title="save" type="submit"><span
@ -218,6 +237,7 @@
</div>
<div class="panel-heading">
<div class="btn-group pull-right">
<a class="btn btn-success btn-xs" style="color: black;" href="javascript:testTg()">Test TG</a>
<button class="btn btn-primary btn-xs" title="save" type="submit"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
</div>
@ -244,6 +264,7 @@
</div>
<div class="panel-heading">
<div class="btn-group pull-right">
<a class="btn btn-success btn-xs" style="color: black;" href="javascript:testSms()">Test SMS</a>
<button class="btn btn-primary btn-xs" title="save" type="submit"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
</div>
@ -281,6 +302,7 @@
</div>
<div class="panel-heading">
<div class="btn-group pull-right">
<a class="btn btn-success btn-xs" style="color: black;" href="javascript:testWa()">Test WA</a>
<button class="btn btn-primary btn-xs" title="save" type="submit"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
</div>
@ -413,6 +435,34 @@
</div>
</div>
</div>
{* <div class="panel-heading" id="envato">
<div class="btn-group pull-right">
<button class="btn btn-primary btn-xs" title="save" type="submit"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
</div>
Envato / Codecanyon
</div>
<div class="panel-body">
<div class="form-group">
<label class="col-md-2 control-label">Envato Personal Token</label>
<div class="col-md-6">
<input type="password" class="form-control" id="envato_token" name="envato_token"
value="{$_c['envato_token']}" placeholder="BldWuBsxxxxxxxxxxxPDzPozHAPui">
</div>
<span class="help-block col-md-4"><a href="https://build.envato.com/create-token/"
target="_blank">Create Token</a></span>
</div>
<div class="form-group">
<label class="control-label col-md-offset-2 col-md-8" style="text-align: left;">Envato
Permission<br>
- View and search Envato sites<br>
- Download the user's purchased items<br>
- List purchases the user has made<br><br>
<a href="https://codecanyon.net/category/php-scripts?term=phpnuxbill" target="_blank"
class="btn btn-xs btn-primary">View MarketPlace</a>
</label>
</div>
</div> *}
</div>
<div class="panel-body">
@ -440,4 +490,23 @@ add dst-host=*.{$_domain}</pre>
</div>
</div>
</form>
<script>
function testWa() {
var target = prompt("Phone number\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testWa='+target;
}
}
function testSms() {
var target = prompt("Phone number\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testSms='+target;
}
}
function testTg() {
window.location.href = '{$_url}settings/app&testTg=test';
}
</script>
{include file="sections/footer.tpl"}

54
ui/ui/codecanyon.tpl Normal file
View File

@ -0,0 +1,54 @@
{include file="sections/header.tpl"}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-primary panel-hovered">
<div class="panel-heading">
<div class="btn-group pull-right">
<a class="btn btn-danger btn-xs" href="https://codecanyon.net/category/php-scripts?term=phpnuxbill"
target="_blank">Buy Plugin</a>
</div>
Plugin Purcashed
</div>
<div class="panel-body row">
{if Lang::arrayCount($plugins) > 0}
{foreach $plugins as $plugin}
<div class="col-md-4">
<div class="box box-hovered mb20 box-primary">
<div class="box-header">
<h3 class="box-title text1line">{$plugin['item']['name']}</h3>
</div>
<div class="box-body"><small><i>@{$plugin['item']['author_username']} &bull; Last update:
{Lang::dateFormat($plugin['item']['updated_at'])}</i></small></div>
<div class="box-footer ">
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="{$plugin['item']['author_url']}" target="_blank" class="btn btn-primary"><i
class="ion ion-chatboxes"></i> Author</a>
<a href="{$plugin['item']['url']}" target="_blank" class="btn btn-success"><i
class="ion ion-chatboxes"></i> Product</a>
<a {if $zipExt } href="{$_url}codecanyon/install/{$plugin['item']['id']}"
onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else} href="#" onclick="alert('PHP ZIP extension is not installed')"
{/if}
class="btn btn-danger"><i class="ion ion-chatboxes"></i> Install</a>
</div>
</div>
</div>
</div>
{/foreach}
{else}
<div class="col-md-12">
<div class="alert alert-info">
<i class="fa fa-info-circle"></i> No plugins purcashed yet.
</div>
</div>
{/if}
</div>
<div class="panel-footer">
{if $chached_until}Cached Until {$chached_until} <a href="{$_url}codecanyon/reload">Force reload</a>
&bull; {/if}<a
href="https://github.com/hotspotbilling/phpnuxbill/wiki/Selling-Paid-Plugin-or-Payment-Gateway"
target="_blank"> Sell your own plugin/paymentgateway/theme?</a>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

View File

@ -104,7 +104,12 @@
</table>
</div>
<div class="box-footer">
<a href="https://paypal.me/ibnux" target="_blank" class="btn btn-primary btn-sm btn-block">Paypal</a>
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="https://paypal.me/ibnux" target="_blank"
class="btn btn-primary btn-sm btn-block">Paypal</a>
<a href="https://wise.com/pay/me/ibnum37" target="_blank"
class="btn btn-primary btn-sm btn-block">Wise</a>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,6 @@
{include file="sections/header.tpl"}
<div class="row">
<div class="col-lg-3 col-xs-6">
<div class="small-box bg-aqua">
@ -59,92 +60,314 @@
</div>
</div>
</div>
<!-- solid sales graph -->
{if $_c['hide_mrc'] != 'yes'}
<div class="box box-solid ">
<div class="box-header">
<i class="fa fa-th"></i>
<h3 class="box-title">{Lang::T('Monthly Registered Customers')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<button type="button" class="btn bg-teal btn-sm" data-widget="remove"><i class="fa fa-times"></i>
</button>
</div>
</div>
<div class="box-body border-radius-none">
<canvas class="chart" id="chart" style="height: 250px;"></canvas>
</div>
</div>
{/if}
<!-- solid sales graph -->
{if $_c['hide_tms'] != 'yes'}
<div class="box box-solid ">
<div class="box-header">
<i class="fa fa-inbox"></i>
<h3 class="box-title">{Lang::T('Total Monthly Sales')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<button type="button" class="btn bg-teal btn-sm" data-widget="remove"><i class="fa fa-times"></i>
</button>
</div>
</div>
<div class="box-body border-radius-none">
<canvas class="chart" id="salesChart" style="height: 250px;"></canvas>
</div>
</div>
{/if}
<div class="row">
<div class="col-md-7">
{if $_c['disable_voucher'] != 'yes' && $stocks['unused']>0 || $stocks['used']>0}
<div class="panel panel-primary mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">Vouchers Stock</div>
{if $_c['hide_vs'] != 'yes'}
<div class="panel panel-primary mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">Vouchers Stock</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{$_L['Plan_Name']}</th>
<th>unused</th>
<th>used</th>
</tr>
</thead>
<tbody>
{foreach $plans as $stok}
<tr>
<td>{$stok['name_plan']}</td>
<td>{$stok['unused']}</td>
<td>{$stok['used']}</td>
</tr>
</tbody>
{/foreach}
<tr>
<td>Total</td>
<td>{$stocks['unused']}</td>
<td>{$stocks['used']}</td>
</tr>
</table>
</div>
</div>
{/if}
{/if}
{if $_c['hide_uet'] != 'yes'}
<div class="panel panel-warning mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">{$_L['User_Expired_Today']}</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{$_L['Plan_Name']}</th>
<th>unused</th>
<th>used</th>
<th>Id</th>
<th>{$_L['Username']}</th>
<th>{$_L['Created_On']}</th>
<th>{$_L['Expires_On']}</th>
</tr>
</thead>
<tbody>
{foreach $plans as $stok}
{$no = 1}
{foreach $expire as $expired}
<tr>
<td>{$stok['name_plan']}</td>
<td>{$stok['unused']}</td>
<td>{$stok['used']}</td>
<td>{$no++}</td>
<td><a href="{$_url}customers/viewu/{$expired['username']}">{$expired['username']}</a></td>
<td>{Lang::dateAndTimeFormat($expired['recharged_on'],$expired['recharged_time'])}
</td>
<td>{Lang::dateAndTimeFormat($expired['expiration'],$expired['time'])}
</td>
</tr>
</tbody>
{/foreach}
<tr>
<td>Total</td>
<td>{$stocks['unused']}</td>
<td>{$stocks['used']}</td>
</tr>
</table>
</div>
&nbsp; {$paginator['contents']}
</div>
{/if}
</div>
<div class="col-md-5">
{if $_c['hide_pg'] != 'yes'}
<div class="panel panel-success panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('Payment Gateway')}: {$_c['payment_gateway']}</div>
</div>
{/if}
{if $_c['hide_aui'] != 'yes'}
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('All Users Insights')}</div>
<div class="panel-body">
<canvas id="userRechargesChart"></canvas>
</div>
</div>
{/if}
<div class="panel panel-warning mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">{$_L['User_Expired_Today']}</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>{$_L['Username']}</th>
<th>{$_L['Created_On']}</th>
<th>{$_L['Expires_On']}</th>
</tr>
</thead>
<tbody>
{$no = 1}
{foreach $expire as $expired}
<tr>
<td>{$no++}</td>
<td><a href="{$_url}customers/viewu/{$expired['username']}">{$expired['username']}</a></td>
<td>{Lang::dateAndTimeFormat($expired['recharged_on'],$expired['recharged_time'])}
</td>
<td>{Lang::dateAndTimeFormat($expired['expiration'],$expired['time'])}
</td>
</tr>
</tbody>
{/foreach}
</table>
{if $_c['hide_al'] != 'yes'}
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading"><a href="{$_url}logs">{$_L['Activity_Log']}</a></div>
<div class="panel-body">
<ul class="list-unstyled">
{foreach $dlog as $dlogs}
<li class="primary">
<span class="point"></span>
<span class="time small text-muted">{time_elapsed_string($dlogs['date'],true)}</span>
<p>{$dlogs['description']}</p>
</li>
{/foreach}
</ul>
</div>
</div>
&nbsp; {$paginator['contents']}
</div>
{/if}
</div>
<div class="col-md-5">
<div class="panel panel-success panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('Payment Gateway')}: {$_c['payment_gateway']}</div>
</div>
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading"><a href="{$_url}logs">{$_L['Activity_Log']}</a></div>
<div class="panel-body">
<ul class="list-unstyled">
{foreach $dlog as $dlogs}
<li class="primary">
<span class="point"></span>
<span class="time small text-muted">{time_elapsed_string($dlogs['date'],true)}</span>
<p>{$dlogs['description']}</p>
</li>
{/foreach}
</ul>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
<script type="text/javascript">
{if $_c['hide_mrc'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
var counts = JSON.parse('{/literal}{$monthlyRegistered|json_encode}{literal}');
var monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var labels = [];
var data = [];
for (var i = 1; i <= 12; i++) {
var month = counts.find(count => count.date === i);
labels.push(month ? monthNames[i - 1] : monthNames[i - 1].substring(0, 3));
data.push(month ? month.count : 0);
}
var ctx = document.getElementById('chart').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Registered Members',
data: data,
backgroundColor: 'rgba(0, 0, 255, 0.5)',
borderColor: 'rgba(0, 0, 255, 0.7)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
});
{/literal}
{/if}
{if $_c['hide_tmc'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
var monthlySales = JSON.parse('{/literal}{$monthlySales|json_encode}{literal}');
var monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var labels = [];
var data = [];
for (var i = 1; i <= 12; i++) {
var month = findMonthData(monthlySales, i);
labels.push(month ? monthNames[i - 1] : monthNames[i - 1].substring(0, 3));
data.push(month ? month.totalSales : 0);
}
var ctx = document.getElementById('salesChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Monthly Sales',
data: data,
backgroundColor: 'rgba(2, 10, 242)', // Customize the background color
borderColor: 'rgba(255, 99, 132, 1)', // Customize the border color
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
});
function findMonthData(monthlySales, month) {
for (var i = 0; i < monthlySales.length; i++) {
if (monthlySales[i].month === month) {
return monthlySales[i];
}
}
return null;
}
{/literal}
{/if}
{if $_c['hide_aui'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
// Get the data from PHP and assign it to JavaScript variables
var u_act = '{/literal}{$u_act}{literal}';
var c_all = '{/literal}{$c_all}{literal}';
var u_all = '{/literal}{$u_all}{literal}';
//lets calculate the inactive users as reported
var expired = u_all - u_act;
var inactive = c_all - u_all;
// Create the chart data
var data = {
labels: ['Active Users', 'Expired Users', 'Inactive Users'],
datasets: [{
label: 'User Recharges',
data: [parseInt(u_act), parseInt(expired), parseInt(inactive)],
backgroundColor: ['rgba(4, 191, 13)', 'rgba(191, 35, 4)', 'rgba(0, 0, 255, 0.5'],
borderColor: ['rgba(0, 255, 0, 1)', 'rgba(255, 99, 132, 1)', 'rgba(0, 0, 255, 0.7'],
borderWidth: 1
}]
};
// Create chart options
var options = {
responsive: true,
aspectRatio: 1,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 15
}
}
}
};
// Get the canvas element and create the chart
var ctx = document.getElementById('userRechargesChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'pie',
data: data,
options: options
});
});
{/literal}
{/if}
</script>
<script>
window.addEventListener('DOMContentLoaded', function() {
$.getJSON("./version.json?" + Math.random(), function(data) {

View File

@ -8,19 +8,21 @@
<div class="col-md-4">
<div class="box box-hovered mb20 box-primary">
<div class="box-header">
<h3 class="box-title">{$plugin['name']}</h3>
<h3 class="box-title text1line">{$plugin['name']}</h3>
</div>
<div class="box-body" style="overflow-y: scroll;">
<div style="max-height: 50px; min-height: 50px;">{$plugin['description']}</div>
</div>
<div class="box-body">{$plugin['description']}<br><small><i>@{$plugin['author']} Last update: {$plugin['last_update']}</i></small></div>
<div class="box-footer ">
<center><small><i>@{$plugin['author']} Last update: {$plugin['last_update']}</i></small></center>
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="{$plugin['url']}" target="_blank"
class="btn btn-primary"><i class="ion ion-chatboxes"></i> Website</a>
<a href="{$plugin['github']}" target="_blank"
class="btn btn-success"><i class="ion ion-chatboxes"></i> Github</a>
<a {if $zipExt }
href="{$_url}pluginmanager/install/plugin/{$plugin['id']}" onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else}
href="#" onclick="alert('PHP ZIP extension is not available')"
<a href="{$plugin['url']}" target="_blank" class="btn btn-primary"><i
class="ion ion-chatboxes"></i> Website</a>
<a href="{$plugin['github']}" target="_blank" class="btn btn-success"><i
class="ion ion-chatboxes"></i> Github</a>
<a {if $zipExt } href="{$_url}pluginmanager/install/plugin/{$plugin['id']}"
onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else} href="#" onclick="alert('PHP ZIP extension is not installed')"
{/if}
class="btn btn-warning"><i class="ion ion-chatboxes"></i> Install</a>
</div>
@ -37,19 +39,21 @@
<div class="col-md-4">
<div class="box box-hovered mb20 box-primary">
<div class="box-header">
<h3 class="box-title">{$pg['name']}</h3>
<h3 class="box-title text1line">{$pg['name']}</h3>
</div>
<div class="box-body" style="overflow-y: scroll;">
<div style="max-height: 50px; min-height: 50px;">{$pg['description']}</div>
</div>
<div class="box-body">{$pg['description']}<br><small><i>@{$pg['author']} Last update: {$pg['last_update']}</i></small></div>
<div class="box-footer ">
<center><small><i>@{$pg['author']} Last update: {$pg['last_update']}</i></small></center>
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="{$pg['url']}" target="_blank"
class="btn btn-primary"><i class="ion ion-chatboxes"></i> Website</a>
<a href="{$pg['github']}" target="_blank"
class="btn btn-success"><i class="ion ion-chatboxes"></i> Github</a>
<a {if $zipExt }
href="{$_url}pluginmanager/install/payment/{$pg['id']}" onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else}
href="#" onclick="alert('PHP ZIP extension is not available')"
<a href="{$pg['url']}" target="_blank" class="btn btn-primary"><i
class="ion ion-chatboxes"></i> Website</a>
<a href="{$pg['github']}" target="_blank" class="btn btn-success"><i
class="ion ion-chatboxes"></i> Github</a>
<a {if $zipExt } href="{$_url}pluginmanager/install/payment/{$pg['id']}"
onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else} href="#" onclick="alert('PHP ZIP extension is not available')"
{/if}
class="btn btn-warning"><i class="ion ion-chatboxes"></i> Install</a>
</div>

View File

@ -4,7 +4,7 @@
<div class="col-sm-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{$_L['Recharge_Account']}</h3>
<h3 class="panel-title">Edit Plan</h3>
</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}prepaid/edit-post">

View File

@ -14,7 +14,7 @@
<link rel="stylesheet" href="ui/ui/fonts/MaterialDesign/css/materialdesignicons.min.css">
<link rel="stylesheet" href="ui/ui/styles/modern-AdminLTE.min.css">
<style>
::-moz-selection {
/* Code for Firefox */
@ -57,9 +57,16 @@
<div class="box-footer">
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="./update.php?step=4" class="btn btn-info btn-sm btn-block">Update Database</a>
<a href="{$_url}community#update" class="btn btn-primary btn-sm btn-block">Update
<a href="{$_url}community#update" class="btn btn-success btn-sm btn-block">Update
PHPNuxBill</a>
</div>
<br>
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="https://github.com/hotspotbilling/phpnuxbill/discussions" target="_blank"
class="btn btn-success btn-sm btn-block">Ask Github Community</a>
<a href="https://t.me/phpnuxbill" target="_blank"
class="btn btn-primary btn-sm btn-block">Ask Telegram Community</a>
</div>
<br><br>
<a href="javascript::history.back()" onclick="history.back()"
class="btn btn-warning btn-block">back</a>

View File

@ -30,6 +30,23 @@
.select2-container .select2-selection--single .select2-selection__rendered {
margin-top: 0px !important;
}
@media (min-width: 768px) {
.outer {
height: 200px
/* Or whatever */
}
}
.text1line {
display: block;
/* or inline-block */
text-overflow: ellipsis;
word-wrap: break-word;
overflow: hidden;
max-height: 1em;
line-height: 1em;
}
</style>
{if isset($xheader)}
@ -182,7 +199,7 @@
<li {if $_routes[1] eq 'by-period'}class="active" {/if}><a
href="{$_url}reports/by-period">{$_L['Period_Reports']}</a></li>
<li {if $_routes[1] eq 'activation'}class="active" {/if}><a
href="{$_url}reports/activation">{Lang::T('Activation History')}</a></li>
href="{$_url}reports/activation">{Lang::T('Activation History')}</a></li>
{$_MENU_REPORTS}
</ul>
</li>
@ -252,15 +269,20 @@
href="{$_url}settings/users">{$_L['Administrator_Users']}</a></li>
<li {if $_routes[1] eq 'dbstatus'}class="active" {/if}><a
href="{$_url}settings/dbstatus">{$_L['Backup_Restore']}</a></li>
<li {if $_routes[0] eq 'pluginmanager'}class="active" {/if}>
<a href="{$_url}pluginmanager">{Lang::T('Plugin Manager')}</a>
</li>
<li {if $_system_menu eq 'paymentgateway'}class="active" {/if}>
<a href="{$_url}paymentgateway">
<span class="text">{Lang::T('Payment Gateway')}</span>
</a>
</li>
{$_MENU_SETTINGS}
<li {if $_routes[0] eq 'pluginmanager'}class="active" {/if}>
<a href="{$_url}pluginmanager"><i class="glyphicon glyphicon-tasks"></i>
{Lang::T('Plugin Manager')} <small class="label pull-right">Free</small></a>
</li>
{* <li {if $_routes[0] eq 'codecanyon'}class="active" {/if}>
<a href="{$_url}codecanyon"><i class="glyphicon glyphicon-shopping-cart"></i>
Codecanyon.net <small class="label pull-right">Paid</small></a>
</li> *}
</ul>
</li>
{$_MENU_AFTER_SETTINGS}
@ -274,10 +296,10 @@
<ul class="treeview-menu">
<li {if $_routes[1] eq 'list'}class="active" {/if}><a
href="{$_url}logs/phpnuxbill">PhpNuxBill</a></li>
{if $_c['radius_enable']}
{if $_c['radius_enable']}
<li {if $_routes[1] eq 'radius'}class="active" {/if}><a href="{$_url}logs/radius">Radius</a>
</li>
{/if}
{/if}
</ul>
{$_MENU_LOGS}
</li>
@ -301,11 +323,11 @@
</section>
<section class="content">
{if isset($notify)}
<div class="alert alert-{if $notify_t == 's'}success{else}danger{/if}">
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">×</span>
</button>
<div>{$notify}</div>
</div>
{if isset($notify)}
<div class="alert alert-{if $notify_t == 's'}success{else}danger{/if}">
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">×</span>
</button>
<div>{$notify}</div>
</div>
{/if}

View File

@ -1,3 +1,3 @@
{
"version": "2024.1.16"
"version": "2024.2.7"
}