Compare commits

...

757 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
5a2e75017e 2025.2.3 2025-02-03 14:11:48 +07:00
609a24df80 pretty url error page 2025-02-03 12:10:33 +07:00
31dda69d3d get ready for Pretty URL 2025-01-31 16:54:22 +07:00
d60b1827d9 add $app_url 2025-01-31 16:23:16 +07:00
1e43ac210a url using getUrl 2025-01-31 16:22:58 +07:00
9bf80467a1 Merge pull request #383 from agstrxyz/patch-24
accsessiontime
2025-01-26 05:14:15 +01:00
093ac7e336 Update phpnuxbill.sql (#384)
acctsessiontime
2025-01-26 05:13:38 +01:00
a67681424e Update updates.json (#385)
accsessiontime
2025-01-26 05:12:10 +01:00
7799bf1aee Update radius.php
nasipaddress
acctsessiontime
2025-01-23 18:02:52 +07:00
3502be2c78 Merge pull request #382 from ahmadhusein17/Development
Language fixes
2025-01-19 15:31:36 +07:00
f6084f6ca4 Update balance-edit.tpl 2025-01-19 15:26:48 +07:00
123b74001a Update bandwidth-add.tpl 2025-01-19 15:25:45 +07:00
a39bbc3b34 Update bandwidth-edit.tpl 2025-01-19 15:24:47 +07:00
a15f759dcb Update coupons-add.tpl 2025-01-19 15:22:38 +07:00
883eb1bbee Update coupons-edit.tpl 2025-01-19 15:18:57 +07:00
8af46e5e51 Update customers-add.tpl 2025-01-19 15:16:11 +07:00
1f1e529e3a Update customers-edit.tpl 2025-01-19 15:09:05 +07:00
bed81136a1 Update hotspot-edit.tpl 2025-01-19 14:24:48 +07:00
f322282c6d Update language-add.tpl 2025-01-19 14:20:06 +07:00
396ece50f4 Update pool-add.tpl 2025-01-19 14:19:07 +07:00
681d0214c8 Update pool-edit.tpl 2025-01-19 14:18:30 +07:00
c7d9f8e8c6 Update port-add.tpl 2025-01-19 14:17:58 +07:00
4dd190a61d Update port-edit.tpl 2025-01-19 14:17:14 +07:00
88bb3a29f1 Update pppoe-add.tpl 2025-01-19 14:16:34 +07:00
5e8414aa61 Update pppoe-edit.tpl 2025-01-19 14:13:03 +07:00
01e832a955 Update radius-nas-add.tpl 2025-01-19 14:07:57 +07:00
ada655a074 Update radius-nas-edit.tpl 2025-01-19 14:07:10 +07:00
3f3435157c Update routers-add.tpl 2025-01-19 14:04:57 +07:00
ec6d6cf203 Update routers-edit.tpl 2025-01-19 14:03:42 +07:00
7026438acc Update voucher-add.tpl 2025-01-19 14:02:30 +07:00
8a6ee9490b Update vpn-add.tpl 2025-01-19 14:00:36 +07:00
89808f3291 Update vpn-edit.tpl 2025-01-19 13:57:23 +07:00
8f6bc9f66e Update balance-add.tpl 2025-01-19 13:51:09 +07:00
87ebabe1c0 Add files via upload 2025-01-19 13:48:10 +07:00
bf95713512 Update hotspot-add.tpl 2025-01-19 13:26:53 +07:00
0f19c80cfc change to google maps 2025-01-17 10:37:24 +07:00
b232ccddbf online_customer using pppoe_username 2025-01-16 09:54:48 +07:00
f4944866a4 fix sql installer and update 2025-01-16 09:52:24 +07:00
61fafcd5cf Merge pull request #380 from ahmadhusein17/Development
Update routers.tpl
2025-01-16 09:51:50 +07:00
0b2a9bac21 PPPOE allow Monthly instead Period 2025-01-10 15:30:48 +07:00
cbc66fa470 Update routers.tpl 2025-01-07 21:07:42 +07:00
e2f2146f1e Merge branch 'Development' 2025-01-07 16:44:09 +07:00
0dc50a68f5 change status online to &bull; 2025-01-07 16:43:41 +07:00
690e5912c0 change disable_coupons != yes to enable_coupons == yes 2025-01-07 16:43:41 +07:00
0e302a5171 change active users to active customers 2025-01-07 16:43:41 +07:00
45e2bb5a96 merge coupon and voucer menu 2025-01-07 16:43:41 +07:00
9aff4dbf26 send back plan on update accounting 2025-01-07 16:43:15 +07:00
0eb900ffd6 Update plan.php 2025-01-07 16:43:15 +07:00
73e85a97ce Update Package.php 2025-01-07 16:42:52 +07:00
da84015aab change status online to &bull; 2025-01-07 16:41:52 +07:00
754b23b53f change disable_coupons != yes to enable_coupons == yes 2025-01-07 16:26:33 +07:00
f997ecc702 change active users to active customers 2025-01-07 16:19:28 +07:00
5b7ffec115 merge coupon and voucer menu 2025-01-07 16:16:38 +07:00
1cbdf5a9b2 send back plan on update accounting 2025-01-07 14:46:51 +07:00
273b6d80c2 Merge pull request #378 from agstrxyz/patch-23
Update plan.php
2025-01-07 14:46:32 +07:00
1a5a7124f7 Update plan.php 2025-01-04 01:59:20 +07:00
4fe0e8d58c Merge pull request #376 from Focuslinkstech/Development
Development
2025-01-02 14:42:20 +07:00
bbdf561b3b Merge pull request #377 from agstrxyz/patch-22
Update Package.php
2025-01-02 14:40:33 +07:00
96fccecbcf Update Package.php 2025-01-01 20:44:10 +07:00
0780438ca8 Update radius.php
Fix Package not showing on voucher when sorting using date created while printing

Its should be Invalid Voucher... in radius-rest
2024-12-30 22:57:37 +01:00
cfe0a14f31 Enhancements and Fixes
Added Vouchers Printing Per Page option, default is 36 for A4 papers.
Fix voucher printing date showing time and seconds (its annoying), replaced with group by days created, and also added  number of vouchers created on each days to make it more unique and classy
2024-12-30 22:49:46 +01:00
b1e145f0dc by mac and nasid 2024-12-30 09:56:59 +07:00
357b7932e9 Merge pull request #375 from agstrxyz/patch-21
Update radius.php
2024-12-30 08:37:00 +07:00
b3f6417bbe Merge pull request #374 from agstrxyz/patch-20
Update app-miscellaneous.tpl
2024-12-30 08:35:34 +07:00
051a8de5f2 Merge pull request #373 from agstrxyz/patch-19
Interim update rest radius
2024-12-30 08:35:02 +07:00
1a931ef617 Update radius.php
Interim-Update freeradius rest
2024-12-29 21:03:46 +07:00
c5907c194b Update app-miscellaneous.tpl
radius rest interim update
2024-12-29 19:17:55 +07:00
1906b1aefd Update cron.php
radius rest interim update check
2024-12-29 19:15:19 +07:00
62c447da9b Merge pull request #372 from agstrxyz/patch-18
Update App.php
2024-12-27 19:16:47 +07:00
51b68b648c Update App.php 2024-12-27 17:13:53 +07:00
86407f731b Merge pull request #371 from Focuslinkstech/Development
Development
2024-12-26 18:47:25 +07:00
d2d52d0ad0 Merge branch 'hotspotbilling:Development' into Development 2024-12-25 17:35:06 +01:00
c527f1cc7c fix radius rest pppoe 2024-12-25 21:12:58 +07:00
47469df558 Merge branch 'hotspotbilling:Development' into Development 2024-12-25 14:52:58 +01:00
c5fd7c0249 Improvement and Fixes
Add print now features when generating vouchers and print them immediately after generation.

Add multiple voucher code deletion, admin can mark multiple vouchers codes and delete them.

Fix issue of plan names displays in the voucher table even when voucher are empty, very annoying
2024-12-25 14:52:10 +01:00
1ea971d3f5 fix acctSessionId 2024-12-25 20:51:44 +07:00
7d5e0c7a8e Merge pull request #370 from Focuslinkstech/Development
Development
2024-12-25 20:41:42 +07:00
cf6f89b3f2 fixed coupon bugs
fix add and edit bug
2024-12-25 10:20:29 +01:00
2dc50c3637 Update coupons.php
fixed change status error
2024-12-25 10:04:20 +01:00
da0ca4f087 Merge pull request #369 from Focuslinkstech/Development
Development
2024-12-23 16:36:38 +07:00
486c849eee Merge branch 'hotspotbilling:Development' into Development 2024-12-22 09:30:20 +01:00
c7a09c60cb fix lang 2024-12-22 15:22:08 +07:00
fce910455a Lang 2024-12-22 15:16:00 +07:00
ade4e22454 Merge pull request #368 from ahmadhusein17/Development
Update selectGateway.tpl
2024-12-22 15:13:36 +07:00
487d31358d Merge branch 'master' into Development 2024-12-22 15:12:35 +07:00
daf1bb7d67 fix radius rest pppoe_username 2024-12-22 15:10:01 +07:00
7c547c967e fix whereRaw 2024-12-22 12:23:49 +07:00
2e61c89d89 Update autoload.php
Add null condition too
2024-12-21 11:16:29 +01:00
27c736fa69 Merge pull request #367 from gerandonk/Development
add voucher date created and print by date
2024-12-20 17:18:01 +07:00
b3fd1d5a3e add voucher date created and print by date
add voucher date created and print by date
2024-12-20 16:48:06 +07:00
5d492def3c Update selectGateway.tpl
Update translation
2024-12-20 16:08:29 +07:00
0dfa412be8 Update phpnuxbill.sql
why integer has  CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL ??
2024-12-20 14:19:22 +07:00
c6cb63a1c5 Update phpnuxbill.sql
why integer has character sets?
`user_id` int(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
to
`user_id` int(11) ,
2024-12-20 14:16:31 +07:00
32418f05fb Merge pull request #366 from Focuslinkstech/Development
Development
2024-12-20 14:15:20 +07:00
3c1d8dbb8a Merge branch 'Development' of https://github.com/Focuslinkstech/phpnuxbill into Development 2024-12-17 20:00:34 +01:00
67a097fddf Enhancement
Add coupon unlimited usage 0 is unlimited
Add more logic to coupon brute force attack
2024-12-17 19:59:04 +01:00
e03b250fb7 Merge pull request #365 from Focuslinkstech/Development
New Feature and Bux Fix
2024-12-17 15:28:27 +07:00
8a36746a0f New Feature and Bux Fix
Lots of changes has been made that i cant recall
Added Coupon
Fix installation bug
Add more nav list
Fix this and that
2024-12-16 17:45:13 +01:00
fd920748be Urgent Fix registration logic 2024-12-06 15:30:43 +07:00
e32e0aba16 2024.12.6 2024-12-06 14:56:44 +07:00
98589cd283 Merge pull request #362 from Focuslinkstech/Development
Development
2024-12-06 14:33:12 +07:00
fd20ede7aa Merge branch 'hotspotbilling:Development' into Development 2024-12-05 17:04:12 +01:00
226ed41075 patch: Update
add css and plan price to user billing
2024-12-05 17:01:16 +01:00
9ec090a621 Merge pull request #359 from Focuslinkstech/Development
Patch Update: Enhancement
2024-12-05 11:52:10 +07:00
c8d5861f2e Patch Update: Enhancement
Fixed Activation and Order History disappear when customer username is  Changed
2024-12-04 14:37:01 +01:00
92491e22ec check if method exists 2024-12-03 13:38:29 +07:00
f9e7a8dbb4 Merge pull request #358 from gerandonk/Development
add sync function
2024-12-03 13:35:03 +07:00
5559069aac Merge pull request #357 from ahmadhusein17/Development
Language fixes
2024-12-03 11:47:44 +07:00
d6144537c4 add sync function
add sync function
fix radius time monthly validity
fix sync button for hotspot with uptime limit and data limit
2024-12-03 11:15:15 +07:00
04742535ba Update activation-list.tpl 2024-11-30 23:25:13 +07:00
f76753b247 Update dashboard.tpl 2024-11-30 23:22:28 +07:00
e4f209030f Update forgot.tpl 2024-11-30 23:16:37 +07:00
5c54223454 Update login-custom-moon.tpl 2024-11-30 23:13:57 +07:00
13a2de2664 Update login-noreg.tpl 2024-11-30 23:12:32 +07:00
538cf4e675 Update login.tpl 2024-11-30 23:11:35 +07:00
9edb0b4d2f Update orderBalance.tpl 2024-11-30 23:10:12 +07:00
d0a7f2b991 Update orderHistory.tpl 2024-11-30 23:09:06 +07:00
f4c15c3475 Update orderView.tpl 2024-11-30 23:06:31 +07:00
50dd4b4839 Update register-otp.tpl 2024-11-30 23:00:21 +07:00
1d7dca8ada Update register.tpl 2024-11-30 22:45:34 +07:00
694b4d7bac Update selectGateway.tpl 2024-11-30 22:43:05 +07:00
82dce56209 Update sendPlan.tpl 2024-11-30 22:38:38 +07:00
b2ac84900e Merge pull request #355 from ahmadhusein17/Development
Development
2024-11-29 13:17:14 +07:00
16de814091 Update profile.tpl
Correct the wrong Indonesian
2024-11-29 12:35:13 +07:00
4ce4633999 Update dashboard.tpl
Correct the wrong Indonesian
2024-11-29 12:29:54 +07:00
5788c9c0a7 Merge pull request #354 from Focuslinkstech/Development
Patch Addon
2024-11-29 11:27:40 +07:00
7acab4d50e New Feature
Add New self registration notification for admin
2024-11-28 18:35:01 +01:00
79d0921ebe Dynamic Custom Login Page
Add Dynamic image changing functions, for swift action for image update
2024-11-28 17:59:12 +01:00
77d25738b1 Patch Addon
Add container dark mode
Add Registration Mandatory Fields
2024-11-28 16:16:05 +01:00
b34b9064d0 2024.11.28 2024-11-28 09:58:02 +07:00
13adfc8254 Merge pull request #353 from Focuslinkstech/Development
Development
2024-11-28 09:51:44 +07:00
73b829b8c1 Update init.php
remove lang debug
2024-11-25 11:36:30 +01:00
91e7caae80 dont translate english to english 2024-11-21 18:10:24 +07:00
40a39f0ced Don't translate english to english 2024-11-21 18:10:05 +07:00
699ffc1302 Fix ajax api-get-text 2024-11-21 18:09:03 +07:00
5e2eaa5578 Custom Mikrotik SMS Command 2024-11-21 18:08:51 +07:00
38f6738c45 fix register with otp and hide dashboard 2024-11-21 15:03:28 +07:00
63e564c9dd Fix otp registration as it still check is sms url exists, so i remove it 2024-11-21 10:09:36 +07:00
27a5d2d45a check setting 2024-11-21 09:56:14 +07:00
153da7c63b registration can force to upload photo 2024-11-20 14:44:23 +07:00
c9778e71b9 fix option select custom field 2024-11-19 18:18:51 +07:00
eb970b257a Custom Fields for registration and Profile, but not yet finished for upload picture 2024-11-19 18:11:34 +07:00
242dda2e99 remove You do not have permission to access this page in demo mode 2024-11-19 13:20:53 +07:00
5445aedcd1 how do dev test if it has "You do not have permission to access this page in demo mode"? 2024-11-19 13:20:38 +07:00
3f77dcf129 Merge pull request #352 from Focuslinkstech/Development 2024-11-18 20:04:38 +07:00
357df8d986 change case 2024-11-18 13:25:03 +01:00
16199b4c18 Patch Update
fix moon custom login page logo not showing after upload
add registration now allowed in demo mode, add warning when registration is disabled
2024-11-16 16:59:42 +01:00
318b52905d Merge pull request #350 from Focuslinkstech/Development
Development
2024-11-12 10:06:28 +07:00
581b765c28 add default values
add default images
fix bugs template bugs
add tawk.to and add footer links
Fix moon template
2024-11-11 19:54:44 +01:00
c3355ad8f7 New Feature: Customer login settings
you can change or add multiple custom login-page template

you can also add your own login page

just add a login page with name
"login-custom-[template-name].tpl"

e.g.

login-custom-star.tpl
login-custom-moon.tpl
login-custom-sun.tpl
login-custom-venus.tpl
2024-11-11 17:42:57 +01:00
5bb724b9b3 11.11 2024-11-11 15:32:26 +07:00
e431f07a71 fix show bw 2024-11-11 15:32:03 +07:00
b5efe81637 Merge pull request #349 from ahmadhusein17/patch-1
Update indonesia.json
2024-11-08 16:17:16 +07:00
c827c983ce Update indonesia.json
Indonesian translation fix
2024-11-08 11:58:57 +07:00
a81ddd68dd Merge pull request #347 from Focuslinkstech/Development
Development
2024-11-08 11:03:51 +07:00
9c06225cc2 Merge pull request #348 from gerandonk/Development
add tax on bills tag
2024-11-08 06:54:07 +07:00
6a5fd9d11a add tax on bills tag
add tax inside bills tag notification
show tax in admin recharge page
2024-11-07 23:28:52 +07:00
d20fea1cdf Update app-devices.tpl
enhance device list view
2024-11-07 14:01:05 +01:00
3bd8ebdc76 Update app-miscellaneous.tpl
Enhance the template
2024-11-07 07:42:48 +01:00
8dcd00bc6b Update app-miscellaneous.tpl
fix not responsive on mobile
2024-11-06 17:32:59 +01:00
56ddf65fdf maybe fix this and fix that, i lost track 2024-11-06 16:08:16 +07:00
a2347dd241 enable button after 5 seconds 2024-11-06 13:50:22 +07:00
b316e4a242 change confirm() to ask() 2024-11-06 13:45:36 +07:00
2b7361cf6b Merge pull request #346 from gerandonk/Development
update
2024-11-06 11:46:10 +07:00
5ccddf11be update
postpaid logic for more than 1 period
fix mikrotiksms not working
2024-11-06 11:34:28 +07:00
dbf1623ca6 Merge pull request #345 from Focuslinkstech/Development
Update Package.php
2024-11-06 09:32:50 +07:00
e17f07f410 Merge branch 'hotspotbilling:Development' into Development 2024-11-05 19:56:01 +01:00
23bba0b189 Update Package.php
Reversed failed  logic
2024-11-05 19:53:31 +01:00
167732094b Merge pull request #344 from Focuslinkstech/Development
Bugs Fixes and bring back our dear extend expiry feature
2024-11-05 23:21:50 +07:00
9569847612 Fix Security
javascript not add to general settings
2024-11-05 16:45:39 +01:00
4c1f49ea81 Update app-miscellaneous.tpl
reduced the help text size
2024-11-05 16:25:01 +01:00
b7fdc90aa7 Bugs Fixes
fix extend expiry bug
2024-11-05 16:05:31 +01:00
fec7780f50 Merge pull request #341 from Focuslinkstech/Development
send welcome email bug
2024-11-05 19:15:49 +07:00
7d24b719b5 Merge pull request #342 from gerandonk/Development
fix postpaid more than 1 period
2024-11-05 19:14:10 +07:00
a34f1bb0fc fix postpaid more than 1 period 2024-11-05 19:00:03 +07:00
2863d70214 Fix typo
fixed typo in device, change demo to Demo
2024-11-05 11:32:08 +01:00
949a3d27eb Update app-settings.tpl
fixed callout text
2024-11-05 11:16:27 +01:00
1b7f449d96 Update app-settings.tpl
replace footer not with callout to make it more decent
2024-11-05 11:13:26 +01:00
0295ab44e5 send welcome email bug
fixed welcome email sending bug
2024-11-05 10:13:56 +01:00
7f35e7fb68 fix Tawk.to 2024-11-05 12:07:54 +07:00
b28b40280f Remove debug 2024-11-05 11:09:56 +07:00
35243cb9b0 fix [[payment_link]] 2024-11-05 10:42:41 +07:00
de4c6c730f [[payment_link]] documentation 2024-11-05 09:36:56 +07:00
53b64548fc to use [[payment_link]] you need to add url manually https://domain.tld/[[payment_link]] 2024-11-05 08:55:21 +07:00
bf11fd2f2c Merge pull request #340 from gerandonk/Development
fix payment_link
2024-11-05 08:52:35 +07:00
e0f150633c fix payment_link
if url detected localhost, change your host in config.php to make work
2024-11-05 03:53:47 +07:00
06c68ccebd Critical update 2024-11-04 15:13:17 +07:00
6db2f2bf0d fix critical bug customer can recharge without balance when using balance. and move Balance to select Gateway 2024-11-04 15:10:58 +07:00
32a64d944a remove index.php 2024-11-04 13:57:28 +07:00
1903dc6b45 generate token 2024-11-04 13:54:45 +07:00
27bd2590f2 add Security settings 2024-11-04 12:05:17 +07:00
8908f4bdc3 enable/disable CSRF 2024-11-04 12:05:08 +07:00
7fb08eb76f fix unpaid order 2024-11-04 10:44:39 +07:00
8a0c17b319 Add order by Firstname or lastname requested by CountryCom LLC, thanks 2024-11-01 17:03:00 +07:00
afbb39b1d9 Reports using select2 2024-11-01 14:56:52 +07:00
7006ad8c40 Merge pull request #339 from gerandonk/Development
change postpaid logic
2024-11-01 06:55:31 +07:00
99b30f747f change postpaid logic
fix postpaid expired date error for new customer or customer without plan
2024-11-01 02:46:27 +07:00
1f1430fd21 Fix delete old photo 2024-10-31 14:46:51 +07:00
93583afb65 Allow Customer buy balance with any amount settings 2024-10-31 14:01:51 +07:00
9779d77f22 fix gateway selected 2024-10-31 13:56:32 +07:00
3dfb12d830 Customer Photo 2024-10-31 13:49:51 +07:00
6e446192d8 set photo to header customer 2024-10-31 13:38:54 +07:00
51811bd753 REMOVE CSRF (annoying when refresh page) when open edit customer page, add upload photo when edit Customer 2024-10-31 13:36:43 +07:00
db8affce1f Add photo to customer table 2024-10-31 13:34:54 +07:00
c740820731 Delete extend_expiry settings 2024-10-31 13:15:36 +07:00
80e0dc6485 update face detection 2024-10-30 18:05:39 +07:00
5552fb20d5 fix facedetection 2024-10-30 18:05:12 +07:00
f08211a83a fix variable header 2024-10-30 18:00:37 +07:00
71d6024d62 Upload Admin face with Face Detection 2024-10-30 17:49:21 +07:00
73993c4f25 add field data to tbl_users 2024-10-30 17:49:21 +07:00
25be52fe9c add photo field 2024-10-30 17:49:21 +07:00
5d79f9d842 Merge pull request #338 from Focuslinkstech/Development
Customer can buy Custom Balance
2024-10-29 15:06:39 +07:00
70d8cd8e01 Feature: Custom Balance
Add custom balance in customer dashboard

Note: i only test it with Paystack and Razorpay payment gateway and it works as expected

test it very well and report any error or bug
2024-10-29 08:46:03 +01:00
df7293e3d0 Merge branch 'Development' of https://github.com/Focuslinkstech/phpnuxbill into Development 2024-10-29 08:23:56 +01:00
7614422bd8 Merge branch 'master' into Development 2024-10-29 14:17:31 +07:00
5ff9c74f63 Merge pull request #337 from Focuslinkstech/master
security
2024-10-29 14:16:23 +07:00
8fdbe0ec1d security
add csrf token to customers post and get methods
2024-10-28 11:57:48 +01:00
e4fb835f2c Update header.tpl
Fix Additional Information header button
2024-10-28 10:48:47 +01:00
d69086d99b dont show echo 2024-10-28 15:49:44 +07:00
dc42a09685 Merge branch 'hotspotbilling:Development' into Development 2024-10-27 15:07:00 +01:00
08b2e16e73 fix footer set language 2024-10-26 19:22:35 +07:00
8e41749cd4 fix error message 2024-10-26 19:15:38 +07:00
383a1e55d7 Merge pull request #334 from Focuslinkstech/Development
patch
2024-10-26 19:14:45 +07:00
3778499611 Merge pull request #333 from taukir007/master
Registration Form: Separate username and phone number fields if OTP Required is enabled.
2024-10-26 19:14:29 +07:00
fda935b3a6 Update header.tpl
fix panel header
2024-10-25 13:00:29 +01:00
d5c2c72a74 Update register-otp.tpl 2024-10-25 15:03:28 +06:00
0df7027851 Refactor registration form: Separate username and phone number fields
- Changed the phone number input field to be readonly and renamed it to 'phone_number'.
- Introduced a new input field for 'username' in the registration section.
- Adjusted the panel headings and layout for clarity and consistency.
2024-10-25 15:01:14 +06:00
00081d40e5 Update registration form
- Change input name from 'username' to 'phone_number'
- Add inputmode and pattern attributes for better validation
2024-10-25 14:58:02 +06:00
01c2808e43 Enhance registration process with OTP verification
- Separate username and phone number handling
- Improve Phone Number validation and handling
- Stop changes to the registration form when the wrong password is entered
- Enhance error messages for better user experience
2024-10-25 14:51:01 +06:00
e02bf9863f fix [[trx_date]] 2024-10-25 14:38:04 +07:00
7f72b0c34a cannot deactivate postpaid 2024-10-25 14:05:57 +07:00
a04c3dd82b don't show Deactivate if it postpaid 2024-10-25 13:48:45 +07:00
1a0b3ffaf3 hook cronjob_end 2024-10-25 13:45:11 +07:00
3ddfc92b18 Merge pull request #332 from ahmadhusein17/Development
Added Confirmation Alert
2024-10-25 13:44:34 +07:00
9de7a07de4 Merge pull request #331 from gerandonk/Development
fix warning array key
2024-10-25 13:44:00 +07:00
52f65ec143 Merge pull request #330 from Focuslinkstech/Development
patch update
2024-10-25 13:43:31 +07:00
e8a800bbd6 fix log that beed IP, that make plugin CLI error 2024-10-24 14:11:10 +07:00
36162381cb Update pool-add.tpl 2024-10-23 19:27:44 +07:00
d14599f720 Update pool-edit.tpl 2024-10-23 19:26:43 +07:00
244e552eb6 Update port-add.tpl 2024-10-23 19:25:47 +07:00
6bb9b001dc Update port-edit.tpl 2024-10-23 19:12:36 +07:00
7c42ab5b89 Update pppoe-add.tpl 2024-10-23 19:11:49 +07:00
717f29c95d Update pppoe-edit.tpl 2024-10-23 19:09:35 +07:00
ee52a2ec1e Update radius-nas-add.tpl 2024-10-23 19:08:31 +07:00
2b3d3a6796 Update radius-nas-edit.tpl 2024-10-23 19:07:41 +07:00
859e0ce6af Update recharge.tpl 2024-10-23 19:06:14 +07:00
8c5ed98f09 Update refill.tpl 2024-10-23 19:05:33 +07:00
8347a982c5 Update routers-edit.tpl 2024-10-23 19:04:24 +07:00
60188c736b Update voucher-add.tpl 2024-10-23 19:03:40 +07:00
ba729f44fc Update plan-edit.tpl 2024-10-23 18:58:46 +07:00
954a892f86 Update language-add.tpl 2024-10-23 18:55:31 +07:00
fe41b92a7c Update hotspot-edit.tpl 2024-10-23 18:54:19 +07:00
b835520ccf Update hotspot-add.tpl 2024-10-23 18:53:25 +07:00
3adfd2f2c3 Update hotspot-edit.tpl 2024-10-23 18:52:36 +07:00
434d7d8cf9 Update deposit.tpl 2024-10-23 18:49:53 +07:00
c617bb9a4d Update customers-add.tpl 2024-10-23 18:48:55 +07:00
853af04362 Update customers-edit.tpl 2024-10-23 18:48:03 +07:00
d8894ab296 Update balance-add.tpl 2024-10-23 18:42:19 +07:00
0c2249daf9 Update balance-edit.tpl 2024-10-23 18:41:36 +07:00
e7f4c88648 Update bandwidth-add.tpl 2024-10-23 18:40:08 +07:00
da541595a4 Update bandwidth-edit.tpl 2024-10-23 18:39:25 +07:00
495da312a8 Update admin-edit.tpl 2024-10-23 18:38:15 +07:00
71cc8290a3 Update admin-add.tpl 2024-10-23 18:37:39 +07:00
575ef1c117 Update routers-add.tpl 2024-10-23 18:36:49 +07:00
bcb39f114a Update routers-edit.tpl 2024-10-23 18:35:28 +07:00
544706d82e Update voucher-add.tpl 2024-10-23 18:34:40 +07:00
6f4f788d8f Update vpn-edit.tpl 2024-10-23 18:33:01 +07:00
0f59250215 Update vpn-add.tpl 2024-10-23 18:32:21 +07:00
bcff2253ee Update pool-edit.tpl 2024-10-23 18:28:42 +07:00
676caec8ca Update port-add.tpl 2024-10-23 18:26:53 +07:00
9d6cd6756c Update port-edit.tpl 2024-10-23 18:25:40 +07:00
b375460789 Update pppoe-add.tpl 2024-10-23 18:24:56 +07:00
b7aee6f91c Update pppoe-edit.tpl 2024-10-23 18:24:11 +07:00
b4907ed661 Update pool-add.tpl 2024-10-23 18:23:13 +07:00
44d091cae8 Update plan-edit.tpl 2024-10-23 18:21:38 +07:00
766c2c268e Update message.tpl 2024-10-23 18:20:28 +07:00
14f9559033 Update message-bulk.tpl 2024-10-23 18:19:48 +07:00
53885a6f7d Update language-add.tpl 2024-10-23 18:17:55 +07:00
46a83d4e30 Update customers-add.tpl 2024-10-23 18:16:52 +07:00
b313f1de36 Update bandwidth-add.tpl 2024-10-23 18:15:31 +07:00
a6df867ca1 Update deposit.tpl 2024-10-23 18:14:20 +07:00
7339be29f8 Update balance-add.tpl 2024-10-23 18:13:14 +07:00
8ae88adc17 Update hotspot-add.tpl 2024-10-23 18:11:54 +07:00
c960baaf1f Add files via upload 2024-10-23 18:09:57 +07:00
5e310cba29 fix warning array key
fix Warning: Undefined array key "HTTP_HOST"
fix Warning: Undefined array key "SERVER_PORT"
2024-10-23 14:59:27 +07:00
eb1d804a4d patch update
make settings form individual to reduce conflicts
2024-10-23 08:28:00 +01:00
6ffa396b05 2024.10.23 2024-10-23 14:15:47 +07:00
cd5f9101f2 only admin can edit customer 2024-10-23 14:13:32 +07:00
f8878ad8b6 only admin can show password 2024-10-23 14:12:55 +07:00
dc55957a53 Refill Balance with Custom Amount Requested by Javi Tech 2024-10-23 14:04:11 +07:00
bdda199523 center invoice 2024-10-22 11:35:08 +07:00
8086802bf6 tawkto bring customer data 2024-10-22 10:34:44 +07:00
53aa187d1f register with otp, force username to phone number 2024-10-21 14:19:13 +07:00
7685323bd6 phone_otp_type forgot to add 2024-10-21 14:15:39 +07:00
1fe2dac580 Merge pull request #328 from agstrxyz/patch-17
Update cron.php
2024-10-21 07:29:47 +07:00
0428d9620e Update cron.php
voucher expired tidak terhapus dari mikrotik sebab tidak menemukan data pada tabel customer $c  untuk di eksekusi ke device.php
2024-10-20 21:47:08 +07:00
ad862640cc Merge pull request #327 from gerandonk/Development
make sync button not remove active hotspot user
2024-10-20 12:05:01 +07:00
a4e8ae8c5c make sync button not remove active hotspot user
make sync button not remove active hotspot user
2024-10-20 01:07:00 +07:00
d4c34afb8d Merge pull request #326 from ahmadhusein17/Development
Fix Footer
2024-10-18 15:30:13 +07:00
0dc4d22da3 Fix Footer 2024-10-18 14:18:48 +07:00
169a7afe67 Merge pull request #324 from ahmadhusein17/Development
Fix footer that is too long.
2024-10-18 13:33:28 +07:00
67890881f3 Fix footer that is too long. 2024-10-18 13:29:29 +07:00
3febb60253 fix variable sms_otp_registration 2024-10-18 12:57:59 +07:00
005155e2a5 fix variable settings 2024-10-18 11:37:45 +07:00
f8a15e4754 ## 2024.10.18
- Single Session Admin Can be set in the Settings
- Auto expired unpaid transaction
- Registration Type
- Can Login as User from Customer View
- Can select customer register must using OTP or not
- Add Meta.php for additional information
2024-10-18 10:59:52 +07:00
f0da633808 Single session Admin can be set in the misc settings 2024-10-18 10:59:52 +07:00
5f6b14ab7e Merge pull request #320 from ahmadhusein17/master
For Header and Footer in Customer!
2024-10-17 18:34:02 +07:00
3475a6086d For Header and Footer in Customer!
Minor improvements to the appearance of the footer. (⁠◕⁠ᴗ⁠◕⁠✿⁠)
2024-10-17 16:06:56 +07:00
49f194a7f2 Admin can Login as Customer 2024-10-17 15:14:39 +07:00
b3744a5007 fix Customer view to view tbl_payment_gateway 2024-10-17 14:09:11 +07:00
234e5e3967 add try catch to handle invalid value 2024-10-17 13:38:50 +07:00
97296abf06 fix unpaid expired check 2024-10-17 13:14:53 +07:00
a7232e2b92 fix unpaid logic 2024-10-17 11:39:15 +07:00
7c0bdeea41 CREATE TABLE IF NOT EXISTS tbl_meta 2024-10-17 11:32:39 +07:00
5566a7ebb5 Add Meta class for meta data attributes 2024-10-17 11:28:52 +07:00
de49a9992f Price Before Discount unrequired 2024-10-17 09:54:06 +07:00
0cf5483353 check expired payments 2024-10-17 09:44:21 +07:00
b15fdf1d6a Setting sAllow Registration = Yes/Voucher/No Registration 2024-10-17 09:35:26 +07:00
ca98ca2223 profile logic Username 2024-10-16 16:01:34 +07:00
5ccb8520d3 change logic username field 2024-10-16 13:41:12 +07:00
ff4e620b75 add registration settings to set username 2024-10-16 13:40:54 +07:00
1b7e5c7510 Setting for registration using OTP or not 2024-10-16 11:40:52 +07:00
084cc0e0fb miscellaneous.tpl to app-miscellaneous.tpl 2024-10-16 11:36:25 +07:00
25d9524f53 fix header 2024-10-16 11:35:24 +07:00
4308765bec Merge pull request #319 from Focuslinkstech/Development
Template Redesiged:
2024-10-15 20:22:22 +07:00
70dbe59319 Template Redesiged:
Settings page redesigned
2024-10-15 13:29:48 +01:00
577ed31f57 ## 2024.10.15
- CSRF Security
- Admin can only have 1 active session
- Move Miscellaneous Settings to new page
- Fix Customer Online
- Count Shared user online for Radius REST
- Fix Invoice Print
2024-10-15 16:19:43 +07:00
064e4c80ed code to code 2024-10-15 16:10:34 +07:00
70bcff7679 Merge pull request #317 from gerandonk/Development
($plan['is_radius'] == '1')
2024-10-11 22:53:03 +07:00
47b729867d Merge pull request #318 from Focuslinkstech/Development
move miscellaneous to settings sub-menu for quick access
2024-10-11 22:52:37 +07:00
696b2e4789 move miscellaneous to settings sub-menu for quick access
add csrf token check for settings and its environments
2024-10-11 16:11:03 +01:00
0f0929db2a ($plan['is_radius'] == '1') 2024-10-11 18:59:38 +07:00
7267bd082a Api always Valid 2024-10-11 11:42:38 +07:00
d5cba4b3c1 add option for check is customer online 2024-10-11 11:37:45 +07:00
155b2959b5 fix api-get-text 2024-10-11 11:29:57 +07:00
a35137b7ab fix logic Session Admin especially isApi 2024-10-11 11:09:27 +07:00
83dd564e53 fix position Admin::_info(); 2024-10-11 11:07:47 +07:00
8e8a52d807 login_token VARCHAR(40) 2024-10-11 11:07:23 +07:00
5bc273a9dd session_destroy(); inside removeCookie() 2024-10-11 10:38:24 +07:00
f9fe261e55 session_destroy(); 2024-10-11 10:37:35 +07:00
0dc79cd5c4 ->select('login_token') 2024-10-11 10:37:23 +07:00
b32e2901af Merge pull request #316 from Focuslinkstech/Development
Development
2024-10-11 08:01:40 +07:00
f77d7051c1 remove unused variable 2024-10-10 17:02:04 +01:00
60e1eacc59 fix login loop 2024-10-10 16:24:36 +01:00
82ffc15c03 Merge branch 'hotspotbilling:Development' into Development 2024-10-10 15:49:32 +01:00
6e5450d104 CSRF added to customer acounts update 2024-10-10 15:48:32 +01:00
6458b792f6 Merge pull request #315 from Focuslinkstech/Development
Fight Against Insecurity Ongoing
2024-10-10 21:44:40 +07:00
6be0da383c fixed template issue 2024-10-10 15:13:29 +01:00
534886f8f3 Fix app stage issue 2024-10-10 15:04:12 +01:00
c9b9808112 Fight Against Insecurity : Prevent Admin multiple Login Sessions, its a security threat to phpnuxbill.
plase note: if you are running nuxbill on localhost please set app_stage to something else e.g.
$_app_stage = 'Demo';
its very important
2024-10-10 14:33:27 +01:00
e737ae9d29 $routes['2'] = 0; 2024-10-10 17:25:21 +07:00
78e3f2e8fb Merge 2024-10-10 10:52:13 +07:00
3eaa302128 add CSRF Token on customer login 2024-10-10 10:50:48 +07:00
9bc3ccc02b Added token expiration: 30 minutes by default 2024-10-10 10:50:48 +07:00
99e8b20bb3 Testing CSRF from admin login, if works well then we will make it official 2024-10-10 10:50:48 +07:00
bd30261e84 move the CSRF Function to global function for easy access 2024-10-10 10:50:48 +07:00
96365eef2a Added more security flags to prevent XSS attack from cookie. 2024-10-10 10:50:48 +07:00
c08c069479 Critical Updates, Fight Against Insecurity 2024-10-10 10:50:48 +07:00
71d653f3d1 Merge pull request #314 from gerandonk/Development
fix send plan radius
2024-10-10 10:48:23 +07:00
b1919555e5 Fix Lang function again 2024-10-10 10:24:01 +07:00
2522f3112e Spanish Lang by @ORConsulTech 2024-10-10 10:23:47 +07:00
ba63face92 change folder ui/ui/user-ui to ui/ui/customer 2024-10-10 10:19:01 +07:00
8f78f5184e maybe fix buy plan for friend 2024-10-10 10:18:35 +07:00
e9ae2e04ce Merge branch 'hotspotbilling:Development' into Development 2024-10-10 08:18:12 +07:00
56c69122e4 fix send plan radius
tolong di koreksi siapa tau ada kesalahan ambil code nya
2024-10-10 08:11:53 +07:00
2198c00d52 add radius to htaccess firewall 2024-10-09 17:00:33 +07:00
695b3c7a6f Fix radius rest for shared user online, check user online by count Start status. 2024-10-09 16:01:32 +07:00
f7cb6c196f Merge branch 'Development' 2024-10-08 11:22:39 +07:00
5adb09efcf show customer is online or not and Rearange Customer View 2024-10-08 10:19:59 +07:00
e70a6c4dae show is customer online in the Active List 2024-10-08 10:19:59 +07:00
4f3647beae Show is Customer Online in the customer list 2024-10-08 10:19:59 +07:00
9016ecbb98 Fix Invoice Print 2024-10-08 10:19:59 +07:00
277ab87645 Update radius.php
*Fix shared user limit
2024-10-08 10:19:59 +07:00
3a929c5418 Update radius.php
*Fix shared user limit
2024-10-08 10:19:59 +07:00
2771eba0ee Update invoice-print.tpl
Change print template design
2024-10-08 10:19:59 +07:00
5b51af1b6e change variable user to name to check pppoe online customer 2024-10-08 09:50:23 +07:00
05eab22062 show customer is online or not and Rearange Customer View 2024-10-07 15:05:22 +07:00
d4eaaad535 show is customer online in the Active List 2024-10-07 15:05:22 +07:00
3407571474 Show is Customer Online in the customer list 2024-10-07 15:05:22 +07:00
da1341d971 Fix Invoice Print 2024-10-07 15:05:22 +07:00
e4e70c5104 Merge pull request #311 from agstrxyz/patch-16
Update radius.php
2024-10-04 22:36:16 +07:00
fbde8b0056 Update radius.php
*Fix shared user limit
2024-10-04 22:20:42 +07:00
9552783eee Update radius.php
*Fix shared user limit
2024-10-04 21:16:26 +07:00
44f852ab2f Merge pull request #310 from ahmadhusein17/Development
Update invoice-print.tpl
2024-10-01 13:59:14 +07:00
3da0c356de Update invoice-print.tpl
Change print template design
2024-09-28 21:02:29 +07:00
d2db2bef79 Remove debug die() 2024-09-26 17:27:45 +07:00
44af731c4c PPPOE don't set IP for expired Plan 2024-09-26 09:43:12 +07:00
0a9733b0b3 Fix Unset IP 2024-09-25 17:14:50 +07:00
79c25a64d6 add option to show bandwith plan in the Miscellaneous settings 2024-09-25 15:06:15 +07:00
e4d3aff618 Fix Bug email 2024-09-25 15:02:51 +07:00
6462572fb8 show bandwidth plan 2024-09-25 15:00:13 +07:00
a51462ef1a show remaining bill 2024-09-24 10:55:07 +07:00
4ffbac878c Merge pull request #309 from gerandonk/Development
if exist remote ip on pppoe, it will reset on expired
2024-09-24 06:45:49 +07:00
3a2c55e0d8 if exist remote ip on pppoe, it will reset on expired
fix bugs expired user not going expired pool if remote-ip exist
2024-09-23 20:25:48 +07:00
229eae5c8f additional cost can be minus 2024-09-23 17:11:32 +07:00
aa0432df38 Discount price requested by Fiberwan 2024-09-23 16:43:24 +07:00
8e3a16a123 add Burst preset for bandwith 2024-09-23 16:09:59 +07:00
aa1ef2c41c When forgot password, tell to wait for n seconds before resend 2024-09-23 13:43:53 +07:00
4567f82a06 Merge pull request #306 from gerandonk/Development
Change Local address to remote address
2024-09-21 22:18:45 +07:00
ce61df9057 Merge pull request #308 from ahmadhusein17/Development
Update profile.tpl
2024-09-21 22:17:56 +07:00
6a24c6c11e Update activation-list.tpl 2024-09-21 15:37:50 +07:00
b1ad007db0 Update pool.tpl 2024-09-21 15:27:35 +07:00
bfd014417b Update profile.tpl
Display improvements
2024-09-20 19:37:28 +07:00
9b1adb15da Forgot Password and Forgot Username for Customer, requested by fiberwan 2024-09-20 17:07:18 +07:00
686d2a188a Add Public Header and Footer in user-ui folder, and use it for public page 2024-09-20 10:23:01 +07:00
1a65c04666 Merge branch 'Development' of https://github.com/gerandonk/phpnuxbill into Development 2024-09-18 19:13:45 +07:00
7e9f6123e8 Merge pull request #304 from Focuslinkstech/master
add seperate css for cron monitor
2024-09-15 00:26:18 +07:00
f738c9b120 add seperate css for cron monitor 2024-09-13 12:03:18 +01:00
5f7f785d99 Merge pull request #303 from agstrxyz/patch-15
Update MikrotikVpn.php
2024-09-13 16:44:06 +07:00
c0c54ce767 Merge pull request #302 from Focuslinkstech/master
fix admin dark mode switch button
2024-09-13 16:43:58 +07:00
f81f3df700 add mailer error reporting 2024-09-13 10:43:42 +01:00
3c6ef3bcf3 Update MikrotikVpn.php 2024-09-13 16:31:03 +07:00
985ddda41f fix panel warning bottom radius 2024-09-13 09:55:58 +01:00
a31286d781 fix toggle switch button mobile view 2024-09-13 09:47:43 +01:00
c2916da215 fix dark button 2024-09-13 09:33:59 +01:00
82569239e2 update changelog 2024-09-13 14:46:14 +07:00
bc0b3abe92 Merge pull request #301 from agstrxyz/Development
Paket vpn tunnel remot
2024-09-13 06:15:28 +07:00
f5d9649f97 Add files via upload 2024-09-13 00:46:40 +07:00
095e8937a2 Paket vpn tunnel remot 2024-09-13 00:43:46 +07:00
5f50d725f1 Merge pull request #298 from Focuslinkstech/Development
add styles to some tables
2024-09-12 23:40:11 +07:00
68de3a71b9 if admin session time error, it logout admin out whether admin are online or not, once time reach it logout you out 2024-09-12 11:39:45 +01:00
fe532a6238 change the time calculation logic 2024-09-11 16:52:34 +01:00
05b681df47 change current_time to current_date 2024-09-11 14:38:54 +01:00
723e99ebed fix panel headers in css, add cron job monitor to check if cron is running, has run, or not setup 2024-09-11 14:19:13 +01:00
13aabeea8e add styles to some tables 2024-09-09 20:45:13 +01:00
400672454c Merge pull request #297 from Focuslinkstech/Development
add htmlspecialchars_decode
2024-09-09 22:11:32 +07:00
aa3e522bc9 add htmlspecialchars_decode 2024-09-09 13:18:10 +01:00
b7fce955ac chenge _post to _get 2024-09-09 14:01:55 +07:00
594fab1151 fix toggle dark mode admin area 2024-09-09 13:55:39 +07:00
3aead7a98a Fix toggle dark mode 2024-09-09 13:23:26 +07:00
9a1321f597 Merge branch 'master' into Development 2024-09-09 10:47:14 +07:00
45bb3f04ee add back button on inbox 2024-09-09 10:26:28 +07:00
0d2b140bcf Fix Balance sent/received 2024-09-09 10:23:51 +07:00
f736868819 update remote-address instead local-address 2024-09-09 09:44:28 +07:00
84bc680f40 Change Local-Ip PPPoE to Remote-IP
Change Local-Ip PPPoE to Remote-IP in customer page and ignor attribute if empty
2024-09-09 07:19:48 +07:00
53512cfa58 Merge pull request #295 from Focuslinkstech/Development
more css style added
2024-09-08 17:50:50 +07:00
d0e66d8651 more css style added 2024-09-08 09:50:54 +01:00
c45ea74a29 Merge pull request #294 from Focuslinkstech/Development
fix pending transaction in customer dashboard
2024-09-07 18:29:40 +07:00
22615eb278 fix pending transaction in customer dashboard 2024-09-07 12:25:21 +01:00
ae550df078 Merge pull request #293 from Focuslinkstech/Development
We added bold to sidebar, we want your opinion if it looks good or we…
2024-09-06 20:16:27 +07:00
cc74667451 fix customer dashboard table list, , Package Bandwidth and Recharge Button 2024-09-06 13:56:33 +01:00
0fec69df89 We added bold to sidebar, we want your opinion if it looks good or we should remove it.
Please if you dont like the bold feature kindly report it
2024-09-06 13:19:23 +01:00
36b4a282ee Merge pull request #292 from hotspotbilling/revert-291-master
Revert "Just Fixes"
2024-09-06 18:00:50 +07:00
cb5ffb4236 Revert "Just Fixes" 2024-09-06 18:00:33 +07:00
2328489a14 Merge pull request #291 from Focuslinkstech/master 2024-09-06 17:56:44 +07:00
f8351e46f6 Merge pull request #290 from Focuslinkstech/Development 2024-09-06 17:56:17 +07:00
cf9a11ef94 add margin to the reports table list 2024-09-06 11:48:20 +01:00
80d4c904d0 Merge branch 'hotspotbilling:Development' into Development 2024-09-06 11:44:40 +01:00
9e63d7ce20 add margin to the table list 2024-09-06 11:36:30 +01:00
b517ef2b73 fix ignore list 2024-09-06 11:00:09 +07:00
ea0c663278 MikrotikPppoeCustom for PPPOE Custom Username, IP and Password 2024-09-06 10:59:26 +07:00
299fd90949 fix dark mode drop-down menu 2024-09-05 16:13:14 +01:00
c255a7be49 fix modal in dark mode 2024-09-05 15:47:28 +01:00
4f74aa0bff Please if you dont like the bold feature kindly report it 2024-09-05 14:14:50 +01:00
93d53cc6d8 We added bold to sidebar, we want your opinion if it looks good or we should remove it 2024-09-05 13:59:19 +01:00
40f452cff8 just little bold 2024-09-05 13:46:07 +01:00
aa787af017 fix bandwidth name not displayed on active packages on customer dashboard 2024-09-05 10:36:28 +01:00
af753d3a52 remove excess div 2024-09-05 10:07:22 +01:00
d060e21032 Added to Balance list 2024-09-05 10:05:23 +01:00
f43a2dcd7a Merge branch 'hotspotbilling:Development' into Development 2024-09-05 09:51:21 +01:00
0b5bc1fe71 Add margin to tables list in customer dashboard to give it unique view 2024-09-05 09:48:21 +01:00
4f969c787a Merge pull request #289 from Focuslinkstech/Development
Fix disabled on table list in dark mode
2024-09-05 14:13:40 +07:00
087ce1d0b6 Shift table down to fit the container 2024-09-05 08:11:17 +01:00
d7d76dde94 Fix disabled on table list in dark mode 2024-09-05 07:51:15 +01:00
7cbb874029 Merge pull request #288 from Focuslinkstech/Development
Development
2024-09-04 16:46:01 +07:00
44fe6a0ff7 fix list group item 2024-09-04 09:24:42 +01:00
74ec6128ab fixed box header color 2024-09-04 09:17:22 +01:00
899c27f1a7 Add Dark Mode to Customer Dashboard 2024-09-04 08:57:53 +01:00
f7a86666c2 Merge pull request #287 from Focuslinkstech/Development
Dark Mode Journey Has Started
2024-09-04 09:54:47 +07:00
a22e2e1b2c Dark Mode Journey Has Started 2024-09-03 17:43:39 +01:00
cc6babf04a Merge pull request #286 from Focuslinkstech/Development
fix WhatsApp welcome message not sending
2024-09-01 12:01:31 +07:00
9e7698f83d fix WhatsApp welcome message not sending 2024-08-31 12:37:20 +01:00
e1008ae61f move error customer to folder user-ui 2024-08-30 14:02:23 +07:00
ef0d19393f forgot to check enabled router when showing offline router 2024-08-30 13:53:13 +07:00
8e2a40d670 Error page for customer 2024-08-30 11:44:57 +07:00
34482ff0d4 move register button to right 2024-08-30 11:22:18 +07:00
35ace619d2 Merge pull request #283 from ahmadhusein17/Development
Development
2024-08-29 23:39:07 +07:00
6da6041cb8 Update admin-add.tpl 2024-08-29 23:33:43 +07:00
463ee27116 Update sendPlan.tpl 2024-08-29 19:03:57 +07:00
fe930a1012 Update selectGateway.tpl 2024-08-29 19:01:28 +07:00
abd22418ce Update profile.tpl 2024-08-29 18:55:21 +07:00
9099584d7c Update orderView.tpl 2024-08-29 18:46:21 +07:00
5a84e10b70 Update orderHistory.tpl 2024-08-29 18:30:55 +07:00
89786c64af Update orderBalance.tpl 2024-08-29 18:25:29 +07:00
e7ec68872b Update header.tpl 2024-08-29 18:17:53 +07:00
82d756567e add Registration code sms verification code to Language, some carrier block text Verification 2024-08-29 15:33:49 +07:00
9436a6e8f1 remove active when extend 2024-08-29 11:23:57 +07:00
e8885e91ec Merge pull request #282 from ahmadhusein17/Development
Development
2024-08-29 09:04:37 +07:00
8e96f1457a Update page-edit.tpl 2024-08-28 22:55:30 +07:00
2a2e9c3bd4 Update message.tpl 2024-08-28 22:53:55 +07:00
b8e856518f Update message.tpl 2024-08-28 22:36:11 +07:00
4e19894152 Update invoice.tpl 2024-08-28 22:34:25 +07:00
414f9929e5 Update invoice-print.tpl 2024-08-28 22:33:23 +07:00
5bcf278733 Update deposit.tpl 2024-08-28 22:30:08 +07:00
453ea564cf Update dbstatus.tpl 2024-08-28 22:29:12 +07:00
a167d97e3c Update dashboard.tpl 2024-08-28 22:25:25 +07:00
55349b40dd Update bandwidth.tpl 2024-08-28 22:15:01 +07:00
bac8819c31 Update bandwidth-edit.tpl 2024-08-28 22:14:06 +07:00
aaeaa9305a Update bandwidth-add.tpl 2024-08-28 22:13:24 +07:00
f37987a4c6 Update balance.tpl 2024-08-28 22:12:19 +07:00
393939fab4 Update balance-edit.tpl 2024-08-28 22:10:51 +07:00
0189234b56 Update app-settings.tpl 2024-08-28 22:08:33 +07:00
8d4d88f702 Update app-localisation.tpl 2024-08-28 21:46:15 +07:00
5d79cb65ce Update admin-edit.tpl 2024-08-28 21:42:45 +07:00
1d08e8d204 Update admin-add.tpl 2024-08-28 21:38:56 +07:00
afaafd6195 remove debug 2024-08-28 14:50:26 +07:00
bd9989eaf2 hide when no router offline 2024-08-28 14:39:19 +07:00
352 changed files with 25492 additions and 27249 deletions

9
.gitignore vendored
View File

@ -46,10 +46,17 @@ system/devices/**
!system/devices/Radius.php
!system/devices/RadiusRest.php
!system/devices/MikrotikPppoe.php
!system/devices/MikrotikPppoeCustom.php
!system/devices/MikrotikHotspot.php
/.vs
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

@ -13,6 +13,11 @@
Allow from all
</Files>
<Files radius.php>
Order Allow,Deny
Allow from all
</Files>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f

View File

@ -2,6 +2,54 @@
# CHANGELOG
## 2024.10.23
- Custom Balance admin refill Requested by Javi Tech
- Only Admin can edit Customer Requested by Fiberwan
- Only Admin can show password Requested by Fiberwan
## 2024.10.18
- Single Session Admin Can be set in the Settings
- Auto expired unpaid transaction
- Registration Type
- Can Login as User from Customer View
- Can select customer register must using OTP or not
- Add Meta.php for additional information
## 2024.10.15
- CSRF Security
- Admin can only have 1 active session
- Move Miscellaneous Settings to new page
- Fix Customer Online
- Count Shared user online for Radius REST
- Fix Invoice Print
## 2024.10.7
- Show Customer is Online or not
- Change Invoice Theme for printing
- Rearange Customer View
## 2024.9.23
- Discount Price
- Burst Preset
## 2024.9.20
- Forgot Password
- Forgot Username
- Public header template
## 2024.9.13
- Add Selling Mikrotik VPN By @agstrxyz
- Theme Redesign by @Focuslinkstech
- Fix That and this
## 2024.8.28
- add Router Status Offline/Online by @Focuslinkstech

View File

@ -36,7 +36,7 @@ Most current web servers with PHP & MySQL installed will be capable of running P
Minimum Requirements
- Linux or Windows OS
- Minimum PHP Version 7.4
- Minimum PHP Version 8.2
- Both PDO & MySQLi Support
- PHP-GD2 Image Library
- PHP-CURL
@ -83,27 +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
- [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

View File

@ -5,4 +5,4 @@
**/
header('location: ../index.php?_route=admin/');
header('location: ../?_route=admin/');

View File

@ -1,10 +1,15 @@
<?php
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$host = $_SERVER['HTTP_HOST'];
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ||
(isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443)) ? "https://" : "http://";
// Check if HTTP_HOST is set, otherwise use a default value or SERVER_NAME
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
$baseDir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
define('APP_URL', $protocol . $host . $baseDir);
$_app_stage = 'Live'; # Do not change this
$db_host = "localhost"; # Database Host

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,12 +42,13 @@ spl_autoload_register('_autoloader');
if (!file_exists($root_path . 'config.php')) {
$root_path .= '..' . DIRECTORY_SEPARATOR;
if (!file_exists($root_path . 'config.php')) {
r2('install');
r2('./install');
}
}
if (!file_exists($root_path . File::pathFixer('system/orm.php'))) {
die($root_path . "orm.php file not found");
echo $root_path . "orm.php file not found";
die();
}
$DEVICE_PATH = $root_path . File::pathFixer('system/devices');
@ -55,11 +56,13 @@ $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';
if (!file_exists($UPLOAD_PATH . File::pathFixer('/notifications.default.json'))) {
die($UPLOAD_PATH . File::pathFixer("/notifications.default.json file not found"));
echo $UPLOAD_PATH . File::pathFixer("/notifications.default.json file not found");
die();
}
require_once $root_path . 'config.php';
@ -67,11 +70,11 @@ require_once $root_path . File::pathFixer('system/orm.php');
require_once $root_path . File::pathFixer('system/autoload/PEAR2/Autoload.php');
include $root_path . File::pathFixer('system/autoload/Hookers.php');
if($db_password != null && ($db_pass == null || empty($db_pass))){
if ($db_password != null && ($db_pass == null || empty($db_pass))) {
// compability for old version
$db_pass = $db_password;
}
if($db_pass != null){
if ($db_pass != null) {
// compability for old version
$db_password = $db_pass;
}
@ -85,7 +88,7 @@ if ($_app_stage != 'Live') {
if ($isApi) {
define('U', APP_URL . '/system/api.php?r=');
} else {
define('U', APP_URL . '/index.php?_route=');
define('U', APP_URL . '/?_route=');
}
// notification message
@ -109,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'];
@ -119,7 +140,7 @@ if (empty($http_proxy) && !empty($config['http_proxy'])) {
date_default_timezone_set($config['timezone']);
if ((!empty($radius_user) && $config['radius_enable']) || _post('radius_enable')) {
if(!empty($radius_password)){
if (!empty($radius_password)) {
// compability for old version
$radius_pass = $radius_password;
}
@ -134,32 +155,25 @@ if ((!empty($radius_user) && $config['radius_enable']) || _post('radius_enable')
// Check if the user has selected a language
if (!empty($_SESSION['user_language'])) {
$config['language'] = $_SESSION['user_language'];
}else if (!empty($_COOKIE['user_language'])) {
} else if (!empty($_COOKIE['user_language'])) {
$config['language'] = $_COOKIE['user_language'];
}else if(User::getID()>0){
} else if (User::getID() > 0) {
$lang = User::getAttribute("Language");
if(!empty($lang)){
if (!empty($lang)) {
$config['language'] = $lang;
}
}
if (empty($_SESSION['Lang'])) {
if (empty($config['language'])) {
$config['language'] = 'english';
}
$lan_file = $root_path . File::pathFixer('system/lan/' . $config['language'] . '.json');
if (file_exists($lan_file)) {
$_L = json_decode(file_get_contents($lan_file), true);
$_SESSION['Lang'] = $_L;
} else {
$_L['author'] = 'Auto Generated by iBNuX Script';
$_SESSION['Lang'] = $_L;
file_put_contents($lan_file, json_encode($_L));
}
} else {
$_L = $_SESSION['Lang'];
if (empty($config['language'])) {
$config['language'] = 'english';
}
$lan_file = $root_path . File::pathFixer('system/lan/' . $config['language'] . '.json');
if (file_exists($lan_file)) {
$_L = json_decode(file_get_contents($lan_file), true);
} else {
$_L['author'] = 'Auto Generated by PHPNuxBill Script';
file_put_contents($lan_file, json_encode($_L));
}
function safedata($value)
{
@ -201,7 +215,7 @@ function _auth($login = true)
return true;
} else {
if ($login) {
r2(U . 'login');
r2(getUrl('login'));
} else {
return false;
}
@ -214,7 +228,7 @@ function _admin($login = true)
return true;
} else {
if ($login) {
r2(U . 'login');
r2(getUrl('login'));
} else {
return false;
}
@ -238,8 +252,12 @@ function _log($description, $type = '', $userid = '0')
} elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) //to check ip from share internet
{
$d->ip = $_SERVER['HTTP_CLIENT_IP'];
} else {
} else if (isset($_SERVER["REMOTE_ADDR"])) {
$d->ip = $_SERVER["REMOTE_ADDR"];
} else if (php_sapi_name() == 'cli') {
$d->ip = 'CLI';
} else {
$d->ip = 'Unknown';
}
$d->save();
}
@ -262,6 +280,13 @@ function showResult($success, $message = '', $result = [], $meta = [])
die();
}
/**
* make url canonical or standar
*/
function getUrl($url)
{
return Text::url($url);
}
function generateUniqueNumericVouchers($totalVouchers, $length = 8)
{
@ -334,16 +359,16 @@ function _alert($text, $type = 'success', $url = "home", $time = 3)
if (!isset($ui)) return;
if (strlen($url) > 4) {
if (substr($url, 0, 4) != "http") {
$url = U . $url;
$url = getUrl($url);
}
} else {
$url = U . $url;
$url = getUrl($url);
}
$ui->assign('text', $text);
$ui->assign('type', $type);
$ui->assign('time', $time);
$ui->assign('url', $url);
$ui->display('alert.tpl');
$ui->display('admin/alert.tpl');
die();
}
@ -356,12 +381,12 @@ function displayMaintenanceMessage(): void
{
global $config, $ui;
$date = $config['maintenance_date'];
if ($date){
if ($date) {
$ui->assign('date', $date);
}
http_response_code(503);
$ui->assign('companyName', $config['CompanyName']);
$ui->display('maintenance.tpl');
$ui->display('admin/maintenance.tpl');
die();
}
@ -374,4 +399,3 @@ function isTableExist($table)
return false;
}
}

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

@ -27,6 +27,7 @@ CREATE TABLE `tbl_customers` (
`id` int NOT NULL,
`username` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`password` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`photo` VARCHAR(128) NOT NULL DEFAULT '/user.default.jpg',
`pppoe_username` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login',
`pppoe_password` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login',
`pppoe_ip` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'For PPPOE Login',
@ -71,6 +72,7 @@ DROP TABLE IF EXISTS `tbl_payment_gateway`;
CREATE TABLE `tbl_payment_gateway` (
`id` int NOT NULL,
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_id` int(11) NOT NULL DEFAULT 0,
`gateway` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'xendit | midtrans',
`gateway_trx_id` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`plan_id` int NOT NULL,
@ -149,6 +151,7 @@ CREATE TABLE `tbl_transactions` (
`id` int NOT NULL,
`invoice` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_id` int(11) NOT NULL DEFAULT 0,
`plan_name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`price` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`recharged_on` date NOT NULL,
@ -166,6 +169,7 @@ DROP TABLE IF EXISTS `tbl_users`;
CREATE TABLE `tbl_users` (
`id` int UNSIGNED NOT NULL,
`root` int NOT NULL DEFAULT '0' COMMENT 'for sub account',
`photo` VARCHAR(128) NOT NULL DEFAULT '/admin.default.png',
`username` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`fullname` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
@ -176,7 +180,9 @@ CREATE TABLE `tbl_users` (
`ward` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'kelurahan',
`user_type` enum('SuperAdmin','Admin','Report','Agent','Sales') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`status` enum('Active','Inactive') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'Active',
`data` TEXT NULL DEFAULT NULL COMMENT 'to put additional data',
`last_login` datetime DEFAULT NULL,
`login_token` VARCHAR(40),
`creationdate` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@ -207,6 +213,7 @@ CREATE TABLE `tbl_voucher` (
`code` varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`status` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`used_date` DATETIME NULL DEFAULT NULL,
`generated_by` int NOT NULL DEFAULT '0' COMMENT 'id admin'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@ -223,6 +230,7 @@ CREATE TABLE `rad_acct` (
`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 '',
`acctsessiontime` BIGINT NOT NULL DEFAULT '0',
`acctinputoctets` BIGINT NOT NULL DEFAULT '0',
`acctoutputoctets` BIGINT NOT NULL DEFAULT '0',
`acctstatustype` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
@ -243,6 +251,63 @@ 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',
`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';
CREATE TABLE IF NOT EXISTS `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
) 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`),
@ -364,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

@ -42,7 +42,7 @@ try {
$CHAPchallenge = _req('CHAPchallenge');
$isCHAP = false;
if (!empty($CHAPassword)) {
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY `username` = '$username'")->find_one();
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY username = '$username' AND status = 'Active'")->find_one();
if ($c) {
if (Password::chap_verify($c['password'], $CHAPassword, $CHAPchallenge)) {
$password = $c['password'];
@ -68,7 +68,7 @@ try {
}
}
} else {
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY `pppoe_username` = '$username'")->find_one();
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY pppoe_username = '$username' AND status = 'Active'")->find_one();
if ($c) {
if (Password::chap_verify($c['password'], $CHAPassword, $CHAPchallenge)) {
$password = $c['password'];
@ -109,9 +109,9 @@ try {
}
if ($username == $password) {
$username = Text::alphanumeric($username, "-_.,");
$d = ORM::for_table('tbl_voucher')->whereRaw("BINARY `code` = '$username'")->find_one();
$d = ORM::for_table('tbl_voucher')->whereRaw("BINARY code = '$username'")->find_one();
} else {
$d = ORM::for_table('tbl_customers')->whereRaw("BINARY `username` = '$username'")->find_one();
$d = ORM::for_table('tbl_customers')->whereRaw("BINARY username = '$username' AND status = 'Active'")->find_one();
if ($d['password'] != $password) {
if ($d['pppoe_password'] != $password) {
unset($d);
@ -136,7 +136,7 @@ try {
$CHAPchallenge = _req('CHAPchallenge');
$isCHAP = false;
if (!empty($CHAPassword)) {
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY `username` = '$username'")->find_one();
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY username = '$username' AND status = 'Active'")->find_one();
if ($c) {
if (Password::chap_verify($c['password'], $CHAPassword, $CHAPchallenge)) {
$password = $c['password'];
@ -162,14 +162,16 @@ try {
}
}
} else {
$c = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY `pppoe_username` = '$username'")->find_one();
$c = ORM::for_table('tbl_customers')->select('password')->select('username')->select('pppoe_password')->whereRaw("BINARY pppoe_username = '$username' AND status = 'Active'")->find_one();
if ($c) {
if (Password::chap_verify($c['password'], $CHAPassword, $CHAPchallenge)) {
$password = $c['password'];
$username = $c['username'];
$isVoucher = false;
$isCHAP = true;
} else if (!empty($c['pppoe_password']) && Password::chap_verify($c['pppoe_password'], $CHAPassword, $CHAPchallenge)) {
$password = $c['pppoe_password'];
$username = $c['username'];
$isVoucher = false;
$isCHAP = true;
} else {
@ -201,10 +203,18 @@ try {
], 401);
}
}
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY `username` = '$username'")->find_one();
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY username = '$username'")->find_one();
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) {
$username = $c['username'];
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY username = '$username'")->find_one();
}
}
if ($tur) {
if (!$isVoucher && !$isCHAP) {
$d = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY `username` = '$username'")->find_one();
$d = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY username = '$username' AND status = 'Active'")->find_one();
if ($d) {
if ($d['password'] != $password) {
if ($d['pppoe_password'] != $password) {
@ -212,7 +222,7 @@ try {
}
}
} else {
$d = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY `pppoe_username` = '$username'")->find_one();
$d = ORM::for_table('tbl_customers')->select('password')->select('pppoe_password')->whereRaw("BINARY pppoe_username = '$username' AND status = 'Active'")->find_one();
if ($d) {
if ($d['password'] != $password) {
if ($d['pppoe_password'] != $password) {
@ -226,14 +236,14 @@ try {
} else {
if ($isVoucher) {
$username = Text::alphanumeric($username, "-_.,");
$v = ORM::for_table('tbl_voucher')->whereRaw("BINARY `code` = '$username'")->where('routers', 'radius')->find_one();
$v = ORM::for_table('tbl_voucher')->whereRaw("BINARY code = '$username' AND routers = 'radius'")->find_one();
if ($v) {
if ($v['status'] == 0) {
if (Package::rechargeUser(0, $v['routers'], $v['id_plan'], "Voucher", $username)) {
$v->status = "1";
$v->used_date = date('Y-m-d H:i:s');
$v->save();
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY `username` = '$username'")->find_one();
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY username = '$username'")->find_one();
if ($tur) {
process_radiust_rest($tur, $code);
} else {
@ -246,7 +256,7 @@ try {
show_radius_result(['Reply-Message' => 'Voucher Expired...'], 401);
}
} else {
show_radius_result(['Reply-Message' => 'Voucher Expired..'], 401);
show_radius_result(['Reply-Message' => 'Invalid Voucher..'], 401);
}
} else {
show_radius_result(['Reply-Message' => 'Internet Plan Expired..'], 401);
@ -264,76 +274,58 @@ try {
}
header("HTTP/1.1 200 ok");
$d = ORM::for_table('rad_acct')
->whereRaw("BINARY `username` = '$username'")
->where('acctstatustype', _post('acctStatusType'))
->whereRaw("BINARY username = '$username' AND macaddr = '" . _post('macAddr') . "' AND nasid = '" . _post('nasid') . "'")
->findOne();
if (!$d) {
$d = ORM::for_table('rad_acct')->create();
}
$acctOutputOctets = _post('acctOutputOctets', 0);
$acctInputOctets = _post('acctInputOctets', 0);
if (_post('acctStatusType') == 'Stop') {
// log in the Start only
$start = ORM::for_table('rad_acct')
->whereRaw("BINARY `username` = '$username'")
->where('acctstatustype', 'Start')
->findOne();
if (!$start) {
$start = ORM::for_table('rad_acct')->create();
}
if ($acctOutputOctets !== false && $acctInputOctets !== false) {
$start->acctOutputOctets += intval($acctOutputOctets);
$start->acctInputOctets += intval($acctInputOctets);
} else {
$start->acctOutputOctets = 0;
$start->acctInputOctets = 0;
}
$start->acctsessionid = _post('acctSessionId');
$start->username = $username;
$start->realm = _post('realm');
$start->nasipaddress = _post('nasip');
$start->nasid = _post('nasid');
$start->nasportid = _post('nasPortId');
$start->nasporttype = _post('nasPortType');
$start->framedipaddress = _post('framedIPAddress');
$start->acctstatustype = _post('acctStatusType');
$start->macaddr = _post('macAddr');
$start->dateAdded = date('Y-m-d H:i:s');
$start->save();
if ($acctOutputOctets !== false && $acctInputOctets !== false) {
$d->acctOutputOctets += intval($acctOutputOctets);
$d->acctInputOctets += intval($acctInputOctets);
} else {
$d->acctOutputOctets = 0;
$d->acctInputOctets = 0;
} else {
if ($acctOutputOctets !== false && $acctInputOctets !== false) {
$d->acctOutputOctets += intval($acctOutputOctets);
$d->acctInputOctets += intval($acctInputOctets);
} else {
$d->acctOutputOctets = 0;
$d->acctInputOctets = 0;
}
}
$d->acctsessionid = _post('acctSessionId');
$d->username = $username;
$d->realm = _post('realm');
$d->nasipaddress = _post('nasip');
$d->nasipaddress = _post('nasIpAddress');
$d->acctsessiontime = intval(_post('acctSessionTime'));
$d->nasid = _post('nasid');
$d->nasportid = _post('nasPortId');
$d->nasporttype = _post('nasPortType');
$d->framedipaddress = _post('framedIPAddress');
$d->acctstatustype = _post('acctStatusType');
if (in_array(_post('acctStatusType'), ['Start', 'Stop'])) {
$d->acctstatustype = _post('acctStatusType');
}
$d->macaddr = _post('macAddr');
$d->dateAdded = date('Y-m-d H:i:s');
$d->save();
if (_post('acctStatusType') == 'Start') {
$tur = ORM::for_table('tbl_user_recharges')->whereRaw("BINARY `username` = '$username'")->where('status', 'on')->where('routers', 'radius')->find_one();
$plan = ORM::for_table('tbl_plans')->where('id', $tur['plan_id'])->find_one();
if ($plan['limit_type'] == "Data_Limit" || $plan['limit_type'] == "Both_Limit") {
$totalUsage = $d['acctOutputOctets'] + $d['acctInputOctets'];
$attrs['reply:Mikrotik-Total-Limit'] = Text::convertDataUnit($plan['data_limit'], $plan['data_unit']) - $totalUsage;
if ($attrs['reply:Mikrotik-Total-Limit'] < 0) {
$attrs['reply:Mikrotik-Total-Limit'] = 0;
show_radius_result(["control:Auth-Type" => "Accept", 'Reply-Message' => 'You have exceeded your data limit.'], 401);
// 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) {
// 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();
if ($plan['limit_type'] == "Data_Limit" || $plan['limit_type'] == "Both_Limit") {
$totalUsage = $d['acctOutputOctets'] + $d['acctInputOctets'];
$attrs['reply:Mikrotik-Total-Limit'] = Text::convertDataUnit($plan['data_limit'], $plan['data_unit']) - $totalUsage;
if ($attrs['reply:Mikrotik-Total-Limit'] < 0) {
$attrs['reply:Mikrotik-Total-Limit'] = 0;
show_radius_result(["control:Auth-Type" => "Accept", 'Reply-Message' => 'You have exceeded your data limit.'], 401);
}
}
}
process_radiust_rest($tur, 200);
}
show_radius_result([
"control:Auth-Type" => "Accept",
@ -364,6 +356,16 @@ function process_radiust_rest($tur, $code)
global $config;
$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'")
->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 ($bw['rate_down_unit'] == 'Kbps') {
$unitdown = 'K';
} else {
@ -403,7 +405,7 @@ function process_radiust_rest($tur, $code)
if ($plan['typebp'] == "Limited") {
if ($plan['limit_type'] == "Data_Limit" || $plan['limit_type'] == "Both_Limit") {
$raddact = ORM::for_table('rad_acct')->whereRaw("BINARY `username` = '$tur[username]'")->where('acctstatustype', 'Start')->find_one();
$raddact = ORM::for_table('rad_acct')->whereRaw("BINARY username = '$tur[username]'")->where('acctstatustype', 'Start')->find_one();
$totalUsage = intval($raddact['acctOutputOctets']) + intval($raddact['acctInputOctets']);
$attrs['reply:Mikrotik-Total-Limit'] = Text::convertDataUnit($plan['data_limit'], $plan['data_unit']) - $totalUsage;
if ($attrs['reply:Mikrotik-Total-Limit'] < 0) {

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

@ -11,35 +11,62 @@ class Admin
public static function getID()
{
global $db_pass, $config;
$enable_session_timeout = $config['enable_session_timeout'];
if ($enable_session_timeout) {
$timeout = 60;
if ($config['session_timeout_duration']) {
$timeout = intval($config['session_timeout_duration']);
}
$session_timeout_duration = $timeout * 60; // Convert minutes to seconds
}
global $db_pass, $config, $isApi;
if (isset($_SESSION['aid']) && isset($_SESSION['aid_expiration']) && $_SESSION['aid_expiration'] > time()) {
return $_SESSION['aid'];
} elseif ($enable_session_timeout && isset($_SESSION['aid']) && isset($_SESSION['aid_expiration']) && $_SESSION['aid_expiration'] <= time()) {
self::removeCookie();
session_destroy();
_alert(Lang::T('Session has expired. Please log in again.'), 'danger', "admin");
return 0;
$enable_session_timeout = $config['enable_session_timeout'] == 1;
$session_timeout_duration = $config['session_timeout_duration'] ? intval($config['session_timeout_duration'] * 60) : intval(60 * 60); // Convert minutes to seconds
if ($isApi) {
$enable_session_timeout = false;
}
// Check if cookie is set and valid
if ($enable_session_timeout && !empty($_SESSION['aid']) && !empty($_SESSION['aid_expiration'])) {
if ($_SESSION['aid_expiration'] > time()) {
$isValid = self::validateToken($_SESSION['aid'], $_COOKIE['aid']);
if (!$isValid) {
self::removeCookie();
_alert(Lang::T('Token has expired. Please log in again.'), 'danger', "admin");
return 0;
}
// extend timeout duration
$_SESSION['aid_expiration'] = time() + $session_timeout_duration;
return $_SESSION['aid'];
} else {
// Session expired, log out the user
self::removeCookie();
_alert(Lang::T('Session has expired. Please log in again.'), 'danger', "admin");
return 0;
}
} else if (!empty($_SESSION['aid'])) {
$isValid = self::validateToken($_SESSION['aid'], $_COOKIE['aid']);
if (!$isValid) {
self::removeCookie();
_alert(Lang::T('Token has expired. Please log in again.') . '.'.$_SESSION['aid'], 'danger', "admin");
return 0;
}
return $_SESSION['aid'];
}
// Check if the cookie is set and valid
elseif (isset($_COOKIE['aid'])) {
// id.time.sha1
$tmp = explode('.', $_COOKIE['aid']);
if (sha1($tmp[0] . '.' . $tmp[1] . '.' . $db_pass) == $tmp[2]) {
if (time() - $tmp[1] < 86400 * 7) {
$_SESSION['aid'] = $tmp[0];
if ($enable_session_timeout) {
$_SESSION['aid_expiration'] = time() + $session_timeout_duration;
if (sha1("$tmp[0].$tmp[1].$db_pass") == $tmp[2]) {
// Validate the token in the cookie
$isValid = self::validateToken($tmp[0], $_COOKIE['aid']);
if ($isApi) {
// For now API need to always return true, next need to add revoke token API
$isValid = true;
}
if (!empty($_COOKIE['aid']) && !$isValid) {
self::removeCookie();
_alert(Lang::T('Token has expired. Please log in again.') . '..', 'danger', "admin");
return 0;
} else {
if (time() - $tmp[1] < 86400 * 7) {
$_SESSION['aid'] = $tmp[0];
if ($enable_session_timeout) {
$_SESSION['aid_expiration'] = time() + $session_timeout_duration;
}
return $tmp[0];
}
return $tmp[0];
}
}
}
@ -51,28 +78,55 @@ class Admin
{
global $db_pass, $config;
$enable_session_timeout = $config['enable_session_timeout'];
$session_timeout_duration = intval($config['session_timeout_duration']) * 60; // Convert minutes to seconds
if (isset($aid)) {
$time = time();
$token = $aid . '.' . $time . '.' . sha1($aid . '.' . $time . '.' . $db_pass);
setcookie('aid', $token, time() + 86400 * 7);
$token = $aid . '.' . $time . '.' . sha1("$aid.$time.$db_pass");
// Detect the current protocol
$isSecure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
// Set cookie with security flags
setcookie('aid', $token, [
'expires' => time() + 86400 * 7, // 7 days
'path' => '/',
'domain' => '',
'secure' => $isSecure,
'httponly' => true,
'samesite' => 'Lax', // or Strict
]);
$_SESSION['aid'] = $aid;
if ($enable_session_timeout) {
$timeout = 60;
if ($config['session_timeout_duration']) {
$timeout = intval($config['session_timeout_duration']);
}
$session_timeout_duration = $timeout * 60; // Convert minutes to seconds
$_SESSION['aid_expiration'] = $time + $session_timeout_duration;
}
self::upsertToken($aid, $token);
return $token;
}
return '';
}
public static function removeCookie()
{
global $_app_stage;
if (isset($_COOKIE['aid'])) {
setcookie('aid', '', time() - 86400);
$isSecure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
setcookie('aid', '', [
'expires' => time() - 3600,
'path' => '/',
'domain' => '',
'secure' => $isSecure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_destroy();
session_unset();
session_start();
unset($_COOKIE['aid'], $_SESSION['aid']);
}
}
@ -87,4 +141,24 @@ class Admin
return null;
}
}
public static function upsertToken($aid, $token)
{
$query = ORM::for_table('tbl_users')->findOne($aid);
$query->login_token = sha1($token);
$query->save();
}
public static function validateToken($aid, $cookieToken)
{
global $config;
$query = ORM::for_table('tbl_users')->select('login_token')->findOne($aid);
if ($config['single_session'] != 'yes') {
return true; // For multi-session, any token is valid
}
if (empty($query)) {
return true;
}
return $query->login_token === sha1($cookieToken);
}
}

View File

@ -19,6 +19,9 @@ class App{
}
public static function getTokenValue($key){
if(empty($key)){
return "";
}
if(isset($_SESSION[$key])){
return $_SESSION[$key];
}else{
@ -26,4 +29,23 @@ class App{
}
}
}
public static function getVoucher(){
return md5(microtime());
}
public static function setVoucher($token, $value){
$_SESSION[$token] = $value;
}
public static function getVoucherValue($key){
if(empty($key)){
return "";
}
if(isset($_SESSION[$key])){
return $_SESSION[$key];
}else{
return "";
}
}
}

View File

@ -30,13 +30,9 @@ class Balance
public static function min($id_customer, $amount)
{
$c = ORM::for_table('tbl_customers')->where('id', $id_customer)->find_one();
if ($c && $c['balance'] >= $amount) {
$c->balance = $c['balance'] - $amount;
$c->save();
return true;
} else {
return false;
}
$c->balance = $c['balance'] - $amount;
$c->save();
return true;
}
public static function plusByPhone($phone_customer, $amount)

55
system/autoload/Csrf.php Normal file
View File

@ -0,0 +1,55 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
class Csrf
{
private static $tokenExpiration = 1800; // 30 minutes
public static function generateToken($length = 16)
{
return bin2hex(random_bytes($length));
}
public static function validateToken($token, $storedToken)
{
return hash_equals($token, $storedToken);
}
public static function check($token)
{
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'];
if (time() - $tokenTime > self::$tokenExpiration) {
self::clearToken();
return false;
}
return self::validateToken($token, $storedToken);
}
return false;
}
return true;
}
public static function generateAndStoreToken()
{
$token = self::generateToken();
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_time'] = time();
return $token;
}
public static function clearToken()
{
unset($_SESSION['csrf_token'], $_SESSION['csrf_token_time']);
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
@ -86,13 +87,55 @@ 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);
return file_exists($dst_dir);
}
public static function makeThumb($srcFile, $thumbFile, $thumbSize = 200)
{
/* Determine the File Type */
$type = substr($srcFile, strrpos($srcFile, '.') + 1);
$imgsize = getimagesize($srcFile);
$oldW = $imgsize[0];
$oldH = $imgsize[1];
$mime = $imgsize['mime'];
switch ($mime) {
case 'image/gif':
$src = imagecreatefromgif($srcFile);
break;
case 'image/png':
$src = imagecreatefrompng($srcFile);
break;
case 'image/jpeg':
$src = imagecreatefromjpeg($srcFile);
break;
default:
return false;
break;
}
/* Calculate the New Image Dimensions */
$limiting_dim = 0;
if ($oldH > $oldW) {
/* Portrait */
$limiting_dim = $oldW;
} else {
/* Landscape */
$limiting_dim = $oldH;
}
/* Create the New Image */
$new = imagecreatetruecolor($thumbSize, $thumbSize);
/* Transcribe the Source Image into the New (Square) Image */
imagecopyresampled($new, $src, 0, 0, ($oldW - $limiting_dim) / 2, ($oldH - $limiting_dim) / 2, $thumbSize, $thumbSize, $limiting_dim, $limiting_dim);
imagejpeg($new, $thumbFile, 100);
imagedestroy($new);
return file_exists($thumbFile);
}
/**
* file path fixer

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

@ -10,15 +10,8 @@ class Lang
{
public static function T($key)
{
global $_L, $lan_file, $root_path, $config;
global $_L, $lan_file, $config;
if (empty($lan_file)) {
$lan_file = $root_path . File::pathFixer('system/lan/' . $config['language'] . '.json');
}
if (is_array($_SESSION['Lang'])) {
$_L = array_merge($_L, $_SESSION['Lang']);
}
$key = preg_replace('/\s+/', ' ', $key);
if (!empty($_L[$key])) {
return $_L[$key];
@ -34,15 +27,14 @@ class Lang
if (empty($iso)) {
return $val;
}
if (!empty($iso) && !empty($val)) {
if (!empty($iso) && !empty($val) && $iso != 'en') {
$temp = Lang::translate($val, $iso);
if (!empty($temp)) {
$val = $temp;
}
}
$_L[$key] = $val;
$_SESSION['Lang'][$key] = $val;
file_put_contents($lan_file, json_encode($_SESSION['Lang'], JSON_PRETTY_PRINT));
file_put_contents($lan_file, json_encode($_L, JSON_PRETTY_PRINT));
return $val;
}
}

View File

@ -17,12 +17,18 @@ require $root_path . 'system/autoload/mail/SMTP.php';
class Message
{
public static function sendTelegram($txt)
public static function sendTelegram($txt, $chat_id = null, $topik = '')
{
global $config;
run_hook('send_telegram', [$txt]); #HOOK
if (!empty($config['telegram_bot']) && !empty($config['telegram_target_id'])) {
return Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?chat_id=' . $config['telegram_target_id'] . '&text=' . urlencode($txt));
run_hook('send_telegram', [$txt, $chat_id, $topik]); #HOOK
if (!empty($config['telegram_bot'])) {
if (empty($chat_id)) {
$chat_id = $config['telegram_target_id'];
}
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));
}
}
@ -40,31 +46,39 @@ 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::sendSMS($config['sms_url'], $phone, $txt);
} catch (Exception $e) {
self::MikrotikSendSMS($config['sms_url'], $phone, $txt);
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());
}
}
}
}
public static function MikrotikSendSMS($router_name, $to, $message)
{
global $_app_stage, $client_m;
global $_app_stage, $client_m, $config;
if ($_app_stage == 'demo') {
return null;
}
@ -73,7 +87,10 @@ class Message
$iport = explode(":", $mikrotik['ip_address']);
$client_m = new RouterOS\Client($iport[0], $mikrotik['username'], $mikrotik['password'], ($iport[1]) ? $iport[1] : null);
}
$smsRequest = new RouterOS\Request('/tool sms send');
if (empty($config['mikrotik_sms_command'])) {
$config['mikrotik_sms_command'] = "/tool sms send";
}
$smsRequest = new RouterOS\Request($config['mikrotik_sms_command']);
$smsRequest
->setArgument('phone-number', $to)
->setArgument('message', $message);
@ -86,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)) {
@ -113,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']);
}
@ -134,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')) {
@ -148,12 +180,20 @@ 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()) {
$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;
}
$mail->send();
//<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">
}
@ -170,41 +210,86 @@ class Message
$msg = str_replace('[[plan]]', $package, $msg);
$msg = str_replace('[[package]]', $package, $msg);
$msg = str_replace('[[price]]', Lang::moneyFormat($price), $msg);
// Calculate bills and additional costs
list($bills, $add_cost) = User::getBills($customer['id']);
if ($add_cost > 0) {
$note = "";
// Initialize note and total variables
$note = "";
$total = $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";
}
$note .= "Total : " . Lang::moneyFormat($add_cost + $price) . "\n";
$msg = str_replace('[[bills]]', $note, $msg);
} else {
$msg = str_replace('[[bills]]', '', $msg);
$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($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
$msg = str_replace('[[bills]]', $note, $msg);
if ($ds) {
$msg = str_replace('[[expired_date]]', Lang::dateAndTimeFormat($ds['expiration'], $ds['time']), $msg);
} else {
$msg = str_replace('[[expired_date]]', "", $msg);
}
if (strpos($msg, '[[payment_link]]') !== false) {
// token only valid for 1 day, for security reason
$token = User::generateToken($customer['id'], 1);
if (!empty($token['token'])) {
$tur = ORM::for_table('tbl_user_recharges')
->where('customer_id', $customer['id'])
->where('namebp', $package)
->find_one();
if ($tur) {
$url = '?_route=home&recharge=' . $tur['id'] . '&uid=' . urlencode($token['token']);
$msg = str_replace('[[payment_link]]', $url, $msg);
}
} else {
$msg = str_replace('[[payment_link]]', '', $msg);
}
}
if (
!empty($customer['phonenumber']) && strlen($customer['phonenumber']) > 5
&& !empty($message) && in_array($via, ['sms', 'wa'])
) {
if ($via == 'sms') {
echo Message::sendSMS($customer['phonenumber'], $msg);
Message::sendSMS($customer['phonenumber'], $msg);
} else if ($via == 'email') {
self::sendEmail($customer['email'], '[' . $config['CompanyName'] . '] ' . Lang::T("Internet Plan Reminder"), $msg);
} else if ($via == 'wa') {
echo Message::sendWhatsapp($customer['phonenumber'], $msg);
Message::sendWhatsapp($customer['phonenumber'], $msg);
}
}
return "$via: $msg";
}
public static function sendBalanceNotification($cust, $balance, $balance_now, $message, $via)
public static function sendBalanceNotification($cust, $target, $balance, $balance_now, $message, $via)
{
global $config;
$msg = str_replace('[[name]]', $cust['fullname'] . ' (' . $cust['username'] . ')', $message);
$msg = str_replace('[[name]]', $target['fullname'] . ' (' . $target['username'] . ')', $message);
$msg = str_replace('[[current_balance]]', Lang::moneyFormat($balance_now), $msg);
$msg = str_replace('[[balance]]', Lang::moneyFormat($balance), $msg);
$phone = $cust['phonenumber'];
@ -214,24 +299,26 @@ class Message
) {
if ($via == 'sms') {
Message::sendSMS($phone, $msg);
} else if ($config['user_notification_payment'] == 'email') {
} else if ($via == 'email') {
self::sendEmail($cust['email'], '[' . $config['CompanyName'] . '] ' . Lang::T("Balance Notification"), $msg);
} else if ($via == 'wa') {
Message::sendWhatsapp($phone, $msg);
}
self::addToInbox($cust['id'], Lang::T('Balance Notification'), $msg);
}
return "$via: $msg";
}
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);
$textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $trx['invoice'], $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateAndTimeFormat($trx['recharged_on'], $trx['recharged_time']), $textInvoice);
$textInvoice = str_replace('[[trx_date]]', Lang::dateAndTimeFormat($trx['recharged_on'], $trx['recharged_time']), $textInvoice);
if (!empty($trx['note'])) {
$textInvoice = str_replace('[[note]]', $trx['note'], $textInvoice);
}
@ -240,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);
@ -250,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') {
@ -258,4 +385,49 @@ class Message
Message::sendWhatsapp($cust['phonenumber'], $textInvoice);
}
}
public static function addToInbox($to_customer_id, $subject, $body, $from = 'System')
{
$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();
}
}

118
system/autoload/Meta.php Normal file
View File

@ -0,0 +1,118 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
*
* Meta is for save adtional information in database for every existing table
*
* * Good for Plugin, so plugin don't need to modify existing tables
*
* Example to put data
* if plan need to add point value for loyalty poin
* Meta::for("tbl_plans")->set(1, 'point', '24');
* it means tbl_plans with id 1 have point value 24, customer will get 24 point for loyalty if buy plan with id 1
* You need to create the logic for that, Meta only hold the data
*
* Example to get data
* $point = Meta::for("tbl_plans")->get(1, 'point');
* this will return the point value of plan with id 1
*
* to get all key value
* $metas = Meta::for("tbl_plans")->getAll(1);
*
* to delete 1 data
* Meta::for("tbl_plans")->delete(1, 'point');
*
* to delete all data
* Meta::for("tbl_plans")->deleteAll(1);
**/
class Meta
{
protected $table = '';
protected function __construct($table)
{
$this->table = $table;
}
public static function for($table)
{
return new self($table);
}
public function get($id, $key)
{
// get the Value
return ORM::for_table('tbl_meta')
->select('value')
->where('tbl', $this->table)
->where('tbl_id', $id)
->where('name', $key)
->find_one()['value'];
}
public function getAll($id)
{
//get all key Value
$metas = [];
$result = ORM::for_table('tbl_meta')
->select('name')
->select('value')
->where('tbl', $this->table)
->where('tbl_id', $id)
->find_array();
foreach ($result as $value) {
$metas[$value['name']] = $value['value'];
}
return $metas;
}
public function set($id, $key, $value = '')
{
$meta = ORM::for_table('tbl_meta')
->where('tbl', $this->table)
->where('tbl_id', $id)
->where('name', $key)
->find_one();
if (!$meta) {
$meta = ORM::for_table('tbl_meta')->create();
$meta->tbl = $this->table;
$meta->tbl_id = $id;
$meta->name = $key;
$meta->value = $value;
$meta->save();
$result = $meta->id();
if ($result) {
return $result;
}
} else {
$meta->value = $value;
$meta->save();
return $meta['id'];
}
}
public function delete($id, $key = '')
{
// get the Value
return ORM::for_table('tbl_meta')
->select('value')
->where('tbl', $this->table)
->where('tbl_id', $id)
->where('name', $key)
->delete();
}
public function deleteAll($id)
{
//get all key Value
return ORM::for_table('tbl_meta')
->select('value')
->where('tbl', $this->table)
->where('tbl_id', $id)
->delete_many();
}
}

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

@ -21,7 +21,6 @@ class Package
public static function rechargeUser($id_customer, $router_name, $plan_id, $gateway, $channel, $note = '')
{
global $config, $admin, $c, $p, $b, $t, $d, $zero, $trx, $_app_stage, $isChangePlan;
$date_now = date("Y-m-d H:i:s");
$date_only = date("Y-m-d");
$time_only = date("H:i:s");
$time = date("H:i:s");
@ -64,7 +63,7 @@ class Package
} else {
// Additional cost
list($bills, $add_cost) = User::getBills($id_customer);
if ($add_cost > 0 && $router_name != 'balance') {
if ($add_cost != 0 && $router_name != 'balance') {
foreach ($bills as $k => $v) {
$note .= $k . " : " . Lang::moneyFormat($v) . "\n";
}
@ -75,10 +74,10 @@ class Package
if (!$p['enabled']) {
if (!isset($admin) || !isset($admin['id']) || empty($admin['id'])) {
r2(U . 'home', 'e', Lang::T('Plan Not found'));
r2(getUrl('home'), 'e', Lang::T('Plan Not found'));
}
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
r2(U . 'dashboard', 'e', Lang::T('Plan Not found'));
r2(getUrl('dashboard'), 'e', Lang::T('You do not have permission to access this page'));
}
}
@ -100,57 +99,11 @@ class Package
if ($router_name == 'balance') {
// insert table transactions
$t = ORM::for_table('tbl_transactions')->create();
$t->invoice = $inv = "INV-" . Package::_raid();
$t->username = $c['username'];
$t->plan_name = $p['name_plan'];
$t->price = $p['price'];
$t->recharged_on = $date_only;
$t->recharged_time = date("H:i:s");
$t->expiration = $date_only;
$t->time = $time;
$t->method = "$gateway - $channel";
$t->routers = $router_name;
$t->type = "Balance";
if ($admin) {
$t->admin_id = ($admin['id']) ? $admin['id'] : '0';
} else {
$t->admin_id = '0';
}
$t->save();
return self::rechargeBalance($c, $p, $gateway, $channel);
}
$balance_before = $c['balance'];
Balance::plus($id_customer, $p['price']);
$balance = $c['balance'] + $p['price'];
$textInvoice = Lang::getNotifText('invoice_balance');
$textInvoice = str_replace('[[company_name]]', $config['CompanyName'], $textInvoice);
$textInvoice = str_replace('[[address]]', $config['address'], $textInvoice);
$textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $inv, $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateTimeFormat($date_now), $textInvoice);
$textInvoice = str_replace('[[payment_gateway]]', $gateway, $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', $channel, $textInvoice);
$textInvoice = str_replace('[[type]]', 'Balance', $textInvoice);
$textInvoice = str_replace('[[plan_name]]', $p['name_plan'], $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($p['price']), $textInvoice);
$textInvoice = str_replace('[[name]]', $c['fullname'], $textInvoice);
$textInvoice = str_replace('[[user_name]]', $c['username'], $textInvoice);
$textInvoice = str_replace('[[user_password]]', $c['password'], $textInvoice);
$textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
$textInvoice = str_replace('[[balance_before]]', Lang::moneyFormat($balance_before), $textInvoice);
$textInvoice = str_replace('[[balance]]', Lang::moneyFormat($balance), $textInvoice);
if ($config['user_notification_payment'] == 'sms') {
Message::sendSMS($c['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'wa') {
Message::sendWhatsapp($c['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'email') {
Message::sendEmail($c['email'], '[' . $config['CompanyName'] . '] ' . Lang::T("Invoice") . ' ' . $inv, $textInvoice);
}
return true;
if ($router_name == 'Custom Balance') {
return self::rechargeCustomBalance($c, $p, $gateway, $channel);
}
/**
@ -191,17 +144,40 @@ class Package
if ($p['validity_unit'] == 'Months') {
$date_exp = date("Y-m-d", strtotime('+' . $p['validity'] . ' month'));
} else if ($p['validity_unit'] == 'Period') {
$date_tmp = date("Y-m-$day_exp", strtotime('+' . $p['validity'] . ' month'));
$dt1 = new DateTime("$date_only");
$dt2 = new DateTime("$date_tmp");
$diff = $dt2->diff($dt1);
$sum = $diff->format("%a"); // => 453
if ($sum >= 35 * $p['validity']) {
$date_exp = date("Y-m-$day_exp", strtotime('+0 month'));
} else {
$date_exp = date("Y-m-$day_exp", strtotime('+' . $p['validity'] . ' month'));
};
$time = date("23:59:00");
$current_date = new DateTime($date_only);
$exp_date = clone $current_date;
$exp_date->modify('first day of next month');
$exp_date->setDate($exp_date->format('Y'), $exp_date->format('m'), $day_exp);
$min_days = 7 * $p['validity'];
$max_days = 35 * $p['validity'];
$days_until_exp = $exp_date->diff($current_date)->days;
// If less than min_days away, move to the next period
while ($days_until_exp < $min_days) {
$exp_date->modify('+1 month');
$days_until_exp = $exp_date->diff($current_date)->days;
}
// If more than max_days away, move to the previous period
while ($days_until_exp > $max_days) {
$exp_date->modify('-1 month');
$days_until_exp = $exp_date->diff($current_date)->days;
}
// Final check to ensure we're not less than min_days or in the past
if ($days_until_exp < $min_days || $exp_date <= $current_date) {
$exp_date->modify('+1 month');
}
// Adjust for multiple periods
if ($p['validity'] > 1) {
$exp_date->modify('+' . ($p['validity'] - 1) . ' months');
}
$date_exp = $exp_date->format('Y-m-d');
$time = "23:59:59";
} else if ($p['validity_unit'] == 'Days') {
$datetime = explode(' ', date("Y-m-d H:i:s", strtotime('+' . $p['validity'] . ' day')));
$date_exp = $datetime[0];
@ -219,35 +195,39 @@ class Package
if ($b) {
$lastExpired = Lang::dateAndTimeFormat($b['expiration'], $b['time']);
$isChangePlan = false;
if ($config['extend_expiry'] != 'no') {
if ($b['namebp'] == $p['name_plan'] && $b['status'] == 'on') {
// if it same internet plan, expired will extend
if ($p['validity_unit'] == 'Months') {
if ($b['namebp'] == $p['name_plan'] && $b['status'] == 'on' && $config['extend_expiry'] == 'yes') {
// if it same internet plan, expired will extend
switch ($p['validity_unit']) {
case 'Months':
$date_exp = date("Y-m-d", strtotime($b['expiration'] . ' +' . $p['validity'] . ' months'));
$time = $b['time'];
} else if ($p['validity_unit'] == 'Period') {
break;
case 'Period':
$date_exp = date("Y-m-$day_exp", strtotime($b['expiration'] . ' +' . $p['validity'] . ' months'));
$time = date("23:59:00");
} else if ($p['validity_unit'] == 'Days') {
break;
case 'Days':
$date_exp = date("Y-m-d", strtotime($b['expiration'] . ' +' . $p['validity'] . ' days'));
$time = $b['time'];
} else if ($p['validity_unit'] == 'Hrs') {
break;
case 'Hrs':
$datetime = explode(' ', date("Y-m-d H:i:s", strtotime($b['expiration'] . ' ' . $b['time'] . ' +' . $p['validity'] . ' hours')));
$date_exp = $datetime[0];
$time = $datetime[1];
} else if ($p['validity_unit'] == 'Mins') {
break;
case 'Mins':
$datetime = explode(' ', date("Y-m-d H:i:s", strtotime($b['expiration'] . ' ' . $b['time'] . ' +' . $p['validity'] . ' minutes')));
$date_exp = $datetime[0];
$time = $datetime[1];
}
} else {
$isChangePlan = true;
break;
}
} else {
$isChangePlan = true;
}
//if ($b['status'] == 'on') {
$dvc = Package::getDevice($p);
if ($_app_stage != 'demo') {
if ($_app_stage != 'Demo') {
try {
if (file_exists($dvc)) {
require_once $dvc;
@ -257,7 +237,7 @@ class Package
}
} catch (Throwable $e) {
Message::sendTelegram(
"Sistem Error. When activate Package. You need to sync manually\n" .
"System Error. When activate Package. You need to sync manually\n" .
"Router: $router_name\n" .
"Customer: u$c[username]\n" .
"Plan: p$p[name_plan]\n" .
@ -266,7 +246,7 @@ class Package
);
} catch (Exception $e) {
Message::sendTelegram(
"Sistem Error. When activate Package. You need to sync manually\n" .
"System Error. When activate Package. You need to sync manually\n" .
"Router: $router_name\n" .
"Customer: u$c[username]\n" .
"Plan: p$p[name_plan]\n" .
@ -303,6 +283,7 @@ class Package
$t = ORM::for_table('tbl_transactions')->create();
$t->invoice = $inv = "INV-" . Package::_raid();
$t->username = $c['username'];
$t->user_id = $id_customer;
$t->plan_name = $p['name_plan'];
if ($gateway == 'Voucher' && User::isUserVoucher($channel)) {
//its already paid
@ -362,7 +343,7 @@ class Package
} else {
// active plan not exists
$dvc = Package::getDevice($p);
if ($_app_stage != 'demo') {
if ($_app_stage != 'Demo') {
try {
if (file_exists($dvc)) {
require_once $dvc;
@ -372,7 +353,7 @@ class Package
}
} catch (Throwable $e) {
Message::sendTelegram(
"Sistem Error. When activate Package. You need to sync manually\n" .
"System Error. When activate Package. You need to sync manually\n" .
"Router: $router_name\n" .
"Customer: u$c[username]\n" .
"Plan: p$p[name_plan]\n" .
@ -381,7 +362,7 @@ class Package
);
} catch (Exception $e) {
Message::sendTelegram(
"Sistem Error. When activate Package. You need to sync manually\n" .
"System Error. When activate Package. You need to sync manually\n" .
"Router: $router_name\n" .
"Customer: u$c[username]\n" .
"Plan: p$p[name_plan]\n" .
@ -418,6 +399,7 @@ class Package
$t = ORM::for_table('tbl_transactions')->create();
$t->invoice = $inv = "INV-" . Package::_raid();
$t->username = $c['username'];
$t->user_id = $id_customer;
$t->plan_name = $p['name_plan'];
if ($gateway == 'Voucher' && User::isUserVoucher($channel)) {
$t->price = 0;
@ -493,6 +475,130 @@ class Package
return $inv;
}
public static function rechargeBalance($customer, $plan, $gateway, $channel, $note = '')
{
global $admin, $config;
// insert table transactions
$t = ORM::for_table('tbl_transactions')->create();
$t->invoice = $inv = "INV-" . Package::_raid();
$t->username = $customer['username'];
$t->user_id = $customer['id'];
$t->plan_name = $plan['name_plan'];
$t->price = $plan['price'];
$t->recharged_on = date("Y-m-d");
$t->recharged_time = date("H:i:s");
$t->expiration = date("Y-m-d");
$t->time = date("H:i:s");
$t->method = "$gateway - $channel";
$t->routers = 'balance';
$t->type = "Balance";
$t->note = $note;
if ($admin) {
$t->admin_id = ($admin['id']) ? $admin['id'] : '0';
} else {
$t->admin_id = '0';
}
$t->save();
$balance_before = $customer['balance'];
Balance::plus($customer['id'], $plan['price']);
$balance = $customer['balance'] + $plan['price'];
$textInvoice = Lang::getNotifText('invoice_balance');
$textInvoice = str_replace('[[company_name]]', $config['CompanyName'], $textInvoice);
$textInvoice = str_replace('[[address]]', $config['address'], $textInvoice);
$textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $inv, $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateTimeFormat(date("Y-m-d H:i:s")), $textInvoice);
$textInvoice = str_replace('[[trx_date]]', Lang::dateTimeFormat(date("Y-m-d H:i:s")), $textInvoice);
$textInvoice = str_replace('[[payment_gateway]]', $gateway, $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', $channel, $textInvoice);
$textInvoice = str_replace('[[type]]', 'Balance', $textInvoice);
$textInvoice = str_replace('[[plan_name]]', $plan['name_plan'], $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($plan['price']), $textInvoice);
$textInvoice = str_replace('[[name]]', $customer['fullname'], $textInvoice);
$textInvoice = str_replace('[[user_name]]', $customer['username'], $textInvoice);
$textInvoice = str_replace('[[user_password]]', $customer['password'], $textInvoice);
$textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
$textInvoice = str_replace('[[balance_before]]', Lang::moneyFormat($balance_before), $textInvoice);
$textInvoice = str_replace('[[balance]]', Lang::moneyFormat($balance), $textInvoice);
if ($config['user_notification_payment'] == 'sms') {
Message::sendSMS($customer['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'wa') {
Message::sendWhatsapp($customer['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'email') {
Message::sendEmail($customer['email'], '[' . $config['CompanyName'] . '] ' . Lang::T("Invoice") . ' ' . $inv, $textInvoice);
}
return $t->id();
}
public static function rechargeCustomBalance($customer, $plan, $gateway, $channel, $note = '')
{
global $admin, $config;
$plan = ORM::for_table('tbl_payment_gateway')
->where('username', $customer['username'])
->where('routers', 'Custom Balance')
->where('status', '1')
->find_one();
if (!$plan) {
return false;
}
// insert table transactions
$t = ORM::for_table('tbl_transactions')->create();
$t->invoice = $inv = "INV-" . Package::_raid();
$t->username = $customer['username'];
$t->user_id = $customer['id'];
$t->plan_name = 'Custom Balance';
$t->price = $plan['price'];
$t->recharged_on = date("Y-m-d");
$t->recharged_time = date("H:i:s");
$t->expiration = date("Y-m-d");
$t->time = date("H:i:s");
$t->method = "$gateway - $channel";
$t->routers = 'balance';
$t->type = "Balance";
$t->note = $note;
if ($admin) {
$t->admin_id = ($admin['id']) ? $admin['id'] : '0';
} else {
$t->admin_id = '0';
}
$t->save();
$balance_before = $customer['balance'];
Balance::plus($customer['id'], $plan['price']);
$balance = $customer['balance'] + $plan['price'];
$textInvoice = Lang::getNotifText('invoice_balance');
$textInvoice = str_replace('[[company_name]]', $config['CompanyName'], $textInvoice);
$textInvoice = str_replace('[[address]]', $config['address'], $textInvoice);
$textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $inv, $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateTimeFormat(date("Y-m-d H:i:s")), $textInvoice);
$textInvoice = str_replace('[[trx_date]]', Lang::dateTimeFormat(date("Y-m-d H:i:s")), $textInvoice);
$textInvoice = str_replace('[[payment_gateway]]', $gateway, $textInvoice);
$textInvoice = str_replace('[[payment_channel]]', $channel, $textInvoice);
$textInvoice = str_replace('[[type]]', 'Balance', $textInvoice);
$textInvoice = str_replace('[[plan_name]]', $plan['name_plan'], $textInvoice);
$textInvoice = str_replace('[[plan_price]]', Lang::moneyFormat($plan['price']), $textInvoice);
$textInvoice = str_replace('[[name]]', $customer['fullname'], $textInvoice);
$textInvoice = str_replace('[[user_name]]', $customer['username'], $textInvoice);
$textInvoice = str_replace('[[user_password]]', $customer['password'], $textInvoice);
$textInvoice = str_replace('[[footer]]', $config['note'], $textInvoice);
$textInvoice = str_replace('[[balance_before]]', Lang::moneyFormat($balance_before), $textInvoice);
$textInvoice = str_replace('[[balance]]', Lang::moneyFormat($balance), $textInvoice);
if ($config['user_notification_payment'] == 'sms') {
Message::sendSMS($customer['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'wa') {
Message::sendWhatsapp($customer['phonenumber'], $textInvoice);
} else if ($config['user_notification_payment'] == 'email') {
Message::sendEmail($customer['email'], '[' . $config['CompanyName'] . '] ' . Lang::T("Invoice") . ' ' . $inv, $textInvoice);
}
return $t->id();
}
public static function _raid()
{
return ORM::for_table('tbl_transactions')->max('id') + 1;

View File

@ -10,15 +10,16 @@ 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);
$url = U . implode('/', $routes);
$url = getUrl(implode('/', $routes));
if (count($search) > 0) {
$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();
@ -83,7 +84,7 @@ class Paginator
{
global $routes;
global $_L;
$url = U . implode('/', $routes);
$url = getUrl(implode('/', $routes));
$query = urlencode($query);
$adjacents = "2";
$page = (int)(empty(_get('p')) ? 1 : _get('p'));
@ -169,7 +170,7 @@ class Paginator
{
global $routes;
global $_L;
$url = U . $routes['0'] . '/' . $routes['1'] . '/';
$url = getUrl($routes['0'] . '/' . $routes['1'] . '/');
$adjacents = "2";
$page = (int)(!isset($routes['2']) ? 1 : $routes['2']);
$pagination = "";
@ -277,7 +278,7 @@ class Paginator
{
global $routes;
global $_L;
$url = U . $routes['0'] . '/' . $routes['1'] . '/';
$url = getUrl($routes['0'] . '/' . $routes['1'] . '/');
$adjacents = "2";
$page = (int)(!isset($routes['2']) ? 1 : $routes['2']);
$pagination = "";

View File

@ -109,4 +109,44 @@ 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);
if ($config['url_canonical'] == 'yes') {
$u = str_replace('?_route=', '', U);
$pos = strpos($url, '&');
if ($pos === false) {
return $u . $url;
} else {
return $u . substr($url, 0, $pos) . '?' . substr($url, $pos + 1);
}
} else {
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

@ -26,6 +26,15 @@ class User
return 0;
}
public static function getTawkToHash($email)
{
global $config;
if (!empty($config['tawkto_api_key']) && !empty($email)) {
return hash_hmac('sha256', $email, $config['tawkto_api_key']);
}
return '';
}
public static function getBills($id = 0)
{
if (!$id) {
@ -157,19 +166,38 @@ class User
return [];
}
public static function generateToken($uid, $validDays = 30)
{
global $db_pass;
if ($validDays >= 30) {
$time = time();
} else {
// for customer, deafult expired is 30 days
$time = strtotime('+ ' . (30 - $validDays) . ' days');
}
return [
'time' => $time,
'token' => $uid . '.' . $time . '.' . sha1($uid . '.' . $time . '.' . $db_pass)
];
}
public static function setCookie($uid)
{
global $db_pass;
if (isset($uid)) {
$time = time();
setcookie('uid', $uid . '.' . $time . '.' . sha1($uid . '.' . $time . '.' . $db_pass), time() + 86400 * 30);
$token = self::generateToken($uid);
setcookie('uid', $token['token'], time() + 86400 * 30, "/");
return $token;
} else {
return false;
}
}
public static function removeCookie()
{
if (isset($_COOKIE['uid'])) {
setcookie('uid', '', time() - 86400);
setcookie('uid', '', time() - 86400, "/");
}
}
@ -178,7 +206,7 @@ class User
global $config;
if ($config['maintenance_mode'] == true) {
if ($config['maintenance_mode_logout'] == true) {
r2(U . 'logout', 'd', '');
r2(getUrl('logout'), 'd', '');
} else {
displayMaintenanceMessage();
}
@ -190,13 +218,28 @@ class User
if ($d['status'] == 'Banned') {
_alert(Lang::T('This account status') . ' : ' . Lang::T($d['status']), 'danger', "logout");
}
if (empty($d['username'])) {
r2(U . 'logout', 'd', '');
return $d;
}
public static function _infoByName($username)
{
global $config;
if ($config['maintenance_mode'] == true) {
if ($config['maintenance_mode_logout'] == true) {
r2(getUrl('logout'), 'd', '');
} else {
displayMaintenanceMessage();
}
}
$d = ORM::for_table('tbl_customers')->where("username", $username)->find_one();
if ($d['status'] == 'Banned') {
_alert(Lang::T('This account status') . ' : ' . Lang::T($d['status']), 'danger', "logout");
}
return $d;
}
public static function isUserVoucher($kode) {
public static function isUserVoucher($kode)
{
$regex = '/^GC\d+C.{10}$/';
return preg_match($regex, $kode);
}
@ -209,16 +252,78 @@ class User
$d = ORM::for_table('tbl_user_recharges')
->select('tbl_user_recharges.id', 'id')
->selects([
'customer_id', 'username', 'plan_id', 'namebp', 'recharged_on', 'recharged_time', 'expiration', 'time',
'status', 'method', 'plan_type', 'name_bw',
'customer_id',
'username',
'plan_id',
'namebp',
'recharged_on',
'recharged_time',
'expiration',
'time',
'status',
'method',
'plan_type',
['tbl_user_recharges.routers', 'routers'],
['tbl_user_recharges.type', 'type'],
'admin_id', 'prepaid'
'admin_id',
'prepaid'
])
->left_outer_join('tbl_plans', ['tbl_plans.id', '=', 'tbl_user_recharges.plan_id'])
->left_outer_join('tbl_bandwidth', ['tbl_bandwidth.id', '=', 'tbl_plans.id_bw'])
->select('tbl_bandwidth.name_bw', 'name_bw')
->select('tbl_plans.price', 'price')
->where('customer_id', $id)
->left_outer_join('tbl_plans', array('tbl_plans.id', '=', 'tbl_user_recharges.plan_id'))
->left_outer_join('tbl_bandwidth', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))
->find_many();
return $d;
}
public static function setFormCustomField($uid = 0)
{
global $UPLOAD_PATH;
$fieldPath = $UPLOAD_PATH . DIRECTORY_SEPARATOR . "customer_field.json";
if (!file_exists($fieldPath)) {
return '';
}
$fields = json_decode(file_get_contents($fieldPath), true);
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)
{
global $UPLOAD_PATH;
$fieldPath = $UPLOAD_PATH . DIRECTORY_SEPARATOR . "customer_field.json";
if (!file_exists($fieldPath)) {
return '';
}
$fields = json_decode(file_get_contents($fieldPath), true);
$attrs = [];
if (!$register) {
$attrs = self::getAttributes('', $uid);
$ui->assign('attrs', $attrs);
}
$html = '';
$ui->assign('register', $register);
foreach ($fields as $field) {
if ($register) {
if ($field['register']) {
$ui->assign('field', $field);
$html .= $ui->fetch('customer/custom_field.tpl');
}
} 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,20 +67,28 @@ 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';
}
$admin = Admin::_info();
try {
if (!empty($_GET['uid'])) {
$_COOKIE['uid'] = $_GET['uid'];
}
$admin = Admin::_info();
$sys_render = $root_path . File::pathFixer('system/controllers/' . $handler . '.php');
if (file_exists($sys_render)) {
$menus = array();
@ -92,7 +100,7 @@ try {
foreach ($menu_registered as $menu) {
if ($menu['admin'] && _admin(false)) {
if (count($menu['auth']) == 0 || in_array($admin['user_type'], $menu['auth'])) {
$menus[$menu['position']] .= '<li' . (($routes[1] == $menu['function']) ? ' class="active"' : '') . '><a href="' . U . 'plugin/' . $menu['function'] . '">';
$menus[$menu['position']] .= '<li' . (($routes[1] == $menu['function']) ? ' class="active"' : '') . '><a href="' . getUrl('plugin/' . $menu['function']) . '">';
if (!empty($menu['icon'])) {
$menus[$menu['position']] .= '<i class="' . $menu['icon'] . '"></i>';
}
@ -103,7 +111,7 @@ try {
$menus[$menu['position']] .= '<span class="text">' . $menu['name'] . '</span></a></li>';
}
} else if (!$menu['admin'] && _auth(false)) {
$menus[$menu['position']] .= '<li' . (($routes[1] == $menu['function']) ? ' class="active"' : '') . '><a href="' . U . 'plugin/' . $menu['function'] . '">';
$menus[$menu['position']] .= '<li' . (($routes[1] == $menu['function']) ? ' class="active"' : '') . '><a href="' . getUrl('plugin/' . $menu['function']) . '">';
if (!empty($menu['icon'])) {
$menus[$menu['position']] .= '<i class="' . $menu['icon'] . '"></i>';
}
@ -120,7 +128,15 @@ try {
unset($menus, $menu_registered);
include($sys_render);
} else {
r2(U . 'dashboard', 'e', 'not found');
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(
@ -128,12 +144,13 @@ try {
$e->getMessage() . "\n" .
$e->getTraceAsString()
);
if (!Admin::getID()) {
r2(U . 'home', 'e', $e->getMessage());
if (empty($_SESSION['aid'])) {
$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('router-error.tpl');
$ui->display('admin/error.tpl');
die();
} catch (Exception $e) {
Message::sendTelegram(
@ -141,11 +158,12 @@ try {
$e->getMessage() . "\n" .
$e->getTraceAsString()
);
if (!Admin::getID()) {
r2(U . 'home', 'e', $e->getMessage());
if (empty($_SESSION['aid'])) {
$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('router-error.tpl');
$ui->display('admin/error.tpl');
die();
}

View File

@ -1,6 +1,7 @@
{
"require": {
"mpdf/mpdf": "^8.1",
"smarty/smarty": "=4.5.3"
"smarty/smarty": "=4.5.3",
"yosiazwan/php-facedetection": "^0.1.0"
}
}

44
system/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a33a5e0440af423877195440decefd29",
"content-hash": "a5f201dce3d594a500f2b9e9a8532e66",
"packages": [
{
"name": "mpdf/mpdf",
@ -476,6 +476,48 @@
"source": "https://github.com/smarty-php/smarty/tree/v4.5.3"
},
"time": "2024-05-28T21:46:01+00:00"
},
{
"name": "yosiazwan/php-facedetection",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/yosiazwan/php-facedetection.git",
"reference": "b016273ceceacd85562bbc50384fbabc947fe525"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yosiazwan/php-facedetection/zipball/b016273ceceacd85562bbc50384fbabc947fe525",
"reference": "b016273ceceacd85562bbc50384fbabc947fe525",
"shasum": ""
},
"require": {
"ext-gd": "*",
"php": ">=5.2.0"
},
"type": "library",
"autoload": {
"classmap": [
"FaceDetector.php",
"Exception/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0"
],
"authors": [
{
"name": "Maurice Svay",
"homepage": "https://github.com/mauricesvay/php-facedetection/graphs/contributors"
}
],
"description": "PHP class to detect one face in images. A pure PHP port of an existing JS code from Karthik Tharavad.",
"homepage": "https://github.com/mauricesvay/php-facedetection",
"support": {
"source": "https://github.com/yosiazwan/php-facedetection/tree/0.1.0"
},
"time": "2016-01-26T22:10:00+00:00"
}
],
"packages-dev": [],

View File

@ -18,11 +18,17 @@ switch ($action) {
case 'change-password':
run_hook('customer_view_change_password'); #HOOK
$ui->display('user-ui/change-password.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('customer/change-password.tpl');
break;
case 'change-password-post':
$password = _post('password');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('accounts/change-password'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
run_hook('customer_change_password'); #HOOK
if ($password != '') {
$d_pass = $user['password'];
@ -30,10 +36,10 @@ switch ($action) {
$cnpass = _post('cnpass');
if ($password == $d_pass) {
if (!Validator::Length($password, 36, 2)) {
r2(U . 'accounts/change-password', 'e', 'New Password must be 2 to 35 character');
r2(getUrl('accounts/change-password'), 'e', 'New Password must be 2 to 35 character');
}
if ($npass != $cnpass) {
r2(U . 'accounts/change-password', 'e', 'Both Password should be same');
r2(getUrl('accounts/change-password'), 'e', 'Both Password should be same');
}
$user->password = $npass;
$turs = ORM::for_table('tbl_user_recharges')->where('customer_id', $user['id'])->find_many();
@ -58,19 +64,25 @@ switch ($action) {
_log('[' . $user['username'] . ']: Password changed successfully', 'User', $user['id']);
_alert(Lang::T('Password changed successfully, Please login again'), 'success', "login");
} else {
die($password);
r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password'));
r2(getUrl('accounts/change-password'), 'e', Lang::T('Incorrect Current Password'));
}
} else {
r2(U . 'accounts/change-password', 'e', Lang::T('Incorrect Current Password'));
r2(getUrl('accounts/change-password'), 'e', Lang::T('Incorrect Current Password'));
}
break;
case 'profile':
run_hook('customer_view_edit_profile'); #HOOK
$ui->display('user-ui/profile.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->assign('customFields', User::getFormCustomField($ui, false, $user['id']));
$ui->display('customer/profile.tpl');
break;
case 'edit-profile-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('accounts/profile'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$fullname = _post('fullname');
$address = _post('address');
$email = _post('email');
@ -84,44 +96,105 @@ switch ($action) {
$msg .= 'Phone Number must be a number' . '<br>';
}
$user->fullname = $fullname;
$user->address = $address;
if ($_c['allow_phone_otp'] != 'yes') {
$user->phonenumber = $phonenumber;
}
if ($_c['allow_email_otp'] != 'yes') {
$user->email = $email;
}
if (empty($msg)) {
if (!empty($_FILES['photo']['name']) && file_exists($_FILES['photo']['tmp_name'])) {
if (function_exists('imagecreatetruecolor')) {
$hash = md5_file($_FILES['photo']['tmp_name']);
$subfolder = substr($hash, 0, 2);
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR . $subfolder . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$imgPath = $folder . $hash . '.jpg';
if (!file_exists($imgPath)) {
File::resizeCropImage($_FILES['photo']['tmp_name'], $imgPath, 1600, 1600, 100);
}
if (!file_exists($imgPath . '.thumb.jpg')) {
if (_post('faceDetect') == 'yes') {
try {
$detector = new svay\FaceDetector();
$detector->setTimeout(5000);
$detector->faceDetect($imgPath);
$detector->cropFaceToJpeg($imgPath . '.thumb.jpg', false);
} catch (Exception $e) {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
} catch (Throwable $e) {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
}
} else {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
}
}
if (file_exists($imgPath)) {
if ($user['photo'] != '' && strpos($user['photo'], 'default') === false) {
if (file_exists($UPLOAD_PATH . $user['photo'])) {
unlink($UPLOAD_PATH . $user['photo']);
if (file_exists($UPLOAD_PATH . $user['photo'] . '.thumb.jpg')) {
unlink($UPLOAD_PATH . $user['photo'] . '.thumb.jpg');
}
}
}
$user->photo = '/photos/' . $subfolder . '/' . $hash . '.jpg';
}
if (file_exists($_FILES['photo']['tmp_name'])) unlink($_FILES['photo']['tmp_name']);
} else {
r2(getUrl('settings/app'), 'e', 'PHP GD is not installed');
}
}
$user->save();
$user->fullname = $fullname;
$user->address = $address;
if ($_c['allow_phone_otp'] != 'yes') {
$user->phonenumber = $phonenumber;
}
if ($_c['allow_email_otp'] != 'yes') {
$user->email = $email;
}
_log('[' . $user['username'] . ']: ' . Lang::T('User Updated Successfully'), 'User', $user['id']);
r2(U . 'accounts/profile', 's', Lang::T('User Updated Successfully'));
User::setFormCustomField($user['id']);
$user->save();
_log('[' . $user['username'] . ']: ' . Lang::T('User Updated Successfully'), 'User', $user['id']);
r2(getUrl('accounts/profile'), 's', Lang::T('User Updated Successfully'));
}else{
r2(getUrl('accounts/profile'), 'e', $msg);
}
break;
case 'phone-update':
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->assign('new_phone', $_SESSION['new_phone']);
$ui->display('user-ui/phone-update.tpl');
$ui->display('customer/phone-update.tpl');
break;
case 'phone-update-otp':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$phone = Lang::phoneFormat(_post('phone'));
$username = $user['username'];
$otpPath = $CACHE_PATH . '/sms/';
$_SESSION['new_phone'] = $phone;
// Validate the phone number format
if (!preg_match('/^[0-9]{10,}$/', $phone) || empty($phone)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid phone number format'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Invalid phone number format'));
}
if (empty($config['sms_url'])) {
r2(U . 'accounts/phone-update', 'e', Lang::T('SMS server not Available, Please try again later'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('SMS server not Available, Please try again later'));
}
$d = ORM::for_table('tbl_customers')->whereNotEqual('username', $username)->where('phonenumber', $phone)->find_one();
if ($d) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Phone number already registered by another customer'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Phone number already registered by another customer'));
}
if (!file_exists($otpPath)) {
mkdir($otpPath);
@ -132,27 +205,31 @@ switch ($action) {
// expired 10 minutes
if (file_exists($otpFile) && time() - filemtime($otpFile) < 600) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Please wait ') . (600 - (time() - filemtime($otpFile))) . Lang::T(' seconds before sending another SMS'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Please wait ') . (600 - (time() - filemtime($otpFile))) . Lang::T(' seconds before sending another SMS'));
} else {
$otp = rand(100000, 999999);
file_put_contents($otpFile, $otp);
file_put_contents($phoneFile, $phone);
// send send OTP to user
if ($config['phone_otp_type'] === 'sms') {
Message::sendSMS($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
Message::sendSMS($phone, $config['CompanyName'] . "\n\n" . Lang::T("Verification code") . "\n$otp");
} elseif ($config['phone_otp_type'] === 'whatsapp') {
Message::sendWhatsapp($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
Message::sendWhatsapp($phone, $config['CompanyName'] . "\n\n" . Lang::T("Verification code") . "\n$otp");
} elseif ($config['phone_otp_type'] === 'both') {
Message::sendSMS($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
Message::sendWhatsapp($phone, $config['CompanyName'] . "\n Your Verification code is: $otp");
Message::sendSMS($phone, $config['CompanyName'] . "\n\n" . Lang::T("Verification code") . "\n$otp");
Message::sendWhatsapp($phone, $config['CompanyName'] . "\n\n" . Lang::T("Verification code") . "\n$otp");
}
//redirect after sending OTP
r2(U . 'accounts/phone-update', 'e', Lang::T('Verification code has been sent to your phone'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Verification code has been sent to your phone'));
}
break;
case 'phone-update-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$phone = Lang::phoneFormat(_post('phone'));
$otp_code = _post('otp');
$username = $user['username'];
@ -160,11 +237,11 @@ switch ($action) {
// Validate the phone number format
if (!preg_match('/^[0-9]{10,}$/', $phone)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid phone number format'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Invalid phone number format'));
}
if (empty($config['sms_url'])) {
r2(U . 'accounts/phone-update', 'e', Lang::T('SMS server not Available, Please try again later'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('SMS server not Available, Please try again later'));
}
$otpFile = $otpPath . sha1($username . $db_pass) . ".txt";
@ -172,7 +249,7 @@ switch ($action) {
// Check if OTP file exists
if (!file_exists($otpFile)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Please request OTP first'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Please request OTP first'));
exit();
}
@ -180,21 +257,21 @@ switch ($action) {
if (time() - filemtime($otpFile) > 1200) {
unlink($otpFile);
unlink($phoneFile);
r2(U . 'accounts/phone-update', 'e', Lang::T('Verification code expired'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Verification code expired'));
exit();
} else {
$code = file_get_contents($otpFile);
// Check if OTP code matches
if ($code != $otp_code) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Wrong Verification code'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('Wrong Verification code'));
exit();
}
// Check if the phone number matches the one that requested the OTP
$savedPhone = file_get_contents($phoneFile);
if ($savedPhone !== $phone) {
r2(U . 'accounts/phone-update', 'e', Lang::T('The phone number does not match the one that requested the OTP'));
r2(getUrl('accounts/phone-update'), 'e', Lang::T('The phone number does not match the one that requested the OTP'));
exit();
}
@ -207,30 +284,36 @@ switch ($action) {
$user->phonenumber = Lang::phoneFormat($phone);
$user->save();
r2(U . 'accounts/profile', 's', Lang::T('Phone number updated successfully'));
r2(getUrl('accounts/profile'), 's', Lang::T('Phone number updated successfully'));
break;
case 'email-update':
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->assign('new_email', $_SESSION['new_email']);
$ui->display('user-ui/email-update.tpl');
$ui->display('customer/email-update.tpl');
break;
case 'email-update-otp':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('accounts/email-update'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$email = trim(_post('email'));
$username = $user['username'];
$otpPath = $CACHE_PATH . '/email/';
$_SESSION['new_email'] = $email;
// Validate the phone number format
if (!Validator::Email($email)) {
r2(U . 'accounts/email-update', 'e', Lang::T('Invalid Email address format'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Invalid Email address format'));
}
if (empty($config['smtp_host'])) {
r2(U . 'accounts/email-update', 'e', Lang::T('Email server not Available, Please ask admin to configure it'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Email server not Available, Please ask admin to configure it'));
}
$d = ORM::for_table('tbl_customers')->whereNotEqual('username', $username)->where('email', $email)->find_one();
if ($d) {
r2(U . 'accounts/email-update', 'e', Lang::T('Email already used by another Customer'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Email already used by another Customer'));
}
if (!file_exists($otpPath)) {
mkdir($otpPath);
@ -241,7 +324,7 @@ switch ($action) {
// expired 10 minutes
if (file_exists($otpFile) && time() - filemtime($otpFile) < 600) {
r2(U . 'accounts/email-update', 'e', Lang::T('Please wait ') . (600 - (time() - filemtime($otpFile))) . Lang::T(' seconds before sending another Email'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Please wait ') . (600 - (time() - filemtime($otpFile))) . Lang::T(' seconds before sending another Email'));
} else {
$otp = rand(100000, 999999);
file_put_contents($otpFile, $otp);
@ -250,24 +333,28 @@ switch ($action) {
$body = Lang::T("Hello") . ' ' . $user['fullname'] . ",\n\n" . Lang::T("Your Email Verification Code is:") . " $otp";
Message::sendEmail($email, Lang::T('Change Email Verification Code'), $body);
//redirect after sending OTP
r2(U . 'accounts/email-update', 'e', Lang::T('Verification code has been sent to your email. Check Spam folder if not found.'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Verification code has been sent to your email. Check Spam folder if not found.'));
}
break;
case 'email-update-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('accounts/email-update'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$email = trim(_post('email'));
$otp_code = _post('otp');
$username = $user['username'];
$otpPath = $CACHE_PATH . '/email/';
// Validate the phone number format
if (!Validator::Email($email)) {
r2(U . 'accounts/email-update', 'e', Lang::T('Invalid Email address format'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Invalid Email address format'));
exit();
}
if (empty($config['smtp_host'])) {
r2(U . 'accounts/email-update', 'e', Lang::T('Email server not Available, Please ask admin to configure it'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Email server not Available, Please ask admin to configure it'));
}
$otpFile = $otpPath . sha1($username . $db_pass) . ".txt";
@ -275,7 +362,7 @@ switch ($action) {
// Check if OTP file exists
if (!file_exists($otpFile)) {
r2(U . 'accounts/email-update', 'e', Lang::T('Please request OTP first'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Please request OTP first'));
exit();
}
@ -283,21 +370,21 @@ switch ($action) {
if (time() - filemtime($otpFile) > 1200) {
unlink($otpFile);
unlink($emailFile);
r2(U . 'accounts/email-update', 'e', Lang::T('Verification code expired'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Verification code expired'));
exit();
} else {
$code = file_get_contents($otpFile);
// Check if OTP code matches
if ($code != $otp_code) {
r2(U . 'accounts/email-update', 'e', Lang::T('Wrong Verification code'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('Wrong Verification code'));
exit();
}
// Check if the phone number matches the one that requested the OTP
$savedEmail = file_get_contents($emailFile);
if ($savedEmail !== $email) {
r2(U . 'accounts/email-update', 'e', Lang::T('The Email Address does not match the one that requested the OTP'));
r2(getUrl('accounts/email-update'), 'e', Lang::T('The Email Address does not match the one that requested the OTP'));
exit();
}
@ -309,7 +396,7 @@ switch ($action) {
$user->email = $email;
$user->save();
r2(U . 'accounts/profile', 's', Lang::T('Email Address updated successfully'));
r2(getUrl('accounts/profile'), 's', Lang::T('Email Address updated successfully'));
break;
case 'language-update-post':
@ -322,10 +409,8 @@ switch ($action) {
if (file_exists($lan_file)) {
$_L = json_decode(file_get_contents($lan_file), true);
$_SESSION['Lang'] = $_L;
} else {
$_L['author'] = 'Auto Generated by iBNuX Script';
$_SESSION['Lang'] = $_L;
file_put_contents($lan_file, json_encode($_L));
}
User::setAttribute("Language", $selected_language);
@ -335,5 +420,5 @@ switch ($action) {
break;
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -5,8 +5,12 @@
* by https://t.me/ibnux
**/
if(Admin::getID()){
r2(U.'dashboard', "s", Lang::T("You are already logged in"));
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Expires: Tue, 01 Jan 2000 00:00:00 GMT");
header("Pragma: no-cache");
if (Admin::getID()) {
r2(getUrl('dashboard'), "s", Lang::T("You are already logged in"));
}
if (isset($routes['1'])) {
@ -19,6 +23,11 @@ switch ($do) {
case 'post':
$username = _post('username');
$password = _post('password');
//csrf token
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
_alert(Lang::T('Invalid or Expired CSRF Token') . ".", 'danger', "admin");
}
run_hook('admin_login'); #HOOK
if ($username != '' and $password != '') {
$d = ORM::for_table('tbl_users')->where('username', $username)->find_one();
@ -32,26 +41,28 @@ switch ($do) {
_log($username . ' ' . Lang::T('Login Successful'), $d['user_type'], $d['id']);
if ($isApi) {
if ($token) {
showResult(true, Lang::T('Login Successful'), ['token' => "a.".$token]);
showResult(true, Lang::T('Login Successful'), ['token' => "a." . $token]);
} else {
showResult(false, Lang::T('Invalid Username or Password'));
}
}
_alert(Lang::T('Login Successful'),'success', "dashboard");
_alert(Lang::T('Login Successful'), 'success', "dashboard");
} else {
_log($username . ' ' . Lang::T('Failed Login'), $d['user_type']);
_alert(Lang::T('Invalid Username or Password').".",'danger', "admin");
_alert(Lang::T('Invalid Username or Password') . ".", 'danger', "admin");
}
} else {
_alert(Lang::T('Invalid Username or Password')."..",'danger', "admin");
_alert(Lang::T('Invalid Username or Password') . "..", 'danger', "admin");
}
} else {
_alert(Lang::T('Invalid Username or Password')."...",'danger', "admin");
_alert(Lang::T('Invalid Username or Password') . "...", 'danger', "admin");
}
break;
default:
run_hook('view_login'); #HOOK
$ui->display('admin-login.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin/admin/login.tpl');
break;
}

View File

@ -26,14 +26,25 @@ 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']);
echo $bw['name_bw'];
die();
case 'balance':
$balance = ORM::for_table('tbl_customers')->select("balance")->find_one($routes['2'])['balance'];
if ($routes['3'] == '1') {
echo Lang::moneyFormat($balance);
} else {
echo $balance;
}
die();
case 'server':
$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'))) {
@ -65,32 +76,91 @@ switch ($action) {
$server = _post('server');
$jenis = _post('jenis');
if (in_array($admin['user_type'], array('SuperAdmin', 'Admin'))) {
if ($server == 'radius') {
$d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->find_many();
} else {
$d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->find_many();
switch ($server) {
case 'radius':
$d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->find_many();
break;
case '':
break;
default:
$d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->find_many();
break;
}
} else {
if ($server == 'radius') {
$d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->where('enabled', '1')->find_many();
} else {
$d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->where('enabled', '1')->find_many();
switch ($server) {
case 'radius':
$d = ORM::for_table('tbl_plans')->where('is_radius', 1)->where('type', $jenis)->find_many();
break;
case '':
break;
default:
$d = ORM::for_table('tbl_plans')->where('routers', $server)->where('type', $jenis)->find_many();
break;
}
}
$ui->assign('d', $d);
$ui->display('autoload.tpl');
$ui->display('admin/autoload/plan.tpl');
break;
case 'customer_is_active':
$d = ORM::for_table('tbl_user_recharges')->where('customer_id', $routes['2'])->findOne();
if ($d) {
if ($d['status'] == 'on') {
die('<span class="label label-success" title="Expired ' . Lang::dateAndTimeFormat($d['expiration'], $d['time']) . '">' . $d['namebp'] . '</span>');
} else {
die('<span class="label label-danger" title="Expired ' . Lang::dateAndTimeFormat($d['expiration'], $d['time']) . '">' . $d['namebp'] . '</span>');
if ($config['check_customer_online'] == 'yes') {
$c = ORM::for_table('tbl_customers')->where('username', $routes['2'])->find_one();
$p = ORM::for_table('tbl_plans')->find_one($routes['3']);
$dvc = Package::getDevice($p);
if ($_app_stage != 'Demo') {
if (file_exists($dvc)) {
require_once $dvc;
try {
//don't wait more than 5 seconds for response from device, otherwise we get timeout error.
ini_set('default_socket_timeout', 5);
if ((new $p['device'])->online_customer($c, $p['routers'])) {
echo '<span style="color: green;" title="online">&bull;</span>';
}else{
echo '<span style="color: yellow;" title="offline">&bull;</span>';
}
} catch (Exception $e) {
echo '<span style="color: red;" title="'.$e->getMessage().'">&bull;</span>';
}
}
}
}
break;
case 'plan_is_active':
$ds = ORM::for_table('tbl_user_recharges')->where('customer_id', $routes['2'])->find_array();
if ($ds) {
$ps = [];
$c = ORM::for_table('tbl_customers')->find_one($routes['2']);
foreach ($ds as $d) {
if ($d['status'] == 'on') {
if ($config['check_customer_online'] == 'yes') {
$p = ORM::for_table('tbl_plans')->find_one($d['plan_id']);
$dvc = Package::getDevice($p);
$status = "";
if ($_app_stage != 'Demo') {
if (file_exists($dvc)) {
require_once $dvc;
try {
//don't wait more than 5 seconds for response from device, otherwise we get timeout error.
ini_set('default_socket_timeout', 5);
if ((new $p['device'])->online_customer($c, $p['routers'])) {
$status = '<span style="color: green;" title="online">&bull;</span>';
}else{
$status = '<span style="color: yellow;" title="offline">&bull;</span>';
}
} catch (Exception $e) {
$status = '<span style="color: red;" title="'.$e->getMessage().'">&bull;</span>';
}
}
}
}
$ps[] = ('<span class="label label-primary m-1" title="Expired ' . Lang::dateAndTimeFormat($d['expiration'], $d['time']) . '">' . $d['namebp'] . ' ' . $status . '</span>');
} else {
$ps[] = ('<span class="label label-danger m-1" title="Expired ' . Lang::dateAndTimeFormat($d['expiration'], $d['time']) . '">' . $d['namebp'] . '</span>');
}
}
echo implode("<br>", $ps);
} else {
die('<span class="label label-danger">&bull;</span>');
die('');
}
break;
case 'customer_select2':
@ -111,5 +181,5 @@ switch ($action) {
echo json_encode(['results' => $json]);
die();
default:
$ui->display('a404.tpl');
$ui->display('admin/404.tpl');
}

View File

@ -25,10 +25,10 @@ switch ($action) {
if (file_exists($dvc)) {
require_once $dvc;
if ((new $p['device'])->online_customer($user, $bill['routers'])) {
die('<a href="' . U . 'home&mikrotik=logout&id=' . $bill['id'] . '" onclick="return confirm(\'' . Lang::T('Disconnect Internet?') . '\')" class="btn btn-success btn-xs btn-block">' . Lang::T('You are Online, Logout?') . '</a>');
die('<a href="' . getUrl('home&mikrotik=logout&id=' . $bill['id']) . '" onclick="return confirm(\'' . Lang::T('Disconnect Internet?') . '\')" class="btn btn-success btn-xs btn-block">' . Lang::T('You are Online, Logout?') . '</a>');
} else {
if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'])) {
die('<a href="' . U . 'home&mikrotik=login&id=' . $bill['id'] . '" onclick="return confirm(\'' . Lang::T('Connect to Internet?') . '\')" class="btn btn-danger btn-xs btn-block">' . Lang::T('Not Online, Login now?') . '</a>');
die('<a href="' . getUrl('home&mikrotik=login&id=' . $bill['id']) . '" onclick="return confirm(\'' . Lang::T('Connect to Internet?') . '\')" class="btn btn-danger btn-xs btn-block">' . Lang::T('Not Online, Login now?') . '</a>');
} else {
die(Lang::T('-'));
}
@ -45,6 +45,10 @@ switch ($action) {
die('--');
}
break;
case 'bw_name':
$bw = ORM::for_table('tbl_bandwidth')->select("name_bw")->find_one($routes['2']);
echo $bw['name_bw'];
die();
case 'inbox_unread':
$count = ORM::for_table('tbl_customers_inbox')->where('customer_id', $user['id'])->whereRaw('date_read is null')->count('id');
if ($count > 0) {
@ -54,7 +58,7 @@ switch ($action) {
case 'inbox':
$inboxs = ORM::for_table('tbl_customers_inbox')->selects(['id', 'subject', 'date_created'])->where('customer_id', $user['id'])->whereRaw('date_read is null')->order_by_desc('date_created')->limit(10)->find_many();
foreach ($inboxs as $inbox) {
echo '<li><a href="' . U . 'mail/view/' . $inbox['id'] . '">' . $inbox['subject'] . '<br><sub class="text-muted">' . Lang::dateTimeFormat($inbox['date_created']) . '</sub></a></li>';
echo '<li><a href="' . getUrl('mail/view/' . $inbox['id']) . '">' . $inbox['subject'] . '<br><sub class="text-muted">' . Lang::dateTimeFormat($inbox['date_created']) . '</sub></a></li>';
}
die();
case 'language':
@ -65,7 +69,7 @@ switch ($action) {
if (is_file('system/lan/' . $file) && !in_array($file, ['index.html', 'country.json', '.DS_Store'])) {
$file = str_replace(".json", "", $file);
if(!empty($file)){
echo '<li><a href="' . U . 'accounts/language-update-post&lang=' . $file. '">';
echo '<li><a href="' . getUrl('accounts/language-update-post&lang=' . $file) . '">';
if($select == $file){
echo '<span class="glyphicon glyphicon-ok"></span> ';
}
@ -75,5 +79,5 @@ switch ($action) {
}
die();
default:
$ui->display('404.tpl');
die();
}

View File

@ -13,12 +13,11 @@ $action = $routes['1'];
$ui->assign('_admin', $admin);
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
r2(U . "dashboard", 'e', Lang::T('You do not have permission to access this page'));
r2(getUrl('dashboard'), 'e', Lang::T('You do not have permission to access this page'));
}
switch ($action) {
case 'list':
$ui->assign('xfooter', '<script type="text/javascript" src="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,9 +50,9 @@ 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(U . 'bandwidth/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('bandwidth/list'), 'e', Lang::T('Account Not Found'));
}
break;
@ -66,7 +65,7 @@ switch ($action) {
$d = ORM::for_table('tbl_bandwidth')->find_one($id);
if ($d) {
$d->delete();
r2(U . 'bandwidth/list', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('bandwidth/list'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -123,9 +122,9 @@ switch ($action) {
$d->burst = $burst;
$d->save();
r2(U . 'bandwidth/list', 's', Lang::T('Data Created Successfully'));
r2(getUrl('bandwidth/list'), 's', Lang::T('Data Created Successfully'));
} else {
r2(U . 'bandwidth/add', 'e', $msg);
r2(getUrl('bandwidth/add'), 'e', $msg);
}
break;
@ -179,12 +178,12 @@ switch ($action) {
$d->burst = $burst;
$d->save();
r2(U . 'bandwidth/list', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('bandwidth/list'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'bandwidth/edit/' . $id, 'e', $msg);
r2(getUrl('bandwidth/edit/') . $id, 'e', $msg);
}
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

@ -0,0 +1,315 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
* Coupons Controller by https://t.me/focuslinkstech
**/
_admin();
$ui->assign('_title', Lang::T('Coupons'));
$ui->assign('_system_menu', 'crm');
$action = $routes['1'];
$ui->assign('_admin', $admin);
switch ($action) {
case 'add':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
$ui->assign('_title', Lang::T('Add Coupon'));
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/coupons/add.tpl');
break;
case 'add-post':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$code = Text::alphanumeric(_post('code', ''));
$type = _post('type', '');
$value = floatval(_post('value', ''));
$description = _post('description', '');
$max_usage = _post('max_usage', '0');
$min_order_amount = _post('min_order_amount', '');
$max_discount_amount = intval(_post('max_discount_amount', ''));
$status = _post('status', 'active');
$start_date = strtotime(_post('start_date', '0000-00-00'));
$end_date = strtotime(_post('end_date', '0000-00-00'));
$error = [];
if (empty($code)) {
$error[] = Lang::T('Coupon Code is required');
}
if (empty($type)) {
$error[] = Lang::T('Coupon Type is required');
}
if (empty($value)) {
$error[] = Lang::T('Coupon Value is required');
}
if (empty($description)) {
$error[] = Lang::T('Coupon Description is required');
}
if ($max_usage < 0) {
$error[] = Lang::T('Coupon Maximum Usage must be greater than or equal to 0');
}
if (empty($min_order_amount)) {
$error[] = Lang::T('Coupon Minimum Order Amount is required');
}
if (empty($max_discount_amount)) {
$error[] = Lang::T('Coupon Maximum Discount Amount is required');
}
if (empty($status)) {
$error[] = Lang::T('Coupon Status is required');
}
if (empty($start_date)) {
$error[] = Lang::T('Coupon Start Date is required');
}
if (empty($end_date)) {
$error[] = Lang::T('Coupon End Date is required');
}
if (!empty($error)) {
r2(getUrl('coupons/add'), 'e', implode('<br>', $error));
exit;
}
//check if coupon code already exists
$coupon = ORM::for_table('tbl_coupons')->where('code', $code)->find_one();
if ($coupon) {
r2(getUrl('coupons/add'), 'e', Lang::T('Coupon Code already exists'));
exit;
}
$coupon = ORM::for_table('tbl_coupons')->create();
$coupon->code = $code;
$coupon->type = $type;
$coupon->value = $value;
$coupon->description = $description;
$coupon->max_usage = $max_usage;
$coupon->min_order_amount = $min_order_amount;
$coupon->max_discount_amount = $max_discount_amount;
$coupon->status = $status;
$coupon->start_date = date('Y-m-d', $start_date);
$coupon->end_date = date('Y-m-d', $end_date);
$coupon->created_at = date('Y-m-d H:i:s');
try {
$coupon->save();
r2(getUrl('coupons'), 's', Lang::T('Coupon has been added successfully'));
} catch (Exception $e) {
_log(Lang::T('Error adding coupon: ' . $e->getMessage()));
r2(getUrl('coupons/add'), 'e', Lang::T('Error adding coupon: ' . $e->getMessage()));
}
break;
case 'edit':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
$coupon_id = intval($routes['2']);
if (empty($coupon_id)) {
r2(getUrl('coupons'), 'e', Lang::T('Invalid Coupon ID'));
exit;
}
$coupon = ORM::for_table('tbl_coupons')->find_one($coupon_id);
if (!$coupon) {
r2(getUrl('coupons'), 'e', Lang::T('Coupon Not Found'));
exit;
}
$ui->assign('coupon', $coupon);
$ui->assign('_title', Lang::T('Edit Coupon: ' . $coupon['code']));
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/coupons/edit.tpl');
break;
case 'edit-post':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$code = Text::alphanumeric(_post('code', ''));
$type = _post('type', '');
$value = floatval(_post('value', ''));
$description = _post('description', '');
$max_usage = _post('max_usage', '');
$min_order_amount = _post('min_order_amount', '');
$max_discount_amount = intval(_post('max_discount_amount', ''));
$status = _post('status', 'active');
$start_date = strtotime(_post('start_date', '0000-00-00'));
$end_date = strtotime(_post('end_date', '0000-00-00'));
$error = [];
if (empty($code)) {
$error[] = Lang::T('Coupon code is required');
}
if (empty($type)) {
$error[] = Lang::T('Coupon type is required');
}
if (empty($value)) {
$error[] = Lang::T('Coupon value is required');
}
if (empty($description)) {
$error[] = Lang::T('Coupon description is required');
}
if ($max_usage < 0) {
$error[] = Lang::T('Coupon Maximum Usage must be greater than or equal to 0');
}
if (empty($min_order_amount)) {
$error[] = Lang::T('Coupon minimum order amount is required');
}
if (empty($max_discount_amount)) {
$error[] = Lang::T('Coupon maximum discount amount is required');
}
if (empty($status)) {
$error[] = Lang::T('Coupon status is required');
}
if (empty($start_date)) {
$error[] = Lang::T('Coupon start date is required');
}
if (empty($end_date)) {
$error[] = Lang::T('Coupon end date is required');
}
if (!empty($error)) {
r2(getUrl('coupons/edit/') . $coupon_id, 'e', implode('<br>', $error));
exit;
}
$coupon = ORM::for_table('tbl_coupons')->find_one($coupon_id);
$coupon->code = $code;
$coupon->type = $type;
$coupon->value = $value;
$coupon->description = $description;
$coupon->max_usage = $max_usage;
$coupon->min_order_amount = $min_order_amount;
$coupon->max_discount_amount = $max_discount_amount;
$coupon->status = $status;
$coupon->start_date = date('Y-m-d', $start_date);
$coupon->end_date = date('Y-m-d', $end_date);
$coupon->updated_at = date('Y-m-d H:i:s');
try {
$coupon->save();
r2(getUrl('coupons'), 's', Lang::T('Coupon has been updated successfully'));
} catch (Exception $e) {
_log(Lang::T('Error updating coupon: ') . $e->getMessage());
r2(getUrl('coupons/edit/') . $coupon_id, 'e', Lang::T('Error updating coupon: ') . $e->getMessage());
}
break;
case 'delete':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$couponIds = json_decode($_POST['couponIds'], true);
if (is_array($couponIds) && !empty($couponIds)) {
// Delete coupons from the database
ORM::for_table('tbl_coupons')
->where_in('id', $couponIds)
->delete_many();
// Return success response
echo json_encode(['status' => 'success', 'message' => Lang::T("Coupons Deleted Successfully.")]);
exit;
} else {
echo json_encode(['status' => 'error', 'message' => Lang::T("Invalid or missing coupon IDs.")]);
exit;
}
} else {
echo json_encode(['status' => 'error', 'message' => Lang::T("Invalid request method.")]);
}
break;
case 'status':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$couponId = $_GET['coupon_id'] ?? '';
$csrf_token = $_GET['csrf_token'] ?? '';
$status = $_GET['status'] ?? '';
if (empty($couponId) || empty($csrf_token) || !Csrf::check($csrf_token) || empty($status)) {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Invalid request"));
exit;
}
$coupon = ORM::for_table('tbl_coupons')->where('id', $couponId)->find_one();
if (!$coupon) {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Coupon not found."));
exit;
}
$coupon->status = $status;
$coupon->save();
r2($_SERVER['HTTP_REFERER'], 's', Lang::T("Coupon status updated successfully."));
} else {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Invalid request method"));
}
break;
default:
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
$ui->assign('_title', Lang::T('Coupons'));
$ui->assign('_system_menu', 'crm');
$search = _post('search');
$filter = _post('filter', 'none');
$couponsData = ORM::for_table('tbl_coupons')
->table_alias('c')
->select_many(
'c.id',
'c.code',
'c.type',
'c.value',
'c.description',
'c.max_usage',
'c.usage_count',
'c.status',
'c.min_order_amount',
'c.max_discount_amount',
'c.start_date',
'c.end_date',
'c.created_at',
'c.updated_at'
);
// Apply filters
if ($search != '') {
$searchLike = "%$search%";
$couponsData->whereRaw(
"code LIKE ? OR type LIKE ? OR value LIKE ? OR max_usage LIKE ? OR usage_count LIKE ? OR status LIKE ? OR min_order_amount LIKE ? OR max_discount_amount LIKE ?",
[$searchLike, $searchLike, $searchLike, $searchLike, $searchLike, $searchLike, $searchLike, $searchLike]
);
}
$couponsData->order_by_asc('c.id');
$coupons = Paginator::findMany($couponsData, ['search' => $search], 5, '');
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->assign('coupons', $coupons);
$ui->display('admin/coupons/list.tpl');
break;
}

View File

@ -25,6 +25,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");
}
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$cs = ORM::for_table('tbl_customers')
->select('tbl_customers.id', 'id')
@ -153,7 +157,8 @@ switch ($action) {
}
$ui->assign('xheader', $leafletpickerHeader);
run_hook('view_add_customer'); #HOOK
$ui->display('customers-add.tpl');
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/customers/add.tpl');
break;
case 'recharge':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
@ -161,22 +166,43 @@ switch ($action) {
}
$id_customer = $routes['2'];
$plan_id = $routes['3'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/view/') . $id_customer, 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one();
if ($b) {
$gateway = 'Recharge';
$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;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
} else {
$tax_rate = $tax_rate_setting;
}
if ($tax_enable === 'yes') {
$tax = Package::tax($plan['price'], $tax_rate);
} else {
$tax = 0;
}
list($bills, $add_cost) = User::getBills($id_customer);
if ($using == 'balance' && $config['enable_balance'] == 'yes') {
if (!$cust) {
r2(U . 'plan/recharge', 'e', Lang::T('Customer not found'));
r2(getUrl('plan/recharge'), 'e', Lang::T('Customer not found'));
}
if (!$plan) {
r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
r2(getUrl('plan/recharge'), 'e', Lang::T('Plan not found'));
}
if ($cust['balance'] < ($plan['price'] + $add_cost)) {
r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
if ($cust['balance'] < ($plan['price'] + $add_cost + $tax)) {
r2(getUrl('plan/recharge'), 'e', Lang::T('insufficient balance'));
}
$gateway = 'Recharge Balance';
}
@ -189,7 +215,12 @@ switch ($action) {
if (count($usings) == 0) {
$usings[] = Lang::T('Cash');
}
$abills = User::getAttributes("Bill");
if ($tax_enable === 'yes') {
$ui->assign('tax', $tax);
}
$ui->assign('usings', $usings);
$ui->assign('abills', $abills);
$ui->assign('bills', $bills);
$ui->assign('add_cost', $add_cost);
$ui->assign('cust', $cust);
@ -197,9 +228,11 @@ switch ($action) {
$ui->assign('channel', $channel);
$ui->assign('server', $b['routers']);
$ui->assign('plan', $plan);
$ui->display('recharge-confirm.tpl');
$ui->assign('add_inv', $add_inv);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/plan/recharge-confirm.tpl');
} else {
r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
r2(getUrl('customers/view/') . $id_customer, 'e', 'Cannot find active plan');
}
break;
case 'deactivate':
@ -208,6 +241,10 @@ switch ($action) {
}
$id_customer = $routes['2'];
$plan_id = $routes['3'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/view/') . $id_customer, 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$b = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('plan_id', $plan_id)->find_one();
if ($b) {
$p = ORM::for_table('tbl_plans')->where('id', $b['plan_id'])->find_one();
@ -229,13 +266,17 @@ switch ($action) {
$b->save();
_log('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for ' . $b['username'], 'User', $b['customer_id']);
Message::sendTelegram('Admin ' . $admin['username'] . ' Deactivate ' . $b['namebp'] . ' for u' . $b['username']);
r2(U . 'customers/view/' . $id_customer, 's', 'Success deactivate customer to Mikrotik');
r2(getUrl('customers/view/') . $id_customer, 's', 'Success deactivate customer to Mikrotik');
}
}
r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
r2(getUrl('customers/view/') . $id_customer, 'e', 'Cannot find active plan');
break;
case 'sync':
$id_customer = $routes['2'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/view/') . $id_customer, 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$bs = ORM::for_table('tbl_user_recharges')->where('customer_id', $id_customer)->where('status', 'on')->findMany();
if ($bs) {
$routers = [];
@ -248,16 +289,37 @@ switch ($action) {
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $p['device'])->add_customer($c, $p);
if (method_exists($dvc, 'sync_customer')) {
(new $p['device'])->sync_customer($c, $p);
}else{
(new $p['device'])->add_customer($c, $p);
}
} else {
new Exception(Lang::T("Devices Not Found"));
}
}
}
}
r2(U . 'customers/view/' . $id_customer, 's', 'Sync success to ' . implode(", ", $routers));
r2(getUrl('customers/view/') . $id_customer, 's', 'Sync success to ' . implode(", ", $routers));
}
r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
r2(getUrl('customers/view/') . $id_customer, 'e', 'Cannot find active plan');
break;
case 'login':
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'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/view/') . $id, 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$customer = ORM::for_table('tbl_customers')->find_one($id);
if ($customer) {
$_SESSION['uid'] = $id;
User::setCookie($id);
_alert("You are logging in as $customer[fullname],<br>don't logout just close tab.", 'info', "home", 10);
}
_alert(Lang::T('Customer not found'), 'danger', "customers");
break;
case 'viewu':
$customer = ORM::for_table('tbl_customers')->where('username', $routes['2'])->find_one();
@ -268,8 +330,6 @@ switch ($action) {
$customer = ORM::for_table('tbl_customers')->find_one($id);
}
if ($customer) {
// Fetch the Customers Attributes values from the tbl_customer_custom_fields table
$customFields = ORM::for_table('tbl_customers_fields')
->where('customer_id', $customer['id'])
@ -278,28 +338,44 @@ switch ($action) {
if (empty($v)) {
$v = 'activation';
}
if ($v == 'order') {
$v = 'order';
$query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id');
$order = Paginator::findMany($query);
$ui->assign('order', $order);
} else if ($v == 'activation') {
$query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id');
$activation = Paginator::findMany($query);
$ui->assign('activation', $activation);
switch ($v) {
case 'order':
$v = 'order';
$query = ORM::for_table('tbl_payment_gateway')->where('user_id', $customer['id'])->order_by_desc('id');
$order = Paginator::findMany($query);
if (empty($order) || $order < 5) {
$query = ORM::for_table('tbl_payment_gateway')->where('username', $customer['username'])->order_by_desc('id');
$order = Paginator::findMany($query);
}
$ui->assign('order', $order);
break;
case 'activation':
$query = ORM::for_table('tbl_transactions')->where('user_id', $customer['id'])->order_by_desc('id');
$activation = Paginator::findMany($query);
if (empty($activation) || $activation < 5) {
$query = ORM::for_table('tbl_transactions')->where('username', $customer['username'])->order_by_desc('id');
$activation = Paginator::findMany($query);
}
$ui->assign('activation', $activation);
break;
}
$ui->assign('packages', User::_billing($customer['id']));
$ui->assign('v', $v);
$ui->assign('d', $customer);
$ui->assign('customFields', $customFields);
$ui->assign('xheader', $leafletpickerHeader);
$ui->display('customers-view.tpl');
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/customers/view.tpl');
} else {
r2(U . 'customers/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('customers/list'), 'e', Lang::T('Account Not Found'));
}
break;
case 'edit':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) {
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'];
@ -310,13 +386,31 @@ switch ($action) {
->where('customer_id', $id)
->find_many();
if ($d) {
if (isset($routes['3']) && $routes['3'] == 'deletePhoto') {
if ($d['photo'] != '' && strpos($d['photo'], 'default') === false) {
if (file_exists($UPLOAD_PATH . $d['photo']) && strpos($d['photo'], 'default') === false) {
unlink($UPLOAD_PATH . $d['photo']);
if (file_exists($UPLOAD_PATH . $d['photo'] . '.thumb.jpg')) {
unlink($UPLOAD_PATH . $d['photo'] . '.thumb.jpg');
}
}
$d->photo = '/user.default.jpg';
$d->save();
$ui->assign('notify_t', 's');
$ui->assign('notify', 'You have successfully deleted the photo');
} else {
$ui->assign('notify_t', 'e');
$ui->assign('notify', 'No photo found to delete');
}
}
$ui->assign('d', $d);
$ui->assign('statuses', ORM::for_table('tbl_customers')->getEnum("status"));
$ui->assign('customFields', $customFields);
$ui->assign('xheader', $leafletpickerHeader);
$ui->display('customers-edit.tpl');
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/customers/edit.tpl');
} else {
r2(U . 'customers/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('customers/list'), 'e', Lang::T('Account Not Found'));
}
break;
@ -325,6 +419,10 @@ switch ($action) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$id = $routes['2'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/view/') . $id, 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
run_hook('delete_customer'); #HOOK
$c = ORM::for_table('tbl_customers')->find_one($id);
if ($c) {
@ -355,11 +453,16 @@ switch ($action) {
$c->delete();
} catch (Exception $e) {
}
r2(U . 'customers/list', 's', Lang::T('User deleted Successfully'));
r2(getUrl('customers/list'), 's', Lang::T('User deleted Successfully'));
}
break;
case 'add-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/add'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$username = alphanumeric(_post('username'), ":+_.@-");
$fullname = _post('fullname');
$password = trim(_post('password'));
@ -444,7 +547,7 @@ switch ($action) {
$welcomeMessage = str_replace('[[name]]', $d['fullname'], $welcomeMessage);
$welcomeMessage = str_replace('[[username]]', $d['username'], $welcomeMessage);
$welcomeMessage = str_replace('[[password]]', $d['password'], $welcomeMessage);
$welcomeMessage = str_replace('[[url]]', APP_URL . '/index.php?_route=login', $welcomeMessage);
$welcomeMessage = str_replace('[[url]]', APP_URL . '/?_route=login', $welcomeMessage);
$emailSubject = "Welcome to " . $config['CompanyName'];
@ -455,12 +558,12 @@ switch ($action) {
'args' => [$d['phonenumber'], $welcomeMessage]
],
'whatsapp' => [
'enabled' => isset($_POST['wa']) && $_POST['wa'] == 'wa',
'enabled' => isset($_POST['wa']),
'method' => 'sendWhatsapp',
'args' => [$d['phonenumber'], $welcomeMessage]
],
'email' => [
'enabled' => isset($_POST['email']),
'enabled' => isset($_POST['mail']),
'method' => 'Message::sendEmail',
'args' => [$d['email'], $emailSubject, $welcomeMessage, $d['email']]
]
@ -477,13 +580,18 @@ switch ($action) {
}
}
}
r2(U . 'customers/list', 's', Lang::T('Account Created Successfully'));
r2(getUrl('customers/list'), 's', Lang::T('Account Created Successfully'));
} else {
r2(U . 'customers/add', 'e', $msg);
r2(getUrl('customers/add'), 'e', $msg);
}
break;
case 'edit-post':
$id = _post('id');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers/edit/') . $id, 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$username = alphanumeric(_post('username'), ":+_.@-");
$fullname = _post('fullname');
$account_type = _post('account_type');
@ -511,7 +619,6 @@ switch ($action) {
$msg .= 'Full Name should be between 2 to 25 characters' . '<br>';
}
$id = _post('id');
$c = ORM::for_table('tbl_customers')->find_one($id);
if (!$c) {
@ -536,8 +643,8 @@ switch ($action) {
if (ORM::for_table('tbl_customers')->where('username', $username)->find_one()) {
$msg .= Lang::T('Username already used by another customer') . '<br>';
}
if(ORM::for_table('tbl_customers')->where('pppoe_username', $username)->find_one()){
$msg.= Lang::T('Username already used by another pppoe username customer') . '<br>';
if (ORM::for_table('tbl_customers')->where('pppoe_username', $username)->find_one()) {
$msg .= Lang::T('Username already used by another pppoe username customer') . '<br>';
}
$userDiff = true;
}
@ -561,6 +668,54 @@ switch ($action) {
}
if ($msg == '') {
if (!empty($_FILES['photo']['name']) && file_exists($_FILES['photo']['tmp_name'])) {
if (function_exists('imagecreatetruecolor')) {
$hash = md5_file($_FILES['photo']['tmp_name']);
$subfolder = substr($hash, 0, 2);
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR . $subfolder . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$imgPath = $folder . $hash . '.jpg';
if (!file_exists($imgPath)) {
File::resizeCropImage($_FILES['photo']['tmp_name'], $imgPath, 1600, 1600, 100);
}
if (!file_exists($imgPath . '.thumb.jpg')) {
if (_post('faceDetect') == 'yes') {
try {
$detector = new svay\FaceDetector();
$detector->setTimeout(5000);
$detector->faceDetect($imgPath);
$detector->cropFaceToJpeg($imgPath . '.thumb.jpg', false);
} catch (Exception $e) {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
} catch (Throwable $e) {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
}
} else {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
}
}
if (file_exists($imgPath)) {
if ($c['photo'] != '' && strpos($c['photo'], 'default') === false) {
if (file_exists($UPLOAD_PATH . $c['photo'])) {
unlink($UPLOAD_PATH . $c['photo']);
if (file_exists($UPLOAD_PATH . $c['photo'] . '.thumb.jpg')) {
unlink($UPLOAD_PATH . $c['photo'] . '.thumb.jpg');
}
}
}
$c->photo = '/photos/' . $subfolder . '/' . $hash . '.jpg';
}
if (file_exists($_FILES['photo']['tmp_name'])) unlink($_FILES['photo']['tmp_name']);
} else {
r2(getUrl('settings/app'), 'e', 'PHP GD is not installed');
}
}
if ($userDiff) {
$c->username = $username;
}
@ -644,13 +799,13 @@ switch ($action) {
(new $p['device'])->change_username($p, $oldusername, $username);
}
if ($pppoeDiff && $tur['type'] == 'PPPOE') {
if(empty($oldPppoeUsername) && !empty($pppoe_username)){
if (empty($oldPppoeUsername) && !empty($pppoe_username)) {
// admin just add pppoe username
(new $p['device'])->change_username($p, $username, $pppoe_username);
}else if(empty($pppoe_username) && !empty($oldPppoeUsername)){
} else if (empty($pppoe_username) && !empty($oldPppoeUsername)) {
// admin want to use customer username
(new $p['device'])->change_username($p, $oldPppoeUsername, $username);
}else{
} else {
// regular change pppoe username
(new $p['device'])->change_username($p, $oldPppoeUsername, $pppoe_username);
}
@ -665,18 +820,18 @@ switch ($action) {
$tur->save();
}
}
r2(U . 'customers/view/' . $id, 's', 'User Updated Successfully');
r2(getUrl('customers/view/') . $id, 's', 'User Updated Successfully');
} else {
r2(U . 'customers/edit/' . $id, 'e', $msg);
r2(getUrl('customers/edit/') . $id, 'e', $msg);
}
break;
default:
run_hook('list_customers'); #HOOK
$search = _post('search');
$order = _post('order', 'username');
$filter = _post('filter', 'Active');
$orderby = _post('orderby', 'asc');
$search = _req('search');
$order = _req('order', 'username');
$filter = _req('filter', 'Active');
$orderby = _req('orderby', 'asc');
$order_pos = [
'username' => 0,
'created_at' => 8,
@ -694,12 +849,20 @@ switch ($action) {
$query = ORM::for_table('tbl_customers');
$query->where("status", $filter);
}
if ($orderby == 'asc') {
$query->order_by_asc($order);
if ($order == 'lastname') {
$query->order_by_expr("SUBSTR(fullname, INSTR(fullname, ' ')) $orderby");
} else {
$query->order_by_desc($order);
if ($orderby == 'asc') {
$query->order_by_asc($order);
} else {
$query->order_by_desc($order);
}
}
if (_post('export', '') == 'csv') {
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('customers'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$d = $query->findMany();
$h = false;
set_time_limit(-1);
@ -749,6 +912,7 @@ switch ($action) {
$ui->assign('order', $order);
$ui->assign('order_pos', $order_pos[$order]);
$ui->assign('orderby', $orderby);
$ui->display('customers.tpl');
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('admin/customers/list.tpl');
break;
}

View File

@ -0,0 +1,53 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Custom Fields'));
$ui->assign('_system_menu', 'settings');
$action = $routes['1'];
$ui->assign('_admin', $admin);
$fieldPath = $UPLOAD_PATH . DIRECTORY_SEPARATOR . "customer_field.json";
switch ($action) {
case 'save':
print_r($_POST);
$datas = [];
$count = count($_POST['name']);
for($n=0;$n<$count;$n++){
if(!empty($_POST['name'][$n])){
$datas[] = [
'order' => $_POST['order'][$n],
'name' => Text::alphanumeric(strtolower(str_replace(" ", "_", $_POST['name'][$n])), "_"),
'type' => $_POST['type'][$n],
'placeholder' => $_POST['placeholder'][$n],
'value' => $_POST['value'][$n],
'register' => $_POST['register'][$n],
'required' => $_POST['required'][$n]
];
}
}
if(count($datas)>1){
usort($datas, function ($item1, $item2) {
return $item1['order'] <=> $item2['order'];
});
}
if(file_put_contents($fieldPath, json_encode($datas))){
r2(getUrl('customfield'), 's', 'Successfully saved custom fields!');
}else{
r2(getUrl('customfield'), 'e', 'Failed to save custom fields!');
}
default:
$fields = [];
if(file_exists($fieldPath)){
$fields = json_decode(file_get_contents($fieldPath), true);
}
$ui->assign('fields', $fields);
$ui->display('admin/settings/customfield.tpl');
break;
}

View File

@ -1,228 +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(U . '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')->order_by_desc('name')->find_array();
print_r($routeroffs);
$ui->assign('routeroffs', $routeroffs);
}
// 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(U.'dashboard');
}if(User::getID()){
r2(U.'home');
//r2(getUrl('dashboard'));
$handler = 'dashboard';
}else if(User::getID()){
//r2(getUrl('home'));
$handler = 'home';
}else{
r2(U.'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

@ -0,0 +1,169 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
$step = _req('step', 0);
$otpPath = $CACHE_PATH . File::pathFixer('/forgot/');
if ($step == '-1') {
$_COOKIE['forgot_username'] = '';
setcookie('forgot_username', '', time() - 3600, '/');
$step = 0;
}
if (!empty($_COOKIE['forgot_username']) && in_array($step, [0, 1])) {
$step = 1;
$_POST['username'] = $_COOKIE['forgot_username'];
}
if ($step == 1) {
$username = _post('username');
if (!empty($username)) {
$ui->assign('username', $username);
if (!file_exists($otpPath)) {
mkdir($otpPath);
}
setcookie('forgot_username', $username, time() + 3600, '/');
$user = ORM::for_table('tbl_customers')->selects(['phonenumber', 'email'])->where('username', $username)->find_one();
if ($user) {
$otpPath .= sha1($username . $db_pass) . ".txt";
if (file_exists($otpPath) && time() - filemtime($otpPath) < 600) {
$sec = time() - filemtime($otpPath);
$ui->assign('notify_t', 's');
$ui->assign('notify', Lang::T("Verification Code already Sent to Your Phone/Email/Whatsapp, please wait")." $sec seconds.");
} else {
$via = $config['user_notification_reminder'];
if ($via == 'email') {
$via = 'sms';
}
$otp = mt_rand(100000, 999999);
file_put_contents($otpPath, $otp);
if ($via == 'sms') {
Message::sendSMS($user['phonenumber'], $config['CompanyName'] . " C0de: $otp");
} else {
Message::sendWhatsapp($user['phonenumber'], $config['CompanyName'] . " C0de: $otp");
}
Message::sendEmail(
$user['email'],
$config['CompanyName'] . Lang::T("Your Verification Code") . ' : ' . $otp,
Lang::T("Your Verification Code") . ' : <b>' . $otp . '</b>'
);
$ui->assign('notify_t', 's');
$ui->assign('notify', Lang::T("If your Username is found, Verification Code has been Sent to Your Phone/Email/Whatsapp"));
}
} else {
// Username not found
$ui->assign('notify_t', 's');
$ui->assign('notify', Lang::T("If your Username is found, Verification Code has been Sent to Your Phone/Email/Whatsapp") . ".");
}
} else {
$step = 0;
}
} else if ($step == 2) {
$username = _post('username');
$otp_code = _post('otp_code');
if (!empty($username) && !empty($otp_code)) {
$otpPath .= sha1($username . $db_pass) . ".txt";
if (file_exists($otpPath) && time() - filemtime($otpPath) <= 600) {
$otp = file_get_contents($otpPath);
if ($otp == $otp_code) {
$pass = mt_rand(10000, 99999);
$user = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
$user->password = $pass;
$user->save();
$ui->assign('username', $username);
$ui->assign('passsword', $pass);
$ui->assign('notify_t', 's');
$ui->assign('notify', Lang::T("Verification Code Valid"));
if (file_exists($otpPath)) {
unlink($otpPath);
}
setcookie('forgot_username', '', time() - 3600, '/');
} else {
r2(getUrl('forgot&step=1'), 'e', Lang::T('Invalid Username or Verification Code'));
}
} else {
if (file_exists($otpPath)) {
unlink($otpPath);
}
r2(getUrl('forgot&step=1'), 'e', Lang::T('Invalid Username or Verification Code'));
}
} else {
r2(getUrl('forgot&step=1'), 'e', Lang::T('Invalid Username or Verification Code'));
}
} else if ($step == 7) {
$find = _post('find');
$step = 6;
if (!empty($find)) {
$via = $config['user_notification_reminder'];
if ($via == 'email') {
$via = 'sms';
}
if (!file_exists($otpPath)) {
mkdir($otpPath);
}
$otpPath .= sha1($find . $db_pass) . ".txt";
$users = ORM::for_table('tbl_customers')->selects(['username', 'phonenumber', 'email'])->where('phonenumber', $find)->find_array();
if ($users) {
// prevent flooding only can request every 10 minutes
if (!file_exists($otpPath) || (file_exists($otpPath) && time() - filemtime($otpPath) >= 600)) {
$usernames = implode(", ", array_column($users, 'username'));
if ($via == 'sms') {
Message::sendSMS($find, Lang::T("Your username for") . ' ' . $config['CompanyName'] . "\n" . $usernames);
} else {
Message::sendWhatsapp($find, Lang::T("Your username for") . ' ' . $config['CompanyName'] . "\n" . $usernames);
}
file_put_contents($otpPath, time());
}
$ui->assign('notify_t', 's');
$ui->assign('notify', Lang::T("Usernames have been sent to your phone/Whatsapp") . " $find");
$step = 0;
} else {
$users = ORM::for_table('tbl_customers')->selects(['username', 'phonenumber', 'email'])->where('email', $find)->find_array();
if ($users) {
// prevent flooding only can request every 10 minutes
if (!file_exists($otpPath) || (file_exists($otpPath) && time() - filemtime($otpPath) >= 600)) {
$usernames = implode(", ", array_column($users, 'username'));
$phones = [];
foreach ($users as $user) {
if (!in_array($user['phonenumber'], $phones)) {
if ($via == 'sms') {
Message::sendSMS($user['phonenumber'], Lang::T("Your username for") . ' ' . $config['CompanyName'] . "\n" . $usernames);
} else {
Message::sendWhatsapp($user['phonenumber'], Lang::T("Your username for") . ' ' . $config['CompanyName'] . "\n" . $usernames);
}
$phones[] = $user['phonenumber'];
}
}
Message::sendEmail(
$user['email'],
Lang::T("Your username for") . ' ' . $config['CompanyName'],
Lang::T("Your username for") . ' ' . $config['CompanyName'] . "\n" . $usernames
);
file_put_contents($otpPath, time());
}
$ui->assign('notify_t', 's');
$ui->assign('notify', Lang::T("Usernames have been sent to your phone/Whatsapp/Email"));
$step = 0;
} else {
$ui->assign('notify_t', 'e');
$ui->assign('notify', Lang::T("No data found"));
}
}
}
}
// delete old files
$pth = $CACHE_PATH . File::pathFixer('/forgot/');
$fs = scandir($pth);
foreach ($fs as $file) {
if(is_file($pth.$file) && time() - filemtime($pth.$file) > 3600) {
unlink($pth.$file);
}
}
$ui->assign('step', $step);
$ui->assign('_title', Lang::T('Forgot Password'));
$ui->display('customer/forgot.tpl');

View File

@ -23,18 +23,18 @@ if (_post('send') == 'balance') {
}
$target = ORM::for_table('tbl_customers')->where('username', _post('username'))->find_one();
if (!$target) {
r2(U . 'home', 'd', Lang::T('Username not found'));
r2(getUrl('home'), 'd', Lang::T('Username not found'));
}
$username = _post('username');
$balance = _post('balance');
if ($user['balance'] < $balance) {
r2(U . 'home', 'd', Lang::T('insufficient balance'));
r2(getUrl('home'), 'd', Lang::T('insufficient balance'));
}
if (!empty($config['minimum_transfer']) && intval($balance) < intval($config['minimum_transfer'])) {
r2(U . 'home', 'd', Lang::T('Minimum Transfer') . ' ' . Lang::moneyFormat($config['minimum_transfer']));
r2(getUrl('home'), 'd', Lang::T('Minimum Transfer') . ' ' . Lang::moneyFormat($config['minimum_transfer']));
}
if ($user['username'] == $target['username']) {
r2(U . 'home', 'd', Lang::T('Cannot send to yourself'));
r2(getUrl('home'), 'd', Lang::T('Cannot send to yourself'));
}
if (Balance::transfer($user['id'], $username, $balance)) {
//sender
@ -71,13 +71,14 @@ if (_post('send') == 'balance') {
$d->pg_url_payment = 'balance';
$d->status = 2;
$d->save();
Message::sendBalanceNotification($user, $balance, ($user['balance'] - $balance), Lang::getNotifText('balance_send'), $config['user_notification_payment']);
Message::sendBalanceNotification($target, $balance, ($target['balance'] + $balance), Lang::getNotifText('balance_received'), $config['user_notification_payment']);
//
Message::sendBalanceNotification($user, $target, $balance, ($user['balance'] - $balance), Lang::getNotifText('balance_send'), $config['user_notification_payment']);
Message::sendBalanceNotification($target, $user, $balance, ($target['balance'] + $balance), Lang::getNotifText('balance_received'), $config['user_notification_payment']);
Message::sendTelegram("#u$user[username] send balance to #u$target[username] \n" . Lang::moneyFormat($balance));
r2(U . 'home', 's', Lang::T('Sending balance success'));
r2(getUrl('home'), 's', Lang::T('Sending balance success'));
}
} else {
r2(U . 'home', 'd', Lang::T('Failed, balance is not available'));
r2(getUrl('home'), 'd', Lang::T('Failed, balance is not available'));
}
} else if (_post('send') == 'plan') {
if ($user['status'] != 'Active') {
@ -89,18 +90,17 @@ if (_post('send') == 'balance') {
foreach ($actives as $active) {
$router = ORM::for_table('tbl_routers')->where('name', $active['routers'])->find_one();
if ($router) {
r2(U . "order/send/$router[id]/$active[plan_id]&u=" . trim(_post('username')), 's', Lang::T('Review package before recharge'));
r2(getUrl('order/send/$router[id]/$active[plan_id]&u=') . trim(_post('username')), 's', Lang::T('Review package before recharge'));
}
}
r2(U . 'home', 'w', Lang::T('Your friend do not have active package'));
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'])) {
foreach ($_bill as $tur) {
if($tur['status'] == 'on'){
if ($tur['status'] == 'on') {
$p = ORM::for_table('tbl_plans')->findOne($tur['plan_id']);
if ($p) {
$c = ORM::for_table('tbl_customers')->findOne($tur['customer_id']);
@ -109,7 +109,11 @@ if (isset($_GET['sync']) && !empty($_GET['sync'])) {
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $p['device'])->add_customer($c, $p);
if (method_exists($dvc, 'sync_customer')) {
(new $p['device'])->sync_customer($c, $p);
}else{
(new $p['device'])->add_customer($c, $p);
}
} else {
new Exception(Lang::T("Devices Not Found"));
}
@ -123,7 +127,7 @@ if (isset($_GET['sync']) && !empty($_GET['sync'])) {
}
}
}
r2(U . 'home', 's', $log);
r2(getUrl('home'), 's', $log);
}
if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
@ -131,7 +135,7 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
_alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', "");
}
if (!empty(App::getTokenValue(_get('stoken')))) {
r2(U . "voucher/invoice/");
r2(getUrl('voucher/invoice/'));
die();
}
$bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['recharge'])->where('username', $user['username'])->findOne();
@ -142,29 +146,17 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
$routers = ORM::for_table('tbl_routers')->where('name', $bill['routers'])->find_one();
$router = $routers['id'];
}
if ($config['enable_balance'] == 'yes') {
$plan = ORM::for_table('tbl_plans')->find_one($bill['plan_id']);
if (!$plan['enabled']) {
r2(U . "home", 'e', 'Plan is not exists');
}
if ($user['balance'] > $plan['price']) {
r2(U . "order/pay/$router/$bill[plan_id]&stoken=" . _get('stoken'), 'e', 'Order Plan');
} else {
r2(U . "order/buy/$router/$bill[plan_id]", 'e', 'Order Plan');
}
} else {
r2(U . "order/buy/$router/$bill[plan_id]", 'e', 'Order Plan');
}
r2(getUrl("order/gateway/$router/$bill[plan_id]"));
}
} else if (!empty(_get('extend'))) {
if ($user['status'] != 'Active') {
_alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', "");
}
if (!$config['extend_expired']) {
r2(U . 'home', 'e', "cannot extend");
r2(getUrl('home'), 'e', "cannot extend");
}
if (!empty(App::getTokenValue(_get('stoken')))) {
r2(U . 'home', 'e', "You already extend");
r2(getUrl('home'), 'e', "You already extend");
}
$id = _get('extend');
$tur = ORM::for_table('tbl_user_recharges')->where('customer_id', $user['id'])->where('id', $id)->find_one();
@ -179,7 +171,7 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
// is already extend
$last = file_get_contents($path);
if ($last == $m) {
r2(U . 'home', 'e', "You already extend for this month");
r2(getUrl('home'), 'e', "You already extend for this month");
}
}
if ($tur['status'] != 'on') {
@ -188,6 +180,8 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
global $isChangePlan;
$isChangePlan = true;
(new $p['device'])->add_customer($user, $p);
} else {
new Exception(Lang::T("Devices Not Found"));
@ -202,17 +196,17 @@ 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']));
r2(U . 'home', 's', "Extend until $expiration");
r2(getUrl('home'), 's', "Extend until $expiration");
} else {
r2(U . 'home', 'e', "Plan is not expired");
r2(getUrl('home'), 'e', "Plan is not expired");
}
} else {
r2(U . 'home', 'e', "Plan Not Found or Not Active");
r2(getUrl('home'), 'e', "Plan Not Found or Not Active");
}
} else if (isset($_GET['deactivate']) && !empty($_GET['deactivate'])) {
$bill = ORM::for_table('tbl_user_recharges')->where('id', $_GET['deactivate'])->where('username', $user['username'])->findOne();
@ -233,9 +227,9 @@ if (isset($_GET['recharge']) && !empty($_GET['recharge'])) {
$bill->save();
_log('User ' . $bill['username'] . ' Deactivate ' . $bill['namebp'], 'Customer', $bill['customer_id']);
Message::sendTelegram('User u' . $bill['username'] . ' Deactivate ' . $bill['namebp']);
r2(U . 'home', 's', 'Success deactivate ' . $bill['namebp']);
r2(getUrl('home'), 's', 'Success deactivate ' . $bill['namebp']);
} else {
r2(U . 'home', 'e', 'No Active Plan');
r2(getUrl('home'), 'e', 'No Active Plan');
}
}
@ -250,10 +244,10 @@ if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && $_c['hs_auth_m
require_once $dvc;
if ($_GET['mikrotik'] == 'login') {
(new $p['device'])->connect_customer($user, $_SESSION['nux-ip'], $_SESSION['nux-mac'], $bill['routers']);
r2(U . 'home', 's', Lang::T('Login Request successfully'));
r2(getUrl('home'), 's', Lang::T('Login Request successfully'));
} else if ($_GET['mikrotik'] == 'logout') {
(new $p['device'])->disconnect_customer($user, $bill['routers']);
r2(U . 'home', 's', Lang::T('Logout Request successfully'));
r2(getUrl('home'), 's', Lang::T('Logout Request successfully'));
}
} else {
new Exception(Lang::T("Devices Not Found"));
@ -262,7 +256,7 @@ if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && $_c['hs_auth_m
}
if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && !empty($_SESSION['nux-hostname']) && $_c['hs_auth_method'] == 'hchap')) {
$apkurl = (((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'onoff')|| $_SERVER['SERVER_PORT'] == 443)?'https':'http').'://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$apkurl = (((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'onoff') || $_SERVER['SERVER_PORT'] == 443) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$ui->assign('nux_mac', $_SESSION['nux-mac']);
$ui->assign('nux_ip', $_SESSION['nux-ip']);
$keys = explode('-', $_SESSION['nux-key']);
@ -273,23 +267,23 @@ if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && !empty($_SESSI
$ui->assign('hchap', $_GET['hchap']);
$ui->assign('logged', $_GET['logged']);
if ($_app_stage != 'demo') {
if ($_GET['mikrotik'] == 'login') {
r2(U . 'home&hchap=true', 's', Lang::T('Login Request successfully'));
}
$getmsg = $_GET['msg'];
///get auth notification from mikrotik
if($getmsg == 'Connected') {
$msg .= Lang::T($getmsg);
r2(U . 'home&logged=1', 's', $msg);
} else if($getmsg){
$msg .= Lang::T($getmsg);
r2(U . 'home', 's', $msg);
}
if ($_GET['mikrotik'] == 'login') {
r2(getUrl('home&hchap=true'), 's', Lang::T('Login Request successfully'));
}
$getmsg = $_GET['msg'];
///get auth notification from mikrotik
if ($getmsg == 'Connected') {
$msg .= Lang::T($getmsg);
r2(getUrl('home&logged=1'), 's', $msg);
} else if ($getmsg) {
$msg .= Lang::T($getmsg);
r2(getUrl('home'), 's', $msg);
}
}
}
if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && !empty($_SESSION['nux-hostname']) && $_c['hs_auth_method'] == 'hchap')) {
$apkurl = (((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'onoff')|| $_SERVER['SERVER_PORT'] == 443)?'https':'http').'://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$apkurl = (((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'onoff') || $_SERVER['SERVER_PORT'] == 443) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$ui->assign('nux_mac', $_SESSION['nux-mac']);
$ui->assign('nux_ip', $_SESSION['nux-ip']);
$keys = explode('-', $_SESSION['nux-key']);
@ -300,29 +294,40 @@ if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && !empty($_SESSI
$ui->assign('hchap', $_GET['hchap']);
$ui->assign('logged', $_GET['logged']);
if ($_app_stage != 'demo') {
if ($_GET['mikrotik'] == 'login') {
r2(U . 'home&hchap=true', 's', Lang::T('Login Request successfully'));
}
$getmsg = $_GET['msg'];
///get auth notification from mikrotik
if($getmsg == 'Connected') {
$msg .= Lang::T($getmsg);
r2(U . 'home&logged=1', 's', $msg);
} else if($getmsg){
$msg .= Lang::T($getmsg);
r2(U . 'home', 's', $msg);
}
if ($_GET['mikrotik'] == 'login') {
r2(getUrl('home&hchap=true'), 's', Lang::T('Login Request successfully'));
}
$getmsg = $_GET['msg'];
///get auth notification from mikrotik
if ($getmsg == 'Connected') {
$msg .= Lang::T($getmsg);
r2(getUrl('home&logged=1'), 's', $msg);
} else if ($getmsg) {
$msg .= Lang::T($getmsg);
r2(getUrl('home'), 's', $msg);
}
}
}
$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) {
$widgets[$i]['content'] = $e->getMessage();
}
}
$ui->assign('widgets', $widgets);
$ui->assign('unpaid', ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one());
$ui->assign('code', alphanumeric(_get('code'), "-"));
$abills = User::getAttributes("Bill");
$ui->assign('abills', $abills);
run_hook('view_customer_dashboard'); #HOOK
$ui->display('user-ui/dashboard.tpl');
$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

@ -11,7 +11,7 @@ if ($maintenance_mode == true) {
}
if (User::getID()) {
r2(U . 'home');
r2(getUrl('home'));
}
if (isset($routes['1'])) {
@ -24,6 +24,11 @@ switch ($do) {
case 'post':
$username = _post('username');
$password = _post('password');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
_msglog('e', Lang::T('Invalid or Expired CSRF Token'));
r2(getUrl('login'));
}
run_hook('customer_login'); #HOOK
if ($username != '' and $password != '') {
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
@ -34,29 +39,41 @@ switch ($do) {
}
if (Password::_uverify($password, $d_pass) == true) {
$_SESSION['uid'] = $d['id'];
User::setCookie($d['id']);
$token = User::setCookie($d['id']);
$d->last_login = date('Y-m-d H:i:s');
$d->save();
_log($username . ' ' . Lang::T('Login Successful'), 'User', $d['id']);
if ($isApi) {
if ($token) {
showResult(true, Lang::T('Login Successful'), ['token' => "u." . $token]);
} else {
showResult(false, Lang::T('Invalid Username or Password'));
}
}
_alert(Lang::T('Login Successful'), 'success', "home");
} else {
_msglog('e', Lang::T('Invalid Username or Password'));
_log($username . ' ' . Lang::T('Failed Login'), 'User');
r2(U . 'login');
r2(getUrl('login'));
}
} else {
_msglog('e', Lang::T('Invalid Username or Password'));
r2(U . 'login');
r2(getUrl('login'));
}
} else {
_msglog('e', Lang::T('Invalid Username or Password'));
r2(U . 'login');
r2(getUrl('login'));
}
break;
case 'activation':
if (!empty(_post('voucher_only'))) {
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
_msglog('e', Lang::T('Invalid or Expired CSRF Token'));
r2(getUrl('login'));
}
$voucher = Text::alphanumeric(_post('voucher_only'), "-_.,");
$tur = ORM::for_table('tbl_user_recharges')
->where('username', $voucher)
@ -86,7 +103,7 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
} else {
new Exception(Lang::T("Devices Not Found"));
@ -95,13 +112,13 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
_alert(Lang::T("Voucher activation success, now you can login"), 'danger', $config['voucher_redirect']);
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, you are connected to internet"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, you are connected to internet"));
}
} else {
_alert(Lang::T('Internet Plan Expired'), 'danger', "login");
}
} else {
$v = ORM::for_table('tbl_voucher')->whereRaw("BINARY `code` = '$voucher'")->find_one();
$v = ORM::for_table('tbl_voucher')->whereRaw("BINARY code = '$voucher'")->find_one();
if (!$v) {
_alert(Lang::T('Voucher invalid'), 'danger', "login");
}
@ -131,7 +148,7 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
} else {
new Exception(Lang::T("Devices Not Found"));
@ -140,7 +157,7 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
_alert(Lang::T("Voucher activation success, now you can login"), 'danger', $config['voucher_redirect']);
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, you are connected to internet"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, you are connected to internet"));
}
} else {
_alert(Lang::T('Internet Plan Expired'), 'danger', "login");
@ -158,7 +175,7 @@ switch ($do) {
} else {
$voucher = Text::alphanumeric(_post('voucher'), "-_.,");
$username = _post('username');
$v1 = ORM::for_table('tbl_voucher')->whereRaw("BINARY `code` = '$voucher'")->find_one();
$v1 = ORM::for_table('tbl_voucher')->whereRaw("BINARY code = '$voucher'")->find_one();
if ($v1) {
// voucher exists, check customer exists or not
$user = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
@ -173,11 +190,11 @@ switch ($do) {
if ($d->save()) {
$user = ORM::for_table('tbl_customers')->where('username', $username)->find_one($d->id());
if (!$user) {
r2(U . 'login', 'e', Lang::T('Voucher activation failed'));
r2(getUrl('login'), 'e', Lang::T('Voucher activation failed'));
}
} else {
_alert(Lang::T('Login Successful'), 'success', "dashboard");
r2(U . 'login', 'e', Lang::T('Voucher activation failed') . '.');
r2(getUrl('login'), 'e', Lang::T('Voucher activation failed') . '.');
}
}
if ($v1['status'] == 0) {
@ -205,7 +222,7 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
} else {
new Exception(Lang::T("Devices Not Found"));
@ -214,26 +231,26 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, you are connected to internet"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, you are connected to internet"));
}
} catch (Exception $e) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
}
}
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
} else {
// if failed to recharge, restore old password
$user->password = $oldPass;
$user->save();
r2(U . 'login', 'e', Lang::T("Failed to activate voucher"));
r2(getUrl('login'), 'e', Lang::T("Failed to activate voucher"));
}
} else {
// used voucher
@ -252,7 +269,7 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
} else {
new Exception(Lang::T("Devices Not Found"));
@ -261,39 +278,80 @@ switch ($do) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
} catch (Exception $e) {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, now you can login"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
}
} else {
if (!empty($config['voucher_redirect'])) {
r2($config['voucher_redirect'], 's', Lang::T("Voucher activation success, you are connected to internet"));
} else {
r2(U . "login", 's', Lang::T("Voucher activation success, now you can login"));
r2(getUrl('login'), 's', Lang::T("Voucher activation success, now you can login"));
}
}
} else {
// voucher used by other customer
r2(U . 'login', 'e', Lang::T('Voucher Not Valid'));
r2(getUrl('login'), 'e', Lang::T('Voucher Not Valid'));
}
}
} else {
_msglog('e', Lang::T('Invalid Username or Password'));
r2(U . 'login');
r2(getUrl('login'));
}
}
default:
run_hook('customer_view_login'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
if ($config['disable_registration'] == 'yes') {
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_title', Lang::T('Activation'));
$ui->assign('code', alphanumeric(_get('code'), "-"));
$ui->display('user-ui/login-noreg.tpl');
$ui->display('customer/login-noreg.tpl');
} else {
$ui->display('user-ui/login.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'));
switch ($config['login_page_type']) {
case 'custom':
$ui->display('customer/login-custom-' . $config['login_Page_template'] . '.tpl');
break;
default:
$ui->display('customer/login.tpl');
break;
}
}
break;
}

View File

@ -1,12 +1,17 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Expires: Tue, 01 Jan 2000 00:00:00 GMT");
header("Pragma: no-cache");
run_hook('customer_logout'); #HOOK
if (session_status() == PHP_SESSION_NONE) session_start();
Admin::removeCookie();
User::removeCookie();
session_destroy();
_alert(Lang::T('Logout Successful'),'warning', "login");
_alert(Lang::T('Logout Successful'), 'warning', "login");

View File

@ -80,12 +80,45 @@ 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');
if (!empty($keep)) {
ORM::raw_execute("DELETE FROM tbl_logs WHERE UNIX_TIMESTAMP(date) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))");
r2(U . "logs/list/", 's', "Delete logs older than $keep days");
r2(getUrl('logs/list/'), 's', "Delete logs older than $keep days");
}
if ($q != '') {
$query = ORM::for_table('tbl_logs')->where_like('description', '%' . $q . '%')->order_by_desc('id');
@ -97,14 +130,14 @@ 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'));
$keep = _post('keep');
if (!empty($keep)) {
ORM::raw_execute("DELETE FROM radpostauth WHERE UNIX_TIMESTAMP(authdate) < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL $keep DAY))", [], 'radius');
r2(U . "logs/radius/", 's', "Delete logs older than $keep days");
r2(getUrl('logs/radius/'), 's', "Delete logs older than $keep days");
}
if ($q != '') {
$query = ORM::for_table('radpostauth', 'radius')->where_like('username', '%' . $q . '%')->order_by_desc('id');
@ -116,10 +149,37 @@ 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(U . 'logs/list/', 's', '');
r2(getUrl('logs/list/'), 's', '');
}

View File

@ -14,7 +14,7 @@ switch ($action) {
case 'view':
$mail = ORM::for_table('tbl_customers_inbox')->where('customer_id', $user['id'])->find_one($routes['2']);
if(!$mail){
r2(U. 'mail', 'e', Lang::T('Message Not Found'));
r2(getUrl('mail'), 'e', Lang::T('Message Not Found'));
}
if($mail['date_read'] == null){
$mail->date_read = date('Y-m-d H:i:s');
@ -29,14 +29,14 @@ switch ($action) {
$ui->assign('tipe', 'view');
$ui->assign('_system_menu', 'inbox');
$ui->assign('_title', Lang::T('Inbox'));
$ui->display('user-ui/inbox.tpl');
$ui->display('customer/inbox.tpl');
break;
case 'delete':
if($routes['2']){
if(ORM::for_table('tbl_customers_inbox')->where('customer_id', $user['id'])->where('id', $routes['2'])->find_one()->delete()){
r2(U. 'mail', 's', Lang::T('Mail Deleted Successfully'));
r2(getUrl('mail'), 's', Lang::T('Mail Deleted Successfully'));
}else{
r2(U. 'home', 'e', Lang::T('Failed to Delete Message'));
r2(getUrl('home'), 'e', Lang::T('Failed to Delete Message'));
}
break;
}
@ -57,5 +57,5 @@ switch ($action) {
$ui->assign('mails', $mails);
$ui->assign('_system_menu', 'inbox');
$ui->assign('_title', Lang::T('Inbox'));
$ui->display('user-ui/inbox.tpl');
$ui->display('customer/inbox.tpl');
}

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,13 +45,23 @@ 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(U . 'map/customer', 'e', 'action not defined');
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 './index.php?_route=autoload/customer_select2&s='+params.term;
return '{$appUrl}/?_route=autoload/customer_select2&s='+params.term;
}else{
return './index.php?_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(U . '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(U . '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(U . '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(U . '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(U . 'message/send_sms', 'e', 'action not defined');
r2(getUrl('message/send_sms'), 'e', 'action not defined');
}

View File

@ -15,30 +15,36 @@ switch ($action) {
$ui->assign('_system_menu', 'voucher');
$ui->assign('_title', Lang::T('Order Voucher'));
run_hook('customer_view_order'); #HOOK
$ui->display('user-ui/order.tpl');
$ui->display('customer/order.tpl');
break;
case 'history':
$ui->assign('_system_menu', 'history');
$query = ORM::for_table('tbl_payment_gateway')->where('username', $user['username'])->order_by_desc('id');
$query = ORM::for_table('tbl_payment_gateway')->where('user_id', $user['id'])->order_by_desc('id');
$d = Paginator::findMany($query);
if (empty($order) || $order < 5) {
$query = ORM::for_table('tbl_payment_gateway')->where('username', $user['username'])->order_by_desc('id');
$d = Paginator::findMany($query);
}
$ui->assign('d', $d);
$ui->assign('_title', Lang::T('Order History'));
run_hook('customer_view_order_history'); #HOOK
$ui->display('user-ui/orderHistory.tpl');
$ui->display('customer/orderHistory.tpl');
break;
case 'balance':
if (strpos($user['email'], '@') === false) {
r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address"));
r2(getUrl('accounts/profile'), 'e', Lang::T("Please enter your email address"));
}
$ui->assign('_title', 'Top Up');
$ui->assign('_system_menu', 'balance');
$plans_balance = ORM::for_table('tbl_plans')->where('enabled', '1')->where('type', 'Balance')->where('prepaid', 'yes')->find_many();
$ui->assign('plans_balance', $plans_balance);
$ui->display('user-ui/orderBalance.tpl');
$ui->display('customer/orderBalance.tpl');
break;
case 'package':
if (strpos($user['email'], '@') === false) {
r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address"));
r2(getUrl('accounts/profile'), 'e', Lang::T("Please enter your email address"));
}
$ui->assign('_title', 'Order Plan');
$ui->assign('_system_menu', 'package');
@ -113,14 +119,21 @@ switch ($action) {
->where('type', 'Hotspot')
->where('prepaid', 'yes')
->find_many();
$plans_vpn = ORM::for_table('tbl_plans')
->where('plan_type', $account_type)
->where('enabled', '1')->where('is_radius', 0)
->where('type', 'VPN')
->where('prepaid', 'yes')
->find_many();
}
$ui->assign('routers', $routers);
$ui->assign('radius_pppoe', $radius_pppoe);
$ui->assign('radius_hotspot', $radius_hotspot);
$ui->assign('plans_pppoe', $plans_pppoe);
$ui->assign('plans_hotspot', $plans_hotspot);
$ui->assign('plans_vpn', $plans_vpn);
run_hook('customer_view_order_plan'); #HOOK
$ui->display('user-ui/orderPlan.tpl');
$ui->display('customer/orderPlan.tpl');
break;
case 'unpaid':
$d = ORM::for_table('tbl_payment_gateway')
@ -131,12 +144,12 @@ switch ($action) {
r_find_unpaid'); #HOOK
if ($d) {
if (empty($d['pg_url_payment'])) {
r2(U . "order/buy/" . $trx['routers_id'] . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment"));
r2(getUrl('order/buy/') . $trx['routers_id'] . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment"));
} else {
r2(U . "order/view/" . $d['id'] . '/check/', 's', Lang::T("You have unpaid transaction"));
r2(getUrl('order/view/') . $d['id'] . '/check/', 's', Lang::T("You have unpaid transaction"));
}
} else {
r2(U . "order/package/", 's', Lang::T("You have no unpaid transaction"));
r2(getUrl('order/package/'), 's', Lang::T("You have no unpaid transaction"));
}
break;
case 'view':
@ -147,15 +160,15 @@ switch ($action) {
run_hook('customer_view_payment'); #HOOK
// jika tidak ditemukan, berarti punya orang lain
if (empty($trx)) {
r2(U . "order/package", 'w', Lang::T("Payment not found"));
r2(getUrl('order/package'), 'w', Lang::T("Payment not found"));
}
// jika url kosong, balikin ke buy, kecuali cancel
if ($trx['status'] == 1 && empty($trx['pg_url_payment']) && $routes['3'] != 'cancel') {
r2(U . "order/buy/" . (($trx['routers_id'] == 0) ? $trx['routers'] : $trx['routers_id']) . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment"));
r2(getUrl('order/buy/') . (($trx['routers_id'] == 0) ? $trx['routers'] : $trx['routers_id']) . '/' . $trx['plan_id'], 'w', Lang::T("Checking payment"));
}
if ($routes['3'] == 'check') {
if (!file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $trx['gateway'] . '.php')) {
r2(U . 'order/view/' . $trxid, 'e', Lang::T("No Payment Gateway Available"));
r2(getUrl('order/view/') . $trxid, 'e', Lang::T("No Payment Gateway Available"));
}
run_hook('customer_check_payment_status'); #HOOK
include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $trx['gateway'] . '.php';
@ -172,7 +185,7 @@ switch ($action) {
->find_one($trxid);
}
if (empty($trx)) {
r2(U . "order/package", 'e', Lang::T("Transaction Not found"));
r2(getUrl('order/package'), 'e', Lang::T("Transaction Not found"));
}
$router = ORM::for_table('tbl_routers')->where('name', $trx['routers'])->find_one();
@ -185,28 +198,26 @@ switch ($action) {
$ui->assign('plan', $plan);
$ui->assign('bandw', $bandw);
$ui->assign('_title', 'TRX #' . $trxid);
$ui->display('user-ui/orderView.tpl');
$ui->display('customer/orderView.tpl');
break;
case 'pay':
if ($config['enable_balance'] != 'yes') {
r2(U . "order/package", 'e', Lang::T("Balance not enabled"));
r2(getUrl('order/package'), 'e', Lang::T("Balance not enabled"));
}
if (!empty(App::getTokenValue($_GET['stoken']))) {
r2(U . "voucher/invoice/");
r2(getUrl('voucher/invoice/'));
die();
}
if ($user['status'] != 'Active') {
_alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', "");
}
$plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']);
if (empty($plan)) {
r2(U . "order/package", 'e', Lang::T("Plan Not found"));
}
if (!$plan['enabled']) {
r2(U . "home", 'e', 'Plan is not exists');
$plan = ORM::for_table('tbl_plans')->find_one($routes[3]);
if (!$plan) {
r2(getUrl('order/package'), 'e', Lang::T("Plan Not found"));
}
if ($plan['is_radius'] == '1') {
$router_name = 'radius';
$router = 'radius';
} else {
$router_name = $plan['routers'];
}
@ -230,27 +241,27 @@ switch ($action) {
$tax = 0;
}
// Tax calculation stop
if ($plan && $plan['enabled'] && $user['balance'] >= $plan['price'] + $tax) {
$total_cost = $plan['price'] + $add_cost + $tax;
if ($plan && $plan['enabled'] && $user['balance'] >= $total_cost) {
if (Package::rechargeUser($user['id'], $router_name, $plan['id'], 'Customer', 'Balance')) {
// if success, then get the balance
Balance::min($user['id'], $plan['price'] + $add_cost + $tax);
Balance::min($user['id'], $total_cost);
App::setToken($_GET['stoken'], "success");
r2(U . "voucher/invoice/", 's', Lang::T("Success to buy package"));
r2(getUrl('voucher/invoice/'), 's', Lang::T("Success to buy package"));
} else {
r2(U . "order/package", 'e', Lang::T("Failed to buy package"));
r2(getUrl('order/package'), 'e', Lang::T("Failed to buy package"));
Message::sendTelegram("Buy Package with Balance Failed\n\n#u$c[username] #buy \n" . $plan['name_plan'] .
"\nRouter: " . $router_name .
"\nPrice: " . $plan['price'] + $tax);
"\nPrice: " . $total_cost);
}
} else {
r2(U . "home", 'e', 'Plan is not exists');
r2(getUrl('order/gateway/$routes[2]/$routes[3]'), 'e', Lang::T("Insufficient balance"));
}
break;
case 'send':
if ($config['enable_balance'] != 'yes') {
r2(U . "order/package", 'e', Lang::T("Balance not enabled"));
r2(getUrl('order/package'), 'e', Lang::T("Balance not enabled"));
}
if ($user['status'] != 'Active') {
_alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', "");
@ -259,12 +270,13 @@ switch ($action) {
$ui->assign('_system_menu', 'package');
$plan = ORM::for_table('tbl_plans')->find_one($routes['3']);
if (empty($plan)) {
r2(U . "order/package", 'e', Lang::T("Plan Not found"));
r2(getUrl('order/package'), 'e', Lang::T("Plan Not found"));
}
if (!$plan['enabled']) {
r2(U . "home", 'e', 'Plan is not exists');
r2(getUrl('home'), 'e', 'Plan is not exists');
}
if ($routes['2'] == 'radius') {
if ($plan['is_radius'] == '1') {
$routes['2'] = 0;
$router_name = 'radius';
} else {
$router_name = $plan['routers'];
@ -300,13 +312,13 @@ switch ($action) {
}
if (!$target) {
r2(U . 'home', 'd', Lang::T('Username not found'));
r2(getUrl('home'), 'd', Lang::T('Username not found'));
}
if ($user['balance'] < $plan['price']) {
r2(U . 'home', 'd', Lang::T('insufficient balance'));
r2(getUrl('home'), 'd', Lang::T('insufficient balance'));
}
if ($user['username'] == $target['username']) {
r2(U . "order/pay/$routes[2]/$routes[3]", 's', '^_^ v');
r2(getUrl('order/pay/$routes[2]/$routes[3]'), 's', '^_^ v');
}
$active = ORM::for_table('tbl_user_recharges')
->where('username', _post('username'))
@ -314,7 +326,7 @@ switch ($action) {
->find_one();
if ($active && $active['plan_id'] != $plan['id']) {
r2(U . "order/package", 'e', Lang::T("Target has active plan, different with current plant.") . " [ <b>$active[namebp]</b> ]");
r2(getUrl('order/package'), 'e', Lang::T("Target has active plan, different with current plant.") . " [ <b>$active[namebp]</b> ]");
}
$result = Package::rechargeUser($target['id'], $router_name, $plan['id'], $user['username'], 'Balance');
if (!empty($result)) {
@ -323,6 +335,7 @@ switch ($action) {
//sender
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $user['username'];
$d->user_id = $user['id'];
$d->gateway = $target['username'];
$d->plan_id = $plan['id'];
$d->plan_name = $plan['name_plan'];
@ -342,6 +355,7 @@ switch ($action) {
//receiver
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $target['username'];
$d->user_id = $target['id'];
$d->gateway = $user['username'];
$d->plan_id = $plan['id'];
$d->plan_name = $plan['name_plan'];
@ -357,7 +371,7 @@ switch ($action) {
$d->trx_invoice = $result;
$d->status = 2;
$d->save();
r2(U . "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 .
@ -367,7 +381,7 @@ switch ($action) {
$errorMessage .= "\nTax: " . $tax;
}
r2(U . "order/package", 'e', Lang::T("Failed to Send package"));
r2(getUrl('order/package'), 'e', Lang::T("Failed to Send package"));
Message::sendTelegram($errorMessage);
}
}
@ -375,13 +389,13 @@ switch ($action) {
$ui->assign('router', $router_name);
$ui->assign('plan', $plan);
$ui->assign('tax', $tax);
$ui->display('user-ui/sendPlan.tpl');
$ui->display('customer/sendPlan.tpl');
break;
case 'gateway':
$ui->assign('_title', Lang::T('Select Payment Gateway'));
$ui->assign('_system_menu', 'package');
if (strpos($user['email'], '@') === false) {
r2(U . 'accounts/profile', 'e', Lang::T("Please enter your email address"));
r2(getUrl('accounts/profile'), 'e', Lang::T("Please enter your email address"));
}
$tax_enable = isset($config['enable_tax']) ? $config['enable_tax'] : 'no';
$tax_rate_setting = isset($config['tax_rate']) ? $config['tax_rate'] : null;
@ -396,32 +410,139 @@ 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'])) {
$_SESSION['coupon_attempts'] = 0;
$_SESSION['last_attempt_time'] = time();
}
if ($_SESSION['coupon_attempts'] >= 5) {
$timeout = 10 * 60; // 10 minutes in seconds
$time_diff = time() - $_SESSION['last_attempt_time'];
if ($time_diff >= $timeout) {
$_SESSION['coupon_attempts'] = 0;
$_SESSION['last_attempt_time'] = time();
} else {
$remaining_time = ceil(($timeout - $time_diff) / 60);
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Too many invalid attempts. Please try again after $remaining_time minutes."));
}
}
if (_post('coupon')) {
if ($plan['routers'] === 'balance') {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Coupon not available for Balance"));
}
$coupon = ORM::for_table('tbl_coupons')->where('code', _post('coupon'))->find_one();
if (!$coupon) {
$_SESSION['coupon_attempts']++;
$_SESSION['last_attempt_time'] = time();
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Coupon not found"));
}
if ($coupon['status'] != 'active') {
$_SESSION['coupon_attempts']++;
$_SESSION['last_attempt_time'] = time();
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Coupon is not active"));
}
// Reset attempts after a successful coupon validation
$_SESSION['coupon_attempts'] = 0;
$_SESSION['last_attempt_time'] = time();
$today = date('Y-m-d');
if ($today < $coupon['start_date'] || $today > $coupon['end_date']) {
$_SESSION['coupon_attempts']++;
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Coupon is not valid for today"));
}
if ($coupon['max_usage'] > 0 && $coupon['usage_count'] >= $coupon['max_usage']) {
$_SESSION['coupon_attempts']++;
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Coupon usage limit reached"));
}
if ($plan['price'] < $coupon['min_order_amount']) {
$_SESSION['coupon_attempts']++;
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("The order amount does not meet the minimum requirement for this coupon"));
}
// Calculate discount value
$discount = 0;
switch ($coupon['type']) {
case 'percent':
$discount = ($coupon['value'] / 100) * $plan['price'];
if ($discount > $coupon['max_discount_amount']) {
$discount = $coupon['max_discount_amount'];
}
break;
case 'fixed':
$discount = $coupon['value'];
break;
}
// Ensure discount does not exceed the plan price
if ($discount >= $plan['price']) {
r2($_SERVER['HTTP_REFERER'], 'e', Lang::T("Discount value exceeds the plan price"));
}
$plan['price'] -= $discount;
$coupon->usage_count = $coupon['usage_count'] + 1;
$coupon->save();
$ui->assign('discount', $discount);
$ui->assign('notify', Lang::T("Coupon applied successfully. You saved " . Lang::moneyFormat($discount)));
$ui->assign('notify_t', 's');
}
}
$tax = Package::tax($plan['price'] + $add_cost, $tax_rate);
$pgs = array_values(explode(',', $config['payment_gateway']));
if (count($pgs) == 0) {
sendTelegram("Payment Gateway not set, please set it in Settings");
_log(Lang::T("Payment Gateway not set, please set it in Settings"));
r2(U . "home", 'e', Lang::T("Failed to create Transaction.."));
r2(getUrl('home'), 'e', Lang::T("Failed to create Transaction.."));
}
if (count($pgs) > 0) {
$ui->assign('pgs', $pgs);
if ($tax_enable === 'yes') {
$ui->assign('tax', $tax);
}
if (_post('custom') == '1') {
if (_post('amount') > 0) {
$ui->assign('custom', '1');
$ui->assign('amount', _post('amount'));
} else {
r2(getUrl('order/balance'), 'e', Lang::T("Please enter amount"));
}
}
$ui->assign('route2', $routes[2]);
$ui->assign('route3', $routes[3]);
$ui->assign('add_cost', $add_cost);
$ui->assign('bills', $bills);
$ui->assign('plan', $plan);
$ui->display('user-ui/selectGateway.tpl');
$ui->display('customer/selectGateway.tpl');
break;
} else {
sendTelegram("Payment Gateway not set, please set it in Settings");
_log(Lang::T("Payment Gateway not set, please set it in Settings"));
r2(U . "home", 'e', Lang::T("Failed to create Transaction.."));
r2(getUrl('home'), 'e', Lang::T("Failed to create Transaction.."));
}
case 'buy':
$gateway = _post('gateway');
$discount = _post('discount') ?: 0;
if ($gateway == 'balance') {
unset($_SESSION['gateway']);
r2(getUrl('order/pay/') . $routes[2] . '/' . $routes[3]);
}
if (empty($gateway) && !empty($_SESSION['gateway'])) {
$gateway = $_SESSION['gateway'];
} else if (!empty($gateway)) {
@ -431,111 +552,156 @@ switch ($action) {
_alert(Lang::T('This account status') . ' : ' . Lang::T($user['status']), 'danger', "");
}
if (empty($gateway)) {
r2(U . 'order/gateway/' . $routes[2] . '/' . $routes[3], 'w', Lang::T("Please select Payment Gateway"));
r2(getUrl('order/gateway/') . $routes[2] . '/' . $routes[3], 'w', Lang::T("Please select Payment Gateway"));
}
run_hook('customer_buy_plan'); #HOOK
include $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $gateway . '.php';
call_user_func($gateway . '_validate_config');
$plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']);
if ($plan['is_radius'] == '1') {
$router['id'] = 0;
$router['name'] = 'radius';
} else if ($routes['2'] > 0) {
$router = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routes['2']);
} else {
$router['id'] = 0;
$router['name'] = 'balance';
}
if (empty($router) || empty($plan)) {
r2(U . "order/package", 'e', Lang::T("Plan Not found"));
}
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
if ($d) {
if ($d['pg_url_payment']) {
r2(U . "order/view/" . $d['id'], 'w', Lang::T("You already have unpaid transaction, cancel it or pay it."));
} else {
if ($gateway == $d['gateway']) {
$id = $d['id'];
switch (_post('custom')) {
case '1':
$amount = _post('amount');
$amount = (float) $amount;
if ($amount <= 0) {
r2(getUrl('order/gateway/') . $routes[2] . '/' . $routes[3], 'w', Lang::T("Please enter amount"));
}
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
if ($d) {
if ($d['pg_url_payment']) {
r2(getUrl('order/view/') . $d['id'], 'w', Lang::T("You already have unpaid transaction, cancel it or pay it."));
} else {
if ($gateway == $d['gateway']) {
$id = $d['id'];
} else {
$d->status = 4;
$d->save();
}
}
}
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $user['username'];
$d->user_id = $user['id'];
$d->gateway = $gateway;
$d->plan_id = 0;
$d->plan_name = 'Custom';
$d->routers_id = '0';
$d->routers = 'Custom Balance';
$d->price = $amount;
$d->created_date = date('Y-m-d H:i:s');
$d->status = 1;
$d->save();
$id = $d->id;
break;
default:
$plan = ORM::for_table('tbl_plans')->where('enabled', '1')->find_one($routes['3']);
if ($plan['is_radius'] == '1') {
$router['id'] = 0;
$router['name'] = 'radius';
} else if ($routes['2'] > 0) {
$router = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routes['2']);
} else {
$d->status = 4;
$router['id'] = 0;
$router['name'] = 'balance';
}
if (empty($router) || empty($plan)) {
r2(getUrl('order/package'), 'e', Lang::T("Plan Not found"));
}
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
if ($d) {
if ($d['pg_url_payment']) {
r2(getUrl('order/view/') . $d['id'], 'w', Lang::T("You already have unpaid transaction, cancel it or pay it."));
} else {
if ($gateway == $d['gateway']) {
$id = $d['id'];
} else {
$d->status = 4;
$d->save();
}
}
}
$add_cost = 0;
$tax = 0;
if ($router['name'] != 'balance') {
list($bills, $add_cost) = User::getBills($id_customer);
}
// 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;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
} else {
$tax_rate = $tax_rate_setting;
}
if ($tax_enable === 'yes') {
$tax = Package::tax($plan['price'], $tax_rate);
}
// Tax calculation stop
if (empty($id)) {
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $user['username'];
$d->user_id = $user['id'];
$d->gateway = $gateway;
$d->plan_id = $plan['id'];
$d->plan_name = $plan['name_plan'];
$d->routers_id = $router['id'];
$d->routers = $router['name'];
if ($plan['validity_unit'] == 'Period') {
// Postpaid price from field
$add_inv = User::getAttribute("Invoice", $id_customer);
if (empty($add_inv) or $add_inv == 0) {
$d->price = $plan['price'] + $add_cost + $tax - $discount;
} else {
$d->price = $add_inv + $add_cost + $tax - $discount;
}
} else {
$d->price = $plan['price'] + $add_cost + $tax - $discount;
}
$d->created_date = date('Y-m-d H:i:s');
$d->status = 1;
$d->save();
$id = $d->id();
} else {
$d->username = $user['username'];
$d->user_id = $user['id'];
$d->gateway = $gateway;
$d->plan_id = $plan['id'];
$d->plan_name = $plan['name_plan'];
$d->routers_id = $router['id'];
$d->routers = $router['name'];
if ($plan['validity_unit'] == 'Period') {
// Postpaid price from field
$add_inv = User::getAttribute("Invoice", $id_customer);
if (empty($add_inv) or $add_inv == 0) {
$d->price = ($plan['price'] + $add_cost + $tax - $discount);
} else {
$d->price = ($add_inv + $add_cost + $tax - $discount);
}
} else {
$d->price = ($plan['price'] + $add_cost + $tax - $discount);
}
//$d->price = ($plan['price'] + $add_cost);
$d->created_date = date('Y-m-d H:i:s');
$d->status = 1;
$d->save();
}
}
}
$add_cost = 0;
$tax = 0;
if ($router['name'] != 'balance') {
list($bills, $add_cost) = User::getBills($id_customer);
}
// 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;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
} else {
$tax_rate = $tax_rate_setting;
}
if ($tax_enable === 'yes') {
$tax = Package::tax($plan['price'], $tax_rate);
}
// Tax calculation stop
if (empty($id)) {
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $user['username'];
$d->gateway = $gateway;
$d->plan_id = $plan['id'];
$d->plan_name = $plan['name_plan'];
$d->routers_id = $router['id'];
$d->routers = $router['name'];
if ($plan['validity_unit'] == 'Period') {
// Postpaid price from field
$add_inv = User::getAttribute("Invoice", $id_customer);
if (empty($add_inv) or $add_inv == 0) {
$d->price = ($plan['price'] + $add_cost + $tax);
} else {
$d->price = ($add_inv + $add_cost + $tax);
}
} else {
$d->price = ($plan['price'] + $add_cost + $tax);
}
$d->created_date = date('Y-m-d H:i:s');
$d->status = 1;
$d->save();
$id = $d->id();
} else {
$d->username = $user['username'];
$d->gateway = $gateway;
$d->plan_id = $plan['id'];
$d->plan_name = $plan['name_plan'];
$d->routers_id = $router['id'];
$d->routers = $router['name'];
if ($plan['validity_unit'] == 'Period') {
// Postpaid price from field
$add_inv = User::getAttribute("Invoice", $id_customer);
if (empty($add_inv) or $add_inv == 0) {
$d->price = ($plan['price'] + $add_cost + $tax);
} else {
$d->price = ($add_inv + $add_cost + $tax);
}
} else {
$d->price = ($plan['price'] + $add_cost + $tax);
}
//$d->price = ($plan['price'] + $add_cost);
$d->created_date = date('Y-m-d H:i:s');
$d->status = 1;
$d->save();
break;
}
if (!$id) {
r2(U . "order/package/" . $d['id'], 'e', Lang::T("Failed to create Transaction.."));
r2(getUrl('order/package/') . $d['id'], 'e', Lang::T("Failed to create Transaction.."));
} else {
call_user_func($gateway . '_create_transaction', $d, $user);
}
break;
default:
r2(U . "order/package/", 's', '');
r2(getUrl('order/package/'), 's', '');
}

View File

@ -16,6 +16,6 @@ if(file_exists(__DIR__."/../../pages/".str_replace(".","",$action).".html")){
$ui->assign("PageFile",$action);
$ui->assign("pageHeader",$action);
run_hook('customer_view_page'); #HOOK
$ui->display('user-ui/pages.tpl');
$ui->display('customer/pages.tpl');
}else
$ui->display('user-ui/404.tpl');
$ui->display('customer/404.tpl');

View File

@ -26,7 +26,7 @@ if (strpos($action, "-reset") !== false) {
} else {
file_put_contents($path, Http::getData('https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/pages_template/' . $action . '.html'));
}
r2(U . 'pages/' . $action);
r2(getUrl('pages/') . $action);
} else if (strpos($action, "-post") === false) {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
@ -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");
@ -78,10 +78,10 @@ if (strpos($action, "-reset") !== false) {
file_put_contents("$PAGES_PATH/vouchers/" . _post('template_name') . '.html', $html);
}
}
r2(U . 'pages/' . $action, 's', Lang::T("Saving page success"));
r2(getUrl('pages/') . $action, 's', Lang::T("Saving page success"));
} else {
r2(U . 'pages/' . $action, 'e', Lang::T("Failed to save page, make sure i can write to folder pages, <i>chmod 664 pages/*.html<i>"));
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

@ -16,7 +16,7 @@ switch ($action) {
if (file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $pg . '.php')) {
deleteFile($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR, $pg);
}
r2(U . 'paymentgateway', 's', Lang::T('Payment Gateway Deleted'));
r2(getUrl('paymentgateway'), 's', Lang::T('Payment Gateway Deleted'));
case 'audit':
$pg = alphanumeric($routes[2]);
@ -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') {
@ -61,7 +61,7 @@ switch ($action) {
$d->value = $pgs;
$d->save();
}
r2(U . 'paymentgateway', 's', Lang::T('Payment Gateway saved successfully'));
r2(getUrl('paymentgateway'), 's', Lang::T('Payment Gateway saved successfully'));
}
if (file_exists($PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR . $action . '.php')) {
@ -70,18 +70,18 @@ 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 {
if (!empty($action)) {
r2(U . 'paymentgateway', 'w', Lang::T('Payment Gateway Not Found'));
r2(getUrl('paymentgateway'), 'w', Lang::T('Payment Gateway Not Found'));
} else {
$files = scandir($PAYMENTGATEWAY_PATH);
foreach ($files as $file) {
@ -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 './index.php?_route=autoload/customer_select2&s='+params.term;
return '{$appUrl}/?_route=autoload/customer_select2&s='+params.term;
}else{
return './index.php?_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'])) {
@ -49,7 +51,11 @@ switch ($action) {
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $p['device'])->add_customer($c, $p);
if (method_exists($dvc, 'sync_customer')) {
(new $p['device'])->sync_customer($c, $p);
} else {
(new $p['device'])->add_customer($c, $p);
}
} else {
new Exception(Lang::T("Devices Not Found"));
}
@ -62,7 +68,7 @@ switch ($action) {
$log .= "PLAN NOT FOUND : $tur[username], $tur[namebp], $tur[type], $tur[routers]<br>";
}
}
r2(U . 'plan/list', 's', $log);
r2(getUrl('plan/list'), 's', $log);
case 'recharge':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
@ -78,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':
@ -101,15 +107,39 @@ 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;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
} else {
$tax_rate = $tax_rate_setting;
}
if ($tax_enable === 'yes') {
$tax = Package::tax($plan['price'], $tax_rate);
} else {
$tax = 0;
}
// Tax calculation stop
$total_cost = $plan['price'] + $add_cost + $tax;
if ($using == 'balance' && $config['enable_balance'] == 'yes') {
if (!$cust) {
r2(U . 'plan/recharge', 'e', Lang::T('Customer not found'));
r2(getUrl('plan/recharge'), 'e', Lang::T('Customer not found'));
}
if (!$plan) {
r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
r2(getUrl('plan/recharge'), 'e', Lang::T('Plan not found'));
}
if ($cust['balance'] < ($plan['price'] + $add_cost)) {
r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
if ($cust['balance'] < $total_cost) {
r2(getUrl('plan/recharge'), 'e', Lang::T('insufficient balance'));
}
$gateway = 'Recharge Balance';
}
@ -122,6 +152,9 @@ switch ($action) {
if (count($usings) == 0) {
$usings[] = Lang::T('Cash');
}
if ($tax_enable === 'yes') {
$ui->assign('tax', $tax);
}
$ui->assign('usings', $usings);
$ui->assign('bills', $bills);
$ui->assign('add_cost', $add_cost);
@ -131,9 +164,10 @@ 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(U . 'plan/recharge', 'e', $msg);
r2(getUrl('plan/recharge'), 'e', $msg);
}
break;
@ -145,13 +179,15 @@ switch ($action) {
$server = _post('server');
$planId = _post('plan');
$using = _post('using');
$stoken = _post('stoken');
$svoucher = _post('svoucher');
if (!empty(App::getTokenValue($stoken))) {
$username = App::getTokenValue($stoken);
$plan = ORM::for_table('tbl_plans')->find_one($planId);
if (!empty(App::getVoucherValue($svoucher))) {
$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();
}
@ -165,16 +201,36 @@ switch ($action) {
$channel = $admin['fullname'];
$cust = User::_info($id_customer);
list($bills, $add_cost) = User::getBills($id_customer);
// 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;
if ($tax_rate_setting === 'custom') {
$tax_rate = $custom_tax_rate;
} else {
$tax_rate = $tax_rate_setting;
}
if ($tax_enable === 'yes') {
$tax = Package::tax($plan['price'], $tax_rate);
} else {
$tax = 0;
}
// Tax calculation stop
$total_cost = $plan['price'] + $add_cost + $tax;
if ($using == 'balance' && $config['enable_balance'] == 'yes') {
$plan = ORM::for_table('tbl_plans')->find_one($planId);
//$plan = ORM::for_table('tbl_plans')->find_one($planId);
if (!$cust) {
r2(U . 'plan/recharge', 'e', Lang::T('Customer not found'));
r2(getUrl('plan/recharge'), 'e', Lang::T('Customer not found'));
}
if (!$plan) {
r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
r2(getUrl('plan/recharge'), 'e', Lang::T('Plan not found'));
}
if ($cust['balance'] < ($plan['price'] + $add_cost)) {
r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
if ($cust['balance'] < $total_cost) {
r2(getUrl('plan/recharge'), 'e', Lang::T('insufficient balance'));
}
$gateway = 'Recharge Balance';
}
@ -185,18 +241,18 @@ switch ($action) {
}
if (Package::rechargeUser($id_customer, $server, $planId, $gateway, $channel)) {
if ($using == 'balance') {
Balance::min($cust['id'], $plan['price'] + $add_cost);
Balance::min($cust['id'], $total_cost);
}
$in = ORM::for_table('tbl_transactions')->where('username', $cust['username'])->order_by_desc('id')->find_one();
Package::createInvoice($in);
App::setToken($stoken, $cust['username']);
$ui->display('invoice.tpl');
App::setVoucher($svoucher, $cust['username']);
$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(U . 'plan/recharge', 'e', "Failed to recharge account");
r2(getUrl('plan/recharge'), 'e', "Failed to recharge account");
}
} else {
r2(U . 'plan/recharge', 'e', $msg);
r2(getUrl('plan/recharge'), 'e', $msg);
}
break;
@ -208,13 +264,26 @@ switch ($action) {
$c = ORM::for_table('tbl_customers')->where('username', $in['username'])->find_one();
if ($c) {
Message::sendInvoice($c, $in);
r2(U . 'plan/view/' . $id, 's', "Success send to customer");
r2(getUrl('plan/view/') . $id, 's', "Success send to customer");
}
r2(U . 'plan/view/' . $id, 'd', "Customer not found");
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;
@ -228,20 +297,22 @@ switch ($action) {
$ui->assign('content', $content);
} else {
$id = _post('id');
if (empty($id)) {
$id = $routes['2'];
}
$d = ORM::for_table('tbl_transactions')->where('id', $id)->find_one();
$ui->assign('in', $d);
$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);
@ -261,9 +332,9 @@ 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(U . 'plan/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('plan/list'), 'e', Lang::T('Account Not Found'));
}
break;
@ -271,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
@ -288,7 +359,7 @@ switch ($action) {
}
$d->delete();
_log('[' . $admin['username'] . ']: ' . 'Delete Plan for Customer ' . $c['username'] . ' [' . $in['plan_name'] . '][' . Lang::moneyFormat($in['price']) . ']', $admin['user_type'], $admin['id']);
r2(U . 'plan/list', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('plan/list'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -354,14 +425,14 @@ switch ($action) {
}
$d->save();
_log('[' . $admin['username'] . ']: ' . 'Edit Plan for Customer ' . $d['username'] . ' to [' . $d['namebp'] . '][' . Lang::moneyFormat($p['price']) . ']', $admin['user_type'], $admin['id']);
r2(U . 'plan/list', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('plan/list'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'plan/edit/' . $id, 'e', $msg);
r2(getUrl('plan/edit/') . $id, 'e', $msg);
}
break;
case 'voucher':
$ui->assign('_title', Lang::T('Vouchers'));
$ui->assign('_title', Lang::T('Voucher Cards'));
$search = _req('search');
$router = _req('router');
$customer = _req('customer');
@ -371,9 +442,10 @@ switch ($action) {
$ui->assign('customer', $customer);
$ui->assign('status', $status);
$ui->assign('plan', $plan);
$ui->assign('_system_menu', 'cards');
$query = ORM::for_table('tbl_plans')
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'));
->inner_join('tbl_voucher', ['tbl_plans.id', '=', 'tbl_voucher.id_plan']);
if (!empty($router)) {
$query->where('tbl_voucher.routers', $router);
@ -394,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) {
@ -458,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':
@ -473,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':
@ -492,7 +564,7 @@ switch ($action) {
$jml++;
}
}
r2(U . 'plan/voucher', 's', "$jml " . Lang::T('Data Deleted Successfully'));
r2(getUrl('plan/voucher'), 's', "$jml " . Lang::T('Data Deleted Successfully'));
}
case 'print-voucher':
$from_id = _post('from_id');
@ -500,12 +572,15 @@ switch ($action) {
$pagebreak = _post('pagebreak');
$limit = _post('limit');
$vpl = _post('vpl');
$selected_datetime = _post('selected_datetime');
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;
}
@ -542,6 +617,17 @@ switch ($action) {
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
->where('tbl_voucher.status', '0')
->where_gt('tbl_voucher.id', $from_id);
} else if ($from_id > 0 && $planid == 0 && $selected_datetime != '') {
$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_raw("DATE(created_at) = ?", [$selected_datetime])
->where_gt('tbl_voucher.id', $from_id)
->limit($limit);
$vc = ORM::for_table('tbl_plans')
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
->where('tbl_voucher.status', '0')
->where_gt('tbl_voucher.id', $from_id);
} else {
$v = ORM::for_table('tbl_plans')
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
@ -551,6 +637,16 @@ switch ($action) {
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
->where('tbl_voucher.status', '0');
}
if (!empty($selected_datetime)) {
$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)
->limit($limit);
$vc = ORM::for_table('tbl_plans')
->left_outer_join('tbl_voucher', array('tbl_plans.id', '=', 'tbl_voucher.id_plan'))
->where('tbl_voucher.status', '0');
}
if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
$v = $v->find_many();
$vc = $vc->count();
@ -577,6 +673,19 @@ switch ($action) {
$ui->assign('limit', $limit);
$ui->assign('planid', $planid);
$createdate = ORM::for_table('tbl_voucher')
->select_expr(
"CASE WHEN DATE(created_at) = CURDATE() THEN 'Today' ELSE DATE(created_at) END",
'created_datetime'
)
->where_not_equal('created_at', '0')
->select_expr('COUNT(*)', 'voucher_count')
->group_by('created_datetime')
->order_by_desc('created_datetime')
->find_array();
$ui->assign('createdate', $createdate);
$voucher = [];
$n = 1;
foreach ($v as $vs) {
@ -592,16 +701,21 @@ switch ($action) {
$ui->assign('voucher', $voucher);
$ui->assign('vc', $vc);
$ui->assign('selected_datetime', $selected_datetime);
//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');
$voucher_format = _post('voucher_format');
@ -609,18 +723,22 @@ switch ($action) {
$server = _post('server');
$numbervoucher = _post('numbervoucher');
$lengthcode = _post('lengthcode');
$printNow = _post('print_now', 'no');
$voucherPerPage = _post('voucher_per_page', '36');
$msg = '';
if ($type == '' or $plan == '' or $server == '' or $numbervoucher == '' or $lengthcode == '') {
$msg .= Lang::T('All field is required') . '<br>';
if (empty($type) || empty($plan) || empty($server) || empty($numbervoucher) || empty($lengthcode)) {
$msg .= Lang::T('All fields are required') . '<br>';
}
if (Validator::UnsignedNumber($numbervoucher) == false) {
if (!Validator::UnsignedNumber($numbervoucher)) {
$msg .= 'The Number of Vouchers must be a number' . '<br>';
}
if (Validator::UnsignedNumber($lengthcode) == false) {
if (!Validator::UnsignedNumber($lengthcode)) {
$msg .= 'The Length Code must be a number' . '<br>';
}
if ($msg == '') {
// Update or create voucher prefix
if (!empty($prefix)) {
$d = ORM::for_table('tbl_appconfig')->where('setting', 'voucher_prefix')->find_one();
if ($d) {
@ -633,11 +751,14 @@ switch ($action) {
$d->save();
}
}
run_hook('create_voucher'); #HOOK
run_hook('create_voucher'); // HOOK
$vouchers = [];
$newVoucherIds = [];
if ($voucher_format == 'numbers') {
if (strlen($lengthcode) < 6) {
$msg .= 'The Length Code must be a more than 6 for numbers' . '<br>';
if ($lengthcode < 6) {
$msg .= 'The Length Code must be more than 6 for numbers' . '<br>';
}
$vouchers = generateUniqueNumericVouchers($numbervoucher, $lengthcode);
} else {
@ -657,19 +778,91 @@ switch ($action) {
$d->type = $type;
$d->routers = $server;
$d->id_plan = $plan;
$d->code = $prefix . $code;
$d->code = "$prefix$code";
$d->user = '0';
$d->status = '0';
$d->generated_by = $admin['id'];
$d->save();
}
if ($numbervoucher == 1) {
r2(U . 'plan/voucher-view/' . $d->id(), 's', Lang::T('Create Vouchers Successfully'));
$newVoucherIds[] = $d->id();
}
r2(U . 'plan/voucher', 's', Lang::T('Create Vouchers Successfully'));
if ($printNow == 'yes' && count($newVoucherIds) > 0) {
$template = file_get_contents("pages/Voucher.html");
$template = str_replace('[[company_name]]', $config['CompanyName'], $template);
$vouchersToPrint = ORM::for_table('tbl_voucher')
->left_outer_join('tbl_plans', ['tbl_plans.id', '=', 'tbl_voucher.id_plan'])
->where_in('tbl_voucher.id', $newVoucherIds)
->find_many();
$voucherHtmls = [];
$n = 1;
foreach ($vouchersToPrint as $vs) {
$temp = $template;
$temp = str_replace('[[qrcode]]', '<img src="qrcode/?data=' . $vs['code'] . '">', $temp);
$temp = str_replace('[[price]]', Lang::moneyFormat($vs['price']), $temp);
$temp = str_replace('[[voucher_code]]', $vs['code'], $temp);
$temp = str_replace('[[plan]]', $vs['name_plan'], $temp);
$temp = str_replace('[[counter]]', $n, $temp);
$voucherHtmls[] = $temp;
$n++;
}
$vc = count($voucherHtmls);
$ui->assign('voucher', $voucherHtmls);
$ui->assign('vc', $vc);
$ui->assign('jml', 0);
$ui->assign('from_id', 0);
$ui->assign('vpl', '3');
$ui->assign('pagebreak', $voucherPerPage);
$ui->display('admin/print/voucher.tpl');
}
if ($numbervoucher == 1) {
r2(getUrl('plan/voucher-view/') . $d->id(), 's', Lang::T('Create Vouchers Successfully'));
}
r2(getUrl('plan/voucher'), 's', Lang::T('Create Vouchers Successfully'));
} else {
r2(U . 'plan/add-voucher/' . $id, 'e', $msg);
r2(getUrl('plan/add-voucher/') . $id, 'e', $msg);
}
break;
case 'voucher-delete-many':
header('Content-Type: application/json');
$admin = Admin::_info();
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
echo json_encode(['status' => 'error', 'message' => Lang::T('You do not have permission to access this page')]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$voucherIds = json_decode($_POST['voucherIds'], true);
if (is_array($voucherIds) && !empty($voucherIds)) {
$voucherIds = array_map('intval', $voucherIds);
try {
ORM::for_table('tbl_voucher')
->where_in('id', $voucherIds)
->delete_many();
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => Lang::T('Failed to delete vouchers.')]);
exit;
}
// Return success response
echo json_encode(['status' => 'success', 'message' => Lang::T("Vouchers Deleted Successfully.")]);
exit;
} else {
echo json_encode(['status' => 'error', 'message' => Lang::T("Invalid or missing voucher IDs.")]);
exit;
}
} else {
echo json_encode(['status' => 'error', 'message' => Lang::T("Invalid request method.")]);
}
break;
@ -687,11 +880,11 @@ switch ($action) {
$voucher = ORM::for_table('tbl_voucher')
->find_one($id);
if (!in_array($voucher['generated_by'], $sales)) {
r2(U . 'plan/voucher/', 'e', Lang::T('Voucher Not Found'));
r2(getUrl('plan/voucher/'), 'e', Lang::T('Voucher Not Found'));
}
}
if (!$voucher) {
r2(U . 'plan/voucher/', 'e', Lang::T('Voucher Not Found'));
r2(getUrl('plan/voucher/'), 'e', Lang::T('Voucher Not Found'));
}
$plan = ORM::for_table('tbl_plans')->find_one($voucher['id_plan']);
if ($voucher && $plan) {
@ -723,21 +916,21 @@ 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(U . 'plan/voucher/', 'e', Lang::T('Voucher Not Found'));
r2(getUrl('plan/voucher/'), 'e', Lang::T('Voucher Not Found'));
}
break;
case 'voucher-delete':
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) {
$d->delete();
r2(U . 'plan/voucher', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('plan/voucher'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -748,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;
@ -758,7 +951,7 @@ switch ($action) {
}
$code = Text::alphanumeric(_post('code'), "-_.,");
$user = ORM::for_table('tbl_customers')->where('id', _post('id_customer'))->find_one();
$v1 = ORM::for_table('tbl_voucher')->whereRaw("BINARY `code` = '$code'")->where('status', 0)->find_one();
$v1 = ORM::for_table('tbl_voucher')->whereRaw("BINARY code = '$code'")->where('status', 0)->find_one();
run_hook('refill_customer'); #HOOK
if ($v1) {
@ -768,12 +961,12 @@ 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(U . 'plan/refill', 'e', "Failed to refill account");
r2(getUrl('plan/refill'), 'e', "Failed to refill account");
}
} else {
r2(U . 'plan/refill', 'e', Lang::T('Voucher Not Valid'));
r2(getUrl('plan/refill'), 'e', Lang::T('Voucher Not Valid'));
}
break;
case 'deposit':
@ -788,46 +981,64 @@ 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'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$user = _post('id_customer');
$amount = _post('amount');
$plan = _post('id_plan');
$stoken = _req('stoken');
if (App::getTokenValue($stoken)) {
$c = ORM::for_table('tbl_customers')->where('id', $user)->find_one();
$in = ORM::for_table('tbl_transactions')->where('username', $c['username'])->order_by_desc('id')->find_one();
$note = _post('note');
$svoucher = _req('svoucher');
$c = ORM::for_table('tbl_customers')->find_one($user);
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();
}
run_hook('deposit_customer'); #HOOK
if (!empty($user) && !empty($plan)) {
if (Package::rechargeUser($user, 'balance', $plan, "Deposit", $admin['fullname'])) {
$c = ORM::for_table('tbl_customers')->where('id', $user)->find_one();
$in = ORM::for_table('tbl_transactions')->where('username', $c['username'])->order_by_desc('id')->find_one();
if (!empty($user) && strlen($amount) > 0 && $amount != 0) {
$plan = [];
$plan['name_plan'] = Lang::T('Balance');
$plan['price'] = $amount;
$trxId = Package::rechargeBalance($c, $plan, "Deposit", $admin['fullname'], $note);
if ($trxId > 0) {
$in = ORM::for_table('tbl_transactions')->find_one($trxId);
Package::createInvoice($in);
if (!empty($stoken)) {
App::setToken($stoken, $in['id']);
if (!empty($svoucher)) {
App::setVoucher($svoucher, $trxId);
}
$ui->display('invoice.tpl');
$ui->display('admin/plan/invoice.tpl');
} else {
r2(U . 'plan/refill', 'e', "Failed to refill account");
r2(getUrl('plan/refill'), 'e', "Failed to refill account");
}
} else if (!empty($user) && !empty($plan)) {
$p = ORM::for_table('tbl_plans')->find_one($plan);
$trxId = Package::rechargeBalance($c, $p, "Deposit", $admin['fullname'], $note);
if ($trxId > 0) {
$in = ORM::for_table('tbl_transactions')->find_one($trxId);
Package::createInvoice($in);
if (!empty($svoucher)) {
App::setVoucher($svoucher, $trxId);
}
$ui->display('admin/plan/invoice.tpl');
} else {
r2(getUrl('plan/refill'), 'e', "Failed to refill account");
}
} else {
r2(U . 'plan/refill', 'e', "All field is required");
r2(getUrl('plan/refill'), 'e', "All field is required");
}
break;
case 'extend':
$id = $routes[2];
$days = $routes[3];
$stoken = $_GET['stoken'];
if (App::getTokenValue($stoken)) {
r2(U . 'plan', 's', "Extend already done");
$svoucher = $_GET['svoucher'];
if (App::getVoucherValue($svoucher)) {
r2(getUrl('plan'), 's', "Extend already done");
}
$tur = ORM::for_table('tbl_user_recharges')->find_one($id);
$status = $tur['status'];
@ -839,7 +1050,7 @@ switch ($action) {
//expired
$expiration = date('Y-m-d', strtotime(" +$days day"));
}
App::setToken($stoken, $id);
App::setVoucher($svoucher, $id);
$c = ORM::for_table('tbl_customers')->findOne($tur['customer_id']);
if ($c) {
$p = ORM::for_table('tbl_plans')->find_one($tur['plan_id']);
@ -848,6 +1059,8 @@ switch ($action) {
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
global $isChangePlan;
$isChangePlan = true;
(new $p['device'])->add_customer($c, $p);
} else {
new Exception(Lang::T("Devices Not Found"));
@ -857,23 +1070,22 @@ switch ($action) {
$tur->status = "on";
$tur->save();
} else {
r2(U . 'plan', 's', "Plan not found");
r2(getUrl('plan'), 's', "Plan not found");
}
} else {
r2(U . 'plan', 's', "Customer not found");
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']);
r2(U . 'plan', 's', "Extend until $expiration");
_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(U . 'plan', 's', "Customer is not expired yet");
r2(getUrl('plan'), 's', "Customer is not expired yet");
}
break;
default:
$ui->assign('xfooter', '<script type="text/javascript" src="ui/lib/c/plan.js"></script>');
$ui->assign('_title', Lang::T('Customer'));
$search = _post('search');
$status = _req('status');
@ -914,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

@ -7,5 +7,5 @@
if(function_exists($routes[1])){
call_user_func($routes[1]);
}else{
r2(U.'dashboard', 'e', 'Function not found');
r2(getUrl('dashboard'), 'e', 'Function not found');
}

View File

@ -25,7 +25,7 @@ if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) {
$json = json_decode($txt, true);
if (empty($json['plugins']) && empty($json['payment_gateway'])) {
unlink($cache);
r2(U . 'pluginmanager');
r2(getUrl('pluginmanager'));
}
} else {
$data = Http::getData($plugin_repository);
@ -34,24 +34,25 @@ if (file_exists($cache) && time() - filemtime($cache) < (24 * 60 * 60)) {
}
switch ($action) {
case 'refresh':
if (file_exists($cache)) unlink($cache);
r2(U . "pluginmanager", 's', 'Refresh success');
if (file_exists($cache))
unlink($cache);
r2(getUrl('pluginmanager'), 's', 'Refresh success');
break;
case 'dlinstall':
if ($_app_stage == 'demo') {
r2(U . "pluginmanager", 'e', 'Demo Mode cannot install as it Security risk');
if ($_app_stage == 'Demo') {
r2(getUrl('pluginmanager'), 'e', 'Demo Mode cannot install as it Security risk');
}
if (!is_writeable($CACHE_PATH)) {
r2(U . "pluginmanager", 'e', 'Folder cache/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder cache/ is not writable');
}
if (!is_writeable($PLUGIN_PATH)) {
r2(U . "pluginmanager", 'e', 'Folder plugin/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder plugin/ is not writable');
}
if (!is_writeable($DEVICE_PATH)) {
r2(U . "pluginmanager", 'e', 'Folder devices/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder devices/ is not writable');
}
if (!is_writeable($UI_PATH . DIRECTORY_SEPARATOR . 'themes')) {
r2(U . "pluginmanager", 'e', 'Folder themes/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder themes/ is not writable');
}
$cache = $CACHE_PATH . DIRECTORY_SEPARATOR . 'installer' . DIRECTORY_SEPARATOR;
if (!file_exists($cache)) {
@ -97,7 +98,7 @@ switch ($action) {
}
//Cleaning
File::deleteFolder($cache);
r2(U . "pluginmanager", 's', 'Installation success');
r2(getUrl('pluginmanager'), 's', 'Installation success');
} else if (_post('gh_url', '') != '') {
$ghUrl = _post('gh_url', '');
if (!empty($config['github_token']) && !empty($config['github_username'])) {
@ -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;
@ -155,23 +156,27 @@ switch ($action) {
}
}
File::deleteFolder($cache);
r2(U . "pluginmanager", 's', 'Installation success');
r2(getUrl('pluginmanager'), 's', 'Installation success');
} else {
r2(U . 'pluginmanager', 'e', 'Nothing Installed');
r2(getUrl('pluginmanager'), 'e', 'Nothing Installed');
}
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(U . "pluginmanager", 'e', 'Folder cache/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder cache/ is not writable');
}
if (!is_writeable($PLUGIN_PATH)) {
r2(U . "pluginmanager", 'e', 'Folder plugin/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder plugin/ is not writable');
}
set_time_limit(-1);
$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) {
@ -199,12 +204,12 @@ switch ($action) {
$folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/');
}
if (!file_exists($folder)) {
r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown');
r2(getUrl('pluginmanager'), 'e', 'Extracted Folder is unknown');
}
scanAndRemovePath($folder, $PLUGIN_PATH . DIRECTORY_SEPARATOR);
File::deleteFolder($folder);
unlink($file);
r2(U . "pluginmanager", 's', 'Plugin ' . $plugin . ' has been deleted');
r2(getUrl('pluginmanager'), 's', 'Plugin ' . $plugin . ' has been deleted');
break;
}
}
@ -212,17 +217,21 @@ 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(U . "pluginmanager", 'e', 'Folder cache/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder cache/ is not writable');
}
if (!is_writeable($PLUGIN_PATH)) {
r2(U . "pluginmanager", 'e', 'Folder plugin/ is not writable');
r2(getUrl('pluginmanager'), 'e', 'Folder plugin/ is not writable');
}
set_time_limit(-1);
$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) {
@ -250,12 +259,12 @@ switch ($action) {
$folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/');
}
if (!file_exists($folder)) {
r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown');
r2(getUrl('pluginmanager'), 'e', 'Extracted Folder is unknown');
}
File::copyFolder($folder, $PLUGIN_PATH . DIRECTORY_SEPARATOR, ['README.md', 'LICENSE']);
File::deleteFolder($folder);
unlink($file);
r2(U . "pluginmanager", 's', 'Plugin ' . $plugin . ' has been installed');
r2(getUrl('pluginmanager'), 's', 'Plugin ' . $plugin . ' has been installed');
break;
}
}
@ -287,12 +296,12 @@ switch ($action) {
$folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/');
}
if (!file_exists($folder)) {
r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown');
r2(getUrl('pluginmanager'), 'e', 'Extracted Folder is unknown');
}
File::copyFolder($folder, $PAYMENTGATEWAY_PATH . DIRECTORY_SEPARATOR, ['README.md', 'LICENSE']);
File::deleteFolder($folder);
unlink($file);
r2(U . "paymentgateway", 's', 'Payment Gateway ' . $plugin . ' has been installed');
r2(getUrl('paymentgateway'), 's', 'Payment Gateway ' . $plugin . ' has been installed');
break;
}
}
@ -324,12 +333,12 @@ switch ($action) {
$folder = $CACHE_PATH . File::pathFixer('/' . $plugin . '-master/');
}
if (!file_exists($folder)) {
r2(U . "pluginmanager", 'e', 'Extracted Folder is unknown');
r2(getUrl('pluginmanager'), 'e', 'Extracted Folder is unknown');
}
File::copyFolder($folder, $DEVICE_PATH . DIRECTORY_SEPARATOR, ['README.md', 'LICENSE']);
File::deleteFolder($folder);
unlink($file);
r2(U . "settings/devices", 's', 'Device ' . $plugin . ' has been installed');
r2(getUrl('settings/devices'), 's', 'Device ' . $plugin . ' has been installed');
break;
}
}
@ -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="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,9 +48,9 @@ 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(U . 'pool/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('pool/list'), 'e', Lang::T('Account Not Found'));
}
break;
@ -65,7 +64,7 @@ switch ($action) {
}
$d->delete();
r2(U . 'pool/list', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('pool/list'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -78,7 +77,7 @@ switch ($action) {
$log .= 'DONE: ' . $pool['pool_name'] . ': ' . $pool['range_ip'] . '<br>';
}
}
r2(U . 'pool/list', 's', $log);
r2(getUrl('pool/list'), 's', $log);
break;
case 'add-post':
$name = _post('name');
@ -108,9 +107,9 @@ switch ($action) {
(new MikrotikPppoe())->add_pool($b);
}
$b->save();
r2(U . 'pool/list', 's', Lang::T('Data Created Successfully'));
r2(getUrl('pool/list'), 's', Lang::T('Data Created Successfully'));
} else {
r2(U . 'pool/add', 'e', $msg);
r2(getUrl('pool/add'), 'e', $msg);
}
break;
@ -143,12 +142,135 @@ switch ($action) {
(new MikrotikPppoe())->update_pool($old, $d);
}
r2(U . 'pool/list', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('pool/list'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'pool/edit/' . $id, 'e', $msg);
r2(getUrl('pool/edit/') . $id, 'e', $msg);
}
case 'port':
$name = _post('name');
if ($name != '') {
$query = ORM::for_table('tbl_port_pool')->where_like('pool_name', '%' . $name . '%')->order_by_desc('id');
$d = Paginator::findMany($query, ['name' => $name]);
} else {
$query = ORM::for_table('tbl_port_pool')->order_by_desc('id');
$d = Paginator::findMany($query);
}
$ui->assign('d', $d);
run_hook('view_port'); #HOOK
$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('admin/port/add.tpl');
break;
case 'edit-port':
$id = $routes['2'];
$d = ORM::for_table('tbl_port_pool')->find_one($id);
if ($d) {
$ui->assign('d', $d);
run_hook('view_edit_port'); #HOOK
$ui->display('admin/port/edit.tpl');
} else {
r2(getUrl('pool/port'), 'e', Lang::T('Account Not Found'));
}
break;
case 'delete-port':
$id = $routes['2'];
run_hook('delete_port'); #HOOK
$d = ORM::for_table('tbl_port_pool')->find_one($id);
if ($d) {
$d->delete();
r2(getUrl('pool/port'), 's', Lang::T('Data Deleted Successfully'));
}
break;
case 'sync':
$pools = ORM::for_table('tbl_port_pool')->find_many();
$log = '';
foreach ($pools as $pool) {
if ($pool['routers'] != 'radius') {
(new MikrotikPppoe())->update_pool($pool, $pool);
$log .= 'DONE: ' . $pool['port_name'] . ': ' . $pool['range_port'] . '<br>';
}
}
r2(getUrl('pool/list'), 's', $log);
break;
case 'add-port-post':
$name = _post('name');
$port_range = _post('port_range');
$public_ip = _post('public_ip');
$routers = _post('routers');
run_hook('add_pool'); #HOOK
$msg = '';
if (Validator::Length($name, 30, 2) == false) {
$msg .= 'Name should be between 3 to 30 characters' . '<br>';
}
if ($port_range == '' or $routers == '') {
$msg .= Lang::T('All field is required') . '<br>';
}
$d = ORM::for_table('tbl_port_pool')->where('routers', $routers)->find_one();
if ($d) {
$msg .= Lang::T('Routers already have ports, each router can only have 1 port range!') . '<br>';
}
if ($msg == '') {
$b = ORM::for_table('tbl_port_pool')->create();
$b->public_ip = $public_ip;
$b->port_name = $name;
$b->range_port = $port_range;
$b->routers = $routers;
$b->save();
r2(getUrl('pool/port'), 's', Lang::T('Data Created Successfully'));
} else {
r2(getUrl('pool/add-port'), 'e', $msg);
}
break;
case 'edit-port-post':
$name = _post('name');
$public_ip = _post('public_ip');
$range_port = _post('range_port');
$routers = _post('routers');
run_hook('edit_port'); #HOOK
$msg = '';
$msg = '';
if (Validator::Length($name, 30, 2) == false) {
$msg .= 'Name should be between 3 to 30 characters' . '<br>';
}
if ($range_port == '' or $routers == '') {
$msg .= Lang::T('All field is required') . '<br>';
}
$id = _post('id');
$d = ORM::for_table('tbl_port_pool')->find_one($id);
$old = ORM::for_table('tbl_port_pool')->find_one($id);
if (!$d) {
$msg .= Lang::T('Data Not Found') . '<br>';
}
if ($msg == '') {
$d->port_name = $name;
$d->public_ip = $public_ip;
$d->range_port = $range_port;
$d->routers = $routers;
$d->save();
r2(getUrl('pool/port'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(getUrl('pool/edit-port/') . $id, 'e', $msg);
}
break;
default:
r2(U . 'pool/list/', 's', '');
r2(getUrl('pool/list/'), 's', '');
}

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');
@ -58,12 +58,12 @@ switch ($action) {
if ($msg == '') {
require_once $DEVICE_PATH . DIRECTORY_SEPARATOR . "Radius.php";
if ((new Radius())->nasAdd($shortname, $nasname, $ports, $secret, $routers, $description, $type, $server, $community) > 0) {
r2(U . 'radius/nas-list/', 's', "NAS Added");
r2(getUrl('radius/nas-list/'), 's', "NAS Added");
} else {
r2(U . 'radius/nas-add/', 'e', "NAS Added Failed");
r2(getUrl('radius/nas-add/'), 'e', "NAS Added Failed");
}
} else {
r2(U . 'radius/nas-add', 'e', $msg);
r2(getUrl('radius/nas-add'), 'e', $msg);
}
break;
case 'nas-edit':
@ -78,9 +78,9 @@ 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(U . 'radius/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('radius/list'), 'e', Lang::T('Account Not Found'));
}
break;
@ -115,12 +115,12 @@ switch ($action) {
if ($msg == '') {
require_once $DEVICE_PATH . DIRECTORY_SEPARATOR . "Radius.php";
if ((new Radius())->nasUpdate($id, $shortname, $nasname, $ports, $secret, $routers, $description, $type, $server, $community)) {
r2(U . 'radius/list/', 's', "NAS Saved");
r2(getUrl('radius/list/'), 's', "NAS Saved");
} else {
r2(U . 'radius/nas-add', 'e', 'NAS NOT Exists');
r2(getUrl('radius/nas-add'), 'e', 'NAS NOT Exists');
}
} else {
r2(U . 'radius/nas-add', 'e', $msg);
r2(getUrl('radius/nas-add'), 'e', $msg);
}
break;
case 'nas-delete':
@ -129,7 +129,7 @@ switch ($action) {
if ($d) {
$d->delete();
} else {
r2(U . 'radius/nas-list', 'e', 'NAS Not found');
r2(getUrl('radius/nas-list'), 'e', 'NAS Not found');
}
default:
$ui->assign('_system_menu', 'radius');
@ -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

@ -5,6 +5,9 @@
* by https://t.me/ibnux
**/
if ($_c['disable_registration'] == 'noreg') {
_alert(Lang::T('Registration Disabled'), 'danger', "login");
}
if (isset($routes['1'])) {
$do = $routes['1'];
} else {
@ -22,36 +25,39 @@ switch ($do) {
$password = _post('password');
$cpassword = _post('cpassword');
$address = _post('address');
if (!empty($config['sms_url']) && $_c['allow_phone_otp'] == 'yes') {
$phonenumber = Lang::phoneFormat($username);
$username = $phonenumber;
} else if (strlen($username) < 21) {
$phonenumber = $username;
}
// Separate phone number input if OTP is required
$phone_number = ($config['sms_otp_registration'] == 'yes') ? alphanumeric(_post('phone_number'), "+_.@-") : $username;
$msg = '';
if (Validator::Length($username, 35, 2) == false) {
$msg .= 'Username should be between 3 to 55 characters' . '<br>';
$msg .= "Username should be between 3 to 55 characters<br>";
}
if (Validator::Length($fullname, 36, 2) == false) {
$msg .= 'Full Name should be between 3 to 25 characters' . '<br>';
if ($config['man_fields_fname'] == 'yes') {
if (Validator::Length($fullname, 36, 2) == false) {
$msg .= "Full Name should be between 3 to 25 characters<br>";
}
}
if (!Validator::Length($password, 35, 2)) {
$msg .= 'Password should be between 3 to 35 characters' . '<br>';
$msg .= "Password should be between 3 to 35 characters<br>";
}
if (!Validator::Email($email)) {
$msg .= 'Email is not Valid<br>';
if ($config['man_fields_email'] == 'yes') {
if (!Validator::Email($email)) {
$msg .= 'Email is not Valid<br>';
}
}
if ($password != $cpassword) {
$msg .= Lang::T('Passwords does not match') . '<br>';
}
if (!empty($config['sms_url']) && $_c['allow_phone_otp'] == 'yes') {
$otpPath .= sha1($username . $db_pass) . ".txt";
// OTP verification if OTP is enabled
if ($_c['sms_otp_registration'] == 'yes') {
$otpPath .= sha1("$phone_number$db_pass") . ".txt";
run_hook('validate_otp'); #HOOK
//expired 10 minutes
// Expire after 10 minutes
if (file_exists($otpPath) && time() - filemtime($otpPath) > 1200) {
unlink($otpPath);
r2(U . 'register', 's', 'Verification code expired');
r2(getUrl('register'), 's', 'Verification code expired');
} else if (file_exists($otpPath)) {
$code = file_get_contents($otpPath);
if ($code != $otp_code) {
@ -59,97 +65,222 @@ switch ($do) {
$ui->assign('fullname', $fullname);
$ui->assign('address', $address);
$ui->assign('email', $email);
$ui->assign('phonenumber', $phonenumber);
$ui->assign('phone_number', $phone_number);
$ui->assign('notify', 'Wrong Verification code');
$ui->assign('notify_t', 'd');
$ui->display('user-ui/register-otp.tpl');
$ui->assign('_title', Lang::T('Register'));
$ui->display('customer/register-otp.tpl');
exit();
} else {
unlink($otpPath);
}
} else {
r2(U . 'register', 's', 'No Verification code');
r2(getUrl('register'), 's', 'No Verification code');
}
}
// Check if username already exists
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
if ($d) {
$msg .= Lang::T('Account already axist') . '<br>';
$msg .= Lang::T('Account already exists') . '<br>';
}
if ($msg == '') {
run_hook('register_user'); #HOOK
$d = ORM::for_table('tbl_customers')->create();
$d->username = alphanumeric($username, "+_.@-");
$d->password = $password;
$d->fullname = $fullname;
$d->address = $address;
$d->email = $email;
$d->phonenumber = $phonenumber;
$d->phonenumber = $phone_number;
if ($d->save()) {
$user = $d->id();
r2(U . 'login', 's', Lang::T('Register Success! You can login now'));
if ($config['photo_register'] == 'yes' && !empty($_FILES['photo']['name']) && file_exists($_FILES['photo']['tmp_name'])) {
if (function_exists('imagecreatetruecolor')) {
$hash = md5_file($_FILES['photo']['tmp_name']);
$subfolder = substr($hash, 0, 2);
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR . $subfolder . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$imgPath = $folder . $hash . '.jpg';
File::resizeCropImage($_FILES['photo']['tmp_name'], $imgPath, 1600, 1600, 100);
$d->photo = '/photos/' . $subfolder . '/' . $hash . '.jpg';
$d->save();
}
}
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>';
if ($config['reg_nofify_admin'] == 'yes') {
sendTelegram($config['CompanyName'] . ' - ' . Lang::T('New User Registration') . "\n\nFull Name: " . $fullname . "\nUsername: " . $username . "\nEmail: " . $email . "\nPhone Number: " . $phone_number . "\nAddress: " . $address);
}
r2(getUrl('login'), 's', Lang::T('Register Success! You can login now'));
} else {
$ui->assign('username', $username);
$ui->assign('fullname', $fullname);
$ui->assign('address', $address);
$ui->assign('email', $email);
$ui->assign('phonenumber', $phonenumber);
$ui->assign('phone_number', $phone_number);
$ui->assign('notify', 'Failed to register');
$ui->assign('notify_t', 'd');
$ui->assign('_title', Lang::T('Register'));
run_hook('view_otp_register'); #HOOK
$ui->display('user-ui/register-rotp.tpl');
$ui->display('customer/register-rotp.tpl');
}
} else {
$ui->assign('username', $username);
$ui->assign('fullname', $fullname);
$ui->assign('address', $address);
$ui->assign('email', $email);
$ui->assign('phonenumber', $phonenumber);
$ui->assign('phone_number', $phone_number);
$ui->assign('notify', $msg);
$ui->assign('notify_t', 'd');
$ui->display('user-ui/register.tpl');
$ui->assign('_title', Lang::T('Register'));
// Check if OTP is enabled
if (!empty($config['sms_url']) && $_c['sms_otp_registration'] == 'yes') {
// Display register-otp.tpl if OTP is enabled
$ui->display('customer/register-otp.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));
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;
default:
if (!empty($config['sms_url']) && $_c['allow_phone_otp'] == 'yes') {
$username = _post('username');
if (!empty($username)) {
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
if ($_c['sms_otp_registration'] == 'yes') {
$phone_number = _post('phone_number');
if (!empty($phone_number)) {
$d = ORM::for_table('tbl_customers')->where('username', $phone_number)->find_one();
if ($d) {
r2(U . 'register', 's', Lang::T('Account already axist'));
r2(getUrl('register'), 's', Lang::T('Account already exists'));
}
if (!file_exists($otpPath)) {
mkdir($otpPath);
touch($otpPath . 'index.html');
}
$otpPath .= sha1($username . $db_pass) . ".txt";
//expired 10 minutes
$otpPath .= sha1($phone_number . $db_pass) . ".txt";
if (file_exists($otpPath) && time() - filemtime($otpPath) < 600) {
$ui->assign('username', $username);
$ui->assign('phone_number', $phone_number);
$ui->assign('notify', 'Please wait ' . (600 - (time() - filemtime($otpPath))) . ' seconds before sending another SMS');
$ui->assign('notify_t', 'd');
$ui->display('user-ui/register-otp.tpl');
$ui->assign('_title', Lang::T('Register'));
$ui->display('customer/register-otp.tpl');
} else {
$otp = rand(100000, 999999);
file_put_contents($otpPath, $otp);
Message::sendSMS($username, $config['CompanyName'] . "\nYour Verification code are: $otp");
$ui->assign('username', $username);
$ui->assign('notify', 'Verification code has been sent to your phone');
if ($config['phone_otp_type'] == 'whatsapp') {
Message::sendWhatsapp($phone_number, $config['CompanyName'] . "\n\n" . Lang::T("Registration code") . "\n$otp");
} else if ($config['phone_otp_type'] == 'both') {
Message::sendWhatsapp($phone_number, $config['CompanyName'] . "\n\n" . Lang::T("Registration code") . "\n$otp");
Message::sendSMS($phone_number, $config['CompanyName'] . "\n\n" . Lang::T("Registration code") . "\n$otp");
} else {
Message::sendSMS($phone_number, $config['CompanyName'] . "\n\n" . Lang::T("Registration code") . "\n$otp");
}
$ui->assign('phone_number', $phone_number);
$ui->assign('notify', 'Registration code has been sent to your phone');
$ui->assign('notify_t', 's');
$ui->display('user-ui/register-otp.tpl');
$ui->assign('_title', Lang::T('Register'));
$ui->assign('customFields', User::getFormCustomField($ui, true));
$ui->display('customer/register-otp.tpl');
}
} else {
$ui->assign('_title', Lang::T('Register'));
run_hook('view_otp_register'); #HOOK
$ui->display('user-ui/register-rotp.tpl');
$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', "");
$ui->assign('address', "");
$ui->assign('email', "");
$ui->assign('otp', false);
$ui->assign('_title', Lang::T('Register'));
run_hook('view_register'); #HOOK
$ui->display('user-ui/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(U . "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,9 +38,9 @@ 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(U . 'routers/list', 'e', Lang::T('Account Not Found'));
r2(getUrl('routers/list'), 'e', Lang::T('Account Not Found'));
}
break;
@ -65,7 +50,7 @@ switch ($action) {
$d = ORM::for_table('tbl_routers')->find_one($id);
if ($d) {
$d->delete();
r2(U . 'routers/list', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('routers/list'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -109,9 +94,9 @@ switch ($action) {
$d->enabled = $enabled;
$d->save();
r2(U . 'routers/edit/' . $d->id(), 's', Lang::T('Data Created Successfully'));
r2(getUrl('routers/edit/') . $d->id(), 's', Lang::T('Data Created Successfully'));
} else {
r2(U . 'routers/add', 'e', $msg);
r2(getUrl('routers/add'), 'e', $msg);
}
break;
@ -197,14 +182,13 @@ switch ($action) {
$p->set('routers', $name);
$p->save();
}
r2(U . 'routers/list', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('routers/list'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'routers/edit/' . $id, 'e', $msg);
r2(getUrl('routers/edit/') . $id, 'e', $msg);
}
break;
default:
$ui->assign('xfooter', '<script type="text/javascript" src="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

@ -2,7 +2,7 @@
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
if (!empty($query)) {
if (!empty($query)) {
$results = ORM::for_table('tbl_customers')
->where_like('username', "%$query%")
->find_many();

View File

@ -33,7 +33,7 @@ switch ($action) {
}
}
}
r2(U . 'services/hotspot', 's', $log);
r2(getUrl('services/hotspot'), 's', $log);
} else if ($routes['2'] == 'pppoe') {
$plans = ORM::for_table('tbl_plans')->where('type', 'PPPOE')->find_many();
$log = '';
@ -49,11 +49,10 @@ switch ($action) {
}
}
}
r2(U . 'services/pppoe', 's', $log);
r2(getUrl('services/pppoe'), 's', $log);
}
r2(U . 'services/hotspot', 'w', 'Unknown command');
r2(getUrl('services/hotspot'), 'w', 'Unknown command');
case 'hotspot':
$ui->assign('xfooter', '<script type="text/javascript" src="ui/lib/c/hotspot.js"></script>');
$name = _req('name');
$type1 = _req('type1');
$type2 = _req('type2');
@ -83,9 +82,9 @@ switch ($action) {
$bws = ORM::for_table('tbl_plans')->distinct()->select("id_bw")->where('tbl_plans.type', 'Hotspot')->findArray();
$ids = array_column($bws, 'id_bw');
if(count($ids)){
if (count($ids)) {
$ui->assign('bws', ORM::for_table('tbl_bandwidth')->select("id")->select('name_bw')->where_id_in($ids)->findArray());
}else{
} else {
$ui->assign('bws', []);
}
$ui->assign('type2s', ORM::for_table('tbl_plans')->getEnum("plan_type"));
@ -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,9 +190,9 @@ 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(U . 'services/hotspot', 'e', Lang::T('Account Not Found'));
r2(getUrl('services/hotspot'), 'e', Lang::T('Account Not Found'));
}
break;
@ -214,7 +213,7 @@ switch ($action) {
}
$d->delete();
r2(U . 'services/hotspot', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('services/hotspot'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -307,9 +306,9 @@ switch ($action) {
new Exception(Lang::T("Devices Not Found"));
}
}
r2(U . 'services/edit/' . $d->id(), 's', Lang::T('Data Created Successfully'));
r2(getUrl('services/edit/') . $d->id(), 's', Lang::T('Data Created Successfully'));
} else {
r2(U . 'services/add', 'e', $msg);
r2(getUrl('services/add'), 'e', $msg);
}
break;
@ -321,6 +320,7 @@ switch ($action) {
$id_bw = _post('id_bw');
$typebp = _post('typebp');
$price = _post('price');
$price_old = _post('price_old');
$limit_type = _post('limit_type');
$time_limit = _post('time_limit');
$time_unit = _post('time_unit');
@ -353,6 +353,11 @@ switch ($action) {
} else {
$msg .= Lang::T('Data Not Found') . '<br>';
}
if ($price_old <= $price) {
$price_old = '';
}
run_hook('edit_plan'); #HOOK
if ($msg == '') {
$b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one();
@ -378,6 +383,7 @@ switch ($action) {
$d->name_plan = $name;
$d->id_bw = $id_bw;
$d->price = $price; // Set price with or without tax based on configuration
$d->price_old = $price_old;
$d->typebp = $typebp;
$d->limit_type = $limit_type;
$d->time_limit = $time_limit;
@ -413,15 +419,14 @@ switch ($action) {
new Exception(Lang::T("Devices Not Found"));
}
}
r2(U . 'services/hotspot', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('services/hotspot'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'services/edit/' . $id, 'e', $msg);
r2(getUrl('services/edit/') . $id, 'e', $msg);
}
break;
case 'pppoe':
$ui->assign('_title', Lang::T('PPPOE Plans'));
$ui->assign('xfooter', '<script type="text/javascript" src="ui/lib/c/pppoe.js"></script>');
$name = _post('name');
$name = _req('name');
@ -453,9 +458,9 @@ switch ($action) {
$bws = ORM::for_table('tbl_plans')->distinct()->select("id_bw")->where('tbl_plans.type', 'PPPOE')->findArray();
$ids = array_column($bws, 'id_bw');
if(count($ids)){
if (count($ids)) {
$ui->assign('bws', ORM::for_table('tbl_bandwidth')->select("id")->select('name_bw')->where_id_in($ids)->findArray());
}else{
} else {
$ui->assign('bws', []);
}
$ui->assign('type2s', ORM::for_table('tbl_plans')->getEnum("plan_type"));
@ -509,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':
@ -528,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':
@ -571,9 +576,9 @@ 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(U . 'services/pppoe', 'e', Lang::T('Account Not Found'));
r2(getUrl('services/pppoe'), 'e', Lang::T('Account Not Found'));
}
break;
@ -595,7 +600,7 @@ switch ($action) {
}
$d->delete();
r2(U . 'services/pppoe', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('services/pppoe'), 's', Lang::T('Data Deleted Successfully'));
}
break;
@ -693,9 +698,9 @@ switch ($action) {
new Exception(Lang::T("Devices Not Found"));
}
}
r2(U . 'services/pppoe', 's', Lang::T('Data Created Successfully'));
r2(getUrl('services/pppoe'), 's', Lang::T('Data Created Successfully'));
} else {
r2(U . 'services/pppoe-add', 'e', $msg);
r2(getUrl('services/pppoe-add'), 'e', $msg);
}
break;
@ -705,6 +710,7 @@ switch ($action) {
$name = _post('name_plan');
$id_bw = _post('id_bw');
$price = _post('price');
$price_old = _post('price_old');
$validity = _post('validity');
$validity_unit = _post('validity_unit');
$routers = _post('routers');
@ -728,6 +734,10 @@ switch ($action) {
$msg .= Lang::T('All field is required') . '<br>';
}
if ($price_old <= $price) {
$price_old = '';
}
$d = ORM::for_table('tbl_plans')->where('id', $id)->find_one();
$old = ORM::for_table('tbl_plans')->where('id', $id)->find_one();
if ($d) {
@ -758,6 +768,7 @@ switch ($action) {
$d->name_plan = $name;
$d->id_bw = $id_bw;
$d->price = $price;
$d->price_old = $price_old;
$d->plan_type = $plan_type;
$d->validity = $validity;
$d->validity_unit = $validity_unit;
@ -788,9 +799,9 @@ switch ($action) {
new Exception(Lang::T("Devices Not Found"));
}
}
r2(U . 'services/pppoe', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('services/pppoe'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'services/pppoe-edit/' . $id, 'e', $msg);
r2(getUrl('services/pppoe-edit/') . $id, 'e', $msg);
}
break;
case 'balance':
@ -806,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'));
@ -819,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'];
@ -828,13 +839,14 @@ switch ($action) {
if ($d) {
run_hook('delete_balance'); #HOOK
$d->delete();
r2(U . 'services/balance', 's', Lang::T('Data Deleted Successfully'));
r2(getUrl('services/balance'), 's', Lang::T('Data Deleted Successfully'));
}
break;
case 'balance-edit-post':
$id = _post('id');
$name = _post('name');
$price = _post('price');
$price_old = _post('price_old');
$enabled = _post('enabled');
$prepaid = _post('prepaid');
@ -851,17 +863,21 @@ switch ($action) {
} else {
$msg .= Lang::T('Data Not Found') . '<br>';
}
if ($price_old <= $price) {
$price_old = '';
}
run_hook('edit_ppoe'); #HOOK
if ($msg == '') {
$d->name_plan = $name;
$d->price = $price;
$d->enabled = $enabled;
$d->price_old = $price_old;
$d->prepaid = 'yes';
$d->save();
r2(U . 'services/balance', 's', Lang::T('Data Updated Successfully'));
r2(getUrl('services/balance'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'services/balance-edit/' . $id, 'e', $msg);
r2(getUrl('services/balance-edit/') . $id, 'e', $msg);
}
break;
case 'balance-add-post':
@ -896,11 +912,390 @@ switch ($action) {
$d->prepaid = 'yes';
$d->save();
r2(U . 'services/balance', 's', Lang::T('Data Created Successfully'));
r2(getUrl('services/balance'), 's', Lang::T('Data Created Successfully'));
} else {
r2(U . 'services/balance-add', 'e', $msg);
r2(getUrl('services/balance-add'), 'e', $msg);
}
break;
case 'vpn':
$ui->assign('_title', Lang::T('VPN Plans'));
$name = _post('name');
$name = _req('name');
$type1 = _req('type1');
$type2 = _req('type2');
$type3 = _req('type3');
$bandwidth = _req('bandwidth');
$valid = _req('valid');
$device = _req('device');
$status = _req('status');
$router = _req('router');
$ui->assign('type1', $type1);
$ui->assign('type2', $type2);
$ui->assign('type3', $type3);
$ui->assign('bandwidth', $bandwidth);
$ui->assign('valid', $valid);
$ui->assign('device', $device);
$ui->assign('status', $status);
$ui->assign('router', $router);
$append_url = "&type1=" . urlencode($type1)
. "&type2=" . urlencode($type2)
. "&type3=" . urlencode($type3)
. "&bandwidth=" . urlencode($bandwidth)
. "&valid=" . urlencode($valid)
. "&device=" . urlencode($device)
. "&status=" . urlencode($status)
. "&router=" . urlencode($router);
$bws = ORM::for_table('tbl_plans')->distinct()->select("id_bw")->where('tbl_plans.type', 'VPN')->findArray();
$ids = array_column($bws, 'id_bw');
if (count($ids)) {
$ui->assign('bws', ORM::for_table('tbl_bandwidth')->select("id")->select('name_bw')->where_id_in($ids)->findArray());
} else {
$ui->assign('bws', []);
}
$ui->assign('type2s', ORM::for_table('tbl_plans')->getEnum("plan_type"));
$ui->assign('type3s', ORM::for_table('tbl_plans')->getEnum("typebp"));
$ui->assign('valids', ORM::for_table('tbl_plans')->getEnum("validity_unit"));
$ui->assign('routers', array_column(ORM::for_table('tbl_plans')->distinct()->select("routers")->whereNotEqual('routers', '')->findArray(), 'routers'));
$devices = [];
$files = scandir($DEVICE_PATH);
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if ($ext == 'php') {
$devices[] = pathinfo($file, PATHINFO_FILENAME);
}
}
$ui->assign('devices', $devices);
$query = ORM::for_table('tbl_bandwidth')
->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))
->where('tbl_plans.type', 'VPN');
if (!empty($type1)) {
$query->where('tbl_plans.prepaid', $type1);
}
if (!empty($type2)) {
$query->where('tbl_plans.plan_type', $type2);
}
if (!empty($type3)) {
$query->where('tbl_plans.typebp', $type3);
}
if (!empty($bandwidth)) {
$query->where('tbl_plans.id_bw', $bandwidth);
}
if (!empty($valid)) {
$query->where('tbl_plans.validity_unit', $valid);
}
if (!empty($router)) {
if ($router == 'radius') {
$query->where('tbl_plans.is_radius', '1');
} else {
$query->where('tbl_plans.routers', $router);
}
}
if (!empty($device)) {
$query->where('tbl_plans.device', $device);
}
if (in_array($status, ['0', '1'])) {
$query->where('tbl_plans.enabled', $status);
}
if ($name != '') {
$query->where_like('tbl_plans.name_plan', '%' . $name . '%');
}
$d = Paginator::findMany($query, ['name' => $name], 20, $append_url);
$ui->assign('d', $d);
run_hook('view_list_vpn'); #HOOK
$ui->display('admin/vpn/list.tpl');
break;
case 'vpn-add':
$ui->assign('_title', Lang::T('VPN Plans'));
$d = ORM::for_table('tbl_bandwidth')->find_many();
$ui->assign('d', $d);
$r = ORM::for_table('tbl_routers')->find_many();
$ui->assign('r', $r);
$devices = [];
$files = scandir($DEVICE_PATH);
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if ($ext == 'php') {
$devices[] = pathinfo($file, PATHINFO_FILENAME);
}
}
$ui->assign('devices', $devices);
run_hook('view_add_vpn'); #HOOK
$ui->display('admin/vpn/add.tpl');
break;
case 'vpn-edit':
$ui->assign('_title', Lang::T('VPN Plans'));
$id = $routes['2'];
$d = ORM::for_table('tbl_plans')->find_one($id);
if ($d) {
if (empty($d['device'])) {
if ($d['is_radius']) {
$d->device = 'Radius';
} else {
$d->device = 'MikrotikVpn';
}
$d->save();
}
$ui->assign('d', $d);
$p = ORM::for_table('tbl_pool')->where('routers', ($d['is_radius']) ? 'radius' : $d['routers'])->find_many();
$ui->assign('p', $p);
$b = ORM::for_table('tbl_bandwidth')->find_many();
$ui->assign('b', $b);
$r = [];
if ($d['is_radius']) {
$r = ORM::for_table('tbl_routers')->find_many();
}
$ui->assign('r', $r);
$devices = [];
$files = scandir($DEVICE_PATH);
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if ($ext == 'php') {
$devices[] = pathinfo($file, PATHINFO_FILENAME);
}
}
$ui->assign('devices', $devices);
//select expired plan
if ($d['is_radius']) {
$exps = ORM::for_table('tbl_plans')->selects('id', 'name_plan')->where('type', 'VPN')->where("is_radius", 1)->findArray();
} else {
$exps = ORM::for_table('tbl_plans')->selects('id', 'name_plan')->where('type', 'VPN')->where("routers", $d['routers'])->findArray();
}
$ui->assign('exps', $exps);
run_hook('view_edit_vpn'); #HOOK
$ui->display('admin/vpn/edit.tpl');
} else {
r2(getUrl('services/vpn'), 'e', Lang::T('Account Not Found'));
}
break;
case 'vpn-delete':
$id = $routes['2'];
$d = ORM::for_table('tbl_plans')->find_one($id);
if ($d) {
run_hook('delete_vpn'); #HOOK
$dvc = Package::getDevice($d);
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $d['device'])->remove_plan($d);
} else {
new Exception(Lang::T("Devices Not Found"));
}
}
$d->delete();
r2(getUrl('services/vpn'), 's', Lang::T('Data Deleted Successfully'));
}
break;
case 'vpn-add-post':
$name = _post('name_plan');
$plan_type = _post('plan_type');
$radius = _post('radius');
$id_bw = _post('id_bw');
$price = _post('price');
$validity = _post('validity');
$validity_unit = _post('validity_unit');
$routers = _post('routers');
$device = _post('device');
$pool = _post('pool_name');
$enabled = _post('enabled');
$prepaid = _post('prepaid');
$expired_date = _post('expired_date');
$msg = '';
if (Validator::UnsignedNumber($validity) == false) {
$msg .= 'The validity must be a number' . '<br>';
}
if (Validator::UnsignedNumber($price) == false) {
$msg .= 'The price must be a number' . '<br>';
}
if ($name == '' or $id_bw == '' or $price == '' or $validity == '' or $pool == '') {
$msg .= Lang::T('All field is required') . '<br>';
}
if (empty($radius)) {
if ($routers == '') {
$msg .= Lang::T('All field is required') . '<br>';
}
}
$d = ORM::for_table('tbl_plans')->where('name_plan', $name)->find_one();
if ($d) {
$msg .= Lang::T('Name Plan Already Exist') . '<br>';
}
run_hook('add_vpn'); #HOOK
if ($msg == '') {
$b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one();
if ($b['rate_down_unit'] == 'Kbps') {
$unitdown = 'K';
$raddown = '000';
} else {
$unitdown = 'M';
$raddown = '000000';
}
if ($b['rate_up_unit'] == 'Kbps') {
$unitup = 'K';
$radup = '000';
} else {
$unitup = 'M';
$radup = '000000';
}
$rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown;
$radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst'];
$rate = trim($rate . " " . $b['burst']);
$d = ORM::for_table('tbl_plans')->create();
$d->type = 'VPN';
$d->name_plan = $name;
$d->id_bw = $id_bw;
$d->price = $price;
$d->plan_type = $plan_type;
$d->validity = $validity;
$d->validity_unit = $validity_unit;
$d->pool = $pool;
if (!empty($radius)) {
$d->is_radius = 1;
$d->routers = '';
} else {
$d->is_radius = 0;
$d->routers = $routers;
}
if ($prepaid == 'no') {
if ($expired_date > 28 && $expired_date < 1) {
$expired_date = 20;
}
$d->expired_date = $expired_date;
} else {
$d->expired_date = 0;
}
$d->enabled = $enabled;
$d->prepaid = $prepaid;
$d->device = $device;
$d->save();
$dvc = Package::getDevice($d);
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $d['device'])->add_plan($d);
} else {
new Exception(Lang::T("Devices Not Found"));
}
}
r2(getUrl('services/vpn'), 's', Lang::T('Data Created Successfully'));
} else {
r2(getUrl('services/vpn-add'), 'e', $msg);
}
break;
case 'edit-vpn-post':
$id = _post('id');
$plan_type = _post('plan_type');
$name = _post('name_plan');
$id_bw = _post('id_bw');
$price = _post('price');
$price_old = _post('price_old');
$validity = _post('validity');
$validity_unit = _post('validity_unit');
$routers = _post('routers');
$device = _post('device');
$pool = _post('pool_name');
$plan_expired = _post('plan_expired');
$enabled = _post('enabled');
$prepaid = _post('prepaid');
$expired_date = _post('expired_date');
$on_login = _post('on_login');
$on_logout = _post('on_logout');
$msg = '';
if (Validator::UnsignedNumber($validity) == false) {
$msg .= 'The validity must be a number' . '<br>';
}
if (Validator::UnsignedNumber($price) == false) {
$msg .= 'The price must be a number' . '<br>';
}
if ($name == '' or $id_bw == '' or $price == '' or $validity == '' or $pool == '') {
$msg .= Lang::T('All field is required') . '<br>';
}
if($price_old<=$price){
$price_old = '';
}
$d = ORM::for_table('tbl_plans')->where('id', $id)->find_one();
$old = ORM::for_table('tbl_plans')->where('id', $id)->find_one();
if ($d) {
} else {
$msg .= Lang::T('Data Not Found') . '<br>';
}
run_hook('edit_vpn'); #HOOK
if ($msg == '') {
$b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one();
if ($b['rate_down_unit'] == 'Kbps') {
$unitdown = 'K';
$raddown = '000';
} else {
$unitdown = 'M';
$raddown = '000000';
}
if ($b['rate_up_unit'] == 'Kbps') {
$unitup = 'K';
$radup = '000';
} else {
$unitup = 'M';
$radup = '000000';
}
$rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown;
$radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst'];
$rate = trim($rate . " " . $b['burst']);
$d->name_plan = $name;
$d->id_bw = $id_bw;
$d->price = $price;
$d->price_old = $price_old;
$d->plan_type = $plan_type;
$d->validity = $validity;
$d->validity_unit = $validity_unit;
$d->routers = $routers;
$d->pool = $pool;
$d->plan_expired = $plan_expired;
$d->enabled = $enabled;
$d->prepaid = $prepaid;
$d->device = $device;
$d->on_login = $on_login;
$d->on_logout = $on_logout;
if ($prepaid == 'no') {
if ($expired_date > 28 && $expired_date < 1) {
$expired_date = 20;
}
$d->expired_date = $expired_date;
} else {
$d->expired_date = 0;
}
$d->save();
$dvc = Package::getDevice($d);
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
require_once $dvc;
(new $d['device'])->update_plan($old, $d);
} else {
new Exception(Lang::T("Devices Not Found"));
}
}
r2(getUrl('services/vpn'), 's', Lang::T('Data Updated Successfully'));
} else {
r2(getUrl('services/vpn-edit/') . $id, 'e', $msg);
}
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);
@ -34,11 +34,11 @@ switch ($action) {
$dev = pathinfo($file, PATHINFO_FILENAME);
require_once $DEVICE_PATH . DIRECTORY_SEPARATOR . $file;
$dvc = new $dev;
if(method_exists($dvc, 'description')){
if (method_exists($dvc, 'description')) {
$arr = $dvc->description();
$arr['file'] = $dev;
$devices[] = $arr;
}else{
} else {
$devices[] = [
'title' => $dev,
'description' => '',
@ -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,29 +58,70 @@ 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(U . "settings/app", 's', 'Test Whatsapp has been send<br>Result: ' . $result);
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(U . "settings/app", 's', 'Test SMS has been send<br>Result: ' . $result);
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(U . "settings/app", 's', 'Test Email has been send');
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(U . "settings/app", 's', 'Test Telegram has been send<br>Result: ' . $result);
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 {
$logo = $UPLOAD_URL_PATH . DIRECTORY_SEPARATOR . 'logo.default.png';
}
$ui->assign('logo', $logo);
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);
$themes = [];
$files = scandir('ui/themes/');
foreach ($files as $file) {
@ -88,10 +129,25 @@ switch ($action) {
$themes[] = $file;
}
}
$template_files = glob('ui/ui/customer/login-custom-*.tpl');
$templates = [];
foreach ($template_files as $file) {
$parts = explode('-', basename($file, '.tpl'));
$template_identifier = $parts[2] ?? 'unknown';
$templates[] = [
'filename' => basename($file),
'value' => $template_identifier,
'name' => str_replace('_', ' ', ucfirst($template_identifier))
];
}
$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';
}
@ -111,36 +167,54 @@ switch ($action) {
$d->save();
}
}
if (empty($config['mikrotik_sms_command'])) {
$config['mikrotik_sms_command'] = "/tool sms send";
}
$ui->assign('template_files', $templates);
$ui->assign('_c', $config);
$ui->assign('php', $php);
$ui->assign('dir', str_replace('controllers', '', __DIR__));
$ui->assign('themes', $themes);
run_hook('view_app_settings'); #HOOK
$ui->display('app-settings.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$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");
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/app'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$company = _post('CompanyName');
$custom_tax_rate = filter_var(_post('custom_tax_rate'), FILTER_SANITIZE_SPECIAL_CHARS);
if (preg_match('/[^0-9.]/', $custom_tax_rate)) {
r2(U . 'settings/app', 'e', 'Special characters are not allowed in tax rate');
r2(getUrl('settings/app'), 'e', 'Special characters are not allowed in tax rate');
die();
}
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(U . 'settings/app', 'e', 'PHP GD is not installed');
r2(getUrl('settings/app'), 'e', 'PHP GD is not installed');
}
}
if ($company == '') {
r2(U . 'settings/app', 'e', Lang::T('All field is required'));
if ($_POST['general'] && $company == '') {
r2(getUrl('settings/app'), 'e', Lang::T('All field is required'));
} else {
if ($radius_enable) {
try {
@ -151,13 +225,99 @@ 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('router-error.tpl');
$ui->display('admin/error.tpl');
die();
}
}
// Save all settings including tax system
// Save all settings including tax system
$_POST['man_fields_email'] = isset($_POST['man_fields_email']) ? 'yes' : 'no';
$_POST['man_fields_fname'] = isset($_POST['man_fields_fname']) ? 'yes' : 'no';
$_POST['man_fields_address'] = isset($_POST['man_fields_address']) ? 'yes' : 'no';
$_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) {
@ -170,26 +330,9 @@ switch ($action) {
$d->save();
}
}
//checkbox
$checks = ['hide_mrc', 'hide_tms', 'hide_aui', 'hide_al', 'hide_uet', 'hide_vs', 'hide_pg'];
foreach ($checks as $check) {
if (!isset($_POST[$check])) {
$d = ORM::for_table('tbl_appconfig')->where('setting', $check)->find_one();
if ($d) {
$d->value = 'no';
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = $check;
$d->value = 'no';
$d->save();
}
}
}
_log('[' . $admin['username'] . ']: ' . Lang::T('Settings Saved Successfully'), $admin['user_type'], $admin['id']);
r2(U . 'settings/app', 's', Lang::T('Settings Saved Successfully'));
r2(getUrl('settings/app'), 's', Lang::T('Settings Saved Successfully'));
}
break;
@ -217,20 +360,29 @@ switch ($action) {
$ui->assign('tlist', $timezonelist);
$ui->assign('xjq', ' $("#tzone").select2(); ');
run_hook('view_localisation'); #HOOK
$ui->display('app-localisation.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$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/localisation'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$tzone = _post('tzone');
$date_format = _post('date_format');
$country_code_phone = _post('country_code_phone');
$lan = _post('lan');
run_hook('save_localisation'); #HOOK
if ($tzone == '' or $date_format == '' or $lan == '') {
r2(U . '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;
@ -295,6 +447,16 @@ switch ($action) {
$d->value = _post('pppoe_plan');
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'vpn_plan')->find_one();
if ($d) {
$d->value = _post('vpn_plan');
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'vpn_plan';
$d->value = _post('vpn_plan');
$d->save();
}
$currency_code = $_POST['currency_code'];
$d = ORM::for_table('tbl_appconfig')->where('setting', 'currency_code')->find_one();
@ -304,9 +466,8 @@ switch ($action) {
$d = ORM::for_table('tbl_appconfig')->where('setting', 'language')->find_one();
$d->value = $lan;
$d->save();
unset($_SESSION['Lang']);
_log('[' . $admin['username'] . ']: ' . 'Settings Saved Successfully', $admin['user_type'], $admin['id']);
r2(U . 'settings/localisation', 's', 'Settings Saved Successfully');
r2(getUrl('settings/localisation'), 's', 'Settings Saved Successfully');
}
break;
@ -324,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')
@ -377,26 +538,30 @@ switch ($action) {
$ui->assign('d', $d);
$ui->assign('search', $search);
run_hook('view_list_admin'); #HOOK
$ui->display('admin.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin/admin/list.tpl');
break;
case 'users-add':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$csrf_token = Csrf::generateAndStoreToken();
$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
@ -413,9 +578,11 @@ switch ($action) {
}
$ui->assign('d', $d);
$ui->assign('_title', $d['username']);
$ui->display('admin-view.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin/admin/view.tpl');
} else {
r2(U . 'settings/users', 'e', Lang::T('Account Not Found'));
r2(getUrl('settings/users'), 'e', Lang::T('Account Not Found'));
}
break;
case 'users-edit':
@ -423,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'];
}
@ -447,12 +614,31 @@ switch ($action) {
}
}
if ($d) {
if (isset($routes['3']) && $routes['3'] == 'deletePhoto') {
if ($d['photo'] != '' && strpos($d['photo'], 'default') === false) {
if (file_exists($UPLOAD_PATH . $d['photo']) && strpos($d['photo'], 'default') === false) {
unlink($UPLOAD_PATH . $d['photo']);
if (file_exists($UPLOAD_PATH . $d['photo'] . '.thumb.jpg')) {
unlink($UPLOAD_PATH . $d['photo'] . '.thumb.jpg');
}
}
$d->photo = '/admin.default.png';
$d->save();
$ui->assign('notify_t', 's');
$ui->assign('notify', 'You have successfully deleted the photo');
} else {
$ui->assign('notify_t', 'e');
$ui->assign('notify', 'No photo found to delete');
}
}
$ui->assign('id', $id);
$ui->assign('d', $d);
run_hook('view_edit_admin'); #HOOK
$ui->display('admin-edit.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin/admin/edit.tpl');
} else {
r2(U . 'settings/users', 'e', Lang::T('Account Not Found'));
r2(getUrl('settings/users'), 'e', Lang::T('Account Not Found'));
}
break;
@ -460,18 +646,20 @@ 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(U . 'settings/users', 'e', 'Sorry You can\'t delete yourself');
r2(getUrl('settings/users'), 'e', 'Sorry You can\'t delete yourself');
}
$d = ORM::for_table('tbl_users')->find_one($id);
if ($d) {
run_hook('delete_admin'); #HOOK
$d->delete();
r2(U . 'settings/users', 's', Lang::T('User deleted Successfully'));
r2(getUrl('settings/users'), 's', Lang::T('User deleted Successfully'));
} else {
r2(U . 'settings/users', 'e', Lang::T('Account Not Found'));
r2(getUrl('settings/users'), 'e', Lang::T('Account Not Found'));
}
break;
@ -479,6 +667,13 @@ 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') . ".");
}
$username = _post('username');
$fullname = _post('fullname');
$password = _post('password');
@ -536,13 +731,20 @@ switch ($action) {
}
_log('[' . $admin['username'] . ']: ' . "Created $user_type <b>$username</b>", $admin['user_type'], $admin['id']);
r2(U . 'settings/users', 's', Lang::T('Account Created Successfully'));
r2(getUrl('settings/users'), 's', Lang::T('Account Created Successfully'));
} else {
r2(U . 'settings/users-add', 'e', $msg);
r2(getUrl('settings/users-add'), 'e', $msg);
}
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') . ".");
}
$username = _post('username');
$fullname = _post('fullname');
$password = _post('password');
@ -599,6 +801,56 @@ switch ($action) {
}
run_hook('edit_admin'); #HOOK
if ($msg == '') {
if (!empty($_FILES['photo']['name']) && file_exists($_FILES['photo']['tmp_name'])) {
if (function_exists('imagecreatetruecolor')) {
$hash = md5_file($_FILES['photo']['tmp_name']);
$subfolder = substr($hash, 0, 2);
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$folder = $UPLOAD_PATH . DIRECTORY_SEPARATOR . 'photos' . DIRECTORY_SEPARATOR . $subfolder . DIRECTORY_SEPARATOR;
if (!file_exists($folder)) {
mkdir($folder);
}
$imgPath = $folder . $hash . '.jpg';
if (!file_exists($imgPath)) {
File::resizeCropImage($_FILES['photo']['tmp_name'], $imgPath, 1600, 1600, 100);
}
if (!file_exists($imgPath . '.thumb.jpg')) {
if (_post('faceDetect') == 'yes') {
try {
$detector = new svay\FaceDetector();
$detector->setTimeout(5000);
$detector->faceDetect($imgPath);
$detector->cropFaceToJpeg($imgPath . '.thumb.jpg', false);
} catch (Exception $e) {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
} catch (Throwable $e) {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
}
} else {
File::makeThumb($imgPath, $imgPath . '.thumb.jpg', 200);
}
}
if (file_exists($imgPath)) {
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')) {
unlink($UPLOAD_PATH . $d['photo'] . '.thumb.jpg');
}
}
}
$d->photo = '/photos/' . $subfolder . '/' . $hash . '.jpg';
}
if (file_exists($_FILES['photo']['tmp_name']))
unlink($_FILES['photo']['tmp_name']);
} else {
r2(getUrl('settings/app'), 'e', 'PHP GD is not installed');
}
}
$d->username = $username;
if ($password != '') {
$password = Password::_crypt($password);
@ -629,19 +881,28 @@ switch ($action) {
$d->save();
_log('[' . $admin['username'] . ']: $username ' . Lang::T('User Updated Successfully'), $admin['user_type'], $admin['id']);
r2(U . 'settings/users', 's', 'User Updated Successfully');
r2(getUrl('settings/users-view/') . $id, 's', 'User Updated Successfully');
} else {
r2(U . 'settings/users-edit/' . $id, 'e', $msg);
r2(getUrl('settings/users-edit/') . $id, 'e', $msg);
}
break;
case 'change-password':
run_hook('view_change_password'); #HOOK
$ui->display('change-password.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$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)) {
r2(getUrl('settings/change-password'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
if ($password != '') {
$d = ORM::for_table('tbl_users')->where('username', $admin['username'])->find_one();
run_hook('change_password'); #HOOK
@ -651,10 +912,10 @@ switch ($action) {
$npass = _post('npass');
$cnpass = _post('cnpass');
if (!Validator::Length($npass, 15, 5)) {
r2(U . 'settings/change-password', 'e', 'New Password must be 6 to 14 character');
r2(getUrl('settings/change-password'), 'e', 'New Password must be 6 to 14 character');
}
if ($npass != $cnpass) {
r2(U . 'settings/change-password', 'e', 'Both Password should be same');
r2(getUrl('settings/change-password'), 'e', 'Both Password should be same');
}
$npass = Password::_crypt($npass);
@ -664,15 +925,15 @@ switch ($action) {
_msglog('s', Lang::T('Password changed successfully, Please login again'));
_log('[' . $admin['username'] . ']: Password changed successfully', $admin['user_type'], $admin['id']);
r2(U . 'admin');
r2(getUrl('admin'));
} else {
r2(U . 'settings/change-password', 'e', Lang::T('Incorrect Current Password'));
r2(getUrl('settings/change-password'), 'e', Lang::T('Incorrect Current Password'));
}
} else {
r2(U . 'settings/change-password', 'e', Lang::T('Incorrect Current Password'));
r2(getUrl('settings/change-password'), 'e', Lang::T('Incorrect Current Password'));
}
} else {
r2(U . 'settings/change-password', 'e', Lang::T('Incorrect Current Password'));
r2(getUrl('settings/change-password'), 'e', Lang::T('Incorrect Current Password'));
}
break;
@ -686,15 +947,25 @@ switch ($action) {
} else {
$ui->assign('_json', json_decode(file_get_contents($UPLOAD_PATH . DIRECTORY_SEPARATOR . 'notifications.default.json'), true));
}
$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");
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(getUrl('settings/notifications'), 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
file_put_contents($UPLOAD_PATH . "/notifications.json", json_encode($_POST));
r2(U . 'settings/notifications', 's', Lang::T('Settings Saved Successfully'));
r2(getUrl('settings/notifications'), 's', Lang::T('Settings Saved Successfully'));
break;
case 'dbstatus':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
@ -710,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");
}
@ -735,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");
}
@ -782,10 +1059,11 @@ switch ($action) {
} catch (Throwable $e) {
} catch (Exception $e) {
}
if (file_exists($_FILES['json']['tmp_name'])) unlink($_FILES['json']['tmp_name']);
r2(U . "settings/dbstatus", 's', "Restored $suc success $fal failed");
if (file_exists($_FILES['json']['tmp_name']))
unlink($_FILES['json']['tmp_name']);
r2(getUrl('settings/dbstatus'), 's', "Restored $suc success $fal failed");
} else {
r2(U . "settings/dbstatus", 'e', 'Upload failed');
r2(getUrl('settings/dbstatus'), 'e', 'Upload failed');
}
break;
case 'language':
@ -798,12 +1076,21 @@ switch ($action) {
} else {
$ui->assign('langs', []);
}
$ui->display('language-add.tpl');
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$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') . ".");
}
file_put_contents($lan_file, json_encode($_POST, JSON_PRETTY_PRINT));
r2(U . 'settings/language', 's', Lang::T('Translation saved Successfully'));
r2(getUrl('settings/language'), 's', Lang::T('Translation saved Successfully'));
break;
case 'maintenance':
@ -811,7 +1098,15 @@ switch ($action) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
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') . ".");
}
$status = isset($_POST['maintenance_mode']) ? 1 : 0; // Checkbox returns 1 if checked, otherwise 0
$force_logout = isset($_POST['maintenance_mode_logout']) ? 1 : 0; // Checkbox returns 1 if checked, otherwise 0
$date = isset($_POST['maintenance_date']) ? $_POST['maintenance_date'] : null;
@ -835,13 +1130,50 @@ switch ($action) {
}
}
r2(U . "settings/maintenance", 's', Lang::T('Settings Saved Successfully'));
r2(getUrl('settings/maintenance'), 's', Lang::T('Settings Saved Successfully'));
}
$csrf_token = Csrf::generateAndStoreToken();
$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':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
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') . ".");
}
foreach ($_POST 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();
}
}
r2(getUrl('settings/miscellaneous'), 's', Lang::T('Settings Saved Successfully'));
}
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->assign('_c', $config);
$ui->assign('_title', Lang::T('Miscellaneous Settings'));
$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);
@ -17,12 +53,12 @@ switch ($action) {
case 'activation':
run_hook('view_activate_voucher'); #HOOK
$ui->assign('code', alphanumeric(_get('code'), "-_.,"));
$ui->display('user-ui/activation.tpl');
$ui->display('customer/activation.tpl');
break;
case 'activation-post':
$code = alphanumeric(_post('code'), "-_.,");
$v1 = ORM::for_table('tbl_voucher')->whereRaw("BINARY `code` = '$code'")->where('status', 0)->find_one();
$v1 = ORM::for_table('tbl_voucher')->whereRaw("BINARY code = '$code'")->where('status', 0)->find_one();
run_hook('customer_activate_voucher'); #HOOK
if ($v1) {
if (Package::rechargeUser($user['id'], $v1['routers'], $v1['id_plan'], "Voucher", $code)) {
@ -30,23 +66,29 @@ switch ($action) {
$v1->used_date = date('Y-m-d H:i:s');
$v1->user = $user['username'];
$v1->save();
r2(U . "voucher/list-activated", 's', Lang::T('Activation Vouchers Successfully'));
r2(getUrl('voucher/list-activated'), 's', Lang::T('Activation Vouchers Successfully'));
} else {
r2(U . 'voucher/activation', 'e', "Failed to refill account");
r2(getUrl('voucher/activation'), 'e', "Failed to refill account");
}
} else {
r2(U . 'voucher/activation', 'e', Lang::T('Voucher Not Valid'));
r2(getUrl('voucher/activation'), 'e', Lang::T('Voucher Not Valid'));
}
break;
case 'list-activated':
$ui->assign('_system_menu', 'list-activated');
$query = ORM::for_table('tbl_transactions')->where('username', $user['username'])->order_by_desc('id');
$query = ORM::for_table('tbl_transactions')->where('user_id', $user['id'])->order_by_desc('id');
$d = Paginator::findMany($query);
if (empty($d) || $d < 5) {
$query = ORM::for_table('tbl_transactions')->where('username', $user['username'])->order_by_desc('id');
$d = Paginator::findMany($query);
}
$ui->assign('d', $d);
$ui->assign('_title', Lang::T('Activation History'));
run_hook('customer_view_activation_list'); #HOOK
$ui->display('user-ui/activation-list.tpl');
$ui->display('customer/activation-list.tpl');
break;
case 'invoice':
@ -58,11 +100,23 @@ switch ($action) {
}
if ($in) {
Package::createInvoice($in);
$ui->display('user-ui/invoice-customer.tpl');
$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(U . 'voucher/list-activated', 'e', Lang::T('Not Found'));
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

@ -1,6 +1,27 @@
<?php
include "../init.php";
$lockFile = "$CACHE_PATH/router_monitor.lock";
if (!is_dir($CACHE_PATH)) {
echo "Directory '$CACHE_PATH' does not exist. Exiting...\n";
exit;
}
$lock = fopen($lockFile, 'c');
if ($lock === false) {
echo "Failed to open lock file. Exiting...\n";
exit;
}
if (!flock($lock, LOCK_EX | LOCK_NB)) {
echo "Script is already running. Exiting...\n";
fclose($lock);
exit;
}
$isCli = true;
if (php_sapi_name() !== 'cli') {
$isCli = false;
@ -9,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";
}
@ -24,84 +45,115 @@ 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();
$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) {
$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();
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']) {
$lockFile = $CACHE_PATH . '/router_monitor.lock';
if (!is_dir($CACHE_PATH)) {
echo "Directory '$CACHE_PATH' does not exist. Exiting...\n";
exit;
}
$lock = fopen($lockFile, 'c');
if ($lock === false) {
echo "Failed to open lock file. Exiting...\n";
exit;
}
if (!flock($lock, LOCK_EX | LOCK_NB)) {
echo "Script is already running. Exiting...\n";
fclose($lock);
exit;
}
echo "Checking router status...\n";
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
if (!$routers) {
echo "No active routers found in the database.\n";
@ -116,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 {
@ -186,14 +238,15 @@ if ($config['router_check']) {
Message::SendEmail($adminEmail, $subject, $message);
sendTelegram($message);
}
if (defined('PHP_SAPI') && PHP_SAPI === 'cli') {
echo "Cronjob finished\n";
} else {
echo "</pre>";
}
flock($lock, LOCK_UN);
fclose($lock);
unlink($lockFile);
echo "Router monitoring finished checking.\n";
}
flock($lock, LOCK_UN);
fclose($lock);
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

@ -33,10 +33,38 @@ class MikrotikHotspot
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$isExp = ORM::for_table('tbl_plans')->select("id")->where('plan_expired', $plan['id'])->find_one();
$this->removeHotspotUser($client, $customer['username']);
if ($isExp){
$this->removeHotspotActiveUser($client, $customer['username']);
}
$this->addHotspotUser($client, $plan, $customer);
}
function sync_customer($customer, $plan)
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$t = ORM::for_table('tbl_user_recharges')->where('username', $customer['username'])->where('status', 'on')->find_one();
if ($t) {
$printRequest = new RouterOS\Request('/ip/hotspot/user/print');
$printRequest->setArgument('.proplist', '.id,limit-uptime,limit-bytes-total');
$printRequest->setQuery(RouterOS\Query::where('name', $customer['username']));
$userInfo = $client->sendSync($printRequest);
$id = $userInfo->getProperty('.id');
$uptime = $userInfo->getProperty('limit-uptime');
$data = $userInfo->getProperty('limit-bytes-total');
if (!empty($id) && (!empty($uptime) || !empty($data))) {
$setRequest = new RouterOS\Request('/ip/hotspot/user/set');
$setRequest->setArgument('numbers', $id);
$setRequest->setArgument('profile', $t['namebp']);
$client->sendSync($setRequest);
} else {
$this->add_customer($customer, $plan);
}
}
}
function remove_customer($customer, $plan)
{
@ -94,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
@ -109,9 +140,10 @@ class MikrotikHotspot
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ip hotspot active print',
RouterOS\Query::where('name', $customer['username'])
RouterOS\Query::where('user', $customer['username'])
);
return $client->sendSync($printRequest)->getProperty('.id');
$id = $client->sendSync($printRequest)->getProperty('.id');
return $id;
}
function connect_customer($customer, $ip, $mac_address, $router_name)
@ -173,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
@ -210,7 +245,7 @@ class MikrotikHotspot
function getClient($ip, $user, $pass)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$iport = explode(":", $ip);
@ -220,7 +255,7 @@ class MikrotikHotspot
function removeHotspotUser($client, $username)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request(
@ -238,7 +273,7 @@ class MikrotikHotspot
function addHotspotUser($client, $plan, $customer)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$addRequest = new RouterOS\Request('/ip/hotspot/user/add');
@ -306,7 +341,7 @@ class MikrotikHotspot
function setHotspotUser($client, $user, $pass)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request('/ip/hotspot/user/print');
@ -323,7 +358,7 @@ class MikrotikHotspot
function setHotspotUserPackage($client, $username, $plan_name)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request('/ip/hotspot/user/print');
@ -340,7 +375,7 @@ class MikrotikHotspot
function removeHotspotActiveUser($client, $username)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$onlineRequest = new RouterOS\Request('/ip/hotspot/active/print');
@ -356,7 +391,7 @@ class MikrotikHotspot
function getIpHotspotUser($client, $username)
{
global $_app_stage;
if ($_app_stage == 'demo') {
if ($_app_stage == 'Demo') {
return null;
}
$printRequest = new RouterOS\Request(

View File

@ -33,9 +33,10 @@ class MikrotikPppoe
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$cid = self::getIdByCustomer($customer, $client);
$isExp = ORM::for_table('tbl_plans')->select("id")->where('plan_expired', $plan['id'])->find_one();
if (empty($cid)) {
//customer not exists, add it
$this->addPpoeUser($client, $plan, $customer);
$this->addPpoeUser($client, $plan, $customer, $isExp);
}else{
$setRequest = new RouterOS\Request('/ppp/secret/set');
$setRequest->setArgument('numbers', $cid);
@ -49,14 +50,24 @@ class MikrotikPppoe
} else {
$setRequest->setArgument('name', $customer['username']);
}
if (!empty($customer['pppoe_ip'])) {
$setRequest->setArgument('local-address', $customer['pppoe_ip']);
}else{
$setRequest->setArgument('local-address', '0.0.0.0');
}
$unsetIP = false;
if (!empty($customer['pppoe_ip']) && !$isExp){
$setRequest->setArgument('remote-address', $customer['pppoe_ip']);
} else {
$unsetIP = true;
}
$setRequest->setArgument('profile', $plan['name_plan']);
$setRequest->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'] . ' | ' . implode(', ', User::getBillNames($customer['id'])));
$client->sendSync($setRequest);
if($unsetIP){
$unsetRequest = new RouterOS\Request('/ppp/secret/unset');
$unsetRequest->setArgument('.id', $cid);
$unsetRequest->setArgument('value-name','remote-address');
$client->sendSync($unsetRequest);
}
//disconnect then
if(isset($isChangePlan) && $isChangePlan){
$this->removePpoeActive($client, $customer['username']);
@ -67,6 +78,11 @@ class MikrotikPppoe
}
}
function sync_customer($customer, $plan)
{
$this->add_customer($customer, $plan);
}
function remove_customer($customer, $plan)
{
$mikrotik = $this->info($plan['routers']);
@ -133,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(
@ -189,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(
@ -222,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']);
@ -237,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']);
@ -262,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']);
@ -286,9 +308,17 @@ class MikrotikPppoe
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ppp active print',
RouterOS\Query::where('user', $customer['username'])
RouterOS\Query::where('name', $customer['username'])
);
return $client->sendSync($printRequest)->getProperty('.id');
$id = $client->sendSync($printRequest)->getProperty('.id');
if(empty($id)){
$printRequest = new RouterOS\Request(
'/ppp active print',
RouterOS\Query::where('name', $customer['pppoe_username'])
);
$id = $client->sendSync($printRequest)->getProperty('.id');
}
return $id;
}
function info($name)
@ -299,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);
@ -309,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');
@ -321,7 +351,7 @@ class MikrotikPppoe
$client->sendSync($removeRequest);
}
function addPpoeUser($client, $plan, $customer)
function addPpoeUser($client, $plan, $customer, $isExp = false)
{
$setRequest = new RouterOS\Request('/ppp/secret/add');
$setRequest->setArgument('service', 'pppoe');
@ -337,8 +367,8 @@ class MikrotikPppoe
} else {
$setRequest->setArgument('name', $customer['username']);
}
if (!empty($customer['pppoe_ip'])) {
$setRequest->setArgument('local-address', $customer['pppoe_ip']);
if (!empty($customer['pppoe_ip']) && !$isExp) {
$setRequest->setArgument('remote-address', $customer['pppoe_ip']);
}
$client->sendSync($setRequest);
}
@ -346,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');
@ -362,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(
@ -375,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');
@ -390,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(

View File

@ -0,0 +1,512 @@
<?php
use PEAR2\Net\RouterOS;
class MikrotikVpn
{
function description()
{
return [
'title' => 'Mikrotik Vpn',
'description' => 'To handle connection between PHPNuxBill with Mikrotik VPN',
'author' => 'agstr',
'url' => [
'Github' => 'https://github.com/agstrxyz',
'Telegram' => 'https://t.me/agstrxyz',
'Youtube' => 'https://www.youtube.com/@agstrxyz',
'Donate' => 'https://paypal.me/ibnux'
]
];
}
function add_customer($customer, $plan)
{
global $isChangePlan;
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$cid = self::getIdByCustomer($customer, $client);
if (empty($cid)) {
$this->addVpnUser($client, $plan, $customer);
} else {
$setRequest = new RouterOS\Request('/ppp/secret/set');
$setRequest->setArgument('numbers', $cid);
if (!empty($customer['pppoe_password'])) {
$setRequest->setArgument('password', $customer['pppoe_password']);
} else {
$setRequest->setArgument('password', $customer['password']);
}
if (!empty($customer['pppoe_username'])) {
$setRequest->setArgument('name', $customer['pppoe_username']);
} else {
$setRequest->setArgument('name', $customer['username']);
}
if (!empty($customer['pppoe_ip'])) {
$setRequest->setArgument('remote-address', $customer['pppoe_ip']);
} else {
$setRequest->setArgument('remote-address', '0.0.0.0');
}
$setRequest->setArgument('profile', $plan['name_plan']);
$setRequest->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'] . ' | ' . implode(', ', User::getBillNames($customer['id'])));
$client->sendSync($setRequest);
if (isset($isChangePlan) && $isChangePlan) {
$this->removeVpnActive($client, $customer['username']);
if (!empty($customer['pppoe_username'])) {
$this->removeVpnActive($client, $customer['pppoe_username']);
}
}
}
}
function sync_customer($customer, $plan)
{
$this->add_customer($customer, $plan);
}
function remove_customer($customer, $plan)
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
if (!empty($plan['plan_expired'])) {
$p = ORM::for_table("tbl_plans")->find_one($plan['plan_expired']);
if ($p) {
$this->add_customer($customer, $p);
$this->removeVpnActive($client, $customer['username']);
if (!empty($customer['pppoe_username'])) {
$this->removeVpnActive($client, $customer['pppoe_username']);
}
return;
}
}
$this->removeVpnUser($client, $customer['username'], $customer['id']);
if (!empty($customer['pppoe_username'])) {
$this->removeVpnUser($client, $customer['pppoe_username'], $customer['id']);
}
$this->removeVpnActive($client, $customer['username']);
if (!empty($customer['pppoe_username'])) {
$this->removeVpnActive($client, $customer['pppoe_username']);
}
}
public function change_username($plan, $from, $to)
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request('/ppp/secret/print');
$printRequest->setQuery(RouterOS\Query::where('name', $from));
$cid = $client->sendSync($printRequest)->getProperty('.id');
if (!empty($cid)) {
$setRequest = new RouterOS\Request('/ppp/secret/set');
$setRequest->setArgument('numbers', $cid);
$setRequest->setArgument('name', $to);
$client->sendSync($setRequest);
$this->removeVpnActive($client, $from);
}
}
function add_plan($plan)
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$bw = ORM::for_table("tbl_bandwidth")->find_one($plan['id_bw']);
if ($bw['rate_down_unit'] == 'Kbps') {
$unitdown = 'K';
} else {
$unitdown = 'M';
}
if ($bw['rate_up_unit'] == 'Kbps') {
$unitup = 'K';
} else {
$unitup = 'M';
}
$rate = $bw['rate_up'] . $unitup . "/" . $bw['rate_down'] . $unitdown;
if (!empty(trim($bw['burst']))) {
$rate .= ' ' . $bw['burst'];
}
$pool = ORM::for_table("tbl_pool")->where("pool_name", $plan['pool'])->find_one();
$addRequest = new RouterOS\Request('/ppp/profile/add');
$client->sendSync(
$addRequest
->setArgument('name', $plan['name_plan'])
->setArgument('local-address', (!empty($pool['local_ip'])) ? $pool['local_ip'] : $pool['pool_name'])
->setArgument('remote-address', $pool['pool_name'])
->setArgument('rate-limit', $rate)
);
}
function getIdByCustomer($customer, $client)
{
$printRequest = new RouterOS\Request('/ppp/secret/print');
$printRequest->setQuery(RouterOS\Query::where('name', $customer['username']));
$id = $client->sendSync($printRequest)->getProperty('.id');
if (empty($id)) {
if (!empty($customer['pppoe_username'])) {
$printRequest = new RouterOS\Request('/ppp/secret/print');
$printRequest->setQuery(RouterOS\Query::where('name', $customer['pppoe_username']));
$id = $client->sendSync($printRequest)->getProperty('.id');
}
}
return $id;
}
function update_plan($old_name, $new_plan)
{
$mikrotik = $this->info($new_plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ppp profile print .proplist=.id',
RouterOS\Query::where('name', $old_name['name_plan'])
);
$profileID = $client->sendSync($printRequest)->getProperty('.id');
if (empty($profileID)) {
$this->add_plan($new_plan);
} else {
$bw = ORM::for_table("tbl_bandwidth")->find_one($new_plan['id_bw']);
if ($bw['rate_down_unit'] == 'Kbps') {
$unitdown = 'K';
} else {
$unitdown = 'M';
}
if ($bw['rate_up_unit'] == 'Kbps') {
$unitup = 'K';
} else {
$unitup = 'M';
}
$rate = $bw['rate_up'] . $unitup . "/" . $bw['rate_down'] . $unitdown;
if (!empty(trim($bw['burst']))) {
$rate .= ' ' . $bw['burst'];
}
$pool = ORM::for_table("tbl_pool")->where("pool_name", $new_plan['pool'])->find_one();
$setRequest = new RouterOS\Request('/ppp/profile/set');
$client->sendSync(
$setRequest
->setArgument('numbers', $profileID)
->setArgument('local-address', (!empty($pool['local_ip'])) ? $pool['local_ip'] : $pool['pool_name'])
->setArgument('remote-address', $pool['pool_name'])
->setArgument('rate-limit', $rate)
->setArgument('on-up', $new_plan['on_login'])
->setArgument('on-down', $new_plan['on_logout'])
);
}
}
function remove_plan($plan)
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ppp profile print .proplist=.id',
RouterOS\Query::where('name', $plan['name_plan'])
);
$profileID = $client->sendSync($printRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ppp/profile/remove');
$client->sendSync(
$removeRequest
->setArgument('numbers', $profileID)
);
}
function add_pool($pool)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$mikrotik = $this->info($pool['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$addRequest = new RouterOS\Request('/ip/pool/add');
$client->sendSync(
$addRequest
->setArgument('name', $pool['pool_name'])
->setArgument('ranges', $pool['range_ip'])
);
}
function update_pool($old_pool, $new_pool)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$mikrotik = $this->info($new_pool['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ip pool print .proplist=.id',
RouterOS\Query::where('name', $old_pool['pool_name'])
);
$poolID = $client->sendSync($printRequest)->getProperty('.id');
if (empty($poolID)) {
$this->add_pool($new_pool);
} else {
$setRequest = new RouterOS\Request('/ip/pool/set');
$client->sendSync(
$setRequest
->setArgument('numbers', $poolID)
->setArgument('name', $new_pool['pool_name'])
->setArgument('ranges', $new_pool['range_ip'])
);
}
}
function remove_pool($pool)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$mikrotik = $this->info($pool['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ip pool print .proplist=.id',
RouterOS\Query::where('name', $pool['pool_name'])
);
$poolID = $client->sendSync($printRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ip/pool/remove');
$client->sendSync(
$removeRequest
->setArgument('numbers', $poolID)
);
}
function online_customer($customer, $router_name)
{
$mikrotik = $this->info($router_name);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$printRequest = new RouterOS\Request(
'/ppp active print',
RouterOS\Query::where('user', $customer['username'])
);
return $client->sendSync($printRequest)->getProperty('.id');
}
function info($name)
{
return ORM::for_table('tbl_routers')->where('name', $name)->find_one();
}
function getClient($ip, $user, $pass)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$iport = explode(":", $ip);
return new RouterOS\Client($iport[0], $user, $pass, ($iport[1]) ? $iport[1] : null);
}
function removeVpnUser($client, $username, $cstid)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$printRequest = new RouterOS\Request('/ppp/secret/print');
//$printRequest->setArgument('.proplist', '.id');
$printRequest->setQuery(RouterOS\Query::where('name', $username));
$id = $client->sendSync($printRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ppp/secret/remove');
$removeRequest->setArgument('numbers', $id);
$client->sendSync($removeRequest);
$this->rmNat($client, $cstid);
}
function addVpnUser($client, $plan, $customer)
{
$setRequest = new RouterOS\Request('/ppp/secret/add');
$setRequest->setArgument('service', 'any');
$setRequest->setArgument('profile', $plan['name_plan']);
$setRequest->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'] . ' | ' . implode(', ', User::getBillNames($customer['id'])));
if (!empty($customer['pppoe_password'])) {
$setRequest->setArgument('password', $customer['pppoe_password']);
} else {
$setRequest->setArgument('password', $customer['password']);
}
if (!empty($customer['pppoe_username'])) {
$setRequest->setArgument('name', $customer['pppoe_username']);
} else {
$setRequest->setArgument('name', $customer['username']);
}
if (!empty($customer['pppoe_ip'])) {
$ips = $customer['pppoe_ip'];
$setRequest->setArgument('remote-address', $customer['pppoe_ip']);
} else {
$ips = $this->checkIpAddr($plan['pool'], $customer['id']);
$setRequest->setArgument('remote-address', $ips);
}
$this->addNat($client, $plan, $customer, $ips);
$client->sendSync($setRequest);
$customer->service_type = 'VPN';
$customer->pppoe_ip = $ips;
$customer->save();
}
function removeVpnActive($client, $username)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$onlineRequest = new RouterOS\Request('/ppp/active/print');
$onlineRequest->setArgument('.proplist', '.id');
$onlineRequest->setQuery(RouterOS\Query::where('name', $username));
$id = $client->sendSync($onlineRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ppp/active/remove');
$removeRequest->setArgument('numbers', $id);
$client->sendSync($removeRequest);
}
function addIpToAddressList($client, $ip, $listName, $comment = '')
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$addRequest = new RouterOS\Request('/ip/firewall/address-list/add');
$client->sendSync(
$addRequest
->setArgument('address', $ip)
->setArgument('comment', $comment)
->setArgument('list', $listName)
);
}
function removeIpFromAddressList($client, $ip)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$printRequest = new RouterOS\Request(
'/ip firewall address-list print .proplist=.id',
RouterOS\Query::where('address', $ip)
);
$id = $client->sendSync($printRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ip/firewall/address-list/remove');
$client->sendSync(
$removeRequest
->setArgument('numbers', $id)
);
}
function addNat($client, $plan, $cust, $ips)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$this->checkPort($cust['id'], 'Winbox', $plan['routers']);
$this->checkPort($cust['id'], 'Api', $plan['routers']);
$this->checkPort($cust['id'], 'Web', $plan['routers']);
$tcf = ORM::for_table('tbl_customers_fields')
->where('customer_id', $cust['id'])
->find_many();
$ip = ORM::for_table('tbl_port_pool')
->where('routers', $plan['routers'])
->find_one();
foreach ($tcf as $cf) {
$dst = $cf['field_value'];
$cmnt = $cf['field_name'];
if ($cmnt == 'Winbox') {
$tp = '8291';
}
if ($cmnt == 'Web') {
$tp = '80';
}
if ($cmnt == 'Api') {
$tp = '8728';
}
if ($cmnt == 'Winbox' || $cmnt == 'Web' || $cmnt == 'Api') {
$addRequest = new RouterOS\Request('/ip/firewall/nat/add');
$client->sendSync(
$addRequest
->setArgument('chain', 'dstnat')
->setArgument('protocol', 'tcp')
->setArgument('dst-port', $dst)
->setArgument('action', 'dst-nat')
->setArgument('to-addresses', $ips)
->setArgument('to-ports', $tp)
->setArgument('dst-address', $ip['public_ip'])
->setArgument('comment', $cmnt . ' || ' . $cust['username'])
);
}
}
}
function rmNat($client, $cstid)
{
global $_app_stage;
if ($_app_stage == 'demo') {
return null;
}
$cst = ORM::for_table('tbl_customers')->find_one($cstid);
$printRequest = new RouterOS\Request('/ip/firewall/nat/print');
$printRequest->setQuery(RouterOS\Query::where('to-addresses', $cst['pppoe_ip']));
$nats = $client->sendSync($printRequest);
foreach ($nats as $nat) {
$id = $client->sendSync($printRequest)->getProperty('.id');
$removeRequest = new RouterOS\Request('/ip/firewall/nat/remove');
$removeRequest->setArgument('numbers', $id);
$client->sendSync($removeRequest);
}
}
function checkPort($id, $portn, $router)
{
$tcf = ORM::for_table('tbl_customers_fields')
->where('customer_id', $id)
->where('field_name', $portn)
->find_one();
$ports = ORM::for_table('tbl_port_pool')
->where('routers', $router)
->find_one();
$port = explode('-', $ports['range_port']);
if (empty($tcf) && !empty($ports)) {
repeat:
$portr = rand($port['0'], $port['1']);
if (ORM::for_table('tbl_customers_fields')->where('field_value', $portr)->find_one()) {
if ($portr == $port['1']) {
return;
}
goto repeat;
}
$cf = ORM::for_table('tbl_customers_fields')->create();
$cf->customer_id = $id;
$cf->field_name = $portn;
$cf->field_value = $portr;
$cf->save();
}
}
function checkIpAddr($pname, $id)
{
$c = ORM::for_table('tbl_customers')->find_one($id);
$ipp = ORM::for_table('tbl_pool')
->where('pool_name', $pname)
->find_one();
$ip_r = explode('-', $ipp['range_ip']);
$ip_1 = explode('.', $ip_r['0']);
$ip_2 = explode('.', $ip_r['1']);
repeat:
$ipt = rand($ip_1['3'], $ip_2['3']);
$ips = $ip_1['0'] . '.' . $ip_1['1'] . '.' . $ip_1['2'] . '.' . $ipt;
if (empty($c['pppoe_ip'])) {
if (ORM::for_table('tbl_customers')->where('pppoe_ip', $ips)->find_one()) {
if ($ip_2['3'] == $ipt) {
return;
}
goto repeat;
}
return $ips;
}
}
}

View File

@ -50,18 +50,42 @@ class Radius
if ($p['validity_unit'] == 'Months') {
$date_exp = date("Y-m-d", strtotime('+' . $p['validity'] . ' month'));
$time = date("H:i:s");
} else if ($p['validity_unit'] == 'Period') {
$date_tmp = date("Y-m-$day_exp", strtotime('+' . $p['validity'] . ' month'));
$dt1 = new DateTime("$date_only");
$dt2 = new DateTime("$date_tmp");
$diff = $dt2->diff($dt1);
$sum = $diff->format("%a"); // => 453
if ($sum >= 35 * $p['validity']) {
$date_exp = date("Y-m-$day_exp", strtotime('+0 month'));
} else {
$date_exp = date("Y-m-$day_exp", strtotime('+' . $p['validity'] . ' month'));
};
$time = date("23:59:00");
$current_date = new DateTime($date_only);
$exp_date = clone $current_date;
$exp_date->modify('first day of next month');
$exp_date->setDate($exp_date->format('Y'), $exp_date->format('m'), $day_exp);
$min_days = 7 * $p['validity'];
$max_days = 35 * $p['validity'];
$days_until_exp = $exp_date->diff($current_date)->days;
// If less than min_days away, move to the next period
while ($days_until_exp < $min_days) {
$exp_date->modify('+1 month');
$days_until_exp = $exp_date->diff($current_date)->days;
}
// If more than max_days away, move to the previous period
while ($days_until_exp > $max_days) {
$exp_date->modify('-1 month');
$days_until_exp = $exp_date->diff($current_date)->days;
}
// Final check to ensure we're not less than min_days or in the past
if ($days_until_exp < $min_days || $exp_date <= $current_date) {
$exp_date->modify('+1 month');
}
// Adjust for multiple periods
if ($p['validity'] > 1) {
$exp_date->modify('+' . ($p['validity'] - 1) . ' months');
}
$date_exp = $exp_date->format('Y-m-d');
$time = "23:59:59";
} else if ($p['validity_unit'] == 'Days') {
$datetime = explode(' ', date("Y-m-d H:i:s", strtotime('+' . $p['validity'] . ' day')));
$date_exp = $datetime[0];
@ -80,6 +104,14 @@ class Radius
$this->customerAddPlan($customer, $plan, $date_exp . ' ' . $time);
}
}
function sync_customer($customer, $plan)
{
$t = ORM::for_table('tbl_user_recharges')->where('username', $customer['username'])->where('status', 'on')->findOne();
$date_exp = $t['expiration'];
$time = $t['time'];
$this->customerAddPlan($customer, $plan, $date_exp . ' ' . $time);
}
function remove_customer($customer, $plan)
{
@ -341,12 +373,11 @@ class Radius
// expired user
if ($expired != '') {
//extend session time only if the plan are the same
if ($plan['plan_id'] == $p['plan_id'] && $config['extend_expiry'] != 'no') {
// session timeout [it reset everyday, am still making my research] we can use it for something like 1H/Day - since it reset daily, and Max-All-Session clear everything
//$this->upsertCustomer($customer['username'], 'Session-Timeout', 3600); // 3600 = 1 hour
$this->upsertCustomer($customer['username'], 'Max-All-Session', strtotime($expired) - time());
$this->upsertCustomer($customer['username'], 'Expiration', date('d M Y H:i:s', strtotime($expired)));
}
// session timeout [it reset everyday, am still making my research] we can use it for something like 1H/Day - since it reset daily, and Max-All-Session clear everything
//$this->upsertCustomer($customer['username'], 'Session-Timeout', 3600); // 3600 = 1 hour
$this->upsertCustomer($customer['username'], 'Max-All-Session', strtotime($expired) - time());
$this->upsertCustomer($customer['username'], 'Expiration', date('d M Y H:i:s', strtotime($expired)));
// Mikrotik Spesific
$this->upsertCustomer(
$customer['username'],
@ -372,7 +403,7 @@ class Radius
$this->upsertCustomerAttr($customer['username'], 'Framed-IP-Address', '0.0.0.0', ':=');
$this->upsertCustomerAttr($customer['username'], 'Framed-IP-Netmask', '255.255.255.0', ':=');
}
}
}
return true;

View File

@ -21,6 +21,11 @@ class RadiusRest {
function add_customer($customer, $plan)
{
}
function sync_customer($customer, $plan)
{
$this->add_customer($customer, $plan);
}
// Remove Customer to Mikrotik/Device
function remove_customer($customer, $plan)

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",
@ -381,8 +382,9 @@
"SuperAdmin": "Super Admin",
"Lists": "Daftar",
"Vouchers": "Voucher",
"Refill_Customer": "Isi Ulang Pelanggan",
"Recharge_Customer": "Isi Ulang Pelanggan",
"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",
@ -490,7 +492,7 @@
"Enable_Tax_System": "Aktifkan Sistem Pajak",
"Tax_will_be_calculated_in_Internet_Plan_Price": "Pajak akan dihitung dalam Harga Paket Internet",
"Tax_Rate": "Persentase pajak",
"Custom": "Kebiasaan",
"Custom": "Kustom",
"Tax_Rates_in_percentage": "Tarif Pajak dalam persentase",
"Custom_Tax_Rate": "Tarif Pajak Khusus",
"Enter_Custom_Tax_Rate": "Masukkan Tarif Pajak Khusus",
@ -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,13 +535,12 @@
"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",
"Customer_Geo_Location_Information": "Informasi Lokasi Geo Pelanggan",
"": "",
"Code": "Kode",
"Send_Personal_Message": "Kirim Pesan Pribadi",
"Send_Via": "Kirim melalui",
@ -547,7 +549,7 @@
"Customer_Name": "Nama Pelanggan",
"Customer_Username": "Nama Pengguna Pelanggan",
"Customer_Phone": "Telepon Pelanggan",
"Your_Company_Name": "Nama perusahaan Anda",
"Your_Company_Name": "Nama Perusahaan Anda",
"Change": "Mengubah",
"Change_Phone_Number": "Ubah Nomor Telepon",
"Current_Number": "Nomor Saat Ini",
@ -555,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",
@ -568,11 +570,349 @@
"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",
"Balance" "Saldo",
"Verification_code": "Kod3 V3r1fik@s1",
"Registration_code": "Kod3 R3g1str@s1",
"TX": "TX",
"RX": "RX",
"Database": "Database"
"Database": "Database",
"Additional_Billing": "Penagihan Tambahan",
"paid_off": "terbayar lunas",
"Sync_account_if_you_failed_login_to_internet": "Sinkronkan akun jika Anda gagal masuk ke internet",
"_Are_You_Sure_": "Apa kamu yakin?",
"Send_your_balance___": "Kirim saldo Anda?",
"_": "-",
"Search_Users": "Pencarian Pengguna",
"Routers_Maps": "Peta Router",
"Theme_Voucher": "Tema Voucher",
"Payment_Info": "Info Pembayaran",
"Documentation": "Dokumentasi",
"Customers": "Pelanggan",
"Package_Name": "Nama Paket",
"Routers_Offline": "Router Off",
"Cron_appear_not_been_setup__please_check_your_cron_setup_": "Cron tampaknya belum disiapkan, silakan periksa pengaturan cron Anda.",
"Buy": "Beli",
"You_are_already_logged_in": "Anda sudah masuk",
"PPPOE_Package": "Paket PPPoE",
"Prepaid": "Prabayar",
"Postpaid": "Pascabayar",
"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 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 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",
"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 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",
"Update_PHPNuxBill": "Perbarui PHPNuxBill",
"Ask_Github_Community": "Tanya Komunitas Github",
"Ask_Telegram_Community": "Tanya Komunitas Telegram",
"Token_has_expired__Please_log_in_again_": "Token telah kedaluwarsa. Silakan masuk lagi.",
"danger": "bahaya",
"Application_Name___Company_Name": "Nama Aplikasi \/ Nama Perusahaan",
"For_PDF_Reports___Best_size_1078_x_200___uploaded_image_will_be_autosize": "Untuk Laporan PDF | Ukuran terbaik 1078 x 200 | gambar yang diunggah akan berukuran otomatis",
"Print_Max_Char": "Cetak Max Char",
"For_invoice_print_using_Thermal_Printer": "Untuk cetak faktur menggunakan Printer Thermal",
"Theme": "Tema",
"Default": "Bawaan",
"Theme_Info": "Info Tema",
"This_used_for_admin_to_select_payment_in_recharge__using_comma_for_every_new_options": "Ini digunakan untuk admin untuk memilih pembayaran dalam pengisian ulang, menggunakan koma untuk setiap opsi baru",
"Income_will_reset_every_this_day": "Pendapatan akan direset setiap hari ini",
"edit_at_config_php": "edit di config.php",
"Hide_Dashboard_Content": "Sembunyikan Konten Dasbor",
"Redirect_URL_after_Activation": "Pengalihan URL setelah Aktivasi",
"Enable_Radius": "Aktifkan Radius",
"Radius_Instructions": "Petunjuk Radius",
"Customer_can_request_to_extend_expirations": "Pelanggan dapat meminta perpanjangan masa berlaku",
"i_agree_to_extends_and_will_paid_full_after_this": "Saya setuju untuk memperpanjang dan akan membayar penuh setelah ini",
"Customer_Balance_System": "Sistem Saldo Pelanggan",
"Telegram_Bot_Token": "Token Bot Telegram",
"Telegram_User_Channel_Group_ID": "ID Pengguna\/Saluran\/Grup Telegram",
"You_will_get_Payment_and_Error_notification": "Anda akan mendapatkan pemberitahuan Pembayaran dan Kesalahan",
"Test_SMS": "Tes SMS",
"SMS_Server_URL": "URL Server SMS",
"Must_include": "Harus menyertakan",
"it_will_be_replaced_": "itu akan diganti.",
"Or_use_Mikrotik_SMS": "Atau gunakan Mikrotik SMS",
"Select_Router": "Pilih Router",
"You_can_use": "Anda dapat menggunakan",
"in_here_too_": "di sini juga.",
"Free_Server": "Server Gratis",
"WhatsApp_Server_URL": "URL Server WhatsApp",
"Empty_this_to_use_internal_mail___PHP": "Kosongkan ini untuk menggunakan internal mail() PHP",
"SMTP_Username": "Nama Pengguna SMTP",
"SMTP_Password": "Kata Sandi SMTP",
"SMTP_Security": "Keamanan SMTP",
"Mail_Reply_To": "Balas Email Ke",
"Customer_will_reply_email_to_this_address__empty_if_you_want_to_use_From_Address": "Pelanggan akan membalas email ke alamat ini, kosong jika Anda ingin menggunakan Alamat Dari",
"None": "Tidak ada",
"By_WhatsApp": "Melalui WhatsApp",
"By_SMS": "Melalui SMS",
"By_Email": "Melalui Email",
"From_Direct_Chat_Link_": "Dari Tautan Obrolan Langsung.",
"Access_Token": "Token Akses",
"Empty_this_to_randomly_created_API_key": "Kosongkan ini ke kunci API yang dibuat secara acak",
"Enable_Session_Timeout": "Aktifkan Batas Waktu Sesi",
"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": "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 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",
"By_WhatsApp_and_SMS": "Melalui WhatsApp dan SMS",
"Email_OTP_Required": "Email OTP Diperlukan",
"OTP_is_required_when_user_want_to_change_Email_Address": "OTP diperlukan ketika pengguna ingin mengubah Alamat Email",
"Show_Bandwidth_Plan": "Tampilkan Paket Bandwidth",
"_for_Customer": "untuk Pelanggan",
"Custome": "Pelanggan",
"Custome_Tax_Rate": "Tarif Pajak Bea Cukai",
"Enter_Custome_Tax_Rate": "Masukkan Tarif Pajak Pelanggan",
"Authentication": "Autentikasi",
"Github_Username": "Nama Pengguna Github",
"Github_Token": "Token Github",
"Create_GitHub_personal_access_token": "Buat token akses pribadi GitHub",
"only_need_repo_scope": "hanya butuh cakupan repo",
"This_will_allow_you_to_download_plugin_from_private_paid_repository": "Ini akan memungkinkan Anda mengunduh plugin dari repositori pribadi\/berbayar",
"Expired_Cronjob_Every_5_Minutes": "Cronjob Kedaluwarsa Setiap 5 Menit",
"Expired_Cronjob_Every_1_Hour": "Cronjob Kedaluwarsa Setiap 1 Jam",
"Reminder_Cronjob_Every_7_AM": "Pengingat Cronjob Setiap Jam 7 Pagi",
"Check_if_Customer_Online": "Periksa apakah Pelanggan Online",
"Active_Customers": "Pelanggan Aktif",
"This_will_show_is_Customer_currently_is_online_or_not": "Ini akan menunjukkan apakah Pelanggan sedang online atau tidak",
"3_Months": "3 Bulan",
"Used_Date": "Tanggal Penggunaan",
"Plugin_Installer": "Pemasang Plugin",
"Upload_Zip_Plugin_Theme_Device": "Unggah Plugin\/Tema\/Perangkat Zip",
"Install": "Pasang",
"via_SMS": "melalui SMS",
"Via_WhatsApp": "Melalui WhatsApp",
"Via_WhatsApp_and_SMS": "Melalui WhatsApp dan SMS",
"Send_Bulk_Message": "Kirim Pesan Massal",
"Group": "Kelompok",
"All_Customers": "Semua Pelanggan",
"New_Customers": "Pelanggan Baru",
"Expired_Customers": "Pelanggan yang Kedaluwarsa",
"Message_per_time": "Pesan per waktu",
"5_Messages": "5 Pesan",
"10_Messages": "10 Pesan",
"15_Messages": "15 Pesan",
"20_Messages": "20 Pesan",
"30_Messages": "30 Pesan",
"40_Messages": "40 Pesan",
"50_Messages": "50 Pesan",
"60_Messages": "60 Pesan",
"Use_20_and_above_if_you_are_sending_to_all_customers_to_avoid_server_time_out": "Gunakan 20 dan di atasnya jika Anda mengirim ke semua pelanggan untuk menghindari waktu server habis",
"Delay": "Menunda",
"No_Delay": "Tidak Ada Penundaan",
"5_Seconds": "5 Detik",
"10_Seconds": "10 Detik",
"15_Seconds": "15 Detik",
"20_Seconds": "20 Detik",
"Use_at_least_5_secs_if_you_are_sending_to_all_customers_to_avoid_being_banned_by_your_message_provider": "Gunakan setidaknya 5 detik jika Anda mengirim ke semua pelanggan untuk menghindari pemblokiran oleh penyedia pesan Anda",
"Testing__if_checked_no_real_message_is_sent_": "Pengujian [jika dicentang, tidak ada pesan nyata yang dikirim]",
"Message_Results": "Hasil Pesan",
"VPN_Plans": "Paket VPN",
"VPN_Package": "Paket VPN",
"Balance_Package": "Paket Saldo",
"New_Service_Package": "Paket Layanan Baru",
"Package_Price": "Harga Paket",
"Radius_Package": "Paket Radius",
"Hotspot_Package": "Paket Hotspot",
"Maintenance_Mode_Settings": "Pengaturan Mode Pemeliharaan",
"Status_": "Status:",
"Force_Logout_": "Paksa Keluar:",
"End_Date_": "Tanggal Berakhir:",
"Save": "Simpan",
"Not_Active": "Tidak Aktif",
"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",
"Minute": "Menit",
"Hour": "Jam",
"Buy_Balance_Plans": "Beli Paket Saldo",
"New_Voucher_for_10mbps_Created": "Voucher Baru untuk 10mbps Dibuat",
"Previous": "Sebelumnya",
"Share": "Bagikan",
"Agent": "Agen",
"Sub_District": "Kecamatan",
"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.",
"Photo": "Foto",
"just_now": "baru saja",
"Face_Detection": "Deteksi Wajah",
"Password_should_be_minimum_6_characters": "Kata sandi minimal harus 6 karakter",
"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 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",
"Package_Type": "Tipe Paket",
"Package_Validity": "Validitas Paket",
"Expired_Internet_Package": "Paket Internet Kedaluwarsa",
"Default___Remove_Customer": "Default - Hapus Pelanggan",
"When_Expired__customer_will_be_move_to_selected_internet_package": "Jika masa berlaku habis, pelanggan akan dipindahkan ke paket internet yang dipilih",
"Data_Change": "Perubahan Data",
"Home_Address": "Alamat Rumah",
"Email_Address": "Alamat Email",
"Custom_Balance": "Saldo Kustom",
"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",
"Pending_Payments": "Pembayaran Tertunda",
"Cancelled_Payments": "Pembayaran yang Dibatalkan",
"Daily_Sales": "Penjualan Harian",
"Monthly_Sales": "Penjualan Bulanan",
"Weekly_Sales": "Penjualan Mingguan",
"How_its_Works": "Cara kerjanya",
"_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 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",
"Search_Phone_Number_____": "Cari Nomor Telepon.....",
"Transaction_ID": "ID Transaksi",
"Transaction_Ref": "Referensi Transaksi",
"Voucher_Code": "Kode Voucher",
"Amount": "Jumlah",
"Transaction_Status": "Status Transaksi",
"Payment_Method": "Metode Pembayaran",
"Payment_Date": "Tanggal Pembayaran",
"Plan_Expiry_Date": "Tanggal Kedaluwarsa Paket",
"Created_on": "Dibuat pada",
"Expires_on": "Kedaluwarsa pada",
"Package_Details": "Rincian Paket",
"Summary": "Ringkasan",
"Allow_Balance_custom_amount": "Izinkan Saldo jumlah khusus",
"Allow_Customer_buy_balance_with_any_amount": "Izinkan Pelanggan membeli saldo dengan jumlah berapa pun",
"Or": "Atau",
"Filter": "Menyaring",
"Show_chart": "Tampilkan grafik",
"Start_Date": "Tanggal Mulai",
"Start_time": "Waktu Mulai",
"End_Date": "Tanggal Akhir",
"End_Time": "Waktu berakhir",
"Internet_Plans": "Paket Internet",
"Methods": "Metode",
"Hap_Lite": "Hap Lite",
"balance": "saldo",
"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 paginasi",
"First_Name": "Nama Depan",
"Last_Name": "Nama Belakang",
"General": "Umum",
"Registration": "Pendaftaran",
"Allow_Registration": "Izinkan Registrasi",
"Voucher_Only": "Hanya Voucher",
"No_Registration": "Tidak Ada Registrasi",
"Registration_Username": "Nama Pengguna Registrasi",
"Customer_Registration_need_to_validate_using_OTP": "Registrasi Pelanggan perlu divalidasi menggunakan OTP",
"SMS_Notification": "Pemberitahuan SMS",
"Tax_Rates_by_percentage": "Tarif Pajak Berdasarkan Persentase",
"Settings_For_Mikrotik": "Pengaturan Untuk Mikrotik",
"Settings_For_Cron_Expired": "Pengaturan Untuk Cron Kedaluwarsa",
"Choose_one__above_or_below": "Pilih salah satu, di atas atau di bawah",
"Settings_For_Cron_Reminder": "Pengaturan Untuk Pengingat Cron",
"Security": "Keamanan",
"Enable_CSRF_Validation": "Aktifkan Validasi CSRF",
"Cross_site_request_forgery": "Pemalsuan permintaan lintas situs",
"Forgot_Password": "Lupa Kata Sandi",
"Validity_Periode": "Periode Validitas",
"Transaction_History_List": "Daftar Riwayat Transaksi",
"plan": "paket",
"package": "paket",
"Cards": "Kartu",
"CRM": "CRM",
"Coupons": "Kupon",
"Custom_Fields": "Bidang Kustom",
"Search_Coupons": "Cari Kupon",
"Add_Coupon": "Tambahkan Kupon",
"Value": "Nilai",
"Max_Usage": "Penggunaan Maksimal",
"Usage_Count": "Jumlah Pemakaian",
"Min_Order": "Pesanan Min",
"Max_Discount": "Diskon Maksimal",
"Updated_Date": "Tanggal Diperbarui",
"Action": "Tindakan",
"No_coupons_found_": "Tidak ada kupon yang ditemukan.",
"Delete_Selected": "Hapus yang Dipilih",
"Coupon_Code": "Kode Kupon",
"Random": "Acak",
"Unique_code_for_the_coupon": "Kode unik untuk kupon",
"Fixed_Discount": "Diskon Tetap",
"Percent_Discount": "Diskon Persen",
"Discount_Value": "Nilai Diskon",
"Value_of_the_discount__amount_or_percentage_": "Nilai diskon (jumlah atau persentase)",
"Brief_explanation_of_the_coupon": "Penjelasan singkat tentang kupon",
"Maximum_number_of_times_this_coupon_can_be_used_0_is_Unlimited": "Jumlah maksimum penggunaan kupon ini 0 adalah Tidak Terbatas",
"Minimum_Order_Amount": "Jumlah Pesanan Minimum",
"Minimum_cart_total_required_to_use_this_coupon": "Total keranjang minimum yang diperlukan untuk menggunakan kupon ini",
"Max_Discount_Amount": "Jumlah Diskon Maksimum",
"Maximum_discount_amount_applicable__for_percent_type_": "Jumlah diskon maksimum yang berlaku (untuk jenis persen)",
"Value_of_the_discount__percentage__max_100_": "Nilai diskon (persentase, maks 100)",
"Value_of_the_discount__amount_": "Nilai diskon (jumlah)",
"Voucher_Cards": "Kartu Voucher",
"Create_Date": "Tanggal Pembuatan",
"Postpaid_Recharge_for_the_first_time_use": "Isi Ulang Pascabayar untuk penggunaan pertama kali",
"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 paket di atas",
"Note": "Catatan",
"Customer_Login_Page_Settings": "Pengaturan Halaman Login Pelanggan",
"Choose_Template": "Pilih Template",
"Select_your_login_template_type": "Pilih jenis template login Anda",
"Select_Login_Page": "Pilih Halaman Login",
"Select_your_preferred_login_template": "Pilih template login pilihan Anda",
"Page_Heading___Company_Name": "Judul Halaman \/ Nama Perusahaan",
"This_Name_will_be_shown_on_the_login_wallpaper": "Nama ini akan ditampilkan pada wallpaper login",
"Page_Description": "Deskripsi Halaman",
"This_will_also_display_on_wallpaper__You_can_use_html_tag": "Ini juga akan ditampilkan di wallpaper, Anda dapat menggunakan tag html",
"Favicon": "Ikon favicon",
"Best_size_30_x_30___uploaded_image_will_be_autosize": "Ukuran terbaik 30 x 30 | gambar yang diunggah akan berukuran otomatis",
"Login_Page_Logo": "Logo Halaman Login",
"Best_size_300_x_60___uploaded_image_will_be_autosize": "Ukuran terbaik 300 x 60 | gambar yang diunggah akan berukuran otomatis",
"Login_Page_Wallpaper": "Wallpaper Halaman Login",
"Best_size_1920_x_1080___uploaded_image_will_be_autosize": "Ukuran terbaik 1920 x 1080 | gambar yang diunggah akan berukuran otomatis",
"Photo_Required": "Foto Diperlukan",
"Customer_Registration_need_to_upload_their_photo": "Registrasi Pelanggan perlu mengunggah foto mereka",
"Notify_Admin": "Beritahu Admin",
"Notify_Admin_upon_self_registration": "Beritahu Admin saat registrasi mandiri",
"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]",
"Visit": "Kunjungi",
"sync": "Sinkron"
}

View File

@ -5,10 +5,10 @@
"Registration_Info": "Informaci\u00f3n de registro",
"Voucher_not_found__please_buy_voucher_befor_register": "Cup\u00f3n no encontrado, compre el cup\u00f3n antes de registrarse",
"Register_Success__You_can_login_now": "\u00a1Registro exitoso! Puedes iniciar sesi\u00f3n ahora",
"Log_in_to_Member_Panel": "Log in to Member Panel",
"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": "DIGITAL-RED",
"PHPNuxBill": "Compañia",
"Username": "Usuario",
"Password": "Contrase\u00f1a",
"Passwords_does_not_match": "Las contrase\u00f1as no coinciden",
@ -27,7 +27,7 @@
"Failed_to_save_page__make_sure_i_can_write_to_folder_pages___i_chmod_664_pages___html_i_": "No se pudo guardar la p\u00e1gina, aseg\u00farese de que pueda escribir en las p\u00e1ginas de la carpeta, <i>chmod 664 pages\/*.html<i>",
"Saving_page_success": "Guardando el \u00e9xito de la p\u00e1gina",
"Sometimes_you_need_to_refresh_3_times_until_content_change": "A veces es necesario actualizar 3 veces hasta que cambie el contenido",
"Dashboard": "Dashboard",
"Dashboard": "Panel",
"Search_Customers___": "Buscar clientes...",
"My_Account": "Mi cuenta",
"My_Profile": "Mi perfil",
@ -140,7 +140,7 @@
"Administrator_Users": "Usuarios administradores",
"Manage_Administrator": "Administrar administrador",
"Add_New_Administrator": "Agregar nuevo administrador",
"Localisation": "Localizaci\u00f3n",
"Localisation": "Idioma y Fecha",
"Backup_Restore": "Copia de seguridad\/restauracion",
"General_Settings": "Configuraci\u00f3n general",
"Date": "Fecha",
@ -213,166 +213,630 @@
"Folder_Name": "Nombre de la carpeta",
"Translator": "Traducir",
"Language_Name_Already_Exist": "El nombre del idioma ya existe",
"Payment_Gateway": "Payment Gateway",
"Community": "Community",
"1_user_can_be_used_for_many_devices_": "1 user can be used for many devices?",
"Cannot_be_change_after_saved": "Cannot be change after saved",
"Explain_Coverage_of_router": "Jelaskan Cakupan wilayah hotspot",
"Name_of_Area_that_router_operated": "Nama Lokasi\/Wilayah Router beroperasi",
"Payment_Notification_URL__Recurring_Notification_URL__Pay_Account_Notification_URL": "Payment Notification URL, Recurring Notification URL, Pay Account Notification URL",
"Finish_Redirect_URL__Unfinish_Redirect_URL__Error_Redirect_URL": "Finish Redirect URL, Unfinish Redirect URL, Error Redirect URL",
"Status": "Status",
"Plan_Not_found": "Plan Not found",
"Failed_to_create_transaction_": "Failed to create transaction.",
"Seller_has_not_yet_setup_Xendit_payment_gateway": "Seller has not yet setup Xendit payment gateway",
"Admin_has_not_yet_setup_Xendit_payment_gateway__please_tell_admin": "Admin has not yet setup Xendit payment gateway, please tell admin",
"Buy_this__your_active_package_will_be_overwrite": "Buy this? your active package will be overwrite",
"You_already_have_unpaid_transaction__cancel_it_or_pay_it_": "You already have unpaid transaction, cancel it or pay it.",
"Transaction_Not_found": "Transaction Not found",
"Cancel_it_": "Cancel it?",
"expired": "expired",
"Check_for_Payment": "Check for Payment",
"Transaction_still_unpaid_": "Transaction still unpaid.",
"Paid_Date": "Paid Date",
"Transaction_has_been_paid_": "Transaction has been paid.",
"PAID": "PAID",
"CANCELED": "CANCELED",
"UNPAID": "UNPAID",
"PAY_NOW": "PAY NOW",
"Buy_Hotspot_Plan": "Buy Hotspot Plan",
"Buy_PPOE_Plan": "Buy PPOE Plan",
"Package": "Package",
"Order_Internet_Package": "Order Internet Package",
"Unknown_Command_": "Unknown Command.",
"Checking_payment": "Checking payment",
"Create_Transaction_Success": "Create Transaction Success",
"You_have_unpaid_transaction": "You have unpaid transaction",
"TripayPayment_Channel": "TripayPayment Channel",
"Payment_Channel": "Payment Channel",
"Payment_check_failed_": "Payment check failed.",
"Order_Package": "Order Package",
"Transactions": "Transactions",
"Payments": "Payments",
"History": "History",
"Order_History": "Order History",
"Gateway": "Gateway",
"Date_Done": "Date Done",
"Unpaid_Order": "Unpaid Order",
"Payment_Gateway_Not_Found": "Payment Gateway Not Found",
"Payment_Gateway_saved_successfully": "Payment Gateway saved successfully",
"ORDER": "ORDER",
"Package_History": "Package History",
"Buy_History": "Buy History",
"Activation_History": "Activation History",
"Buy_Package": "Buy Package",
"Email": "Email",
"Company_Footer": "Company Footer",
"Will_show_below_user_pages": "Will show below user pages",
"Request_OTP": "Request OTP",
"Verification_Code": "Verification Code",
"SMS_Verification_Code": "SMS Verification Code",
"Please_enter_your_email_address": "Please enter your email address",
"Failed_to_create_Paypal_transaction_": "Failed to create Paypal transaction.",
"Payment_Gateway": "Pasarela de Pago",
"Community": "Comunidad",
"1_user_can_be_used_for_many_devices_": "1 usuario puede utilizarse para varios dispositivos?",
"Cannot_be_change_after_saved": "No se puede cambiar despu\u00e9s de guardar",
"Explain_Coverage_of_router": "Explicar la cobertura del enrutador",
"Name_of_Area_that_router_operated": "Nombre del \u00e1rea que operaba el enrutador",
"Payment_Notification_URL__Recurring_Notification_URL__Pay_Account_Notification_URL": "URL de notificaci\u00f3n de pago, URL de notificaci\u00f3n recurrente, URL de notificaci\u00f3n de cuenta de pago",
"Finish_Redirect_URL__Unfinish_Redirect_URL__Error_Redirect_URL": "URL de redireccionamiento finalizada, URL de redireccionamiento incompleta, URL de redireccionamiento con error",
"Status": "Estado",
"Plan_Not_found": "Plan no encontrado",
"Failed_to_create_transaction_": "No se pudo crear la transacci\u00f3n.",
"Seller_has_not_yet_setup_Xendit_payment_gateway": "El vendedor a\u00fan no ha configurado la pasarela de pago Xendit",
"Admin_has_not_yet_setup_Xendit_payment_gateway__please_tell_admin": "El administrador a\u00fan no ha configurado la pasarela de pago Xendit, inf\u00f3rmeselo al administrador.",
"Buy_this__your_active_package_will_be_overwrite": "\u00bfCompraste esto? Tu paquete activo se sobrescribir\u00e1",
"You_already_have_unpaid_transaction__cancel_it_or_pay_it_": "Ya tienes transacci\u00f3n impaga, canc\u00e9lala o p\u00e1gala.",
"Transaction_Not_found": "Transacci\u00f3n no encontrada",
"Cancel_it_": "\u00bfCancelalo?",
"expired": "Caducada",
"Check_for_Payment": "Verificar pago",
"Transaction_still_unpaid_": "Transacci\u00f3n a\u00fan impaga.",
"Paid_Date": "Fecha de pago",
"Transaction_has_been_paid_": "La transacci\u00f3n ha sido pagada.",
"PAID": "PAGADA",
"CANCELED": "CANCELADA",
"UNPAID": "NO PAGADO",
"PAY_NOW": "PAGAR AHORA",
"Buy_Hotspot_Plan": "Comprar plan de hotspot",
"Buy_PPOE_Plan": "Comprar Plan PPPoE",
"Package": "Paquete",
"Order_Internet_Package": "Solicitar paquete de Internet",
"Unknown_Command_": "Comando desconocido.",
"Checking_payment": "Comprobando el pago",
"Create_Transaction_Success": "Crear transacciones exitosas",
"You_have_unpaid_transaction": "Tienes transacci\u00f3n impaga",
"TripayPayment_Channel": "Canal de pago Tripay",
"Payment_Channel": "Canal de pago",
"Payment_check_failed_": "El cheque de pago fall\u00f3.",
"Order_Package": "Paquete de pedido",
"Transactions": "Transacciones",
"Payments": "Pagos",
"History": "Historial",
"Order_History": "Historial de pedidos",
"Gateway": "Puerta",
"Date_Done": "Fecha de finalizaci\u00f3n",
"Unpaid_Order": "Orden no pagada",
"Payment_Gateway_Not_Found": "Pasarela de pago no encontrada",
"Payment_Gateway_saved_successfully": "Pasarela de pago guardada exitosamente",
"ORDER": "ORDEN",
"Package_History": "Historial de paquetes",
"Buy_History": "Historial de compra",
"Activation_History": "Historial de activaci\u00f3n",
"Buy_Package": "Comprar paquete",
"Email": "Correo",
"Company_Footer": "Pie de p\u00e1gina",
"Will_show_below_user_pages": "Se mostrar\u00e1n debajo de las p\u00e1ginas de usuario.",
"Request_OTP": "Solicitar OTP",
"Verification_Code": "Verificacion por codigo",
"SMS_Verification_Code": "C\u00f3digo de verificaci\u00f3n por SMS",
"Please_enter_your_email_address": "Por favor, introduzca su direcci\u00f3n de correo electr\u00f3nico",
"Failed_to_create_Paypal_transaction_": "No se pudo crear la transacci\u00f3n de Paypal.",
"Plugin": "Plugin",
"Plugin_Manager": "Plugin Manager",
"User_Notification": "User Notification",
"Expired_Notification": "Expired Notification",
"User_will_get_notification_when_package_expired": "User will get notification when package expired",
"Expired_Notification_Message": "Expired Notification Message",
"Payment_Notification": "Payment Notification",
"User_will_get_invoice_notification_when_buy_package_or_package_refilled": "User will get invoice notification when buy package or package refilled",
"Current_IP": "Current IP",
"Current_MAC": "Current MAC",
"Login_Status": "Login Status",
"Login_Request_successfully": "Login Request successfully",
"Logout_Request_successfully": "Logout Request successfully",
"Disconnect_Internet_": "Disconnect Internet?",
"Not_Online__Login_now_": "Not Online, Login now?",
"You_are_Online__Logout_": "You are Online, Logout?",
"Connect_to_Internet_": "Connect to Internet?",
"Your_account_not_connected_to_internet": "Your account not connected to internet",
"Balance": "Balance",
"Balance_System": "Balance System",
"Enable_System": "Enable System",
"Allow_Transfer": "Allow Transfer",
"Telegram_Notification": "Telegram Notification",
"SMS_OTP_Registration": "SMS OTP Registration",
"Whatsapp_Notification": "Whatsapp Notification",
"Plugin_Manager": "Administraci\u00f3n de Plugins",
"User_Notification": "Notificaci\u00f3n de usuario",
"Expired_Notification": "Notificaci\u00f3n caducada",
"User_will_get_notification_when_package_expired": "El usuario recibir\u00e1 una notificaci\u00f3n cuando el paquete caduque",
"Expired_Notification_Message": "Mensaje de notificaci\u00f3n caducado",
"Payment_Notification": "Notificacion de pago",
"User_will_get_invoice_notification_when_buy_package_or_package_refilled": "El usuario recibir\u00e1 una notificaci\u00f3n de factura cuando compre el paquete o recargue el paquete.",
"Current_IP": "IP actual",
"Current_MAC": "MAC actual",
"Login_Status": "Estado de inicio de sesi\u00f3n",
"Login_Request_successfully": "Solicitud de inicio de sesi\u00f3n exitosa",
"Logout_Request_successfully": "Solicitud de cierre de sesi\u00f3n exitosa",
"Disconnect_Internet_": "\u00bfDesconectar Internet?",
"Not_Online__Login_now_": "No en l\u00ednea, \u00bfiniciar sesi\u00f3n ahora?",
"You_are_Online__Logout_": "Est\u00e1s en l\u00ednea, \u00bfcerrar sesi\u00f3n?",
"Connect_to_Internet_": "\u00bfConectado a Internet?",
"Your_account_not_connected_to_internet": "Su cuenta no est\u00e1 conectada a Internet",
"Balance": "Saldo",
"Balance_System": "Sistema de Saldo",
"Enable_System": "Habilitar sistema",
"Allow_Transfer": "Permitir transferencia",
"Telegram_Notification": "Notificaci\u00f3n de Telegram",
"SMS_OTP_Registration": "Registro OTP por SMS",
"Whatsapp_Notification": "Notificaci\u00f3n de whatsapp",
"Tawk_to_Chat_Widget": "Tawk.to Chat Widget",
"Invoice": "Invoice",
"Country_Code_Phone": "Country Code Phone",
"Voucher_activation_menu_will_be_hidden": "Voucher activation menu will be hidden",
"Customer_can_deposit_money_to_buy_voucher": "Customer can deposit money to buy voucher",
"Allow_balance_transfer_between_customers": "Allow balance transfer between customers",
"Refill_Balance": "Refill Balance",
"Balance_Plans": "Balance Plans",
"Failed_to_create_transaction__": "Failed to create transaction. ",
"Failed_to_check_status_transaction__": "Failed to check status transaction. ",
"Disable_Voucher": "Disable Voucher",
"Reminder_Notification": "Reminder Notification",
"Reminder_Notification_Message": "Reminder Notification Message",
"Reminder_7_days": "Reminder 7 days",
"Reminder_3_days": "Reminder 3 days",
"Reminder_1_day": "Reminder 1 day",
"PPPOE_Password": "PPPOE Password",
"User_Cannot_change_this__only_admin__if_it_Empty_it_will_use_user_password": "User Cannot change this, only admin. if it Empty it will use user password",
"Invoice_Balance_Message": "Invoice Balance Message",
"Invoice_Notification_Payment": "Invoice Notification Payment",
"Balance_Notification_Payment": "Balance Notification Payment",
"Buy_Balance": "Buy Balance",
"Price": "Price",
"Validity": "Validity",
"Disable_auto_renewal_": "Disable auto renewal?",
"Auto_Renewal_On": "Auto Renewal On",
"Enable_auto_renewal_": "Enable auto renewal?",
"Auto_Renewal_Off": "Auto Renewal Off",
"Invoice_Footer": "Invoice Footer",
"Pay_With_Balance": "Pay With Balance",
"Pay_this_with_Balance__your_active_package_will_be_overwrite": "Pay this with Balance? your active package will be overwrite",
"Success_to_buy_package": "Success to buy package",
"Auto_Renewal": "Auto Renewal",
"View": "View",
"Back": "Back",
"Active": "Active",
"Transfer_Balance": "Transfer Balance",
"Send_your_balance_": "Send your balance?",
"Send": "Send",
"Cannot_send_to_yourself": "Cannot send to yourself",
"Sending_balance_success": "Sending balance success",
"From": "From",
"To": "To",
"insufficient_balance": "insufficient balance",
"Send_Balance": "Send Balance",
"Received_Balance": "Received Balance",
"Minimum_Balance_Transfer": "Minimum Balance Transfer",
"Minimum_Transfer": "Minimum Transfer",
"Company_Logo": "Company Logo",
"Expired_IP_Pool": "Expired IP Pool",
"Invoice": "Factura",
"Country_Code_Phone": "C\u00f3digo de pa\u00eds Tel\u00e9fono",
"Voucher_activation_menu_will_be_hidden": "El men\u00fa de activaci\u00f3n del vale estar\u00e1 oculto",
"Customer_can_deposit_money_to_buy_voucher": "El cliente puede depositar dinero para comprar un vale.",
"Allow_balance_transfer_between_customers": "Permitir transferencia de saldo entre clientes",
"Refill_Balance": "Saldo de recarga",
"Balance_Plans": "Planes de Saldo",
"Failed_to_create_transaction__": "No se pudo crear la transacci\u00f3n.",
"Failed_to_check_status_transaction__": "No se pudo verificar el estado de la transacci\u00f3n.",
"Disable_Voucher": "Desactivar Fichas",
"Reminder_Notification": "Notificaci\u00f3n de recordatorio",
"Reminder_Notification_Message": "Mensaje de notificaci\u00f3n de recordatorio",
"Reminder_7_days": "Recordatorio 7 d\u00edas",
"Reminder_3_days": "Recordatorio 3 d\u00edas",
"Reminder_1_day": "Recordatorio 1 d\u00edas",
"PPPOE_Password": "Contrase\u00f1a PPPOE",
"User_Cannot_change_this__only_admin__if_it_Empty_it_will_use_user_password": "El usuario no puede cambiar esto, solo el administrador. si est\u00e1 vac\u00edo usar\u00e1 la contrase\u00f1a de usuario",
"Invoice_Balance_Message": "Mensaje de saldo de factura",
"Invoice_Notification_Payment": "Pago de notificaci\u00f3n de factura",
"Balance_Notification_Payment": "Pago de notificaci\u00f3n de saldo",
"Buy_Balance": "Comprar Saldo",
"Price": "Precio",
"Validity": "Validez",
"Disable_auto_renewal_": "\u00bfDesactivar la renovaci\u00f3n autom\u00e1tica?",
"Auto_Renewal_On": "Renovaci\u00f3n autom\u00e1tica activada",
"Enable_auto_renewal_": "\u00bfHabilitar la renovaci\u00f3n autom\u00e1tica?",
"Auto_Renewal_Off": "Renovaci\u00f3n autom\u00e1tica desactivada",
"Invoice_Footer": "Pie de p\u00e1gina de factura",
"Pay_With_Balance": "Pagar con saldo",
"Pay_this_with_Balance__your_active_package_will_be_overwrite": "\u00bfPagar esto con Saldo? su paquete activo ser\u00e1 sobrescrito",
"Success_to_buy_package": "\u00c9xito al comprar el paquete.",
"Auto_Renewal": "Auto renovaci\u00f3n",
"View": "Vista",
"Back": "Atras",
"Active": "Activo",
"Transfer_Balance": "Transferir saldo",
"Send_your_balance_": "\u00bfEnviar tu saldo?",
"Send": "Enviar",
"Cannot_send_to_yourself": "No puedes enviarte a ti mismo",
"Sending_balance_success": "Env\u00edo de saldo exitoso",
"From": "De",
"To": "A",
"insufficient_balance": "Saldo Insuficiente",
"Send_Balance": "Saldo Enviado",
"Received_Balance": "Saldo Recibido",
"Minimum_Balance_Transfer": "Transferencia de saldo m\u00ednimo",
"Minimum_Transfer": "Transferencia m\u00ednima",
"Company_Logo": "Logo de la compa\u00f1\u00eda",
"Expired_IP_Pool": "Grupo de IP caducado",
"Proxy": "Proxy",
"Proxy_Server": "Proxy Server",
"Proxy_Server_Login": "Proxy Server Login",
"Proxy_Server": "Servidor Proxy",
"Proxy_Server_Login": "Iniciar sesi\u00f3n en el servidor proxy",
"Hotspot_Plan": "Hotspot Plan",
"PPPOE_Plan": "PPPOE Plan",
"UNKNOWN": "UNKNOWN",
"Are_You_Sure_": "Are You Sure?",
"Success_to_send_package": "Success to send package",
"Target_has_active_plan__different_with_current_plant_": "Target has active plan, different with current plant.",
"Recharge_a_friend": "Recharge a friend",
"Buy_for_friend": "Buy for friend",
"Buy_this_for_friend_account_": "Buy this for friend account?",
"Review_package_before_recharge": "Review package before recharge",
"Activate": "Activate",
"Deactivate": "Deactivate",
"Sync": "Sync",
"Failed_to_create_PaymeTrust_transaction_": "Failed to create PaymeTrust transaction.",
"Location": "Location",
"Voucher_Format": "Voucher Format",
"Service_Type": "Service Type",
"Others": "Others",
"PPPOE_Plan": "PPPoE Plan",
"UNKNOWN": "DESCONOCIDO",
"Are_You_Sure_": "Estas seguro\/a?",
"Success_to_send_package": "\u00c9xito al enviar el paquete",
"Target_has_active_plan__different_with_current_plant_": "Objetivo tiene plan activo, diferente con planta actual.",
"Recharge_a_friend": "Recargar a un amigo\/a",
"Buy_for_friend": "Comprar para amigo\/a",
"Buy_this_for_friend_account_": "\u00bfCompra esto por cuenta de amigo\/a?",
"Review_package_before_recharge": "Revisar paquete antes de recargar",
"Activate": "Activado",
"Deactivate": "Desactivado",
"Sync": "Sincronizar",
"Failed_to_create_PaymeTrust_transaction_": "No se pudo crear la transacci\u00f3n PaymeTrust.",
"Location": "Localizacion",
"Voucher_Format": "Formato de cup\u00f3n",
"Service_Type": "Tipo de Servicios",
"Others": "Otros",
"PPPoE": "PPPoE",
"Hotspot": "Hotspot",
"Monthly_Registered_Customers": "Monthly Registered Customers",
"Total_Monthly_Sales": "Total Monthly Sales",
"Active_Users": "Active Users",
"Languge_set_to_spanish": "Bahasa disetel ke bahasa Spanyol"
"Monthly_Registered_Customers": "Clientes registrados mensualmente",
"Total_Monthly_Sales": "Ventas mensuales totales",
"Active_Users": "Usuarios activos",
"Languge_set_to_spanish": "Idioma establecido en espa\u00f1ol",
"Login": "Acceso",
"Forgot_Password": "Has olvidado tu contrase\u00f1a",
"success": "Logueado con Exito",
"Click_Here": "Haga clic aqu\u00ed",
"Search_Users": "Buscar usuarios",
"SuperAdmin": "Superadministrador",
"Lists": "Listas de Clientes",
"Vouchers": "Fichas",
"Refill_Customer": "Recarga de cliente",
"Recharge_Customer": "Recargar Cliente",
"Internet_Plan": "Plan de Internet",
"Send_Message": "Enviar mensaje",
"Single_Customer": "Cliente \u00fanico",
"Bulk_Customers": "Clientes al por mayor",
"Routers_Maps": "Mapas de enrutadores",
"Theme_Voucher": "Cup\u00f3n tem\u00e1tico",
"Customer_Announcement": "Anuncio para el cliente",
"Payment_Info": "Informaci\u00f3n de pago",
"Privacy_Policy": "Pol\u00edtica de Privacidad",
"Terms_and_Conditions": "T\u00e9rminos y condiciones",
"Maintenance_Mode": "Modo de mantenimiento",
"Devices": "Dispositivos",
"Logs": "Registros",
"Documentation": "Documentaci\u00f3n",
"Expired": "Venci\u00f3",
"Customers": "Clientes",
"Package_Name": "Nombre del paquete",
"Created___Expired": "Creado \/ Expirado",
"Internet_Package": "Paquete de Internet",
"year": "A\u00f1o",
"month": "Mes",
"week": "Semana",
"day": "D\u00eda",
"hour": "Hora",
"minute": "Minuto",
"second": "Segundo",
"ago": "Atr\u00e1s",
"Prev": "Anterior",
"Cron_Job_last_ran_on": "La \u00faltima ejecuci\u00f3n del trabajo cron se realiz\u00f3 el",
"All_Users_Insights": "Informaci\u00f3n de todos los usuarios",
"Created_Date": "Fecha de creaci\u00f3n",
"Ascending": "Ascendente",
"Descending": "Descendiendo",
"Banned": "Prohibido",
"Disabled": "Desactivado",
"Inactive": "Inactivo",
"Suspended": "Suspendido",
"Add": "Agregar",
"Account_Type": "Tipo de cuenta",
"Contact": "Contacto",
"Active_Customers": "Clientes activos",
"Extend": "Extender",
"Application_Name___Company_Name": "Nombre de la aplicaci\u00f3n \/ Nombre de la empresa",
"For_PDF_Reports___Best_size_1078_x_200___uploaded_image_will_be_autosize": "Para informes en PDF | Tama\u00f1o \u00f3ptimo 1078 x 200 | La imagen cargada se ajustar\u00e1 autom\u00e1ticamente",
"Print_Max_Char": "Imprimir Max Char",
"For_invoice_print_using_Thermal_Printer": "Para imprimir facturas mediante impresora t\u00e9rmica",
"Theme": "Tema",
"Default": "Por defecto",
"Theme_Info": "Informaci\u00f3n del tema",
"Recharge_Using": "Recargar usando",
"Cash": "Dinero",
"Bank_Transfer": "Transferencia bancaria",
"This_used_for_admin_to_select_payment_in_recharge__using_comma_for_every_new_options": "Esto se utiliza para que el administrador seleccione el pago en la recarga, utilizando una coma para cada nueva opci\u00f3n.",
"Income_reset_date": "Fecha de reinicio de ingresos",
"Income_will_reset_every_this_day": "Los ingresos se restablecer\u00e1n cada d\u00eda.",
"edit_at_config_php": "editar en config.php",
"Hide_Dashboard_Content": "Ocultar el contenido del panel",
"No": "No",
"Yes": "S\u00ed",
"Disable_Registration": "Deshabilitar registro",
"Customer_just_Login_with_Phone_number_and_Voucher_Code__Voucher_will_be_password": "El cliente solo debe iniciar sesi\u00f3n con su n\u00famero de tel\u00e9fono y el c\u00f3digo del cup\u00f3n. El cup\u00f3n ser\u00e1 su contrase\u00f1a.",
"Redirect_URL_after_Activation": "Redireccionar URL despu\u00e9s de la activaci\u00f3n",
"After_Customer_activate_voucher_or_login__customer_will_be_redirected_to_this_url": "Despu\u00e9s de que el Cliente active el cup\u00f3n o inicie sesi\u00f3n, ser\u00e1 redirigido a esta URL",
"Enable_Radius": "Habilitar radio",
"Radius_Instructions": "Instrucciones de radio",
"Extend_Postpaid_Expiration": "Extender vencimiento de pospago",
"Allow_Extend": "Permitir extender",
"Customer_can_request_to_extend_expirations": "El cliente puede solicitar la extensi\u00f3n de los vencimientos.",
"Extend_Days": "Extender d\u00edas",
"Confirmation_Message": "Mensaje de confirmaci\u00f3n",
"i_agree_to_extends_and_will_paid_full_after_this": "Acepto extender y pagar\u00e9 el total despu\u00e9s de esto.",
"Customer_Balance_System": "Sistema de saldo de clientes",
"Telegram_Bot_Token": "Token de bot de Telegram",
"Telegram_User_Channel_Group_ID": "ID de usuario\/canal\/grupo de Telegram",
"You_will_get_Payment_and_Error_notification": "Recibir\u00e1 una notificaci\u00f3n de pago y error.",
"Test_SMS": "Prueba de SMS",
"SMS_Server_URL": "URL del servidor SMS",
"Must_include": "Debe incluir",
"it_will_be_replaced_": "Ser\u00e1 reemplazado.",
"Or_use_Mikrotik_SMS": "O utilice Mikrotik SMS",
"Select_Router": "Seleccionar enrutador",
"You_can_use": "Puedes utilizar",
"in_here_too_": "Aqu\u00ed tambi\u00e9n.",
"Free_Server": "Servidor gratuito",
"WhatsApp_Server_URL": "URL del servidor de WhatsApp",
"Email_Notification": "Notificaci\u00f3n por correo electr\u00f3nico",
"Empty_this_to_use_internal_mail___PHP": "Vac\u00ede esto para usar el correo interno PHP",
"SMTP_Username": "Nombre de usuario SMTP",
"SMTP_Password": "Contrase\u00f1a SMTP",
"SMTP_Security": "Seguridad SMTP",
"Mail_Reply_To": "Responder a correo",
"Customer_will_reply_email_to_this_address__empty_if_you_want_to_use_From_Address": "El cliente responder\u00e1 el correo electr\u00f3nico a esta direcci\u00f3n; deje el campo vac\u00edo si desea utilizar la direcci\u00f3n de remitente.",
"None": "Ninguno",
"By_WhatsApp": "Por WhatsApp",
"By_SMS": "Por SMS",
"By_Email": "Por correo electr\u00f3nico",
"From_Direct_Chat_Link_": "Desde el enlace de chat directo.",
"Access_Token": "Token de acceso",
"Empty_this_to_randomly_created_API_key": "Vac\u00ede esto para crear una clave API aleatoria",
"This_Token_will_act_as_SuperAdmin_Admin": "Este token actuar\u00e1 como SuperAdmin\/Admin",
"Miscellaneous": "Miscel\u00e1neas",
"Enable_Session_Timeout": "Habilitar tiempo de espera de sesi\u00f3n",
"Logout_Admin_if_not_Available_Online_a_period_of_time": "Cerrar sesi\u00f3n como administrador si no est\u00e1 disponible o en l\u00ednea durante un per\u00edodo de tiempo",
"Timeout_Duration": "Duraci\u00f3n del tiempo de espera",
"Enter_the_session_timeout_duration__minutes_": "Introduzca la duraci\u00f3n del tiempo de espera de la sesi\u00f3n (minutos)",
"Idle_Timeout__Logout_Admin_if_Idle_for_xx_minutes": "Tiempo de espera inactivo, cerrar sesi\u00f3n como administrador si est\u00e1 inactivo durante xx minutos",
"New_Version_Notification": "Notificaci\u00f3n de nueva versi\u00f3n",
"Enabled": "Activado",
"This_is_to_notify_you_when_new_updates_is_available": "Esto es para notificarle cuando haya nuevas actualizaciones disponibles.",
"Router_Check": "Comprobaci\u00f3n del enrutador",
"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": "Si est\u00e1 habilitado, el sistema notificar\u00e1 al administrador cuando el enrutador se desconecte. Si el administrador tiene 10 o m\u00e1s enrutadores y muchos clientes, se superpondr\u00e1; puede deshabilitarlo.",
"Phone_OTP_Required": "Se requiere OTP en el tel\u00e9fono",
"OTP_is_required_when_user_want_to_change_phone_number_and_registration": "Se requiere OTP cuando el usuario desea cambiar el n\u00famero de tel\u00e9fono y el registro",
"OTP_Method": "M\u00e9todo OTP",
"by_WhatsApp": "por WhatsApp",
"By_WhatsApp_and_SMS": "Por WhatsApp y SMS",
"The_method_which_OTP_will_be_sent_to_user": "El m\u00e9todo mediante el cual se enviar\u00e1 la OTP al usuario",
"Email_OTP_Required": "Se requiere OTP en el correo electr\u00f3nico",
"OTP_is_required_when_user_want_to_change_Email_Address": "Se requiere OTP cuando el usuario desea cambiar la direcci\u00f3n de correo electr\u00f3nico",
"Extend_Package_Expiry": "Extender la caducidad del paquete",
"If_user_buy_same_internet_plan__expiry_date_will_extend": "Si el usuario compra el mismo plan de Internet, la fecha de vencimiento se extender\u00e1",
"Show_Bandwidth_Plan": "Mostrar plan de ancho de banda",
"_for_Customer": "Para el cliente",
"Hotspot_Auth_Method": "M\u00e9todo de autenticaci\u00f3n de punto de acceso",
"Api": "API",
"Http_Chap": "Http-Chap",
"Hotspot_Authentication_Method__Make_sure_you_have_changed_your_hotspot_login_page_": "M\u00e9todo de autenticaci\u00f3n del punto de acceso. Aseg\u00farate de haber cambiado la p\u00e1gina de inicio de sesi\u00f3n del punto de acceso.",
"Tax_System": "Sistema tributario",
"Enable_Tax_System": "Habilitar el sistema tributario",
"Tax_will_be_calculated_in_Internet_Plan_Price": "El impuesto se calcular\u00e1 en el precio del plan de Internet.",
"Tax_Rate": "Tasa de impuesto",
"Custome": "Personalizado",
"Tax_Rates_in_percentage": "Tasas de impuestos en porcentaje",
"Custome_Tax_Rate": "Tasa de impuesto al cliente",
"Enter_Custome_Tax_Rate": "Introduzca la tasa de impuesto personalizada",
"Enter_the_custom_tax_rate__e_g___3_75_for_3_75__": "Introduzca la tasa de impuesto personalizada (por ejemplo, 3,75 para 3,75 %)",
"Authentication": "Autenticaci\u00f3n",
"Github_Username": "Nombre de usuario de Github",
"Github_Token": "Token de Github",
"Create_GitHub_personal_access_token": "Crear un token de acceso personal de GitHub",
"only_need_repo_scope": "Solo se necesita el \u00e1mbito del repositorio",
"This_will_allow_you_to_download_plugin_from_private_paid_repository": "Esto le permitir\u00e1 descargar el complemento desde un repositorio privado\/pago.",
"Expired_Cronjob_Every_5_Minutes": "Cronjob vencido cada 5 minutos",
"Expired_Cronjob_Every_1_Hour": "Cronjob vencido cada 1 hora",
"Reminder_Cronjob_Every_7_AM": "Recordatorio de Cronjob cada 7 a. m.",
"Email_not_sent__Mailer_Error__": "Correo electr\u00f3nico no enviado, error de Mailer:",
"Language_Editor": "Editor de idioma",
"Radius_Package": "Paquete Radius",
"Change_title_in_user_Plan_order": "Cambiar t\u00edtulo en el pedido del plan de usuario",
"Hotspot_Package": "Paquete de punto de acceso",
"PPPOE_Package": "Paquete PPPoE",
"VPN_Package": "Paquete VPN",
"Translation": "Traducci\u00f3n",
"Agent": "Agente",
"Session_has_expired__Please_log_in_again_": "La sesi\u00f3n ha expirado. Por favor, inicie sesi\u00f3n nuevamente.",
"danger": "Peligro",
"City": "Pais",
"District": "Distrito",
"State": "Estado",
"Zip": "C\u00f3digo Postal",
"Personal": "Personal",
"Bandwidth": "Ancho de banda",
"Translation_saved_Successfully": "La traducci\u00f3n se guard\u00f3 correctamente",
"Filter": "Filtrar",
"Show_chart": "Mostrar gr\u00e1fico",
"Start_Date": "Fecha de inicio",
"Start_time": "Hora de inicio",
"End_Date": "Fecha de finalizaci\u00f3n",
"End_Time": "Fin del tiempo",
"Internet_Plans": "Planes de Internet",
"Methods": "M\u00e9todos",
"AREA1": "\u00c1REA 1",
"AREA2": "\u00c1REA 2",
"AREA4": "\u00c1REA 4",
"AREA3": "\u00c1REA 3",
"MK_ADMIN": "Administrador de MK",
"AREA6": "\u00c1REA 6",
"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",
"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,159 +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`;"
]
}
"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);"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

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.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -176,4 +176,6 @@ return array(
'Smarty_Variable' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_variable.php',
'TPC_yyStackEntry' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_internal_configfileparser.php',
'TP_yyStackEntry' => $vendorDir . '/smarty/smarty/libs/sysplugins/smarty_internal_templateparser.php',
'svay\\Exception\\NoFaceException' => $vendorDir . '/yosiazwan/php-facedetection/Exception/NoFaceException.php',
'svay\\FaceDetector' => $vendorDir . '/yosiazwan/php-facedetection/FaceDetector.php',
);

View File

@ -22,8 +22,6 @@ class ComposerAutoloaderInit405fa5c7a0972c286ef93b1161b83367
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit405fa5c7a0972c286ef93b1161b83367', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit405fa5c7a0972c286ef93b1161b83367', 'loadClassLoader'));

View File

@ -229,6 +229,8 @@ class ComposerStaticInit405fa5c7a0972c286ef93b1161b83367
'Smarty_Variable' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_variable.php',
'TPC_yyStackEntry' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_internal_configfileparser.php',
'TP_yyStackEntry' => __DIR__ . '/..' . '/smarty/smarty/libs/sysplugins/smarty_internal_templateparser.php',
'svay\\Exception\\NoFaceException' => __DIR__ . '/..' . '/yosiazwan/php-facedetection/Exception/NoFaceException.php',
'svay\\FaceDetector' => __DIR__ . '/..' . '/yosiazwan/php-facedetection/FaceDetector.php',
);
public static function getInitializer(ClassLoader $loader)

View File

@ -494,6 +494,51 @@
"source": "https://github.com/smarty-php/smarty/tree/v4.5.3"
},
"install-path": "../smarty/smarty"
},
{
"name": "yosiazwan/php-facedetection",
"version": "0.1.0",
"version_normalized": "0.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/yosiazwan/php-facedetection.git",
"reference": "b016273ceceacd85562bbc50384fbabc947fe525"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yosiazwan/php-facedetection/zipball/b016273ceceacd85562bbc50384fbabc947fe525",
"reference": "b016273ceceacd85562bbc50384fbabc947fe525",
"shasum": ""
},
"require": {
"ext-gd": "*",
"php": ">=5.2.0"
},
"time": "2016-01-26T22:10:00+00:00",
"type": "library",
"installation-source": "source",
"autoload": {
"classmap": [
"FaceDetector.php",
"Exception/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0"
],
"authors": [
{
"name": "Maurice Svay",
"homepage": "https://github.com/mauricesvay/php-facedetection/graphs/contributors"
}
],
"description": "PHP class to detect one face in images. A pure PHP port of an existing JS code from Karthik Tharavad.",
"homepage": "https://github.com/mauricesvay/php-facedetection",
"support": {
"source": "https://github.com/yosiazwan/php-facedetection/tree/0.1.0"
},
"install-path": "../yosiazwan/php-facedetection"
}
],
"dev": true,

View File

@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'a9c0e955937e3ccb2ff050c71b77353b298a982b',
'reference' => '925c24cbd822f776eb913df987a063f95c6d9cc0',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'a9c0e955937e3ccb2ff050c71b77353b298a982b',
'reference' => '925c24cbd822f776eb913df987a063f95c6d9cc0',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -91,5 +91,14 @@
'aliases' => array(),
'dev_requirement' => false,
),
'yosiazwan/php-facedetection' => array(
'pretty_version' => '0.1.0',
'version' => '0.1.0.0',
'reference' => 'b016273ceceacd85562bbc50384fbabc947fe525',
'type' => 'library',
'install_path' => __DIR__ . '/../yosiazwan/php-facedetection',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@ -1,26 +0,0 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

Binary file not shown.

View File

@ -131,13 +131,7 @@ abstract class Smarty_Internal_TemplateBase extends Smarty_Internal_Data
public function display($template = null, $cache_id = null, $compile_id = null, $parent = null)
{
// display template
if(strpos($template, "user-ui") === false){
if(strpos($template, "section/user-") === false){
$template = str_replace("user-",'', $template);
}else{
$template = str_replace("section/user-",'user-ui/', $template);
}
}
$template = str_replace("section/user-",'customer/', $template);
$this->_execute($template, $cache_id, $compile_id, $parent, 1);
}

View File

@ -0,0 +1,13 @@
<?php
/**
* Throws exception if face was not detected in `faceDetect` call.
*/
namespace svay\Exception;
use Exception;
class NoFaceException extends Exception {
}

View File

@ -0,0 +1,396 @@
<?php
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// @Author Karthik Tharavaad
// karthik_tharavaad@yahoo.com
// @Contributor Maurice Svay
// maurice@svay.Com
namespace svay;
use Exception;
use svay\Exception\NoFaceException;
class FaceDetector
{
protected $detection_data;
protected $canvas;
protected $face;
private $reduced_canvas;
protected $timeout = null;
protected $time_start;
/**
* Creates a face-detector with the given configuration
*
* Configuration can be either passed as an array or as
* a filepath to a serialized array file-dump
*
* @param string|array $detection_data
*
* @throws Exception
*/
public function __construct($detection_data = 'detection.json')
{
if (is_array($detection_data)) {
$this->detection_data = $detection_data;
return;
}
if (!is_file($detection_data)) {
// fallback to same file in this class's directory
$detection_data = dirname(__FILE__) . DIRECTORY_SEPARATOR . $detection_data;
if (!is_file($detection_data)) {
throw new \Exception("Couldn't load detection data");
}
}
$this->detection_data = json_decode(file_get_contents($detection_data));
}
public function setTimeout($micro_seconds)
{
$this->timeout = $micro_seconds;
}
public function faceDetect($file)
{
$this->time_start = microtime();
if (is_resource($file)) {
$this->canvas = $file;
} elseif (is_file($file)) {
$this->canvas = imagecreatefromjpeg($file);
} elseif (is_string($file)) {
$this->canvas = imagecreatefromstring($file);
} else {
throw new Exception("Can not load $file");
}
$sharpen = array(
array(0.0, -1.0, 0.0),
array(-1.0, 5.0, -1.0),
array(0.0, -1.0, 0.0)
);
$divisor = array_sum(array_map('array_sum', $sharpen));
imageconvolution($this->canvas, $sharpen, $divisor, 0);
$im_width = imagesx($this->canvas);
$im_height = imagesy($this->canvas);
//Resample before detection?
$diff_width = 320 - $im_width;
$diff_height = 240 - $im_height;
if ($diff_width > $diff_height) {
$ratio = $im_width / 320;
} else {
$ratio = $im_height / 240;
}
if ($ratio != 0) {
$this->reduced_canvas = imagecreatetruecolor($im_width / $ratio, $im_height / $ratio);
imagecopyresampled(
$this->reduced_canvas,
$this->canvas,
0,
0,
0,
0,
$im_width / $ratio,
$im_height / $ratio,
$im_width,
$im_height
);
$stats = $this->getImgStats($this->reduced_canvas);
$this->face = $this->doDetectGreedyBigToSmall(
$stats['ii'],
$stats['ii2'],
$stats['width'],
$stats['height']
);
if ($this->face['w'] > 0) {
$this->face['x'] *= $ratio;
$this->face['y'] *= $ratio;
$this->face['w'] *= $ratio;
}
} else {
$stats = $this->getImgStats($this->canvas);
$this->face = $this->doDetectGreedyBigToSmall(
$stats['ii'],
$stats['ii2'],
$stats['width'],
$stats['height']
);
}
return ($this->face['w'] > 0);
}
public function toJpeg()
{
$color = imagecolorallocate($this->canvas, 255, 0, 0); //red
imagerectangle(
$this->canvas,
$this->face['x'],
$this->face['y'],
$this->face['x'] + $this->face['w'],
$this->face['y'] + $this->face['w'],
$color
);
header('Content-type: image/jpeg');
imagejpeg($this->canvas);
}
/**
* Crops the face from the photo.
* Should be called after `faceDetect` function call
* If file is provided, the face will be stored in file, other way it will be output to standard output.
*
* @param string|null $outFileName file name to store. If null, will be printed to output
* @param boolean|false $resize resize crop image.
* @param int $width widht of new crop image. $resize value must 'true'. default to 200
* @param int $height height of new crop image. $resize value must 'true'. default to 200
*
* @throws NoFaceException
*/
public function cropFaceToJpeg($outFileName = null, $width = 200)
{
if (empty($this->face)) {
throw new NoFaceException('No face detected');
}
// if (!$resize) {
$x = ($a = $this->face['x'] - $this->face['w'] / 2) > 0 ? $a : 0;
$y = ($b = $this->face['y'] - $this->face['w'] / 2) > 0 ? $b : 0;
$im_width = imagesx($this->canvas);
$im_height = imagesy($this->canvas);
$w = ($w = $this->face['w'] * 2) > $im_width ? $im_width : $w;
$h = ($h = $w) > $im_height ? $im_height : $h;
$canvas = imagecreatetruecolor($width, $width);
imagecopy($canvas, $this->canvas, 0, 0, $x, $y, $w, $h);
// $canvas = imagecreatetruecolor($this->face['w'], $this->face['w']);
// imagecopy($canvas, $this->canvas, 0, 0, $this->face['x'], $this->face['y'], $this->face['w'], $this->face['w']);
// } else {
// $x = ($a = $this->face['x'] - $width / 2) > 0 ? $a : 0;
// $y = ($b = $this->face['y'] - $width / 2) > 0 ? $b : 0;
// $im_width = imagesx($this->canvas);
// $im_height = imagesy($this->canvas);
// $w = ($w = $width * 2) > $im_width ? $im_width : $w;
// $h = ($h = $w) > $im_height ? $im_height : $h;
// $canvas = imagecreatetruecolor($w, $h);
// imagecopy($canvas, $this->canvas, 0, 0, $width, $width, $w, $h);
// // $canvas = imagecreatetruecolor($width, $width);
// // imagecopyresized($canvas, $this->canvas, 0, 0, $this->face['x'], $this->face['y'], $width, $width, $this->face['w'], $this->face['w']);
// }
if ($outFileName === null) {
header('Content-type: image/jpeg');
}
imagejpeg($canvas, $outFileName);
}
public function toJson()
{
return json_encode($this->face);
}
public function getFace()
{
return $this->face;
}
protected function getImgStats($canvas)
{
$image_width = imagesx($canvas);
$image_height = imagesy($canvas);
$iis = $this->computeII($canvas, $image_width, $image_height);
return array(
'width' => $image_width,
'height' => $image_height,
'ii' => $iis['ii'],
'ii2' => $iis['ii2']
);
}
protected function computeII($canvas, $image_width, $image_height)
{
$ii_w = $image_width + 1;
$ii_h = $image_height + 1;
$ii = array();
$ii2 = array();
for ($i = 0; $i < $ii_w; $i++) {
$ii[$i] = 0;
$ii2[$i] = 0;
}
for ($i = 1; $i < $ii_h - 1; $i++) {
$ii[$i * $ii_w] = 0;
$ii2[$i * $ii_w] = 0;
$rowsum = 0;
$rowsum2 = 0;
for ($j = 1; $j < $ii_w - 1; $j++) {
$rgb = ImageColorAt($canvas, $j, $i);
$red = ($rgb >> 16) & 0xFF;
$green = ($rgb >> 8) & 0xFF;
$blue = $rgb & 0xFF;
$grey = (0.2989 * $red + 0.587 * $green + 0.114 * $blue) >> 0; // this is what matlab uses
$rowsum += $grey;
$rowsum2 += $grey * $grey;
$ii_above = ($i - 1) * $ii_w + $j;
$ii_this = $i * $ii_w + $j;
$ii[$ii_this] = $ii[$ii_above] + $rowsum;
$ii2[$ii_this] = $ii2[$ii_above] + $rowsum2;
}
}
return array('ii' => $ii, 'ii2' => $ii2);
}
protected function doDetectGreedyBigToSmall($ii, $ii2, $width, $height)
{
$s_w = $width / 20.0;
$s_h = $height / 20.0;
$start_scale = $s_h < $s_w ? $s_h : $s_w;
$scale_update = 1 / 1.2;
for ($scale = $start_scale; $scale > 1; $scale *= $scale_update) {
if ($this->timeout && microtime() - $this->time_start > $this->timeout) {
throw new Exception("Face dectection has timed out");
}
$w = (20 * $scale) >> 0;
$endx = $width - $w - 1;
$endy = $height - $w - 1;
$step = max($scale, 2) >> 0;
$inv_area = 1 / ($w * $w);
for ($y = 0; $y < $endy; $y += $step) {
for ($x = 0; $x < $endx; $x += $step) {
$passed = $this->detectOnSubImage($x, $y, $scale, $ii, $ii2, $w, $width + 1, $inv_area);
if ($passed) {
return array('x' => $x, 'y' => $y, 'w' => $w);
}
} // end x
} // end y
} // end scale
return null;
}
protected function detectOnSubImage($x, $y, $scale, $ii, $ii2, $w, $iiw, $inv_area)
{
$mean = ($ii[($y + $w) * $iiw + $x + $w] + $ii[$y * $iiw + $x] - $ii[($y + $w) * $iiw + $x] - $ii[$y * $iiw + $x + $w]) * $inv_area;
$vnorm = ($ii2[($y + $w) * $iiw + $x + $w]
+ $ii2[$y * $iiw + $x]
- $ii2[($y + $w) * $iiw + $x]
- $ii2[$y * $iiw + $x + $w]) * $inv_area - ($mean * $mean);
$vnorm = $vnorm > 1 ? sqrt($vnorm) : 1;
$count_data = count($this->detection_data);
for ($i_stage = 0; $i_stage < $count_data; $i_stage++) {
$stage = $this->detection_data[$i_stage];
$trees = $stage[0];
$stage_thresh = $stage[1];
$stage_sum = 0;
$count_trees = count($trees);
for ($i_tree = 0; $i_tree < $count_trees; $i_tree++) {
$tree = $trees[$i_tree];
$current_node = $tree[0];
$tree_sum = 0;
while ($current_node != null) {
$vals = $current_node[0];
$node_thresh = $vals[0];
$leftval = $vals[1];
$rightval = $vals[2];
$leftidx = $vals[3];
$rightidx = $vals[4];
$rects = $current_node[1];
$rect_sum = 0;
$count_rects = count($rects);
for ($i_rect = 0; $i_rect < $count_rects; $i_rect++) {
$s = $scale;
$rect = $rects[$i_rect];
$rx = ($rect[0] * $s + $x) >> 0;
$ry = ($rect[1] * $s + $y) >> 0;
$rw = ($rect[2] * $s) >> 0;
$rh = ($rect[3] * $s) >> 0;
$wt = $rect[4];
$r_sum = ($ii[($ry + $rh) * $iiw + $rx + $rw]
+ $ii[$ry * $iiw + $rx]
- $ii[($ry + $rh) * $iiw + $rx]
- $ii[$ry * $iiw + $rx + $rw]) * $wt;
$rect_sum += $r_sum;
}
$rect_sum *= $inv_area;
$current_node = null;
if ($rect_sum >= $node_thresh * $vnorm) {
if ($rightidx == -1) {
$tree_sum = $rightval;
} else {
$current_node = $tree[$rightidx];
}
} else {
if ($leftidx == -1) {
$tree_sum = $leftval;
} else {
$current_node = $tree[$leftidx];
}
}
}
$stage_sum += $tree_sum;
}
if ($stage_sum < $stage_thresh) {
return false;
}
}
return true;
}
}

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