Compare commits

..

250 Commits

Author SHA1 Message Date
8f099bdefb Merge pull request 'Upload files to "/"' (#4) from nestict/mitrobill:master into master
Reviewed-on: kevinowino869/mitrobill#4
2025-05-14 14:50:13 +02:00
9fae0f84fd Upload files to "/" 2025-05-14 14:37:41 +02:00
d50a66845f Update README.md
Signed-off-by: kevinowino869 <kevinowino869@www.codelab.nestict.africa>
2025-04-14 11:01:36 +02:00
915af6fc03 Merge pull request 'Development' (#2) from Development into master
Reviewed-on: kevinowino869/mitrobill#2
2025-04-14 11:00:04 +02:00
0a29ec9a86 refactor: replace the javascript popup with sweetalert which prevent some devices to purchase data plans and packages via captive portal, clean up footer template and improve script formatting 2025-04-11 09:19:17 +01:00
27fd677a0a feat: add coupon settings panel with enable/disable option and save functionality 2025-04-10 08:13:01 +01:00
e2f24c0cc6 fix: correct subject field handling in message sending logic and update related IDs in bulk template 2025-04-09 20:16:11 +01:00
cf60c470b1 feat: add subject validation for message types and display error if missing 2025-04-09 12:50:30 +01:00
1cc7057dca feat: add subject field for messages and implement validation based on selected channel 2025-04-09 12:36:13 +01:00
1740c568f9 feat: add subject field for bulk messaging and update validation logic 2025-04-09 11:39:00 +01:00
3347b39f3b refactor: improve SMS and email handling in Message class; add success/failure return values and update language file with new translations 2025-04-09 10:29:22 +01:00
f0b9b56bb0 feat: add invoice listing page with DataTables integration and update routing 2025-04-08 18:59:15 +01:00
24b713804a feat: implement invoice listing functionality with DataTables integration 2025-04-08 18:30:36 +01:00
182add517c feat: implement custom invoice generation functionality 2025-04-08 17:45:18 +01:00
45cc2afab5 fix: update invoice parameter documentation for clarity 2025-04-08 14:30:48 +01:00
ba19b1c569 Added new functionality to Invoice class: generate invoice PDF, save to database, and send email. Also, updated language file with new translations. 2025-04-08 14:30:24 +01:00
5caa9f905b feat: add Font Awesome 6.4.0 stylesheet to admin header template 2025-04-04 08:08:34 +01:00
28541f366c feat: add fullname column to transaction reports and update related templates 2025-04-02 11:29:25 +01:00
ad7998ebbf feat: add fullname field to transaction reports and update report template 2025-04-02 11:23:17 +01:00
4c64cfabd2 fix: update demo stage check to use consistent casing 2025-04-02 11:11:05 +01:00
9bae41dbe7 fix: correct notification reminder configuration keys for 3-day and 7-day reminders 2025-03-30 11:26:40 +01:00
bad0545be5 feat: enhance messaging system to support multiple channels including email and inbox 2025-03-24 10:24:08 +01:00
43a92c5d3b feat: add fullname field to transaction reports, pdf export and update language file 2025-03-22 18:34:51 +01:00
11e5ebe103 feat: update .gitignore to include invoices directory and add index.html 2025-03-22 17:52:12 +01:00
c573c49fb9 "Added exception for system/uploads/invoices/ directory to .gitignore" 2025-03-22 17:41:04 +01:00
7bfbdb1efb feat: add fullname field to activation report and update language file 2025-03-22 16:15:16 +01:00
dc28298d53 feat: add username field to customer query 2025-03-22 15:51:36 +01:00
655e0494d3 Merge pull request #411 from dicobaja/patch/which-php
use `where` command for windows host
2025-03-20 15:37:30 +07:00
4a441c5763 fix variable inside ', it must be inside " 2025-03-20 10:43:52 +07:00
5072ff8ba2 fix: use where instead of which for windows host 2025-03-19 21:44:30 +07:00
d506dd66ff 2025.3.19 2025-03-19 14:20:00 +07:00
e9b0cfd8f0 Merge branch 'master' into Development 2025-03-19 14:19:27 +07:00
aa4dbc0cea Merge pull request #410 from dicobaja/patch/MR-cache-file
update path of monthly registered cache file
2025-03-19 14:18:44 +07:00
4ef054466d fix: update $cacheMRfile path to cache folder 2025-03-19 11:38:12 +07:00
41a3cbe700 Merge pull request #408 from dicobaja/patch/sql-tbl-port-pool
missing `tbl_port_pool` in phpnuxbill.sql
2025-03-19 09:18:42 +07:00
4938840c5d fix: missing tbl_port_pool in phpnuxbill.sql 2025-03-19 09:00:03 +07:00
127d43e45d Update .gitignore to include invoices directory and ensure paid.png is tracked 2025-03-18 17:35:43 +01:00
cdfbab7119 Refactor invoice handling to use 'invoice' key and improve payment link generation
Still in development
2025-03-18 16:51:37 +01:00
1a2b85ae4f Merge branch 'Development' of https://github.com/hotspotbilling/phpnuxbill into Development 2025-03-18 15:28:51 +01:00
2e2d967a5b Refactor Email invoice template handling and enhance payment link generation 2025-03-18 15:24:24 +01:00
c45e19189a Fix case sensitivity in invoice template file paths 2025-03-18 13:12:30 +01:00
d372bf4711 remove debug 2025-03-18 13:09:33 +01:00
8b8a0357f0 Fix custom login page image uploading 2025-03-18 13:08:19 +01:00
1cb0e30e6b Implement custom login page settings with validation and image upload support 2025-03-18 13:07:19 +01:00
20916b44f0 throw error if failed disconnect customer 2025-03-17 14:50:56 +07:00
c63545d33a invoice link 2025-03-17 14:48:01 +07:00
84500cdfc9 Add DejaVuSansCondensed-Oblique font to the project 2025-03-16 13:08:41 +01:00
5b21ffcde5 Add option to allow custom balance amounts and update toggle icon 2025-03-16 13:07:58 +01:00
009040cd3c Fix expired list widget 2025-03-14 10:27:33 +07:00
781481e118 Fix expired list widget 2025-03-14 10:26:47 +07:00
66d67cb61d Add email attachment support and improve message formatting 2025-03-13 11:05:32 +01:00
3372da2ac4 2025.3.13 new Invoice print 2025-03-13 17:04:07 +07:00
803d04a91d add fullname to log extend 2025-03-13 15:59:55 +07:00
17de653752 paid icon 2025-03-13 15:22:25 +07:00
9301f1058c save pdf invoice customer from admin page 2025-03-13 15:07:15 +07:00
0868d61271 Download PDF invoice Customers 2025-03-13 15:07:15 +07:00
5f353392e3 Merge pull request #407 from ahmadhusein17/Development
Fix Text
2025-03-13 15:06:52 +07:00
fdd8dad509 Update indonesia.json 2025-03-12 00:29:50 +07:00
3563fa531b Update community.tpl
Minor fixes to make it look better
2025-03-12 00:24:11 +07:00
525f2311fc Update community.tpl
Update a bit to make it look better
2025-03-12 00:18:46 +07:00
3b6a6d2f55 Update recharge-confirm.tpl 2025-03-11 23:10:52 +07:00
3cebfa2171 Refactor login and registration templates to support dynamic logo, wallpaper, and favicon loading; change footer position to fixed 2025-03-11 15:50:06 +01:00
c65b569f94 Enhance mobile responsiveness and improve touch device support in login template 2025-03-11 09:20:00 +01:00
ca5a7d60cf API Rest Documentation? 2025-03-11 14:51:18 +07:00
5987ffafce Insomnia API Rest Collection 2025-03-11 14:39:56 +07:00
d7bbb4d18f Fix API request 2025-03-11 14:39:05 +07:00
e3c173bea4 Different expired message 2025-03-11 12:29:46 +07:00
78e1e2f989 add new Docs wiki 2025-03-11 12:28:22 +07:00
3c7e6c7a64 Different Reminder for PPPOE and Hotspot using <divider> 2025-03-11 11:55:48 +07:00
827bb8706d 2025.3.10 2025-03-10 13:53:43 +07:00
4731a506bc only admin can recharge Rp. 0 2025-03-10 13:53:02 +07:00
597c051948 Telegram message to topik 2025-03-10 13:35:54 +07:00
40d3127ca1 Merge pull request #406 from gerandonk/Development
add [[bills]] for invoice message
2025-03-10 12:34:25 +07:00
c75133c9d2 add [[bills]] for invoice message 2025-03-10 03:30:27 +07:00
ed3369244c Merge pull request #405 from gerandonk/Development
bandwith 0=unlimited
2025-03-08 21:05:51 +07:00
cf9abbfb45 bandwith 0=unlimited
add bandwith 0=unlimited or null for device MikrotikHotspot and MikrotikPppoe
2025-03-08 18:01:21 +07:00
c74d2bf7ec Merge pull request #404 from ahmadhusein17/Development
Development
2025-03-06 18:51:24 +01:00
ec9a06f468 feat: add service type selection and display in message bulk management 2025-03-06 18:44:20 +01:00
4e3d89a23c feat: add message logging functionality with CSV export and management 2025-03-06 11:45:45 +01:00
30ca1d1b2d Update recharge_a_friend.tpl 2025-03-06 00:04:44 +07:00
1cbff8f9a4 Update account_info.tpl 2025-03-06 00:03:06 +07:00
43b1025d3c refactor: enhance code formatting and add demo mode check for actions 2025-03-05 15:02:58 +01:00
da14a7bfef refactor: improve code readability by formatting conditional statements and adding demo mode checks 2025-03-05 13:59:43 +01:00
8429ed763e remove widget html_only, add widget html_php_card 2025-03-04 16:14:10 +07:00
3b7d478635 fix method query 2025-03-04 15:16:48 +07:00
ee3dcc05a0 fix reports by methods 2025-03-04 13:37:52 +07:00
1e7d9cf6ef Merge branch 'master' into Development 2025-03-03 14:28:27 +07:00
986016083b routeros 7.18 changelog "has put !empty sentence when API query returns nothing"; so i try to add that type, maybe works 2025-03-03 14:28:18 +07:00
c95de08ed8 ignore .idea 2025-03-03 10:15:53 +07:00
d657632876 remove .idea 2025-03-03 10:15:43 +07:00
d8dbe68f51 Update notification reminder conditions to allow for more flexible configuration 2025-03-02 15:36:14 +01:00
366ef73d57 Add notification reminder settings and improve PHP configuration 2025-03-02 15:29:57 +01:00
dfdf35286f Merge branch 'master' into Development 2025-02-26 10:12:26 +07:00
eea99d218d 6,6 2025-02-26 10:11:44 +07:00
08d1de563e 6.6 2025-02-26 10:11:44 +07:00
51a0b859ab 2025.2.25 2025-02-26 10:11:44 +07:00
47a57912ba Default widget for agent and sales 2025-02-26 10:11:44 +07:00
7bbaee1b88 Update indonesia.json 2025-02-26 10:11:44 +07:00
791e6d55d0 Update indonesia.json 2025-02-26 10:11:44 +07:00
4564ef90d5 Update system.tpl 2025-02-26 10:11:44 +07:00
23524e4c1f Update widgets_add_edit.tpl 2025-02-26 10:11:44 +07:00
c486acaaf6 Update notifications.tpl 2025-02-26 10:11:43 +07:00
e3476a971c Update widgets_add_edit.tpl 2025-02-26 10:11:43 +07:00
b9ccc7561c Update widgets.tpl 2025-02-26 10:11:43 +07:00
5a1bb441af Update active.tpl 2025-02-26 10:11:43 +07:00
19a48c0c88 Widget For Customer 2025-02-26 10:11:43 +07:00
8d2c334da0 Different Widget for Admin, Agent, Sales, Customer. Agent, Sales not yet have widget 2025-02-26 10:11:43 +07:00
9163f02717 sql default widget for customer 2025-02-26 10:11:43 +07:00
da86f2c422 Different Widget for admin, Agent, Sales and Customers Editor 2025-02-26 10:11:43 +07:00
f929560384 fix pppoe_username accounting radius rest 2025-02-26 10:11:43 +07:00
1387f32865 add user in the Table Widget 2025-02-26 10:11:43 +07:00
a39ff8a9a4 Update widgets_add_edit.tpl 2025-02-26 10:11:43 +07:00
eb96c7a1e4 Update widgets.tpl 2025-02-26 10:11:42 +07:00
83a2505eed 6,6 2025-02-25 14:43:31 +07:00
e91fbe89ba 6.6 2025-02-25 14:42:44 +07:00
925097c0b2 2025.2.25 2025-02-25 14:38:51 +07:00
6cd46cdf7c Default widget for agent and sales 2025-02-25 14:38:35 +07:00
9b331813d1 Merge pull request #402 from ahmadhusein17/Development
Development
2025-02-25 14:38:04 +07:00
1e500d8941 Update indonesia.json 2025-02-21 19:47:15 +07:00
c433363d31 Update indonesia.json 2025-02-21 19:36:42 +07:00
3901213a73 Update system.tpl 2025-02-21 18:58:54 +07:00
8860ad1d13 Update widgets_add_edit.tpl 2025-02-21 18:53:01 +07:00
5df32c7119 Update notifications.tpl 2025-02-21 18:46:55 +07:00
08be62053e Update widgets_add_edit.tpl 2025-02-21 18:42:14 +07:00
71bf301ccf Update widgets.tpl 2025-02-21 18:37:29 +07:00
0e32f128af Update active.tpl 2025-02-21 18:32:32 +07:00
686f3de2d3 Widget For Customer 2025-02-21 16:04:53 +07:00
b379266973 Different Widget for Admin, Agent, Sales, Customer. Agent, Sales not yet have widget 2025-02-21 16:04:53 +07:00
9332063c87 sql default widget for customer 2025-02-21 16:04:18 +07:00
db4c643f93 Different Widget for admin, Agent, Sales and Customers Editor 2025-02-21 16:04:18 +07:00
1a2f2e24cd fix pppoe_username accounting radius rest 2025-02-21 16:04:18 +07:00
92ce363b16 add user in the Table Widget 2025-02-21 16:04:18 +07:00
5ddbf8258c Translation into Spanish 2025-02-21 16:04:18 +07:00
4105ad0b8b Translation into Spanish 2025-02-21 16:04:18 +07:00
af22a26c4c Update add.tpl 2025-02-21 16:04:18 +07:00
a2ade795c7 Delete .idea/misc.xml 2025-02-21 16:04:18 +07:00
7b36a46c50 Delete .idea/misc.xml
Delete .idea/vcs.xml

Delete .idea/phpnuxbill.iml

Delete .idea/modules.xml

Revert "Delete .idea/misc.xml"

This reverts commit e9a947a24a.
2025-02-21 16:04:18 +07:00
fa9fe241e2 Translation into Spanish 2025-02-21 16:04:18 +07:00
1aa4110552 Merge pull request #401 from ormendoza/Development
Translation into Spanish
2025-02-21 16:03:12 +07:00
8917b2ab7b Merge pull request #400 from ahmadhusein17/Development
Update translation
2025-02-21 10:44:35 +07:00
8238061510 Translation into Spanish 2025-02-20 18:25:47 -04:00
096b4fb3af Translation into Spanish 2025-02-19 11:05:59 -04:00
5d3eea1f45 Update add.tpl 2025-02-19 09:54:30 -04:00
ee6098774a Merge remote-tracking branch 'origin/Development' into Development 2025-02-19 09:53:49 -04:00
d272e572c0 Delete .idea/modules.xml 2025-02-19 10:41:42 -04:00
2be111e9df Delete .idea/phpnuxbill.iml 2025-02-19 10:41:26 -04:00
6cf7d6b5de Delete .idea/vcs.xml 2025-02-19 10:41:10 -04:00
e9a947a24a Delete .idea/misc.xml 2025-02-19 10:40:41 -04:00
20c0543143 Delete .idea/misc.xml
Delete .idea/vcs.xml

Delete .idea/phpnuxbill.iml

Delete .idea/modules.xml

Revert "Delete .idea/misc.xml"

This reverts commit e9a947a24a.
2025-02-19 09:43:31 -04:00
cd1034c3ab Translation into Spanish 2025-02-19 09:36:28 -04:00
f9a938830d Update widgets_add_edit.tpl 2025-02-18 17:41:13 +07:00
5b79432f9a Update widgets.tpl 2025-02-18 17:37:12 +07:00
023b4884d1 full widget support 2025-02-18 15:52:53 +07:00
fca86ac4dc remove hidden dashboard content on settings 2025-02-18 15:52:41 +07:00
ef51d833d8 fix link 2025-02-18 15:50:06 +07:00
c05a943ff5 add widget settings 2025-02-18 15:49:46 +07:00
693d7f4acf widget multiple section 2025-02-18 14:28:55 +07:00
5b9cdd6681 reformat code 2025-02-18 14:28:32 +07:00
a013373554 default widget value 2025-02-17 14:40:47 +07:00
aeae936f17 Spanish Translation by @ORConsulTech 2025-02-17 14:40:33 +07:00
ea203ebf64 widget add edit 2025-02-17 14:39:23 +07:00
da0779e368 panel-footer color 2025-02-17 14:39:02 +07:00
4bb5361d4b default widgets 2025-02-17 13:54:30 +07:00
69a7d842e0 Merge pull request #399 from ahmadhusein17/Development
Development
2025-02-17 13:46:03 +07:00
fefcd56801 Update change-password.tpl 2025-02-16 20:07:46 +07:00
5f0df84fe7 Update activation.tpl 2025-02-16 19:59:57 +07:00
80d213bea3 Update activation-list.tpl 2025-02-16 19:57:42 +07:00
0b52d3eb35 Update inbox.tpl 2025-02-16 19:51:03 +07:00
569d5e3670 Update dbstatus.tpl 2025-02-16 19:19:10 +07:00
7396bcdddf Update customfield.tpl 2025-02-16 19:05:22 +07:00
eee1ad72f8 Update customfield.tpl 2025-02-16 18:59:38 +07:00
2b80f5e6f1 Update widgets.tpl 2025-02-16 18:41:35 +07:00
f98aecc61f Update widgets_add_edit.tpl 2025-02-16 18:29:50 +07:00
4cc85b9261 add tbl_widgets 2025-02-14 17:55:56 +07:00
eff0c7dab7 getting ready for customizeable dashboard with widget 2025-02-14 17:08:05 +07:00
30bdb89d91 fix save settings 2025-02-14 10:07:12 +07:00
8fc7afb173 Merge branch 'Development' of https://github.com/hotspotbilling/phpnuxbill into Development 2025-02-12 08:12:11 +01:00
fa5be4c196 delete debug 2025-02-12 11:56:18 +07:00
304de7a999 fix 404 2025-02-12 11:50:04 +07:00
1ad1320779 Fix setting save 2025-02-12 11:35:20 +07:00
a3f66fdd84 update toggle icon for dark mode switch 2025-02-11 18:30:23 +01:00
86b18e27cd Merge branch 'master' into Development 2025-02-11 13:30:14 +07:00
535a44bb54 2025.2.11 2025-02-11 13:29:23 +07:00
929f75dc4d add connect and wait timeout 2025-02-11 13:29:22 +07:00
8e7a1dc0f2 2025.2.11 2025-02-11 13:27:46 +07:00
6865d321c3 add connect and wait timeout 2025-02-11 13:07:53 +07:00
75d6f17eb5 fix csrf token 2025-02-09 18:37:19 +01:00
4bc47a8d85 fix language 2025-02-09 16:37:13 +01:00
0a3205915f Enhancement and Improvements
Refactor CSRF class: improve token handling and update session variable names

Replace bulk message with ajax based message sending.
Added support for multiple recipients in bulk message, and also router based filtering.

Add support for multiple recipients in bulk message from customer list as requested by one of our Member. you can now send messages to multiple recipients at once from customer list.

Added Exception for CRON but not tested yet. i dont have multiple routers.
Added notify to know if cron has been executed or not.
2025-02-09 16:06:59 +01:00
60d945d87f Merge pull request #397 from gerandonk/Development
fix postpaid invoice display from attributes
2025-02-09 10:03:24 +07:00
685b325ef3 fix postpaid invoice display from attributes 2025-02-09 01:23:06 +07:00
e0884c0a5a 2025.2.7 2025-02-07 15:19:04 +07:00
b6fadae2e5 fix cookies 2025-02-07 15:18:50 +07:00
91271b9f00 Fix sub folder 2025-02-07 15:18:41 +07:00
a2237390e0 fix redirect to install 2025-02-06 11:31:52 +07:00
b3169a2060 Merge branch 'Development' 2025-02-05 16:38:37 +07:00
b3f4edb2d7 remove debug 2025-02-05 16:38:09 +07:00
f301382a72 finishing 2025-02-05 16:38:09 +07:00
92f2caeece 2025.2.5 2025-02-05 16:38:09 +07:00
9fd8feb16f fix javascript 2025-02-05 16:38:09 +07:00
ee022781b1 fix orderplan 2025-02-05 16:38:09 +07:00
85c5441934 Update Barcode Scan UI 2025-02-05 16:38:09 +07:00
9cc7c0c811 pretty url stage 8 2025-02-05 16:38:09 +07:00
7f785a7c4a pretty url stage 7, thanks regex 2025-02-05 16:38:09 +07:00
05aa1499ab pretty url stage 6 2025-02-05 16:38:09 +07:00
2469aa6b99 pretty url stage 5 2025-02-05 16:38:09 +07:00
4d71c1d48e pretty url stage 4 2025-02-05 16:38:09 +07:00
bdef2b62c3 fix test wa email sms 2025-02-05 16:38:09 +07:00
1cae09763f Update 404.tpl 2025-02-05 16:38:08 +07:00
4ba7c14693 Update 404.tpl 2025-02-05 16:38:08 +07:00
aa9a2e4277 Update login.tpl 2025-02-05 16:38:07 +07:00
bd87f0143b remove debug 2025-02-05 16:25:37 +07:00
8c94e9ea01 finishing 2025-02-05 16:25:03 +07:00
d09e657ed5 2025.2.5 2025-02-05 16:10:40 +07:00
fbe541ef9c fix javascript 2025-02-05 16:02:40 +07:00
5feeb2625e fix orderplan 2025-02-05 15:49:02 +07:00
0d116fcb5b Merge pull request #396 from ahmadhusein17/Development
Update Barcode Scan UI
2025-02-05 15:44:32 +07:00
8d704f496e pretty url stage 8 2025-02-05 15:44:06 +07:00
94569bca0e Update Barcode Scan UI 2025-02-05 15:29:26 +07:00
129302d558 pretty url stage 7, thanks regex 2025-02-05 14:45:04 +07:00
56762ef277 pretty url stage 6 2025-02-05 14:13:21 +07:00
75880e8366 pretty url stage 5 2025-02-05 09:48:05 +07:00
cbacb1c52f pretty url stage 4 2025-02-05 09:40:02 +07:00
01d966082c fix test wa email sms 2025-02-05 09:39:06 +07:00
e4124bd210 Merge pull request #395 from ahmadhusein17/patch-2
Update 404.tpl
2025-02-05 09:38:41 +07:00
0d4fde6481 Merge pull request #394 from ahmadhusein17/patch-1
Update 404.tpl
2025-02-05 09:38:34 +07:00
6790fdd6d4 Merge pull request #393 from ahmadhusein17/patch-3
Update login.tpl
2025-02-05 09:38:25 +07:00
40d8a4edfc Merge pull request #392 from ahmadhusein17/Development
Back button tidied up
2025-02-05 09:38:20 +07:00
09eedf4999 Update login.tpl 2025-02-04 22:09:35 +07:00
9adf1412bc Update 404.tpl 2025-02-04 21:57:26 +07:00
d3e813e7df Update 404.tpl 2025-02-04 21:48:26 +07:00
6ccabf8b45 Merge pull request #391 from gerandonk/Development
add [[payment_link]] for single message
2025-02-04 20:03:38 +07:00
af064ac5bb Merge pull request #390 from ahmadhusein17/Development
Development
2025-02-04 20:02:49 +07:00
0ec7176d1e add [[payment_link]] for single message
add [[payment_link]] for single message to able send payment link manualy to customer.
2025-02-04 19:44:06 +07:00
c594cf28bb Merge branch 'hotspotbilling:Development' into Development 2025-02-04 18:03:44 +07:00
2a038d63bb Update login.tpl 2025-02-04 18:03:24 +07:00
7170b63890 Merge pull request #389 from ahmadhusein17/Development
Update indonesia.json
2025-02-04 17:42:06 +07:00
d44c58f610 Update indonesia.json 2025-02-04 17:31:08 +07:00
10adbe48ab fix url recharge and voucher 2025-02-04 17:22:11 +07:00
b3cb6de028 2025.2.4 2025-02-04 17:13:30 +07:00
96dca1a38b fix order/gateway url 2025-02-04 16:53:30 +07:00
face7360e8 fix logout button 2025-02-04 16:06:05 +07:00
f520352bc9 remove maps menu under Network 2025-02-04 15:50:15 +07:00
a81bb5c4f5 Fix bulk Message delay and no phone number 2025-02-04 15:29:29 +07:00
8d9919afa7 fix bulk broadcasting message 2025-02-04 15:13:42 +07:00
3386b17b1b fix maps marker 2025-02-04 13:51:43 +07:00
60c7f61579 strip unused js css 2025-02-04 13:50:43 +07:00
fa05ffa58b stage 3 restructure ui folder 2025-02-04 10:56:02 +07:00
fa3e256174 stage 2 restructured ui folder 2025-02-04 10:22:14 +07:00
0346a843ea change 404 header footer and admin tpl 2025-02-04 09:23:55 +07:00
278 changed files with 13051 additions and 25127 deletions

8
.gitignore vendored
View File

@ -53,4 +53,10 @@ docker-compose.yml
docs/**
!docs/*.html
!docs/*.md
.htaccess
.htaccess
.idea
!docs/insomnia.rest.json
!system/uploads/paid.png
system/uploads/invoices/**
!system/uploads/invoices/
!system/uploads/invoices/index.html

View File

@ -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
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](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

File diff suppressed because one or more lines are too long

1
docs/insomnia.rest.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -42,7 +42,7 @@ spl_autoload_register('_autoloader');
if (!file_exists($root_path . 'config.php')) {
$root_path .= '..' . DIRECTORY_SEPARATOR;
if (!file_exists($root_path . 'config.php')) {
r2(getUrl('install'));
r2('./install');
}
}
@ -56,6 +56,7 @@ $UPLOAD_PATH = $root_path . File::pathFixer('system/uploads');
$CACHE_PATH = $root_path . File::pathFixer('system/cache');
$PAGES_PATH = $root_path . File::pathFixer('pages');
$PLUGIN_PATH = $root_path . File::pathFixer('system/plugin');
$WIDGET_PATH = $root_path . File::pathFixer('system/widgets');
$PAYMENTGATEWAY_PATH = $root_path . File::pathFixer('system/paymentgateway');
$UI_PATH = 'ui';
@ -111,6 +112,24 @@ $result = ORM::for_table('tbl_appconfig')->find_many();
foreach ($result as $value) {
$config[$value['setting']] = $value['value'];
}
if(empty($config['dashboard_Admin'])){
$config['dashboard_Admin'] = "12.7,5.12";
}
if(empty($config['dashboard_Agent'])){
$config['dashboard_Agent'] = "12.7,5.12";
}
if(empty($config['dashboard_Sales'])){
$config['dashboard_Sales'] = "12.7,5.12";
}
if(empty($config['dashboard_Customer'])){
$config['dashboard_Customer'] = "6,6";
}
$_c = $config;
if (empty($http_proxy) && !empty($config['http_proxy'])) {
$http_proxy = $config['http_proxy'];
@ -349,7 +368,7 @@ function _alert($text, $type = 'success', $url = "home", $time = 3)
$ui->assign('type', $type);
$ui->assign('time', $time);
$ui->assign('url', $url);
$ui->display('alert.tpl');
$ui->display('admin/alert.tpl');
die();
}
@ -367,7 +386,7 @@ function displayMaintenanceMessage(): void
}
http_response_code(503);
$ui->assign('companyName', $config['CompanyName']);
$ui->display('maintenance.tpl');
$ui->display('admin/maintenance.tpl');
die();
}

View File

@ -1374,7 +1374,7 @@ pre {
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
background-color: #f6f8fa;
border: 1px solid #ccc;
border-radius: 4px;
}
@ -2126,7 +2126,7 @@ th {
}
.table-hover > tbody > tr:hover > td,
.table-hover > tbody > tr:hover > th {
background-color: #f5f5f5;
background-color: #f6f8fa;
}
table col[class*="col-"] {
position: static;
@ -2151,7 +2151,7 @@ table th[class*="col-"] {
.table > thead > tr.active > th,
.table > tbody > tr.active > th,
.table > tfoot > tr.active > th {
background-color: #f5f5f5;
background-color: #f6f8fa;
}
.table-hover > tbody > tr > td.active:hover,
.table-hover > tbody > tr > th.active:hover,
@ -3170,7 +3170,7 @@ tbody.collapse.in {
.dropdown-menu > li > a:focus {
color: #262626;
text-decoration: none;
background-color: #f5f5f5;
background-color: #f6f8fa;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
@ -4293,7 +4293,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
padding: 8px 15px;
margin-bottom: 20px;
list-style: none;
background-color: #f5f5f5;
background-color: #f6f8fa;
border-radius: 4px;
}
.breadcrumb > li {
@ -4711,7 +4711,7 @@ a.thumbnail.active {
height: 20px;
margin-bottom: 20px;
overflow: hidden;
background-color: #f5f5f5;
background-color: #f6f8fa;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
@ -4854,7 +4854,7 @@ a.list-group-item:hover,
a.list-group-item:focus {
color: #555;
text-decoration: none;
background-color: #f5f5f5;
background-color: #f6f8fa;
}
.list-group-item.disabled,
.list-group-item.disabled:hover,
@ -5023,7 +5023,7 @@ a.list-group-item-danger.active:focus {
}
.panel-footer {
padding: 10px 15px;
background-color: #f5f5f5;
background-color: #f6f8fa;
border-top: 1px solid #ddd;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
@ -5197,7 +5197,7 @@ a.list-group-item-danger.active:focus {
}
.panel-default > .panel-heading {
color: #333;
background-color: #f5f5f5;
background-color: #f6f8fa;
border-color: #ddd;
}
.panel-default > .panel-heading + .panel-collapse > .panel-body {
@ -5329,7 +5329,7 @@ a.list-group-item-danger.active:focus {
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
background-color: #f6f8fa;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);

View File

@ -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',
@ -277,6 +287,28 @@ CREATE TABLE IF NOT EXISTS `tbl_coupons` (
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
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',
`user` ENUM('Admin','Agent','Sales','Customer') NOT NULL DEFAULT 'Admin',
`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;
CREATE TABLE 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;
ALTER TABLE `rad_acct`
ADD PRIMARY KEY (`id`),
ADD KEY `username` (`username`),
@ -397,3 +429,48 @@ VALUES (
'2022-09-06 16:09:50',
'2014-06-23 01:43:07'
);
INSERT INTO `tbl_widgets` (`id`, `orders`, `position`, `user`, `enabled`, `title`, `widget`, `content`) VALUES
(1, 1, 1, 'Admin', 1, 'Top Widget', 'top_widget', ''),
(2, 2, 1, 'Admin', 1, 'Default Info', 'default_info_row', ''),
(3, 1, 2, 'Admin', 1, 'Graph Monthly Registered Customers', 'graph_monthly_registered_customers', ''),
(4, 2, 2, 'Admin', 1, 'Graph Monthly Sales', 'graph_monthly_sales', ''),
(5, 3, 2, 'Admin', 1, 'Voucher Stocks', 'voucher_stocks', ''),
(6, 4, 2, 'Admin', 1, 'Customer Expired', 'customer_expired', ''),
(7, 1, 3, 'Admin', 1, 'Cron Monitor', 'cron_monitor', ''),
(8, 2, 3, 'Admin', 1, 'Mikrotik Cron Monitor', 'mikrotik_cron_monitor', ''),
(9, 3, 3, 'Admin', 1, 'Info Payment Gateway', 'info_payment_gateway', ''),
(10, 4, 3, 'Admin', 1, 'Graph Customers Insight', 'graph_customers_insight', ''),
(11, 5, 3, 'Admin', 1, 'Activity Log', 'activity_log', ''),
(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', ''),
(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', ''),
(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', '');

643
login.html Normal file
View 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">&copy; 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>

View File

@ -207,7 +207,7 @@ try {
if (!$tur) {
// if check if pppoe_username
$c = ORM::for_table('tbl_customers')->select('username')->select('pppoe_password')->whereRaw("BINARY pppoe_username = '$username'")->find_one();
if($c){
if ($c) {
$username = $c['username'];
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY username = '$username'")->find_one();
}
@ -274,7 +274,7 @@ try {
}
header("HTTP/1.1 200 ok");
$d = ORM::for_table('rad_acct')
->whereRaw("BINARY username = '$username' AND macaddr = '"._post('macAddr')."' AND nasid = '"._post('nasid')."'")
->whereRaw("BINARY username = '$username' AND macaddr = '" . _post('macAddr') . "' AND nasid = '" . _post('nasid') . "'")
->findOne();
if (!$d) {
$d = ORM::for_table('rad_acct')->create();
@ -292,19 +292,27 @@ try {
$d->username = $username;
$d->realm = _post('realm');
$d->nasipaddress = _post('nasIpAddress');
$d->acctsessiontime = intval(_post('acctSessionTime'));
$d->acctsessiontime = intval(_post('acctSessionTime'));
$d->nasid = _post('nasid');
$d->nasportid = _post('nasPortId');
$d->nasporttype = _post('nasPortType');
$d->framedipaddress = _post('framedIPAddress');
if(in_array(_post('acctStatusType'), ['Start', 'Stop'])){
if (in_array(_post('acctStatusType'), ['Start', 'Stop'])) {
$d->acctstatustype = _post('acctStatusType');
}
$d->macaddr = _post('macAddr');
$d->dateAdded = date('Y-m-d H:i:s');
// pastikan data akunting yang disimpan memang customer aktif phpnuxbill
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY username = '$username' AND `status` = 'on' AND `routers` = 'radius'")->find_one();
if($tur){
if (!$tur) {
// check if pppoe_username
$c = ORM::for_table('tbl_customers')->select('username')->whereRaw("BINARY pppoe_username = '$username'")->find_one();
if ($c) {
$username = $c['username'];
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY username = '$username'")->find_one();
}
}
if ($tur) {
$d->save();
if (_post('acctStatusType') == 'Start') {
$plan = ORM::for_table('tbl_plans')->where('id', $tur['plan_id'])->find_one();
@ -349,15 +357,15 @@ function process_radiust_rest($tur, $code)
$plan = ORM::for_table('tbl_plans')->where('id', $tur['plan_id'])->find_one();
$bw = ORM::for_table("tbl_bandwidth")->find_one($plan['id_bw']);
// Count User Onlines
$USRon = ORM::for_table('rad_acct')
->whereRaw("BINARY username = '".$tur['username']."' AND acctStatusType = 'Start'")
$USRon = ORM::for_table('rad_acct')
->whereRaw("BINARY username = '" . $tur['username'] . "' AND acctStatusType = 'Start'")
->find_array();
// get all the IP
$ips = array_column($USRon, 'framedipaddress');
// check if user reach shared_users limit but IP is not in the list active
if (count($USRon) >= $plan['shared_users'] && $plan['type'] == 'Hotspot' && !in_array(_post('framedIPAddress'), $ips)) {
show_radius_result(["control:Auth-Type" => "Accept", 'Reply-Message' => 'You are already logged in - access denied ('.$USRon.')'], 401);
}
if (count($USRon) >= $plan['shared_users'] && $plan['type'] == 'Hotspot' && !in_array(_post('framedIPAddress'), $ips)) {
show_radius_result(["control:Auth-Type" => "Accept", 'Reply-Message' => 'You are already logged in - access denied (' . $USRon . ')'], 401);
}
if ($bw['rate_down_unit'] == 'Kbps') {
$unitdown = 'K';
} else {

View File

@ -4,25 +4,66 @@
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>QRCode Scanner</title>
<title>QR Code Scanner</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
#container {
background-color: #ffffff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
#qr-reader {
width: 100%;
height: auto;
margin-bottom: 20px;
}
#qr-reader-results {
padding: 10px;
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 5px;
}
button {
margin-top: 30px;
margin-bottom: 30px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div id="qr-reader" style="width:100%; height:auto"></div>
<div id="qr-reader-results"></div>
<div id="container">
<h2>QR Code Scanner</h2>
<div id="qr-reader"></div>
<div id="qr-reader-results"></div>
<button id="camera-button">Open Camera</button>
</div>
<script src="qrcode.min.js"></script>
<script>
function docReady(fn) {
// see if DOM is already available
// lihat apakah DOM sudah tersedia
if (document.readyState === "complete" ||
document.readyState === "interactive") {
// call on next available tick
// panggil di detik berikutnya yang tersedia
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
@ -51,6 +92,9 @@
docReady(function() {
var resultContainer = document.getElementById('qr-reader-results');
var lastResult, countResults = 0;
var html5QrcodeScanner;
var isCameraOpen = false;
function onScanSuccess(decodedText, decodedResult) {
if (decodedText !== lastResult) {
++countResults;
@ -67,14 +111,26 @@
}
}
var html5QrcodeScanner = new Html5QrcodeScanner(
"qr-reader", {
fps: 10,
qrbox: 250
});
html5QrcodeScanner.render(onScanSuccess);
function toggleCamera() {
if (isCameraOpen) {
html5QrcodeScanner.clear();
document.getElementById('camera-button').textContent = "Open Camera";
isCameraOpen = false;
} else {
html5QrcodeScanner = new Html5QrcodeScanner(
"qr-reader", {
fps: 10,
qrbox: 250
});
html5QrcodeScanner.render(onScanSuccess);
document.getElementById('camera-button').textContent = "Close Camera";
isCameraOpen = true;
}
}
document.getElementById('camera-button').addEventListener('click', toggleCamera);
});
</script>
</body>
</html>
</html>

View File

@ -48,7 +48,28 @@ $ui = new class($key)
}
function getAll()
{
return $this->assign;
$result = [];
foreach ($this->assign as $key => $value) {
if($value instanceof ORM){
$result[$key] = $value->as_array();
}else if($value instanceof IdiormResultSet){
$count = count($value);
for($n=0;$n<$count;$n++){
foreach ($value[$n] as $k=>$v) {
$result[$key][$n][$k] = $v;
}
}
}else{
$result[$key] = $value;
}
}
return $result;
}
function fetch()
{
return "";
}
};

View File

@ -22,8 +22,8 @@ class Csrf
public static function check($token)
{
global $config;
if($config['csrf_enabled'] == 'yes') {
global $config, $isApi;
if($config['csrf_enabled'] == 'yes' && !$isApi) {
if (isset($_SESSION['csrf_token'], $_SESSION['csrf_token_time'], $token)) {
$storedToken = $_SESSION['csrf_token'];
$tokenTime = $_SESSION['csrf_token_time'];

View File

@ -87,7 +87,7 @@ class File
$src_img = $image_create($source_file);
imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $nwidth, $nheight, $width, $height);
$image($dst_img, $dst_dir, $quality);
imagepng($dst_img, $dst_dir);
if ($dst_img) imagedestroy($dst_img);
if ($src_img) imagedestroy($src_img);

View File

@ -14,14 +14,14 @@
class Http
{
public static function getData($url, $headers = [])
public static function getData($url, $headers = [], $connect_timeout = 3000, $wait_timeout = 3000)
{
global $http_proxy, $http_proxyauth, $admin;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 100);
curl_setopt($ch, CURLOPT_TIMEOUT, 100);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $wait_timeout);
if (is_array($headers) && count($headers) > 0) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
@ -49,15 +49,15 @@ class Http
return (!empty($server_output)) ? $server_output : $error_msg;
}
public static function postJsonData($url, $array_post, $headers = [], $basic = null)
public static function postJsonData($url, $array_post, $headers = [], $basic = null, $connect_timeout = 3000, $wait_timeout = 3000)
{
global $http_proxy, $http_proxyauth, $admin;
$headers[] = 'Content-Type: application/json';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 100);
curl_setopt($ch, CURLOPT_TIMEOUT, 100);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $wait_timeout);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLINFO_HEADER_OUT, false);
if (!empty($http_proxy)) {
@ -92,15 +92,15 @@ class Http
}
public static function postData($url, $array_post, $headers = [], $basic = null)
public static function postData($url, $array_post, $headers = [], $basic = null, $connect_timeout = 3000, $wait_timeout = 3000)
{
global $http_proxy, $http_proxyauth, $admin;
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 100);
curl_setopt($ch, CURLOPT_TIMEOUT, 100);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $wait_timeout);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLINFO_HEADER_OUT, false);
if (!empty($http_proxy)) {

347
system/autoload/Invoice.php Normal file
View 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;
}
}

View File

@ -17,15 +17,18 @@ require $root_path . 'system/autoload/mail/SMTP.php';
class Message
{
public static function sendTelegram($txt, $chat_id = null)
public static function sendTelegram($txt, $chat_id = null, $topik = '')
{
global $config;
run_hook('send_telegram', [$txt, $chat_id = null]); #HOOK
run_hook('send_telegram', [$txt, $chat_id, $topik]); #HOOK
if (!empty($config['telegram_bot'])) {
if (empty($chat_id)) {
$chat_id = $config['telegram_target_id'];
}
return Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?chat_id=' . $chat_id . '&text=' . urlencode($txt));
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));
}
}
@ -43,24 +46,32 @@ 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 (Exception $e) {
} catch (Throwable $e) {
// ignore, add to logs
_log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
self::logMessage('SMS', $phone, $txt, 'Error', $e->getMessage());
}
} else {
try {
self::MikrotikSendSMS($config['sms_url'], $phone, $txt);
} catch (Exception $e) {
self::logMessage('MikroTikSMS', $phone, $txt, 'Success');
} catch (Throwable $e) {
// ignore, add to logs
_log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
self::logMessage('MikroTikSMS', $phone, $txt, 'Error', $e->getMessage());
}
}
} else {
$smsurl = str_replace('[number]', urlencode($phone), $config['sms_url']);
$smsurl = str_replace('[text]', urlencode($txt), $smsurl);
return Http::getData($smsurl);
try {
$response = Http::getData($smsurl);
self::logMessage('SMS HTTP Response', $phone, $txt, 'Success', $response);
return $response;
} catch (Throwable $e) {
self::logMessage('SMS HTTP Request', $phone, $txt, 'Error', $e->getMessage());
}
}
}
}
@ -92,15 +103,24 @@ class Message
if (empty($txt)) {
return "kosong";
}
run_hook('send_whatsapp', [$phone, $txt]); #HOOK
run_hook('send_whatsapp', [$phone, $txt]); // HOOK
if (!empty($config['wa_url'])) {
$waurl = str_replace('[number]', urlencode(Lang::phoneFormat($phone)), $config['wa_url']);
$waurl = str_replace('[text]', urlencode($txt), $waurl);
return Http::getData($waurl);
try {
$response = Http::getData($waurl);
self::logMessage('WhatsApp HTTP Response', $phone, $txt, 'Success', $response);
return $response;
} catch (Throwable $e) {
self::logMessage('WhatsApp HTTP Request', $phone, $txt, 'Error', $e->getMessage());
}
}
}
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)) {
@ -119,18 +139,20 @@ 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();
if (isset($debug_mail) && $debug_mail == 'Dev') {
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
}
$mail->Host = $config['smtp_host'];
$mail->SMTPAuth = true;
$mail->Username = $config['smtp_user'];
$mail->Password = $config['smtp_pass'];
$mail->Host = $config['smtp_host'];
$mail->SMTPAuth = true;
$mail->Username = $config['smtp_user'];
$mail->Password = $config['smtp_pass'];
$mail->SMTPSecure = $config['smtp_ssltls'];
$mail->Port = $config['smtp_port'];
$mail->Port = $config['smtp_port'];
if (!empty($config['mail_from'])) {
$mail->setFrom($config['mail_from']);
}
@ -140,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')) {
@ -154,13 +180,19 @@ class Message
$html = str_replace('[[Company_Name]]', nl2br($config['CompanyName']), $html);
$html = str_replace('[[Body]]', nl2br($body), $html);
$mail->isHTML(true);
$mail->Body = $html;
$mail->Body = $html;
$mail->Body = $html;
} else {
$mail->isHTML(false);
$mail->Body = $body;
$mail->Body = $body;
}
if (!$mail->send()) {
_log(Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo);
$errorMessage = Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo;
self::logMessage('Email', $to, $body, 'Error', $errorMessage);
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;">
@ -198,7 +230,7 @@ class Message
$tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no';
if ($tax_enable === 'yes') {
$tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float) $config['custom_tax_rate'] : null;
$tax_rate = ($tax_rate_setting === 'custom') ? $custom_tax_rate : $tax_rate_setting;
$tax = Package::tax($price, $tax_rate);
@ -279,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);
@ -295,7 +327,7 @@ class Message
$textInvoice = str_replace('[[payment_channel]]', trim($gc[1]), $textInvoice);
$textInvoice = str_replace('[[type]]', $trx['type'], $textInvoice);
$textInvoice = str_replace('[[plan_name]]', $trx['plan_name'], $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($trx['price']), $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($trx['price']), $textInvoice);
$textInvoice = str_replace('[[name]]', $cust['fullname'], $textInvoice);
$textInvoice = str_replace('[[note]]', $cust['note'], $textInvoice);
$textInvoice = str_replace('[[user_name]]', $trx['username'], $textInvoice);
@ -305,6 +337,46 @@ class Message
$textInvoice = str_replace('[[expired_date]]', Lang::dateAndTimeFormat($trx['expiration'], $trx['time']), $textInvoice);
$textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
$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
$note = "";
$total = $trx['price'];
// Add bills to the note if there are any additional costs
if ($add_cost != 0) {
foreach ($bills as $k => $v) {
$note .= $k . " : " . Lang::moneyFormat($v) . "\n";
}
$total += $add_cost;
}
// Calculate tax
$tax = 0;
$tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no';
if ($tax_enable === 'yes') {
$tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float) $config['custom_tax_rate'] : null;
$tax_rate = ($tax_rate_setting === 'custom') ? $custom_tax_rate : $tax_rate_setting;
$tax = Package::tax($trx['price'], $tax_rate);
if ($tax != 0) {
$note .= "Tax : " . Lang::moneyFormat($tax) . "\n";
$total += $tax;
}
}
// Add total to the note
$note .= "Total : " . Lang::moneyFormat($total) . "\n";
// Replace placeholders in the message
$textInvoice = str_replace('[[bills]]', $note, $textInvoice);
if ($config['user_notification_payment'] == 'sms') {
Message::sendSMS($cust['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'email') {
@ -317,12 +389,45 @@ class Message
public static function addToInbox($to_customer_id, $subject, $body, $from = 'System')
{
$v = ORM::for_table('tbl_customers_inbox')->create();
$v->from = $from;
$v->customer_id = $to_customer_id;
$v->subject = $subject;
$v->date_created = date('Y-m-d H:i:s');
$v->body = nl2br($body);
$v->save();
$user = User::find($to_customer_id);
try {
$v = ORM::for_table('tbl_customers_inbox')->create();
$v->from = $from;
$v->customer_id = $to_customer_id;
$v->subject = $subject;
$v->date_created = date('Y-m-d H:i:s');
$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) {
return $message;
}
$msgs = explode("<divider>", $message);
if ($type == "PPPOE") {
return $msgs[1];
} else {
return $msgs[0];
}
}
public static function logMessage($messageType, $recipient, $messageContent, $status, $errorMessage = null)
{
$log = ORM::for_table('tbl_message_logs')->create();
$log->message_type = $messageType;
$log->recipient = $recipient;
$log->message_content = $messageContent;
$log->status = $status;
$log->error_message = $errorMessage;
$log->save();
}
}

View File

@ -46,7 +46,11 @@ class Response extends Message
/**
* The last response for a request.
*/
const TYPE_FINAL = '!done';
const TYPE_FINAL = '!done';/**
* The empty response for a request.
*/
const TYPE_EMPTY = '!empty';
/**
* A response with data.
@ -246,6 +250,7 @@ class Response extends Message
{
switch ($type) {
case self::TYPE_FINAL:
case self::TYPE_EMPTY:
case self::TYPE_DATA:
case self::TYPE_ERROR:
case self::TYPE_FATAL:

View File

@ -10,7 +10,7 @@ class Paginator
{
public static function findMany($query, $search = [], $per_page = '10', $append_url = "", $toArray = false)
{
global $routes, $ui;
global $routes, $ui, $isApi;
$adjacents = "2";
$page = _get('p', 1);
$page = (empty($page) ? 1 : $page);
@ -19,6 +19,7 @@ class Paginator
$url .= '&' . http_build_query($search);
}
$url .= $append_url.'&p=';
$url = Text::fixUrl($url);
$totalReq = $query->count();
$lastpage = ceil($totalReq / $per_page);
$lpm1 = $lastpage - 1;
@ -71,7 +72,7 @@ class Paginator
if ($ui) {
$ui->assign('paginator', $result);
}
if($toArray){
if($toArray || $isApi){
return $query->offset($startpoint)->limit($per_page)->find_array();
}else{
return $query->offset($startpoint)->limit($per_page)->find_many();

View File

@ -110,6 +110,12 @@ class Text
return $result;
}
/**
* ...$data means it can take any number of arguments.
* it can url($var1, $var2, $var3) or url($var1)
* and variable will be merge with implode
* @return string the URL with all the arguments combined.
*/
public static function url(...$data){
global $config;
$url = implode("", $data);
@ -125,4 +131,22 @@ class Text
return U . $url;
}
}
public static function fixUrl($url){
//if url dont have ? then add it with replace first & to ?
if(strpos($url, '?') === false && strpos($url, '&')!== false){
return substr($url, 0, strpos($url, '&')). '?'. substr($url, strpos($url, '&')+1);
}
return $url;
}
// this will return & or ?
public static function isQA(){
global $config;
if ($config['url_canonical'] == 'yes') {
return '?';
} else {
return '&';
}
}
}

View File

@ -29,7 +29,7 @@ class User
public static function getTawkToHash($email)
{
global $config;
if (!empty($config['tawkto_api_key']) && !Empty($email)) {
if (!empty($config['tawkto_api_key']) && !empty($email)) {
return hash_hmac('sha256', $email, $config['tawkto_api_key']);
}
return '';
@ -169,11 +169,11 @@ class User
public static function generateToken($uid, $validDays = 30)
{
global $db_pass;
if($validDays>=30){
if ($validDays >= 30) {
$time = time();
}else{
} else {
// for customer, deafult expired is 30 days
$time = strtotime('+ '.(30 - $validDays).' days');
$time = strtotime('+ ' . (30 - $validDays) . ' days');
}
return [
@ -187,7 +187,7 @@ class User
global $db_pass;
if (isset($uid)) {
$token = self::generateToken($uid);
setcookie('uid', $token['token'], time() + 86400 * 30);
setcookie('uid', $token['token'], time() + 86400 * 30, "/");
return $token;
} else {
return false;
@ -197,7 +197,7 @@ class User
public static function removeCookie()
{
if (isset($_COOKIE['uid'])) {
setcookie('uid', '', time() - 86400);
setcookie('uid', '', time() - 86400, "/");
}
}
@ -277,45 +277,53 @@ class User
return $d;
}
public static function setFormCustomField($uid = 0){
public static function setFormCustomField($uid = 0)
{
global $UPLOAD_PATH;
$fieldPath = $UPLOAD_PATH . DIRECTORY_SEPARATOR . "customer_field.json";
if(!file_exists($fieldPath)){
if (!file_exists($fieldPath)) {
return '';
}
$fields = json_decode(file_get_contents($fieldPath), true);
foreach($fields as $field){
if(!empty(_post($field['name']))){
foreach ($fields as $field) {
if (!empty(_post($field['name']))) {
self::setAttribute($field['name'], _post($field['name']), $uid);
}
}
}
public static function getFormCustomField($ui, $register = false, $uid = 0){
public static function getFormCustomField($ui, $register = false, $uid = 0)
{
global $UPLOAD_PATH;
$fieldPath = $UPLOAD_PATH . DIRECTORY_SEPARATOR . "customer_field.json";
if(!file_exists($fieldPath)){
if (!file_exists($fieldPath)) {
return '';
}
$fields = json_decode(file_get_contents($fieldPath), true);
$attrs = [];
if(!$register){
if (!$register) {
$attrs = self::getAttributes('', $uid);
$ui->assign('attrs', $attrs);
}
$html = '';
$ui->assign('register', $register);
foreach($fields as $field){
if($register){
if($field['register']){
foreach ($fields as $field) {
if ($register) {
if ($field['register']) {
$ui->assign('field', $field);
$html .= $ui->fetch('customer/custom_field.tpl');
}
}else{
} else {
$ui->assign('field', $field);
$html .= $ui->fetch('customer/custom_field.tpl');
}
}
return $html;
}
public static function find($id)
{
return ORM::for_table('tbl_customers')->find_one($id);
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
/**
* Validator class
*/
class Widget
{
public static function rows($rows, $result){
$result .= '<div class="row">';
foreach($rows as $row){
}
$result .= '</div>';
}
public static function columns($cols, $result){
$c = count($cols);
switch($c){
case 1:
$result .= '<div class="col-md-12">';
break;
case 2:
$result .= '<div class="col-md-6">';
break;
case 3:
$result .= '<div class="col-md-4">';
break;
case 4:
$result .= '<div class="col-md-4">';
break;
case 5:
$result .= '<div class="col-md-4">';
break;
default:
$result .= '<div class="col-md-1">';
break;
}
foreach($cols as $col){
}
$result .= '</div>';
}
}

View File

@ -67,21 +67,25 @@ if (isset($_SESSION['notify'])) {
unset($_SESSION['ntype']);
}
if(!isset($_GET['_route'])) {
$req = ltrim(parse_url($_SERVER['REQUEST_URI'])['path'], '/');
}else{
if (!isset($_GET['_route'])) {
$req = ltrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
$len = strlen(ltrim(parse_url(APP_URL, PHP_URL_PATH), '/'));
if ($len > 0) {
$req = ltrim(substr($req, $len), '/');
}
} else {
// Routing Engine
$req = _get('_route');
}
$routes = explode('/', $req);
$ui->assign('_routes', $routes);
$handler = $routes[0];
if ($handler == '') {
$handler = 'default';
}
try {
if(!empty($_GET['uid'])){
if (!empty($_GET['uid'])) {
$_COOKIE['uid'] = $_GET['uid'];
}
$admin = Admin::_info();
@ -124,11 +128,15 @@ try {
unset($menus, $menu_registered);
include($sys_render);
} else {
// header 404
header("HTTP/1.0 404 Not Found");
header("Content-Type: text/html; charset=utf-8");
echo "404 Not Found";
die();
if( empty($_SERVER["HTTP_SEC_FETCH_DEST"]) || $_SERVER["HTTP_SEC_FETCH_DEST"] != 'document' ){
// header 404
header("HTTP/1.0 404 Not Found");
header("Content-Type: text/html; charset=utf-8");
echo "404 Not Found";
die();
}else{
r2(getUrl('login'));
}
}
} catch (Throwable $e) {
Message::sendTelegram(
@ -137,11 +145,12 @@ try {
$e->getTraceAsString()
);
if (empty($_SESSION['aid'])) {
$ui->display('customer/error.tpl'); die();
$ui->display('customer/error.tpl');
die();
}
$ui->assign("error_message", $e->getMessage() . '<br><pre>' . $e->getTraceAsString() . '</pre>');
$ui->assign("error_title", "PHPNuxBill Crash");
$ui->display('error.tpl');
$ui->display('admin/error.tpl');
die();
} catch (Exception $e) {
Message::sendTelegram(
@ -150,10 +159,11 @@ try {
$e->getTraceAsString()
);
if (empty($_SESSION['aid'])) {
$ui->display('customer/error.tpl'); die();
$ui->display('customer/error.tpl');
die();
}
$ui->assign("error_message", $e->getMessage() . '<br><pre>' . $e->getTraceAsString() . '</pre>');
$ui->assign("error_title", "PHPNuxBill Crash");
$ui->display('error.tpl');
$ui->display('admin/error.tpl');
die();
}

View File

@ -420,5 +420,5 @@ switch ($action) {
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -63,6 +63,6 @@ switch ($do) {
run_hook('view_login'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin-login.tpl');
$ui->display('admin/admin/login.tpl');
break;
}

View File

@ -26,7 +26,7 @@ switch ($action) {
}
$ui->assign('routers', $routers);
$ui->assign('d', $d);
$ui->display('autoload-pool.tpl');
$ui->display('admin/autoload/pool.tpl');
break;
case 'bw_name':
$bw = ORM::for_table('tbl_bandwidth')->select("name_bw")->find_one($routes['2']);
@ -44,7 +44,7 @@ switch ($action) {
$d = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$ui->assign('d', $d);
$ui->display('autoload-server.tpl');
$ui->display('admin/autoload/server.tpl');
break;
case 'pppoe_ip_used':
if (!empty(_get('ip'))) {
@ -100,7 +100,7 @@ switch ($action) {
}
$ui->assign('d', $d);
$ui->display('autoload.tpl');
$ui->display('admin/autoload/plan.tpl');
break;
case 'customer_is_active':
if ($config['check_customer_online'] == 'yes') {
@ -181,5 +181,5 @@ switch ($action) {
echo json_encode(['results' => $json]);
die();
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -79,5 +79,5 @@ switch ($action) {
}
die();
default:
$ui->display('404.tpl');
die();
}

View File

@ -18,7 +18,6 @@ if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
switch ($action) {
case 'list':
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/bandwidth.js"></script>');
run_hook('view_list_bandwidth'); #HOOK
$name = _post('name');
if ($name != '') {
@ -30,7 +29,7 @@ switch ($action) {
}
$ui->assign('d', $d);
$ui->display('bandwidth.tpl');
$ui->display('admin/bandwidth/list.tpl');
break;
case 'add':
@ -38,7 +37,7 @@ switch ($action) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
run_hook('view_add_bandwidth'); #HOOK
$ui->display('bandwidth-add.tpl');
$ui->display('admin/bandwidth/add.tpl');
break;
case 'edit':
@ -51,7 +50,7 @@ switch ($action) {
if ($d) {
$ui->assign('burst', explode(" ", $d['burst']));
$ui->assign('d', $d);
$ui->display('bandwidth-edit.tpl');
$ui->display('admin/bandwidth/edit.tpl');
} else {
r2(getUrl('bandwidth/list'), 'e', Lang::T('Account Not Found'));
}
@ -186,5 +185,5 @@ switch ($action) {
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -19,8 +19,8 @@ switch ($action) {
$ui->assign('masters', $masters);
$ui->assign('devs', $devs);
$ui->display('community-rollback.tpl');
$ui->display('admin/rollback.tpl');
break;
default:
$ui->display('community.tpl');
$ui->display('admin/community.tpl');
}

View File

@ -22,7 +22,7 @@ switch ($action) {
}
$ui->assign('_title', Lang::T('Add Coupon'));
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('coupons-add.tpl');
$ui->display('admin/coupons/add.tpl');
break;
case 'add-post':
@ -131,7 +131,7 @@ switch ($action) {
$ui->assign('coupon', $coupon);
$ui->assign('_title', Lang::T('Edit Coupon: ' . $coupon['code']));
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('coupons-edit.tpl');
$ui->display('admin/coupons/edit.tpl');
break;
case 'edit-post':
@ -310,6 +310,6 @@ switch ($action) {
$coupons = Paginator::findMany($couponsData, ['search' => $search], 5, '');
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->assign('coupons', $coupons);
$ui->display('coupons.tpl');
$ui->display('admin/coupons/list.tpl');
break;
}

View File

@ -158,7 +158,7 @@ switch ($action) {
$ui->assign('xheader', $leafletpickerHeader);
run_hook('view_add_customer'); #HOOK
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers-add.tpl');
$ui->display('admin/customers/add.tpl');
break;
case 'recharge':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
@ -176,6 +176,10 @@ switch ($action) {
$channel = $admin['fullname'];
$cust = User::_info($id_customer);
$plan = ORM::for_table('tbl_plans')->find_one($b['plan_id']);
$add_inv = User::getAttribute("Invoice", $id_customer);
if (!empty($add_inv)) {
$plan['price'] = $add_inv;
}
$tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no';
$tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null;
@ -224,8 +228,9 @@ switch ($action) {
$ui->assign('channel', $channel);
$ui->assign('server', $b['routers']);
$ui->assign('plan', $plan);
$ui->assign('add_inv', $add_inv);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('recharge-confirm.tpl');
$ui->display('admin/plan/recharge-confirm.tpl');
} else {
r2(getUrl('customers/view/') . $id_customer, 'e', 'Cannot find active plan');
}
@ -364,7 +369,7 @@ switch ($action) {
$ui->assign('customFields', $customFields);
$ui->assign('xheader', $leafletpickerHeader);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers-view.tpl');
$ui->display('admin/customers/view.tpl');
} else {
r2(getUrl('customers/list'), 'e', Lang::T('Account Not Found'));
}
@ -403,7 +408,7 @@ switch ($action) {
$ui->assign('customFields', $customFields);
$ui->assign('xheader', $leafletpickerHeader);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers-edit.tpl');
$ui->display('admin/customers/edit.tpl');
} else {
r2(getUrl('customers/list'), 'e', Lang::T('Account Not Found'));
}
@ -908,6 +913,6 @@ switch ($action) {
$ui->assign('order_pos', $order_pos[$order]);
$ui->assign('orderby', $orderby);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers.tpl');
$ui->display('admin/customers/list.tpl');
break;
}

View File

@ -48,6 +48,6 @@ switch ($action) {
$fields = json_decode(file_get_contents($fieldPath), true);
}
$ui->assign('fields', $fields);
$ui->display('customfield.tpl');
$ui->display('admin/settings/customfield.tpl');
break;
}

View File

@ -1,233 +1,66 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Dashboard'));
$ui->assign('_admin', $admin);
if (isset($_GET['refresh'])) {
$files = scandir($CACHE_PATH);
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') {
unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file);
}
}
r2(getUrl('dashboard'), 's', 'Data Refreshed');
}
$reset_day = $config['reset_day'];
if (empty($reset_day)) {
$reset_day = 1;
}
//first day of month
if (date("d") >= $reset_day) {
$start_date = date('Y-m-' . $reset_day);
} else {
$start_date = date('Y-m-' . $reset_day, strtotime("-1 MONTH"));
}
$current_date = date('Y-m-d');
$month_n = date('n');
$iday = ORM::for_table('tbl_transactions')
->where('recharged_on', $current_date)
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->sum('price');
if ($iday == '') {
$iday = '0.00';
}
$ui->assign('iday', $iday);
$imonth = ORM::for_table('tbl_transactions')
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->where_gte('recharged_on', $start_date)
->where_lte('recharged_on', $current_date)->sum('price');
if ($imonth == '') {
$imonth = '0.00';
}
$ui->assign('imonth', $imonth);
if ($config['enable_balance'] == 'yes'){
$cb = ORM::for_table('tbl_customers')->whereGte('balance', 0)->sum('balance');
$ui->assign('cb', $cb);
}
$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count();
if (empty($u_act)) {
$u_act = '0';
}
$ui->assign('u_act', $u_act);
$u_all = ORM::for_table('tbl_user_recharges')->count();
if (empty($u_all)) {
$u_all = '0';
}
$ui->assign('u_all', $u_all);
$c_all = ORM::for_table('tbl_customers')->count();
if (empty($c_all)) {
$c_all = '0';
}
$ui->assign('c_all', $c_all);
if ($config['hide_uet'] != 'yes') {
//user expire
$query = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $current_date)
->order_by_desc('expiration');
$expire = Paginator::findMany($query);
// Get the total count of expired records for pagination
$totalCount = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $current_date)
->count();
// Pass the total count and current page to the paginator
$paginator['total_count'] = $totalCount;
// Assign the pagination HTML to the template variable
$ui->assign('expire', $expire);
}
//activity log
$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->find_many();
$ui->assign('dlog', $dlog);
$log = ORM::for_table('tbl_logs')->count();
$ui->assign('log', $log);
if ($config['hide_vs'] != 'yes') {
$cacheStocksfile = $CACHE_PATH . File::pathFixer('/VoucherStocks.temp');
$cachePlanfile = $CACHE_PATH . File::pathFixer('/VoucherPlans.temp');
//Cache for 5 minutes
if (file_exists($cacheStocksfile) && time() - filemtime($cacheStocksfile) < 600) {
$stocks = json_decode(file_get_contents($cacheStocksfile), true);
$plans = json_decode(file_get_contents($cachePlanfile), true);
} else {
// Count stock
$tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many();
$plans = array();
$stocks = array("used" => 0, "unused" => 0);
$n = 0;
foreach ($tmp as $plan) {
$unused = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 0)->count();
$used = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 1)->count();
if ($unused > 0 || $used > 0) {
$plans[$n]['name_plan'] = $plan['name_plan'];
$plans[$n]['unused'] = $unused;
$plans[$n]['used'] = $used;
$stocks["unused"] += $unused;
$stocks["used"] += $used;
$n++;
}
}
file_put_contents($cacheStocksfile, json_encode($stocks));
file_put_contents($cachePlanfile, json_encode($plans));
}
}
$cacheMRfile = File::pathFixer('/monthlyRegistered.temp');
//Cache for 1 hour
if (file_exists($cacheMRfile) && time() - filemtime($cacheMRfile) < 3600) {
$monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true);
} else {
//Monthly Registered Customers
$result = ORM::for_table('tbl_customers')
->select_expr('MONTH(created_at)', 'month')
->select_expr('COUNT(*)', 'count')
->where_raw('YEAR(created_at) = YEAR(NOW())')
->group_by_expr('MONTH(created_at)')
->find_many();
$monthlyRegistered = [];
foreach ($result as $row) {
$monthlyRegistered[] = [
'date' => $row->month,
'count' => $row->count
];
}
file_put_contents($cacheMRfile, json_encode($monthlyRegistered));
}
$cacheMSfile = $CACHE_PATH . File::pathFixer('/monthlySales.temp');
//Cache for 12 hours
if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) {
$monthlySales = json_decode(file_get_contents($cacheMSfile), true);
} else {
// Query to retrieve monthly data
$results = ORM::for_table('tbl_transactions')
->select_expr('MONTH(recharged_on)', 'month')
->select_expr('SUM(price)', 'total')
->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->group_by_expr('MONTH(recharged_on)')
->find_many();
// Create an array to hold the monthly sales data
$monthlySales = array();
// Iterate over the results and populate the array
foreach ($results as $result) {
$month = $result->month;
$totalSales = $result->total;
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => $totalSales
);
}
// Fill in missing months with zero sales
for ($month = 1; $month <= 12; $month++) {
if (!isset($monthlySales[$month])) {
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => 0
);
}
}
// Sort the array by month
ksort($monthlySales);
// Reindex the array
$monthlySales = array_values($monthlySales);
file_put_contents($cacheMSfile, json_encode($monthlySales));
}
if ($config['router_check']) {
$routeroffs = ORM::for_table('tbl_routers')->selects(['id', 'name', 'last_seen'])->where('status', 'Offline')->where('enabled', '1')->order_by_desc('name')->find_array();
$ui->assign('routeroffs', $routeroffs);
}
$timestampFile = "$UPLOAD_PATH/cron_last_run.txt";
if (file_exists($timestampFile)) {
$lastRunTime = file_get_contents($timestampFile);
$ui->assign('run_date', date('Y-m-d h:i:s A', $lastRunTime));
}
// Assign the monthly sales data to Smarty
$ui->assign('start_date', $start_date);
$ui->assign('current_date', $current_date);
$ui->assign('monthlySales', $monthlySales);
$ui->assign('xfooter', '');
$ui->assign('monthlyRegistered', $monthlyRegistered);
$ui->assign('stocks', $stocks);
$ui->assign('plans', $plans);
run_hook('view_dashboard'); #HOOK
$ui->display('dashboard.tpl');
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Dashboard'));
$ui->assign('_admin', $admin);
if (isset($_GET['refresh'])) {
$files = scandir($CACHE_PATH);
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') {
unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file);
}
}
r2(getUrl('dashboard'), 's', 'Data Refreshed');
}
$tipeUser = _req("user");
if (empty($tipeUser)) {
$tipeUser = 'Admin';
}
$ui->assign('tipeUser', $tipeUser);
$reset_day = $config['reset_day'];
if (empty($reset_day)) {
$reset_day = 1;
}
//first day of month
if (date("d") >= $reset_day) {
$start_date = date('Y-m-' . $reset_day);
} else {
$start_date = date('Y-m-' . $reset_day, strtotime("-1 MONTH"));
}
$current_date = date('Y-m-d');
$ui->assign('start_date', $start_date);
$ui->assign('current_date', $current_date);
$tipeUser = $admin['user_type'];
if (in_array($tipeUser, ['SuperAdmin', 'Admin'])) {
$tipeUser = 'Admin';
}
$widgets = ORM::for_table('tbl_widgets')->where("enabled", 1)->where('user', $tipeUser)->order_by_asc("orders")->findArray();
$count = count($widgets);
for ($i = 0; $i < $count; $i++) {
try{
if(file_exists($WIDGET_PATH . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php")){
require_once $WIDGET_PATH . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php";
$widgets[$i]['content'] = (new $widgets[$i]['widget'])->getWidget($widgets[$i]);
}else{
$widgets[$i]['content'] = "Widget not found";
}
} catch (Throwable $e) {
$widgets[$i]['content'] = $e->getMessage();
}
}
$ui->assign('widgets', $widgets);
run_hook('view_dashboard'); #HOOK
$ui->display('admin/dashboard.tpl');

View File

@ -5,9 +5,13 @@
**/
if(Admin::getID()){
r2(getUrl('dashboard'));
}if(User::getID()){
r2(getUrl('home'));
//r2(getUrl('dashboard'));
$handler = 'dashboard';
}else if(User::getID()){
//r2(getUrl('home'));
$handler = 'home';
}else{
r2(getUrl('login'));
//r2(getUrl('login'));
$handler = 'login';
}
include($root_path . File::pathFixer('system/controllers/' . $handler . '.php'));

View File

@ -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);
@ -83,7 +83,7 @@ switch ($action) {
$ui->assign('mdate', $mdate);
$ui->assign('recharged_on', $mdate);
run_hook('print_by_date'); #HOOK
$ui->display('print-by-date.tpl');
$ui->display('admin/print/by-date.tpl');
break;
case 'pdf-by-date':
@ -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 != '') {
@ -281,7 +291,7 @@ EOF;
$ui->assign('tdate', $tdate);
$ui->assign('stype', $stype);
run_hook('print_by_period'); #HOOK
$ui->display('print-by-period.tpl');
$ui->display('admin/print/by-period.tpl');
break;
@ -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>
@ -427,5 +444,5 @@ EOF;
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -95,8 +95,7 @@ if (_post('send') == 'balance') {
}
r2(getUrl('home'), 'w', Lang::T('Your friend do not have active package'));
}
$_bill = User::_billing();
$ui->assign('_bills', $_bill);
// Sync plan to router
if (isset($_GET['sync']) && !empty($_GET['sync'])) {
@ -147,7 +146,7 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
$routers = ORM::for_table('tbl_routers')->where('name', $bill['routers'])->find_one();
$router = $routers['id'];
}
r2(getUrl('order/gateway/$router/$bill[plan_id]'));
r2(getUrl("order/gateway/$router/$bill[plan_id]"));
}
} else if (!empty(_get('extend'))) {
if ($user['status'] != 'Active') {
@ -197,8 +196,8 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
$tur->save();
App::setToken(_get('stoken'), $id);
file_put_contents($path, $m);
_log("Customer $tur[customer_id] $tur[username] extend for $days days", "Customer", $user['id']);
Message::sendTelegram("#u$user[username] #extend #" . $p['type'] . " \n" . $p['name_plan'] .
_log("Customer $tur[customer_id] $user[fullname] ($tur[username]) extend for $days days", "Customer", $user['id']);
Message::sendTelegram("#u$user[username] ($user[fullname]) #id$tur[customer_id] #extend #" . $p['type'] . " \n" . $p['name_plan'] .
"\nLocation: " . $p['routers'] .
"\nCustomer: " . $user['fullname'] .
"\nNew Expired: " . Lang::dateAndTimeFormat($expiration, $tur['time']));
@ -310,46 +309,25 @@ if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && !empty($_SESSI
}
}
$tcf = ORM::for_table('tbl_customers_fields')
->where('customer_id', $user['id'])
->find_many();
$vpn = ORM::for_table('tbl_port_pool')
->find_one();
$ui->assign('cf', $tcf);
$ui->assign('vpn', $vpn);
$unpaid = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
// check expired payments
if ($unpaid) {
try {
if (strtotime($unpaid['expired_date']) < time()) {
$unpaid->status = 4;
$unpaid->save();
$unpaid = [];
$widgets = ORM::for_table('tbl_widgets')->where("enabled", 1)->where('user', 'Customer')->order_by_asc("orders")->findArray();
$count = count($widgets);
for ($i = 0; $i < $count; $i++) {
try{
if(file_exists($WIDGET_PATH . DIRECTORY_SEPARATOR . 'customer' . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php")){
require_once $WIDGET_PATH . DIRECTORY_SEPARATOR . 'customer' . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php";
$widgets[$i]['content'] = (new $widgets[$i]['widget'])->getWidget($widgets[$i]);
}else{
$widgets[$i]['content'] = "Widget not found";
}
} catch (Throwable $e) {
} catch (Exception $e) {
}
try {
if (strtotime($unpaid['created_date'], "+24 HOUR") < time()) {
$unpaid->status = 4;
$unpaid->save();
$unpaid = [];
}
} catch (Throwable $e) {
} catch (Exception $e) {
$widgets[$i]['content'] = $e->getMessage();
}
}
$ui->assign('unpaid', $unpaid);
$ui->assign('code', alphanumeric(_get('code'), "-"));
$ui->assign('widgets', $widgets);
$abills = User::getAttributes("Bill");
$ui->assign('abills', $abills);
$ui->assign('code', alphanumeric(_get('code'), "-"));
run_hook('view_customer_dashboard'); #HOOK
$ui->display('customer/dashboard.tpl');

View File

@ -0,0 +1,27 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
*
**/
_admin();
$ui->assign('_title', Lang::T('Invoice Lists'));
$ui->assign('_system_menu', 'reports');
$action = $routes['1'];
$ui->assign('_admin', $admin);
if (empty($action)) {
$action = 'list';
}
switch ($action) {
case 'list':
$ui->assign('xheader', '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css">');
$ui->assign('invoices', Invoice::getAll());
$ui->display('admin/invoices/list.tpl');
break;
default:
$ui->display('admin/404.tpl');
}

View File

@ -80,6 +80,39 @@ switch ($action) {
}
break;
case 'message-csv':
$logs = ORM::for_table('tbl_message_logs')
->select('id')
->select('message_type')
->select('recipient')
->select('message_content')
->select('status')
->select('error_message')
->select('sent_at')
->order_by_asc('id')->find_array();
$h = false;
set_time_limit(-1);
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header("Content-type: text/csv");
header('Content-Disposition: attachment;filename="message-logs_' . date('Y-m-d_H_i') . '.csv"');
header('Content-Transfer-Encoding: binary');
foreach ($logs as $log) {
$ks = [];
$vs = [];
foreach ($log as $k => $v) {
$ks[] = $k;
$vs[] = $v;
}
if (!$h) {
echo '"' . implode('";"', $ks) . "\"\n";
$h = true;
}
echo '"' . implode('";"', $vs) . "\"\n";
}
break;
case 'list':
$q = (_post('q') ? _post('q') : _get('q'));
$keep = _post('keep');
@ -97,7 +130,7 @@ switch ($action) {
$ui->assign('d', $d);
$ui->assign('q', $q);
$ui->display('logs.tpl');
$ui->display('admin/logs/system.tpl');
break;
case 'radius':
$q = (_post('q') ? _post('q') : _get('q'));
@ -116,9 +149,36 @@ switch ($action) {
$ui->assign('d', $d);
$ui->assign('q', $q);
$ui->display('logs-radius.tpl');
$ui->display('admin/logs/radius.tpl');
break;
case 'message':
$q = _post('q') ?: _get('q');
$keep = (int) _post('keep');
if (!empty($keep)) {
ORM::raw_execute("DELETE FROM tbl_message_logs WHERE UNIX_TIMESTAMP(sent_at) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))");
r2(getUrl('logs/message/'), 's', "Deleted logs older than $keep days");
}
if ($q !== null && $q !== '') {
$query = ORM::for_table('tbl_message_logs')
->whereRaw("message_type LIKE '%$q%' OR recipient LIKE '%$q%' OR message_content LIKE '%$q%' OR status LIKE '%$q%' OR error_message LIKE '%$q%'")
->order_by_desc('sent_at');
$d = Paginator::findMany($query, ['q' => $q]);
} else {
$query = ORM::for_table('tbl_message_logs')->order_by_desc('sent_at');
$d = Paginator::findMany($query);
}
if ($d) {
$ui->assign('d', $d);
} else {
$ui->assign('d', []);
}
$ui->assign('q', $q);
$ui->display('admin/logs/message.tpl');
break;
default:
r2(getUrl('logs/list/'), 's', '');

View File

@ -15,6 +15,9 @@ if (empty($action)) {
$action = 'customer';
}
$ui->assign('xheader', '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css">');
$ui->assign('xfooter', '<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>');
switch ($action) {
case 'customer':
if(!empty(_req('search'))){
@ -42,12 +45,22 @@ switch ($action) {
}
$ui->assign('search', $search);
$ui->assign('customers', $customerData);
$ui->assign('xheader', '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css">');
$ui->assign('_title', Lang::T('Customer Geo Location Information'));
$ui->assign('xfooter', '<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>');
$ui->display('customers-map.tpl');
$ui->display('admin/maps/customers.tpl');
break;
case 'routers':
$name = _post('name');
$query = ORM::for_table('tbl_routers')->where_not_equal('coordinates', '')->order_by_desc('id');
$query->selects(['id', 'name', 'coordinates', 'description', 'coverage', 'enabled']);
if ($name != '') {
$query->where_like('name', '%' . $name . '%');
}
$d = Paginator::findMany($query, ['name' => $name], '20', '', true);
$ui->assign('name', $name);
$ui->assign('d', $d);
$ui->assign('_title', Lang::T('Routers Geo Location Information'));
$ui->display('admin/maps/routers.tpl');
break;
default:
r2(getUrl('map/customer'), 'e', 'action not defined');
break;

View File

@ -22,6 +22,8 @@ switch ($action) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$appUrl = APP_URL;
$select2_customer = <<<EOT
<script>
document.addEventListener("DOMContentLoaded", function(event) {
@ -30,9 +32,9 @@ document.addEventListener("DOMContentLoaded", function(event) {
ajax: {
url: function(params) {
if(params.term != undefined){
return './?_route=autoload/customer_select2&s='+params.term;
return '{$appUrl}/?_route=autoload/customer_select2&s='+params.term;
}else{
return './?_route=autoload/customer_select2';
return '{$appUrl}/?_route=autoload/customer_select2';
}
}
}
@ -46,7 +48,7 @@ EOT;
$id = $routes['2'];
$ui->assign('id', $id);
$ui->assign('xfooter', $select2_customer);
$ui->display('message.tpl');
$ui->display('admin/message/single.tpl');
break;
case 'send-post':
@ -55,40 +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 (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'));
}
//Send the message
if ($via == 'sms' || $via == 'both') {
$smsSent = Message::sendSMS($c['phonenumber'], $message);
}
// Replace placeholders in message and subject
$currentMessage = str_replace(
['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
[$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
$message
);
if ($via == 'wa' || $via == 'both') {
$waSent = Message::sendWhatsapp($c['phonenumber'], $message);
}
$currentSubject = str_replace(
['[[name]]', '[[user_name]]', '[[phone]]', '[[company_name]]'],
[$customer['fullname'], $customer['username'], $customer['phonenumber'], $config['CompanyName']],
$subject
);
if (isset($smsSent) || isset($waSent)) {
r2(getUrl('message/send'), 's', Lang::T('Message Sent Successfully'));
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);
}
} 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':
@ -96,143 +137,362 @@ EOT;
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
// Get form data
$group = $_POST['group'];
$message = $_POST['message'];
$via = $_POST['via'];
$test = isset($_POST['test']) && $_POST['test'] === 'on' ? 'yes' : 'no';
$batch = $_POST['batch'];
$delay = $_POST['delay'];
$ui->assign('routers', ORM::forTable('tbl_routers')->where('enabled', '1')->find_many());
$ui->display('admin/message/bulk.tpl');
break;
// Initialize counters
case 'send_bulk_ajax':
// Check user permissions
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
die(json_encode(['status' => 'error', 'message' => 'Permission denied']));
}
set_time_limit(0);
// Get request parameters
$group = $_REQUEST['group'] ?? '';
$message = $_REQUEST['message'] ?? '';
$via = $_REQUEST['via'] ?? '';
$batch = $_REQUEST['batch'] ?? 100;
$page = $_REQUEST['page'] ?? 0;
$router = $_REQUEST['router'] ?? null;
$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' => 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
$startpoint = $page * $batch;
$customers = [];
$totalCustomers = 0;
if (isset($router) && !empty($router)) {
switch ($router) {
case 'radius':
$routerName = 'Radius';
break;
default:
$router = ORM::for_table('tbl_routers')->find_one($router);
if (!$router) {
die(json_encode(['status' => 'error', 'message' => LANG::T('Invalid router')]));
}
$routerName = $router->name;
break;
}
}
if (isset($router) && !empty($router)) {
$query = ORM::for_table('tbl_user_recharges')
->left_outer_join('tbl_customers', 'tbl_user_recharges.customer_id = tbl_customers.id')
->where('tbl_user_recharges.routers', $routerName);
switch ($service) {
case 'all':
break;
default:
$validServices = ['PPPoE', 'Hotspot', 'VPN'];
if (in_array($service, $validServices)) {
$query->where('type', $service);
}
break;
}
$totalCustomers = $query->count();
$query->offset($startpoint)
->limit($batch);
switch ($group) {
case 'all':
break;
case 'new':
$query->where_raw("DATE(recharged_on) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)");
break;
case 'expired':
$query->where('tbl_user_recharges.status', 'off');
break;
case 'active':
$query->where('tbl_user_recharges.status', 'on');
break;
}
// Fetch the customers
$query->selects([
['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 {
switch ($group) {
case 'all':
$totalCustomersQuery = ORM::for_table('tbl_customers');
switch ($service) {
case 'all':
break;
default:
$validServices = ['PPPoE', 'Hotspot', 'VPN'];
if (in_array($service, $validServices)) {
$totalCustomersQuery->where('service_type', $service);
}
break;
}
$totalCustomers = $totalCustomersQuery->count();
$customers = $totalCustomersQuery->offset($startpoint)->limit($batch)->find_array();
break;
case 'new':
$totalCustomersQuery = ORM::for_table('tbl_customers')
->where_raw("DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)");
switch ($service) {
case 'all':
break;
default:
$validServices = ['PPPoE', 'Hotspot', 'VPN'];
if (in_array($service, $validServices)) {
$totalCustomersQuery->where('service_type', $service);
}
break;
}
$totalCustomers = $totalCustomersQuery->count();
$customers = $totalCustomersQuery->offset($startpoint)->limit($batch)->find_array();
break;
case 'expired':
$totalCustomersQuery = ORM::for_table('tbl_user_recharges')
->where('status', 'off');
switch ($service) {
case 'all':
break;
default:
$validServices = ['PPPoE', 'Hotspot', 'VPN'];
if (in_array($service, $validServices)) {
$totalCustomersQuery->where('type', $service);
}
break;
}
$totalCustomers = $totalCustomersQuery->count();
$customers = $totalCustomersQuery->select('customer_id')->offset($startpoint)->limit($batch)->find_array();
break;
case 'active':
$totalCustomersQuery = ORM::for_table('tbl_user_recharges')
->where('status', 'on');
switch ($service) {
case 'all':
break;
default:
$validServices = ['PPPoE', 'Hotspot', 'VPN'];
if (in_array($service, $validServices)) {
$totalCustomersQuery->where('type', $service);
}
break;
}
$totalCustomers = $totalCustomersQuery->count();
$customers = $totalCustomersQuery->select('customer_id')->offset($startpoint)->limit($batch)->find_array(); // Get customer data
break;
}
}
// Ensure $customers is always an array
if (!$customers) {
$customers = [];
}
// Send messages
$totalSMSSent = 0;
$totalSMSFailed = 0;
$totalWhatsappSent = 0;
$totalWhatsappFailed = 0;
$totalEmailSent = 0;
$totalEmailFailed = 0;
$totalInboxSent = 0;
$totalInboxFailed = 0;
$batchStatus = [];
//$subject = $config['CompanyName'] . ' ' . Lang::T('Notification Message');
$form = 'Admin';
if (_req('send') == 'now') {
// Check if fields are empty
if ($group == '' || $message == '' || $via == '') {
r2(getUrl('message/send_bulk'), 'e', Lang::T('All fields are required'));
foreach ($customers as $customer) {
$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
);
$phoneNumber = preg_replace('/\D/', '', $customer['phonenumber']);
if (empty($phoneNumber)) {
$batchStatus[] = [
'name' => $customer['fullname'],
'phone' => '',
'status' => 'No Phone Number'
];
continue;
}
if ($test) {
$batchStatus[] = [
'name' => $customer['fullname'],
'channel' => 'Test Channel',
'status' => 'Test Mode',
'message' => $currentMessage,
'service' => $service,
'router' => $routerName,
];
} else {
// Get customer details from the database based on the selected group
if ($group == 'all') {
$customers = ORM::for_table('tbl_customers')->find_many()->as_array();
} elseif ($group == 'new') {
// Get customers created just a month ago
$customers = ORM::for_table('tbl_customers')->where_raw("DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)")->find_many()->as_array();
} elseif ($group == 'expired') {
// Get expired user recharges where status is 'off'
$expired = ORM::for_table('tbl_user_recharges')->where('status', 'off')->find_many();
$customer_ids = [];
foreach ($expired as $recharge) {
$customer_ids[] = $recharge->customer_id;
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];
} else {
$totalSMSFailed++;
$batchStatus[] = ['name' => $customer['fullname'], 'phone' => $customer['phonenumber'], 'status' => 'SMS Failed', 'message' => $currentMessage];
}
$customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_many()->as_array();
} elseif ($group == 'active') {
// Get active user recharges where status is 'on'
$active = ORM::for_table('tbl_user_recharges')->where('status', 'on')->find_many();
$customer_ids = [];
foreach ($active as $recharge) {
$customer_ids[] = $recharge->customer_id;
}
$customers = ORM::for_table('tbl_customers')->where_in('id', $customer_ids)->find_many()->as_array();
}
// Set the batch size
$batchSize = $batch;
// Calculate the number of batches
$totalCustomers = count($customers);
$totalBatches = ceil($totalCustomers / $batchSize);
// Loop through batches
for ($batchIndex = 0; $batchIndex < $totalBatches; $batchIndex++) {
// Get the starting and ending index for the current batch
$start = $batchIndex * $batchSize;
$end = min(($batchIndex + 1) * $batchSize, $totalCustomers);
$batchCustomers = array_slice($customers, $start, $end - $start);
// Loop through customers in the current batch and send messages
foreach ($batchCustomers as $customer) {
// Create a copy of the original message for each customer and save it as currentMessage
$currentMessage = $message;
$currentMessage = str_replace('[[name]]', $customer['fullname'], $currentMessage);
$currentMessage = str_replace('[[user_name]]', $customer['username'], $currentMessage);
$currentMessage = str_replace('[[phone]]', $customer['phonenumber'], $currentMessage);
$currentMessage = str_replace('[[company_name]]', $config['CompanyName'], $currentMessage);
// Send the message based on the selected method
if ($test === 'yes') {
// Only for testing, do not send messages to customers
$batchStatus[] = [
'name' => $customer['fullname'],
'phone' => $customer['phonenumber'],
'message' => $currentMessage,
'status' => 'Test Mode - Message not sent'
];
} else {
// Send the actual messages
if ($via == 'sms' || $via == 'both') {
$smsSent = Message::sendSMS($customer['phonenumber'], $currentMessage);
if ($smsSent) {
$totalSMSSent++;
$batchStatus[] = [
'name' => $customer['fullname'],
'phone' => $customer['phonenumber'],
'message' => $currentMessage,
'status' => 'SMS Message Sent'
];
} else {
$totalSMSFailed++;
$batchStatus[] = [
'name' => $customer['fullname'],
'phone' => $customer['phonenumber'],
'message' => $currentMessage,
'status' => 'SMS Message Failed'
];
}
}
if ($via == 'wa' || $via == 'both') {
$waSent = Message::sendWhatsapp($customer['phonenumber'], $currentMessage);
if ($waSent) {
$totalWhatsappSent++;
$batchStatus[] = [
'name' => $customer['fullname'],
'phone' => $customer['phonenumber'],
'message' => $currentMessage,
'status' => 'WhatsApp Message Sent'
];
} else {
$totalWhatsappFailed++;
$batchStatus[] = [
'name' => $customer['fullname'],
'phone' => $customer['phonenumber'],
'message' => $currentMessage,
'status' => 'WhatsApp Message Failed'
];
}
}
}
if ($via === 'wa' || $via == 'both' || $via === 'all') {
if (Message::sendWhatsapp($customer['phonenumber'], $currentMessage)) {
$totalWhatsappSent++;
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Sent', 'message' => $currentMessage];
} else {
$totalWhatsappFailed++;
$batchStatus[] = ['name' => $customer['fullname'], 'channel' => $customer['phonenumber'], 'status' => 'WhatsApp Failed', 'message' => $currentMessage];
}
}
// Introduce a delay between each batch
if ($batchIndex < $totalBatches - 1) {
sleep($delay);
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];
}
}
}
}
$ui->assign('batchStatus', $batchStatus);
$ui->assign('totalSMSSent', $totalSMSSent);
$ui->assign('totalSMSFailed', $totalSMSFailed);
$ui->assign('totalWhatsappSent', $totalWhatsappSent);
$ui->assign('totalWhatsappFailed', $totalWhatsappFailed);
$ui->display('message-bulk.tpl');
// Calculate if there are more customers to process
$hasMore = ($startpoint + $batch) < $totalCustomers;
// Return JSON response
echo json_encode([
'status' => 'success',
'page' => $page + 1,
'batchStatus' => $batchStatus,
'message' => $currentMessage,
'totalSent' => $totalSMSSent + $totalWhatsappSent + $totalEmailSent + $totalInboxSent,
'totalFailed' => $totalSMSFailed + $totalWhatsappFailed + $totalEmailFailed + $totalInboxFailed,
'hasMore' => $hasMore,
'service' => $service,
'router' => $routerName,
]);
break;
case 'send_bulk_selected':
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Set headers
header('Content-Type: application/json');
header('Cache-Control: no-cache, no-store, must-revalidate');
// 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;
$form = 'Admin';
foreach ($customerIds as $customerId) {
$customer = ORM::for_table('tbl_customers')->where('id', $customerId)->find_one();
if ($customer) {
$messageSent = false;
// Check the message type and send accordingly
try {
if ($via === 'sms' || $via === 'all') {
$messageSent = Message::sendSMS($customer['phonenumber'], $message);
}
if (!$messageSent && ($via === 'wa' || $via === 'all')) {
$messageSent = Message::sendWhatsapp($customer['phonenumber'], $message);
}
if (!$messageSent && ($via === 'inbox' || $via === 'all')) {
Message::addToInbox($customer['id'], $subject, $message, $form);
$messageSent = true;
}
if (!$messageSent && ($via === 'email' || $via === 'all')) {
$messageSent = Message::sendEmail($customer['email'], $subject, $message);
}
} catch (Throwable $e) {
$messageSent = false;
$failedCount++;
sendTelegram('Failed to send message to ' . $e->getMessage());
_log('Failed to send message to ' . $customer['fullname'] . ': ' . $e->getMessage());
continue;
}
if ($messageSent) {
$sentCount++;
} else {
$failedCount++;
}
} else {
$failedCount++;
}
}
// Prepare the response
echo json_encode([
'status' => 'success',
'totalSent' => $sentCount,
'totalFailed' => $failedCount
]);
} else {
header('Content-Type: application/json');
echo json_encode(['status' => 'error', 'message' => Lang::T('Invalid request method.')]);
}
break;
default:
r2(getUrl('message/send_sms'), 'e', 'action not defined');
}

View File

@ -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 .
@ -410,6 +410,10 @@ switch ($action) {
if ($router['name'] != 'balance') {
list($bills, $add_cost) = User::getBills($id_customer);
}
$add_inv = User::getAttribute("Invoice", $id_customer);
if (!empty($add_inv)) {
$plan['price'] = $add_inv;
}
if($config['enable_coupons']){
if (!isset($_SESSION['coupon_attempts'])) {

View File

@ -60,9 +60,9 @@ if (strpos($action, "-reset") !== false) {
$ui->assign("writeable", is_writable($path));
$ui->assign("pageHeader", str_replace('_', ' ', $action));
$ui->assign("PageFile", $action);
$ui->display('page-edit.tpl');
$ui->display('admin/settings/page.tpl');
} else
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
} else {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
@ -83,5 +83,5 @@ if (strpos($action, "-reset") !== false) {
r2(getUrl('pages/') . $action, 'e', Lang::T("Failed to save page, make sure i can write to folder pages, <i>chmod 664 pages/*.html<i>"));
}
} else
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -34,7 +34,7 @@ switch ($action) {
$ui->assign('pgs', $pgs);
$ui->assign('pg', $pg);
$ui->assign('q', $q);
$ui->display('paymentgateway-audit.tpl');
$ui->display('admin/paymentgateway/audit.tpl');
break;
case 'auditview':
$pg = alphanumeric($routes[2]);
@ -43,7 +43,7 @@ switch ($action) {
$d['pg_paid_response'] = (!empty($d['pg_paid_response']))? Text::jsonArray21Array(json_decode($d['pg_paid_response'], true)) : [];
$ui->assign('_title', 'Payment Gateway Audit View');
$ui->assign('pg', $d);
$ui->display('paymentgateway-audit-view.tpl');
$ui->display('admin/paymentgateway/audit-view.tpl');
break;
default:
if (_post('save') == 'actives') {
@ -70,13 +70,13 @@ switch ($action) {
if (function_exists($action . '_save_config')) {
call_user_func($action . '_save_config');
} else {
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}
} else {
if (function_exists($action . '_show_config')) {
call_user_func($action . '_show_config');
} else {
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}
}
} else {
@ -92,7 +92,7 @@ switch ($action) {
$ui->assign('_title', 'Payment Gateway Settings');
$ui->assign('pgs', $pgs);
$ui->assign('actives', explode(',', $config['payment_gateway']));
$ui->display('paymentgateway.tpl');
$ui->display('admin/paymentgateway/list.tpl');
}
}
}

View File

@ -12,6 +12,8 @@ $ui->assign('_system_menu', 'plan');
$action = $routes['1'];
$ui->assign('_admin', $admin);
$appUrl = APP_URL;
$select2_customer = <<<EOT
<script>
document.addEventListener("DOMContentLoaded", function(event) {
@ -20,9 +22,9 @@ document.addEventListener("DOMContentLoaded", function(event) {
ajax: {
url: function(params) {
if(params.term != undefined){
return './?_route=autoload/customer_select2&s='+params.term;
return '{$appUrl}/?_route=autoload/customer_select2&s='+params.term;
}else{
return './?_route=autoload/customer_select2';
return '{$appUrl}/?_route=autoload/customer_select2';
}
}
}
@ -30,7 +32,7 @@ document.addEventListener("DOMContentLoaded", function(event) {
});
</script>
EOT;
getUrl('docs');
switch ($action) {
case 'sync':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
@ -82,7 +84,7 @@ switch ($action) {
}
$ui->assign('usings', $usings);
run_hook('view_recharge'); #HOOK
$ui->display('recharge.tpl');
$ui->display('admin/plan/recharge.tpl');
break;
case 'recharge-confirm':
@ -105,11 +107,15 @@ switch ($action) {
$cust = User::_info($id_customer);
$plan = ORM::for_table('tbl_plans')->find_one($planId);
list($bills, $add_cost) = User::getBills($id_customer);
$add_inv = User::getAttribute("Invoice", $id_customer);
if (!empty($add_inv)) {
$plan['price'] = $add_inv;
}
// Tax calculation start
$tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no';
$tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float) $config['custom_tax_rate'] : null;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
@ -158,7 +164,8 @@ switch ($action) {
$ui->assign('server', $server);
$ui->assign('using', $using);
$ui->assign('plan', $plan);
$ui->display('recharge-confirm.tpl');
$ui->assign('add_inv', $add_inv);
$ui->display('admin/plan/recharge-confirm.tpl');
} else {
r2(getUrl('plan/recharge'), 'e', $msg);
}
@ -180,7 +187,7 @@ switch ($action) {
$username = App::getVoucherValue($svoucher);
$in = ORM::for_table('tbl_transactions')->where('username', $username)->order_by_desc('id')->find_one();
Package::createInvoice($in);
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
die();
}
@ -198,7 +205,7 @@ switch ($action) {
// Tax calculation start
$tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no';
$tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float)$config['custom_tax_rate'] : null;
$custom_tax_rate = isset($config['custom_tax_rate']) ? (float) $config['custom_tax_rate'] : null;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
@ -239,7 +246,7 @@ switch ($action) {
$in = ORM::for_table('tbl_transactions')->where('username', $cust['username'])->order_by_desc('id')->find_one();
Package::createInvoice($in);
App::setVoucher($svoucher, $cust['username']);
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
_log('[' . $admin['username'] . ']: ' . 'Recharge ' . $cust['username'] . ' [' . $in['plan_name'] . '][' . Lang::moneyFormat($in['price']) . ']', $admin['user_type'], $admin['id']);
} else {
r2(getUrl('plan/recharge'), 'e', "Failed to recharge account");
@ -262,8 +269,21 @@ switch ($action) {
r2(getUrl('plan/view/') . $id, 'd', "Customer not found");
}
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->assign('_title', 'View Invoice');
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
break;
@ -285,14 +305,14 @@ switch ($action) {
$ui->assign('date', Lang::dateAndTimeFormat($d['recharged_on'], $d['recharged_time']));
}
run_hook('print_invoice'); #HOOK
$ui->display('invoice-print.tpl');
$ui->display('admin/plan/invoice-print.tpl');
break;
case 'edit':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$id = $routes['2'];
$id = $routes['2'];
$d = ORM::for_table('tbl_user_recharges')->find_one($id);
if ($d) {
$ui->assign('d', $d);
@ -312,7 +332,7 @@ switch ($action) {
$ui->assign('p', $ps);
run_hook('view_edit_customer_plan'); #HOOK
$ui->assign('_title', 'Edit Plan');
$ui->display('plan-edit.tpl');
$ui->display('admin/plan/edit.tpl');
} else {
r2(getUrl('plan/list'), 'e', Lang::T('Account Not Found'));
}
@ -322,7 +342,7 @@ switch ($action) {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$id = $routes['2'];
$id = $routes['2'];
$d = ORM::for_table('tbl_user_recharges')->find_one($id);
if ($d) {
run_hook('delete_customer_active_plan'); #HOOK
@ -446,7 +466,7 @@ switch ($action) {
$append_url = "&search=" . urlencode($search) . "&router=" . urlencode($router) . "&customer=" . urlencode($customer) . "&plan=" . urlencode($plan) . "&status=" . urlencode($status);
// option customers
$ui->assign('customers', ORM::for_table('tbl_voucher')->distinct()->select("user")->whereNotEqual("user", '0')->findArray());
$ui->assign('customers', ORM::for_table('tbl_voucher')->distinct()->select("user")->whereNotEqual("user", '0')->findArray());
// option plans
$plns = ORM::for_table('tbl_voucher')->distinct()->select("id_plan")->findArray();
if (count($plns) > 0) {
@ -510,7 +530,7 @@ switch ($action) {
$ui->assign('search', $search);
$ui->assign('page', $page);
run_hook('view_list_voucher'); #HOOK
$ui->display('voucher.tpl');
$ui->display('admin/voucher/list.tpl');
break;
case 'add-voucher':
@ -525,7 +545,7 @@ switch ($action) {
$r = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$ui->assign('r', $r);
run_hook('view_add_voucher'); #HOOK
$ui->display('voucher-add.tpl');
$ui->display('admin/voucher/add.tpl');
break;
case 'remove-voucher':
@ -556,9 +576,11 @@ switch ($action) {
if (empty($vpl)) {
$vpl = 3;
}
if ($pagebreak < 1) $pagebreak = 12;
if ($pagebreak < 1)
$pagebreak = 12;
if ($limit < 1) $limit = $pagebreak * 2;
if ($limit < 1)
$limit = $pagebreak * 2;
if (empty($from_id)) {
$from_id = 0;
}
@ -619,7 +641,7 @@ switch ($action) {
$v = ORM::for_table('tbl_plans')
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
->where('tbl_voucher.status', '0')
->where('tbl_voucher.created_at', $selected_datetime)
->where('tbl_voucher.created_at', $selected_datetime)
->limit($limit);
$vc = ORM::for_table('tbl_plans')
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
@ -684,12 +706,15 @@ switch ($action) {
//for counting pagebreak
$ui->assign('jml', 0);
run_hook('view_print_voucher'); #HOOK
$ui->display('print-voucher.tpl');
$ui->display('admin/print/voucher.tpl');
break;
case 'voucher-post':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
if ($_app_stage == 'Demo') {
r2(getUrl('plan/add-voucher/'), 'e', 'You cannot perform this action in Demo mode');
}
$type = _post('type');
$plan = _post('plan');
@ -791,7 +816,7 @@ switch ($action) {
$ui->assign('from_id', 0);
$ui->assign('vpl', '3');
$ui->assign('pagebreak', $voucherPerPage);
$ui->display('print-voucher.tpl');
$ui->display('admin/print/voucher.tpl');
}
if ($numbervoucher == 1) {
@ -891,7 +916,7 @@ switch ($action) {
$content .= Lang::pad($config['note'], ' ', 2) . "\n";
$ui->assign('_title', Lang::T('View'));
$ui->assign('whatsapp', urlencode("```$content```"));
$ui->display('voucher-view.tpl');
$ui->display('admin/voucher/view.tpl');
} else {
r2(getUrl('plan/voucher/'), 'e', Lang::T('Voucher Not Found'));
}
@ -900,7 +925,7 @@ switch ($action) {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$id = $routes['2'];
$id = $routes['2'];
run_hook('delete_voucher'); #HOOK
$d = ORM::for_table('tbl_voucher')->find_one($id);
if ($d) {
@ -916,7 +941,7 @@ switch ($action) {
$ui->assign('xfooter', $select2_customer);
$ui->assign('_title', Lang::T('Refill Account'));
run_hook('view_refill'); #HOOK
$ui->display('refill.tpl');
$ui->display('admin/plan/refill.tpl');
break;
@ -936,7 +961,7 @@ switch ($action) {
$v1->save();
$in = ORM::for_table('tbl_transactions')->where('username', $user['username'])->order_by_desc('id')->find_one();
Package::createInvoice($in);
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
} else {
r2(getUrl('plan/refill'), 'e', "Failed to refill account");
}
@ -956,7 +981,7 @@ switch ($action) {
$ui->assign('p', ORM::for_table('tbl_plans')->where('enabled', '1')->where('type', 'Balance')->find_many());
}
run_hook('view_deposit'); #HOOK
$ui->display('deposit.tpl');
$ui->display('admin/plan/deposit.tpl');
break;
case 'deposit-post':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
@ -971,7 +996,7 @@ switch ($action) {
if (App::getVoucherValue($svoucher)) {
$in = ORM::for_table('tbl_transactions')->find_one(App::getVoucherValue($svoucher));
Package::createInvoice($in);
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
die();
}
@ -987,7 +1012,7 @@ switch ($action) {
if (!empty($svoucher)) {
App::setVoucher($svoucher, $trxId);
}
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
} else {
r2(getUrl('plan/refill'), 'e', "Failed to refill account");
}
@ -1000,7 +1025,7 @@ switch ($action) {
if (!empty($svoucher)) {
App::setVoucher($svoucher, $trxId);
}
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
} else {
r2(getUrl('plan/refill'), 'e', "Failed to refill account");
}
@ -1050,18 +1075,17 @@ switch ($action) {
} else {
r2(getUrl('plan'), 's', "Customer not found");
}
Message::sendTelegram("#u$tur[username] #extend #" . $p['type'] . " \n" . $p['name_plan'] .
Message::sendTelegram("#u$tur[username] #id$tur[customer_id] #extend by $admin[fullname] #" . $p['type'] . " \n" . $p['name_plan'] .
"\nLocation: " . $p['routers'] .
"\nCustomer: " . $c['fullname'] .
"\nNew Expired: " . Lang::dateAndTimeFormat($expiration, $tur['time']));
_log("$admin[fullname] extend Customer $tur[customer_id] $tur[username] for $days days", $admin['user_type'], $admin['id']);
_log("$admin[fullname] extend Customer $tur[customer_id] $tur[username] #$tur[customer_id] for $days days", $admin['user_type'], $admin['id']);
r2(getUrl('plan'), 's', "Extend until $expiration");
} else {
r2(getUrl('plan'), 's', "Customer is not expired yet");
}
break;
default:
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/plan.js"></script>');
$ui->assign('_title', Lang::T('Customer'));
$search = _post('search');
$status = _req('status');
@ -1102,6 +1126,6 @@ switch ($action) {
$d = Paginator::findMany($query, ['search' => $search], 25, $append_url);
run_hook('view_list_billing'); #HOOK
$ui->assign('d', $d);
$ui->display('plan.tpl');
$ui->display('admin/plan/active.tpl');
break;
}

View File

@ -34,11 +34,12 @@ if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) {
}
switch ($action) {
case 'refresh':
if (file_exists($cache)) unlink($cache);
if (file_exists($cache))
unlink($cache);
r2(getUrl('pluginmanager'), 's', 'Refresh success');
break;
case 'dlinstall':
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
r2(getUrl('pluginmanager'), 'e', 'Demo Mode cannot install as it Security risk');
}
if (!is_writeable($CACHE_PATH)) {
@ -121,7 +122,7 @@ switch ($action) {
$zip->extractTo($cache);
$zip->close();
$folder = $cache . DIRECTORY_SEPARATOR . $plugin . '-main' . DIRECTORY_SEPARATOR;
if(!file_exists($folder)) {
if (!file_exists($folder)) {
$folder = $cache . DIRECTORY_SEPARATOR . $plugin . '-master' . DIRECTORY_SEPARATOR;
}
$success = 0;
@ -161,6 +162,9 @@ switch ($action) {
}
break;
case 'delete':
if ($_app_stage == 'Demo') {
r2(getUrl('pluginmanager'), 'e', 'You cannot perform this action in Demo mode');
}
if (!is_writeable($CACHE_PATH)) {
r2(getUrl('pluginmanager'), 'e', 'Folder cache/ is not writable');
}
@ -171,7 +175,8 @@ switch ($action) {
$tipe = $routes['2'];
$plugin = $routes['3'];
$file = $CACHE_PATH . DIRECTORY_SEPARATOR . $plugin . '.zip';
if (file_exists($file)) unlink($file);
if (file_exists($file))
unlink($file);
if ($tipe == 'plugin') {
foreach ($json['plugins'] as $plg) {
if ($plg['id'] == $plugin) {
@ -212,6 +217,9 @@ switch ($action) {
}
break;
case 'install':
if ($_app_stage == 'Demo') {
r2(getUrl('pluginmanager'), 'e', 'You cannot perform this action in Demo mode');
}
if (!is_writeable($CACHE_PATH)) {
r2(getUrl('pluginmanager'), 'e', 'Folder cache/ is not writable');
}
@ -222,7 +230,8 @@ switch ($action) {
$tipe = $routes['2'];
$plugin = $routes['3'];
$file = $CACHE_PATH . DIRECTORY_SEPARATOR . $plugin . '.zip';
if (file_exists($file)) unlink($file);
if (file_exists($file))
unlink($file);
if ($tipe == 'plugin') {
foreach ($json['plugins'] as $plg) {
if ($plg['id'] == $plugin) {
@ -345,7 +354,7 @@ switch ($action) {
$ui->assign('plugins', $json['plugins']);
$ui->assign('pgs', $json['payment_gateway']);
$ui->assign('dvcs', $json['devices']);
$ui->display('plugin-manager.tpl');
$ui->display('admin/settings/plugin-manager.tpl');
}

View File

@ -20,7 +20,6 @@ require_once $DEVICE_PATH . DIRECTORY_SEPARATOR . 'MikrotikPppoe' . '.php';
switch ($action) {
case 'list':
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/pool.js"></script>');
$name = _post('name');
if ($name != '') {
@ -33,14 +32,14 @@ switch ($action) {
$ui->assign('d', $d);
run_hook('view_pool'); #HOOK
$ui->display('pool.tpl');
$ui->display('admin/pool/list.tpl');
break;
case 'add':
$r = ORM::for_table('tbl_routers')->find_many();
$ui->assign('r', $r);
run_hook('view_add_pool'); #HOOK
$ui->display('pool-add.tpl');
$ui->display('admin/pool/add.tpl');
break;
case 'edit':
@ -49,7 +48,7 @@ switch ($action) {
if ($d) {
$ui->assign('d', $d);
run_hook('view_edit_pool'); #HOOK
$ui->display('pool-edit.tpl');
$ui->display('admin/pool/edit.tpl');
} else {
r2(getUrl('pool/list'), 'e', Lang::T('Account Not Found'));
}
@ -149,7 +148,6 @@ switch ($action) {
}
case 'port':
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/pool.js"></script>');
$name = _post('name');
if ($name != '') {
@ -162,14 +160,14 @@ switch ($action) {
$ui->assign('d', $d);
run_hook('view_port'); #HOOK
$ui->display('port.tpl');
$ui->display('admin/port/list.tpl');
break;
case 'add-port':
$r = ORM::for_table('tbl_routers')->find_many();
$ui->assign('r', $r);
run_hook('view_add_port'); #HOOK
$ui->display('port-add.tpl');
$ui->display('admin/port/add.tpl');
break;
case 'edit-port':
@ -178,7 +176,7 @@ switch ($action) {
if ($d) {
$ui->assign('d', $d);
run_hook('view_edit_port'); #HOOK
$ui->display('port-edit.tpl');
$ui->display('admin/port/edit.tpl');
} else {
r2(getUrl('pool/port'), 'e', Lang::T('Account Not Found'));
}

View File

@ -22,7 +22,7 @@ switch ($action) {
$ui->assign('_system_menu', 'radius');
$ui->assign('_title', "Network Access Server");
$ui->assign('routers', ORM::for_table('tbl_routers')->find_many());
$ui->display('radius-nas-add.tpl');
$ui->display('admin/radius/nas-add.tpl');
break;
case 'nas-add-post':
$shortname = _post('shortname');
@ -78,7 +78,7 @@ switch ($action) {
if ($d) {
$ui->assign('routers', ORM::for_table('tbl_routers')->find_many());
$ui->assign('d', $d);
$ui->display('radius-nas-edit.tpl');
$ui->display('admin/radius/nas-edit.tpl');
} else {
r2(getUrl('radius/list'), 'e', Lang::T('Account Not Found'));
}
@ -147,5 +147,5 @@ switch ($action) {
}
$ui->assign('name', $name);
$ui->assign('nas', $nas);
$ui->display('radius-nas.tpl');
$ui->display('admin/radius/nas.tpl');
}

View File

@ -113,7 +113,8 @@ switch ($do) {
$d->save();
}
}
if (file_exists($_FILES['photo']['tmp_name'])) unlink($_FILES['photo']['tmp_name']);
if (file_exists($_FILES['photo']['tmp_name']))
unlink($_FILES['photo']['tmp_name']);
User::setFormCustomField($user);
run_hook('register_user'); #HOOK
$msg .= Lang::T('Registration successful') . '<br>';
@ -147,8 +148,45 @@ switch ($do) {
// Display register-otp.tpl if OTP is enabled
$ui->display('customer/register-otp.tpl');
} else {
// Display register.tpl if OTP is not enabled
$ui->display('customer/register.tpl');
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
if (!empty($config['login_page_logo']) && file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_logo'])) {
$login_logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_logo'];
} elseif (file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'login-logo.png')) {
$login_logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'login-logo.png';
} else {
$login_logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'login-logo.default.png';
}
if (!empty($config['login_page_wallpaper']) && file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_wallpaper'])) {
$wallpaper = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_wallpaper'];
} elseif (file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'wallpaper.png')) {
$wallpaper = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'wallpaper.png';
} else {
$wallpaper = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'wallpaper.default.png';
}
if (!empty($config['login_page_favicon']) && file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_favicon'])) {
$favicon = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_favicon'];
} elseif (file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'favicon.png')) {
$favicon = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'favicon.png';
} else {
$favicon = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'favicon.default.png';
}
$ui->assign('login_logo', $login_logo);
$ui->assign('wallpaper', $wallpaper);
$ui->assign('favicon', $favicon);
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_title', Lang::T('Login'));
$ui->assign('customFields', User::getFormCustomField($ui, true));
switch ($config['login_page_type']) {
case 'custom':
$ui->display('customer/reg-login-custom-' . $config['login_Page_template'] . '.tpl');
break;
default:
$ui->display('customer/register.tpl');
break;
}
}
}
break;
@ -196,6 +234,36 @@ switch ($do) {
$ui->display('customer/register-rotp.tpl');
}
} else {
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
if (!empty($config['login_page_logo']) && file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_logo'])) {
$login_logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_logo'];
} elseif (file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'login-logo.png')) {
$login_logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'login-logo.png';
} else {
$login_logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'login-logo.default.png';
}
if (!empty($config['login_page_wallpaper']) && file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_wallpaper'])) {
$wallpaper = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_wallpaper'];
} elseif (file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'wallpaper.png')) {
$wallpaper = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'wallpaper.png';
} else {
$wallpaper = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'wallpaper.default.png';
}
if (!empty($config['login_page_favicon']) && file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_favicon'])) {
$favicon = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . $config['login_page_favicon'];
} elseif (file_exists($UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'favicon.png')) {
$favicon = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'favicon.png';
} else {
$favicon = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'favicon.default.png';
}
$ui->assign('login_logo', $login_logo);
$ui->assign('wallpaper', $wallpaper);
$ui->assign('favicon', $favicon);
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_title', Lang::T('Login'));
$ui->assign('customFields', User::getFormCustomField($ui, true));
$ui->assign('username', "");
$ui->assign('fullname', "");
@ -204,7 +272,15 @@ switch ($do) {
$ui->assign('otp', false);
$ui->assign('_title', Lang::T('Register'));
run_hook('view_register'); #HOOK
$ui->display('customer/register.tpl');
switch ($config['login_page_type']) {
case 'custom':
$ui->display('customer/reg-login-custom-' . $config['login_Page_template'] . '.tpl');
break;
default:
$ui->display('customer/register.tpl');
break;
}
}
break;
}

View File

@ -55,9 +55,13 @@ switch ($action) {
->where('type', $tp);
if (count($mts) > 0) {
if (count($mts) != count($methods)) {
$w = [];
$v = [];
foreach ($mts as $mt) {
$query->where_like('method', "$mt - %");
$w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
}
}
if (count($rts) > 0) {
@ -84,9 +88,13 @@ switch ($action) {
}
if (count($mts) > 0) {
if (count($mts) != count($methods)) {
$w = [];
$v = [];
foreach ($mts as $mt) {
$query->where_like('method', "$mt - %");
$w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
}
}
if (count($rts) > 0) {
@ -114,13 +122,6 @@ switch ($action) {
if (count($plns) > 0) {
$query->where_in('plan_name', $plns);
}
if (count($mts) > 0) {
if (count($mts) != count($methods)) {
foreach ($mts as $mt) {
$query->where_like('method', "$mt - %");
}
}
}
$count = $query->count();
if ($count > 0) {
$result['datas'][] = $count;
@ -157,9 +158,13 @@ switch ($action) {
}
if (count($mts) > 0) {
if (count($mts) != count($methods)) {
$w = [];
$v = [];
foreach ($mts as $mt) {
$query->where_like('method', "$mt - %");
$w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
}
}
if (count($rts) > 0) {
@ -241,7 +246,7 @@ switch ($action) {
$total += $v;
$array['data'][] = $v;
}
if($total>0){
if ($total > 0) {
$result['datas'][] = $array;
}
}
@ -253,23 +258,34 @@ 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);
$ui->assign('q', $q);
$ui->display('reports-activation.tpl');
$ui->display('admin/reports/activation.tpl');
break;
case 'by-period':
@ -277,7 +293,7 @@ switch ($action) {
$ui->assign('mtime', $mtime);
$ui->assign('tdate', $tdate);
run_hook('view_reports_by_period'); #HOOK
$ui->display('reports-period.tpl');
$ui->display('admin/reports/period.tpl');
break;
case 'period-view':
@ -286,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);
}
@ -293,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 != '') {
@ -310,7 +330,7 @@ switch ($action) {
$ui->assign('tdate', $tdate);
$ui->assign('stype', $stype);
run_hook('view_reports_period'); #HOOK
$ui->display('reports-period-view.tpl');
$ui->display('admin/reports/period-view.tpl');
break;
case 'daily-report':
@ -343,16 +363,21 @@ 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);
}
if (count($mts) > 0) {
if (count($mts) != count($methods)) {
foreach ($mts as $mt) {
$query->where_like('method', "$mt - %");
}
$w = [];
$v = [];
foreach ($mts as $mt) {
$w[] = 'method';
$v[] = "$mt - %";
}
$query->where_likes($w, $v);
}
if (count($rts) > 0) {
$query->where_in('routers', $rts);
@ -384,6 +409,6 @@ switch ($action) {
$ui->assign('dr', $dr);
$ui->assign('mdate', $mdate);
run_hook('view_daily_reports'); #HOOK
$ui->display('reports.tpl');
$ui->display('admin/reports/list.tpl');
break;
}

View File

@ -23,24 +23,9 @@ $leafletpickerHeader = <<<EOT
EOT;
switch ($action) {
case 'maps':
$name = _post('name');
$query = ORM::for_table('tbl_routers')->where_not_equal('coordinates', '')->order_by_desc('id');
$query->selects(['id', 'name', 'coordinates', 'description', 'coverage', 'enabled']);
if ($name != '') {
$query->where_like('name', '%' . $name . '%');
}
$d = Paginator::findMany($query, ['name' => $name], '20', '', true);
$ui->assign('name', $name);
$ui->assign('d', $d);
$ui->assign('_title', Lang::T('Routers Geo Location Information'));
$ui->assign('xheader', $leafletpickerHeader);
$ui->assign('xfooter', '<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>');
$ui->display('routers-maps.tpl');
break;
case 'add':
run_hook('view_add_routers'); #HOOK
$ui->display('routers-add.tpl');
$ui->display('admin/routers/add.tpl');
break;
case 'edit':
@ -53,7 +38,7 @@ switch ($action) {
if ($d) {
$ui->assign('d', $d);
run_hook('view_router_edit'); #HOOK
$ui->display('routers-edit.tpl');
$ui->display('admin/routers/edit.tpl');
} else {
r2(getUrl('routers/list'), 'e', Lang::T('Account Not Found'));
}
@ -204,7 +189,6 @@ switch ($action) {
break;
default:
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/routers.js"></script>');
$name = _post('name');
$name = _post('name');
@ -215,6 +199,6 @@ switch ($action) {
$d = Paginator::findMany($query, ['name' => $name]);
$ui->assign('d', $d);
run_hook('view_list_routers'); #HOOK
$ui->display('routers.tpl');
$ui->display('admin/routers/list.tpl');
break;
}

View File

@ -53,7 +53,6 @@ switch ($action) {
}
r2(getUrl('services/hotspot'), 'w', 'Unknown command');
case 'hotspot':
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/hotspot.js"></script>');
$name = _req('name');
$type1 = _req('type1');
$type2 = _req('type2');
@ -139,7 +138,7 @@ switch ($action) {
$d = Paginator::findMany($query, ['name' => $name], 20, $append_url);
$ui->assign('d', $d);
run_hook('view_list_plans'); #HOOK
$ui->display('hotspot.tpl');
$ui->display('admin/hotspot/list.tpl');
break;
case 'add':
$d = ORM::for_table('tbl_bandwidth')->find_many();
@ -156,7 +155,7 @@ switch ($action) {
}
$ui->assign('devices', $devices);
run_hook('view_add_plan'); #HOOK
$ui->display('hotspot-add.tpl');
$ui->display('admin/hotspot/add.tpl');
break;
case 'edit':
@ -191,7 +190,7 @@ switch ($action) {
}
$ui->assign('exps', $exps);
run_hook('view_edit_plan'); #HOOK
$ui->display('hotspot-edit.tpl');
$ui->display('admin/hotspot/edit.tpl');
} else {
r2(getUrl('services/hotspot'), 'e', Lang::T('Account Not Found'));
}
@ -428,7 +427,6 @@ switch ($action) {
case 'pppoe':
$ui->assign('_title', Lang::T('PPPOE Plans'));
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/pppoe.js"></script>');
$name = _post('name');
$name = _req('name');
@ -516,7 +514,7 @@ switch ($action) {
$ui->assign('d', $d);
run_hook('view_list_ppoe'); #HOOK
$ui->display('pppoe.tpl');
$ui->display('admin/pppoe/list.tpl');
break;
case 'pppoe-add':
@ -535,7 +533,7 @@ switch ($action) {
}
$ui->assign('devices', $devices);
run_hook('view_add_ppoe'); #HOOK
$ui->display('pppoe-add.tpl');
$ui->display('admin/pppoe/add.tpl');
break;
case 'pppoe-edit':
@ -578,7 +576,7 @@ switch ($action) {
}
$ui->assign('exps', $exps);
run_hook('view_edit_ppoe'); #HOOK
$ui->display('pppoe-edit.tpl');
$ui->display('admin/pppoe/edit.tpl');
} else {
r2(getUrl('services/pppoe'), 'e', Lang::T('Account Not Found'));
}
@ -819,12 +817,12 @@ switch ($action) {
$ui->assign('d', $d);
run_hook('view_list_balance'); #HOOK
$ui->display('balance.tpl');
$ui->display('admin/balance/list.tpl');
break;
case 'balance-add':
$ui->assign('_title', Lang::T('Balance Plans'));
run_hook('view_add_balance'); #HOOK
$ui->display('balance-add.tpl');
$ui->display('admin/balance/add.tpl');
break;
case 'balance-edit':
$ui->assign('_title', Lang::T('Balance Plans'));
@ -832,7 +830,7 @@ switch ($action) {
$d = ORM::for_table('tbl_plans')->find_one($id);
$ui->assign('d', $d);
run_hook('view_edit_balance'); #HOOK
$ui->display('balance-edit.tpl');
$ui->display('admin/balance/edit.tpl');
break;
case 'balance-delete':
$id = $routes['2'];
@ -921,7 +919,6 @@ switch ($action) {
break;
case 'vpn':
$ui->assign('_title', Lang::T('VPN Plans'));
$ui->assign('xfooter', '<script type="text/javascript" src="'.APP_URL.'/ui/lib/c/pppoe.js"></script>');
$name = _post('name');
$name = _req('name');
@ -1009,7 +1006,7 @@ switch ($action) {
$ui->assign('d', $d);
run_hook('view_list_vpn'); #HOOK
$ui->display('vpn.tpl');
$ui->display('admin/vpn/list.tpl');
break;
case 'vpn-add':
@ -1028,7 +1025,7 @@ switch ($action) {
}
$ui->assign('devices', $devices);
run_hook('view_add_vpn'); #HOOK
$ui->display('vpn-add.tpl');
$ui->display('admin/vpn/add.tpl');
break;
case 'vpn-edit':
@ -1071,7 +1068,7 @@ switch ($action) {
}
$ui->assign('exps', $exps);
run_hook('view_edit_vpn'); #HOOK
$ui->display('vpn-edit.tpl');
$ui->display('admin/vpn/edit.tpl');
} else {
r2(getUrl('services/vpn'), 'e', Lang::T('Account Not Found'));
}
@ -1300,5 +1297,5 @@ switch ($action) {
}
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -23,7 +23,7 @@ switch ($action) {
$d->value = 'yes';
$d->save();
}
r2('./docs');
r2(APP_URL . '/docs');
break;
case 'devices':
$files = scandir($DEVICE_PATH);
@ -50,7 +50,7 @@ switch ($action) {
}
}
$ui->assign('devices', $devices);
$ui->display('app-devices.tpl');
$ui->display('admin/settings/devices.tpl');
break;
case 'app':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
@ -58,23 +58,35 @@ switch ($action) {
}
if (!empty(_get('testWa'))) {
if ($_app_stage == 'Demo') {
r2(getUrl('settings/app'), 'e', 'You cannot perform this action in Demo mode');
}
$result = Message::sendWhatsapp(_get('testWa'), 'PHPNuxBill Test Whatsapp');
r2(getUrl('settings/app'), 's', 'Test Whatsapp has been send<br>Result: ' . $result);
}
if (!empty(_get('testSms'))) {
if ($_app_stage == 'Demo') {
r2(getUrl('settings/app'), 'e', 'You cannot perform this action in Demo mode');
}
$result = Message::sendSMS(_get('testSms'), 'PHPNuxBill Test SMS');
r2(getUrl('settings/app'), 's', 'Test SMS has been send<br>Result: ' . $result);
}
if (!empty(_get('testEmail'))) {
if ($_app_stage == 'Demo') {
r2(getUrl('settings/app'), 'e', 'You cannot perform this action in Demo mode');
}
Message::sendEmail(_get('testEmail'), 'PHPNuxBill Test Email', 'PHPNuxBill Test Email Body');
r2(getUrl('settings/app'), 's', 'Test Email has been send');
}
if (!empty(_get('testTg'))) {
if ($_app_stage == 'Demo') {
r2(getUrl('settings/app'), 'e', 'You cannot perform this action in Demo mode');
}
$result = Message::sendTelegram('PHPNuxBill Test Telegram');
r2(getUrl('settings/app'), 's', 'Test Telegram has been send<br>Result: ' . $result);
}
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
$UPLOAD_URL_PATH = str_replace($root_path, '', $UPLOAD_PATH);
if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) {
$logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.png?' . time();
} else {
@ -134,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';
}
@ -165,10 +178,15 @@ switch ($action) {
run_hook('view_app_settings'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('app-settings.tpl');
$ui->display('admin/settings/app.tpl');
break;
case 'app-post':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/app'), 'e', 'You cannot perform this action in Demo mode');
}
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
@ -186,9 +204,11 @@ switch ($action) {
run_hook('save_settings'); #HOOK
if (!empty($_FILES['logo']['name'])) {
if (function_exists('imagecreatetruecolor')) {
if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png')) unlink($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png');
if (file_exists($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png'))
unlink($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png');
File::resizeCropImage($_FILES['logo']['tmp_name'], $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'logo.png', 1078, 200, 100);
if (file_exists($_FILES['logo']['tmp_name'])) unlink($_FILES['logo']['tmp_name']);
if (file_exists($_FILES['logo']['tmp_name']))
unlink($_FILES['logo']['tmp_name']);
} else {
r2(getUrl('settings/app'), 'e', 'PHP GD is not installed');
}
@ -205,7 +225,7 @@ switch ($action) {
$ui->assign("error_message", "Radius table not found.<br><br>" .
$e->getMessage() .
"<br><br>Download <a href=\"https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/Development/install/radius.sql\">here</a> or <a href=\"https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/install/radius.sql\">here</a> and import it to database.<br><br>Check config.php for radius connection details");
$ui->display('error.tpl');
$ui->display('admin/error.tpl');
die();
}
}
@ -216,6 +236,88 @@ switch ($action) {
$_POST['man_fields_custom'] = isset($_POST['man_fields_custom']) ? 'yes' : 'no';
$enable_session_timeout = isset($_POST['enable_session_timeout']) ? 1 : 0;
$_POST['enable_session_timeout'] = $enable_session_timeout;
$_POST['notification_reminder_1day'] = isset($_POST['notification_reminder_1day']) ? 'yes' : 'no';
$_POST['notification_reminder_3days'] = isset($_POST['notification_reminder_3days']) ? 'yes' : 'no';
$_POST['notification_reminder_7days'] = isset($_POST['notification_reminder_7days']) ? 'yes' : 'no';
// hide dashboard
$_POST['hide_mrc'] = _post('hide_mrc', 'no');
$_POST['hide_tms'] = _post('hide_tms', 'no');
$_POST['hide_al'] = _post('hide_al', 'no');
$_POST['hide_uet'] = _post('hide_uet', 'no');
$_POST['hide_vs'] = _post('hide_vs', 'no');
$_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) {
@ -234,98 +336,6 @@ switch ($action) {
}
break;
case 'login-page-post':
// 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");
@ -352,16 +362,19 @@ switch ($action) {
run_hook('view_localisation'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('app-localisation.tpl');
$ui->display('admin/settings/localisation.tpl');
break;
case 'localisation-post':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
if ($_app_stage == 'Demo') {
r2(getUrl('settings/localisation'), 'e', 'You cannot perform this action in Demo mode');
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/app'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
r2(getUrl('settings/localisation'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$tzone = _post('tzone');
$date_format = _post('date_format');
@ -369,7 +382,7 @@ switch ($action) {
$lan = _post('lan');
run_hook('save_localisation'); #HOOK
if ($tzone == '' or $date_format == '' or $lan == '') {
r2(getUrl('settings/app'), 'e', Lang::T('All field is required'));
r2(getUrl('settings/localisation'), 'e', Lang::T('All field is required'));
} else {
$d = ORM::for_table('tbl_appconfig')->where('setting', 'timezone')->find_one();
$d->value = $tzone;
@ -472,11 +485,11 @@ switch ($action) {
} else if ($admin['user_type'] == 'Admin') {
$query = ORM::for_table('tbl_users')
->where_like('username', '%' . $search . '%')->where_any_is([
['user_type' => 'Report'],
['user_type' => 'Agent'],
['user_type' => 'Sales'],
['id' => $admin['id']]
])->order_by_asc('id');
['user_type' => 'Report'],
['user_type' => 'Agent'],
['user_type' => 'Sales'],
['id' => $admin['id']]
])->order_by_asc('id');
$d = Paginator::findMany($query, ['search' => $search]);
} else {
$query = ORM::for_table('tbl_users')
@ -527,7 +540,7 @@ switch ($action) {
run_hook('view_list_admin'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin.tpl');
$ui->display('admin/admin/list.tpl');
break;
case 'users-add':
@ -538,17 +551,17 @@ switch ($action) {
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_title', Lang::T('Add User'));
$ui->assign('agents', ORM::for_table('tbl_users')->where('user_type', 'Agent')->find_many());
$ui->display('admin-add.tpl');
$ui->display('admin/admin/add.tpl');
break;
case 'users-view':
$ui->assign('_title', Lang::T('Edit User'));
$id = $routes['2'];
$id = $routes['2'];
if (empty($id)) {
$id = $admin['id'];
}
//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
@ -567,7 +580,7 @@ switch ($action) {
$ui->assign('_title', $d['username']);
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin-view.tpl');
$ui->display('admin/admin/view.tpl');
} else {
r2(getUrl('settings/users'), 'e', Lang::T('Account Not Found'));
}
@ -577,7 +590,7 @@ switch ($action) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$ui->assign('_title', Lang::T('Edit User'));
$id = $routes['2'];
$id = $routes['2'];
if (empty($id)) {
$id = $admin['id'];
}
@ -623,7 +636,7 @@ switch ($action) {
run_hook('view_edit_admin'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin-edit.tpl');
$ui->display('admin/admin/edit.tpl');
} else {
r2(getUrl('settings/users'), 'e', Lang::T('Account Not Found'));
}
@ -633,8 +646,10 @@ switch ($action) {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$id = $routes['2'];
if ($_app_stage == 'Demo') {
r2(getUrl('settings/users'), 'e', 'You cannot perform this action in Demo mode');
}
$id = $routes['2'];
if (($admin['id']) == $id) {
r2(getUrl('settings/users'), 'e', 'Sorry You can\'t delete yourself');
}
@ -652,6 +667,9 @@ switch ($action) {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
if ($_app_stage == 'Demo') {
r2(getUrl('settings/users-add'), 'e', 'You cannot perform this action in Demo mode');
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/users-add'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
@ -720,6 +738,9 @@ switch ($action) {
break;
case 'users-edit-post':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/users-edit/'), 'e', 'You cannot perform this action in Demo mode');
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/users-edit/'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
@ -813,7 +834,7 @@ switch ($action) {
}
}
if (file_exists($imgPath)) {
if ($d['photo'] != '' && strpos($d['photo'], 'default') === false) {
if ($d['photo'] != '' && strpos($d['photo'], 'default') === false) {
if (file_exists($UPLOAD_PATH . $d['photo'])) {
unlink($UPLOAD_PATH . $d['photo']);
if (file_exists($UPLOAD_PATH . $d['photo'] . '.thumb.jpg')) {
@ -823,7 +844,8 @@ switch ($action) {
}
$d->photo = '/photos/' . $subfolder . '/' . $hash . '.jpg';
}
if (file_exists($_FILES['photo']['tmp_name'])) unlink($_FILES['photo']['tmp_name']);
if (file_exists($_FILES['photo']['tmp_name']))
unlink($_FILES['photo']['tmp_name']);
} else {
r2(getUrl('settings/app'), 'e', 'PHP GD is not installed');
}
@ -869,10 +891,13 @@ switch ($action) {
run_hook('view_change_password'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('change-password.tpl');
$ui->display('admin/change-password.tpl');
break;
case 'change-password-post':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/change-password'), 'e', 'You cannot perform this action in Demo mode');
}
$password = _post('password');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
@ -926,9 +951,12 @@ switch ($action) {
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_default', json_decode(file_get_contents($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'notifications.default.json'), true));
$ui->display('app-notifications.tpl');
$ui->display('admin/settings/notifications.tpl');
break;
case 'notifications-post':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/notifications'), 'e', 'You cannot perform this action in Demo mode');
}
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
@ -953,11 +981,14 @@ switch ($action) {
}
$ui->assign('tables', $tables);
run_hook('view_database'); #HOOK
$ui->display('dbstatus.tpl');
$ui->display('admin/settings/dbstatus.tpl');
}
break;
case 'dbbackup':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/dbstatus'), 'e', 'You cannot perform this action in Demo mode');
}
if (!in_array($admin['user_type'], ['SuperAdmin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
@ -978,6 +1009,9 @@ switch ($action) {
echo json_encode($array);
break;
case 'dbrestore':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/dbstatus'), 'e', 'You cannot perform this action in Demo mode');
}
if (!in_array($admin['user_type'], ['SuperAdmin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
@ -1025,7 +1059,8 @@ switch ($action) {
} catch (Throwable $e) {
} catch (Exception $e) {
}
if (file_exists($_FILES['json']['tmp_name'])) unlink($_FILES['json']['tmp_name']);
if (file_exists($_FILES['json']['tmp_name']))
unlink($_FILES['json']['tmp_name']);
r2(getUrl('settings/dbstatus'), 's', "Restored $suc success $fal failed");
} else {
r2(getUrl('settings/dbstatus'), 'e', 'Upload failed');
@ -1043,10 +1078,13 @@ switch ($action) {
}
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('language-add.tpl');
$ui->display('admin/settings/language-add.tpl');
break;
case 'lang-post':
if ($_app_stage == 'Demo') {
r2(getUrl('settings/dbstatus'), 'e', 'You cannot perform this action in Demo mode');
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/language'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
@ -1062,6 +1100,9 @@ switch ($action) {
}
if (_post('save') == 'save') {
if ($_app_stage == 'Demo') {
r2(getUrl('settings/maintenance'), 'e', 'You cannot perform this action in Demo mode');
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/maintenance'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
@ -1095,7 +1136,7 @@ switch ($action) {
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_c', $config);
$ui->assign('_title', Lang::T('Maintenance Mode Settings'));
$ui->display('maintenance-mode.tpl');
$ui->display('admin/settings/maintenance-mode.tpl');
break;
case 'miscellaneous':
@ -1104,6 +1145,9 @@ switch ($action) {
exit;
}
if (_post('save') == 'save') {
if ($_app_stage == 'Demo') {
r2(getUrl('settings/miscellaneous'), 'e', 'You cannot perform this action in Demo mode');
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/miscellaneous'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
@ -1127,9 +1171,9 @@ switch ($action) {
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_c', $config);
$ui->assign('_title', Lang::T('Miscellaneous Settings'));
$ui->display('app-miscellaneous.tpl');
$ui->display('admin/settings/miscellaneous.tpl');
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -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);
@ -64,11 +100,23 @@ switch ($action) {
}
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');
} else {
r2(getUrl('voucher/list-activated'), 'e', Lang::T('Not Found'));
}
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -0,0 +1,147 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Dashboard Widgets'));
$ui->assign('_system_menu', 'settings');
$action = alphanumeric($routes['1']);
$ui->assign('_admin', $admin);
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
r2(getUrl('dashboard'), 'e', Lang::T('You do not have permission to access this page'));
}
$tipeUser = _req("user");
if (empty($tipeUser)) {
$tipeUser = 'Admin';
}
if($tipeUser == 'Customer') {
$WIDGET_PATH .= DIRECTORY_SEPARATOR. 'customer';
}
$ui->assign('tipeUser', $tipeUser);
$max = ORM::for_table('tbl_widgets')->where("user", $tipeUser)->max('position');
$max2 = substr_count($config['dashboard_' . $tipeUser], '.') + substr_count($config['dashboard_' . $tipeUser], ',') + 1;
if ($max2 > $max) {
$max = $max2;
}
$ui->assign('max', $max);
if ($action == 'add') {
$pos = alphanumeric($routes['2']);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$orders = alphanumeric($_POST['orders']);
$position = alphanumeric($_POST['position']);
$tipeUser = alphanumeric($_POST['tipeUser']);
$enabled = alphanumeric($_POST['enabled']);
$title = _post('title');
$widget = _post('widget');
$d = ORM::for_table('tbl_widgets')->create();
$d->orders = $orders;
$d->position = $position;
$d->user = $tipeUser;
$d->enabled = $enabled;
$d->title = $title;
$d->widget = $widget;
$d->content = _post('content');
$d->save();
if ($d->id() > 0) {
r2(getUrl('widgets&user=' . $tipeUser), 's', 'Widget Added Successfully');
}
}
$files = scandir($WIDGET_PATH);
$widgets = [];
foreach ($files as $file) {
if (strpos($file, '.php') !== false) {
$name = ucwords(str_replace('.php', '', str_replace('_', ' ', $file)));
$widgets[str_replace('.php', '', $file)] = $name;
}
}
$widget['position'] = $pos;
$widget['user'] = $tipeUser;
$ui->assign('users', ORM::for_table('tbl_widgets')->getEnum("user"));
$ui->assign('do', 'add');
$ui->assign('widgets', $widgets);
$ui->assign('widget', $widget);
$ui->display('admin/settings/widgets_add_edit.tpl');
} else if ($action == 'edit') {
// if request method post then save data
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = alphanumeric($_POST['id']);
$orders = alphanumeric($_POST['orders']);
$position = alphanumeric($_POST['position']);
$tipeUser = alphanumeric($_POST['tipeUser']);
$enabled = alphanumeric($_POST['enabled']);
$title = _post('title');
$widget = _post('widget');
$d = ORM::for_table('tbl_widgets')->find_one($id);
$d->orders = $orders;
$d->position = $position;
$d->user = $tipeUser;
$d->enabled = $enabled;
$d->title = $title;
$d->widget = $widget;
$d->content = _post('content');
$d->save();
r2(getUrl('widgets&user=' . $tipeUser), 's', 'Widget Saved Successfully');
}
$id = alphanumeric($routes['2']);
$widget = ORM::for_table('tbl_widgets')->find_one($id);
$files = scandir($WIDGET_PATH);
$widgets = [];
foreach ($files as $file) {
if (strpos($file, '.php') !== false) {
$name = ucwords(str_replace('.php', '', str_replace('_', ' ', $file)));
$widgets[str_replace('.php', '', $file)] = $name;
}
}
$ui->assign('users', ORM::for_table('tbl_widgets')->getEnum("user"));
$ui->assign('do', 'edit');
$ui->assign('widgets', $widgets);
$ui->assign('widget', $widget);
$ui->display('admin/settings/widgets_add_edit.tpl');
} else if ($action == 'delete') {
$id = alphanumeric($routes['2']);
$d = ORM::for_table('tbl_widgets')->find_one($id);
if ($d) {
$d->delete();
r2(getUrl('widgets&user=' . $tipeUser), 's', 'Widget Deleted Successfully');
}
r2(getUrl('widgets&user=' . $tipeUser), 'e', 'Widget Not Found');
} else if (!empty($action) && file_exists("system/widget/$action.php") && !empty($routes['2'])) {
require_once "system/widget/$action.php";
try {
(new $action)->run_command($routes['2']);
} catch (Throwable $e) {
//nothing to do
}
} else if ($action == 'pos') {
$jml = count($_POST['orders']);
for ($i = 0; $i < $jml; $i++) {
$d = ORM::for_table('tbl_widgets')->find_one($_POST['id'][$i]);
$d->orders = $_POST['orders'][$i];
$d->save();
}
r2(getUrl('widgets&user=' . $tipeUser), 's', 'Widget order Saved Successfully');
} else {
if (_post("save") == 'struct') {
$d = ORM::for_table('tbl_appconfig')->where('setting', 'dashboard_' . $tipeUser)->find_one();
if ($d) {
$d->value = _post('dashboard');
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'dashboard_' . $tipeUser;
$d->value = _post('dashboard');
$d->save();
}
_alert("Dashboard Structure Saved Successfully", "success", getUrl('widgets&user=' . $tipeUser));
}
$widgets = ORM::for_table('tbl_widgets')->where("user", $tipeUser)->order_by_asc("orders")->find_many();
$ui->assign('widgets', $widgets);
$ui->display('admin/settings/widgets.tpl');
}

View File

@ -30,7 +30,7 @@ if (php_sapi_name() !== 'cli') {
echo "PHP Time\t" . date('Y-m-d H:i:s') . "\n";
$res = ORM::raw_execute('SELECT NOW() AS WAKTU;');
$statement = ORM::get_last_statement();
$rows = array();
$rows = [];
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
echo "MYSQL Time\t" . $row['WAKTU'] . "\n";
}
@ -45,80 +45,111 @@ echo "Found " . count($d) . " user(s)\n";
run_hook('cronjob'); #HOOK
foreach ($d as $ds) {
$date_now = strtotime(date("Y-m-d H:i:s"));
$expiration = strtotime($ds['expiration'] . ' ' . $ds['time']);
echo $ds['expiration'] . " : " . (($isCli) ? $ds['username'] : Lang::maskText($ds['username']));
if ($date_now >= $expiration) {
echo " : EXPIRED \r\n";
$u = ORM::for_table('tbl_user_recharges')->where('id', $ds['id'])->find_one();
$c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one();
$p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one();
if (empty($c)) {
$c = $u;
}
$dvc = Package::getDevice($p);
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $p['device'])->remove_customer($c, $p);
} else {
echo "Cron error Devices $p[device] not found, cannot disconnect $c[username]";
Message::sendTelegram("Cron error Devices $p[device] not found, cannot disconnect $c[username]");
}
}
echo Message::sendPackageNotification($c, $u['namebp'], $p['price'], $textExpired, $config['user_notification_expired']) . "\n";
//update database user dengan status off
$u->status = 'off';
$u->save();
try {
$date_now = strtotime(date("Y-m-d H:i:s"));
$expiration = strtotime($ds['expiration'] . ' ' . $ds['time']);
echo $ds['expiration'] . " : " . ($isCli ? $ds['username'] : Lang::maskText($ds['username']));
// autorenewal from deposit
if ($config['enable_balance'] == 'yes' && $c['auto_renewal']) {
list($bills, $add_cost) = User::getBills($ds['customer_id']);
if ($add_cost != 0) {
if (!empty($add_cost)) {
if ($date_now >= $expiration) {
echo " : EXPIRED \r\n";
// Fetch user recharge details
$u = ORM::for_table('tbl_user_recharges')->where('id', $ds['id'])->find_one();
if (!$u) {
throw new Exception("User recharge record not found for ID: " . $ds['id']);
}
// Fetch customer details
$c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one();
if (!$c) {
$c = $u;
}
// Fetch plan details
$p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one();
if (!$p) {
throw new Exception("Plan not found for ID: " . $u['plan_id']);
}
$dvc = Package::getDevice($p);
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $p['device'])->remove_customer($c, $p);
} else {
throw new Exception("Cron error: Devices " . $p['device'] . "not found, cannot disconnect ".$c['username']."\n");
}
}
// Send notification and update user status
try {
echo Message::sendPackageNotification(
$c,
$u['namebp'],
$p['price'],
Message::getMessageType($p['type'], $textExpired),
$config['user_notification_expired']
) . "\n";
$u->status = 'off';
$u->save();
} catch (Throwable $e) {
_log($e->getMessage());
sendTelegram($e->getMessage());
echo "Error: " . $e->getMessage() . "\n";
}
// Auto-renewal from deposit
if ($config['enable_balance'] == 'yes' && $c['auto_renewal']) {
[$bills, $add_cost] = User::getBills($ds['customer_id']);
if ($add_cost != 0) {
$p['price'] += $add_cost;
}
}
if ($p && $c['balance'] >= $p['price']) {
if (Package::rechargeUser($ds['customer_id'], $ds['routers'], $p['id'], 'Customer', 'Balance')) {
// if success, then get the balance
Balance::min($ds['customer_id'], $p['price']);
echo "plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n";
echo "auto renewall Success\n";
if ($p && $c['balance'] >= $p['price']) {
if (Package::rechargeUser($ds['customer_id'], $ds['routers'], $p['id'], 'Customer', 'Balance')) {
Balance::min($ds['customer_id'], $p['price']);
echo "plan enabled: " . (string) $p['enabled'] . " | User balance: " . (string) $c['balance'] . " | price " . (string) $p['price'] . "\n";
echo "auto renewal Success\n";
} else {
echo "plan enabled: " . $p['enabled'] . " | User balance: " . $c['balance'] . " | price " . $p['price'] . "\n";
echo "auto renewal Failed\n";
Message::sendTelegram("FAILED RENEWAL #cron\n\n#u." . $c['username'] . " #buy #Hotspot \n" . $p['name_plan'] .
"\nRouter: " . $p['routers'] .
"\nPrice: " . $p['price']);
}
} else {
echo "plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n";
echo "auto renewall Failed\n";
Message::sendTelegram("FAILED RENEWAL #cron\n\n#u$c[username] #buy #Hotspot \n" . $p['name_plan'] .
"\nRouter: " . $p['routers'] .
"\nPrice: " . $p['price']);
echo "no renewal | plan enabled: " . (string) $p['enabled'] . " | User balance: " . (string) $c['balance'] . " | price " . (string) $p['price'] . "\n";
}
} else {
echo "no renewall | plan enabled: $p[enabled] | User balance: $c[balance] | price $p[price]\n";
echo "no renewal | balance" . $config['enable_balance'] . " auto_renewal " . $c['auto_renewal'] . "\n";
}
} else {
echo "no renewall | balance $config[enable_balance] auto_renewal $c[auto_renewal]\n";
echo " : ACTIVE \r\n";
}
} else {
echo " : ACTIVE \r\n";
} catch (Throwable $e) {
// Catch any unexpected errors
_log($e->getMessage());
sendTelegram($e->getMessage());
echo "Unexpected Error: " . $e->getMessage() . "\n";
}
}
//Cek interim-update radiusrest
if ($config['frrest_interim_update'] != 0) {
//Cek interim-update radiusrest
if ($config['frrest_interim_update'] != 0) {
$r_a = ORM::for_table('rad_acct')
->whereRaw("BINARY acctstatustype = 'Start' OR acctstatustype = 'Interim-Update'")
->where_lte('dateAdded', date("Y-m-d H:i:s"))->find_many();
->whereRaw("BINARY acctstatustype = 'Start' OR acctstatustype = 'Interim-Update'")
->where_lte('dateAdded', date("Y-m-d H:i:s"))->find_many();
foreach ($r_a as $ra) {
$interval = $_c['frrest_interim_update']*60;
$timeUpdate = strtotime($ra['dateAdded'])+$interval;
$timeNow = strtotime(date("Y-m-d H:i:s"));
if ($timeNow >= $timeUpdate) {
$ra->acctstatustype = 'Stop';
$ra->save();
}
}
foreach ($r_a as $ra) {
$interval = $_c['frrest_interim_update'] * 60;
$timeUpdate = strtotime($ra['dateAdded']) + $interval;
$timeNow = strtotime(date("Y-m-d H:i:s"));
if ($timeNow >= $timeUpdate) {
$ra->acctstatustype = 'Stop';
$ra->save();
}
}
}
if ($config['router_check']) {
@ -137,7 +168,7 @@ if ($config['router_check']) {
foreach ($routers as $router) {
// check if custom port
if (strpos($router->ip_address, ':') === false){
if (strpos($router->ip_address, ':') === false) {
$ip = $router->ip_address;
$port = 8728;
} else {
@ -207,14 +238,7 @@ if ($config['router_check']) {
Message::SendEmail($adminEmail, $subject, $message);
sendTelegram($message);
}
echo "Router monitoring finished\n";
}
if (defined('PHP_SAPI') && PHP_SAPI === 'cli') {
echo "Cronjob finished\n";
} else {
echo "</pre>";
echo "Router monitoring finished checking.\n";
}
flock($lock, LOCK_UN);
@ -224,5 +248,5 @@ unlink($lockFile);
$timestampFile = "$UPLOAD_PATH/cron_last_run.txt";
file_put_contents($timestampFile, time());
run_hook('cronjob_end'); #HOOK
echo "Cron job finished and completed successfully.\n";

View File

@ -23,7 +23,7 @@ run_hook('cronjob_reminder'); #HOOK
echo "PHP Time\t" . date('Y-m-d H:i:s') . "\n";
$res = ORM::raw_execute('SELECT NOW() AS WAKTU;');
$statement = ORM::get_last_statement();
$rows = array();
$rows = [];
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
echo "MYSQL Time\t" . $row['WAKTU'] . "\n";
}
@ -39,22 +39,52 @@ foreach ($d as $ds) {
$p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one();
$c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one();
if ($p['validity_unit'] == 'Period') {
// Postpaid price from field
$add_inv = User::getAttribute("Invoice", $ds['customer_id']);
if (empty ($add_inv) or $add_inv == 0) {
$price = $p['price'];
} else {
$price = $add_inv;
}
} else {
// Postpaid price from field
$add_inv = User::getAttribute("Invoice", $ds['customer_id']);
if (empty($add_inv) or $add_inv == 0) {
$price = $p['price'];
} else {
$price = $add_inv;
}
} else {
$price = $p['price'];
}
if ($ds['expiration'] == $day7) {
echo Message::sendPackageNotification($c, $p['name_plan'], $price, Lang::getNotifText('reminder_7_day'), $config['user_notification_reminder']) . "\n";
} else if ($ds['expiration'] == $day3) {
echo Message::sendPackageNotification($c, $p['name_plan'], $price, Lang::getNotifText('reminder_3_day'), $config['user_notification_reminder']) . "\n";
} else if ($ds['expiration'] == $day1) {
echo Message::sendPackageNotification($c, $p['name_plan'], $price, Lang::getNotifText('reminder_1_day'), $config['user_notification_reminder']) . "\n";
if ($ds['expiration'] == $day7 && $config['notification_reminder_7days'] !== 'no') {
try {
echo Message::sendPackageNotification(
$c,
$p['name_plan'],
$price,
Message::getMessageType($p['type'], Lang::getNotifText('reminder_7_day')),
$config['user_notification_reminder']
) . "\n";
} 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_3days'] !== 'no') {
try {
echo Message::sendPackageNotification(
$c,
$p['name_plan'],
$price,
Message::getMessageType($p['type'], Lang::getNotifText('reminder_3_day')),
$config['user_notification_reminder']
) . "\n";
} catch (Exception $e) {
sendTelegram("Cron Reminder failed to send 3-day reminder to " . $ds['username'] . " Error: " . $e->getMessage());
}
} else if ($ds['expiration'] == $day1 && $config['notification_reminder_1day'] !== 'no') {
try {
echo Message::sendPackageNotification(
$c,
$p['name_plan'],
$price,
Message::getMessageType($p['type'], Lang::getNotifText('reminder_1_day')),
$config['user_notification_reminder']
) . "\n";
} catch (Exception $e) {
sendTelegram("Cron Reminder failed to send 1-day reminder to " . $ds['username'] . " Error: " . $e->getMessage());
}
}
}
}
}

View File

@ -122,6 +122,9 @@ class MikrotikHotspot
if (!empty(trim($bw['burst']))) {
$rate .= ' ' . $bw['burst'];
}
if ($bw['rate_up'] == '0' || $bw['rate_down'] == '0') {
$rate = '';
}
$addRequest = new RouterOS\Request('/ip/hotspot/user/profile/add');
$client->sendSync(
$addRequest
@ -202,6 +205,9 @@ class MikrotikHotspot
if (!empty(trim($bw['burst']))) {
$rate .= ' ' . $bw['burst'];
}
if ($bw['rate_up'] == '0' || $bw['rate_down'] == '0') {
$rate = '';
}
$setRequest = new RouterOS\Request('/ip/hotspot/user/profile/set');
$client->sendSync(
$setRequest

View File

@ -149,6 +149,9 @@ class MikrotikPppoe
if(!empty(trim($bw['burst']))){
$rate .= ' '.$bw['burst'];
}
if ($bw['rate_up'] == '0' || $bw['rate_down'] == '0') {
$rate = '';
}
$pool = ORM::for_table("tbl_pool")->where("pool_name", $plan['pool'])->find_one();
$addRequest = new RouterOS\Request('/ppp/profile/add');
$client->sendSync(
@ -205,6 +208,9 @@ class MikrotikPppoe
if(!empty(trim($bw['burst']))){
$rate .= ' '.$bw['burst'];
}
if ($bw['rate_up'] == '0' || $bw['rate_down'] == '0') {
$rate = '';
}
$pool = ORM::for_table("tbl_pool")->where("pool_name", $new_plan['pool'])->find_one();
$setRequest = new RouterOS\Request('/ppp/profile/set');
$client->sendSync(
@ -238,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']);
@ -253,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']);
@ -278,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']);
@ -323,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);
@ -333,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');
@ -370,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');
@ -386,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(
@ -399,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');
@ -414,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

View File

@ -4,7 +4,7 @@
"Announcement": "Pemberitahuan",
"Registration_Info": "Info Pendaftaran",
"Voucher_not_found__please_buy_voucher_befor_register": "Voucher tidak ditemukan, silakan beli voucher sebelum mendaftar",
"Register_Success__You_can_login_now": "Daftar Sukses! Anda dapat masuk sekarang",
"Register_Success__You_can_login_now": "Daftar berhadil! Anda dapat masuk sekarang",
"Log_in_to_Member_Panel": "Masuk ke Panel Anggota",
"Register_as_Member": "Daftar sebagai Anggota",
"Enter_Admin_Area": "Masuk ke Admin Panel",
@ -13,11 +13,12 @@
"Password": "Kata Sandi",
"Passwords_does_not_match": "Kata sandi tidak cocok",
"Account_already_axist": "Akun telah ada",
"Manage": "Mengelola",
"Manage": "Kelola",
"Submit": "Kirim",
"Save_Changes": "Simpan Perubahan",
"Cancel": "Batal",
"Edit": "Sunting",
"Order": "Urutan",
"Delete": "Hapus",
"Welcome": "Selamat Datang",
"Data_Created_Successfully": "Data Berhasil Dibuat",
@ -123,7 +124,7 @@
"Period_Reports": "Laporan Periode",
"All_Transactions": "Semua Transaksi",
"Total_Income": "Jumlah Pemasukan",
"All_Transactions_at_Date": "Semua transaksi pada ganggal",
"All_Transactions_at_Date": "Semua transaksi pada tanggal",
"Export_for_Print": "Ekspor untuk cetak",
"Print": "Cetak",
"Export_to_PDF": "Ekspor ke PDF",
@ -199,7 +200,7 @@
"Search_by_Username": "Cari berdasarkan nama pengguna",
"Search_by_Name": "Cari berdasarkan nama",
"Search_by_Code_Voucher": "Cari berdasarkan kode voucher",
"Search": "Mencari",
"Search": "Cari",
"Select_a_customer": "Pilih pelanggan",
"Select_Routers": "Pilih Router",
"Select_Plans": "Pilih Paket",
@ -331,7 +332,7 @@
"Pay_this_with_Balance__your_active_package_will_be_overwrite": "Bayar ini dengan Saldo? Paket aktif Anda akan ditimpa",
"Success_to_buy_package": "Berhasil membeli paket",
"Auto_Renewal": "Perpanjangan otomatis",
"View": "Melihat",
"View": "Lihat",
"Back": "Kembali",
"Active": "Aktif",
"Transfer_Balance": "Kirim saldo",
@ -383,6 +384,7 @@
"Vouchers": "Voucher",
"Refill_Customer": "Isi Ulang Voucher",
"Recharge_Customer": "Isi Ulang Paket",
"Plan": "Paket",
"Plans": "Paket",
"PPPOE": "PPPOE",
"Bandwidth": "Bandwidth",
@ -411,7 +413,7 @@
"Paid": "Dibayar",
"Personal": "Pribadi",
"Coordinates": "Koordinat",
"Confirm": "Mengonfirmasi",
"Confirm": "Konfirmasi",
"Name": "Nama",
"Plan": "Paket",
"Using": "Menggunakan",
@ -419,7 +421,7 @@
"Additional_Cost": "Biaya tambahan",
"Resend": "Kirim ulang",
"Login": "Masuk",
"success": "Sukses",
"success": "Berhasil",
"Click_Here": "Klik disini",
"Your_friend_do_not_have_active_package": "Teman Anda tidak memiliki paket aktif",
"If_your_friend_have_Additional_Cost__you_will_pay_for_that_too": "Jika teman Anda memiliki biaya tambahan, Anda juga akan membayarnya",
@ -503,11 +505,11 @@
"Time": "Waktu",
"Data": "Data",
"1_Period___1_Month__Expires_the_20th_of_each_month": "1 Periode = 1 Bulan, Berakhir pada tanggal 20 setiap bulannya",
"Expired_Date": "Tanggal kadaluarsa",
"Expired_Date": "Tanggal kadaluwarsa",
"Expired_Action": "Tindakan Kedaluwarsa",
"Optional": "Opsional",
"Expired_Internet_Plan": "Paket Internet Kedaluwarsa",
"When_Expired__customer_will_be_move_to_selected_internet_plan": "Ketika Expired, pelanggan akan dipindahkan ke paket internet yang dipilih",
"When_Expired__customer_will_be_move_to_selected_internet_plan": "Ketika kedaluwarsa, pelanggan akan dipindahkan ke paket internet yang dipilih",
"Period": "Periode",
"Rate": "Kecepatan",
"Burst": "Burst",
@ -522,7 +524,8 @@
"Ascending": "Naik",
"Descending": "Menurun",
"Query": "Query",
"Add": "Menambahkan",
"Add": "Tambah",
"Search": "Cari",
"Logout_Successful": "Berhasil Keluar",
"warning": "peringatan",
"Created___Expired": "Dibuat \/ Kedaluwarsa",
@ -532,8 +535,8 @@
"Customer_can_login_but_cannot_buy_internet_plan__Admin_cannot_recharge_customer": "Pelanggan dapat login tetapi tidak dapat membeli paket internet, Admin tidak dapat mengisi ulang pelanggan",
"Don_t_forget_to_deactivate_all_active_plan_too": "Jangan lupa untuk menonaktifkan semua paket aktif juga",
"Attributes": "Atribut",
"Additional_Information": "informasi tambahan",
"City_of_Resident": "Kota Residen",
"Additional_Information": "Informasi tambahan",
"City_of_Resident": "Kota Tempat Tinggal",
"State_of_Resident": "Negara Bagian Tempat Tinggal",
"Zip_Code": "Kode Pos",
"Phone": "Telepon",
@ -554,7 +557,7 @@
"Input_your_phone_number": "Masukkan nomor telepon Anda",
"OTP": "OTP",
"Enter_OTP_that_was_sent_to_your_phone": "Masukkan OTP yang dikirimkan ke ponsel Anda",
"Update": "Memperbarui",
"Update": "Perbarui",
"Verification_code_has_been_sent_to_your_phone": "Kode verifikasi telah dikirimkan ke ponsel Anda",
"Please_wait_1039_seconds_before_sending_another_SMS": "Harap tunggu 1039 detik sebelum mengirim SMS lainnya",
"Please_wait_1015_seconds_before_sending_another_SMS": "Harap tunggu 1015 detik sebelum mengirim SMS lainnya",
@ -567,7 +570,7 @@
"Api": "Api",
"Http_Chap": "Http-Chap",
"Hotspot_Authentication_Method__Make_sure_you_have_changed_your_hotspot_login_page_": "Metode Otentikasi Hotspot. Pastikan Anda telah mengubah halaman login hotspot Anda.",
"Languge_set_to_indonesia": "Language set to indonesia",
"Languge_set_to_indonesia": "Bahasa diatur ke Indonesia",
"Enable": "Aktifkan",
"Diable": "Nonaktifkan",
"Verification_code": "Kod3 V3r1fik@s1",
@ -583,37 +586,37 @@
"_": "-",
"Search_Users": "Pencarian Pengguna",
"Routers_Maps": "Peta Router",
"Theme_Voucher": "Voucher Tema",
"Theme_Voucher": "Tema Voucher",
"Payment_Info": "Info Pembayaran",
"Documentation": "Dokumentasi",
"Customers": "Pelanggan",
"Package_Name": "Nama Paket",
"Routers_Offline": "Router Offline",
"Routers_Offline": "Router Off",
"Cron_appear_not_been_setup__please_check_your_cron_setup_": "Cron tampaknya belum disiapkan, silakan periksa pengaturan cron Anda.",
"Buy": "Membeli",
"Buy": "Beli",
"You_are_already_logged_in": "Anda sudah masuk",
"PPPOE_Package": "Paket PPPoE",
"Prepaid": "Prabayar",
"Postpaid": "Pascabayar",
"Enabled": "Diaktifkan",
"Enabled": "Aktifkan",
"Disable": "Nonaktifkan",
"Create_expired_Internet_Plan": "Buat Paket Internet yang Kedaluwarsa",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Plan": "Ketika pelanggan kedaluwarsa, Anda dapat memindahkannya ke Paket Internet Kedaluwarsa",
"Price_Before_Discount": "Harga Sebelum Diskon",
"For_Discount_Rate__this_is_price_before_get_discount__must_be_more_expensive_with_real_price": "Untuk Discount Rate, ini adalah harga sebelum mendapat diskon, pasti lebih mahal dari harga sebenarnya",
"on_login___on_up": "saat masuk \/ saat mendaftar",
"For_Discount_Rate__this_is_price_before_get_discount__must_be_more_expensive_with_real_price": "Untuk tarif diskon. Ini adalah harga sebelum mendapat diskon, pasti lebih mahal dari harga sebenarnya",
"on_login___on_up": "saat masuk \/ saat naik",
"on_logout___on_down": "saat keluar \/ saat turun",
"Get_Directions": "Dapatkan Petunjuk Arah",
"Not_Working_with_Freeradius_Mysql": "Tidak Bekerja dengan Freeradius Mysql",
"User_Cannot_change_this__only_admin__if_it_Empty_it_will_use_Customer_Credentials": "Pengguna tidak dapat mengubah ini, hanya admin. Jika Kosong, maka akan menggunakan Kredensial Pelanggan",
"Buy_this__your_active_package_will_be_overwritten": "Beli ini? Paket aktif Anda akan ditimpa",
"Buy_this__your_active_package_will_be_overwritten": "Beli paket ini? Paket aktif Anda akan ditimpa",
"Pay_this_with_Balance__your_active_package_will_be_overwritten": "Bayar ini dengan Saldo? Paket aktif Anda akan ditimpa",
"Error": "Kesalahan",
"Internal_Error": "Kesalahan Internal",
"Sorry__the_software_failed_to_process_the_request__if_it_still_happening__please_tell": "Maaf, perangkat lunak gagal memproses permintaan, jika masih terjadi, mohon beri tahu",
"Sorry__the_software_failed_to_process_the_request__if_it_still_happening__please_tell": "Maaf, perangkat lunak gagal memproses permintaan. Jika masih terjadi, mohon beri tahu",
"Try_Again": "Coba Lagi",
"Make_sure_you_use_API_Port__Default_8728": "Pastikan Anda menggunakan Port API, Default 8728",
"Make_sure_Username_and_Password_are_correct": "Pastikan Username dan Password sudah benar",
"Make_sure_Username_and_Password_are_correct": "Pastikan nama pengguna dan kata sandi sudah benar",
"Make_sure_your_hosting_not_blocking_port_to_external": "Pastikan hosting Anda tidak memblokir port ke eksternal",
"Make_sure_your_Mikrotik_accessible_from_PHPNuxBill": "Pastikan Mikrotik Anda dapat diakses dari PHPNuxBill",
"If_you_just_update_PHPNuxBill_from_upload_files__try_click_Update": "Jika Anda baru saja memperbarui PHPNuxBill dari mengunggah file, coba klik Perbarui",
@ -669,11 +672,11 @@
"Logout_Admin_if_not_Available_Online_a_period_of_time": "Logout Admin jika tidak tersedia\/Online dalam jangka waktu tertentu",
"Timeout_Duration": "Durasi Waktu Habis",
"Enter_the_session_timeout_duration__minutes_": "Masukkan durasi batas waktu sesi (menit)",
"Idle_Timeout__Logout_Admin_if_Idle_for_xx_minutes": "Waktu Habis Idle, Keluar dari Admin jika Idle selama xx menit",
"Idle_Timeout__Logout_Admin_if_Idle_for_xx_minutes": "Batas waktu idle, Keluar dari Admin jika Idle selama xx menit",
"New_Version_Notification": "Pemberitahuan Versi Baru",
"This_is_to_notify_you_when_new_updates_is_available": "Ini untuk memberi tahu Anda ketika pembaruan baru tersedia",
"Router_Check": "Pemeriksaan Router",
"If_enabled__the_system_will_notify_Admin_when_router_goes_Offline__If_admin_have_10_or_more_router_and_many_customers__it_will_get_overlapping__you_can_disabled": "Jika diaktifkan, sistem akan memberitahu Admin ketika router Offline, Jika admin memiliki 10 atau lebih router dan banyak pelanggan, maka akan terjadi tumpang tindih, Anda dapat menonaktifkannya",
"If_enabled__the_system_will_notify_Admin_when_router_goes_Offline__If_admin_have_10_or_more_router_and_many_customers__it_will_get_overlapping__you_can_disabled": "Jika diaktifkan, sistem akan memberitahu Admin ketika router Off, Jika Admin memiliki 10 atau lebih router dan banyak pelanggan, maka akan terjadi tumpang tindih, Anda dapat menonaktifkannya",
"Phone_OTP_Required": "Diperlukan OTP Telepon",
"OTP_is_required_when_user_want_to_change_phone_number_and_registration": "OTP diperlukan ketika pengguna ingin mengubah nomor telepon dan registrasi",
"by_WhatsApp": "melalui WhatsApp",
@ -701,7 +704,7 @@
"Used_Date": "Tanggal Penggunaan",
"Plugin_Installer": "Pemasang Plugin",
"Upload_Zip_Plugin_Theme_Device": "Unggah Plugin\/Tema\/Perangkat Zip",
"Install": "Memasang",
"Install": "Pasang",
"via_SMS": "melalui SMS",
"Via_WhatsApp": "Melalui WhatsApp",
"Via_WhatsApp_and_SMS": "Melalui WhatsApp dan SMS",
@ -740,9 +743,9 @@
"Status_": "Status:",
"Force_Logout_": "Paksa Keluar:",
"End_Date_": "Tanggal Berakhir:",
"Save": "Menyimpan",
"Save": "Simpan",
"Not_Active": "Tidak Aktif",
"Limit": "Membatasi",
"Limit": "Batasi",
"Create_expired_Internet_Package": "Buat Paket Internet yang Kedaluwarsa",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Package": "Ketika pelanggan telah kedaluwarsa, Anda dapat memindahkannya ke Paket Internet Kedaluwarsa",
"Miscellaneous_Settings": "Pengaturan Lain-Lain",
@ -751,10 +754,10 @@
"Buy_Balance_Plans": "Beli Paket Saldo",
"New_Voucher_for_10mbps_Created": "Voucher Baru untuk 10mbps Dibuat",
"Previous": "Sebelumnya",
"Share": "Membagikan",
"Share": "Bagikan",
"Agent": "Agen",
"Sub_District": "Kecamatan",
"Ward": "Bangsal",
"Ward": "Kelurahan",
"Profile": "Profil",
"Credentials": "Kredensial",
"Cron_has_not_run_for_over_1_hour__Please_check_your_setup_": "Cron tidak berjalan selama lebih dari 1 jam. Harap periksa pengaturan Anda.",
@ -765,7 +768,7 @@
"Username_should_be_between_3_to_45_characters": "Nama pengguna harus terdiri dari 3 hingga 45 karakter",
"Single_session_Admin": "Sesi Tunggal Admin",
"Admin_can_only_have_single_session_login__it_will_logout_another_session": "Admin hanya dapat memiliki login satu sesi, maka akan keluar dari sesi berikutnya",
"For_Registration_and_Update_Phone_Number": "Untuk Registrasi dan Update Nomor Telepon",
"For_Registration_and_Update_Phone_Number": "Untuk Registrasi dan Perbarui Nomor Telepon",
"Login_as_Customer": "Masuk sebagai Pelanggan",
"Invalid_or_Expired_CSRF_Token": "Token CSRF Tidak Valid atau Kedaluwarsa",
"Edit_Service_Package": "Edit Paket Layanan",
@ -778,8 +781,8 @@
"Home_Address": "Alamat Rumah",
"Email_Address": "Alamat Email",
"Custom_Balance": "Saldo Kustom",
"Input_Desired_Amount": "Masukkan Jumlah yang Diinginkan",
"Advanced_Hotspot_System": "Sistem Hotspot Canggih",
"Input_Desired_Amount": "Masukkan jumlah yang diinginkan",
"Advanced_Hotspot_System": "Sistem Hotspot Lanjutan",
"Successful_Payments": "Pembayaran Berhasil",
"More_Info": "Info lebih lanjut",
"Failed_Payments": "Pembayaran Gagal",
@ -792,7 +795,7 @@
"_Click_this": "Klik ini",
"_to_visit_the_hotspot_login_page": "untuk mengunjungi halaman login hotspot",
"_Choose_your_desired_plan__enter_your_phone_number_and_click_Pay_Now__you_will_be_redirected_to_payment_portal_": "Pilih paket yang Anda inginkan, masukkan nomor telepon Anda dan klik Bayar Sekarang, Anda akan diarahkan ke portal pembayaran.",
"_Pay_with_Demo_Success_": "Bayar dengan Demo Sukses.",
"_Pay_with_Demo_Success_": "Bayar dengan Demo berhasil.",
"_After_Successful_Payment_you_will_be_awarded_the_package_and_you_will_received_your_Voucher_Code_for_login_": "Setelah Pembayaran Berhasil, Anda akan diberikan paket dan Anda akan menerima Kode Voucher untuk login.",
"_Come_back_here_to_see_your_hotspot_performance_at_a_glance_": "Kembali ke sini untuk melihat sekilas kinerja hotspot Anda.",
"Hotspot_Payment_History": "Riwayat Pembayaran Hotspot",
@ -825,7 +828,7 @@
"radius": "radius",
"Max_30_days": "Maksimal 30 hari",
"Information": "Informasi",
"Export_and_Print_will_show_all_data_without_pagination": "Ekspor dan Cetak akan menampilkan semua data tanpa pagination",
"Export_and_Print_will_show_all_data_without_pagination": "Ekspor dan Cetak akan menampilkan semua data tanpa paginasi",
"First_Name": "Nama Depan",
"Last_Name": "Nama Belakang",
"General": "Umum",
@ -885,7 +888,7 @@
"Select_Balance_Package_or_Custom_Amount": "Pilih Paket Saldo atau Jumlah Kustom",
"Or_custom_balance_amount_below": "Atau jumlah saldo khusus di bawah ini",
"Balance_Amount": "Jumlah Saldo",
"Input_custom_balance__will_ignore_plan_above": "Masukkan saldo khusus, akan mengabaikan rencana di atas",
"Input_custom_balance__will_ignore_plan_above": "Masukkan saldo khusus, akan mengabaikan paket di atas",
"Note": "Catatan",
"Customer_Login_Page_Settings": "Pengaturan Halaman Login Pelanggan",
"Choose_Template": "Pilih Template",
@ -909,5 +912,7 @@
"Mandatory_Fields": "Bidang yang wajib diisi",
"Single_Admin_Session": "Sesi Admin Tunggal",
"Mikrotik_SMS_Command": "Perintah SMS Mikrotik",
"Expired_Cronjob_Every_5_Minutes__Recommended_": "Cronjob Kedaluwarsa Setiap 5 Menit [Direkomendasikan]"
}
"Expired_Cronjob_Every_5_Minutes__Recommended_": "Cronjob Kedaluwarsa Setiap 5 Menit [Direkomendasikan]",
"Visit": "Kunjungi",
"sync": "Sinkron"
}

View File

@ -8,7 +8,7 @@
"Log_in_to_Member_Panel": "Iniciar sesi\u00f3n en el panel de miembros",
"Register_as_Member": "Reg\u00edstrese como miembro",
"Enter_Admin_Area": "Panel de administraci\u00f3n",
"PHPNuxBill": "WENJEI",
"PHPNuxBill": "Compañia",
"Username": "Usuario",
"Password": "Contrase\u00f1a",
"Passwords_does_not_match": "Las contrase\u00f1as no coinciden",
@ -541,7 +541,7 @@
"Agent": "Agente",
"Session_has_expired__Please_log_in_again_": "La sesi\u00f3n ha expirado. Por favor, inicie sesi\u00f3n nuevamente.",
"danger": "Peligro",
"City": "Ciudad",
"City": "Pais",
"District": "Distrito",
"State": "Estado",
"Zip": "C\u00f3digo Postal",
@ -565,5 +565,278 @@
"Max_30_days": "M\u00e1ximo 30 d\u00edas",
"Total": "Total",
"Information": "Informaci\u00f3n",
"Export_and_Print_will_show_all_data_without_pagination": "Exportar e imprimir mostrar\u00e1 todos los datos sin paginaci\u00f3n"
"Export_and_Print_will_show_all_data_without_pagination": "Exportar e imprimir mostrar\u00e1 todos los datos sin paginaci\u00f3n",
"Maps": "Mapas",
"Custom_Fields": "Campos personalizados",
"Cron_appear_not_been_setup__please_check_your_cron_setup_": "Aparece que no se ha configurado Cron, consulte su configuraci\u00f3n de Cron.",
"Check_if_Router_Online_": "\u00bfVerifique si Router Online?",
"To_check_whether_the_Router_is_online_or_not__please_visit_the_following_page": "Para verificar si el enrutador est\u00e1 en l\u00ednea o no, visite la p\u00e1gina siguiente",
"Cek_Now": "Cek ahora",
"Enable": "Permitir",
"Disable": "Desactivar",
"Router_Name___Location": "Nombre \/ ubicaci\u00f3n del enrutador",
"Coordinates": "Coordenadas",
"Coverage": "Cobertura",
"Continue_the_process_of_changing_Routers_": "\u00bfContinuar el proceso de cambiar los enrutadores?",
"First_Name": "Nombre",
"Last_Name": "Apellido",
"Plugin_Installer": "Instalador de plugin",
"Upload_Zip_Plugin_Theme_Device": "Subir el complemento\/tema\/dispositivo con zip",
"Install": "Instalar",
"CPU_Load": "Carga de la CPU",
"Temperature": "Temperatura",
"Voltage": "Voltaje",
"Wireless_Status": "Estado inal\u00e1mbrico",
"Interface_Status": "Estado de la interfaz",
"Hotspot_Online_Users": "Usuarios en l\u00ednea de hotspot",
"PPPoE_Online_Users": "Usuarios en l\u00ednea de PPPOE",
"Traffic_Monitor": "Monitor de tr\u00e1fico",
"Interface_Name": "Nombre de la interfaz",
"Tx__bytes_Out_": "Tx (bytes fuera)",
"Rx__bytes_In_": "Rx (bytes en)",
"Total_Usage": "Uso total",
"Uptime": "Tiempo de actividad",
"Server": "Servidor",
"Mac_Address": "Direcci\u00f3n MAC",
"Session_Time_Left": "Tiempo de sesi\u00f3n restante",
"Upload__RX_": "Cargue (RX)",
"Download__TX_": "Descargar (TX)",
"Service": "Servicio",
"Caller_ID": "Identificador de llamadas",
"Download": "Descargar",
"Upload": "Subir",
"Interface": "Interfaz",
"Last_Ip": "\u00daltima IP",
"Last_Activity": "\u00daltima actividad",
"Signal_Strength": "Resistencia a la se\u00f1al",
"Tx___Rx_CCQ": "TX \/ RX CCQ",
"Rx_Rate": "Tasa de rx",
"Tx_Rate": "Tasa de tx",
"Interace": "Interacci\u00f3n",
"TX": "Tx",
"RX": "Rx",
"Date_Time": "Fecha\/hora",
"Topic": "Tema",
"Send_Personal_Message": "Enviar mensaje personal",
"Send_Via": "Enviar",
"via_SMS": "a trav\u00e9s de SMS",
"Via_WhatsApp": "A trav\u00e9s de whatsapp",
"Via_WhatsApp_and_SMS": "A trav\u00e9s de whatsapp y sms",
"Compose_your_message___": "Componga tu mensaje ...",
"Use_placeholders_": "Use marcadores de posici\u00f3n:",
"Customer_Name": "Nombre del cliente",
"Customer_Username": "Nombre de usuario del cliente",
"Customer_Phone": "Tel\u00e9fono del cliente",
"Your_Company_Name": "Nombre de su empresa",
"Sign_in_into_your_account": "Inicie sesi\u00f3n en su cuenta",
"Usernames": "Nombre de usuario",
"Don_t_have_an_account_": "\u00bfNo tienes una cuenta?",
"General": "General",
"Pretty_URL": "Bonita url",
"rename__htaccess_firewall_to__htaccess": "renombrar .htaccess_firewall a .htaccess",
"Customer_Login_Page_Settings": "Configuraci\u00f3n de la p\u00e1gina de inicio de sesi\u00f3n del cliente",
"Choose_Template": "Elija plantilla",
"Custom": "Costumbre",
"Select_your_login_template_type": "Seleccione su tipo de plantilla de inicio de sesi\u00f3n",
"Select_Login_Page": "Seleccione la p\u00e1gina de inicio de sesi\u00f3n",
"Select_your_preferred_login_template": "Seleccione su plantilla de inicio de sesi\u00f3n preferida",
"Page_Heading___Company_Name": "Encabezado de p\u00e1gina \/ nombre de la empresa",
"This_Name_will_be_shown_on_the_login_wallpaper": "Este nombre se mostrar\u00e1 en el fondo de pantalla de inicio de sesi\u00f3n.",
"Page_Description": "Descripci\u00f3n de la p\u00e1gina",
"This_will_also_display_on_wallpaper__You_can_use_html_tag": "Esto tambi\u00e9n se mostrar\u00e1 en papel tapiz, puede usar la etiqueta HTML",
"Favicon": "Favic\u00f3n",
"Best_size_30_x_30___uploaded_image_will_be_autosize": "El mejor tama\u00f1o 30 x 30 | La imagen cargada se automatizar\u00e1",
"Login_Page_Logo": "Logotipo de la p\u00e1gina de inicio de sesi\u00f3n",
"Best_size_300_x_60___uploaded_image_will_be_autosize": "El mejor tama\u00f1o 300 x 60 | La imagen cargada se automatizar\u00e1",
"Login_Page_Wallpaper": "Fondo de pantalla de la p\u00e1gina de inicio de sesi\u00f3n",
"Best_size_1920_x_1080___uploaded_image_will_be_autosize": "El mejor tama\u00f1o 1920 x 1080 | La imagen cargada se automatizar\u00e1",
"Registration": "Registro",
"Allow_Registration": "Permitir el registro",
"No_Registration": "Sin registro",
"Registration_Username": "Nombre de usuario de registro",
"Photo_Required": "Foto requerida",
"Customer_Registration_need_to_upload_their_photo": "El registro del cliente necesita subir su foto",
"Customer_Registration_need_to_validate_using_OTP": "El registro del cliente debe validar con OTP",
"For_Registration_and_Update_Phone_Number": "Para el n\u00famero de tel\u00e9fono de registro y actualizaci\u00f3n",
"Notify_Admin": "Notificar a administrador",
"Notify_Admin_upon_self_registration": "Notificar al administrador sobre el registro autom\u00e1tico",
"Mandatory_Fields": "Campos obligatorios",
"Security": "Seguridad",
"Single_Admin_Session": "Sesi\u00f3n de administrador \u00fanica",
"Admin_can_only_have_single_session_login__it_will_logout_another_session": "El administrador solo puede tener inicio de sesi\u00f3n de una sola sesi\u00f3n, iniciar\u00e1 sesi\u00f3n en otra sesi\u00f3n",
"Enable_CSRF_Validation": "Habilitar la validaci\u00f3n de CSRF",
"Cross_site_request_forgery": "Falsificaci\u00f3n de solicitud de sitio cruzado",
"SMS_Notification": "Notificaci\u00f3n de SMS",
"Mikrotik_SMS_Command": "Comando de mikrotik sms",
"Tax_Rates_by_percentage": "Tasas impositivas por porcentaje",
"Settings_For_Mikrotik": "Configuraci\u00f3n para Mikrotik",
"Settings_For_Cron_Expired": "Configuraci\u00f3n para Cron expirado",
"Expired_Cronjob_Every_5_Minutes__Recommended_": "Cronjob vencido cada 5 minutos [recomendado]",
"Settings_For_Cron_Reminder": "Configuraci\u00f3n para el recordatorio de Cron",
"Login_Page_Settings_Saved_Successfully": "Configuraci\u00f3n de la p\u00e1gina de inicio de sesi\u00f3n guardada correctamente",
"Logout_Successful": "INCOMPTIR SEXITO",
"warning": "Advertencia",
"Go_Back": "Volver",
"Validate": "Validar",
"Forgot_Usernames": "Olvid\u00e9 nombres de usuario",
"You_are_already_logged_in": "Ya est\u00e1s iniciado",
"Hello": "Hola",
"your_internet_package": "Tu paquete de Internet",
"has_been_expired": "ha sido expirado",
"will_be_replaced_with_Customer_Name": "ser\u00e1 reemplazado por el nombre del cliente",
"will_be_replaced_with_Customer_username": "ser\u00e1 reemplazado por el nombre de usuario del cliente",
"will_be_replaced_with_Package_name": "ser\u00e1 reemplazado por el nombre del paquete",
"will_be_replaced_with_Package_price": "ser\u00e1 reemplazado por el precio del paquete",
"additional_bills_for_customers": "facturas adicionales para los clientes",
"will_be_replaced_with_Expiration_date": "ser\u00e1 reemplazado por la fecha de vencimiento",
"Your_Company_Name_at_Settings": "Nombre de su empresa en la configuraci\u00f3n",
"Your_Company_Address_at_Settings": "Direcci\u00f3n de su empresa en la configuraci\u00f3n",
"Your_Company_Phone_at_Settings": "Tel\u00e9fono de su empresa en la configuraci\u00f3n",
"Invoice_number": "N\u00famero de factura",
"Date_invoice_created": "Factura de fecha creada",
"Payment_gateway_user_paid_from": "El usuario de la pasarela de pago pagado por",
"Payment_channel_user_paid_from": "Usuario de canal de pago pagado desde",
"is_Hotspot_or_PPPOE": "es hotspot o pppoe",
"Internet_Package_Prices": "Precios de paquetes de Internet",
"Receiver_name": "Nombre del receptor",
"Username_internet": "Nombre de usuario Internet",
"User_password": "Contrase\u00f1a de usuario",
"Expired_datetime": "Expirado de fecha y hora",
"For_Notes_by_admin": "Para notas de admin",
"Transaction_datetime": "Transacci\u00f3n data de data",
"Balance_Before": "Equilibrar antes",
"Balance_After": "Equilibrar",
"Welcome_Message": "Mensaje de bienvenida",
"will_be_replaced_with_Customer_password": "ser\u00e1 reemplazado por la contrase\u00f1a del cliente",
"will_be_replaced_with_Customer_Portal_URL": "ser\u00e1 reemplazado por URL del portal del cliente",
"will_be_replaced_with_Company_Name": "ser\u00e1 reemplazado por el nombre de la empresa",
"Save": "Guardar",
"Save_as_template": "Guardar como plantilla",
"Template_Name": "Nombre de plantilla",
"Package_Price": "Precio del paquete",
"Voucher_Code": "C\u00f3digo de cup\u00f3n",
"Voucher_Package": "Paquete de cupones",
"Counter": "Contador",
"Sub_District": "Distrito",
"Ward": "Código Postal",
"Profile": "Perfil",
"Photo": "Foto",
"Phone": "Tel\u00e9fono",
"Credentials": "Cartas credenciales",
"RB5009_39": "RB5009-39",
"Admin": "Administraci\u00f3n",
"VPN_Plans": "Planes VPN",
"Using": "Usando",
"Postpaid_Recharge_for_the_first_time_use": "Recarga pospago por primera vez uso",
"Or": "O",
"Confirm": "Confirmar",
"Name": "Nombre",
"Plan": "Plan",
"Resend": "Revender",
"Home_Address": "Direcci\u00f3n de la casa",
"Other": "Otro",
"Business": "Negocio",
"Not_Working_for_freeradius": "No funciona para freeradius",
"Also_Working_for_freeradius": "Tambi\u00e9n trabajando para freeradius",
"User_Cannot_change_this__only_admin__if_it_Empty_it_will_use_Customer_Credentials": "El usuario no puede cambiar esto, solo administrador. Si se vac\u00eda, usar\u00e1 las credenciales del cliente",
"Send_welcome_message": "Enviar mensaje de bienvenida",
"Notification_via": "Notificaci\u00f3n a trav\u00e9s de",
"SMS": "SMS",
"WA": "Whatsapp",
"Attributes": "Atributos",
"Additional_Information": "informaci\u00f3n adicional",
"City_of_Resident": "Ciudad del residente",
"State_of_Resident": "Estado de residente",
"Zip_Code": "C\u00f3digo postal",
"Continue_the_process_of_adding_Customer_Data_": "\u00bfContinuar el proceso de agregar datos de clientes?",
"Customer_cannot_buy_disabled_Package__but_admin_can_recharge_it__use_it_if_you_want_only_admin_recharge_it": "El cliente no puede comprar el paquete discapacitado, pero el administrador puede recargarlo, usarlo si solo desea recargarlo de administrador",
"Postpaid_will_have_fix_expired_date": "Postpaid tendr\u00e1 una fecha vencida",
"Prepaid": "Pagado",
"Postpaid": "Pospago",
"Personal_Package_will_only_show_to_personal_Customer__Business_Package_will_only_show_to_Business_Customer": "El paquete personal solo se mostrar\u00e1 al cliente personal, el paquete comercial solo se mostrar\u00e1 al cliente empresarial",
"Device": "Dispositivo",
"This_Device_are_the_logic_how_PHPNuxBill_Communicate_with_Mikrotik_or_other_Devices": "Este dispositivo es la l\u00f3gica de c\u00f3mo PhpNuxbill se comunica con Mikrotik u otros dispositivos",
"Price_Before_Discount": "Precio antes de descuento",
"For_Discount_Rate__this_is_price_before_get_discount__must_be_more_expensive_with_real_price": "Para una tasa de descuento, este es el precio antes de obtener descuento, debe ser m\u00e1s costoso con un precio real",
"1_Period___1_Month__Expires_the_20th_of_each_month": "1 per\u00edodo = 1 mes, expira el 20 de cada mes",
"Expired_Date": "Fecha vencida",
"Expired_will_be_this_date_every_month": "Caducado ser\u00e1 esta fecha cada mes",
"Expired_Action": "Acci\u00f3n vencida",
"Optional": "Opcional",
"Expired_Internet_Plan": "Plan de Internet vencido",
"When_Expired__customer_will_be_move_to_selected_internet_plan": "Cuando expire, el cliente se trasladar\u00e1 al plan de Internet seleccionado",
"on_login___on_up": "en el login \/ encendido",
"on_logout___on_down": "On-Logout \/ On-Down",
"Continue_the_PPPoE_Package_change_process_": "\u00bfContinuar el proceso de cambio de paquete PPPOE?",
"Period": "Per\u00edodo",
"Login_as_Customer": "Iniciar sesi\u00f3n como cliente",
"Rate": "Tasa",
"Create_Bandwidth_Package_for_expired_Internet_Package": "Crear paquete de ancho de banda para paquete de Internet vencido",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Package": "Cuando el cliente caduque, podr\u00e1 moverlo a Paquete de Internet Caducado",
"Category": "Categor\u00eda",
"Create_expired_Internet_Plan": "Crear plan de Internet vencido",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Plan": "Cuando el cliente caduque, podr\u00e1 moverlo a Plan de Internet Caducado",
"Personal_Package_will_only_show_to_personal_Customer__Business_package_will_only_show_to_Business_Customer": "El paquete personal solo se mostrar\u00e1 a los clientes personales, el paquete comercial solo se mostrar\u00e1 a los clientes comerciales",
"Continue_the_process_of_adding_the_PPPoE_Package_": "\u00bfContinuar con el proceso de agregar el paquete PPPoE?",
"Face_Detection": "Detecci\u00f3n de rostros",
"Customer_cannot_login_again": "El cliente no puede iniciar sesi\u00f3n nuevamente",
"Customer_can_login_but_cannot_buy_internet_package__Admin_cannot_recharge_customer": "El cliente puede iniciar sesi\u00f3n pero no puede comprar un paquete de Internet. El administrador no puede recargar al cliente.",
"Don_t_forget_to_deactivate_all_active_package_too": "No olvides desactivar tambi\u00e9n todos los paquetes activos.",
"Not_Working_with_Freeradius_Mysql": "No funciona con Freeradius Mysql",
"Continue_the_Customer_Data_change_process_": "\u00bfContinuar con el proceso de cambio de datos del cliente?",
"Port_Pool": "Grupo de puerto",
"New_port": "Nuevo puerto",
"Port_Name": "Nombre del puerto",
"Public_IP": "IP p\u00fablica",
"Range_Port": "Rango de Puertos",
"Add_Port_Pool": "Agregar grupo de puertos",
"Continue_the_process_of_adding_Ports_": "\u00bfContinuar con el proceso de agregar puertos?",
"Continue_the_VPN_creation_process_": "\u00bfContinuar con el proceso de creaci\u00f3n de VPN?",
"": "",
"Local_IP": "IP local",
"Test_Connection": "Conexi\u00f3n de prueba",
"Continue_the_process_of_adding_Routers_": "\u00bfContinuar con el proceso de agregar enrutadores?",
"Continue_the_Pool_addition_process_": "\u00bfContinuar con el proceso de adici\u00f3n de la piscina?",
"Private_IP": "IP privada",
"Sync_account_if_you_failed_login_to_internet": "Sincronizar cuenta si no se pudo iniciar sesi\u00f3n en Internet",
"Data_Change": "Cambio de datos",
"Face_Detect": "Detecci\u00f3n de rostro",
"Email_Address": "Direcci\u00f3n de correo electr\u00f3nico",
"Transaction_History_List": "Lista de historial de transacciones",
"Contabo": "Contabo",
"Not_Active": "No activo",
"New_Service_Package": "Nuevo paquete de servicios",
"Limit": "L\u00edmite",
"Time": "Tiempo",
"Data": "Datos",
"ID": "IDENTIFICACI\u00d3N",
"Create_expired_Internet_Package": "Crear paquete de Internet vencido",
"Oops__The_page_you_are_looking_for_was_not_found": "\u00a1Ups! La p\u00e1gina que est\u00e1 buscando no fue encontrada",
"Back_to_Dashboard": "Volver al panel de control",
"Add_User": "Agregar usuario",
"Report_Viewer": "Visor de informes",
"Super_Administrator": "Super administrador",
"Send_Notification": "Enviar notificaci\u00f3n",
"Don_t_Send": "No env\u00ede",
"Registration_successful": "Registro exitoso",
"New_User_Registration": "Registro de nuevo usuario",
"New_Field": "Nuevo campo",
"Installed_Devices": "Dispositivos instalados",
"Miscellaneous_Settings": "Configuraciones varias",
"Display_bandwidth_plan_for_customer": "Mostrar plan de ancho de banda para el cliente",
"Radius_Rest_Interim_Update": "Actualizaci\u00f3n provisional de Radius Rest",
"in_minutes__leave_0_to_disable_this_feature_": "en minutos, deje 0 para deshabilitar esta funci\u00f3n.",
"Check_if_Customer_Online": "Verificar si el cliente est\u00e1 en l\u00ednea",
"This_will_show_is_Customer_currently_is_online_or_not": "Esto mostrar\u00e1 si el cliente est\u00e1 actualmente en l\u00ednea o no.",
"Allow_Balance_Custom_Amount": "Permitir saldo Importe personalizado",
"Allow_Customer_buy_balance_with_any_amount": "Permitir al Cliente comprar saldo con cualquier monto",
"Make_sure_you_use_API_Port__Default_8728": "Aseg\u00farese de utilizar el puerto API, predeterminado 8728",
"Make_sure_Username_and_Password_are_correct": "Aseg\u00farese de que el nombre de usuario y la contrase\u00f1a sean correctos",
"Make_sure_your_hosting_not_blocking_port_to_external": "Aseg\u00farese de que su hosting no bloquee el puerto al externo",
"Make_sure_your_Mikrotik_accessible_from_PHPNuxBill": "Aseg\u00farese de que su Mikrotik sea accesible desde PHPNuxBill",
"If_you_just_update_PHPNuxBill_from_upload_files__try_click_Update": "Si simplemente actualiza PHPNuxBill desde la carga de archivos, intente hacer clic en Actualizar",
"Update": "Actualizar",
"Update_PHPNuxBill": "Actualizar PHPNuxBill",
"Ask_Github_Community": "Pregunta a la comunidad de Github",
"Ask_Telegram_Community": "Pregunta a la comunidad de Telegram"
}

View File

@ -1483,6 +1483,14 @@ class ORM implements ArrayAccess
return $this->_add_simple_where($column_name, 'LIKE', $value);
}
/**
* Add any WHERE ... LIKE clause to your query.
*/
public function where_likes($column = null, $values = null)
{
return $this->_addWhere('(' . implode(' LIKE ? OR ', $column) . ' LIKE ? )', $values);
}
/**
* Add where WHERE ... NOT LIKE clause to your query.
*/

View File

@ -1,191 +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`;"
]
"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);"
]
}

File diff suppressed because one or more lines are too long

BIN
system/uploads/paid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

View File

@ -0,0 +1,15 @@
<?php
class activity_log
{
public function getWidget()
{
global $config, $ui, $current_date, $start_date;
$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->findArray();
$ui->assign('dlog', $dlog);
// $log = ORM::for_table('tbl_logs')->count();
// $ui->assign('log', $log);
return $ui->fetch('widget/activity_log.tpl');
}
}

View File

@ -0,0 +1,17 @@
<?php
class cron_monitor
{
public function getWidget()
{
global $UPLOAD_PATH,$ui;
$timestampFile = "$UPLOAD_PATH/cron_last_run.txt";
if (file_exists($timestampFile)) {
$lastRunTime = file_get_contents($timestampFile);
$ui->assign('run_date', date('Y-m-d h:i:s A', $lastRunTime));
}
return $ui->fetch('widget/cron_monitor.tpl');
}
}

View File

@ -0,0 +1,14 @@
<?php
class account_info
{
public function getWidget()
{
global $ui;
$abills = User::getAttributes("Bill");
$ui->assign('abills', $abills);
return $ui->fetch('widget/customers/account_info.tpl');
}
}

View File

@ -0,0 +1,20 @@
<?php
class active_internet_plan
{
public function getWidget()
{
global $ui, $user;
$_bill = User::_billing();
$ui->assign('_bills', $_bill);
$tcf = ORM::for_table('tbl_customers_fields')
->where('customer_id', $user['id'])
->find_many();
$vpn = ORM::for_table('tbl_port_pool')
->find_one();
$ui->assign('cf', $tcf);
$ui->assign('vpn', $vpn);
return $ui->fetch('widget/customers/active_internet_plan.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class announcement
{
public function getWidget()
{
global $ui;
return $ui->fetch('widget/customers/announcement.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class balance_transfer
{
public function getWidget()
{
global $ui;
return $ui->fetch('widget/customers/balance_transfer.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class button_order_internet_plan
{
public function getWidget()
{
global $ui;
return $ui->fetch('widget/customers/button_order_internet_plan.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class html_only
{
public function getWidget($data = null)
{
global $ui;
return $data['content'];
}
}

View File

@ -0,0 +1,22 @@
<?php
class html_php
{
public function getWidget($data = null)
{
global $ui;
$ui->assign('card_header', $data['title']);
ob_start();
try{
eval('?>'. $data['content']);
}catch(Exception $e){
echo $e->getMessage();
echo "<br>";
echo $e->getTraceAsString();
}
$content = ob_get_clean();
$ui->assign('card_body', $content);
return $ui->fetch('widget/card_html.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class recharge_a_friend
{
public function getWidget()
{
global $ui;
return $ui->fetch('widget/customers/recharge_a_friend.tpl');
}
}

View File

@ -0,0 +1,39 @@
<?php
class unpaid_order
{
public function getWidget()
{
global $ui, $user;
$unpaid = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
// check expired payments
if ($unpaid) {
try {
if (strtotime($unpaid['expired_date']) < time()) {
$unpaid->status = 4;
$unpaid->save();
$unpaid = [];
}
} catch (Throwable $e) {
} catch (Exception $e) {
}
try {
if (strtotime($unpaid['created_date'], "+24 HOUR") < time()) {
$unpaid->status = 4;
$unpaid->save();
$unpaid = [];
}
} catch (Throwable $e) {
} catch (Exception $e) {
}
}
$ui->assign('unpaid', $unpaid);
return $ui->fetch('widget/customers/unpaid_order.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class voucher_activation
{
public function getWidget()
{
global $ui;
return $ui->fetch('widget/customers/voucher_activation.tpl');
}
}

View File

@ -0,0 +1,63 @@
<?php
class customer_expired
{
public function getWidget()
{
global $ui, $current_date, $config;
//user expire
$query = ORM::for_table('tbl_user_recharges')
->table_alias('tur')
->selects([
'c.id',
'tur.username',
'c.fullname',
'c.phonenumber',
'c.email',
'tur.expiration',
'tur.time',
'tur.recharged_on',
'tur.recharged_time',
'tur.namebp',
'tur.routers'
])
->innerJoin('tbl_customers', ['tur.customer_id', '=', 'c.id'], 'c')
->where_lte('expiration', $current_date)
->order_by_desc('expiration');
$expire = Paginator::findMany($query);
// Get the total count of expired records for pagination
$totalCount = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $current_date)
->count();
// 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);
return $ui->fetch('widget/customer_expired.tpl');
}
}

View File

@ -0,0 +1,17 @@
<?php
class default_info_row
{
public function getWidget()
{
global $config,$ui;
if ($config['enable_balance'] == 'yes'){
$cb = ORM::for_table('tbl_customers')->whereGte('balance', 0)->sum('balance');
$ui->assign('cb', $cb);
}
return $ui->fetch('widget/default_info_row.tpl');
}
}

View File

@ -0,0 +1,28 @@
<?php
class graph_customers_insight
{
public function getWidget()
{
global $CACHE_PATH,$ui;
$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count();
if (empty($u_act)) {
$u_act = '0';
}
$ui->assign('u_act', $u_act);
$u_all = ORM::for_table('tbl_user_recharges')->count();
if (empty($u_all)) {
$u_all = '0';
}
$ui->assign('u_all', $u_all);
$c_all = ORM::for_table('tbl_customers')->count();
if (empty($c_all)) {
$c_all = '0';
}
$ui->assign('c_all', $c_all);
return $ui->fetch('widget/graph_customers_insight.tpl');
}
}

View File

@ -0,0 +1,38 @@
<?php
class graph_monthly_registered_customers
{
public function getWidget()
{
global $CACHE_PATH,$ui;
$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);
} else {
//Monthly Registered Customers
$result = ORM::for_table('tbl_customers')
->select_expr('MONTH(created_at)', 'month')
->select_expr('COUNT(*)', 'count')
->where_raw('YEAR(created_at) = YEAR(NOW())')
->group_by_expr('MONTH(created_at)')
->find_many();
$monthlyRegistered = [];
foreach ($result as $row) {
$monthlyRegistered[] = [
'date' => $row->month,
'count' => $row->count
];
}
file_put_contents($cacheMRfile, json_encode($monthlyRegistered));
}
$ui->assign('monthlyRegistered', $monthlyRegistered);
return $ui->fetch('widget/graph_monthly_registered_customers.tpl');
}
}

View File

@ -0,0 +1,60 @@
<?php
class graph_monthly_sales
{
public function getWidget()
{
global $CACHE_PATH, $ui;
$cacheMSfile = $CACHE_PATH . File::pathFixer('/monthlySales.temp');
//Cache for 12 hours
if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) {
$monthlySales = json_decode(file_get_contents($cacheMSfile), true);
} else {
// Query to retrieve monthly data
$results = ORM::for_table('tbl_transactions')
->select_expr('MONTH(recharged_on)', 'month')
->select_expr('SUM(price)', 'total')
->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->group_by_expr('MONTH(recharged_on)')
->find_many();
// Create an array to hold the monthly sales data
$monthlySales = array();
// Iterate over the results and populate the array
foreach ($results as $result) {
$month = $result->month;
$totalSales = $result->total;
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => $totalSales
);
}
// Fill in missing months with zero sales
for ($month = 1; $month <= 12; $month++) {
if (!isset($monthlySales[$month])) {
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => 0
);
}
}
// Sort the array by month
ksort($monthlySales);
// Reindex the array
$monthlySales = array_values($monthlySales);
file_put_contents($cacheMSfile, json_encode($monthlySales));
}
$ui->assign('monthlySales', $monthlySales);
return $ui->fetch('widget/graph_monthly_sales.tpl');
}
}

View File

@ -0,0 +1,21 @@
<?php
class html_php
{
public function getWidget($data = null)
{
global $ui;
$ui->assign('card_header', $data['title']);
ob_start();
try{
eval('?>'. $data['content']);
}catch(Exception $e){
echo $e->getMessage();
echo "<br>";
echo $e->getTraceAsString();
}
$content = ob_get_clean();
return $content;
}
}

View File

@ -0,0 +1,22 @@
<?php
class html_php
{
public function getWidget($data = null)
{
global $ui;
$ui->assign('card_header', $data['title']);
ob_start();
try{
eval('?>'. $data['content']);
}catch(Exception $e){
echo $e->getMessage();
echo "<br>";
echo $e->getTraceAsString();
}
$content = ob_get_clean();
$ui->assign('card_body', $content);
return $ui->fetch('widget/card_html.tpl');
}
}

View File

@ -0,0 +1,11 @@
<?php
class info_payment_gateway
{
public function getWidget($data = null)
{
global $ui;
return $ui->fetch('widget/info_payment_gateway.tpl');
}
}

View File

@ -0,0 +1,16 @@
<?php
class mikrotik_cron_monitor
{
public function getWidget()
{
global $config,$ui;
if ($config['router_check']) {
$routeroffs = ORM::for_table('tbl_routers')->selects(['id', 'name', 'last_seen'])->where('status', 'Offline')->where('enabled', '1')->order_by_desc('name')->find_array();
$ui->assign('routeroffs', $routeroffs);
}
return $ui->fetch('widget/mikrotik_cron_monitor.tpl');
}
}

View File

@ -0,0 +1,18 @@
<?php
```php
class widget_name
{
public static getWidget($data)
{
global $config, $ui;
return $ui->fetch('widget/template');
}
}
```

View File

@ -0,0 +1,51 @@
<?php
class top_widget
{
public function getWidget()
{
global $ui, $current_date, $start_date;
$iday = ORM::for_table('tbl_transactions')
->where('recharged_on', $current_date)
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->sum('price');
if ($iday == '') {
$iday = '0.00';
}
$ui->assign('iday', $iday);
$imonth = ORM::for_table('tbl_transactions')
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->where_gte('recharged_on', $start_date)
->where_lte('recharged_on', $current_date)->sum('price');
if ($imonth == '') {
$imonth = '0.00';
}
$ui->assign('imonth', $imonth);
$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count();
if (empty($u_act)) {
$u_act = '0';
}
$ui->assign('u_act', $u_act);
$u_all = ORM::for_table('tbl_user_recharges')->count();
if (empty($u_all)) {
$u_all = '0';
}
$ui->assign('u_all', $u_all);
$c_all = ORM::for_table('tbl_customers')->count();
if (empty($c_all)) {
$c_all = '0';
}
$ui->assign('c_all', $c_all);
return $ui->fetch('widget/top_widget.tpl');
}
}

View File

@ -0,0 +1,43 @@
<?php
class voucher_stocks
{
public function getWidget()
{
global $CACHE_PATH,$ui;
$cacheStocksfile = $CACHE_PATH . File::pathFixer('/VoucherStocks.temp');
$cachePlanfile = $CACHE_PATH . File::pathFixer('/VoucherPlans.temp');
//Cache for 5 minutes
if (file_exists($cacheStocksfile) && time() - filemtime($cacheStocksfile) < 600) {
$stocks = json_decode(file_get_contents($cacheStocksfile), true);
$plans = json_decode(file_get_contents($cachePlanfile), true);
} else {
// Count stock
$tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many();
$plans = array();
$stocks = array("used" => 0, "unused" => 0);
$n = 0;
foreach ($tmp as $plan) {
$unused = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 0)->count();
$used = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 1)->count();
if ($unused > 0 || $used > 0) {
$plans[$n]['name_plan'] = $plan['name_plan'];
$plans[$n]['unused'] = $unused;
$plans[$n]['used'] = $used;
$stocks["unused"] += $unused;
$stocks["used"] += $used;
$n++;
}
}
file_put_contents($cacheStocksfile, json_encode($stocks));
file_put_contents($cachePlanfile, json_encode($plans));
}
$ui->assign('stocks', $stocks);
$ui->assign('plans', $plans);
return $ui->fetch('widget/voucher_stocks.tpl');
}
}

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=bandwidth/delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=customers/delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=services/delete/" + id;
}
});
});

View File

@ -1,8 +0,0 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=plan/delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=pool/delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=services/pppoe-delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=routers/delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=settings/users-delete/" + id;
}
});
});

View File

@ -1,9 +0,0 @@
$(document).on("click", ".cdelete", function(e) {
e.preventDefault();
var id = this.id;
bootbox.confirm("Are you sure?", function(result) {
if(result){
window.location.href = "index.php?_route=plan/voucher-delete/" + id;
}
});
});

Some files were not shown because too many files have changed in this diff Show More