Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
8f099bdefb | |||
9fae0f84fd | |||
d50a66845f | |||
915af6fc03 | |||
|
0a29ec9a86 | ||
|
27fd677a0a | ||
|
e2f24c0cc6 | ||
|
cf60c470b1 | ||
|
1cc7057dca | ||
|
1740c568f9 | ||
|
3347b39f3b | ||
|
f0b9b56bb0 | ||
|
24b713804a | ||
|
182add517c | ||
|
45cc2afab5 | ||
|
ba19b1c569 | ||
|
5caa9f905b | ||
|
28541f366c | ||
|
ad7998ebbf | ||
|
4c64cfabd2 | ||
|
9bae41dbe7 | ||
|
bad0545be5 | ||
|
43a92c5d3b | ||
|
11e5ebe103 | ||
|
c573c49fb9 | ||
|
7bfbdb1efb | ||
|
dc28298d53 | ||
|
655e0494d3 | ||
|
4a441c5763 | ||
|
5072ff8ba2 | ||
|
d506dd66ff | ||
|
e9b0cfd8f0 | ||
|
aa4dbc0cea | ||
|
4ef054466d | ||
|
41a3cbe700 | ||
|
4938840c5d | ||
|
127d43e45d | ||
|
cdfbab7119 | ||
|
1a2b85ae4f | ||
|
2e2d967a5b | ||
|
c45e19189a | ||
|
d372bf4711 | ||
|
8b8a0357f0 | ||
|
1cb0e30e6b | ||
|
20916b44f0 | ||
|
c63545d33a | ||
|
84500cdfc9 | ||
|
5b21ffcde5 | ||
|
009040cd3c | ||
|
781481e118 | ||
|
66d67cb61d |
5
.gitignore
vendored
5
.gitignore
vendored
@ -56,4 +56,7 @@ docs/**
|
||||
.htaccess
|
||||
.idea
|
||||
!docs/insomnia.rest.json
|
||||
!system/uploads/paid.png
|
||||
!system/uploads/paid.png
|
||||
system/uploads/invoices/**
|
||||
!system/uploads/invoices/
|
||||
!system/uploads/invoices/index.html
|
26
README.md
26
README.md
@ -83,28 +83,4 @@ Contact me at [Telegram](https://t.me/ibnux)
|
||||
|
||||
GNU General Public License version 2 or later
|
||||
|
||||
see [LICENSE](LICENSE) file
|
||||
|
||||
|
||||
## Donate to ibnux
|
||||
|
||||
[](https://paypal.me/ibnux)
|
||||
|
||||
BCA: 5410454825
|
||||
|
||||
Mandiri: 163-000-1855-793
|
||||
|
||||
a.n Ibnu Maksum
|
||||
|
||||
## SPONSORS
|
||||
|
||||
- [mixradius.com](https://mixradius.com/) Paid Services Billing Radius
|
||||
- [mlink.id](https://mlink.id)
|
||||
- [https://github.com/sonyinside](https://github.com/sonyinside)
|
||||
|
||||
## Thanks
|
||||
We appreciate all people who are participating in this project.
|
||||
|
||||
<a href="https://github.com/hotspotbilling/phpnuxbill/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=hotspotbilling/phpnuxbill" />
|
||||
</a>
|
||||
see [LICENSE](LICENSE) file
|
@ -326,7 +326,7 @@ Error message and password prompt
|
||||
{"created":"20240712071211426","creator":"ibnux","title":"$:/state/tabs/controlpanel/toolbars-1345989671","text":"$:/core/ui/ControlPanel/Toolbars/ViewToolbar","modified":"20250211062334510","modifier":"ibnux"},
|
||||
{"title":"$:/status/RequireReloadDueToPluginChange","text":"no"},
|
||||
{"created":"20240712071004482","creator":"i","title":"$:/status/UserName","text":"ibnux","modified":"20240712071005201","modifier":"ibnu"},
|
||||
{"title":"$:/StoryList","created":"20250311074232407","creator":"ibnux","text":"","list":"GettingStarted [[API Rest Documentation]]","modified":"20250311074232407","modifier":"ibnux"},
|
||||
{"title":"$:/StoryList","created":"20250317074031413","creator":"ibnux","text":"","list":"GettingStarted [[Reminder with payment link]]","modified":"20250317074031413","modifier":"ibnux"},
|
||||
{"created":"20240712071159691","creator":"ibnux","title":"$:/theme","text":"$:/themes/tiddlywiki/snowwhite","modified":"20250218074405827","modifier":"ibnux"},
|
||||
{"title":"$:/themes/tiddlywiki/readonly","name":"ReadOnly","author":"JeremyRuston","core-version":">=5.0.0","plugin-type":"theme","description":"Hides the ability to edit tiddlers","dependents":"$:/themes/tiddlywiki/snowwhite","version":"5.3.5","type":"application/json","text":"{\"tiddlers\":{\"$:/themes/tiddlywiki/readonly/styles.tid\":{\"title\":\"$:/themes/tiddlywiki/readonly/styles.tid\",\"tags\":\"[[$:/tags/Stylesheet]]\",\"text\":\"\\\\define button-selector(title)\\nbutton.$title$, .tc-drop-down button.$title$, div.$title$\\n\\\\end\\n\\n\\\\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline\\n\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fclone>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fdelete>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fedit>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fnew-here>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fnew-journal-here>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fimport>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fmanager>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fnew-image>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fnew-journal>>,\\n\u003C\u003Cbutton-selector tc-btn-\\\\%24\\\\%3A\\\\%2Fcore\\\\%2Fui\\\\%2FButtons\\\\%2Fnew-tiddler>> {\\n\\tdisplay: none;\\n}\"}}}"},
|
||||
{"title":"$:/themes/tiddlywiki/snowwhite","name":"Snow White","author":"JeremyRuston","core-version":">=5.0.0","plugin-type":"theme","description":"Emphasises individual tiddlers","dependents":"$:/themes/tiddlywiki/vanilla","plugin-priority":"0","version":"5.3.5","type":"application/json","text":"{\"tiddlers\":{\"$:/themes/tiddlywiki/snowwhite/base\":{\"title\":\"$:/themes/tiddlywiki/snowwhite/base\",\"tags\":\"[[$:/tags/Stylesheet]]\",\"text\":\"\\\\define sidebarbreakpoint-minus-one()\\n\u003C$text text={{{ [{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}removesuffix[px]subtract[1]addsuffix[px]] ~[{$:/themes/tiddlywiki/vanilla/metrics/sidebarbreakpoint}] }}}/>\\n\\\\end\\n\\n\\\\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline\\n\\n.tc-sidebar-header {\\n\\ttext-shadow: 0 1px 0 \u003C\u003Ccolour sidebar-foreground-shadow>>;\\n}\\n\\n.tc-tiddler-info {\\n\\t\u003C\u003Cbox-shadow \\\"inset 1px 2px 3px rgba(0,0,0,0.1)\\\">>\\n}\\n\\n@media screen {\\n\\t.tc-tiddler-frame {\\n\\t\\t\u003C\u003Cbox-shadow \\\"1px 1px 5px rgba(0, 0, 0, 0.3)\\\">>\\n\\t}\\n}\\n\\n@media (max-width: \u003C\u003Csidebarbreakpoint-minus-one>>) {\\n\\t.tc-tiddler-frame {\\n\\t\\t\u003C\u003Cbox-shadow none>>\\n\\t}\\n}\\n\\n.tc-page-controls button svg, .tc-tiddler-controls button svg, .tc-topbar button svg {\\n\\t\u003C\u003Ctransition \\\"fill 150ms ease-in-out\\\">>\\n}\\n\\n.tc-tiddler-controls button.tc-selected,\\n.tc-page-controls button.tc-selected {\\n\\t\u003C\u003Cfilter \\\"drop-shadow(0px -1px 2px rgba(0,0,0,0.25))\\\">>\\n}\\n\\n.tc-tiddler-frame input.tc-edit-texteditor,\\n.tc-tiddler-frame select.tc-edit-texteditor {\\n\\t\u003C\u003Cbox-shadow \\\"inset 0 1px 8px rgba(0, 0, 0, 0.15)\\\">>\\n}\\n\\n.tc-edit-tags {\\n\\t\u003C\u003Cbox-shadow \\\"inset 0 1px 8px rgba(0, 0, 0, 0.15)\\\">>\\n}\\n\\n.tc-tiddler-frame .tc-edit-tags input.tc-edit-texteditor {\\n\\t\u003C\u003Cbox-shadow \\\"none\\\">>\\n\\tborder: none;\\n\\toutline: none;\\n}\\n\\ntextarea.tc-edit-texteditor {\\n\\tfont-family: {{$:/themes/tiddlywiki/vanilla/settings/editorfontfamily}};\\n}\\n\\ncanvas.tc-edit-bitmapeditor {\\n\\t\u003C\u003Cbox-shadow \\\"2px 2px 5px rgba(0, 0, 0, 0.5)\\\">>\\n}\\n\\n.tc-drop-down {\\n\\tborder-radius: 4px;\\n\\t\u003C\u003Cbox-shadow \\\"2px 2px 10px rgba(0, 0, 0, 0.5)\\\">>\\n}\\n\\n.tc-block-dropdown {\\n\\tborder-radius: 4px;\\n\\t\u003C\u003Cbox-shadow \\\"2px 2px 10px rgba(0, 0, 0, 0.5)\\\">>\\n}\\n\\n.tc-modal {\\n\\tborder-radius: 6px;\\n\\t\u003C\u003Cbox-shadow \\\"0 3px 7px rgba(0,0,0,0.3)\\\">>\\n}\\n\\n.tc-modal-footer {\\n\\tborder-radius: 0 0 6px 6px;\\n\\t\u003C\u003Cbox-shadow \\\"inset 0 1px 0 #fff\\\">>;\\n}\\n\\n\\n.tc-alert {\\n\\tborder-radius: 6px;\\n\\t\u003C\u003Cbox-shadow \\\"0 3px 7px rgba(0,0,0,0.6)\\\">>\\n}\\n\\n.tc-notification {\\n\\tborder-radius: 6px;\\n\\t\u003C\u003Cbox-shadow \\\"0 3px 7px rgba(0,0,0,0.3)\\\">>\\n\\ttext-shadow: 0 1px 0 rgba(255,255,255, 0.8);\\n}\\n\\n.tc-sidebar-lists .tc-tab-set .tc-tab-divider {\\n\\tborder-top: none;\\n\\theight: 1px;\\n\\t\u003C\u003Cbackground-linear-gradient \\\"left, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.0) 100%\\\">>\\n}\\n\\n.tc-more-sidebar > .tc-tab-set > .tc-tab-buttons > button {\\n\\t\u003C\u003Cbackground-linear-gradient \\\"left, rgba(0,0,0,0.01) 0%, rgba(0,0,0,0.1) 100%\\\">>\\n}\\n\\n.tc-more-sidebar > .tc-tab-set > .tc-tab-buttons > button.tc-tab-selected {\\n\\t\u003C\u003Cbackground-linear-gradient \\\"left, rgba(0,0,0,0.05) 0%, rgba(255,255,255,0.05) 100%\\\">>\\n}\\n\\n.tc-message-box img {\\n\\t\u003C\u003Cbox-shadow \\\"1px 1px 3px rgba(0,0,0,0.5)\\\">>\\n}\\n\\n.tc-plugin-info {\\n\\t\u003C\u003Cbox-shadow \\\"1px 1px 3px rgba(0,0,0,0.5)\\\">>\\n}\\n\"}}}"},
|
||||
@ -350,7 +350,7 @@ Error message and password prompt
|
||||
{"created":"20240713062428411","creator":"ibnux","text":"Customer just self services, Customer register to get an account, Order the plan and automatically activate for the account.\n\nThen customer just login to Internet with account they register it.\n\nThis need Payment Gateway, PHPNuxBill can support multiple Payment Gateway.\n\nGo to [ext[Plugin Manager|../?_route=pluginmanager]], and install Payment Gateway you need\n\nThen go to [ext[Payment Gateway Settings|../?_route=paymentgateway]], You can activate Multiple Payment Gateway, configure it based the payment Gateway settings, and add it to walled Garden.\n\nDone, you now can selling Internet services.\n\nbut maybe you need to configure [[Mikrotik Login Template]], ","title":"Payment Gateway","modified":"20240714053605590","modifier":"ibnux"},
|
||||
{"created":"20240713062019848","creator":"ibnux","text":"!Step to create PPPOE Plan\n\nPPPOE System based on the Mikrotik Feature inside `PPP`\n\n\n!! For Radius System with Mysql\n \n# [[Add NAS]]\n# [ext[Create Pool|../?_route=pool/list]]\n# [[Create Bandwidth Plan]]\n# [[Create PPPOE Plan]]\n\n!! Integrated Radius System\n\n# [ext[Create Pool|../?_route=pool/list]]\n# [[Create Bandwidth Plan]]\n# [[Create PPPOE Plan]]\n\n!! Without Radius\n\n# [[Add Mikrotik]]\n# [ext[Create Pool|../?_route=pool/list]]\n# [[Create Bandwidth Plan]]\n# [[Create PPPOE Plan]]\n\nAfter That, how do you want to sell Internet Plan?\n\n* Customer self services buy internet using Payment Gateway\n* Customer Buy Voucher and activate it at PHPNuxBill\n\nWhich one do you want?\n\n\u003Cbutton>[[Payment Gateway]]\u003C/button> \u003Cbutton>[[Voucher]]\u003C/button>\n","title":"PPPOE System","modified":"20240715043559596","modifier":"ibnux"},
|
||||
{"created":"20240712074107108","creator":"ibnux","text":"Radius System is complicated, PHPNuxBill will connect to Radius Database, and Freeradius will check from that database\n\n\n```\n ┌──────────────┐\n │ PHPNUXBILL │\n └──────────────┘\n ▲ \n │ \n ▼ \n ┌──────────────┐\n │ Database │\n │ Mysql/MariaDB│\n └──────────────┘\n ▲ \n │ \n ▼ \n ┌──────────────┐\n │ Freeradius │\n └──────────────┘\n ▲ \n │ \n ▼ \n ┌──────────┐ \n │ Mikrotik │ \n └──────────┘ \n```\n\nDid you check Radius System when install PHPNuxBill?\n\nIf no, then you must import Radius Database, download [[SQL File|https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/install/radius.sql]], import it by command line or using PHPMyAdmin.\n\nIf yes, or done import the SQL, Go to [ext[Settings|../index.php?_route=settings/app]] and Enable Radius.\n\nHave you install Freeradius? if no, then follow [[this Guide|https://github.com/hotspotbilling/phpnuxbill/wiki/FreeRadius]] to install Freeradius with Mysql, The Tutorial is not good enough, you need to fix it yourself.\n\nYou want to sell Hotspot or PPPOE?\n\n\u003Cbutton>[[Hotspot System]]\u003C/button>\n\u003Cbutton>[[PPPOE System]]\u003C/button>\n","title":"Radius System with Mysql","modified":"20240713053714715","modifier":"ibnux","tags":"radius"},
|
||||
{"created":"20241105015644306","creator":"ibnux","text":"When sending Reminder or Expired notification, you can add payment link so customer can directly click link to pay\n\n\nAdd `[[payment_link]]` and use your domain url before that\n\nEXAMPLE\n\n```\nhttps://nuxbill.ibnux.com/[[payment_link]]\n```\n\n```\nhttps://nuxbill.ibnux.com/folder/[[payment_link]]\n```\n```\nhttps://192.168.88.2/[[payment_link]]\n```\n```\nhttps://192.168.88.2/folder/[[payment_link]]\n```\n\nWhen customer get notification, can click the link to pay directly, link will be expired after 24 hours after the link created. and customer need to login again.","title":"Reminder with payment link","modified":"20241105021934130","modifier":"ibnux"},
|
||||
{"created":"20241105015644306","creator":"ibnux","text":"When sending Reminder or Expired notification, you can add payment link so customer can directly click link to pay\n\n\nAdd `[[payment_link]]` and use your domain url before that\n\nEXAMPLE\n\n```\nhttps://nuxbill.ibnux.com/[[payment_link]]\n```\n\n```\nhttps://nuxbill.ibnux.com/folder/[[payment_link]]\n```\n```\nhttps://192.168.88.2/[[payment_link]]\n```\n```\nhttps://192.168.88.2/folder/[[payment_link]]\n```\n\nWhen customer get notification, can click the link to pay directly, link will be expired after 24 hours after the link created. and customer need to login again.\n\n!! INVOICE LINK\n\nAdd `[[invoice_link]]` and use your domain url before that\n\nEXAMPLE\n\n```\nhttps://nuxbill.ibnux.com/[[invoice_link]]\n```\n\n```\nhttps://nuxbill.ibnux.com/folder/[[invoice_link]]\n```\n```\nhttps://192.168.88.2/[[invoice_link]]\n```\n```\nhttps://192.168.88.2/folder/[[invoice_link]]\n```\n\n\n\n\u003C\u003C\u003C\nMostly script run from command line, script cannot detect domain used from billing\n\u003C\u003C\u003C\n","title":"Reminder with payment link","modified":"20250317074300132","modifier":"ibnux"},
|
||||
{"created":"20250311045849165","creator":"ibnux","text":"\n\u003C\u003C\u003C\nsettings >> User Notification \n\u003C\u003C\u003C\n\nFor Reminder and Expired Message, you can send different message for PPPOE and HOTSPOt, use `\u003Cdivider>` to create different message, above `\u003Cdivider>` for Hotspot, and below `\u003Cdivider>` for PPPOE\n\nExample:\n\n```\nHello [[name]], \nyour Hotspot package *[[package]]* will be expired in 7 days.\n[[package]] [[price]] \n[[bills]]\n\u003Cdivider>\nHello [[name]], \nyour PPPOE package *[[package]]* will be expired in 7 days.\n[[package]] [[price]] \n[[bills]]\n```","title":"User Notification Text","modified":"20250311052731156","modifier":"ibnux"}
|
||||
]</script><div id="storeArea" style="display:none;"></div>
|
||||
<!--~~ Library modules ~~-->
|
||||
|
@ -251,6 +251,16 @@ CREATE TABLE `tbl_customers_inbox` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
DROP TABLE IF EXISTS `tbl_port_pool`;
|
||||
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;
|
||||
|
||||
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',
|
||||
|
643
login.html
Normal file
643
login.html
Normal file
@ -0,0 +1,643 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>yatmack</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.8.3/core.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link href='https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap' rel='stylesheet'>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
navy: {
|
||||
DEFAULT: '#023047',
|
||||
50: '#e6f0f5',
|
||||
100: '#cce1eb',
|
||||
200: '#99c3d7',
|
||||
300: '#66a5c3',
|
||||
400: '#3387af',
|
||||
500: '#023047',
|
||||
600: '#022640',
|
||||
700: '#011d39',
|
||||
800: '#011332',
|
||||
900: '#010a2b'
|
||||
},
|
||||
orange: {
|
||||
DEFAULT: '#fb8500',
|
||||
50: '#fff4e6',
|
||||
100: '#ffe9cc',
|
||||
200: '#ffd399',
|
||||
300: '#ffbd66',
|
||||
400: '#ffa733',
|
||||
500: '#fb8500',
|
||||
600: '#cc6d00',
|
||||
700: '#995200',
|
||||
800: '#663600',
|
||||
900: '#331b00'
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
'lexend': ['Lexend', 'sans-serif'],
|
||||
},
|
||||
animation: {
|
||||
'spin-fast': 'spin 0.5s linear infinite',
|
||||
'fade-in': 'fadeIn 0.3s ease-in-out',
|
||||
'slide-up': 'slideUp 0.3s ease-out'
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' }
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="font-lexend min-h-screen bg-gradient-to-b from-navy to-navy/95 text-gray-100">
|
||||
<div class="container mx-auto px-2 py-4 max-w-3xl">
|
||||
<div class="bg-white/10 backdrop-blur-lg rounded-2xl shadow-xl mb-6 overflow-hidden border border-white/10">
|
||||
<div class="p-3 relative">
|
||||
<h1 class="text-2xl font-bold text-center text-orange mb-3">YATMACK HOTSPOT</h1>
|
||||
<p class="text-gray-100/90 text-center text-sm mb-4">
|
||||
Select package ? Enter M-Pesa number ? Complete payment
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-2 py-2 px-4 bg-white/5 rounded-xl">
|
||||
<i class="fas fa-headset text-orange"></i>
|
||||
<p class="text-sm font-medium">Support: 254705042522</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-2 sm:py-1 lg:py-1">
|
||||
<div class="mx-auto max-w-screen-2xl px-1 md:px-1">
|
||||
<div class="mx-auto max-w-lg grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 gap-4 p-1" id="cards-container">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="redeemVoucher()" class="w-full bg-orange hover:bg-orange/90 text-white font-medium py-4 px-6 rounded-xl shadow-lg transition duration-200 flex items-center justify-center gap-3 mb-6">
|
||||
<i class="fas fa-ticket-alt"></i>
|
||||
<span>Redeem Voucher</span>
|
||||
</button>
|
||||
<div class="bg-white/10 backdrop-blur-lg rounded-2xl shadow-xl overflow-hidden border border-white/10 mb-6">
|
||||
<div class="p-6 space-y-8">
|
||||
<!-- Reconnect with M-Pesa -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-orange">Reconnect with M-Pesa</h3>
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<input id="mpesaCodeInput" type="text" placeholder="Enter M-Pesa code (e.g., SCK15SKB4Z)" class="flex-grow px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange/50 text-white placeholder:text-gray-400">
|
||||
<button id="reconnectBtn" class="bg-orange hover:bg-orange/90 text-white font-medium py-3 px-6 rounded-xl transition duration-200">Reconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-orange">Active Package Login</h3>
|
||||
<form id="loginForm" action="$(link-login-only)" method="post" $(if chap-id)onSubmit="return doLogin()" $(endif)>
|
||||
<input type="hidden" name="dst" value="$(link-orig)">
|
||||
<input type="hidden" name="popup" value="true">
|
||||
<input type="hidden" name="mac" id="mac" value="$(mac)">
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<input id="usernameInput" name="username" type="text" placeholder="Enter Username (e.g., ACC123456)" class="flex-grow px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange/50 text-white placeholder:text-gray-400">
|
||||
<button id="submitBtn" type="button" onclick="submitLogin()" class="bg-orange hover:bg-orange/90 text-white font-medium py-3 px-6 rounded-xl transition duration-200">Connect</button>
|
||||
</div>
|
||||
<input type="hidden" name="password" value="1234">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-gray-400">© 2025 yatmack. Created by Smartisp</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function fetchData() {
|
||||
var domain = 'https://yatmack2.smartisp.co.ke/';
|
||||
var siteUrl = domain + "/index.php?_route=plugin/hotspot_plan";
|
||||
var routerName = encodeURIComponent("yatmack");
|
||||
var dataparams = `routername=${routerName}`;
|
||||
fetch(siteUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: dataparams
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
populateCards(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fetch error:', error);
|
||||
});
|
||||
}
|
||||
function populateCards(data) {
|
||||
var cardsContainer = document.getElementById('cards-container');
|
||||
cardsContainer.innerHTML = ''; // Clear existing content
|
||||
// Sort the plans by price in ascending order
|
||||
data.data.forEach(router => {
|
||||
// Sort hotspot plans by price
|
||||
router.plans_hotspot.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
|
||||
router.plans_hotspot.forEach(item => {
|
||||
var cardDiv = document.createElement('div');
|
||||
cardDiv.className = 'bg-white border border-black rounded-lg shadow-md overflow-hidden transition duration-300 hover:shadow-lg flex flex-col items-center justify-between mx-auto mb-4 w-40';
|
||||
cardDiv.innerHTML = `
|
||||
<div class="bg-blue-500 text-white w-full py-1">
|
||||
<h2 class="text-sm font-medium uppercase text-center" style="font-size: clamp(0.75rem, 1.5vw, 1rem); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
${item.planname}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="px-4 py-2 flex-grow">
|
||||
<p class="text-2xl font-bold text-blue-600 mb-1">
|
||||
<span class="text-lg font-medium text-black">${item.currency}</span>
|
||||
${item.price}
|
||||
</p>
|
||||
<p class="text-sm text-black mb-2">
|
||||
Valid for ${item.validity} ${item.timelimit}
|
||||
</p>
|
||||
<hr class="border-black mb-2">
|
||||
</div>
|
||||
<div class="px-4 py-2 flex-shrink-0">
|
||||
<a href="#" class="inline-block bg-gray-900 text-white hover:bg-blue-600 font-semibold py-1 px-4 rounded-lg transition duration-300 text-md"
|
||||
onclick="handlePhoneNumberSubmission('${item.planId}', '${item.routerId}'); return false;"
|
||||
data-plan-id="${item.planId}"
|
||||
data-router-id="${item.routerId}">
|
||||
Buy
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
cardsContainer.appendChild(cardDiv);
|
||||
});
|
||||
});
|
||||
}
|
||||
fetchData();
|
||||
function getMacAddress() {
|
||||
return "$(mac)"; // MikroTik replaces this with the user's MAC address
|
||||
}
|
||||
|
||||
function getOrCreateAccountId() {
|
||||
var radiaxid = localStorage.getItem('radiaxid');
|
||||
if (!radiaxid) {
|
||||
radiaxid = getMacAddress();
|
||||
localStorage.setItem('radiaxid', radiaxid);
|
||||
setCookie('radiaxid', radiaxid, 365);
|
||||
}
|
||||
return radiaxid;
|
||||
}
|
||||
|
||||
function getAccountId() {
|
||||
return localStorage.getItem('radiaxid') || getCookie('radiaxid') || getMacAddress();
|
||||
}
|
||||
|
||||
function formatPhoneNumber(phoneNumber) {
|
||||
if (phoneNumber.startsWith('+')) {
|
||||
phoneNumber = phoneNumber.substring(1);
|
||||
}
|
||||
if (phoneNumber.startsWith('0')) {
|
||||
phoneNumber = '254' + phoneNumber.substring(1);
|
||||
}
|
||||
if (phoneNumber.match(/^(7|1)/)) {
|
||||
phoneNumber = '254' + phoneNumber;
|
||||
}
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var loginTimeout;
|
||||
function handlePhoneNumberSubmission(planId, routerId, price) {
|
||||
var accountId = getOrCreateAccountId();
|
||||
var modalHtml = `
|
||||
<div id="paymentModal" class='fixed inset-0 bg-black/30 backdrop-blur-sm z-50 animate-fade-in'>
|
||||
<div class="fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full max-w-md">
|
||||
<div class="bg-white text-black rounded-lg shadow-xl">
|
||||
<div class="flex items-center justify-between p-4 border-b border-gray-300">
|
||||
<h5 class="text-xl font-semibold">
|
||||
Enter Your Mpesa Number
|
||||
</h5>
|
||||
<button class="text-gray-500 hover:text-black" onclick="closeModal('paymentModal')">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="bg-navy-50 border-l-4 border-navy-400 p-4 rounded-md mb-4"> <div class="flex"> <div class="flex-shrink-0"> <svg class="h-5 w-5 text-navy-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1 4v.01m6.938-2.162A9 9 0 1111 3v0a9 9 0 018.938 10.838z" /> </svg> </div> <div class="ml-3"> <p class="text-sm text-navy-700"> You are about to initiate M-pesa payment. Enter phone number below and click Pay Now to initialize payment. </p> </div> </div></div> <input type="text" class="w-full px-4 py-3 border border-orange-300 rounded-lg focus:ring-2 focus:ring-navy-400 focus:border-blue-500 text-black" id="phoneNumberInput" required placeholder="e.g. 0712345678">
|
||||
<div class="text-red-500 mt-1 hidden" id="invalidPhone">Please enter a valid phone number!</div>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-2 p-4 border-t border-gray-300">
|
||||
<button onclick="closeModal('paymentModal')" class="px-4 py-2 bg-orange text-black rounded-lg hover:bg-gray-300">Close</button>
|
||||
<button id="payNowBtn" class="px-4 py-2 bg-navy text-white font-semibold hover:from-blue-600 hover:to-blue-500 flex items-center rounded-lg">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
Pay Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
var modal = document.getElementById('paymentModal');
|
||||
modal.classList.remove('hidden');
|
||||
var payNowBtn = document.getElementById('payNowBtn');
|
||||
var phoneInput = document.getElementById('phoneNumberInput');
|
||||
phoneInput.focus();
|
||||
payNowBtn.addEventListener('click', function() { handlePayment(); });
|
||||
function handlePayment() {
|
||||
if (!phoneInput.value) {
|
||||
document.getElementById('invalidPhone').classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
payNowBtn.disabled = true;
|
||||
payNowBtn.innerHTML = `
|
||||
<svg class='animate-spin -ml-1 mr-3 h-5 w-5 text-white' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
|
||||
<circle class='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' stroke-width='4'></circle>
|
||||
<path class='opacity-75' fill='currentColor' d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'></path>
|
||||
</svg>
|
||||
Processing...
|
||||
`;
|
||||
var formattedPhoneNumber = formatPhoneNumber(phoneInput.value);
|
||||
document.getElementById('usernameInput').value = accountId;
|
||||
|
||||
fetch('https://yatmack2.smartisp.co.ke/index.php?_route=plugin/CreateHotspotuser&type=grant', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
phone_number: formattedPhoneNumber,
|
||||
plan_id: planId,
|
||||
router_id: routerId,
|
||||
account_id: accountId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'error') throw new Error(data.message);
|
||||
closeModal('paymentModal');
|
||||
showProcessingModal();
|
||||
checkPaymentStatus(formattedPhoneNumber);
|
||||
})
|
||||
.catch(error => {
|
||||
closeModal('paymentModal');
|
||||
showErrorModal(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showProcessingModal() {
|
||||
var processingModalHtml = `
|
||||
<div id='processingModal' class='fixed inset-0 bg-black/30 backdrop-blur-sm z-50 animate-fade-in'>
|
||||
<div class='fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full max-w-md animate-slide-up'>
|
||||
<div class='bg-gradient-to-br from-white to-gray-50 shadow-2xl rounded-2xl p-8 text-center border border-gray-100'>
|
||||
<div class='flex justify-center'>
|
||||
<svg class='animate-spin h-20 w-20 text-navy-500' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
|
||||
<circle class='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' stroke-width='4'></circle>
|
||||
<path class='opacity-75' fill='currentColor' d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-6 bg-navy-50/50 backdrop-blur-xs border border-navy-200 rounded-xl p-6 shadow-lg"> <div class="flex items-center space-x-4"> <svg class="h-8 w-8 text-navy-400 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1 4v.01m6.938-2.162A9 9 0 1111 3v0a9 9 0 018.938 10.838z" /> </svg> <div class="flex-1"> <h2 class="text-xl font-semibold text-navy-600 font-lexend">Initializing Payment</h2> <p class="text-navy-500 mt-2">A payment request has been sent to your phone. Please wait while we process your payment.</p> </div> </div> <div class="mt-4 h-2 w-full bg-navy-100 rounded-full overflow-hidden"> <div class="h-2 bg-navy-400 rounded-full animate-pulse"></div> </div> </div> </div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentHTML('beforeend', processingModalHtml);
|
||||
}
|
||||
function closeModal(modalId) {
|
||||
var modal = document.getElementById(modalId);
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
setTimeout(function() { modal.remove(); }, 300);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccessModal() {
|
||||
var successModalHtml = `
|
||||
<div id='successModal' class='fixed inset-0 bg-black/60 backdrop-blur-sm z-50 transition-all duration-300'>
|
||||
<div class='fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full max-w-lg transition-all duration-300 ease-out'>
|
||||
<div class='bg-white/95 backdrop-blur-md shadow-2xl rounded-3xl p-8 text-center border border-gray-200/50 ring-1 ring-gray-900/5'>
|
||||
<div class='flex justify-center mb-6'>
|
||||
<div class='relative'>
|
||||
<div class='absolute inset-0 rounded-full bg-green-500/20 animate-pulse'></div>
|
||||
<svg class='h-16 w-16 text-green-500' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class='space-y-4'>
|
||||
<h2 class='text-2xl font-semibold text-gray-900'>Payment Successful</h2>
|
||||
<div class='bg-green-50 rounded-2xl p-6 border border-green-100'>
|
||||
<div class='flex items-center gap-4'>
|
||||
<svg class='h-6 w-6 text-green-500 flex-shrink-0' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4'/>
|
||||
</svg>
|
||||
<p class='text-sm text-green-700'>Your transaction has been completed successfully. You will be redirected shortly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentHTML('beforeend', successModalHtml);
|
||||
setTimeout(function() { closeModal('successModal'); }, 2000);
|
||||
}
|
||||
|
||||
function showErrorModal(errorMsg) {
|
||||
var errorModalHtml = `
|
||||
<div id='errorModal' class='fixed inset-0 bg-black/60 backdrop-blur-sm z-50 transition-all duration-300'>
|
||||
<div class='fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full max-w-lg transition-all duration-300 ease-out'>
|
||||
<div class='bg-white/95 backdrop-blur-md shadow-2xl rounded-3xl p-8 text-center border border-gray-200/50 ring-1 ring-gray-900/5'>
|
||||
<div class='flex justify-center mb-6'>
|
||||
<div class='relative'>
|
||||
<div class='absolute inset-0 rounded-full bg-red-500/20 animate-pulse'></div>
|
||||
<svg class='h-16 w-16 text-red-500' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 18L18 6M6 6l12 12'/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class='space-y-4'>
|
||||
<h2 class='text-2xl font-semibold text-gray-900'>Payment Failed</h2>
|
||||
<div class='bg-red-50 rounded-2xl p-6 border border-red-100'>
|
||||
<div class='flex items-center gap-4'>
|
||||
<svg class='h-6 w-6 text-red-500 flex-shrink-0' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z'/>
|
||||
</svg>
|
||||
<p class='text-sm text-red-700'>An error occurred while processing your payment. Please try again.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentHTML('beforeend', errorModalHtml);
|
||||
setTimeout(function() { closeModal('errorModal'); }, 2000);
|
||||
}
|
||||
|
||||
function checkPaymentStatus(phoneNumber) {
|
||||
var accountId = getOrCreateAccountId();
|
||||
var checkInterval = setInterval(function() {
|
||||
fetch('https://yatmack2.smartisp.co.ke/index.php?_route=plugin/CreateHotspotuser&type=verify', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({account_id: accountId})
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(data) {
|
||||
console.log('Raw Response:', data);
|
||||
if (data.Resultcode === '3') {
|
||||
clearInterval(checkInterval);
|
||||
closeModal('processingModal');
|
||||
showSuccessModal();
|
||||
if (loginTimeout) clearTimeout(loginTimeout);
|
||||
loginTimeout = setTimeout(function() { document.getElementById('loginForm').submit(); }, 2000);
|
||||
} else if (data.Resultcode === '2') {
|
||||
clearInterval(checkInterval);
|
||||
closeModal('processingModal');
|
||||
showErrorModal(data.Message);
|
||||
} else {
|
||||
console.error('Unexpected result code:', data.Resultcode);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error during fetch request:', error);
|
||||
clearInterval(checkInterval);
|
||||
closeModal('processingModal');
|
||||
showErrorModal('An error occurred while checking payment status.');
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(checkInterval);
|
||||
closeModal('processingModal');
|
||||
showErrorModal('Timeout while waiting for payment confirmation.');
|
||||
}, 300000); // 5 minutes
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var accountId = getOrCreateAccountId();
|
||||
var usernameInput = document.getElementById('usernameInput');
|
||||
if (usernameInput && !usernameInput.value) {
|
||||
usernameInput.value = accountId;
|
||||
}
|
||||
var submitBtn = document.getElementById('submitBtn');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', function() {
|
||||
document.getElementById('loginForm').submit();
|
||||
});
|
||||
}
|
||||
});
|
||||
var loginTimeout; // Variable to store the timeout ID
|
||||
function redeemVoucher() {
|
||||
Swal.fire({
|
||||
title: 'Redeem Voucher',
|
||||
input: 'text',
|
||||
inputPlaceholder: 'Enter voucher code',
|
||||
inputValidator: function(value) {
|
||||
if (!value) {
|
||||
return 'You need to enter a voucher code!';
|
||||
}
|
||||
},
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Redeem',
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: (voucherCode) => {
|
||||
var accountId = voucherCode;
|
||||
return fetch('https://yatmack2.smartisp.co.ke/index.php?_route=plugin/CreateHotspotuser&type=voucher', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({voucher_code: voucherCode, account_id: accountId}),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'error') throw new Error(data.message);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
allowOutsideClick: () => !Swal.isLoading()
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Voucher Redeemed',
|
||||
text: result.value.message,
|
||||
showConfirmButton: false,
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
var username = result.value.username;
|
||||
console.log('Received username from server:', username);
|
||||
var usernameInput = document.querySelector('input[name="username"]');
|
||||
if (usernameInput) {
|
||||
console.log('Found username input element.');
|
||||
usernameInput.value = username;
|
||||
loginTimeout = setTimeout(function() {
|
||||
var loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) {
|
||||
loginForm.submit();
|
||||
} else {
|
||||
console.error('Login form not found.');
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Login form not found. Please try again.',
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
console.error('Username input element not found.');
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Username input not found. Please try again.',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oops...',
|
||||
text: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var reconnectBtn = document.getElementById('reconnectBtn');
|
||||
var mpesaCodeInput = document.getElementById('mpesaCodeInput');
|
||||
var macInput = document.getElementById('mac');
|
||||
var loginForm = document.getElementById('loginForm');
|
||||
if (reconnectBtn) {
|
||||
reconnectBtn.addEventListener('click', function() {
|
||||
// Validate inputs before processing
|
||||
if (!mpesaCodeInput || !macInput || !loginForm) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Required form elements are missing'
|
||||
});
|
||||
return;
|
||||
}
|
||||
var mpesaCode = mpesaCodeInput.value.trim().split(' ')[0];
|
||||
var mac = macInput.value.trim();
|
||||
fetch('https://yatmack2.smartisp.co.ke/index.php?_route=plugin/ReconnectUser', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ mpesa_code: mpesaCode, mac: mac }),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Response data:', data);
|
||||
var resultCode = data.Resultcode;
|
||||
var message = data.Message;
|
||||
var status = data.Status;
|
||||
var username = data.username;
|
||||
if (resultCode === '1') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Invalid Code',
|
||||
text: message
|
||||
});
|
||||
}
|
||||
else if (resultCode === '3') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Expired Package',
|
||||
text: message
|
||||
});
|
||||
}
|
||||
else if (resultCode === '2') {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: status,
|
||||
text: message,
|
||||
showConfirmButton: false,
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
console.log('Received username from server:', username);
|
||||
var usernameInput = document.querySelector('input[name="username"]');
|
||||
if (usernameInput) {
|
||||
console.log('Found username input element.');
|
||||
usernameInput.value = username;
|
||||
setTimeout(function() {
|
||||
var loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) {
|
||||
loginForm.submit();
|
||||
} else {
|
||||
console.error('Login form not found.');
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Login form not found. Please try again.',
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
console.error('Username input element not found.');
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Username input not found. Please try again.',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oops...',
|
||||
text: error.message
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error('Reconnect button not found');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</html>
|
347
system/autoload/Invoice.php
Normal file
347
system/autoload/Invoice.php
Normal file
@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
use Mpdf\Mpdf;
|
||||
|
||||
class Invoice
|
||||
{
|
||||
public static function generateInvoice($invoiceData)
|
||||
{
|
||||
try {
|
||||
if (empty($invoiceData['invoice'])) {
|
||||
throw new Exception(Lang::T("Invoice No is required"));
|
||||
}
|
||||
|
||||
$template = Lang::getNotifText('email_invoice');
|
||||
if (!$template) {
|
||||
throw new Exception(Lang::T("Invoice template not found"));
|
||||
}
|
||||
|
||||
if (strpos($template, '<body') === false) {
|
||||
$template = "<html><body>$template</body></html>";
|
||||
}
|
||||
|
||||
$processedHtml = self::renderTemplate($template, $invoiceData);
|
||||
|
||||
// Debugging: Save processed HTML to file for review
|
||||
// file_put_contents('debug_invoice.html', $processedHtml);
|
||||
|
||||
// Generate PDF
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'margin_left' => 10,
|
||||
'margin_right' => 10,
|
||||
'margin_top' => 10,
|
||||
'margin_bottom' => 10,
|
||||
'default_font' => 'helvetica',
|
||||
'orientation' => 'P',
|
||||
]);
|
||||
|
||||
$mpdf->SetDisplayMode('fullpage');
|
||||
$mpdf->SetProtection(['print']);
|
||||
$mpdf->shrink_tables_to_fit = 1;
|
||||
$mpdf->SetWatermarkText(strtoupper($invoiceData['status'] ?? 'UNPAID'), 0.15);
|
||||
$mpdf->showWatermarkText = true;
|
||||
$mpdf->WriteHTML($processedHtml);
|
||||
|
||||
// Save PDF
|
||||
$filename = "invoice_{$invoiceData['invoice']}.pdf";
|
||||
$outputPath = "system/uploads/invoices/{$filename}";
|
||||
$dir = dirname($outputPath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
$mpdf->Output($outputPath, 'F');
|
||||
|
||||
if (!file_exists($outputPath)) {
|
||||
throw new Exception(Lang::T("Failed to save PDF file"));
|
||||
}
|
||||
|
||||
return $filename;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
_log("Invoice generation failed: " . $e->getMessage());
|
||||
sendTelegram("Invoice generation failed: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static function renderTemplate($template, $invoiceData)
|
||||
{
|
||||
return preg_replace_callback('/\[\[(\w+)\]\]/', function ($matches) use ($invoiceData) {
|
||||
$key = $matches[1];
|
||||
if (!isset($invoiceData[$key])) {
|
||||
_log(Lang::T("Missing invoice key: ") . $key);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (in_array($key, ['created_at', 'due_date'])) {
|
||||
return date('F j, Y', strtotime($invoiceData[$key]));
|
||||
}
|
||||
|
||||
if (in_array($key, ['amount', 'total', 'subtotal', 'tax'])) {
|
||||
return $invoiceData['currency_code'] . number_format((float) $invoiceData[$key], 2);
|
||||
}
|
||||
|
||||
if ($key === 'bill_rows') {
|
||||
return $invoiceData[$key];
|
||||
}
|
||||
|
||||
return htmlspecialchars($invoiceData[$key] ?? '');
|
||||
}, $template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Set default currency code
|
||||
$config['currency_code'] ??= '$';
|
||||
|
||||
$account = ORM::for_table('tbl_customers')->find_one($userId);
|
||||
self::validateAccount($account);
|
||||
if (!$invoiceNo) {
|
||||
$invoiceNo = "INV-" . Package::_raid();
|
||||
}
|
||||
// Fetch invoice if not provided
|
||||
if ($status === "Unpaid" && !$invoice) {
|
||||
$data = ORM::for_table('tbl_user_recharges')->where('customer_id', $userId)
|
||||
->where('status', 'off')
|
||||
->left_outer_join('tbl_plans', 'tbl_user_recharges.namebp = tbl_plans.name_plan')
|
||||
->select('tbl_plans.price', 'price')
|
||||
->select('tbl_plans.name_plan', 'namebp')
|
||||
->find_one();
|
||||
if (!$data) {
|
||||
$data = ORM::for_table('tbl_user_recharges')->where('username', $account->username)
|
||||
->left_outer_join('tbl_plans', 'tbl_user_recharges.namebp = tbl_plans.name_plan')
|
||||
->select('tbl_plans.price', 'price')
|
||||
->select('tbl_plans.name_plan', 'namebp')
|
||||
->where('status', 'off')
|
||||
->find_one();
|
||||
}
|
||||
if (!$data) {
|
||||
throw new Exception(Lang::T("No unpaid invoice found for username:") . $account->username);
|
||||
}
|
||||
$invoice = [
|
||||
'price' => $data->price,
|
||||
'plan_name' => $data->namebp,
|
||||
'routers' => $data->routers,
|
||||
];
|
||||
|
||||
} else if ($status === "Paid" && !$invoice) {
|
||||
$invoice = ORM::for_table("tbl_transactions")->where("username", $account->username)->find_one();
|
||||
}
|
||||
if (!$invoice) {
|
||||
throw new Exception(Lang::T("Transaction not found for username: ") . $account->username);
|
||||
}
|
||||
|
||||
// Get additional bills if not provided
|
||||
if (empty($bills)) {
|
||||
[$bills, $add_cost] = User::getBills($account->id);
|
||||
}
|
||||
|
||||
$invoiceItems = self::generateInvoiceItems($invoice, $bills, $add_cost);
|
||||
$subtotal = array_sum(array_column($invoiceItems, 'amount'));
|
||||
$tax = $config['enable_tax'] ? Package::tax($subtotal) : 0;
|
||||
$tax_rate = $config['tax_rate'] ?? 0;
|
||||
$total = $subtotal + $tax;
|
||||
|
||||
$payLink = self::generatePaymentLink($account, $invoice, $status);
|
||||
$logo = self::getCompanyLogo($UPLOAD_PATH, $root_path);
|
||||
|
||||
$invoiceData = [
|
||||
'invoice' => $invoiceNo,
|
||||
'fullname' => $account->fullname,
|
||||
'email' => $account->email,
|
||||
'address' => $account->address,
|
||||
'phone' => $account->phonenumber,
|
||||
'bill_rows' => self::generateBillRows($invoiceItems, $config['currency_code'], $subtotal, $tax_rate, $tax, $total),
|
||||
'status' => $status,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'due_date' => date('Y-m-d H:i:s', strtotime('+7 days')),
|
||||
'currency' => $config['currency_code'],
|
||||
'company_address' => $config['address'],
|
||||
'company_name' => $config['CompanyName'],
|
||||
'company_phone' => $config['phone'],
|
||||
'logo' => $logo,
|
||||
'payment_link' => $payLink
|
||||
];
|
||||
|
||||
if (empty($invoiceData['bill_rows'])) {
|
||||
throw new Exception(Lang::T("Bill rows data is empty."));
|
||||
}
|
||||
|
||||
$filename = self::generateInvoice($invoiceData);
|
||||
if (!$filename) {
|
||||
throw new Exception(Lang::T("Failed to generate invoice PDF"));
|
||||
}
|
||||
|
||||
$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)
|
||||
{
|
||||
$html = "<table style='width: 100%; border-collapse: collapse; margin: 20px 0;'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style='background: #3498db; color: white; padding: 12px; text-align: left;'>Description</th>
|
||||
<th style='background: #3498db; color: white; padding: 12px; text-align: left;'>Details</th>
|
||||
<th style='background: #3498db; color: white; padding: 12px; text-align: left;'>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
|
||||
foreach ($items as $item) {
|
||||
$desc = htmlspecialchars($item['description'], ENT_QUOTES);
|
||||
$details = htmlspecialchars($item['details'], ENT_QUOTES);
|
||||
$html .= "<tr>
|
||||
<td style='padding: 10px; border-bottom: 1px solid #ddd;'>{$desc}</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>
|
||||
</tr>";
|
||||
}
|
||||
|
||||
$html .= "<tr>
|
||||
<td colspan='2' style='text-align: right; padding: 10px; border-top: 2px solid #3498db;'>Subtotal:</td>
|
||||
<td style='padding: 10px; border-top: 2px solid #3498db;'>{$currency}" . number_format($subtotal, 2) . "</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan='2' style='text-align: right; padding: 10px;'>TAX ({$tax_rate}%):</td>
|
||||
<td style='padding: 10px;'>{$currency}" . number_format($tax, 2) . "</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan='2' style='text-align: right; padding: 10px; font-weight: bold;'>Total:</td>
|
||||
<td style='padding: 10px; font-weight: bold;'>{$currency}" . number_format($total, 2) . "</td>
|
||||
</tr>";
|
||||
|
||||
$html .= "</tbody></table>";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -28,7 +28,7 @@ class Message
|
||||
if (!empty($topik)) {
|
||||
$topik = "message_thread_id=$topik&";
|
||||
}
|
||||
return Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?'.$topik.'chat_id=' . $chat_id . '&text=' . urlencode($txt));
|
||||
return Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?' . $topik . 'chat_id=' . $chat_id . '&text=' . urlencode($txt));
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ class Message
|
||||
$txts = str_split($txt, 160);
|
||||
try {
|
||||
foreach ($txts as $txt) {
|
||||
self::sendSMS($config['sms_url'], $phone, $txt);
|
||||
self::sendSMS($phone, $txt);
|
||||
self::logMessage('SMS', $phone, $txt, 'Success');
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
@ -120,7 +120,7 @@ class Message
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendEmail($to, $subject, $body)
|
||||
public static function sendEmail($to, $subject, $body, $attachmentPath = null)
|
||||
{
|
||||
global $config, $PAGES_PATH, $debug_mail;
|
||||
if (empty($body)) {
|
||||
@ -130,7 +130,6 @@ class Message
|
||||
return "";
|
||||
}
|
||||
run_hook('send_email', [$to, $subject, $body]); #HOOK
|
||||
self::logMessage('Email', $to, $body, 'Success');
|
||||
if (empty($config['smtp_host'])) {
|
||||
$attr = "";
|
||||
if (!empty($config['mail_from'])) {
|
||||
@ -140,6 +139,8 @@ class Message
|
||||
$attr .= "Reply-To: " . $config['mail_reply_to'] . "\r\n";
|
||||
}
|
||||
mail($to, $subject, $body, $attr);
|
||||
self::logMessage('Email', $to, $body, 'Success');
|
||||
return true;
|
||||
} else {
|
||||
$mail = new PHPMailer();
|
||||
$mail->isSMTP();
|
||||
@ -161,6 +162,10 @@ class Message
|
||||
|
||||
$mail->addAddress($to);
|
||||
$mail->Subject = $subject;
|
||||
// Attachments
|
||||
if (!empty($attachmentPath)) {
|
||||
$mail->addAttachment($attachmentPath);
|
||||
}
|
||||
|
||||
if (!file_exists($PAGES_PATH . DIRECTORY_SEPARATOR . 'Email.html')) {
|
||||
if (!copy($PAGES_PATH . '_template' . DIRECTORY_SEPARATOR . 'Email.html', $PAGES_PATH . DIRECTORY_SEPARATOR . 'Email.html')) {
|
||||
@ -176,6 +181,7 @@ class Message
|
||||
$html = str_replace('[[Body]]', nl2br($body), $html);
|
||||
$mail->isHTML(true);
|
||||
$mail->Body = $html;
|
||||
$mail->Body = $html;
|
||||
} else {
|
||||
$mail->isHTML(false);
|
||||
$mail->Body = $body;
|
||||
@ -183,8 +189,10 @@ class Message
|
||||
if (!$mail->send()) {
|
||||
$errorMessage = Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo;
|
||||
self::logMessage('Email', $to, $body, 'Error', $errorMessage);
|
||||
return false;
|
||||
} else {
|
||||
self::logMessage('Email', $to, $body, 'Success');
|
||||
return true;
|
||||
}
|
||||
|
||||
//<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">
|
||||
@ -303,7 +311,7 @@ class Message
|
||||
|
||||
public static function sendInvoice($cust, $trx)
|
||||
{
|
||||
global $config;
|
||||
global $config, $db_pass;
|
||||
$textInvoice = Lang::getNotifText('invoice_paid');
|
||||
$textInvoice = str_replace('[[company_name]]', $config['CompanyName'], $textInvoice);
|
||||
$textInvoice = str_replace('[[address]]', $config['address'], $textInvoice);
|
||||
@ -328,7 +336,11 @@ class Message
|
||||
$textInvoice = str_replace('[[password]]', $cust['password'], $textInvoice);
|
||||
$textInvoice = str_replace('[[expired_date]]', Lang::dateAndTimeFormat($trx['expiration'], $trx['time']), $textInvoice);
|
||||
$textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
|
||||
// Calculate bills and additional costs
|
||||
|
||||
$inv_url = "?_route=voucher/invoice/$trx[id]/" . md5($trx['id'] . $db_pass);
|
||||
$textInvoice = str_replace('[[invoice_link]]', $inv_url, $textInvoice);
|
||||
|
||||
// Calculate bills and additional costs
|
||||
list($bills, $add_cost) = User::getBills($cust['id']);
|
||||
|
||||
// Initialize note and total variables
|
||||
@ -362,7 +374,7 @@ class Message
|
||||
// Add total to the note
|
||||
$note .= "Total : " . Lang::moneyFormat($total) . "\n";
|
||||
|
||||
// Replace placeholders in the message
|
||||
// Replace placeholders in the message
|
||||
$textInvoice = str_replace('[[bills]]', $note, $textInvoice);
|
||||
|
||||
if ($config['user_notification_payment'] == 'sms') {
|
||||
@ -387,20 +399,23 @@ class Message
|
||||
$v->body = nl2br($body);
|
||||
$v->save();
|
||||
self::logMessage("Inbox", $user->username, $body, "Success");
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
$errorMessage = Lang::T("Error adding message to inbox: " . $e->getMessage());
|
||||
self::logMessage('Inbox', $user->username, $body, 'Error', $errorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMessageType($type, $message){
|
||||
if(strpos($message, "<divider>") === false){
|
||||
public static function getMessageType($type, $message)
|
||||
{
|
||||
if (strpos($message, "<divider>") === false) {
|
||||
return $message;
|
||||
}
|
||||
$msgs = explode("<divider>", $message);
|
||||
if($type == "PPPOE"){
|
||||
if ($type == "PPPOE") {
|
||||
return $msgs[1];
|
||||
}else{
|
||||
} else {
|
||||
return $msgs[0];
|
||||
}
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ switch ($action) {
|
||||
if (count($plns) > 0) {
|
||||
$query->where_in('plan_name', $plns);
|
||||
}
|
||||
$x = $query->find_array();
|
||||
$xy = $query->sum('price');
|
||||
$x = $query->find_array();
|
||||
$xy = $query->sum('price');
|
||||
|
||||
$ui->assign('sd', $sd);
|
||||
$ui->assign('ed', $ed);
|
||||
@ -114,7 +114,10 @@ switch ($action) {
|
||||
$query = ORM::for_table('tbl_transactions')
|
||||
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts"))
|
||||
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te"))
|
||||
->order_by_desc('id');
|
||||
->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
|
||||
->select('tbl_transactions.*')
|
||||
->select('tbl_customers.fullname', 'fullname')
|
||||
->order_by_desc('tbl_transactions.id');
|
||||
if (count($tps) > 0) {
|
||||
$query->where_in('type', $tps);
|
||||
}
|
||||
@ -131,13 +134,13 @@ switch ($action) {
|
||||
if (count($plns) > 0) {
|
||||
$query->where_in('plan_name', $plns);
|
||||
}
|
||||
$x = $query->find_array();
|
||||
$xy = $query->sum('price');
|
||||
$x = $query->find_array();
|
||||
$xy = $query->sum('price');
|
||||
|
||||
$title = ' Reports [' . $mdate . ']';
|
||||
$title = str_replace('-', ' ', $title);
|
||||
|
||||
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
|
||||
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
|
||||
if (file_exists($UPLOAD_PATH . '/logo.png')) {
|
||||
$logo = $UPLOAD_URL_PATH . '/logo.png';
|
||||
} else {
|
||||
@ -154,10 +157,11 @@ switch ($action) {
|
||||
</div>
|
||||
<div id="logo"><img id="image" src="' . $logo . '" alt="logo" /></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">
|
||||
<tr>
|
||||
<th>' . Lang::T('Username') . '</th>
|
||||
<th>' . Lang::T('Fullname') . '</th>
|
||||
<th>' . Lang::T('Plan Name') . '</th>
|
||||
<th>' . Lang::T('Type') . '</th>
|
||||
<th>' . Lang::T('Plan Price') . '</th>
|
||||
@ -170,6 +174,7 @@ switch ($action) {
|
||||
foreach ($x as $value) {
|
||||
|
||||
$username = $value['username'];
|
||||
$fullname = $value['fullname'];
|
||||
$plan_name = $value['plan_name'];
|
||||
$type = $value['type'];
|
||||
$price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']);
|
||||
@ -181,6 +186,7 @@ switch ($action) {
|
||||
|
||||
$html .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . "
|
||||
<td>$username</td>
|
||||
<td>$fullname</td>
|
||||
<td>$plan_name</td>
|
||||
<td>$type</td>
|
||||
<td align='right'>$price</td>
|
||||
@ -245,7 +251,7 @@ $style
|
||||
$html
|
||||
EOF;
|
||||
$mpdf->WriteHTML($nhtml);
|
||||
$mpdf->Output('phpnuxbill_reports_'.date('Ymd_His') . '.pdf', 'D');
|
||||
$mpdf->Output('phpnuxbill_reports_' . date('Ymd_His') . '.pdf', 'D');
|
||||
} else {
|
||||
echo 'No Data';
|
||||
}
|
||||
@ -258,13 +264,17 @@ EOF;
|
||||
$stype = _post('stype');
|
||||
|
||||
$d = ORM::for_table('tbl_transactions');
|
||||
$d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
|
||||
->select('tbl_transactions.*')
|
||||
->select('tbl_customers.fullname', 'fullname')
|
||||
->order_by_desc('tbl_transactions.id');
|
||||
if ($stype != '') {
|
||||
$d->where('type', $stype);
|
||||
}
|
||||
$d->where_gte('recharged_on', $fdate);
|
||||
$d->where_lte('recharged_on', $tdate);
|
||||
$d->order_by_desc('id');
|
||||
$x = $d->find_many();
|
||||
$x = $d->find_many();
|
||||
|
||||
$dr = ORM::for_table('tbl_transactions');
|
||||
if ($stype != '') {
|
||||
@ -290,6 +300,10 @@ EOF;
|
||||
$tdate = _post('tdate');
|
||||
$stype = _post('stype');
|
||||
$d = ORM::for_table('tbl_transactions');
|
||||
$d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
|
||||
->select('tbl_transactions.*')
|
||||
->select('tbl_customers.fullname', 'fullname')
|
||||
->order_by_desc('tbl_transactions.id');
|
||||
if ($stype != '') {
|
||||
$d->where('type', $stype);
|
||||
}
|
||||
@ -297,7 +311,7 @@ EOF;
|
||||
$d->where_gte('recharged_on', $fdate);
|
||||
$d->where_lte('recharged_on', $tdate);
|
||||
$d->order_by_desc('id');
|
||||
$x = $d->find_many();
|
||||
$x = $d->find_many();
|
||||
|
||||
$dr = ORM::for_table('tbl_transactions');
|
||||
if ($stype != '') {
|
||||
@ -311,7 +325,7 @@ EOF;
|
||||
$title = ' Reports [' . $mdate . ']';
|
||||
$title = str_replace('-', ' ', $title);
|
||||
|
||||
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
|
||||
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
|
||||
if (file_exists($UPLOAD_PATH . '/logo.png')) {
|
||||
$logo = $UPLOAD_URL_PATH . '/logo.png';
|
||||
} else {
|
||||
@ -332,6 +346,7 @@ EOF;
|
||||
<table id="customers">
|
||||
<tr>
|
||||
<th>' . Lang::T('Username') . '</th>
|
||||
<th>' . Lang::T('Fullname') . '</th>
|
||||
<th>' . Lang::T('Plan Name') . '</th>
|
||||
<th>' . Lang::T('Type') . '</th>
|
||||
<th>' . Lang::T('Plan Price') . '</th>
|
||||
@ -344,6 +359,7 @@ EOF;
|
||||
foreach ($x as $value) {
|
||||
|
||||
$username = $value['username'];
|
||||
$fullname = $value['fullname'];
|
||||
$plan_name = $value['plan_name'];
|
||||
$type = $value['type'];
|
||||
$price = $config['currency_code'] . ' ' . number_format($value['price'], 0, $config['dec_point'], $config['thousands_sep']);
|
||||
@ -355,7 +371,8 @@ EOF;
|
||||
|
||||
$html .= "<tr" . (($c = !$c) ? ' class="alt"' : ' class=""') . ">" . "
|
||||
<td>$username</td>
|
||||
<td>$plan_name</td>
|
||||
<td>$fullname</td>
|
||||
<td>$plan_name</td>
|
||||
<td>$type</td>
|
||||
<td align='right'>$price</td>
|
||||
<td>$recharged_on </td>
|
||||
|
27
system/controllers/invoices.php
Normal file
27
system/controllers/invoices.php
Normal 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');
|
||||
}
|
@ -57,56 +57,79 @@ EOT;
|
||||
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
|
||||
}
|
||||
|
||||
// Get form data
|
||||
$id_customer = $_POST['id_customer'];
|
||||
$message = $_POST['message'];
|
||||
$via = $_POST['via'];
|
||||
$id_customer = $_POST['id_customer'] ?? '';
|
||||
$message = $_POST['message'] ?? '';
|
||||
$via = $_POST['via'] ?? '';
|
||||
$subject = $_POST['subject'] ?? '';
|
||||
|
||||
// Check if fields are empty
|
||||
if ($id_customer == '' or $message == '' or $via == '') {
|
||||
r2(getUrl('message/send'), 'e', Lang::T('All field is required'));
|
||||
} else {
|
||||
// Get customer details from the database
|
||||
$c = ORM::for_table('tbl_customers')->find_one($id_customer);
|
||||
// Validate subject based on the selected channel
|
||||
if (($via === 'all' || $via === 'email' || $via === 'inbox') && empty($subject)) {
|
||||
r2(getUrl('message/send'), 'e', LANG::T('Subject is required to send message using') . ' ' . $via . '.');
|
||||
}
|
||||
|
||||
// Replace placeholders in the message with actual values
|
||||
$message = str_replace('[[name]]', $c['fullname'], $message);
|
||||
$message = str_replace('[[user_name]]', $c['username'], $message);
|
||||
$message = str_replace('[[phone]]', $c['phonenumber'], $message);
|
||||
$message = str_replace('[[company_name]]', $config['CompanyName'], $message);
|
||||
if (strpos($message, '[[payment_link]]') !== false) {
|
||||
// token only valid for 1 day, for security reason
|
||||
$token = User::generateToken($c['id'], 1);
|
||||
if (!empty($token['token'])) {
|
||||
$tur = ORM::for_table('tbl_user_recharges')
|
||||
->where('customer_id', $c['id'])
|
||||
//->where('namebp', $package)
|
||||
->find_one();
|
||||
if ($tur) {
|
||||
$url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']);
|
||||
$message = str_replace('[[payment_link]]', $url, $message);
|
||||
}
|
||||
} else {
|
||||
$message = str_replace('[[payment_link]]', '', $message);
|
||||
if (empty($id_customer) || empty($message) || empty($via)) {
|
||||
r2(getUrl('message/send'), 'e', Lang::T('Customer, Message, and Channel are required'));
|
||||
}
|
||||
|
||||
$customer = ORM::for_table('tbl_customers')->find_one($id_customer);
|
||||
if (!$customer) {
|
||||
r2(getUrl('message/send'), 'e', Lang::T('Customer not found'));
|
||||
}
|
||||
|
||||
// Replace placeholders in message and subject
|
||||
$currentMessage = str_replace(
|
||||
['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
|
||||
[$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
|
||||
$message
|
||||
);
|
||||
|
||||
$currentSubject = str_replace(
|
||||
['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
|
||||
[$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
|
||||
$subject
|
||||
);
|
||||
|
||||
if (strpos($message, '[[payment_link]]') !== false) {
|
||||
$token = User::generateToken($customer['id'], 1);
|
||||
if (!empty($token['token'])) {
|
||||
$tur = ORM::for_table('tbl_user_recharges')
|
||||
->where('customer_id', $customer['id'])
|
||||
->find_one();
|
||||
if ($tur) {
|
||||
$url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']);
|
||||
$currentMessage = str_replace('[[payment_link]]', $url, $currentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Send the message
|
||||
if ($via == 'sms' || $via == 'both') {
|
||||
$smsSent = Message::sendSMS($c['phonenumber'], $message);
|
||||
}
|
||||
|
||||
if ($via == 'wa' || $via == 'both') {
|
||||
$waSent = Message::sendWhatsapp($c['phonenumber'], $message);
|
||||
}
|
||||
|
||||
if (isset($smsSent) || isset($waSent)) {
|
||||
r2(getUrl('message/send'), 's', Lang::T('Message Sent Successfully'));
|
||||
} else {
|
||||
r2(getUrl('message/send'), 'e', Lang::T('Failed to send message'));
|
||||
$currentMessage = str_replace('[[payment_link]]', '', $currentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Send the message through the selected channels
|
||||
$smsSent = $waSent = $emailSent = $inboxSent = false;
|
||||
|
||||
if ($via === 'sms' || $via === 'both' || $via === 'all') {
|
||||
$smsSent = Message::sendSMS($customer['phonenumber'], $currentSubject);
|
||||
}
|
||||
|
||||
if ($via === 'wa' || $via === 'both' || $via === 'all') {
|
||||
$waSent = Message::sendWhatsapp($customer['phonenumber'], $currentSubject);
|
||||
}
|
||||
|
||||
if ($via === 'email' || $via === 'all') {
|
||||
$emailSent = Message::sendEmail($customer['email'], $currentSubject, $currentMessage);
|
||||
}
|
||||
|
||||
if ($via === 'inbox' || $via === 'all') {
|
||||
$inboxSent = Message::addToInbox($customer['id'], $currentSubject, $currentMessage, 'Admin');
|
||||
}
|
||||
|
||||
// Check if any message was sent successfully
|
||||
if ($smsSent || $waSent || $emailSent || $inboxSent) {
|
||||
r2(getUrl('message/send'), 's', Lang::T('Message Sent Successfully'));
|
||||
} else {
|
||||
r2(getUrl('message/send'), 'e', Lang::T('Failed to send message'));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'send_bulk':
|
||||
@ -133,11 +156,16 @@ EOT;
|
||||
$batch = $_REQUEST['batch'] ?? 100;
|
||||
$page = $_REQUEST['page'] ?? 0;
|
||||
$router = $_REQUEST['router'] ?? null;
|
||||
$test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on' ? true : false;
|
||||
$test = isset($_REQUEST['test']) && $_REQUEST['test'] === 'on';
|
||||
$service = $_REQUEST['service'] ?? '';
|
||||
$subject = $_REQUEST['subject'] ?? '';
|
||||
|
||||
if (empty($group) || empty($message) || empty($via) || empty($service)) {
|
||||
die(json_encode(['status' => 'error', 'message' => 'All fields are required']));
|
||||
die(json_encode(['status' => 'error', 'message' => LANG::T('All fields are required')]));
|
||||
}
|
||||
|
||||
if (in_array($via, ['all', 'email', 'inbox']) && empty($subject)) {
|
||||
die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.']));
|
||||
}
|
||||
|
||||
// Get batch of customers based on group
|
||||
@ -153,7 +181,7 @@ EOT;
|
||||
default:
|
||||
$router = ORM::for_table('tbl_routers')->find_one($router);
|
||||
if (!$router) {
|
||||
die(json_encode(['status' => 'error', 'message' => 'Invalid router']));
|
||||
die(json_encode(['status' => 'error', 'message' => LANG::T('Invalid router')]));
|
||||
}
|
||||
$routerName = $router->name;
|
||||
break;
|
||||
@ -200,6 +228,9 @@ EOT;
|
||||
['tbl_customers.phonenumber', 'phonenumber'],
|
||||
['tbl_user_recharges.customer_id', 'customer_id'],
|
||||
['tbl_customers.fullname', 'fullname'],
|
||||
['tbl_customers.username', 'username'],
|
||||
['tbl_customers.email', 'email'],
|
||||
['tbl_customers.service_type', 'service_type'],
|
||||
]);
|
||||
$customers = $query->find_array();
|
||||
} else {
|
||||
@ -287,7 +318,13 @@ EOT;
|
||||
$totalSMSFailed = 0;
|
||||
$totalWhatsappSent = 0;
|
||||
$totalWhatsappFailed = 0;
|
||||
$totalEmailSent = 0;
|
||||
$totalEmailFailed = 0;
|
||||
$totalInboxSent = 0;
|
||||
$totalInboxFailed = 0;
|
||||
$batchStatus = [];
|
||||
//$subject = $config['CompanyName'] . ' ' . Lang::T('Notification Message');
|
||||
$form = 'Admin';
|
||||
|
||||
foreach ($customers as $customer) {
|
||||
$currentMessage = str_replace(
|
||||
@ -296,6 +333,12 @@ EOT;
|
||||
$message
|
||||
);
|
||||
|
||||
$currentSubject = str_replace(
|
||||
['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
|
||||
[$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
|
||||
$subject
|
||||
);
|
||||
|
||||
$phoneNumber = preg_replace('/\D/', '', $customer['phonenumber']);
|
||||
|
||||
if (empty($phoneNumber)) {
|
||||
@ -310,14 +353,14 @@ EOT;
|
||||
if ($test) {
|
||||
$batchStatus[] = [
|
||||
'name' => $customer['fullname'],
|
||||
'phone' => $customer['phonenumber'],
|
||||
'channel' => 'Test Channel',
|
||||
'status' => 'Test Mode',
|
||||
'message' => $currentMessage,
|
||||
'service' => $service,
|
||||
'router' => $routerName,
|
||||
];
|
||||
} else {
|
||||
if ($via == 'sms' || $via == 'both') {
|
||||
if ($via === 'sms' || $via === 'both' || $via === 'all') {
|
||||
if (Message::sendSMS($customer['phonenumber'], $currentMessage)) {
|
||||
$totalSMSSent++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Sent', 'message' => $currentMessage];
|
||||
@ -327,13 +370,33 @@ EOT;
|
||||
}
|
||||
}
|
||||
|
||||
if ($via == 'wa' || $via == 'both') {
|
||||
if ($via === 'wa' || $via == 'both' || $via === 'all') {
|
||||
if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) {
|
||||
$totalWhatsappSent++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage];
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage];
|
||||
} else {
|
||||
$totalWhatsappFailed++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage];
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage];
|
||||
}
|
||||
}
|
||||
|
||||
if ($via === 'email' || $via === 'all') {
|
||||
if (Message::sendEmail($customer['email'], $currentSubject, $currentMessage)) {
|
||||
$totalEmailSent++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Sent', 'message' => $currentMessage];
|
||||
} else {
|
||||
$totalEmailFailed++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['email'], 'status' => 'Email Failed', 'message' => $currentMessage];
|
||||
}
|
||||
}
|
||||
|
||||
if ($via === 'inbox' || $via === 'all') {
|
||||
if (Message::addToInbox($customer['customer_id'], $currentSubject, $currentMessage, $form)) {
|
||||
$totalInboxSent++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Sent', 'message' => $currentMessage];
|
||||
} else {
|
||||
$totalInboxFailed++;
|
||||
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => 'Inbox', 'status' => 'Inbox Message Failed', 'message' => $currentMessage];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -348,8 +411,8 @@ EOT;
|
||||
'page' => $page + 1,
|
||||
'batchStatus' => $batchStatus,
|
||||
'message' => $currentMessage,
|
||||
'totalSent' => $totalSMSSent + $totalWhatsappSent,
|
||||
'totalFailed' => $totalSMSFailed + $totalWhatsappFailed,
|
||||
'totalSent' => $totalSMSSent + $totalWhatsappSent + $totalEmailSent + $totalInboxSent,
|
||||
'totalFailed' => $totalSMSFailed + $totalWhatsappFailed + $totalEmailFailed + $totalInboxFailed,
|
||||
'hasMore' => $hasMore,
|
||||
'service' => $service,
|
||||
'router' => $routerName,
|
||||
@ -365,16 +428,20 @@ EOT;
|
||||
// Get the posted data
|
||||
$customerIds = $_POST['customer_ids'] ?? [];
|
||||
$via = $_POST['message_type'] ?? '';
|
||||
$subject = $_POST['subject'] ?? '';
|
||||
$message = isset($_POST['message']) ? trim($_POST['message']) : '';
|
||||
if (empty($customerIds) || empty($message) || empty($via)) {
|
||||
echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid customer IDs, Message, or Message Type.')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($via === 'all' || $via === 'email' || $via === 'inbox' && empty($subject)) {
|
||||
die(json_encode(['status' => 'error', 'message' => LANG::T('Subject is required to send message using') . ' ' . $via . '.']));
|
||||
}
|
||||
|
||||
// Prepare to send messages
|
||||
$sentCount = 0;
|
||||
$failedCount = 0;
|
||||
$subject = Lang::T('Notification Message');
|
||||
$form = 'Admin';
|
||||
|
||||
foreach ($customerIds as $customerId) {
|
||||
|
@ -371,7 +371,7 @@ switch ($action) {
|
||||
$d->trx_invoice = $result;
|
||||
$d->status = 2;
|
||||
$d->save();
|
||||
r2(getUrl('order/view/$trx_id'), 's', Lang::T("Success to send package"));
|
||||
r2(getUrl("order/view/$trx_id"), 's', Lang::T("Success to send package"));
|
||||
} else {
|
||||
$errorMessage = "Send Package with Balance Failed\n\n#u$user[username] #send \n" . $plan['name_plan'] .
|
||||
"\nRouter: " . $router_name .
|
||||
|
@ -279,6 +279,8 @@ switch ($action) {
|
||||
$ui->assign('wlogo', $width);
|
||||
$ui->assign('hlogo', $height);
|
||||
}
|
||||
|
||||
$ui->assign('public_url', getUrl("voucher/invoice/$id/".md5($id. $db_pass)));
|
||||
$ui->assign('logo', $logo);
|
||||
$ui->assign('_title', 'View Invoice');
|
||||
$ui->display('admin/plan/invoice.tpl');
|
||||
|
@ -58,7 +58,7 @@ switch ($action) {
|
||||
$w = [];
|
||||
$v = [];
|
||||
foreach ($mts as $mt) {
|
||||
$w[] ='method';
|
||||
$w[] = 'method';
|
||||
$v[] = "$mt - %";
|
||||
}
|
||||
$query->where_likes($w, $v);
|
||||
@ -91,7 +91,7 @@ switch ($action) {
|
||||
$w = [];
|
||||
$v = [];
|
||||
foreach ($mts as $mt) {
|
||||
$w[] ='method';
|
||||
$w[] = 'method';
|
||||
$v[] = "$mt - %";
|
||||
}
|
||||
$query->where_likes($w, $v);
|
||||
@ -161,7 +161,7 @@ switch ($action) {
|
||||
$w = [];
|
||||
$v = [];
|
||||
foreach ($mts as $mt) {
|
||||
$w[] ='method';
|
||||
$w[] = 'method';
|
||||
$v[] = "$mt - %";
|
||||
}
|
||||
$query->where_likes($w, $v);
|
||||
@ -246,7 +246,7 @@ switch ($action) {
|
||||
$total += $v;
|
||||
$array['data'][] = $v;
|
||||
}
|
||||
if($total>0){
|
||||
if ($total > 0) {
|
||||
$result['datas'][] = $array;
|
||||
}
|
||||
}
|
||||
@ -258,18 +258,29 @@ switch ($action) {
|
||||
die();
|
||||
case 'by-date':
|
||||
case 'activation':
|
||||
$q = (_post('q') ? _post('q') : _get('q'));
|
||||
$q = trim(_post('q') ?: _get('q'));
|
||||
$keep = _post('keep');
|
||||
|
||||
if (!empty($keep)) {
|
||||
ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))");
|
||||
r2(getUrl('logs/list/'), 's', "Delete logs older than $keep days");
|
||||
ORM::raw_execute("DELETE FROM tbl_transactions WHERE date < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))", [$keep]);
|
||||
r2(getUrl('reports/activation/'), 's', "Deleted logs older than $keep days");
|
||||
}
|
||||
if ($q != '') {
|
||||
$query = ORM::for_table('tbl_transactions')->where_like('invoice', '%' . $q . '%')->order_by_desc('id');
|
||||
|
||||
$query = ORM::for_table('tbl_transactions')
|
||||
->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
|
||||
->select('tbl_transactions.*')
|
||||
->select('tbl_customers.fullname', 'fullname')
|
||||
->order_by_desc('tbl_transactions.id');
|
||||
|
||||
if ($q !== '') {
|
||||
$query->where_like('invoice', "%$q%");
|
||||
}
|
||||
|
||||
try {
|
||||
$d = Paginator::findMany($query, ['q' => $q]);
|
||||
} else {
|
||||
$query = ORM::for_table('tbl_transactions')->order_by_desc('id');
|
||||
$d = Paginator::findMany($query);
|
||||
} catch (Exception $e) {
|
||||
r2(getUrl('reports/activation/'), 'e', 'Database query failed: ' . $e->getMessage());
|
||||
$d = [];
|
||||
}
|
||||
|
||||
$ui->assign('activation', $d);
|
||||
@ -291,6 +302,10 @@ switch ($action) {
|
||||
$stype = _post('stype');
|
||||
|
||||
$d = ORM::for_table('tbl_transactions');
|
||||
$d->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
|
||||
->select('tbl_transactions.*')
|
||||
->select('tbl_customers.fullname', 'fullname')
|
||||
->order_by_desc('tbl_transactions.id');
|
||||
if ($stype != '') {
|
||||
$d->where('type', $stype);
|
||||
}
|
||||
@ -298,7 +313,7 @@ switch ($action) {
|
||||
$d->where_gte('recharged_on', $fdate);
|
||||
$d->where_lte('recharged_on', $tdate);
|
||||
$d->order_by_desc('id');
|
||||
$x = $d->find_many();
|
||||
$x = $d->find_many();
|
||||
|
||||
$dr = ORM::for_table('tbl_transactions');
|
||||
if ($stype != '') {
|
||||
@ -348,7 +363,10 @@ switch ($action) {
|
||||
$query = ORM::for_table('tbl_transactions')
|
||||
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) >= " . strtotime("$sd $ts"))
|
||||
->whereRaw("UNIX_TIMESTAMP(CONCAT(`recharged_on`,' ',`recharged_time`)) <= " . strtotime("$ed $te"))
|
||||
->order_by_desc('id');
|
||||
->left_outer_join('tbl_customers', 'tbl_transactions.username = tbl_customers.username')
|
||||
->select('tbl_transactions.*')
|
||||
->select('tbl_customers.fullname', 'fullname')
|
||||
->order_by_desc('tbl_transactions.id');
|
||||
if (count($tps) > 0) {
|
||||
$query->where_in('type', $tps);
|
||||
}
|
||||
@ -356,7 +374,7 @@ switch ($action) {
|
||||
$w = [];
|
||||
$v = [];
|
||||
foreach ($mts as $mt) {
|
||||
$w[] ='method';
|
||||
$w[] = 'method';
|
||||
$v[] = "$mt - %";
|
||||
}
|
||||
$query->where_likes($w, $v);
|
||||
|
@ -146,7 +146,8 @@ switch ($action) {
|
||||
$r = ORM::for_table('tbl_routers')->find_many();
|
||||
$ui->assign('r', $r);
|
||||
if (function_exists("shell_exec")) {
|
||||
$php = trim(shell_exec('which php'));
|
||||
$which = stripos(php_uname('s'), "Win") === 0 ? 'where' : 'which';
|
||||
$php = trim(shell_exec("$which php"));
|
||||
if (empty($php)) {
|
||||
$php = 'php';
|
||||
}
|
||||
@ -248,6 +249,75 @@ switch ($action) {
|
||||
$_POST['hide_pg'] = _post('hide_pg', 'no');
|
||||
$_POST['hide_aui'] = _post('hide_aui', 'no');
|
||||
|
||||
// Login page post
|
||||
$login_page_title = _post('login_page_head');
|
||||
$login_page_description = _post('login_page_description');
|
||||
$login_Page_template = _post('login_Page_template');
|
||||
$login_page_type = _post('login_page_type');
|
||||
$csrf_token = _post('csrf_token');
|
||||
|
||||
if (!Csrf::check($csrf_token)) {
|
||||
r2(getUrl('settings/app'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
|
||||
}
|
||||
|
||||
if ($login_page_type == 'custom' && (empty($login_Page_template) || empty($login_page_title) || empty($login_page_description))) {
|
||||
r2(getUrl('settings/app'), 'e', 'Please fill all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen($login_page_title) > 25) {
|
||||
r2(getUrl('settings/app'), 'e', 'Login page title must not exceed 25 characters');
|
||||
return;
|
||||
}
|
||||
if (strlen($login_page_description) > 100) {
|
||||
r2(getUrl('settings/app'), 'e', 'Login page description must not exceed 50 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
$image_paths = [];
|
||||
$allowed_types = ['image/jpeg', 'image/png'];
|
||||
|
||||
if ($_FILES['login_page_favicon']['name'] != '') {
|
||||
$favicon_type = $_FILES['login_page_favicon']['type'];
|
||||
if (in_array($favicon_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_favicon']['name'])) {
|
||||
$extension = pathinfo($_FILES['login_page_favicon']['name'], PATHINFO_EXTENSION);
|
||||
$favicon_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('favicon_') . '.' . $extension;
|
||||
File::resizeCropImage($_FILES['login_page_favicon']['tmp_name'], $favicon_path, 16, 16, 100);
|
||||
$_POST['login_page_favicon'] = basename($favicon_path); // Save dynamic file name
|
||||
if (file_exists($_FILES['login_page_favicon']['tmp_name']))
|
||||
unlink($_FILES['login_page_favicon']['tmp_name']);
|
||||
} else {
|
||||
r2(getUrl('settings/app'), 'e', 'Favicon must be a JPG, JPEG, or PNG image.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($_FILES['login_page_wallpaper']['name'] != '') {
|
||||
$wallpaper_type = $_FILES['login_page_wallpaper']['type'];
|
||||
if (in_array($wallpaper_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_wallpaper']['name'])) {
|
||||
$extension = pathinfo($_FILES['login_page_wallpaper']['name'], PATHINFO_EXTENSION);
|
||||
$wallpaper_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('wallpaper_') . '.' . $extension;
|
||||
File::resizeCropImage($_FILES['login_page_wallpaper']['tmp_name'], $wallpaper_path, 1920, 1080, 100);
|
||||
$_POST['login_page_wallpaper'] = basename($wallpaper_path); // Save dynamic file name
|
||||
if (file_exists($_FILES['login_page_wallpaper']['tmp_name']))
|
||||
unlink($_FILES['login_page_wallpaper']['tmp_name']);
|
||||
} else {
|
||||
r2(getUrl('settings/app'), 'e', 'Wallpaper must be a JPG, JPEG, or PNG image.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($_FILES['login_page_logo']['name'] != '') {
|
||||
$logo_type = $_FILES['login_page_logo']['type'];
|
||||
if (in_array($logo_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_logo']['name'])) {
|
||||
$extension = pathinfo($_FILES['login_page_logo']['name'], PATHINFO_EXTENSION);
|
||||
$logo_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('logo_') . '.' . $extension;
|
||||
File::resizeCropImage($_FILES['login_page_logo']['tmp_name'], $logo_path, 300, 60, 100);
|
||||
$_POST['login_page_logo'] = basename($logo_path); // Save dynamic file name
|
||||
if (file_exists($_FILES['login_page_logo']['tmp_name']))
|
||||
unlink($_FILES['login_page_logo']['tmp_name']);
|
||||
} else {
|
||||
r2(getUrl('settings/app'), 'e', 'Logo must be a JPG, JPEG, or PNG image.');
|
||||
}
|
||||
}
|
||||
foreach ($_POST as $key => $value) {
|
||||
$d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one();
|
||||
if ($d) {
|
||||
@ -266,105 +336,6 @@ switch ($action) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'login-page-post':
|
||||
|
||||
if ($_app_stage == 'Demo') {
|
||||
r2(getUrl('settings/app'), 'e', 'You cannot perform this action in Demo mode');
|
||||
}
|
||||
// Login page post
|
||||
$login_page_title = _post('login_page_head');
|
||||
$login_page_description = _post('login_page_description');
|
||||
$login_Page_template = _post('login_Page_template');
|
||||
$login_page_type = _post('login_page_type');
|
||||
$csrf_token = _post('csrf_token');
|
||||
|
||||
if (!Csrf::check($csrf_token)) {
|
||||
r2(getUrl('settings/app'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
|
||||
}
|
||||
|
||||
if ($login_page_type == 'custom' && (empty($login_Page_template) || empty($login_page_title) || empty($login_page_description))) {
|
||||
r2(getUrl('settings/app'), 'e', 'Please fill all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen($login_page_title) > 25) {
|
||||
r2(getUrl('settings/app'), 'e', 'Login page title must not exceed 25 characters');
|
||||
return;
|
||||
}
|
||||
if (strlen($login_page_description) > 100) {
|
||||
r2(getUrl('settings/app'), 'e', 'Login page description must not exceed 50 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = [
|
||||
'login_page_head' => $login_page_title,
|
||||
'login_page_description' => $login_page_description,
|
||||
'login_Page_template' => $login_Page_template,
|
||||
'login_page_type' => $login_page_type,
|
||||
];
|
||||
|
||||
$image_paths = [];
|
||||
$allowed_types = ['image/jpeg', 'image/png'];
|
||||
|
||||
if ($_FILES['login_page_favicon']['name'] != '') {
|
||||
$favicon_type = $_FILES['login_page_favicon']['type'];
|
||||
if (in_array($favicon_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_favicon']['name'])) {
|
||||
$extension = pathinfo($_FILES['login_page_favicon']['name'], PATHINFO_EXTENSION);
|
||||
$favicon_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('favicon_') . '.' . $extension;
|
||||
File::resizeCropImage($_FILES['login_page_favicon']['tmp_name'], $favicon_path, 16, 16, 100);
|
||||
$settings['login_page_favicon'] = basename($favicon_path); // Save dynamic file name
|
||||
if (file_exists($_FILES['login_page_favicon']['tmp_name']))
|
||||
unlink($_FILES['login_page_favicon']['tmp_name']);
|
||||
} else {
|
||||
r2(getUrl('settings/app'), 'e', 'Favicon must be a JPG, JPEG, or PNG image.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($_FILES['login_page_wallpaper']['name'] != '') {
|
||||
$wallpaper_type = $_FILES['login_page_wallpaper']['type'];
|
||||
if (in_array($wallpaper_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_wallpaper']['name'])) {
|
||||
$extension = pathinfo($_FILES['login_page_wallpaper']['name'], PATHINFO_EXTENSION);
|
||||
$wallpaper_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('wallpaper_') . '.' . $extension;
|
||||
File::resizeCropImage($_FILES['login_page_wallpaper']['tmp_name'], $wallpaper_path, 1920, 1080, 100);
|
||||
$settings['login_page_wallpaper'] = basename($wallpaper_path); // Save dynamic file name
|
||||
if (file_exists($_FILES['login_page_wallpaper']['tmp_name']))
|
||||
unlink($_FILES['login_page_wallpaper']['tmp_name']);
|
||||
} else {
|
||||
r2(getUrl('settings/app'), 'e', 'Wallpaper must be a JPG, JPEG, or PNG image.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($_FILES['login_page_logo']['name'] != '') {
|
||||
$logo_type = $_FILES['login_page_logo']['type'];
|
||||
if (in_array($logo_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_logo']['name'])) {
|
||||
$extension = pathinfo($_FILES['login_page_logo']['name'], PATHINFO_EXTENSION);
|
||||
$logo_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('logo_') . '.' . $extension;
|
||||
File::resizeCropImage($_FILES['login_page_logo']['tmp_name'], $logo_path, 300, 60, 100);
|
||||
$settings['login_page_logo'] = basename($logo_path); // Save dynamic file name
|
||||
if (file_exists($_FILES['login_page_logo']['tmp_name']))
|
||||
unlink($_FILES['login_page_logo']['tmp_name']);
|
||||
} else {
|
||||
r2(getUrl('settings/app'), 'e', 'Logo must be a JPG, JPEG, or PNG image.');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one();
|
||||
if ($d) {
|
||||
$d->value = $value;
|
||||
$d->save();
|
||||
} else {
|
||||
$d = ORM::for_table('tbl_appconfig')->create();
|
||||
$d->setting = $key;
|
||||
$d->value = $value;
|
||||
$d->save();
|
||||
}
|
||||
}
|
||||
|
||||
_log('[' . $admin['username'] . ']: ' . Lang::T('Login Page Settings Saved Successfully'), $admin['user_type'], $admin['id']);
|
||||
r2(getUrl('settings/app'), 's', Lang::T('Login Page Settings Saved Successfully'));
|
||||
break;
|
||||
|
||||
case 'localisation':
|
||||
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
|
||||
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
|
||||
@ -590,7 +561,7 @@ switch ($action) {
|
||||
}
|
||||
//allow see himself
|
||||
if ($admin['id'] == $id) {
|
||||
$d = ORM::for_table('tbl_users')->where('id', $id)->find_array($id)[0];
|
||||
$d = ORM::for_table('tbl_users')->where('id', $id)->find_array()[0];
|
||||
} else {
|
||||
if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
|
||||
// Super Admin can see anyone
|
||||
|
@ -4,11 +4,47 @@
|
||||
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
|
||||
* by https://t.me/ibnux
|
||||
**/
|
||||
_auth();
|
||||
|
||||
$ui->assign('_title', Lang::T('Voucher'));
|
||||
$ui->assign('_system_menu', 'voucher');
|
||||
|
||||
$action = $routes['1'];
|
||||
if(!_auth(false)){
|
||||
if($action== 'invoice'){
|
||||
$id = $routes[2];
|
||||
$sign = $routes[3];
|
||||
if($sign != md5($id. $db_pass)) {
|
||||
die("beda");
|
||||
}
|
||||
if (empty($id)) {
|
||||
$in = ORM::for_table('tbl_transactions')->order_by_desc('id')->find_one();
|
||||
} else {
|
||||
$in = ORM::for_table('tbl_transactions')->where('id', $id)->find_one();
|
||||
}
|
||||
if ($in) {
|
||||
Package::createInvoice($in);
|
||||
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
|
||||
$logo = '';
|
||||
if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) {
|
||||
$logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png';
|
||||
$imgsize = getimagesize($logo);
|
||||
$width = $imgsize[0];
|
||||
$height = $imgsize[1];
|
||||
$ui->assign('wlogo', $width);
|
||||
$ui->assign('hlogo', $height);
|
||||
}
|
||||
$ui->assign('public_url', getUrl("voucher/invoice/$id/".md5($id. $db_pass)));
|
||||
$ui->assign('logo', $logo);
|
||||
$ui->display('customer/invoice-customer.tpl');
|
||||
die();
|
||||
} else {
|
||||
r2(getUrl('voucher/list-activated'), 'e', Lang::T('Not Found'));
|
||||
}
|
||||
}else{
|
||||
r2(getUrl('login'));
|
||||
}
|
||||
}
|
||||
|
||||
$user = User::_info();
|
||||
$ui->assign('_user', $user);
|
||||
|
||||
@ -74,6 +110,7 @@ switch ($action) {
|
||||
$ui->assign('wlogo', $width);
|
||||
$ui->assign('hlogo', $height);
|
||||
}
|
||||
$ui->assign('public_url', getUrl("voucher/invoice/$id/".md5($id. $db_pass)));
|
||||
$ui->assign('logo', $logo);
|
||||
$ui->display('customer/invoice-customer.tpl');
|
||||
} else {
|
||||
|
@ -75,13 +75,7 @@ foreach ($d as $ds) {
|
||||
if ($_app_stage != 'demo') {
|
||||
if (file_exists($dvc)) {
|
||||
require_once $dvc;
|
||||
try {
|
||||
(new $p['device'])->remove_customer($c, $p);
|
||||
} catch (Throwable $e) {
|
||||
_log($e->getMessage());
|
||||
sendTelegram($e->getMessage());
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
(new $p['device'])->remove_customer($c, $p);
|
||||
} else {
|
||||
throw new Exception("Cron error: Devices " . $p['device'] . "not found, cannot disconnect ".$c['username']."\n");
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ foreach ($d as $ds) {
|
||||
} else {
|
||||
$price = $p['price'];
|
||||
}
|
||||
if ($ds['expiration'] == $day7 && $config['notification_reminder_7day'] !== 'no') {
|
||||
if ($ds['expiration'] == $day7 && $config['notification_reminder_7days'] !== 'no') {
|
||||
try {
|
||||
echo Message::sendPackageNotification(
|
||||
$c,
|
||||
@ -61,7 +61,7 @@ foreach ($d as $ds) {
|
||||
} catch (Exception $e) {
|
||||
sendTelegram("Cron Reminder failed to send 7-day reminder to " . $ds['username'] . " Error: " . $e->getMessage());
|
||||
}
|
||||
} else if ($ds['expiration'] == $day3 && $config['notification_reminder_3day'] !== 'no') {
|
||||
} else if ($ds['expiration'] == $day3 && $config['notification_reminder_3days'] !== 'no') {
|
||||
try {
|
||||
echo Message::sendPackageNotification(
|
||||
$c,
|
||||
|
@ -244,7 +244,7 @@ class MikrotikPppoe
|
||||
|
||||
function add_pool($pool){
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$mikrotik = $this->info($pool['routers']);
|
||||
@ -259,7 +259,7 @@ class MikrotikPppoe
|
||||
|
||||
function update_pool($old_pool, $new_pool){
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$mikrotik = $this->info($new_pool['routers']);
|
||||
@ -284,7 +284,7 @@ class MikrotikPppoe
|
||||
|
||||
function remove_pool($pool){
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$mikrotik = $this->info($pool['routers']);
|
||||
@ -329,7 +329,7 @@ class MikrotikPppoe
|
||||
function getClient($ip, $user, $pass)
|
||||
{
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$iport = explode(":", $ip);
|
||||
@ -339,7 +339,7 @@ class MikrotikPppoe
|
||||
function removePpoeUser($client, $username)
|
||||
{
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$printRequest = new RouterOS\Request('/ppp/secret/print');
|
||||
@ -376,7 +376,7 @@ class MikrotikPppoe
|
||||
function removePpoeActive($client, $username)
|
||||
{
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$onlineRequest = new RouterOS\Request('/ppp/active/print');
|
||||
@ -392,7 +392,7 @@ class MikrotikPppoe
|
||||
function getIpHotspotUser($client, $username)
|
||||
{
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$printRequest = new RouterOS\Request(
|
||||
@ -405,7 +405,7 @@ class MikrotikPppoe
|
||||
function addIpToAddressList($client, $ip, $listName, $comment = '')
|
||||
{
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$addRequest = new RouterOS\Request('/ip/firewall/address-list/add');
|
||||
@ -420,7 +420,7 @@ class MikrotikPppoe
|
||||
function removeIpFromAddressList($client, $ip)
|
||||
{
|
||||
global $_app_stage;
|
||||
if ($_app_stage == 'demo') {
|
||||
if ($_app_stage == 'Demo') {
|
||||
return null;
|
||||
}
|
||||
$printRequest = new RouterOS\Request(
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,210 +1,211 @@
|
||||
{
|
||||
"2023.8.9": [
|
||||
"ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;",
|
||||
"CREATE TABLE `tbl_customers_meta` (`id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,`meta_key` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `meta_value` longtext COLLATE utf8mb4_general_ci) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
|
||||
"ALTER TABLE `tbl_customers_meta` ADD PRIMARY KEY (`id`);",
|
||||
"ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;"
|
||||
],
|
||||
"2023.8.14": [
|
||||
"ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;"
|
||||
],
|
||||
"2023.8.23": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
|
||||
],
|
||||
"2023.8.28": [
|
||||
"ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;",
|
||||
"ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;"
|
||||
],
|
||||
"2023.9.5": [
|
||||
"DROP TABLE `tbl_language`;",
|
||||
"ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;"
|
||||
],
|
||||
"2023.9.27": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2023.9.28": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2023.10.1": [
|
||||
"ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; "
|
||||
],
|
||||
"2023.10.24": [
|
||||
"ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;"
|
||||
],
|
||||
"2023.12.15": [
|
||||
"ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;"
|
||||
],
|
||||
"2024.1.11": [
|
||||
"ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;"
|
||||
],
|
||||
"2024.2.7": [
|
||||
"ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;",
|
||||
"ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;"
|
||||
],
|
||||
"2024.2.12": [
|
||||
"ALTER TABLE `tbl_users` CHANGE `user_type` `user_type` ENUM('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2024.2.15": [
|
||||
"ALTER TABLE `tbl_users` CHANGE `password` `password` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_users` ADD `phone` VARCHAR(32) NOT NULL DEFAULT '' AFTER `password`, ADD `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `phone`, ADD `city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kota' AFTER `email`, ADD `subdistrict` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kecamatan' AFTER `city`, ADD `ward` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kelurahan' AFTER `subdistrict`;"
|
||||
],
|
||||
"2024.2.16": [
|
||||
"ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;"
|
||||
],
|
||||
"2024.2.19": [
|
||||
"CREATE TABLE `tbl_customers_fields` (`id` INT PRIMARY KEY AUTO_INCREMENT, `customer_id` INT NOT NULL, `field_name` VARCHAR(255) NOT NULL, `field_value` VARCHAR(255) NOT NULL, FOREIGN KEY (customer_id) REFERENCES tbl_customers(id));"
|
||||
],
|
||||
"2024.2.20": [
|
||||
"ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;",
|
||||
"ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;"
|
||||
],
|
||||
"2024.2.20.1": [
|
||||
"DROP TABLE IF EXISTS `tbl_customers_meta`;"
|
||||
],
|
||||
"2024.2.23": [
|
||||
"ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;",
|
||||
"ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;"
|
||||
],
|
||||
"2024.3.3": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `validity_unit` `validity_unit` ENUM('Mins','Hrs','Days','Months','Period') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2024.3.12": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `allow_purchase` `prepaid` ENUM('yes','no') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'yes' COMMENT 'is prepaid';"
|
||||
],
|
||||
"2024.3.14": [
|
||||
"ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;"
|
||||
],
|
||||
"2024.3.19": [
|
||||
"ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;"
|
||||
],
|
||||
"2024.3.19.1": [
|
||||
"ALTER TABLE `tbl_customers` ADD `account_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' AFTER `coordinates`;"
|
||||
],
|
||||
"2024.3.19.2": [
|
||||
"ALTER TABLE `tbl_plans` ADD `plan_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' ;"
|
||||
],
|
||||
"2023.3.20": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
|
||||
],
|
||||
"2024.4.5": [
|
||||
"ALTER TABLE `tbl_payment_gateway` ADD `trx_invoice` VARCHAR(25) NOT NULL DEFAULT '' COMMENT 'from tbl_transactions' AFTER `paid_date`;"
|
||||
],
|
||||
"2024.5.17": [
|
||||
"ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;"
|
||||
],
|
||||
"2024.5.18": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `status` `status` ENUM('Active','Banned','Disabled','Inactive','Limited','Suspended') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active';"
|
||||
],
|
||||
"2024.5.20": [
|
||||
"ALTER TABLE `tbl_customers` ADD `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `address`, ADD `district` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `city`, ADD `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `district`, ADD `zip` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `state`;"
|
||||
],
|
||||
"2024.6.5": [
|
||||
"ALTER TABLE `tbl_plans` ADD `price_old` VARCHAR(40) NOT NULL DEFAULT '' AFTER `price`;",
|
||||
"ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;"
|
||||
],
|
||||
"2024.6.10": [
|
||||
"ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;"
|
||||
],
|
||||
"2024.6.11": [
|
||||
"ALTER TABLE `tbl_plans` ADD `plan_expired` INT NOT NULL DEFAULT '0' AFTER `pool`;",
|
||||
"ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;"
|
||||
],
|
||||
"2024.6.19": [
|
||||
"ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;"
|
||||
],
|
||||
"2024.6.21": [
|
||||
"ALTER TABLE `tbl_plans` ADD `on_login` TEXT NULL DEFAULT NULL AFTER `device`;",
|
||||
"ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;"
|
||||
],
|
||||
"2024.7.6": [
|
||||
"CREATE TABLE IF NOT EXISTS `rad_acct` ( `id` bigint NOT NULL, `acctsessionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `realm` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasid` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasportid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `nasporttype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `framedipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `macaddr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
|
||||
"ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);",
|
||||
"ALTER TABLE `rad_acct` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;"
|
||||
],
|
||||
"2024.7.24": [
|
||||
"ALTER TABLE `tbl_voucher` ADD `used_date` DATETIME NULL DEFAULT NULL AFTER `status`;",
|
||||
"UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;"
|
||||
],
|
||||
"2024.8.1": [
|
||||
"ALTER TABLE `tbl_payment_gateway` CHANGE `gateway_trx_id` `gateway_trx_id` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';"
|
||||
],
|
||||
"2024.8.2": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_customers_inbox` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `customer_id` int NOT NULL, `date_created` datetime NOT NULL, `date_read` datetime DEFAULT NULL, `subject` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `body` TEXT NULL DEFAULT NULL, `from` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'System' COMMENT 'System or Admin or Else',`admin_id` int NOT NULL DEFAULT '0' COMMENT 'other than admin is 0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2024.8.5": [
|
||||
"ALTER TABLE `tbl_customers` ADD `pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
|
||||
"ALTER TABLE `tbl_customers` ADD `pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `pppoe_password`;"
|
||||
],
|
||||
"2024.8.5.1": [
|
||||
"ALTER TABLE `tbl_routers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' AFTER `description`;",
|
||||
"ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;"
|
||||
],
|
||||
"2024.8.6": [
|
||||
"ALTER TABLE `rad_acct` ADD `acctinputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `framedipaddress`;",
|
||||
"ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;"
|
||||
],
|
||||
"2024.8.7": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `coordinates` `coordinates` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates';"
|
||||
],
|
||||
"2024.8.28": [
|
||||
"ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;",
|
||||
"ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;"
|
||||
],
|
||||
"2024.9.13": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2024.10.10": [
|
||||
"ALTER TABLE `tbl_users` ADD `login_token` VARCHAR(40) AFTER `last_login`;"
|
||||
],
|
||||
"2024.10.17": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_meta` ( `id` int UNSIGNED NOT NULL AUTO_INCREMENT, `tbl` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Table name', `tbl_id` int NOT NULL COMMENT 'table value id', `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, `value` mediumtext COLLATE utf8mb4_general_ci, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='This Table to add additional data for any table';"
|
||||
],
|
||||
"2024.10.30": [
|
||||
"ALTER TABLE `tbl_users` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png' AFTER `root`;",
|
||||
"ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;"
|
||||
],
|
||||
"2024.10.31": [
|
||||
"ALTER TABLE `tbl_customers` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg' AFTER `password`;"
|
||||
],
|
||||
"2024.12.5.1": [
|
||||
"ALTER TABLE `tbl_transactions` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;",
|
||||
"ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;"
|
||||
],
|
||||
"2024.12.16": [
|
||||
"CREATE TABLE `tbl_coupons` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(50) NOT NULL UNIQUE, `type` ENUM('fixed', 'percent') NOT NULL, `value` DECIMAL(10,2) NOT NULL, `description` TEXT NOT NULL, `max_usage` INT NOT NULL DEFAULT 1,`usage_count` INT NOT NULL DEFAULT 0,`status` ENUM('active', 'inactive') NOT NULL, `min_order_amount` DECIMAL(10,2) NOT NULL, `max_discount_amount` DECIMAL(10,2) NOT NULL, `start_date` DATE NOT NULL,`end_date` DATE NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);"
|
||||
],
|
||||
"2024.12.20": [
|
||||
"ALTER TABLE `tbl_voucher` ADD `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;"
|
||||
],
|
||||
"2025.1.23": [
|
||||
"ALTER TABLE `rad_acct` ADD `acctsessiontime` BIGINT(12) NOT NULL DEFAULT '0' AFTER `framedipaddress`;"
|
||||
],
|
||||
"2025.2.14": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_widgets` ( `id` int NOT NULL AUTO_INCREMENT, `orders` int NOT NULL DEFAULT '99', `position` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1. top 2. left 3. right 4. bottom',`enabled` tinyint(1) NOT NULL DEFAULT '1', `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `widget` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `content` text COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2025.2.17" : [
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `enabled`, `title`, `widget`, `content`) VALUES (1, 1, 1, 1, 'Top Widget', 'top_widget', ''),(2, 2, 1, 1, 'Default Info', 'default_info_row', ''),(3, 1, 2, 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),(4, 2, 2, 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),(5, 3, 2, 1, 'Voucher Stocks', 'voucher_stocks', ''),(6, 4, 2, 1, 'Customer Expired', 'customer_expired', ''),(7, 1, 3, 1, 'Cron Monitor', 'cron_monitor', ''),(8, 2, 3, 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),(9, 3, 3, 1, 'Info Payment Gateway', 'info_payment_gateway', ''),(10, 4, 3, 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(11, 5, 3, 1, 'Activity Log', 'activity_log', '');"
|
||||
],
|
||||
"2025.2.19" : [
|
||||
"ALTER TABLE `tbl_widgets` ADD `user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin' AFTER `position`;"
|
||||
],
|
||||
"2025.2.21" : [
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (60, 1, 2, 'Customer', 1, 'Account Info', 'account_info', ''),(61, 3, 1, 'Customer', 1, 'Active Internet Plan', 'active_internet_plan', ''),(62, 4, 1, 'Customer', 1, 'Balance Transfer', 'balance_transfer', ''),(63, 1, 1, 'Customer', 1, 'Unpaid Order', 'unpaid_order', ''),(64, 2, 1, 'Customer', 1, 'Announcement', 'announcement', ''),(65, 5, 1, 'Customer', 1, 'Recharge A Friend', 'recharge_a_friend', ''),(66, 2, 2, 'Customer', 1, 'Voucher Activation', 'voucher_activation', '');"
|
||||
],
|
||||
"2025.2.25" : [
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (30, 1, 1, 'Agent', 1, 'Top Widget', 'top_widget', ''), (31, 2, 1, 'Agent', 1, 'Default Info', 'default_info_row', ''), (32, 1, 2, 'Agent', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (33, 2, 2, 'Agent', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (34, 3, 2, 'Agent', 1, 'Voucher Stocks', 'voucher_stocks', ''), (35, 4, 2, 'Agent', 1, 'Customer Expired', 'customer_expired', ''), (36, 1, 3, 'Agent', 1, 'Cron Monitor', 'cron_monitor', ''), (37, 2, 3, 'Agent', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (38, 3, 3, 'Agent', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (39, 4, 3, 'Agent', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(40, 5, 3, 'Agent', 1, 'Activity Log', 'activity_log', '');",
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (41, 1, 1, 'Sales', 1, 'Top Widget', 'top_widget', ''), (42, 2, 1, 'Sales', 1, 'Default Info', 'default_info_row', ''), (43, 1, 2, 'Sales', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (44, 2, 2, 'Sales', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (45, 3, 2, 'Sales', 1, 'Voucher Stocks', 'voucher_stocks', ''), (46, 4, 2, 'Sales', 1, 'Customer Expired', 'customer_expired', ''), (47, 1, 3, 'Sales', 1, 'Cron Monitor', 'cron_monitor', ''), (48, 2, 3, 'Sales', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (49, 3, 3, 'Sales', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (50, 4, 3, 'Sales', 1, 'Graph Customers Insight', 'graph_customers_insight', ''), (51, 5, 3, 'Sales', 1, 'Activity Log', 'activity_log', '');"
|
||||
],
|
||||
"2025.3.5" : [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_message_logs` ( `id` SERIAL PRIMARY KEY, `message_type` VARCHAR(50), `recipient` VARCHAR(255), `message_content` TEXT, `status` VARCHAR(50), `error_message` TEXT, `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
]
|
||||
}
|
||||
"2023.8.9": [
|
||||
"ALTER TABLE `tbl_customers` ADD `balance` decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 'For Money Deposit' AFTER `email`;",
|
||||
"CREATE TABLE `tbl_customers_meta` (`id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,`meta_key` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `meta_value` longtext COLLATE utf8mb4_general_ci) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
|
||||
"ALTER TABLE `tbl_customers_meta` ADD PRIMARY KEY (`id`);",
|
||||
"ALTER TABLE `tbl_customers_meta` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;"
|
||||
],
|
||||
"2023.8.14": [
|
||||
"ALTER TABLE `tbl_customers` ADD `pppoe_password` varchar(45) NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_customers` ADD `auto_renewal` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Auto renewall using balance' AFTER `balance`;"
|
||||
],
|
||||
"2023.8.23": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
|
||||
],
|
||||
"2023.8.28": [
|
||||
"ALTER TABLE `tbl_user_recharges` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;",
|
||||
"ALTER TABLE `tbl_transactions` ADD `recharged_time` time NOT NULL DEFAULT '00:00:00' AFTER `recharged_on`;"
|
||||
],
|
||||
"2023.9.5": [
|
||||
"DROP TABLE `tbl_language`;",
|
||||
"ALTER TABLE `tbl_plans` ADD `pool_expired` varchar(40) NOT NULL DEFAULT '' AFTER `pool`;"
|
||||
],
|
||||
"2023.9.27": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance','Radius') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2023.9.28": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2023.10.1": [
|
||||
"ALTER TABLE `tbl_plans` ADD `is_radius` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '1 is radius' AFTER `routers`; "
|
||||
],
|
||||
"2023.10.24": [
|
||||
"ALTER TABLE `nas` ADD `routers` VARCHAR(32) NOT NULL DEFAULT '' AFTER `description`;"
|
||||
],
|
||||
"2023.12.15": [
|
||||
"ALTER TABLE `tbl_customers` ADD `service_type` ENUM('Hotspot','PPPoE','Others') DEFAULT 'Others' COMMENT 'For selecting user type' AFTER `balance`;"
|
||||
],
|
||||
"2024.1.11": [
|
||||
"ALTER TABLE `tbl_plans` ADD `allow_purchase` ENUM('yes','no') DEFAULT 'yes' COMMENT 'allow to show package in buy package page' AFTER `enabled`;"
|
||||
],
|
||||
"2024.2.7": [
|
||||
"ALTER TABLE `tbl_voucher` ADD `generated_by` INT NOT NULL DEFAULT '0' COMMENT 'id admin' AFTER `status`;",
|
||||
"ALTER TABLE `tbl_users` ADD `root` INT NOT NULL DEFAULT '0' COMMENT 'for sub account' AFTER `id`;"
|
||||
],
|
||||
"2024.2.12": [
|
||||
"ALTER TABLE `tbl_users` CHANGE `user_type` `user_type` ENUM('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2024.2.15": [
|
||||
"ALTER TABLE `tbl_users` CHANGE `password` `password` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_users` ADD `phone` VARCHAR(32) NOT NULL DEFAULT '' AFTER `password`, ADD `email` VARCHAR(128) NOT NULL DEFAULT '' AFTER `phone`, ADD `city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kota' AFTER `email`, ADD `subdistrict` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kecamatan' AFTER `city`, ADD `ward` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'kelurahan' AFTER `subdistrict`;"
|
||||
],
|
||||
"2024.2.16": [
|
||||
"ALTER TABLE `tbl_customers` ADD `created_by` INT NOT NULL DEFAULT '0' AFTER `auto_renewal`;"
|
||||
],
|
||||
"2024.2.19": [
|
||||
"CREATE TABLE `tbl_customers_fields` (`id` INT PRIMARY KEY AUTO_INCREMENT, `customer_id` INT NOT NULL, `field_name` VARCHAR(255) NOT NULL, `field_value` VARCHAR(255) NOT NULL, FOREIGN KEY (customer_id) REFERENCES tbl_customers(id));"
|
||||
],
|
||||
"2024.2.20": [
|
||||
"ALTER TABLE `tbl_plans` ADD `list_expired` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'address list' AFTER `pool_expired`;",
|
||||
"ALTER TABLE `tbl_bandwidth` ADD `burst` VARCHAR(128) NOT NULL DEFAULT '' AFTER `rate_up_unit`;"
|
||||
],
|
||||
"2024.2.20.1": ["DROP TABLE IF EXISTS `tbl_customers_meta`;"],
|
||||
"2024.2.23": [
|
||||
"ALTER TABLE `tbl_transactions` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;",
|
||||
"ALTER TABLE `tbl_user_recharges` ADD `admin_id` INT NOT NULL DEFAULT '1' AFTER `type`;"
|
||||
],
|
||||
"2024.3.3": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `validity_unit` `validity_unit` ENUM('Mins','Hrs','Days','Months','Period') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;"
|
||||
],
|
||||
"2024.3.12": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `allow_purchase` `prepaid` ENUM('yes','no') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'yes' COMMENT 'is prepaid';"
|
||||
],
|
||||
"2024.3.14": [
|
||||
"ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;"
|
||||
],
|
||||
"2024.3.19": [
|
||||
"ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;"
|
||||
],
|
||||
"2024.3.19.1": [
|
||||
"ALTER TABLE `tbl_customers` ADD `account_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' AFTER `coordinates`;"
|
||||
],
|
||||
"2024.3.19.2": [
|
||||
"ALTER TABLE `tbl_plans` ADD `plan_type` ENUM('Business', 'Personal') DEFAULT 'Personal' COMMENT 'For selecting account type' ;"
|
||||
],
|
||||
"2023.3.20": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `pppoe_password` `pppoe_password` VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login';"
|
||||
],
|
||||
"2024.4.5": [
|
||||
"ALTER TABLE `tbl_payment_gateway` ADD `trx_invoice` VARCHAR(25) NOT NULL DEFAULT '' COMMENT 'from tbl_transactions' AFTER `paid_date`;"
|
||||
],
|
||||
"2024.5.17": [
|
||||
"ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;"
|
||||
],
|
||||
"2024.5.18": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `status` `status` ENUM('Active','Banned','Disabled','Inactive','Limited','Suspended') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active';"
|
||||
],
|
||||
"2024.5.20": [
|
||||
"ALTER TABLE `tbl_customers` ADD `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `address`, ADD `district` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `city`, ADD `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `district`, ADD `zip` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci AFTER `state`;"
|
||||
],
|
||||
"2024.6.5": [
|
||||
"ALTER TABLE `tbl_plans` ADD `price_old` VARCHAR(40) NOT NULL DEFAULT '' AFTER `price`;",
|
||||
"ALTER TABLE `tbl_plans` ADD `device` VARCHAR(32) NOT NULL DEFAULT '' AFTER `plan_type`;"
|
||||
],
|
||||
"2024.6.10": [
|
||||
"ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;"
|
||||
],
|
||||
"2024.6.11": [
|
||||
"ALTER TABLE `tbl_plans` ADD `plan_expired` INT NOT NULL DEFAULT '0' AFTER `pool`;",
|
||||
"ALTER TABLE `tbl_plans` DROP `pool_expired`, DROP `list_expired`;"
|
||||
],
|
||||
"2024.6.19": [
|
||||
"ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;"
|
||||
],
|
||||
"2024.6.21": [
|
||||
"ALTER TABLE `tbl_plans` ADD `on_login` TEXT NULL DEFAULT NULL AFTER `device`;",
|
||||
"ALTER TABLE `tbl_plans` ADD `on_logout` TEXT NULL DEFAULT NULL AFTER `on_login`;"
|
||||
],
|
||||
"2024.7.6": [
|
||||
"CREATE TABLE IF NOT EXISTS `rad_acct` ( `id` bigint NOT NULL, `acctsessionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `realm` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasid` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nasportid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `nasporttype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `framedipaddress` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, `macaddr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;",
|
||||
"ALTER TABLE `rad_acct` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`), ADD KEY `framedipaddress` (`framedipaddress`), ADD KEY `acctsessionid` (`acctsessionid`), ADD KEY `nasipaddress` (`nasipaddress`);",
|
||||
"ALTER TABLE `rad_acct` MODIFY `id` bigint NOT NULL AUTO_INCREMENT;"
|
||||
],
|
||||
"2024.7.24": [
|
||||
"ALTER TABLE `tbl_voucher` ADD `used_date` DATETIME NULL DEFAULT NULL AFTER `status`;",
|
||||
"UPDATE `tbl_voucher` SET `used_date`=now() WHERE `status`=1;"
|
||||
],
|
||||
"2024.8.1": [
|
||||
"ALTER TABLE `tbl_payment_gateway` CHANGE `gateway_trx_id` `gateway_trx_id` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE `tbl_payment_gateway` CHANGE `pg_url_payment` `pg_url_payment` VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '';"
|
||||
],
|
||||
"2024.8.2": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_customers_inbox` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, `customer_id` int NOT NULL, `date_created` datetime NOT NULL, `date_read` datetime DEFAULT NULL, `subject` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `body` TEXT NULL DEFAULT NULL, `from` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'System' COMMENT 'System or Admin or Else',`admin_id` int NOT NULL DEFAULT '0' COMMENT 'other than admin is 0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2024.8.5": [
|
||||
"ALTER TABLE `tbl_customers` ADD `pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `password`;",
|
||||
"ALTER TABLE `tbl_customers` ADD `pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login' AFTER `pppoe_password`;"
|
||||
],
|
||||
"2024.8.5.1": [
|
||||
"ALTER TABLE `tbl_routers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '' AFTER `description`;",
|
||||
"ALTER TABLE `tbl_routers` ADD `coverage` VARCHAR(8) NOT NULL DEFAULT '0' AFTER `coordinates`;"
|
||||
],
|
||||
"2024.8.6": [
|
||||
"ALTER TABLE `rad_acct` ADD `acctinputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `framedipaddress`;",
|
||||
"ALTER TABLE `rad_acct` ADD `acctoutputoctets` BIGINT NOT NULL DEFAULT '0' AFTER `acctinputoctets`;"
|
||||
],
|
||||
"2024.8.7": [
|
||||
"ALTER TABLE `tbl_customers` CHANGE `coordinates` `coordinates` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Latitude and Longitude coordinates';"
|
||||
],
|
||||
"2024.8.28": [
|
||||
"ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;",
|
||||
"ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;"
|
||||
],
|
||||
"2024.9.13": [
|
||||
"ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';",
|
||||
"ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2024.10.10": [
|
||||
"ALTER TABLE `tbl_users` ADD `login_token` VARCHAR(40) AFTER `last_login`;"
|
||||
],
|
||||
"2024.10.17": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_meta` ( `id` int UNSIGNED NOT NULL AUTO_INCREMENT, `tbl` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Table name', `tbl_id` int NOT NULL COMMENT 'table value id', `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, `value` mediumtext COLLATE utf8mb4_general_ci, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='This Table to add additional data for any table';"
|
||||
],
|
||||
"2024.10.30": [
|
||||
"ALTER TABLE `tbl_users` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png' AFTER `root`;",
|
||||
"ALTER TABLE `tbl_users` ADD `data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data' AFTER `status`;"
|
||||
],
|
||||
"2024.10.31": [
|
||||
"ALTER TABLE `tbl_customers` ADD `photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg' AFTER `password`;"
|
||||
],
|
||||
"2024.12.5.1": [
|
||||
"ALTER TABLE `tbl_transactions` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;",
|
||||
"ALTER TABLE `tbl_payment_gateway` ADD `user_id` INT(11) NOT NULL DEFAULT 0 AFTER `username`;"
|
||||
],
|
||||
"2024.12.16": [
|
||||
"CREATE TABLE `tbl_coupons` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(50) NOT NULL UNIQUE, `type` ENUM('fixed', 'percent') NOT NULL, `value` DECIMAL(10,2) NOT NULL, `description` TEXT NOT NULL, `max_usage` INT NOT NULL DEFAULT 1,`usage_count` INT NOT NULL DEFAULT 0,`status` ENUM('active', 'inactive') NOT NULL, `min_order_amount` DECIMAL(10,2) NOT NULL, `max_discount_amount` DECIMAL(10,2) NOT NULL, `start_date` DATE NOT NULL,`end_date` DATE NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);"
|
||||
],
|
||||
"2024.12.20": [
|
||||
"ALTER TABLE `tbl_voucher` ADD `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;"
|
||||
],
|
||||
"2025.1.23": [
|
||||
"ALTER TABLE `rad_acct` ADD `acctsessiontime` BIGINT(12) NOT NULL DEFAULT '0' AFTER `framedipaddress`;"
|
||||
],
|
||||
"2025.2.14": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_widgets` ( `id` int NOT NULL AUTO_INCREMENT, `orders` int NOT NULL DEFAULT '99', `position` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1. top 2. left 3. right 4. bottom',`enabled` tinyint(1) NOT NULL DEFAULT '1', `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `widget` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `content` text COLLATE utf8mb4_general_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2025.2.17": [
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `enabled`, `title`, `widget`, `content`) VALUES (1, 1, 1, 1, 'Top Widget', 'top_widget', ''),(2, 2, 1, 1, 'Default Info', 'default_info_row', ''),(3, 1, 2, 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),(4, 2, 2, 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),(5, 3, 2, 1, 'Voucher Stocks', 'voucher_stocks', ''),(6, 4, 2, 1, 'Customer Expired', 'customer_expired', ''),(7, 1, 3, 1, 'Cron Monitor', 'cron_monitor', ''),(8, 2, 3, 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),(9, 3, 3, 1, 'Info Payment Gateway', 'info_payment_gateway', ''),(10, 4, 3, 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(11, 5, 3, 1, 'Activity Log', 'activity_log', '');"
|
||||
],
|
||||
"2025.2.19": [
|
||||
"ALTER TABLE `tbl_widgets` ADD `user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin' AFTER `position`;"
|
||||
],
|
||||
"2025.2.21": [
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (60, 1, 2, 'Customer', 1, 'Account Info', 'account_info', ''),(61, 3, 1, 'Customer', 1, 'Active Internet Plan', 'active_internet_plan', ''),(62, 4, 1, 'Customer', 1, 'Balance Transfer', 'balance_transfer', ''),(63, 1, 1, 'Customer', 1, 'Unpaid Order', 'unpaid_order', ''),(64, 2, 1, 'Customer', 1, 'Announcement', 'announcement', ''),(65, 5, 1, 'Customer', 1, 'Recharge A Friend', 'recharge_a_friend', ''),(66, 2, 2, 'Customer', 1, 'Voucher Activation', 'voucher_activation', '');"
|
||||
],
|
||||
"2025.2.25": [
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (30, 1, 1, 'Agent', 1, 'Top Widget', 'top_widget', ''), (31, 2, 1, 'Agent', 1, 'Default Info', 'default_info_row', ''), (32, 1, 2, 'Agent', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (33, 2, 2, 'Agent', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (34, 3, 2, 'Agent', 1, 'Voucher Stocks', 'voucher_stocks', ''), (35, 4, 2, 'Agent', 1, 'Customer Expired', 'customer_expired', ''), (36, 1, 3, 'Agent', 1, 'Cron Monitor', 'cron_monitor', ''), (37, 2, 3, 'Agent', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (38, 3, 3, 'Agent', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (39, 4, 3, 'Agent', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),(40, 5, 3, 'Agent', 1, 'Activity Log', 'activity_log', '');",
|
||||
"INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES (41, 1, 1, 'Sales', 1, 'Top Widget', 'top_widget', ''), (42, 2, 1, 'Sales', 1, 'Default Info', 'default_info_row', ''), (43, 1, 2, 'Sales', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''), (44, 2, 2, 'Sales', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''), (45, 3, 2, 'Sales', 1, 'Voucher Stocks', 'voucher_stocks', ''), (46, 4, 2, 'Sales', 1, 'Customer Expired', 'customer_expired', ''), (47, 1, 3, 'Sales', 1, 'Cron Monitor', 'cron_monitor', ''), (48, 2, 3, 'Sales', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''), (49, 3, 3, 'Sales', 1, 'Info Payment Gateway', 'info_payment_gateway', ''), (50, 4, 3, 'Sales', 1, 'Graph Customers Insight', 'graph_customers_insight', ''), (51, 5, 3, 'Sales', 1, 'Activity Log', 'activity_log', '');"
|
||||
],
|
||||
"2025.3.5": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_message_logs` ( `id` SERIAL PRIMARY KEY, `message_type` VARCHAR(50), `recipient` VARCHAR(255), `message_content` TEXT, `status` VARCHAR(50), `error_message` TEXT, `sent_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
|
||||
],
|
||||
"2025.3.10": [
|
||||
"CREATE TABLE IF NOT EXISTS `tbl_invoices` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `number` VARCHAR(50) NOT NULL, `customer_id` INT NOT NULL, `fullname` VARCHAR(100) NOT NULL, `email` VARCHAR(100) NOT NULL, `address` TEXT, `status` ENUM('Unpaid', 'Paid', 'Cancelled') NOT NULL DEFAULT 'Unpaid', `due_date` DATETIME NOT NULL, `filename` VARCHAR(255), `amount` DECIMAL(10, 2) NOT NULL, `data` JSON NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);"
|
||||
]
|
||||
}
|
||||
|
0
system/uploads/invoices/index.html
Normal file
0
system/uploads/invoices/index.html
Normal file
File diff suppressed because one or more lines are too long
BIN
system/vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed-Oblique.ttf
vendored
Normal file
BIN
system/vendor/mpdf/mpdf/ttfonts/DejaVuSansCondensed-Oblique.ttf
vendored
Normal file
Binary file not shown.
@ -7,13 +7,13 @@ class customer_expired
|
||||
|
||||
public function getWidget()
|
||||
{
|
||||
global $ui, $current_date;
|
||||
global $ui, $current_date, $config;
|
||||
|
||||
//user expire
|
||||
$query = ORM::for_table('tbl_user_recharges')
|
||||
->table_alias('tur')
|
||||
->selects([
|
||||
'tur.id',
|
||||
'c.id',
|
||||
'tur.username',
|
||||
'c.fullname',
|
||||
'c.phonenumber',
|
||||
@ -25,7 +25,7 @@ class customer_expired
|
||||
'tur.namebp',
|
||||
'tur.routers'
|
||||
])
|
||||
->join('tbl_customers', ['tur.customer_id', '=', 'c.id'], 'c')
|
||||
->innerJoin('tbl_customers', ['tur.customer_id', '=', 'c.id'], 'c')
|
||||
->where_lte('expiration', $current_date)
|
||||
->order_by_desc('expiration');
|
||||
$expire = Paginator::findMany($query);
|
||||
@ -38,6 +38,23 @@ class customer_expired
|
||||
// Pass the total count and current page to the paginator
|
||||
$paginator['total_count'] = $totalCount;
|
||||
|
||||
if(!empty($_COOKIE['expdef']) && $_COOKIE['expdef'] != $config['customer_expired_expdef']) {
|
||||
$d = ORM::for_table('tbl_appconfig')->where('setting', 'customer_expired_expdef')->find_one();
|
||||
if ($d) {
|
||||
$d->value = $_COOKIE['expdef'];
|
||||
$d->save();
|
||||
} else {
|
||||
$d = ORM::for_table('tbl_appconfig')->create();
|
||||
$d->setting = 'customer_expired_expdef';
|
||||
$d->value = $_COOKIE['expdef'];
|
||||
$d->save();
|
||||
}
|
||||
}
|
||||
if(!empty($config['customer_expired_expdef']) && empty($_COOKIE['expdef'])){
|
||||
$_COOKIE['expdef'] = $config['customer_expired_expdef'];
|
||||
setcookie('expdef', $config['customer_expired_expdef'], time() + (86400 * 30), "/");
|
||||
}
|
||||
|
||||
// Assign the pagination HTML to the template variable
|
||||
$ui->assign('expire', $expire);
|
||||
$ui->assign('cookie', $_COOKIE);
|
||||
|
@ -6,7 +6,11 @@ class graph_monthly_registered_customers
|
||||
{
|
||||
global $CACHE_PATH,$ui;
|
||||
|
||||
$cacheMRfile = File::pathFixer('/monthlyRegistered.temp');
|
||||
$cacheMRfile = $CACHE_PATH . File::pathFixer('/monthlyRegistered.temp');
|
||||
//Compatibility for old path
|
||||
if (file_exists($oldCacheMRfile = str_replace($CACHE_PATH, '', $cacheMRfile))) {
|
||||
rename($oldCacheMRfile, $cacheMRfile);
|
||||
}
|
||||
//Cache for 1 hour
|
||||
if (file_exists($cacheMRfile) && time() - filemtime($cacheMRfile) < 3600) {
|
||||
$monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true);
|
||||
|
@ -18,9 +18,9 @@
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<div class="btn-group pull-right">
|
||||
<a class="btn btn-primary btn-xs" title="save"
|
||||
href="{Text::url('customers/csv&token=', $csrf_token)}"
|
||||
onclick="return ask(this, '{Lang::T("This will export to CSV")}?')"><span
|
||||
class="glyphicon glyphicon-download" aria-hidden="true"></span> CSV</a>
|
||||
href="{Text::url('customers/csv&token=', $csrf_token)}" onclick="return ask(this, '{Lang::T("
|
||||
This will export to CSV")}?')"><span class="glyphicon glyphicon-download"
|
||||
aria-hidden="true"></span> CSV</a>
|
||||
</div>
|
||||
{/if}
|
||||
{Lang::T('Manage Contact')}
|
||||
@ -205,14 +205,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<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="email">{Lang::T('Email')}</option>
|
||||
<option value="inbox">{Lang::T('Inbox')}</option>
|
||||
<option value="sms">{Lang::T('SMS')}</option>
|
||||
<option value="wa">{Lang::T('WhatsApp')}</option>
|
||||
</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"
|
||||
placeholder="{Lang::T('Enter your message here...')}"></textarea>
|
||||
</div>
|
||||
@ -260,6 +261,8 @@
|
||||
$('#sendMessageButton').on('click', function () {
|
||||
const message = $('#messageContent').val().trim();
|
||||
const messageType = $('#messageType').val();
|
||||
const subject = $('#subject-content').val().trim();
|
||||
|
||||
|
||||
if (!message) {
|
||||
Swal.fire({
|
||||
@ -271,6 +274,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageType == 'all' || messageType == 'inbox' || messageType == 'email' && !subject) {
|
||||
Swal.fire({
|
||||
title: 'Error!',
|
||||
text: "{Lang::T('Please enter a subject for the message.')}",
|
||||
icon: 'error',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the button and show loading text
|
||||
$(this).prop('disabled', true).text('{Lang::T('Sending...')}');
|
||||
|
||||
@ -332,4 +345,31 @@
|
||||
});
|
||||
});
|
||||
</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" }
|
@ -14,6 +14,7 @@
|
||||
<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/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/select2.min.css" />
|
||||
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/select2-bootstrap.min.css" />
|
||||
@ -29,7 +30,7 @@
|
||||
|
||||
</style>
|
||||
{if isset($xheader)}
|
||||
{$xheader}
|
||||
{$xheader}
|
||||
{/if}
|
||||
|
||||
</head>
|
||||
@ -77,8 +78,8 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li class="user-header">
|
||||
<img src="{$app_url}/{$UPLOAD_PATH}{$_admin['photo']}.thumb.jpg"
|
||||
onerror="this.src='{$app_url}/{$UPLOAD_PATH}/admin.default.png'" class="img-circle"
|
||||
alt="Avatar">
|
||||
onerror="this.src='{$app_url}/{$UPLOAD_PATH}/admin.default.png'"
|
||||
class="img-circle" alt="Avatar">
|
||||
<p>
|
||||
{$_admin['fullname']}
|
||||
<small>{Lang::T($_admin['user_type'])}</small>
|
||||
@ -127,63 +128,64 @@
|
||||
</li>
|
||||
{$_MENU_AFTER_CUSTOMERS}
|
||||
{if !in_array($_admin['user_type'],['Report'])}
|
||||
<li class="{if $_routes[0] eq 'plan' || $_routes[0] eq 'coupons'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="fa fa-ticket"></i> <span>{Lang::T('Services')}</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 'list' }class="active" {/if}><a
|
||||
href="{Text::url('plan/list')}">{Lang::T('Active Customers')}</a></li>
|
||||
{if $_c['disable_voucher'] != 'yes'}
|
||||
<li {if $_routes[1] eq 'refill' }class="active" {/if}><a
|
||||
href="{Text::url('plan/refill')}">{Lang::T('Refill Customer')}</a></li>
|
||||
{/if}
|
||||
{if $_c['disable_voucher'] != 'yes'}
|
||||
<li {if $_routes[1] eq 'voucher' }class="active" {/if}><a
|
||||
href="{Text::url('plan/voucher')}">{Lang::T('Vouchers')}</a></li>
|
||||
{/if}
|
||||
{if $_c['enable_coupons'] == 'yes'}
|
||||
<li {if $_routes[0] eq 'coupons' }class="active" {/if}><a
|
||||
href="{Text::url('coupons')}">{Lang::T('Coupons')}</a></li>
|
||||
{/if}
|
||||
<li {if $_routes[1] eq 'recharge' }class="active" {/if}><a
|
||||
href="{Text::url('plan/recharge')}">{Lang::T('Recharge Customer')}</a></li>
|
||||
{if $_c['enable_balance'] == 'yes'}
|
||||
<li {if $_routes[1] eq 'deposit' }class="active" {/if}><a
|
||||
href="{Text::url('plan/deposit')}">{Lang::T('Refill Balance')}</a></li>
|
||||
{/if}
|
||||
{$_MENU_SERVICES}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="{if $_routes[0] eq 'plan' || $_routes[0] eq 'coupons'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="fa fa-ticket"></i> <span>{Lang::T('Services')}</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 'list' }class="active" {/if}><a
|
||||
href="{Text::url('plan/list')}">{Lang::T('Active Customers')}</a></li>
|
||||
{if $_c['disable_voucher'] != 'yes'}
|
||||
<li {if $_routes[1] eq 'refill' }class="active" {/if}><a
|
||||
href="{Text::url('plan/refill')}">{Lang::T('Refill Customer')}</a></li>
|
||||
{/if}
|
||||
{if $_c['disable_voucher'] != 'yes'}
|
||||
<li {if $_routes[1] eq 'voucher' }class="active" {/if}><a
|
||||
href="{Text::url('plan/voucher')}">{Lang::T('Vouchers')}</a></li>
|
||||
{/if}
|
||||
{if $_c['enable_coupons'] == 'yes'}
|
||||
<li {if $_routes[0] eq 'coupons' }class="active" {/if}><a
|
||||
href="{Text::url('coupons')}">{Lang::T('Coupons')}</a></li>
|
||||
{/if}
|
||||
<li {if $_routes[1] eq 'recharge' }class="active" {/if}><a
|
||||
href="{Text::url('plan/recharge')}">{Lang::T('Recharge Customer')}</a></li>
|
||||
{if $_c['enable_balance'] == 'yes'}
|
||||
<li {if $_routes[1] eq 'deposit' }class="active" {/if}><a
|
||||
href="{Text::url('plan/deposit')}">{Lang::T('Refill Balance')}</a></li>
|
||||
{/if}
|
||||
{$_MENU_SERVICES}
|
||||
</ul>
|
||||
</li>
|
||||
{/if}
|
||||
{$_MENU_AFTER_SERVICES}
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<li class="{if $_system_menu eq 'services'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="ion ion-cube"></i> <span>{Lang::T('Internet Plan')}</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 'hotspot' }class="active" {/if}><a
|
||||
href="{Text::url('services/hotspot')}">Hotspot</a></li>
|
||||
<li {if $_routes[1] eq 'pppoe' }class="active" {/if}><a
|
||||
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>
|
||||
<li {if $_routes[1] eq 'list' }class="active" {/if}><a
|
||||
href="{Text::url('bandwidth/list')}">Bandwidth</a></li>
|
||||
{if $_c['enable_balance'] == 'yes'}
|
||||
<li {if $_routes[1] eq 'balance' }class="active" {/if}><a
|
||||
href="{Text::url('services/balance')}">{Lang::T('Customer Balance')}</a></li>
|
||||
{/if}
|
||||
{$_MENU_PLANS}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="{if $_system_menu eq 'services'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="ion ion-cube"></i> <span>{Lang::T('Internet Plan')}</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 'hotspot' }class="active" {/if}><a
|
||||
href="{Text::url('services/hotspot')}">Hotspot</a></li>
|
||||
<li {if $_routes[1] eq 'pppoe' }class="active" {/if}><a
|
||||
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>
|
||||
<li {if $_routes[1] eq 'list' }class="active" {/if}><a
|
||||
href="{Text::url('bandwidth/list')}">Bandwidth</a></li>
|
||||
{if $_c['enable_balance'] == 'yes'}
|
||||
<li {if $_routes[1] eq 'balance' }class="active" {/if}><a
|
||||
href="{Text::url('services/balance')}">{Lang::T('Customer Balance')}</a></li>
|
||||
{/if}
|
||||
{$_MENU_PLANS}
|
||||
</ul>
|
||||
</li>
|
||||
{/if}
|
||||
{$_MENU_AFTER_PLANS}
|
||||
<li class="{if in_array($_routes[0], ['maps'])}active{/if} treeview">
|
||||
@ -203,18 +205,20 @@
|
||||
</li>
|
||||
<li class="{if $_system_menu eq 'reports'}active{/if} treeview">
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])}
|
||||
<a href="#">
|
||||
<i class="ion ion-clipboard"></i> <span>{Lang::T('Reports')}</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i class="ion ion-clipboard"></i> <span>{Lang::T('Reports')}</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
{/if}
|
||||
<ul class="treeview-menu">
|
||||
<li {if $_routes[1] eq 'reports' }class="active" {/if}><a
|
||||
href="{Text::url('reports')}">{Lang::T('Daily Reports')}</a></li>
|
||||
<li {if $_routes[1] eq 'activation' }class="active" {/if}><a
|
||||
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}
|
||||
</ul>
|
||||
</li>
|
||||
@ -236,68 +240,71 @@
|
||||
</li>
|
||||
{$_MENU_AFTER_MESSAGE}
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<li class="{if $_system_menu eq 'network'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="ion ion-network"></i> <span>{Lang::T('Network')}</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<li {if $_routes[0] eq 'routers' and $_routes[1] eq '' }class="active" {/if}><a
|
||||
href="{Text::url('routers')}">Routers</a></li>
|
||||
<li {if $_routes[0] eq 'pool' and $_routes[1] eq 'list' }class="active" {/if}><a
|
||||
href="{Text::url('pool/list')}">IP Pool</a></li>
|
||||
<li {if $_routes[0] eq 'pool' and $_routes[1] eq 'port' }class="active" {/if}><a
|
||||
href="{Text::url('pool/port')}">Port Pool</a></li>
|
||||
{$_MENU_NETWORK}
|
||||
</ul>
|
||||
</li>
|
||||
{$_MENU_AFTER_NETWORKS}
|
||||
{if $_c['radius_enable']}
|
||||
<li class="{if $_system_menu eq 'radius'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="fa fa-database"></i> <span>{Lang::T('Radius')}</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<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>
|
||||
{$_MENU_RADIUS}
|
||||
</ul>
|
||||
<li class="{if $_system_menu eq 'network'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="ion ion-network"></i> <span>{Lang::T('Network')}</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<li {if $_routes[0] eq 'routers' and $_routes[1] eq '' }class="active" {/if}><a
|
||||
href="{Text::url('routers')}">Routers</a></li>
|
||||
<li {if $_routes[0] eq 'pool' and $_routes[1] eq 'list' }class="active" {/if}><a
|
||||
href="{Text::url('pool/list')}">IP Pool</a></li>
|
||||
<li {if $_routes[0] eq 'pool' and $_routes[1] eq 'port' }class="active" {/if}><a
|
||||
href="{Text::url('pool/port')}">Port Pool</a></li>
|
||||
{$_MENU_NETWORK}
|
||||
</ul>
|
||||
</li>
|
||||
{$_MENU_AFTER_NETWORKS}
|
||||
{if $_c['radius_enable']}
|
||||
<li class="{if $_system_menu eq 'radius'}active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="fa fa-database"></i> <span>{Lang::T('Radius')}</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<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>
|
||||
{$_MENU_RADIUS}
|
||||
</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>
|
||||
{/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 $_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>
|
||||
<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}
|
||||
{$_MENU_AFTER_PAGES}
|
||||
<li
|
||||
@ -310,84 +317,85 @@
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<li {if $_routes[1] eq 'app' }class="active" {/if}><a
|
||||
href="{Text::url('settings/app')}">{Lang::T('General Settings')}</a></li>
|
||||
<li {if $_routes[1] eq 'localisation' }class="active" {/if}><a
|
||||
href="{Text::url('settings/localisation')}">{Lang::T('Localisation')}</a></li>
|
||||
<li {if $_routes[0] eq 'customfield' }class="active" {/if}><a
|
||||
href="{Text::url('customfield')}">{Lang::T('Custom Fields')}</a></li>
|
||||
<li {if $_routes[1] eq 'miscellaneous' }class="active" {/if}><a
|
||||
href="{Text::url('settings/miscellaneous')}">{Lang::T('Miscellaneous')}</a></li>
|
||||
<li {if $_routes[1] eq 'maintenance' }class="active" {/if}><a
|
||||
href="{Text::url('settings/maintenance')}">{Lang::T('Maintenance Mode')}</a></li>
|
||||
<li {if $_routes[0] eq 'widgets' }class="active" {/if}><a
|
||||
href="{Text::url('widgets')}">{Lang::T('Widgets')}</a></li>
|
||||
<li {if $_routes[1] eq 'notifications' }class="active" {/if}><a
|
||||
href="{Text::url('settings/notifications')}">{Lang::T('User Notification')}</a></li>
|
||||
<li {if $_routes[1] eq 'devices' }class="active" {/if}><a
|
||||
href="{Text::url('settings/devices')}">{Lang::T('Devices')}</a></li>
|
||||
<li {if $_routes[1] eq 'app' }class="active" {/if}><a
|
||||
href="{Text::url('settings/app')}">{Lang::T('General Settings')}</a></li>
|
||||
<li {if $_routes[1] eq 'localisation' }class="active" {/if}><a
|
||||
href="{Text::url('settings/localisation')}">{Lang::T('Localisation')}</a></li>
|
||||
<li {if $_routes[0] eq 'customfield' }class="active" {/if}><a
|
||||
href="{Text::url('customfield')}">{Lang::T('Custom Fields')}</a></li>
|
||||
<li {if $_routes[1] eq 'miscellaneous' }class="active" {/if}><a
|
||||
href="{Text::url('settings/miscellaneous')}">{Lang::T('Miscellaneous')}</a></li>
|
||||
<li {if $_routes[1] eq 'maintenance' }class="active" {/if}><a
|
||||
href="{Text::url('settings/maintenance')}">{Lang::T('Maintenance Mode')}</a></li>
|
||||
<li {if $_routes[0] eq 'widgets' }class="active" {/if}><a
|
||||
href="{Text::url('widgets')}">{Lang::T('Widgets')}</a></li>
|
||||
<li {if $_routes[1] eq 'notifications' }class="active" {/if}><a
|
||||
href="{Text::url('settings/notifications')}">{Lang::T('User Notification')}</a></li>
|
||||
<li {if $_routes[1] eq 'devices' }class="active" {/if}><a
|
||||
href="{Text::url('settings/devices')}">{Lang::T('Devices')}</a></li>
|
||||
{/if}
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin','Agent'])}
|
||||
<li {if $_routes[1] eq 'users' }class="active" {/if}><a
|
||||
href="{Text::url('settings/users')}">{Lang::T('Administrator Users')}</a></li>
|
||||
<li {if $_routes[1] eq 'users' }class="active" {/if}><a
|
||||
href="{Text::url('settings/users')}">{Lang::T('Administrator Users')}</a></li>
|
||||
{/if}
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<li {if $_routes[1] eq 'dbstatus' }class="active" {/if}><a
|
||||
href="{Text::url('settings/dbstatus')}">{Lang::T('Backup/Restore')}</a></li>
|
||||
<li {if $_system_menu eq 'paymentgateway' }class="active" {/if}>
|
||||
<a href="{Text::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="{Text::url('pluginmanager')}"><i class="glyphicon glyphicon-tasks"></i>
|
||||
{Lang::T('Plugin Manager')}</a>
|
||||
</li>
|
||||
<li {if $_routes[1] eq 'dbstatus' }class="active" {/if}><a
|
||||
href="{Text::url('settings/dbstatus')}">{Lang::T('Backup/Restore')}</a></li>
|
||||
<li {if $_system_menu eq 'paymentgateway' }class="active" {/if}>
|
||||
<a href="{Text::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="{Text::url('pluginmanager')}"><i class="glyphicon glyphicon-tasks"></i>
|
||||
{Lang::T('Plugin Manager')}</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</li>
|
||||
{$_MENU_AFTER_SETTINGS}
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<li class="{if $_system_menu eq 'logs' }active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="ion ion-clock"></i> <span>{Lang::T('Logs')}</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 'list' }class="active" {/if}><a
|
||||
href="{Text::url('logs/phpnuxbill')}">PhpNuxBill</a></li>
|
||||
{if $_c['radius_enable']}
|
||||
<li {if $_routes[1] eq 'radius' }class="active" {/if}><a
|
||||
href="{Text::url('logs/radius')}">Radius</a>
|
||||
</li>
|
||||
{/if}
|
||||
<li {if $_routes[1] eq 'message' }class="active" {/if}><a
|
||||
<li class="{if $_system_menu eq 'logs' }active{/if} treeview">
|
||||
<a href="#">
|
||||
<i class="ion ion-clock"></i> <span>{Lang::T('Logs')}</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 'list' }class="active" {/if}><a
|
||||
href="{Text::url('logs/phpnuxbill')}">PhpNuxBill</a></li>
|
||||
{if $_c['radius_enable']}
|
||||
<li {if $_routes[1] eq 'radius' }class="active" {/if}><a
|
||||
href="{Text::url('logs/radius')}">Radius</a>
|
||||
</li>
|
||||
{/if}
|
||||
<li {if $_routes[1] eq 'message' }class="active" {/if}><a
|
||||
href="{Text::url('logs/message')}">Message</a></li>
|
||||
{$_MENU_LOGS}
|
||||
</ul>
|
||||
</li>
|
||||
{$_MENU_LOGS}
|
||||
</ul>
|
||||
</li>
|
||||
{/if}
|
||||
{$_MENU_AFTER_LOGS}
|
||||
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
|
||||
<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}">
|
||||
<i class="ion ion-ios-bookmarks"></i>
|
||||
<span class="text">{Lang::T('Documentation')}</span>
|
||||
{if $_c['docs_clicked'] != 'yes'}
|
||||
<span class="pull-right-container"><small
|
||||
class="label pull-right bg-green">New</small></span>
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
<li {if $_system_menu eq 'community' }class="active" {/if}>
|
||||
<a href="{Text::url('community')}">
|
||||
<i class="ion ion-chatboxes"></i>
|
||||
<span class="text">Community</span>
|
||||
</a>
|
||||
</li>
|
||||
<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}">
|
||||
<i class="ion ion-ios-bookmarks"></i>
|
||||
<span class="text">{Lang::T('Documentation')}</span>
|
||||
{if $_c['docs_clicked'] != 'yes'}
|
||||
<span class="pull-right-container"><small
|
||||
class="label pull-right bg-green">New</small></span>
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
<li {if $_system_menu eq 'community' }class="active" {/if}>
|
||||
<a href="{Text::url('community')}">
|
||||
<i class="ion ion-chatboxes"></i>
|
||||
<span class="text">Community</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
{$_MENU_AFTER_COMMUNITY}
|
||||
</ul>
|
||||
@ -395,11 +403,11 @@
|
||||
</aside>
|
||||
|
||||
{if $_c['maintenance_mode'] == 1}
|
||||
<div class="notification-top-bar">
|
||||
<p>{Lang::T('The website is currently in maintenance mode, this means that some or all functionality may be
|
||||
<div class="notification-top-bar">
|
||||
<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> <a
|
||||
href="{Text::url('settings/maintenance')}">{Lang::T('Turn Off')}</a></small></p>
|
||||
</div>
|
||||
href="{Text::url('settings/maintenance')}">{Lang::T('Turn Off')}</a></small></p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="content-wrapper">
|
||||
@ -411,19 +419,19 @@
|
||||
|
||||
<section class="content">
|
||||
{if isset($notify)}
|
||||
<script>
|
||||
// Display SweetAlert toast notification
|
||||
Swal.fire({
|
||||
icon: '{if $notify_t == "s"}success{else}error{/if}',
|
||||
title: '{$notify}',
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 5000,
|
||||
timerProgressBar: true,
|
||||
didOpen: (toast) => {
|
||||
toast.addEventListener('mouseenter', Swal.stopTimer)
|
||||
toast.addEventListener('mouseleave', Swal.resumeTimer)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
||||
<script>
|
||||
// Display SweetAlert toast notification
|
||||
Swal.fire({
|
||||
icon: '{if $notify_t == "s"}success{else}error{/if}',
|
||||
title: '{$notify}',
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 5000,
|
||||
timerProgressBar: true,
|
||||
didOpen: (toast) => {
|
||||
toast.addEventListener('mouseenter', Swal.stopTimer)
|
||||
toast.addEventListener('mouseleave', Swal.resumeTimer)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/if}
|
57
ui/ui/admin/invoices/list.tpl
Normal file
57
ui/ui/admin/invoices/list.tpl
Normal 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"}
|
@ -30,7 +30,8 @@
|
||||
<select class="form-control" name="service" id="service">
|
||||
<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="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>
|
||||
</select>
|
||||
</div>
|
||||
@ -41,8 +42,10 @@
|
||||
<select class="form-control" name="group" id="group">
|
||||
<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="expired" {if $group=='expired' }selected{/if}>{Lang::T('Expired Customers')}</option>
|
||||
<option value="active" {if $group=='active' }selected{/if}>{Lang::T('Active Customers')}</option>
|
||||
<option value="expired" {if $group=='expired' }selected{/if}>{Lang::T('Expired
|
||||
Customers')}</option>
|
||||
<option value="active" {if $group=='active' }selected{/if}>{Lang::T('Active Customers')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,9 +53,13 @@
|
||||
<label class="col-md-2 control-label">{Lang::T('Send Via')}</label>
|
||||
<div class="col-md-6">
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
@ -72,10 +79,21 @@
|
||||
{Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')}
|
||||
</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">
|
||||
<label class="col-md-2 control-label">{Lang::T('Message')}</label>
|
||||
<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">
|
||||
{Lang::T('Testing [if checked no real message is sent]')}
|
||||
</div>
|
||||
@ -93,7 +111,8 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -112,7 +131,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{Lang::T('Customer')}</th>
|
||||
<th>{Lang::T('Phone')}</th>
|
||||
<th>{Lang::T('Channel')}</th>
|
||||
<th>{Lang::T('Status')}</th>
|
||||
<th>{Lang::T('Message')}</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://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}
|
||||
<script>
|
||||
let page = 0;
|
||||
@ -158,6 +205,7 @@
|
||||
page: page,
|
||||
test: $('#test').is(':checked') ? 'on' : 'off',
|
||||
service: $('#service').val(),
|
||||
subject: $('#subject').val(),
|
||||
},
|
||||
dataType: 'json',
|
||||
beforeSend: function () {
|
||||
@ -186,10 +234,10 @@
|
||||
let statusClass = msg.status.includes('Failed') ? 'danger' : 'success';
|
||||
historyTable.row.add([
|
||||
msg.name,
|
||||
msg.phone,
|
||||
msg.channel,
|
||||
`<span class="text-${statusClass}">${msg.status}</span>`,
|
||||
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')
|
||||
]).draw(false); // Add row without redrawing the table
|
||||
});
|
||||
@ -207,7 +255,7 @@
|
||||
console.error("Unexpected response format:", response);
|
||||
$('#status').html(`
|
||||
<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>
|
||||
`);
|
||||
}
|
||||
|
@ -23,12 +23,26 @@
|
||||
<label class="col-md-2 control-label">{Lang::T('Send Via')}</label>
|
||||
<div class="col-md-6">
|
||||
<select class="form-control" name="via" id="via">
|
||||
<option value="sms" selected> {Lang::T('via SMS')}</option>
|
||||
<option value="wa"> {Lang::T('Via WhatsApp')}</option>
|
||||
<option value="both"> {Lang::T('Via WhatsApp and SMS')}</option>
|
||||
</select>
|
||||
<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="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 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">
|
||||
<label class="col-md-2 control-label">{Lang::T('Message')}</label>
|
||||
<div class="col-md-6">
|
||||
@ -64,6 +78,33 @@
|
||||
</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"}
|
@ -38,7 +38,8 @@
|
||||
class="btn btn-success text-black btn-sm hidden-xs hidden-sm" target="_blank">
|
||||
<i class="glyphicon glyphicon-phone"></i>
|
||||
NuxPrint
|
||||
</a>
|
||||
</a><br><br>
|
||||
<input type="text" class="form-control form-sm" style="border: 0px; padding: 1px; background-color: white;" readonly onclick="this.select()" value="{$public_url}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -58,7 +59,7 @@
|
||||
paid.src = '{$app_url}/system/uploads/paid.png';
|
||||
{if !empty($logo)}
|
||||
var img = new Image();
|
||||
img.src = '{$app_url}/{$logo}';
|
||||
img.src = '{$app_url}/{$logo}?{time()}';
|
||||
var new_width = (width / 4) * 2;
|
||||
var new_height = Math.ceil({$hlogo} * (new_width/{$wlogo}));
|
||||
height = height + new_height;
|
||||
|
@ -26,6 +26,7 @@
|
||||
<div class="table-responsive">
|
||||
<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('Fullname')}</th>
|
||||
<th class="text-center">{Lang::T('Plan Name')}</th>
|
||||
<th class="text-center">{Lang::T('Type')}</th>
|
||||
<th class="text-center">{Lang::T('Plan Price')}</th>
|
||||
@ -36,6 +37,7 @@
|
||||
{foreach $d as $ds}
|
||||
<tr>
|
||||
<td>{$ds['username']}</td>
|
||||
<td>{$ds['fullname']}</td>
|
||||
<td class="text-center">{$ds['plan_name']}</td>
|
||||
<td class="text-center">{$ds['type']}</td>
|
||||
<td class="text-right">{Lang::moneyFormat($ds['price'])}</td>
|
||||
|
@ -33,6 +33,7 @@
|
||||
<tr>
|
||||
<th>{Lang::T('Invoice')}</th>
|
||||
<th>{Lang::T('Username')}</th>
|
||||
<th>{Lang::T('Fullname')}</th>
|
||||
<th>{Lang::T('Plan Name')}</th>
|
||||
<th>{Lang::T('Plan Price')}</th>
|
||||
<th>{Lang::T('Type')}</th>
|
||||
@ -48,6 +49,7 @@
|
||||
style="cursor:pointer;">{$ds['invoice']}</td>
|
||||
<td onclick="window.location.href = '{Text::url('')}customers/viewu/{$ds['username']}'"
|
||||
style="cursor:pointer;">{$ds['username']}</td>
|
||||
<td>{$ds['fullname']}</td>
|
||||
<td>{$ds['plan_name']}</td>
|
||||
<td>{Lang::moneyFormat($ds['price'])}</td>
|
||||
<td>{$ds['type']}</td>
|
||||
|
@ -94,10 +94,11 @@
|
||||
<a href="{Text::url('export/pdf-by-date&')}{$filter}" class="btn btn-default"><i
|
||||
class="fa fa-file-pdf-o"></i></a>
|
||||
</th>
|
||||
<th colspan="7"></th>
|
||||
<th colspan="8"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{Lang::T('Username')}</th>
|
||||
<th>{Lang::T('Fullname')}</th>
|
||||
<th>{Lang::T('Type')}</th>
|
||||
<th>{Lang::T('Plan Name')}</th>
|
||||
<th>{Lang::T('Plan Price')}</th>
|
||||
@ -111,6 +112,7 @@
|
||||
{foreach $d as $ds}
|
||||
<tr>
|
||||
<td>{$ds['username']}</td>
|
||||
<td>{$ds['fullname']}</td>
|
||||
<td>{$ds['type']}</td>
|
||||
<td>{$ds['plan_name']}</td>
|
||||
<td class="text-right">{Lang::moneyFormat($ds['price'])}</td>
|
||||
@ -122,9 +124,8 @@
|
||||
{/foreach}
|
||||
<tr>
|
||||
<th>{Lang::T('Total')}</th>
|
||||
<td colspan="2"></td>
|
||||
<th class="text-right">{Lang::moneyFormat($dr)}</th>
|
||||
<td colspan="4"></td>
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -37,6 +37,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{Lang::T('Username')}</th>
|
||||
<th>{Lang::T('Fullname')}</th>
|
||||
<th>{Lang::T('Type')}</th>
|
||||
<th>{Lang::T('Plan Name')}</th>
|
||||
<th>{Lang::T('Plan Price')}</th>
|
||||
@ -50,6 +51,7 @@
|
||||
{foreach $d as $ds}
|
||||
<tr>
|
||||
<td>{$ds['username']}</td>
|
||||
<td>{$ds['fullname']}</td>
|
||||
<td>{$ds['type']}</td>
|
||||
<td>{$ds['plan_name']}</td>
|
||||
<td class="text-right">{Lang::moneyFormat($ds['price'])}</td>
|
||||
|
@ -155,7 +155,7 @@
|
||||
<h3 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseLoginPage"
|
||||
aria-expanded="true" aria-controls="collapseLoginPage">
|
||||
{Lang::T('Customer Login Page Settings')}
|
||||
{Lang::T('Customer Login Page')}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
@ -254,6 +254,37 @@
|
||||
</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-heading" role="tab" id="Registration">
|
||||
<h4 class="panel-title">
|
||||
@ -649,6 +680,24 @@
|
||||
value="{$_c['minimum_transfer']}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">{Lang::T('Allow Balance Custom
|
||||
Amount')}</label>
|
||||
<div class="col-md-5">
|
||||
<select name="allow_balance_custom" id="allow_balance_custom" class="form-control">
|
||||
<option value="no">
|
||||
{Lang::T('No')}
|
||||
</option>
|
||||
<option value="yes" {if $_c['allow_balance_custom']=='yes' }selected="selected" {/if}>
|
||||
{Lang::T('Yes')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help-block col-md-4"><small>
|
||||
{Lang::T('Allow Customer buy balance with any amount')}
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-success btn-block" type="submit">
|
||||
{Lang::T('Save Changes')}
|
||||
</button>
|
||||
|
@ -175,24 +175,6 @@
|
||||
extend')}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group has-error">
|
||||
<label class="col-md-3 control-label">{Lang::T('Allow Balance Custom
|
||||
Amount')}</label>
|
||||
<div class="col-md-5">
|
||||
<select name="allow_balance_custom" id="allow_balance_custom" class="form-control">
|
||||
<option value="no">
|
||||
{Lang::T('No')}
|
||||
</option>
|
||||
<option value="yes" {if $_c['allow_balance_custom']=='yes' }selected="selected" {/if}>
|
||||
{Lang::T('Yes')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help-block col-md-4"><small>
|
||||
{Lang::T('Allow Customer buy balance with any amount')}
|
||||
<br>*Please report any issue or bugs</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -113,6 +113,8 @@
|
||||
<b>[[expired_date]]</b> - {Lang::T('Expired datetime')}.<br>
|
||||
<b>[[footer]]</b> - {Lang::T('Invoice Footer')}.<br>
|
||||
<b>[[note]]</b> - {Lang::T('For Notes by admin')}.<br>
|
||||
<b>[[invoice_link]]</b> - <a href="{$app_url}/docs/#Reminder%20with%20payment%20link"
|
||||
target="_blank">{Lang::T("read documentation")}</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,43 +164,79 @@
|
||||
</div>
|
||||
</div>
|
||||
{if $_c['enable_balance'] == 'yes'}
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label">{Lang::T('Send Balance')}</label>
|
||||
<div class="col-md-6">
|
||||
<textarea class="form-control" id="balance_send" name="balance_send"
|
||||
rows="4">{if $_json['balance_send']}{Lang::htmlspecialchars($_json['balance_send'])}{else}{Lang::htmlspecialchars($_default['balance_send'])}{/if}</textarea>
|
||||
</div>
|
||||
<p class="col-md-4 help-block">
|
||||
<b>[[name]]</b> - {Lang::T('Receiver name')}.<br>
|
||||
<b>[[balance]]</b> - {Lang::T('how much balance have been send')}.<br>
|
||||
<b>[[current_balance]]</b> - {Lang::T('Current Balance')}.
|
||||
</p>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label">{Lang::T('Send Balance')}</label>
|
||||
<div class="col-md-6">
|
||||
<textarea class="form-control" id="balance_send" name="balance_send"
|
||||
rows="4">{if $_json['balance_send']}{Lang::htmlspecialchars($_json['balance_send'])}{else}{Lang::htmlspecialchars($_default['balance_send'])}{/if}</textarea>
|
||||
</div>
|
||||
<p class="col-md-4 help-block">
|
||||
<b>[[name]]</b> - {Lang::T('Receiver name')}.<br>
|
||||
<b>[[balance]]</b> - {Lang::T('how much balance have been send')}.<br>
|
||||
<b>[[current_balance]]</b> - {Lang::T('Current Balance')}.
|
||||
</p>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label">{Lang::T('Received Balance')}</label>
|
||||
<div class="col-md-6">
|
||||
<textarea class="form-control" id="balance_received" name="balance_received"
|
||||
rows="4">{if $_json['balance_received']}{Lang::htmlspecialchars($_json['balance_received'])}{else}{Lang::htmlspecialchars($_default['balance_received'])}{/if}</textarea>
|
||||
</div>
|
||||
<p class="col-md-4 help-block">
|
||||
<b>[[name]]</b> - {Lang::T('Sender name')}.<br>
|
||||
<b>[[balance]]</b> - {Lang::T('how much balance have been received')}.<br>
|
||||
<b>[[current_balance]]</b> - {Lang::T('Current Balance')}.
|
||||
</p>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label">{Lang::T('Received Balance')}</label>
|
||||
<div class="col-md-6">
|
||||
<textarea class="form-control" id="balance_received" name="balance_received"
|
||||
rows="4">{if $_json['balance_received']}{Lang::htmlspecialchars($_json['balance_received'])}{else}{Lang::htmlspecialchars($_default['balance_received'])}{/if}</textarea>
|
||||
</div>
|
||||
<p class="col-md-4 help-block">
|
||||
<b>[[name]]</b> - {Lang::T('Sender name')}.<br>
|
||||
<b>[[balance]]</b> - {Lang::T('how much balance have been received')}.<br>
|
||||
<b>[[current_balance]]</b> - {Lang::T('Current Balance')}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{* <div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label">{Lang::T('PDF Invoice Template')}</label>
|
||||
<div class="col-md-6">
|
||||
<textarea class="form-control" id="email_invoice" name="email_invoice"
|
||||
placeholder="{Lang::T('Template for sending pdf invoice')}" rows="20">
|
||||
{if !empty($_json['email_invoice'])}
|
||||
{Lang::htmlspecialchars($_json['email_invoice'])}
|
||||
{else}
|
||||
{Lang::htmlspecialchars($_default['email_invoice'])}
|
||||
{/if}
|
||||
</textarea>
|
||||
</div>
|
||||
<p class="col-md-4 help-block">
|
||||
<b>[[company_name]]</b> {Lang::T('Your Company Name at Settings')}.<br>
|
||||
<b>[[company_address]]</b> {Lang::T('Your Company Address at Settings')}.<br>
|
||||
<b>[[company_phone]]</b> - {Lang::T('Your Company Phone at Settings')}.<br>
|
||||
<b>[[invoice]]</b> - {Lang::T('Invoice number')}.<br>
|
||||
<b>[[created_at]]</b> - {Lang::T('Date invoice created')}.<br>
|
||||
<b>[[payment_gateway]]</b> - {Lang::T('Payment gateway user paid from')}.<br>
|
||||
<b>[[payment_channel]]</b> - {Lang::T('Payment channel user paid from')}.<br>
|
||||
<b>[[bill_rows]]</b> - {Lang::T('Bills table, where bills are listed')}.<br>
|
||||
<b>[[currency]]</b> - {Lang::T('Your currency code at localisation Settings')}.<br>
|
||||
<b>[[status]]</b> - {Lang::T('Invoice status')}.<br>
|
||||
<b>[[fullname]]</b> - {Lang::T('Receiver name')}.<br>
|
||||
<b>[[user_name]]</b> - {Lang::T('Username internet')}.<br>
|
||||
<b>[[email]]</b> - {Lang::T('Customer email')} .<br>
|
||||
<b>[[phone]]</b> - {Lang::T('Customer phone')}. <br>
|
||||
<b>[[address]]</b> - {Lang::T('Customer phone')}. <br>
|
||||
<b>[[expired_date]]</b> - {Lang::T('Expired datetime')}.<br>
|
||||
<b>[[logo]]</b> - {Lang::T('Your company logo at Settings')}.<br>
|
||||
<b>[[due_date]]</b> - {Lang::T('Invoice Due date, 7 Days after invoice created')}.<br>
|
||||
<b>[[payment_link]]</b> - <a href="{$app_url}/docs/#Reminder%20with%20payment%20link"
|
||||
target="_blank">{Lang::T("read documentation")}</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div> *}
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success btn-block" type="submit">{Lang::T('Save Changes')}</button>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success btn-block" type="submit">{Lang::T('Save Changes')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{include file="sections/footer.tpl"}
|
||||
{include file="sections/footer.tpl"}
|
@ -1,25 +1,25 @@
|
||||
</section>
|
||||
</div>
|
||||
{if isset($_c['CompanyFooter'])}
|
||||
<footer class="main-footer">
|
||||
{$_c['CompanyFooter']}
|
||||
<div class="pull-right">
|
||||
<a href="javascript:showPrivacy()">Privacy</a>
|
||||
•
|
||||
<a href="javascript:showTaC()">T & C</a>
|
||||
</div>
|
||||
</footer>
|
||||
<footer class="main-footer">
|
||||
{$_c['CompanyFooter']}
|
||||
<div class="pull-right">
|
||||
<a href="javascript:showPrivacy()">Privacy</a>
|
||||
•
|
||||
<a href="javascript:showTaC()">T & C</a>
|
||||
</div>
|
||||
</footer>
|
||||
{else}
|
||||
<footer class="main-footer">
|
||||
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">AdminLTE</a>
|
||||
<div class="pull-right">
|
||||
<a href="javascript:showPrivacy()">Privacy</a>
|
||||
•
|
||||
<a href="javascript:showTaC()">T & C</a>
|
||||
</div>
|
||||
</footer>
|
||||
<footer class="main-footer">
|
||||
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">AdminLTE</a>
|
||||
<div class="pull-right">
|
||||
<a href="javascript:showPrivacy()">Privacy</a>
|
||||
•
|
||||
<a href="javascript:showTaC()">T & C</a>
|
||||
</div>
|
||||
</footer>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -50,156 +50,175 @@
|
||||
<script src="{$app_url}/ui/ui/scripts/custom.js?2025.2.5"></script>
|
||||
|
||||
{if isset($xfooter)}
|
||||
{$xfooter}
|
||||
{$xfooter}
|
||||
{/if}
|
||||
|
||||
{if $_c['tawkto'] != ''}
|
||||
<!--Start of Tawk.to Script-->
|
||||
<script type="text/javascript">
|
||||
var isLoggedIn = false;
|
||||
var Tawk_API = {
|
||||
onLoad: function() {
|
||||
Tawk_API.setAttributes({
|
||||
'username' : '{$_user['username']}',
|
||||
'service' : '{$_user['service_type']}',
|
||||
'balance' : '{$_user['balance']}',
|
||||
'account' : '{$_user['account_type']}',
|
||||
'phone' : '{$_user['phonenumber']}'
|
||||
}, function(error) {
|
||||
console.log(error)
|
||||
});
|
||||
<!--Start of Tawk.to Script-->
|
||||
<script type="text/javascript">
|
||||
var isLoggedIn = false;
|
||||
var Tawk_API = {
|
||||
onLoad: function () {
|
||||
Tawk_API.setAttributes({
|
||||
'username': '{$_user['username']}',
|
||||
'service': '{$_user['service_type']}',
|
||||
'balance': '{$_user['balance']}',
|
||||
'account': '{$_user['account_type']}',
|
||||
'phone': '{$_user['phonenumber']}'
|
||||
}, function (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();
|
||||
Tawk_API.visitor = {
|
||||
name: '{$_user['fullname']}',
|
||||
email: '{$_user['email']}',
|
||||
phone: '{$_user['phonenumber']}'
|
||||
};
|
||||
(function() {
|
||||
var s1 = document.createElement("script"),
|
||||
s0 = document.getElementsByTagName("script")[0];
|
||||
s1.async = true;
|
||||
s1.src = 'https://embed.tawk.to/{$_c['tawkto']}';
|
||||
s1.charset = 'UTF-8';
|
||||
s1.setAttribute('crossorigin', '*');
|
||||
s0.parentNode.insertBefore(s1, s0);
|
||||
})();
|
||||
</script>
|
||||
<!--End of Tawk.to Script-->
|
||||
{/if}
|
||||
(function () {
|
||||
var s1 = document.createElement("script"),
|
||||
s0 = document.getElementsByTagName("script")[0];
|
||||
s1.async = true;
|
||||
s1.src = 'https://embed.tawk.to/{$_c['tawkto']}';
|
||||
s1.charset = 'UTF-8';
|
||||
s1.setAttribute('crossorigin', '*');
|
||||
s0.parentNode.insertBefore(s1, s0);
|
||||
})();
|
||||
</script>
|
||||
<!--End of Tawk.to Script-->
|
||||
{/if}
|
||||
|
||||
<script>
|
||||
const toggleIcon = document.getElementById('toggleIcon');
|
||||
const body = document.body;
|
||||
const savedMode = localStorage.getItem('mode');
|
||||
if (savedMode === 'dark') {
|
||||
<script>
|
||||
const toggleIcon = document.getElementById('toggleIcon');
|
||||
const body = document.body;
|
||||
const savedMode = localStorage.getItem('mode');
|
||||
if (savedMode === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
toggleIcon.textContent = '🌞';
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
if (mode === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
toggleIcon.textContent = '🌞';
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
toggleIcon.textContent = '🌜';
|
||||
}
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
if (mode === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
toggleIcon.textContent = '🌜';
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
toggleIcon.textContent = '🌞';
|
||||
}
|
||||
toggleIcon.addEventListener('click', () => {
|
||||
if (body.classList.contains('dark-mode')) {
|
||||
setMode('light');
|
||||
localStorage.setItem('mode', 'light');
|
||||
} else {
|
||||
setMode('dark');
|
||||
localStorage.setItem('mode', 'dark');
|
||||
}
|
||||
|
||||
toggleIcon.addEventListener('click', () => {
|
||||
if (body.classList.contains('dark-mode')) {
|
||||
setMode('light');
|
||||
localStorage.setItem('mode', 'light');
|
||||
} else {
|
||||
setMode('dark');
|
||||
localStorage.setItem('mode', 'dark');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
{literal}
|
||||
<script>
|
||||
var listAtts = document.querySelectorAll(`[api-get-text]`);
|
||||
listAtts.forEach(function(el) {
|
||||
$.get(el.getAttribute('api-get-text'), function(data) {
|
||||
el.innerHTML = data;
|
||||
});
|
||||
<script>
|
||||
var listAtts = document.querySelectorAll(`[api-get-text]`);
|
||||
listAtts.forEach(function (el) {
|
||||
$.get(el.getAttribute('api-get-text'), function (data) {
|
||||
el.innerHTML = data;
|
||||
});
|
||||
$(document).ready(function() {
|
||||
var listAtts = document.querySelectorAll(`button[type="submit"]`);
|
||||
listAtts.forEach(function(el) {
|
||||
if (el.addEventListener) { // all browsers except IE before version 9
|
||||
el.addEventListener("click", function() {
|
||||
});
|
||||
$(document).ready(function () {
|
||||
var listAtts = document.querySelectorAll(`button[type="submit"]`);
|
||||
listAtts.forEach(function (el) {
|
||||
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(
|
||||
`<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(
|
||||
`<span class="loading"></span>`
|
||||
);
|
||||
setTimeout(() => {
|
||||
$(this).prop("disabled", true);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
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() {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
});
|
||||
}, delay);
|
||||
});
|
||||
|
||||
function ask(field, text){
|
||||
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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
function setCookie(name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
function getCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
</script>
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
{/literal}
|
||||
<script>
|
||||
setCookie('user_language', '{$user_language}', 365);
|
||||
setCookie('user_language', '{$user_language}', 365);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<a class="toggle-container" href="#">
|
||||
<i class="toggle-icon" id="toggleIcon">🌞</i>
|
||||
<i class="toggle-icon" id="toggleIcon">🌜</i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown tasks-menu">
|
||||
@ -164,7 +164,7 @@
|
||||
<li {if $_system_menu eq 'history'}class="active" {/if}>
|
||||
<a href="{Text::url('order/history')}">
|
||||
<i class="fa fa-file-text"></i>
|
||||
<span>{Lang::T('Order History')}</span>
|
||||
<span>{Lang::T('Payment History')}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
|
@ -1,4 +1,8 @@
|
||||
{include file="customer/header.tpl"}
|
||||
{if empty($_user)}
|
||||
{include file="customer/header-public.tpl"}
|
||||
{else}
|
||||
{include file="customer/header.tpl"}
|
||||
{/if}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.4/jspdf.min.js"></script>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
@ -12,12 +16,16 @@
|
||||
<pre id="content"
|
||||
style="border: 0px; ;text-align: center; background-color: transparent; background-image: url('{$app_url}/system/uploads/paid.png');background-repeat:no-repeat;background-position: center">{$invoice}</pre>
|
||||
<input type="hidden" name="id" value="{$in['id']}">
|
||||
<a href="{Text::url('voucher/list-activated')}" class="btn btn-default btn-sm"><i
|
||||
{if !empty($_user)}
|
||||
<a href="{Text::url('voucher/list-activated')}" class="btn btn-default btn-sm"><i
|
||||
class="ion-reply-all"></i>{Lang::T('Finish')}</a>
|
||||
{/if}
|
||||
<a href="javascript:download()" class="btn btn-success btn-sm text-black">
|
||||
<i class="glyphicon glyphicon-share"></i> Download</a>
|
||||
<a href="https://api.whatsapp.com/send/?text={$whatsapp}" class="btn btn-primary btn-sm">
|
||||
<i class="glyphicon glyphicon-share"></i> WhatsApp</a>
|
||||
<br><br>
|
||||
<input type="text" class="form-control form-sm" style="border: 0px; padding: 1px; background-color: white;" readonly onclick="this.select()" value="{$public_url}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -37,7 +45,7 @@
|
||||
paid.src = '{$app_url}/system/uploads/paid.png';
|
||||
{if !empty($logo)}
|
||||
var img = new Image();
|
||||
img.src = '{$app_url}/{$logo}';
|
||||
img.src = '{$app_url}/{$logo}?{time()}';
|
||||
var new_width = (width / 4) * 2;
|
||||
var new_height = Math.ceil({$hlogo} * (new_width/{$wlogo}));
|
||||
height = height + new_height;
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2025.3.13"
|
||||
"version": "2025.3.20"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user