Compare commits

...

471 Commits

Author SHA1 Message Date
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 • 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 • 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
194 changed files with 15409 additions and 5134 deletions

1
.gitignore vendored
View File

@ -46,6 +46,7 @@ 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

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

@ -98,6 +98,7 @@ a.n Ibnu Maksum
## SPONSORS
- [mixradius.com](https://mixradius.com/) Paid Services Billing Radius
- [mlink.id](https://mlink.id)
- [https://github.com/sonyinside](https://github.com/sonyinside)

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

View File

@ -47,7 +47,8 @@ if (!file_exists($root_path . 'config.php')) {
}
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');
@ -59,7 +60,8 @@ $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 +69,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 +87,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
@ -119,7 +121,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 +136,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)
{
@ -238,8 +233,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();
}
@ -356,7 +355,7 @@ function displayMaintenanceMessage(): void
{
global $config, $ui;
$date = $config['maintenance_date'];
if ($date){
if ($date) {
$ui->assign('date', $date);
}
http_response_code(503);
@ -374,4 +373,3 @@ function isTableExist($table)
return false;
}
}

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;
@ -243,6 +250,31 @@ CREATE TABLE `tbl_customers_inbox` (
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;
ALTER TABLE `rad_acct`
ADD PRIMARY KEY (`id`),

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,52 +274,19 @@ 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;
@ -319,21 +296,27 @@ try {
$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){
$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 +347,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 +396,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) {
@ -459,4 +452,4 @@ function show_radius_result($array, $code = 200)
die();
}
die(json_encode($array));
}
}

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;
if($config['csrf_enabled'] == 'yes') {
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
@ -93,6 +94,48 @@ class File
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

@ -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,15 @@ require $root_path . 'system/autoload/mail/SMTP.php';
class Message
{
public static function sendTelegram($txt)
public static function sendTelegram($txt, $chat_id = null)
{
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 = null]); #HOOK
if (!empty($config['telegram_bot'])) {
if (empty($chat_id)) {
$chat_id = $config['telegram_target_id'];
}
return Http::getData('https://api.telegram.org/bot' . $config['telegram_bot'] . '/sendMessage?chat_id=' . $chat_id . '&text=' . urlencode($txt));
}
}
@ -48,7 +51,7 @@ class Message
}
} else {
try {
self::sendSMS($config['sms_url'], $phone, $txt);
self::MikrotikSendSMS($config['sms_url'], $phone, $txt);
} catch (Exception $e) {
// ignore, add to logs
_log("Failed to send SMS using Mikrotik.\n" . $e->getMessage(), 'SMS', 0);
@ -64,7 +67,7 @@ class Message
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 +76,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);
@ -153,7 +159,9 @@ class Message
$mail->isHTML(false);
$mail->Body = $body;
}
$mail->send();
if (!$mail->send()) {
_log(Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo);
}
//<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">
}
@ -170,41 +178,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,11 +267,12 @@ 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";
}
@ -232,6 +286,7 @@ class Message
$textInvoice = str_replace('[[phone]]', $config['phone'], $textInvoice);
$textInvoice = str_replace('[[invoice]]', $trx['invoice'], $textInvoice);
$textInvoice = str_replace('[[date]]', Lang::dateAndTimeFormat($trx['recharged_on'], $trx['recharged_time']), $textInvoice);
$textInvoice = str_replace('[[trx_date]]', Lang::dateAndTimeFormat($trx['recharged_on'], $trx['recharged_time']), $textInvoice);
if (!empty($trx['note'])) {
$textInvoice = str_replace('[[note]]', $trx['note'], $textInvoice);
}
@ -258,4 +313,16 @@ class Message
Message::sendWhatsapp($cust['phonenumber'], $textInvoice);
}
}
public static function addToInbox($to_customer_id, $subject, $body, $from = 'System')
{
$v = ORM::for_table('tbl_customers_inbox')->create();
$v->from = $from;
$v->customer_id = $to_customer_id;
$v->subject = $subject;
$v->date_created = date('Y-m-d H:i:s');
$v->body = nl2br($body);
$v->save();
}
}

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

@ -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";
}
@ -78,7 +77,7 @@ class Package
r2(U . '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(U . '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

@ -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,12 +166,31 @@ 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;
}
}
@ -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(U . '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,70 @@ 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;
}
}

View File

@ -79,8 +79,12 @@ $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();
@ -128,12 +132,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('error.tpl');
die();
} catch (Exception $e) {
Message::sendTelegram(
@ -141,11 +145,11 @@ 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('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(U . 'accounts/change-password', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
run_hook('customer_change_password'); #HOOK
if ($password != '') {
$d_pass = $user['password'];
@ -58,7 +64,6 @@ 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'));
}
} else {
@ -68,9 +73,16 @@ switch ($action) {
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(U . 'accounts/profile', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$fullname = _post('fullname');
$address = _post('address');
$email = _post('email');
@ -84,28 +96,89 @@ 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(U . '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(U . 'accounts/profile', 's', Lang::T('User Updated Successfully'));
}else{
r2(U . '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(U . 'accounts/phone-update', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$phone = Lang::phoneFormat(_post('phone'));
$username = $user['username'];
$otpPath = $CACHE_PATH . '/sms/';
@ -139,12 +212,12 @@ switch ($action) {
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'));
@ -153,6 +226,10 @@ switch ($action) {
break;
case 'phone-update-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . 'accounts/phone-update', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$phone = Lang::phoneFormat(_post('phone'));
$otp_code = _post('otp');
$username = $user['username'];
@ -211,10 +288,16 @@ switch ($action) {
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(U . 'accounts/email-update', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$email = trim(_post('email'));
$username = $user['username'];
$otpPath = $CACHE_PATH . '/email/';
@ -256,6 +339,10 @@ switch ($action) {
break;
case 'email-update-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . 'accounts/email-update', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$email = trim(_post('email'));
$otp_code = _post('otp');
$username = $user['username'];
@ -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);

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(U . '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
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin-login.tpl');
break;
}

View File

@ -28,7 +28,18 @@ switch ($action) {
$ui->assign('d', $d);
$ui->display('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);
@ -65,16 +76,26 @@ 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);
@ -82,15 +103,64 @@ switch ($action) {
$ui->display('autoload.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':

View File

@ -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) {

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('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(U . '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(U . '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(U . 'coupons', 's', Lang::T('Coupon has been added successfully'));
} catch (Exception $e) {
_log(Lang::T('Error adding coupon: ' . $e->getMessage()));
r2(U . '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(U . 'coupons', 'e', Lang::T('Invalid Coupon ID'));
exit;
}
$coupon = ORM::for_table('tbl_coupons')->find_one($coupon_id);
if (!$coupon) {
r2(U . '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('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(U . '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(U . 'coupons', 's', Lang::T('Coupon has been updated successfully'));
} catch (Exception $e) {
_log(Lang::T('Error updating coupon: ') . $e->getMessage());
r2(U . '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('coupons.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(U . 'customers', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$cs = ORM::for_table('tbl_customers')
->select('tbl_customers.id', 'id')
@ -153,6 +157,7 @@ switch ($action) {
}
$ui->assign('xheader', $leafletpickerHeader);
run_hook('view_add_customer'); #HOOK
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers-add.tpl');
break;
case 'recharge':
@ -161,12 +166,29 @@ switch ($action) {
}
$id_customer = $routes['2'];
$plan_id = $routes['3'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(U . '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']);
$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) {
@ -175,7 +197,7 @@ switch ($action) {
if (!$plan) {
r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
}
if ($cust['balance'] < ($plan['price'] + $add_cost)) {
if ($cust['balance'] < ($plan['price'] + $add_cost + $tax)) {
r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
}
$gateway = 'Recharge Balance';
@ -189,7 +211,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,6 +224,7 @@ switch ($action) {
$ui->assign('channel', $channel);
$ui->assign('server', $b['routers']);
$ui->assign('plan', $plan);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('recharge-confirm.tpl');
} else {
r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
@ -208,6 +236,10 @@ switch ($action) {
}
$id_customer = $routes['2'];
$plan_id = $routes['3'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(U . '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();
@ -236,6 +268,10 @@ switch ($action) {
break;
case 'sync':
$id_customer = $routes['2'];
$csrf_token = _req('token');
if (!Csrf::check($csrf_token)) {
r2(U . '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,7 +284,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"));
}
@ -259,6 +299,23 @@ switch ($action) {
}
r2(U . '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(U . '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();
case 'view':
@ -268,8 +325,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 +333,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->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers-view.tpl');
} else {
r2(U . '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,10 +381,28 @@ 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->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers-edit.tpl');
} else {
r2(U . 'customers/list', 'e', Lang::T('Account Not Found'));
@ -325,6 +414,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(U . '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) {
@ -360,6 +453,11 @@ switch ($action) {
break;
case 'add-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . 'customers/add', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$username = alphanumeric(_post('username'), ":+_.@-");
$fullname = _post('fullname');
$password = trim(_post('password'));
@ -444,7 +542,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 +553,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']]
]
@ -484,6 +582,11 @@ switch ($action) {
break;
case 'edit-post':
$id = _post('id');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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 +614,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 +638,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 +663,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(U . 'settings/app', 'e', 'PHP GD is not installed');
}
}
if ($userDiff) {
$c->username = $username;
}
@ -644,13 +794,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);
}
@ -673,10 +823,10 @@ switch ($action) {
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 +844,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(U . 'customers', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$d = $query->findMany();
$h = false;
set_time_limit(-1);
@ -749,6 +907,7 @@ switch ($action) {
$ui->assign('order', $order);
$ui->assign('order_pos', $order_pos[$order]);
$ui->assign('orderby', $orderby);
$ui->assign('csrf_token', Csrf::generateAndStoreToken());
$ui->display('customers.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(U . 'customfield', 's', 'Successfully saved custom fields!');
}else{
r2(U . '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('customfield.tpl');
break;
}

View File

@ -210,11 +210,16 @@ if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) {
}
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);
$routeroffs = ORM::for_table('tbl_routers')->selects(['id', 'name', 'last_seen'])->where('status', 'Offline')->where('enabled', '1')->order_by_desc('name')->find_array();
$ui->assign('routeroffs', $routeroffs);
}
$timestampFile = "$UPLOAD_PATH/cron_last_run.txt";
if (file_exists($timestampFile)) {
$lastRunTime = file_get_contents($timestampFile);
$ui->assign('run_date', date('Y-m-d h:i:s A', $lastRunTime));
}
// Assign the monthly sales data to Smarty
$ui->assign('start_date', $start_date);
$ui->assign('current_date', $current_date);

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(U . 'forgot&step=1', 'e', Lang::T('Invalid Username or Verification Code'));
}
} else {
if (file_exists($otpPath)) {
unlink($otpPath);
}
r2(U . 'forgot&step=1', 'e', Lang::T('Invalid Username or Verification Code'));
}
} else {
r2(U . '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

@ -71,8 +71,9 @@ 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'));
}
@ -100,7 +101,7 @@ $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 +110,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"));
}
@ -142,19 +147,7 @@ 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(U. "order/gateway/$router/$bill[plan_id]");
}
} else if (!empty(_get('extend'))) {
if ($user['status'] != 'Active') {
@ -188,6 +181,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"));
@ -262,7 +257,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 +268,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(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 (!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 +295,61 @@ 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(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);
}
}
}
$ui->assign('unpaid', ORM::for_table('tbl_payment_gateway')
$tcf = ORM::for_table('tbl_customers_fields')
->where('customer_id', $user['id'])
->find_many();
$vpn = ORM::for_table('tbl_port_pool')
->find_one();
$ui->assign('cf', $tcf);
$ui->assign('vpn', $vpn);
$unpaid = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one());
->find_one();
// check expired payments
if ($unpaid) {
try {
if (strtotime($unpaid['expired_date']) < time()) {
$unpaid->status = 4;
$unpaid->save();
$unpaid = [];
}
} catch (Throwable $e) {
} catch (Exception $e) {
}
try {
if (strtotime($unpaid['created_date'], "+24 HOUR") < time()) {
$unpaid->status = 4;
$unpaid->save();
$unpaid = [];
}
} catch (Throwable $e) {
} catch (Exception $e) {
}
}
$ui->assign('unpaid', $unpaid);
$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

@ -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(U . 'login');
}
run_hook('customer_login'); #HOOK
if ($username != '' and $password != '') {
$d = ORM::for_table('tbl_customers')->where('username', $username)->find_one();
@ -34,10 +39,17 @@ 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'));
@ -57,6 +69,11 @@ switch ($do) {
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(U . 'login');
}
$voucher = Text::alphanumeric(_post('voucher_only'), "-_.,");
$tur = ORM::for_table('tbl_user_recharges')
->where('username', $voucher)
@ -101,7 +118,7 @@ switch ($do) {
_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");
}
@ -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();
@ -289,11 +306,52 @@ switch ($do) {
}
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

@ -29,7 +29,7 @@ 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']){
@ -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

@ -30,9 +30,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 './?_route=autoload/customer_select2&s='+params.term;
}else{
return './index.php?_route=autoload/customer_select2';
return './?_route=autoload/customer_select2';
}
}
}

View File

@ -15,16 +15,22 @@ 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) {
@ -34,7 +40,7 @@ switch ($action) {
$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) {
@ -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')
@ -185,7 +198,7 @@ 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') {
@ -198,15 +211,13 @@ switch ($action) {
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)) {
$plan = ORM::for_table('tbl_plans')->find_one($routes[3]);
if (!$plan) {
r2(U . "order/package", 'e', Lang::T("Plan Not found"));
}
if (!$plan['enabled']) {
r2(U . "home", 'e', 'Plan is not exists');
}
if ($plan['is_radius'] == '1') {
$router_name = 'radius';
$router = 'radius';
} else {
$router_name = $plan['routers'];
}
@ -230,21 +241,21 @@ 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"));
} else {
r2(U . "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(U . "order/gateway/$routes[2]/$routes[3]", 'e', Lang::T("Insufficient balance"));
}
break;
@ -264,7 +275,8 @@ switch ($action) {
if (!$plan['enabled']) {
r2(U . "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'];
@ -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'];
@ -375,7 +389,7 @@ 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'));
@ -396,6 +410,94 @@ switch ($action) {
if ($router['name'] != 'balance') {
list($bills, $add_cost) = User::getBills($id_customer);
}
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) {
@ -408,12 +510,22 @@ switch ($action) {
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(U . "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");
@ -422,6 +534,11 @@ switch ($action) {
}
case 'buy':
$gateway = _post('gateway');
$discount = _post('discount') ?: 0;
if ($gateway == 'balance') {
unset($_SESSION['gateway']);
r2(U . 'order/pay/' . $routes[2] . '/' . $routes[3]);
}
if (empty($gateway) && !empty($_SESSION['gateway'])) {
$gateway = $_SESSION['gateway'];
} else if (!empty($gateway)) {
@ -436,99 +553,144 @@ switch ($action) {
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(U . "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(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'];
} 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(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'];
} 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.."));

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

@ -20,9 +20,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 './?_route=autoload/customer_select2&s='+params.term;
}else{
return './index.php?_route=autoload/customer_select2';
return './?_route=autoload/customer_select2';
}
}
}
@ -49,7 +49,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"));
}
@ -101,6 +105,26 @@ switch ($action) {
$cust = User::_info($id_customer);
$plan = ORM::for_table('tbl_plans')->find_one($planId);
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') {
if (!$cust) {
r2(U . 'plan/recharge', 'e', Lang::T('Customer not found'));
@ -108,7 +132,7 @@ switch ($action) {
if (!$plan) {
r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
}
if ($cust['balance'] < ($plan['price'] + $add_cost)) {
if ($cust['balance'] < $total_cost) {
r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
}
$gateway = 'Recharge Balance';
@ -122,6 +146,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);
@ -145,10 +172,12 @@ 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');
@ -165,15 +194,35 @@ 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'));
}
if (!$plan) {
r2(U . 'plan/recharge', 'e', Lang::T('Plan not found'));
}
if ($cust['balance'] < ($plan['price'] + $add_cost)) {
if ($cust['balance'] < $total_cost) {
r2(U . 'plan/recharge', 'e', Lang::T('insufficient balance'));
}
$gateway = 'Recharge Balance';
@ -185,11 +234,11 @@ 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']);
App::setVoucher($svoucher, $cust['username']);
$ui->display('invoice.tpl');
_log('[' . $admin['username'] . ']: ' . 'Recharge ' . $cust['username'] . ' [' . $in['plan_name'] . '][' . Lang::moneyFormat($in['price']) . ']', $admin['user_type'], $admin['id']);
} else {
@ -228,11 +277,13 @@ 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');
break;
@ -361,7 +412,7 @@ switch ($action) {
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 +422,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);
@ -500,6 +552,7 @@ switch ($action) {
$pagebreak = _post('pagebreak');
$limit = _post('limit');
$vpl = _post('vpl');
$selected_datetime = _post('selected_datetime');
if (empty($vpl)) {
$vpl = 3;
}
@ -542,6 +595,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 +615,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 +651,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,6 +679,7 @@ switch ($action) {
$ui->assign('voucher', $voucher);
$ui->assign('vc', $vc);
$ui->assign('selected_datetime', $selected_datetime);
//for counting pagebreak
$ui->assign('jml', 0);
@ -602,6 +690,7 @@ switch ($action) {
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Agent', 'Sales'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
$type = _post('type');
$plan = _post('plan');
$voucher_format = _post('voucher_format');
@ -609,18 +698,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 +726,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,12 +753,47 @@ 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();
$newVoucherIds[] = $d->id();
}
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('print-voucher.tpl');
}
if ($numbervoucher == 1) {
r2(U . 'plan/voucher-view/' . $d->id(), 's', Lang::T('Create Vouchers Successfully'));
}
@ -673,6 +804,43 @@ switch ($action) {
}
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;
case 'voucher-view':
$id = $routes[2];
if (in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
@ -758,7 +926,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) {
@ -795,24 +963,42 @@ switch ($action) {
_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');
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');
} else {
r2(U . '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('invoice.tpl');
} else {
@ -825,8 +1011,8 @@ switch ($action) {
case 'extend':
$id = $routes[2];
$days = $routes[3];
$stoken = $_GET['stoken'];
if (App::getTokenValue($stoken)) {
$svoucher = $_GET['svoucher'];
if (App::getVoucherValue($svoucher)) {
r2(U . 'plan', 's', "Extend already done");
}
$tur = ORM::for_table('tbl_user_recharges')->find_one($id);
@ -839,7 +1025,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 +1034,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"));

View File

@ -147,6 +147,132 @@ switch ($action) {
} else {
r2(U . 'pool/edit/' . $id, 'e', $msg);
}
case 'port':
$ui->assign('xfooter', '<script type="text/javascript" src="ui/lib/c/pool.js"></script>');
$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('port.tpl');
break;
case 'add-port':
$r = ORM::for_table('tbl_routers')->find_many();
$ui->assign('r', $r);
run_hook('view_add_port'); #HOOK
$ui->display('port-add.tpl');
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('port-edit.tpl');
} else {
r2(U . '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(U . '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(U . '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(U . 'pool/port', 's', Lang::T('Data Created Successfully'));
} else {
r2(U . '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(U . 'pool/port', 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'pool/edit-port/' . $id, 'e', $msg);
}
break;
default:

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,33 +25,36 @@ 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');
@ -59,10 +65,11 @@ 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);
@ -71,85 +78,133 @@ switch ($do) {
r2(U . '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();
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(U . '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 {
// Display register.tpl if OTP is not enabled
$ui->display('customer/register.tpl');
}
}
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(U . '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 {
$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');
$ui->display('customer/register.tpl');
}
break;
}

View File

@ -83,9 +83,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"));
@ -321,6 +321,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 +354,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 +384,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;
@ -453,9 +460,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"));
@ -705,6 +712,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 +736,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 +770,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;
@ -835,6 +848,7 @@ switch ($action) {
$id = _post('id');
$name = _post('name');
$price = _post('price');
$price_old = _post('price_old');
$enabled = _post('enabled');
$prepaid = _post('prepaid');
@ -851,11 +865,15 @@ 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();
@ -901,6 +919,386 @@ switch ($action) {
r2(U . 'services/balance-add', 'e', $msg);
}
break;
case 'vpn':
$ui->assign('_title', Lang::T('VPN Plans'));
$ui->assign('xfooter', '<script type="text/javascript" src="ui/lib/c/pppoe.js"></script>');
$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('vpn.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('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('vpn-edit.tpl');
} else {
r2(U . '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(U . '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(U . 'services/vpn', 's', Lang::T('Data Created Successfully'));
} else {
r2(U . '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(U . 'services/vpn', 's', Lang::T('Data Updated Successfully'));
} else {
r2(U . 'services/vpn-edit/' . $id, 'e', $msg);
}
break;
default:
$ui->display('a404.tpl');
}

View File

@ -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' => '',
@ -81,6 +81,35 @@ switch ($action) {
$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,6 +117,20 @@ 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")) {
@ -111,11 +154,17 @@ 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
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('app-settings.tpl');
break;
@ -123,6 +172,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 = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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)) {
@ -139,7 +192,7 @@ switch ($action) {
r2(U . 'settings/app', 'e', 'PHP GD is not installed');
}
}
if ($company == '') {
if ($_POST['general'] && $company == '') {
r2(U . 'settings/app', 'e', Lang::T('All field is required'));
} else {
if ($radius_enable) {
@ -151,11 +204,15 @@ 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('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;
foreach ($_POST as $key => $value) {
@ -170,29 +227,104 @@ 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'));
}
break;
case 'login-page-post':
// Login page post
$login_page_title = _post('login_page_head');
$login_page_description = _post('login_page_description');
$login_Page_template = _post('login_Page_template');
$login_page_type = _post('login_page_type');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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(U . 'settings/app', 'e', 'Please fill all required fields');
return;
}
if (strlen($login_page_title) > 25) {
r2(U . 'settings/app', 'e', 'Login page title must not exceed 25 characters');
return;
}
if (strlen($login_page_description) > 100) {
r2(U . 'settings/app', 'e', 'Login page description must not exceed 50 characters');
return;
}
$settings = [
'login_page_head' => $login_page_title,
'login_page_description' => $login_page_description,
'login_Page_template' => $login_Page_template,
'login_page_type' => $login_page_type,
];
$image_paths = [];
$allowed_types = ['image/jpeg', 'image/png'];
if ($_FILES['login_page_favicon']['name'] != '') {
$favicon_type = $_FILES['login_page_favicon']['type'];
if (in_array($favicon_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_favicon']['name'])) {
$extension = pathinfo($_FILES['login_page_favicon']['name'], PATHINFO_EXTENSION);
$favicon_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('favicon_') . '.' . $extension;
File::resizeCropImage($_FILES['login_page_favicon']['tmp_name'], $favicon_path, 16, 16, 100);
$settings['login_page_favicon'] = basename($favicon_path); // Save dynamic file name
if (file_exists($_FILES['login_page_favicon']['tmp_name'])) unlink($_FILES['login_page_favicon']['tmp_name']);
} else {
r2(U . 'settings/app', 'e', 'Favicon must be a JPG, JPEG, or PNG image.');
}
}
if ($_FILES['login_page_wallpaper']['name'] != '') {
$wallpaper_type = $_FILES['login_page_wallpaper']['type'];
if (in_array($wallpaper_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_wallpaper']['name'])) {
$extension = pathinfo($_FILES['login_page_wallpaper']['name'], PATHINFO_EXTENSION);
$wallpaper_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('wallpaper_') . '.' . $extension;
File::resizeCropImage($_FILES['login_page_wallpaper']['tmp_name'], $wallpaper_path, 1920, 1080, 100);
$settings['login_page_wallpaper'] = basename($wallpaper_path); // Save dynamic file name
if (file_exists($_FILES['login_page_wallpaper']['tmp_name'])) unlink($_FILES['login_page_wallpaper']['tmp_name']);
} else {
r2(U . 'settings/app', 'e', 'Wallpaper must be a JPG, JPEG, or PNG image.');
}
}
if ($_FILES['login_page_logo']['name'] != '') {
$logo_type = $_FILES['login_page_logo']['type'];
if (in_array($logo_type, $allowed_types) && preg_match('/\.(jpg|jpeg|png)$/i', $_FILES['login_page_logo']['name'])) {
$extension = pathinfo($_FILES['login_page_logo']['name'], PATHINFO_EXTENSION);
$logo_path = $UPLOAD_PATH . DIRECTORY_SEPARATOR . uniqid('logo_') . '.' . $extension;
File::resizeCropImage($_FILES['login_page_logo']['tmp_name'], $logo_path, 300, 60, 100);
$settings['login_page_logo'] = basename($logo_path); // Save dynamic file name
if (file_exists($_FILES['login_page_logo']['tmp_name'])) unlink($_FILES['login_page_logo']['tmp_name']);
} else {
r2(U . 'settings/app', 'e', 'Logo must be a JPG, JPEG, or PNG image.');
}
}
foreach ($settings as $key => $value) {
$d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one();
if ($d) {
$d->value = $value;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = $key;
$d->value = $value;
$d->save();
}
}
_log('[' . $admin['username'] . ']: ' . Lang::T('Login Page Settings Saved Successfully'), $admin['user_type'], $admin['id']);
r2(U . 'settings/app', 's', Lang::T('Login Page Settings Saved Successfully'));
break;
case 'localisation':
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
@ -217,6 +349,8 @@ switch ($action) {
$ui->assign('tlist', $timezonelist);
$ui->assign('xjq', ' $("#tzone").select2(); ');
run_hook('view_localisation'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('app-localisation.tpl');
break;
@ -224,6 +358,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 = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . 'settings/app', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$tzone = _post('tzone');
$date_format = _post('date_format');
$country_code_phone = _post('country_code_phone');
@ -295,6 +433,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,7 +452,6 @@ 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');
}
@ -377,6 +524,8 @@ switch ($action) {
$ui->assign('d', $d);
$ui->assign('search', $search);
run_hook('view_list_admin'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin.tpl');
break;
@ -384,6 +533,8 @@ 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");
}
$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');
@ -413,6 +564,8 @@ switch ($action) {
}
$ui->assign('d', $d);
$ui->assign('_title', $d['username']);
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin-view.tpl');
} else {
r2(U . 'settings/users', 'e', Lang::T('Account Not Found'));
@ -447,9 +600,28 @@ 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
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('admin-edit.tpl');
} else {
r2(U . 'settings/users', 'e', Lang::T('Account Not Found'));
@ -479,6 +651,10 @@ 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");
}
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . 'settings/users-add', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$username = _post('username');
$fullname = _post('fullname');
$password = _post('password');
@ -543,6 +719,10 @@ switch ($action) {
break;
case 'users-edit-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . 'settings/users-edit/', 'e', Lang::T('Invalid or Expired CSRF Token') . ".");
}
$username = _post('username');
$fullname = _post('fullname');
$password = _post('password');
@ -599,6 +779,55 @@ 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(U . 'settings/app', 'e', 'PHP GD is not installed');
}
}
$d->username = $username;
if ($password != '') {
$password = Password::_crypt($password);
@ -629,7 +858,7 @@ 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(U . 'settings/users-view/' . $id, 's', 'User Updated Successfully');
} else {
r2(U . 'settings/users-edit/' . $id, 'e', $msg);
}
@ -637,11 +866,17 @@ switch ($action) {
case 'change-password':
run_hook('view_change_password'); #HOOK
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('change-password.tpl');
break;
case 'change-password-post':
$password = _post('password');
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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
@ -686,6 +921,9 @@ 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');
break;
@ -693,6 +931,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 = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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'));
break;
@ -798,10 +1040,16 @@ switch ($action) {
} else {
$ui->assign('langs', []);
}
$csrf_token = Csrf::generateAndStoreToken();
$ui->assign('csrf_token', $csrf_token);
$ui->display('language-add.tpl');
break;
case 'lang-post':
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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'));
break;
@ -811,7 +1059,12 @@ switch ($action) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
if (_post('save') == 'save') {
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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;
@ -837,11 +1090,45 @@ switch ($action) {
r2(U . "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');
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') {
$csrf_token = _post('csrf_token');
if (!Csrf::check($csrf_token)) {
r2(U . '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(U . "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('app-miscellaneous.tpl');
break;
default:
$ui->display('a404.tpl');
}

View File

@ -17,12 +17,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)) {
@ -41,12 +41,18 @@ switch ($action) {
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,7 +64,7 @@ switch ($action) {
}
if ($in) {
Package::createInvoice($in);
$ui->display('user-ui/invoice-customer.tpl');
$ui->display('customer/invoice-customer.tpl');
} else {
r2(U . 'voucher/list-activated', 'e', Lang::T('Not Found'));
}

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;
@ -32,6 +53,9 @@ foreach ($d as $ds) {
$u = ORM::for_table('tbl_user_recharges')->where('id', $ds['id'])->find_one();
$c = ORM::for_table('tbl_customers')->where('id', $ds['customer_id'])->find_one();
$p = ORM::for_table('tbl_plans')->where('id', $u['plan_id'])->find_one();
if (empty($c)) {
$c = $u;
}
$dvc = Package::getDevice($p);
if ($_app_stage != 'demo') {
if (file_exists($dvc)) {
@ -50,7 +74,7 @@ foreach ($d as $ds) {
// 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 ($add_cost != 0) {
if (!empty($add_cost)) {
$p['price'] += $add_cost;
}
@ -79,29 +103,26 @@ foreach ($d as $ds) {
}
}
//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";
@ -186,14 +207,22 @@ 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\n";
}
if (defined('PHP_SAPI') && PHP_SAPI === 'cli') {
echo "Cronjob finished\n";
} else {
echo "</pre>";
}
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

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)
{
@ -109,9 +137,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)
@ -210,7 +239,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 +249,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 +267,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 +335,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 +352,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 +369,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 +385,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']);
@ -286,9 +302,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)
@ -321,7 +345,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 +361,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);
}

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)

View File

@ -724,5 +724,234 @@
"Mail_Reply_To": "Mail Reply To",
"Customer_will_reply_email_to_this_address__empty_if_you_want_to_use_From_Address": "Customer will reply email to this address, empty if you want to use From Address",
"You_will_get_Payment_and_Error_notification": "You will get Payment and Error notification",
"Languge_set_to_english": "Bahasa diatur ke bahasa Inggris"
"Languge_set_to_english": "Bahasa diatur ke bahasa Inggris",
"Forgot_Password": "Forgot Password",
"_Are_You_Sure_": "Are You Sure?",
"Send_your_balance___": "Send your balance ?",
"Search_Users": "Search Users",
"Theme_Voucher": "Theme Voucher",
"Payment_Info": "Payment Info",
"Radius_Package": "Radius Package",
"Hotspot_Package": "Hotspot Package",
"PPPOE_Package": "PPPOE Package",
"VPN_Package": "VPN Package",
"Application_Name___Company_Name": "Application Name \/ Company Name",
"Print_Max_Char": "Print Max Char",
"Redirect_URL_after_Activation": "Redirect URL after Activation",
"Enable_Radius": "Enable Radius",
"Customer_Balance_System": "Customer Balance System",
"Telegram_User_Channel_Group_ID": "Telegram User\/Channel\/Group ID",
"Test_SMS": "Test SMS",
"SMS_Server_URL": "SMS Server URL",
"Select_Router": "Select Router",
"Free_Server": "Free Server",
"WhatsApp_Server_URL": "WhatsApp Server URL",
"SMTP_Username": "SMTP Username",
"SMTP_Password": "SMTP Password",
"SMTP_Security": "SMTP Security",
"None": "None",
"By_WhatsApp": "By WhatsApp",
"By_SMS": "By SMS",
"By_Email": "By Email",
"From_Direct_Chat_Link_": "From Direct Chat Link.",
"Access_Token": "Access Token",
"Empty_this_to_randomly_created_API_key": "Empty this to randomly created API key",
"Router_Check": "Router Check",
"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": "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",
"Phone_OTP_Required": "Phone OTP Required",
"OTP_is_required_when_user_want_to_change_phone_number_and_registration": "OTP is required when user want to change phone number and registration",
"by_WhatsApp": "by WhatsApp",
"By_WhatsApp_and_SMS": "By WhatsApp and SMS",
"Email_OTP_Required": "Email OTP Required",
"OTP_is_required_when_user_want_to_change_Email_Address": "OTP is required when user want to change Email Address",
"Show_Bandwidth_Plan": "Show Bandwidth Plan",
"_for_Customer": "for Customer",
"Custome": "Custom",
"Custome_Tax_Rate": "Custome Tax Rate",
"Enter_Custome_Tax_Rate": "Enter Custome Tax Rate",
"Authentication": "Authentication",
"Github_Username": "Github Username",
"Github_Token": "Github Token",
"Create_GitHub_personal_access_token": "Create GitHub personal access token",
"only_need_repo_scope": "only need repo scope",
"This_will_allow_you_to_download_plugin_from_private_paid_repository": "This will allow you to download plugin from private\/paid repository",
"Expired_Cronjob_Every_5_Minutes": "Expired Cronjob Every 5 Minutes",
"Expired_Cronjob_Every_1_Hour": "Expired Cronjob Every 1 Hour",
"Reminder_Cronjob_Every_7_AM": "Reminder Cronjob Every 7 AM",
"Force_Logout_": "Force Logout:",
"Activation": "Activation",
"Package_Name": "Package Name",
"Routers_Offline": "Routers Offline",
"Cron_appear_not_been_setup__please_check_your_cron_setup_": "Cron appear not been setup, please check your cron setup.",
"3_Months": "3 Months",
"Invalid_or_Expired_CSRF_Token": "Invalid or Expired CSRF Token",
"Miscellaneous_Settings": "Miscellaneous Settings",
"Check_if_Customer_Online": "Check if Customer Online",
"This_will_show_is_Customer_currently_is_online_or_not": "This will show is Customer currently is online or not",
"General": "General",
"Tax_Rates_by_percentage": "Tax Rates by percentage",
"Settings_For_Mikrotik": "Mikrotik Settings",
"Settings_For_Cron_Expired": "Settings For Cron Expired",
"Choose_one__above_or_below": "Choose one, above or below",
"Settings_For_Cron_Reminder": "Settings For Cron Reminder",
"Upload_Zip_Plugin_Theme_Device": "Upload Zip Plugin\/Theme\/Device",
"Install": "Install",
"To_download_from_private_paid_repository": "To download from private\/paid repository",
"Set_your_Github_Authentication_first": "Set your Github Authentication first",
"SMS_Notification": "SMS Notification",
"Customer_Registration_need_to_validate_using_OTP": "Customer Registration need to validate using OTP",
"Registration_Username": "Registration Username",
"Registration": "Registration",
"For_Registration_and_Update_Phone_Number": "For Registration and Update Phone Number",
"Voucher_Only": "Voucher Only",
"No_Registration": "No Registration",
"Allow_Registration": "Allow Registration",
"Prepaid": "Prepaid",
"Postpaid": "Postpaid",
"Not_Active": "Not Active",
"New_Service_Package": "New Service Package",
"Limit": "Limit",
"Create_expired_Internet_Package": "Create expired Internet Package",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Package": "When customer expired, you can move it to Expired Internet Package",
"Disable": "Disable",
"Create_expired_Internet_Plan": "Create expired Internet Plan",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Plan": "When customer expired, you can move it to Expired Internet Plan",
"Payment_Method": "Payment Method",
"Created_on": "Created on",
"Expires_on": "Expires on",
"Edit_Service_Package": "Edit Service Package",
"Package_Type": "Package Type",
"Package_Price": "Package Price",
"Price_Before_Discount": "Price Before Discount",
"For_Discount_Rate__this_is_price_before_get_discount__must_be_more_expensive_with_real_price": "For Discount Rate, this is price before get discount, must be more expensive with real price",
"Package_Validity": "Package Validity",
"Expired_Internet_Package": "Expired Internet Package",
"Default___Remove_Customer": "Default - Remove Customer",
"When_Expired__customer_will_be_move_to_selected_internet_package": "When Expired, customer will be move to selected internet package",
"on_login___on_up": "on-login \/ on-up",
"on_logout___on_down": "on-logout \/ on-down",
"Online_Status": "Online Status",
"Last_Seen": "Last Seen",
"Online": "Online",
"Offline": "Offline",
"Check_if_Mikrotik_Online_": "Check if Mikrotik is Online?",
"To_check_if_Mikrotik_is_Online_or_not__go_to_Settings__set_Router_Check_Enabled": "To check if Mikrotik is Online or not, go to Settings, set Router Check Enabled",
"via_SMS": "via SMS",
"Via_WhatsApp": "Via WhatsApp",
"Via_WhatsApp_and_SMS": "Via WhatsApp and SMS",
"Make_sure_you_use_API_Port__Default_8728": "Make sure you use API Port, Default 8728",
"Make_sure_Username_and_Password_are_correct": "Make sure Username and Password are correct",
"Make_sure_your_hosting_not_blocking_port_to_external": "Make sure your hosting not blocking port to external",
"Make_sure_your_Mikrotik_accessible_from_PHPNuxBill": "Make sure your Mikrotik accessible from PHPNuxBill",
"If_you_just_update_PHPNuxBill_from_upload_files__try_click_Update": "If you just update PHPNuxBill from upload files, try click Update",
"Update_PHPNuxBill": "Update PHPNuxBill",
"Ask_Github_Community": "Ask Github Community",
"Ask_Telegram_Community": "Ask Telegram Community",
"Transaction_History_List": "Transaction History List",
"Login_as_Customer": "Login as Customer",
"info": "info",
"Registration_code": "Registration code",
"Admin_can_only_have_single_session_login__it_will_logout_another_session": "Admin can only have single session login, it will logout another session",
"Single_session_Admin": "Single session Admin",
"Get_Directions": "Get Directions",
"Buy_Balance_Plans": "Buy Balance Plans",
"Buy": "Buy",
"Cron_Job_last_ran_on": "Cron Job last ran on",
"VPN_Plans": "VPN Plans",
"Postpaid_Recharge_for_the_first_time_use": "Postpaid Recharge for the first time use",
"Or": "Or",
"Balance_Package": "Balance Package",
"Balance_Custom": "Balance Custom",
"Balance_Amount": "Balance Amount",
"Select_Balance_Package_or_Custom_Amount": "Select Balance Package or Custom Amount",
"Note": "Note",
"Or_custom_balance_amount_below": "Or custom balance amount below",
"Input_custom_balance__will_ignore_plan_above": "Input custom balance, will ignore plan above",
"Cron_has_not_run_for_over_1_hour__Please_check_your_setup_": "Cron has not run for over 1 hour. Please check your setup.",
"Hello": "Hello",
"your_internet_package": "your internet package",
"has_been_expired": "has been expired",
"Welcome_Message": "Welcome Message",
"will_be_replaced_with_Customer_password": "will be replaced with Customer password",
"will_be_replaced_with_Customer_Portal_URL": "will be replaced with Customer Portal URL",
"will_be_replaced_with_Company_Name": "will be replaced with Company Name",
"Token_has_expired__Please_log_in_again_": "Token has expired. Please log in again.",
"Minute": "Minute",
"Hour": "Hour",
"Failed_to_connect_to_device": "Failed to connect to device",
"Custom_Balance": "Custom Balance",
"Input_Desired_Amount": "Input Desired Amount",
"Security": "Security",
"Enable_CSRF_Validation": "Enable CSRF Validation",
"Cross_site_request_forgery": "Cross-site request forgery",
"Validity_Periode": "Validity Periode",
"Insufficient_balance": "Insufficient balance",
"Display_bandwidth_plan_for_customer": "Display bandwidth plan for customer",
"Allow_Balance_Custom_Amount": "Allow Balance Custom Amount",
"Allow_Customer_buy_balance_with_any_amount": "Allow Customer buy balance with any amount",
"Customer_Login_Page_Settings": "Customer Login Page Settings",
"Choose_Template": "Choose Template",
"Select_your_login_template_type": "Select your login template type",
"Select_Login_Page": "Select Login Page",
"Select_your_preferred_login_template": "Select your preferred login template",
"Page_Heading___Company_Name": "Page Heading \/ Company Name",
"This_Name_will_be_shown_on_the_login_wallpaper": "This Name will be shown on the login wallpaper",
"Page_Description": "Page Description",
"This_will_also_display_on_wallpaper__You_can_use_html_tag": "This will also display on wallpaper, You can use html tag",
"Favicon": "Favicon",
"Best_size_30_x_30___uploaded_image_will_be_autosize": "Best size 30 x 30 | uploaded image will be autosize",
"Login_Page_Logo": "Login Page Logo",
"Best_size_300_x_60___uploaded_image_will_be_autosize": "Best size 300 x 60 | uploaded image will be autosize",
"Login_Page_Wallpaper": "Login Page Wallpaper",
"Best_size_1920_x_1080___uploaded_image_will_be_autosize": "Best size 1920 x 1080 | uploaded image will be autosize",
"Single_Admin_Session": "Single Admin Session",
"Expired_Cronjob_Every_5_Minutes__Recommended_": "Expired Cronjob Every 5 Minutes [Recommended]",
"Login_Page_Settings_Saved_Successfully": "Login Page Settings Saved Successfully",
"Sign_in_into_your_account": "Sign in into your account",
"Don_t_have_an_account_": "Don&#39;t have an account?",
"You_do_not_have_permission_to_access_this_page_in_demo_mode": "You do not have permission to access this page in demo mode",
"Custom_Fields": "Custom Fields",
"New_Field": "New Field",
"Data_Change": "Data Change",
"Photo": "Photo",
"Home_Address": "Home Address",
"Email_Address": "Email Address",
"Photo_Required": "Photo Required",
"Customer_Registration_need_to_upload_their_photo": "Customer Registration need to upload their photo",
"Account_already_exists": "Account already exists",
"Notify_Admin": "Notify Admin",
"Notify_Admin_upon_self_registration": "Notify Admin upon self registration",
"Registration_Mandatory_Fields": "Registration Mandatory Fields",
"Mikrotik_SMS_Command": "Mikrotik SMS Command",
"Mandatory_Fields": "Mandatory Fields",
"Usernames": "Usernames",
"Yours_Balance": "Yours Balance",
"Friend_Usernames": "Friend Usernames",
"Send_yours_balance___": "Send yours balance ? ",
"Cards": "Cards",
"CRM": "CRM",
"Coupons": "Coupons",
"Search_Coupons": "Search Coupons",
"Add_Coupon": "Add Coupon",
"Value": "Value",
"Max_Usage": "Max Usage",
"Usage_Count": "Usage Count",
"Min_Order": "Min Order",
"Max_Discount": "Max Discount",
"Updated_Date": "Updated Date",
"Action": "Action",
"No_coupons_found_": "No coupons found.",
"Delete_Selected": "Delete Selected",
"Voucher_Cards": "Voucher Cards",
"Create_Date": "Create Date",
"Message_Results": "Message Results",
"Total_SMS_Sent": "Total SMS Sent",
"Total_SMS_Failed": "Total SMS Failed",
"Total_WhatsApp_Sent": "Total WhatsApp Sent",
"Total_WhatsApp_Failed": "Total WhatsApp Failed",
"First_Name": "First Name",
"Last_Name": "Last Name",
"Not_Working_with_Freeradius_Mysql": "Not Working with Freeradius Mysql",
"Radius_Rest_Interim_Update": "Radius Rest Interim-Update",
"in_minutes__leave_0_to_disable_this_feature_": "in minutes, leave 0 to disable this feature."
}

View File

@ -381,8 +381,8 @@
"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",
"Plans": "Paket",
"PPPOE": "PPPOE",
"Bandwidth": "Bandwidth",
@ -490,7 +490,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",
@ -538,7 +538,6 @@
"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 +546,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",
@ -571,8 +570,344 @@
"Languge_set_to_indonesia": "Language set to 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": "Voucher Tema",
"Payment_Info": "Info Pembayaran",
"Documentation": "Dokumentasi",
"Customers": "Pelanggan",
"Package_Name": "Nama Paket",
"Routers_Offline": "Router Offline",
"Cron_appear_not_been_setup__please_check_your_cron_setup_": "Cron tampaknya belum disiapkan, silakan periksa pengaturan cron Anda.",
"Buy": "Membeli",
"You_are_already_logged_in": "Anda sudah masuk",
"PPPOE_Package": "Paket PPPoE",
"Prepaid": "Prabayar",
"Postpaid": "Pascabayar",
"Enabled": "Diaktifkan",
"Disable": "Nonaktifkan",
"Create_expired_Internet_Plan": "Buat Paket Internet yang Kedaluwarsa",
"When_customer_expired__you_can_move_it_to_Expired_Internet_Plan": "Ketika pelanggan kedaluwarsa, Anda dapat memindahkannya ke Paket Internet Kedaluwarsa",
"Price_Before_Discount": "Harga Sebelum Diskon",
"For_Discount_Rate__this_is_price_before_get_discount__must_be_more_expensive_with_real_price": "Untuk Discount Rate, ini adalah harga sebelum mendapat diskon, pasti lebih mahal dari harga sebenarnya",
"on_login___on_up": "saat masuk \/ saat mendaftar",
"on_logout___on_down": "saat keluar \/ saat turun",
"Get_Directions": "Dapatkan Petunjuk Arah",
"Not_Working_with_Freeradius_Mysql": "Tidak Bekerja dengan Freeradius Mysql",
"User_Cannot_change_this__only_admin__if_it_Empty_it_will_use_Customer_Credentials": "Pengguna tidak dapat mengubah ini, hanya admin. Jika Kosong, maka akan menggunakan Kredensial Pelanggan",
"Buy_this__your_active_package_will_be_overwritten": "Beli ini? Paket aktif Anda akan ditimpa",
"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 Username dan Password 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": "Waktu Habis Idle, Keluar dari Admin jika Idle selama xx menit",
"New_Version_Notification": "Pemberitahuan Versi Baru",
"This_is_to_notify_you_when_new_updates_is_available": "Ini untuk memberi tahu Anda ketika pembaruan baru tersedia",
"Router_Check": "Pemeriksaan Router",
"If_enabled__the_system_will_notify_Admin_when_router_goes_Offline__If_admin_have_10_or_more_router_and_many_customers__it_will_get_overlapping__you_can_disabled": "Jika diaktifkan, sistem akan memberitahu Admin ketika router Offline, Jika admin memiliki 10 atau lebih router dan banyak pelanggan, maka akan terjadi tumpang tindih, Anda dapat menonaktifkannya",
"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": "Memasang",
"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": "Menyimpan",
"Not_Active": "Tidak Aktif",
"Limit": "Membatasi",
"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": "Membagikan",
"Agent": "Agen",
"Sub_District": "Kecamatan",
"Ward": "Bangsal",
"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 Update 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 Canggih",
"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 Sukses.",
"_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 pagination",
"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 rencana 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]"
}

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": "WENJEI",
"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,357 @@
"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": "Ciudad",
"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"
}

View File

@ -59,101 +59,130 @@
"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" : [
"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" : [
"2024.2.20.1": [
"DROP TABLE IF EXISTS `tbl_customers_meta`;"
],
"2024.2.23" : [
"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.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" : [
"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" : [
"2024.3.14": [
"ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;"
],
"2024.3.19" : [
"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" : [
"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" : [
"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" : [
"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" : [
"2024.5.17": [
"ALTER TABLE `tbl_customers` ADD `status` ENUM('Active','Banned','Disabled') NOT NULL DEFAULT 'Active' AFTER `auto_renewal`;"
],
"2024.5.18" : [
"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" : [
"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" : [
"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" : [
"2024.6.10": [
"ALTER TABLE `tbl_pool` ADD `local_ip` VARCHAR(40) NOT NULL DEFAULT '' AFTER `pool_name`;"
],
"2024.6.11" : [
"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" : [
"2024.6.19": [
"ALTER TABLE `tbl_plans` ADD `expired_date` TINYINT(1) NOT NULL DEFAULT '20' AFTER `plan_expired`;"
],
"2024.6.21" : [
"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" : [
"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" : [
"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" : [
"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" : [
"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" : [
"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" : [
"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" : [
"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" : [
"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" : [
"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`;"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 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
);
}

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;
}
}

View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,14 @@
PHP Face Detection
==================
This class can detect one face in images ATM.
This is a pure PHP port of an existing JS code from Karthik Tharavaad.
Requirements
------------
PHP5 with GD
License
-------
GNU GPL v2 (See LICENSE.txt)

View File

@ -0,0 +1,19 @@
{
"name": "mauricesvay/php-facedetection",
"description": "PHP class to detect one face in images. A pure PHP port of an existing JS code from Karthik Tharavad.",
"license": "GPL-2.0",
"homepage": "https://github.com/mauricesvay/php-facedetection",
"authors": [
{
"name": "Maurice Svay",
"homepage": "https://github.com/mauricesvay/php-facedetection/graphs/contributors"
}
],
"require": {
"php": ">=5.2.0",
"ext-gd": "*"
},
"autoload": {
"classmap": ["FaceDetector.php", "Exception/"]
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
<?php
include "FaceDetector.php";
$detector = new svay\FaceDetector('detection.dat');
$detector->faceDetect('lena512color.jpg');
$detector->toJpeg();

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -2,6 +2,7 @@
<!-- user-edit -->
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/users-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="row">
<div class="col-sm-6 col-md-6">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
@ -89,9 +90,9 @@
<label class="col-md-5 control-label">{Lang::T('Send Notification')}</label>
<div class="col-md-7">
<select name="send_notif" id="send_notif" class="form-control">
<option value="-">Don't Send</option>
<option value="sms">By SMS</option>
<option value="wa">By WhatsApp</option>
<option value="-">{Lang::T("Don't Send")}</option>
<option value="sms">{Lang::T('By SMS')}</option>
<option value="wa">{Lang::T('By WhatsApp')}</option>
</select>
</div>
</div>
@ -100,7 +101,7 @@
</div>
</div>
<div class="form-group text-center">
<button class="btn btn-primary" type="submit">{Lang::T('Save Changes')}</button>
<button class="btn btn-primary" onclick="return ask(this, 'Continue the process of adding Admin?')" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}settings/users">{Lang::T('Cancel')}</a>
</div>
</form>
@ -116,4 +117,4 @@
</script>
{/literal}
{include file="sections/footer.tpl"}
{include file="sections/footer.tpl"}

View File

@ -1,7 +1,9 @@
{include file="sections/header.tpl"}
<!-- user-edit -->
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/users-edit-post">
<form class="form-horizontal" method="post" enctype="multipart/form-data" role="form"
action="{$_url}settings/users-edit-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="row">
<div class="col-sm-6 col-md-6">
<div
@ -9,6 +11,20 @@
<div class="panel-heading">{Lang::T('Profile')}</div>
<div class="panel-body">
<input type="hidden" name="id" value="{$d['id']}">
<center>
<img src="{$UPLOAD_PATH}{$d['photo']}.thumb.jpg" width="200"
onerror="this.src='{$UPLOAD_PATH}/admin.default.png'" class="img-circle img-responsive" alt="Foto"
onclick="return deletePhoto({$d['id']})">
</center><br>
<div class="form-group">
<label class="col-md-3 col-xs-12 control-label">{Lang::T('Photo')}</label>
<div class="col-md-6 col-xs-8">
<input type="file" class="form-control" name="photo" accept="image/*">
</div>
<div class="form-group col-md-3 col-xs-4" title="Not always Working">
<label class=""><input type="checkbox" checked name="faceDetect" value="yes"> Facedetect</label>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Full Name')}</label>
<div class="col-md-9">
@ -56,9 +72,9 @@
<div class="col-md-9">
<select name="status" id="status" class="form-control">
<option value="Active" {if $d['status'] eq 'Active'}selected="selected" {/if}>
Active</option>
{Lang::T('Active')}</option>
<option value="Inactive" {if $d['status'] eq 'Inactive'}selected="selected" {/if}>
Inactive</option>
{Lang::T('Inactive')}</option>
</select>
</div>
</div>
@ -92,7 +108,8 @@
<div class="col-md-9">
<select name="root" id="root" class="form-control">
{foreach $agents as $agent}
<option value="{$agent['id']}">{$agent['username']} | {$agent['fullname']} | {$agent['phone']}</option>
<option value="{$agent['id']}">{$agent['username']} | {$agent['fullname']} |
{$agent['phone']}</option>
{/foreach}
</select>
</div>
@ -125,20 +142,27 @@
</div>
</div>
<div class="form-group text-center">
<button class="btn btn-primary" type="submit">{Lang::T('Save Changes')}</button>
<button class="btn btn-primary" onclick="return ask(this, 'Continue the Admin change process?')"
type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}settings/users">{Lang::T('Cancel')}</a>
</div>
</form>
{literal}
<script>
function checkUserType($field){
if($field.value=='Sales'){
$('#agentChooser').removeClass('hidden');
}else{
$('#agentChooser').addClass('hidden');
<script>
function checkUserType($field) {
if ($field.value == 'Sales') {
$('#agentChooser').removeClass('hidden');
} else {
$('#agentChooser').addClass('hidden');
}
}
function deletePhoto(id) {
if (confirm('Delete photo?')) {
if (confirm('Are you sure to delete photo?')) {
window.location.href = '{$_url}settings/users-edit/'+id+'/deletePhoto'
}
}
}
</script>
{/literal}
{include file="sections/footer.tpl"}

View File

@ -24,6 +24,7 @@
{$notify}
{/if}
<form action="{$_url}admin/post" method="post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="form-group has-feedback">
<input type="text" required class="form-control" name="username" placeholder="{Lang::T('Username')}">
<span class="glyphicon glyphicon-user form-control-feedback"></span>

View File

@ -8,6 +8,12 @@
class="panel panel-{if $d['status'] != 'Active'}danger{else}primary{/if} panel-hovered panel-stacked mb30">
<div class="panel-heading">{$d['fullname']}</div>
<div class="panel-body">
<center>
<a href="{$UPLOAD_PATH}{$d['photo']}" target="foto">
<img src="{$UPLOAD_PATH}{$d['photo']}.thumb.jpg" width="200"
onerror="this.src='{$UPLOAD_PATH}/admin.default.png'" class="img-circle img-responsive" alt="Foto">
</a>
</center><br>
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
<b>{Lang::T('Username')}</b> <span class="pull-right">{$d['username']}</span>
@ -47,10 +53,12 @@
<div class="panel-body">
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
<b>{Lang::T('Phone Number')}</b> <span class="pull-right"><a href="tel:{$agent['phone']}">{$agent['phone']}</a></span>
<b>{Lang::T('Phone Number')}</b> <span class="pull-right"><a
href="tel:{$agent['phone']}">{$agent['phone']}</a></span>
</li>
<li class="list-group-item">
<b>{Lang::T('Email')}</b> <span class="pull-right"><a href="mailto:{$agent['email']}">{$agent['email']}</a></span>
<b>{Lang::T('Email')}</b> <span class="pull-right"><a
href="mailto:{$agent['email']}">{$agent['email']}</a></span>
</li>
<li class="list-group-item">
<b>{Lang::T('City')}</b> <span class="pull-right">{$agent['city']}</span>

View File

@ -9,6 +9,7 @@
<div class="md-whiteframe-z1 mb20 text-center" style="padding: 15px">
<div class="col-md-8">
<form id="site-search" method="post" action="{$_url}settings/users/">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="input-group">
<div class="input-group-addon">
<span class="fa fa-search"></span>
@ -62,7 +63,7 @@
class="btn btn-info btn-xs">{Lang::T('Edit')}</a>
{if ($_admin['id']) neq ($ds['id'])}
<a href="{$_url}settings/users-delete/{$ds['id']}" id="{$ds['id']}"
class="btn btn-danger btn-xs" onclick="return confirm('{Lang::T('Delete')}?')"><i class="glyphicon glyphicon-trash"></i></a>
class="btn btn-danger btn-xs" onclick="return ask(this, '{Lang::T('Delete')}?')"><i class="glyphicon glyphicon-trash"></i></a>
{/if}
</td>
<td>{$ds['id']}</td>

View File

@ -13,7 +13,7 @@
<div class="panel-body" style="overflow-y: scroll;">
<div style="max-height: 50px; min-height: 50px;">{nl2br(strip_tags($d['description']))}</div>
</div>
<div class="panel-footer" style="overflow-y: scroll;">
<div class="panel-footer">
<center style="max-height: 40px; min-height: 40px;">
<span class="label label-default">{$d['file']}</span>
{foreach $d['url'] as $name => $url}

View File

@ -13,6 +13,7 @@
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/localisation-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Timezone')}</label>
<div class="col-md-6">
@ -105,7 +106,7 @@
</div>
<hr>
<div class="form-group">
<label class="col-md-2 control-label">Radius Plan</label>
<label class="col-md-2 control-label">{Lang::T('Radius Package')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="radius_plan" name="radius_plan"
value="{if $_c['radius_plan']==''}Radius Plan{else}{$_c['radius_plan']}{/if}">
@ -113,7 +114,7 @@
<span class="help-block col-md-4">{Lang::T('Change title in user Plan order')}</span>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Hotspot Plan</label>
<label class="col-md-2 control-label">{Lang::T('Hotspot Package')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="hotspot_plan" name="hotspot_plan"
value="{if $_c['hotspot_plan']==''}Hotspot Plan{else}{$_c['hotspot_plan']}{/if}">
@ -121,13 +122,21 @@
<span class="help-block col-md-4">{Lang::T('Change title in user Plan order')}</span>
</div>
<div class="form-group">
<label class="col-md-2 control-label">PPPOE Plan</label>
<label class="col-md-2 control-label">{Lang::T('PPPOE Package')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="pppoe_plan" name="pppoe_plan"
value="{if $_c['pppoe_plan']==''}PPPOE Plan{else}{$_c['pppoe_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan order')}</span>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('VPN Package')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="vpn_plan" name="vpn_plan"
value="{if $_c['vpn_plan']==''}VPN Plan{else}{$_c['vpn_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan order')}</span>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
@ -141,4 +150,4 @@
</div>
</div>
{include file="sections/footer.tpl"}
{include file="sections/footer.tpl"}

210
ui/ui/app-miscellaneous.tpl Normal file
View File

@ -0,0 +1,210 @@
{include file="sections/header.tpl"}
<form class="form-horizontal" method="post" role="form" action="" enctype="multipart/form-data">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">
<div class="btn-group pull-right">
<button class="btn btn-primary btn-xs" title="save" name="save" value="save" type="submit"><span
class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
</div>
{Lang::T('Miscellaneous')}
</div>
<div class="panel-body">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('New Version Notification')}</label>
<div class="col-md-5">
<select name="new_version_notify" id="new_version_notify" class="form-control">
<option value="enable" {if $_c['new_version_notify']=='enable' }selected="selected"
{/if}>
{Lang::T('Enabled')}
</option>
<option value="disable" {if $_c['new_version_notify']=='disable' }selected="selected"
{/if}>
{Lang::T('Disabled')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('This is to notify you when new updates is
available')}</small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Router Check')}</label>
<div class="col-md-5">
<select name="router_check" id="router_check" class="form-control">
<option value="0" {if $_c['router_check']=='0' }selected="selected" {/if}>
{Lang::T('Disabled')}
</option>
<option value="1" {if $_c['router_check']=='1' }selected="selected" {/if}>
{Lang::T('Enabled')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('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')}
</small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Phone OTP Required')}</label>
<div class="col-md-5">
<select name="allow_phone_otp" id="allow_phone_otp" class="form-control">
<option value="no" {if $_c['allow_phone_otp']=='no' }selected="selected" {/if}>
{Lang::T('No')}</option>
<option value="yes" {if $_c['allow_phone_otp']=='yes' }selected="selected" {/if}>
{Lang::T('Yes')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('OTP is required when user want to change phone number and
registration')}</small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('OTP Method')}</label>
<div class="col-md-5">
<select name="phone_otp_type" id="phone_otp_type" class="form-control">
<option value="sms" {if $_c['phone_otp_type']=='sms' }selected="selected" {/if}>
{Lang::T('By SMS')}
<option value="whatsapp" {if $_c['phone_otp_type']=='whatsapp' }selected="selected"
{/if}>
{Lang::T('by WhatsApp')}
<option value="both" {if $_c['phone_otp_type']=='both' }selected="selected" {/if}>
{Lang::T('By WhatsApp and SMS')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>{Lang::T('The method which OTP will be sent to
user')}<br>
{Lang::T('For Registration and Update Phone Number')}</small></p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Email OTP Required')}</label>
<div class="col-md-5">
<select name="allow_email_otp" id="allow_email_otp" class="form-control">
<option value="no" {if $_c['allow_email_otp']=='no' }selected="selected" {/if}>
{Lang::T('No')}</option>
<option value="yes" {if $_c['allow_email_otp']=='yes' }selected="selected" {/if}>
{Lang::T('Yes')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('OTP is required when user want to change Email Address')}
</small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Show Bandwidth Plan')}</label>
<div class="col-md-5">
<select name="show_bandwidth_plan" id="show_bandwidth_plan" class="form-control">
<option value="no" {if $_c['show_bandwidth_plan']=='no' }selected="selected" {/if}>
{Lang::T('No')}</option>
<option value="yes" {if $_c['show_bandwidth_plan']=='yes' }selected="selected" {/if}>
{Lang::T('Yes')}</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('Display bandwidth plan for customer')}</small></p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Hotspot Auth Method')}</label>
<div class="col-md-5">
<select name="hs_auth_method" id="auth_method" class="form-control">
<option value="api" {if $_c['hs_auth_method']=='api' }selected="selected" {/if}>
{Lang::T('Api')}
</option>
<option value="hchap" {if $_c['hs_auth_method']=='hchap' }selected="selected" {/if}>
{Lang::T('Http-Chap')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('Hotspot Authentication Method. Make sure you have changed your
hotspot login
page.')} <a href="https://github.com/agstrxyz/phpnuxbill-login-hotspot"
target="_blank">Download
phpnuxbill-login-hotspot</a></small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Radius Rest Interim-Update')}</label>
<div class="col-md-5">
<input type="number" class="form-control" id="frrest_interim_update" name="frrest_interim_update"
value="{if $_c['frrest_interim_update']}{$_c['frrest_interim_update']}{else}0{/if}">
</div>
<p class="help-block col-md-4"><small>
{Lang::T('in minutes, leave 0 to disable this feature.')}</small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Check if Customer Online')}</label>
<div class="col-md-5">
<select name="check_customer_online" id="check_customer_online" class="form-control">
<option value="no">
{Lang::T('No')}
</option>
<option value="yes" {if $_c['check_customer_online']=='yes' }selected="selected" {/if}>
{Lang::T('Yes')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('This will show is Customer currently is online or not')}</small>
</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Extend Package Expiry')}</label>
<div class="col-md-5">
<select name="extend_expiry" id="extend_expiry" class="form-control">
<option value="yes" {if $_c['extend_expiry']!='no' }selected="selected" {/if}>
{Lang::T('Yes')}</option>
<option value="no" {if $_c['extend_expiry']=='no' }selected="selected" {/if}>
{Lang::T('No')}</option>
</select>
</div>
<p class="help-block col-md-4">
<small> {Lang::T('If user buy same internet plan, expiry date will
extend')}</small>
</p>
</div>
<div class="form-group has-error">
<label class="col-md-3 control-label">{Lang::T('Allow Balance Custom
Amount')}</label>
<div class="col-md-5">
<select name="allow_balance_custom" id="allow_balance_custom" class="form-control">
<option value="no">
{Lang::T('No')}
</option>
<option value="yes" {if $_c['allow_balance_custom']=='yes' }selected="selected" {/if}>
{Lang::T('Yes')}
</option>
</select>
</div>
<p class="help-block col-md-4"><small>
{Lang::T('Allow Customer buy balance with any amount')}
<br>*Please report any issue or bugs</small>
</p>
</div>
</div>
</div>
<div class="panel-body">
<div class="form-group">
<button class="btn btn-success btn-block" name="save" value="save" type="submit">
{Lang::T('Save Changes')}
</button>
</div>
</div>
</div>
</div>
</form>
{include file="sections/footer.tpl"}

View File

@ -1,6 +1,7 @@
{include file="sections/header.tpl"}
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/notifications-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
@ -24,7 +25,8 @@
<b>[[username]]</b> - {Lang::T('will be replaced with Customer username')}.<br>
<b>[[package]]</b> - {Lang::T('will be replaced with Package name')}.<br>
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="./docs/#Reminder%20with%20payment%20link" target="_blank">read documentation</a>.
</p>
</div>
</div>
@ -41,7 +43,8 @@
<b>[[package]]</b> - {Lang::T('will be replaced with Package name')}.<br>
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="./docs/#Reminder%20with%20payment%20link" target="_blank">read documentation</a>.
</p>
</div>
</div>
@ -58,7 +61,8 @@
<b>[[package]]</b> - {Lang::T('will be replaced with Package name')}.<br>
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="./docs/#Reminder%20with%20payment%20link" target="_blank">read documentation</a>.
</p>
</div>
</div>
@ -75,7 +79,8 @@
<b>[[package]]</b> - {Lang::T('will be replaced with Package name')}.<br>
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="./docs/#Reminder%20with%20payment%20link" target="_blank">read documentation</a>.
</p>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
<button class="btn btn-success" onclick="return ask(this, 'Continue the balance top-up process?')" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>

View File

@ -1,59 +1,68 @@
{include file="sections/header.tpl"}
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('Edit Service Plan')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/balance-edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Status')}</label>
<div class="col-md-10">
<label class="radio-inline warning">
<input type="radio" checked name="enabled" value="1"> {Lang::T('Enable')}
</label>
<label class="radio-inline">
<input type="radio" name="enabled" value="0"> {Lang::T('Disable')}
</label>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('Edit Service Package')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/balance-edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Status')}</label>
<div class="col-md-10">
<label class="radio-inline warning">
<input type="radio" checked name="enabled" value="1"> {Lang::T('Enable')}
</label>
<label class="radio-inline">
<input type="radio" name="enabled" value="0"> {Lang::T('Disable')}
</label>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Package Name')}</label>
<div class="col-md-6">
<input type="text" required class="form-control" id="name" value="{$d['name_plan']}"
name="name" maxlength="40" placeholder="{Lang::T('Topup')} 100">
</div>
</div>
<div class="form-group has-success">
<label class="col-md-2 control-label">{Lang::T('Package Price')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-addon">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" value="{$d['price']}" required>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-6">
<input type="text" required class="form-control" id="name" value="{$d['name_plan']}" name="name" maxlength="40" placeholder="Topup 100">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-addon">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" value="{$d['price']}" required>
</div>
</div>
{if $_c['enable_tax'] == 'yes'}
{if $_c['tax_rate'] == 'custom'}
</div>
{if $_c['enable_tax'] == 'yes'}
{if $_c['tax_rate'] == 'custom'}
<p class="help-block col-md-4">{number_format($_c['custom_tax_rate'], 2)} % {Lang::T('Tax Rates
will be added')}</p>
{else}
{else}
<p class="help-block col-md-4">{number_format($_c['tax_rate'] * 100, 2)} % {Lang::T('Tax Rates
will be added')}</p>
{/if}
{/if}
{/if}
{/if}
</div>
<div class="form-group has-warning">
<label class="col-md-2 control-label">{Lang::T('Price Before Discount')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-addon">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price_old" required value="{$d['price_old']}">
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<p class="help-block">{Lang::T('For Discount Rate, this is price before get discount, must be more expensive with real price')}</p>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" onclick="return ask(this, 'Continue the process of changing the balance contents?')" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

View File

@ -3,7 +3,7 @@
<div class="row">
<div class="col-sm-12">
<div class="panel panel-hovered mb20 panel-primary">
<div class="panel-heading">{Lang::T('Balance Plans')}</div>
<div class="panel-heading">{Lang::T('Balance Package')}</div>
<div class="panel-body">
<div class="md-whiteframe-z1 mb20 text-center" style="padding: 15px">
<div class="col-md-8">
@ -20,15 +20,15 @@
</form>
</div>
<div class="col-md-4">
<a href="{$_url}services/balance-add" class="btn btn-primary btn-block"><i class="ion ion-android-add"> </i> {Lang::T('New Service Plan')}</a>
<a href="{$_url}services/balance-add" class="btn btn-primary btn-block"><i class="ion ion-android-add"> </i> {Lang::T('New Service Package')}</a>
</div>&nbsp;
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Package Name')}</th>
<th>{Lang::T('Package Price')}</th>
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
@ -36,10 +36,12 @@
{foreach $d as $ds}
<tr {if $ds['enabled'] != 1}class="danger" title="disabled"{/if}>
<td>{$ds['name_plan']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td>
<td>{Lang::moneyFormat($ds['price'])}{if !empty($ds['price_old'])}
<sup style="text-decoration: line-through; color: red">{Lang::moneyFormat($ds['price_old'])}</sup>
{/if}</td>
<td>
<a href="{$_url}services/balance-edit/{$ds['id']}" class="btn btn-info btn-xs">{Lang::T('Edit')}</a>
<a href="{$_url}services/balance-delete/{$ds['id']}" onclick="return confirm('{Lang::T('Delete')}?')" id="{$ds['id']}" class="btn btn-danger btn-xs"><i class="glyphicon glyphicon-trash"></i></a>
<a href="{$_url}services/balance-delete/{$ds['id']}" onclick="return ask(this, '{Lang::T('Delete')}?')" id="{$ds['id']}" class="btn btn-danger btn-xs"><i class="glyphicon glyphicon-trash"></i></a>
</td>
</tr>
{/foreach}

View File

@ -1,24 +1,23 @@
{include file="sections/header.tpl"}
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="col-sm-6">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('Add New Bandwidth')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}bandwidth/add-post">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-6">
<label class="col-md-3 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-9">
<input type="text" class="form-control" id="name" name="name">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-4">
<label class="col-md-3 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="rate_down" name="rate_down">
</div>
<div class="col-md-2">
<div class="col-md-3">
<select class="form-control" id="rate_down_unit" name="rate_down_unit">
<option value="Kbps">Kbps</option>
<option value="Mbps">Mbps</option>
@ -26,59 +25,107 @@
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-4">
<label class="col-md-3 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="rate_up" name="rate_up">
</div>
<div class="col-md-2">
<div class="col-md-3">
<select class="form-control" id="rate_up_unit" name="rate_up_unit">
<option value="Kbps">Kbps</option>
<option value="Mbps">Mbps</option>
</select>
</div>
</div>
<div class="panel-heading">{Lang::T('Optional')}</div>
<div class="form-group">
<label class="col-md-2 control-label">Burst Limit</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Limit]">
<label class="col-md-3 control-label">Burst Limit</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_limit" name="burst[]" placeholder="[Burst/Limit]">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Burst Threshold</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Threshold]">
<label class="col-md-3 control-label">Burst Threshold</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_threshold" name="burst[]" placeholder="[Burst/Threshold]">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Burst Time</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Time]">
<label class="col-md-3 control-label">Burst Time</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_time" name="burst[]" placeholder="[Burst/Time]">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Priority</label>
<div class="col-md-6">
<input type="number" class="form-control" name="burst[]" placeholder="[Priority]">
<label class="col-md-3 control-label">Priority</label>
<div class="col-md-9">
<input type="number" class="form-control" id="burst_priority" name="burst[]" placeholder="[Priority]">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Limit At</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Limit/At]">
<label class="col-md-3 control-label">Limit At</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_limit_at" name="burst[]" placeholder="[Limit/At]">
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Submit')}</button>
<button class="btn btn-primary" onclick="return ask(this, 'Continue the Bandwidth addition process?')" type="submit">{Lang::T('Save')}</button>
Or <a href="{$_url}bandwidth/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="panel panel-default">
<div class="panel-heading">{Lang::T('Burst Limit Preset')}</div>
<div class="list-group">
<a href="#" class="list-group-item active">2x</a>
<a href="javascript:burstIt('2M/2M 4M/4M 1536K/1536K 16/16 8 1M/1M')" class="list-group-item">2M to
4M</a>
<a href="javascript:burstIt('3M/3M 6M/6M 2304K/2304K 16/16 8 1536K/1536K')" class="list-group-item">3M
to 6M</a>
<a href="javascript:burstIt('4M/4M 8M/8M 3M/3M 16/16 8 2M/2M')" class="list-group-item">4M to 8M</a>
<a href="javascript:burstIt('5M/5M 10M/10M 3840k/3840k 16/16 8 2560k/2560k')" class="list-group-item">5M to
10M</a>
<a href="javascript:burstIt('6M/6M 12M/12M 4608k/4608k 16/16 8 3M/3M')" class="list-group-item">6M to 12M</a>
<a href="javascript:burstIt('7M/7M 14M/14M 5376k/5376k 16/16 8 3584k/3584k')" class="list-group-item">7M to
14M</a>
<a href="javascript:burstIt('8M/8M 16M/16M 6M/6M 16/16 8 4M/4M')" class="list-group-item">8M to 16M</a>
<a href="javascript:burstIt('9M/9M 18M/18M 6912k/6912k 16/16 8 4608k/4608k')" class="list-group-item">9M to
18M</a>
<a href="javascript:burstIt('10M/10M 20M/20M 7680k/7680k 16/16 8 5M/5M')" class="list-group-item">10M to 20M</a>
<a href="#" class="list-group-item active">upto 1MB</a>
<a href="javascript:burstIt('1M/1M 2M/2M 768k/768k 16/16 8 512k/512k')" class="list-group-item">1M upto 2M</a>
<a href="javascript:burstIt('2M/2M 3M/3M 1536k/1536k 12/12 8 1024k/1024k')" class="list-group-item">2M upto 3M</a>
<a href="javascript:burstIt('3M/3M 4M/4M 2450k/2450k 10/10 8 1536k/1536k')" class="list-group-item">3M upto 4M</a>
<a href="javascript:burstIt('4M/4M 5M/5M 3M/3M 10/10 8 2M/2M')" class="list-group-item">4M upto 5M</a>
<a href="javascript:burstIt('5M/5M 6M/6M 3680k/3680k 10/10 8 2450k/2450k')" class="list-group-item">5M upto 6M</a>
<a href="javascript:burstIt('6M/6M 7M/7M 4560k/4560k 11/11 8 3M/3M')" class="list-group-item">6M upto 7M</a>
<a href="javascript:burstIt('7M/7M 8M/8M 5460k/5460k 12/12 8 3640k/3640k')" class="list-group-item">7M upto 8M</a>
<a href="javascript:burstIt('8M/8M 9M/9M 6M/6M 12/12 8 4M/4M')" class="list-group-item">8M upto 9M</a>
<a href="javascript:burstIt('9M/9M 10M/10M 6820k/6820k 12/12 8 4550k/4550k')" class="list-group-item">9M upto 10M</a>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}
<script>
function burstIt(value) {
var b = value.split(" ");
$("#burst_limit").val(b[1]);
$("#burst_threshold").val(b[2]);
$("#burst_time").val(b[3]);
$("#burst_priority").val(b[4]);
$("#burst_limit_at").val(b[5]);
var a = b[0].split("/");
$("#rate_down").val(a[0].replace('M',''));
$("#rate_up").val(a[1].replace('M',''));
$('#rate_down_unit').val('Mbps');
$('#rate_up_unit').val('Mbps');
window.scrollTo(0, 0);
}
</script>
{include file="sections/footer.tpl"}

View File

@ -1,7 +1,7 @@
{include file="sections/header.tpl"}
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="col-sm-8">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('Edit Bandwidth')}</div>
<div class="panel-body">
@ -9,18 +9,18 @@
<form class="form-horizontal" method="post" role="form" action="{$_url}bandwidth/edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-6">
<label class="col-md-3 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-9">
<input type="text" class="form-control" id="name" name="name" value="{$d['name_bw']}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-4">
<label class="col-md-3 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="rate_down" name="rate_down"
value="{$d['rate_down']}">
</div>
<div class="col-md-2">
<div class="col-md-3">
<select class="form-control" id="rate_down_unit" name="rate_down_unit">
<option value="Kbps" {if $d['rate_down_unit'] eq 'Kbps'}selected="selected" {/if}>Kbps
</option>
@ -30,11 +30,11 @@
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-4">
<label class="col-md-3 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="rate_up" name="rate_up" value="{$d['rate_up']}">
</div>
<div class="col-md-2">
<div class="col-md-3">
<select class="form-control" id="rate_up_unit" name="rate_up_unit">
<option value="Kbps" {if $d['rate_up_unit'] eq 'Kbps'}selected="selected" {/if}>Kbps
</option>
@ -43,53 +43,106 @@
</select>
</div>
</div>
<div class="panel-heading">{Lang::T('Optional')}</div>
<div class="form-group">
<label class="col-md-2 control-label">Burst Limit</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Limit]" value="{$burst[0]}">
<label class="col-md-3 control-label">Burst Limit</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_limit" name="burst[]"
placeholder="[Burst/Limit]" value="{$burst[0]}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Burst Threshold</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Threshold]" value="{$burst[1]}">
<label class="col-md-3 control-label">Burst Threshold</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_threshold" name="burst[]"
placeholder="[Burst/Threshold]" value="{$burst[1]}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Burst Time</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Time]" value="{$burst[2]}">
<label class="col-md-3 control-label">Burst Time</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_time" name="burst[]"
placeholder="[Burst/Time]" value="{$burst[2]}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Priority</label>
<div class="col-md-6">
<input type="number" class="form-control" name="burst[]" placeholder="[Priority]" value="{$burst[3]}">
<label class="col-md-3 control-label">Priority</label>
<div class="col-md-9">
<input type="number" class="form-control" id="burst_priority" name="burst[]"
placeholder="[Priority]" value="{$burst[3]}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Limit At</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Limit/At]" value="{$burst[4]}">
<label class="col-md-3 control-label">Limit At</label>
<div class="col-md-9">
<input type="text" class="form-control" id="burst_limit_at" name="burst[]" placeholder="[Limit/At]"
value="{$burst[4]}">
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<small>{Lang::T('Editing Bandwidth will not automatically update the plan, you need to edit the plan then save again')}</small>
<small>{Lang::T('Editing Bandwidth will not automatically update the plan, you need to edit the plan then save again')}</small>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Submit')}</button>
<button class="btn btn-primary" onclick="return ask(this, 'Continue the Bandwidth change process?')" type="submit">{Lang::T('Save Change')}</button>
Or <a href="{$_url}bandwidth/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="panel panel-default">
<div class="panel-heading">{Lang::T('Burst Limit Preset')}</div>
<div class="list-group">
<a href="#" class="list-group-item active">2x</a>
<a href="javascript:burstIt('2M/2M 4M/4M 1536K/1536K 16/16 8 1M/1M')" class="list-group-item">2M to
4M</a>
<a href="javascript:burstIt('3M/3M 6M/6M 2304K/2304K 16/16 8 1536K/1536K')" class="list-group-item">3M
to 6M</a>
<a href="javascript:burstIt('4M/4M 8M/8M 3M/3M 16/16 8 2M/2M')" class="list-group-item">4M to 8M</a>
<a href="javascript:burstIt('5M/5M 10M/10M 3840k/3840k 16/16 8 2560k/2560k')" class="list-group-item">5M to
10M</a>
<a href="javascript:burstIt('6M/6M 12M/12M 4608k/4608k 16/16 8 3M/3M')" class="list-group-item">6M to 12M</a>
<a href="javascript:burstIt('7M/7M 14M/14M 5376k/5376k 16/16 8 3584k/3584k')" class="list-group-item">7M to
14M</a>
<a href="javascript:burstIt('8M/8M 16M/16M 6M/6M 16/16 8 4M/4M')" class="list-group-item">8M to 16M</a>
<a href="javascript:burstIt('9M/9M 18M/18M 6912k/6912k 16/16 8 4608k/4608k')" class="list-group-item">9M to
18M</a>
<a href="javascript:burstIt('10M/10M 20M/20M 7680k/7680k 16/16 8 5M/5M')" class="list-group-item">10M to 20M</a>
<a href="#" class="list-group-item active">upto 1MB</a>
<a href="javascript:burstIt('1M/1M 2M/2M 768k/768k 16/16 8 512k/512k')" class="list-group-item">1M upto 2M</a>
<a href="javascript:burstIt('2M/2M 3M/3M 1536k/1536k 12/12 8 1024k/1024k')" class="list-group-item">2M upto 3M</a>
<a href="javascript:burstIt('3M/3M 4M/4M 2450k/2450k 10/10 8 1536k/1536k')" class="list-group-item">3M upto 4M</a>
<a href="javascript:burstIt('4M/4M 5M/5M 3M/3M 10/10 8 2M/2M')" class="list-group-item">4M upto 5M</a>
<a href="javascript:burstIt('5M/5M 6M/6M 3680k/3680k 10/10 8 2450k/2450k')" class="list-group-item">5M upto 6M</a>
<a href="javascript:burstIt('6M/6M 7M/7M 4560k/4560k 11/11 8 3M/3M')" class="list-group-item">6M upto 7M</a>
<a href="javascript:burstIt('7M/7M 8M/8M 5460k/5460k 12/12 8 3640k/3640k')" class="list-group-item">7M upto 8M</a>
<a href="javascript:burstIt('8M/8M 9M/9M 6M/6M 12/12 8 4M/4M')" class="list-group-item">8M upto 9M</a>
<a href="javascript:burstIt('9M/9M 10M/10M 6820k/6820k 12/12 8 4550k/4550k')" class="list-group-item">9M upto 10M</a>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}
<script>
function burstIt(value) {
var b = value.split(" ");
$("#burst_limit").val(b[1]);
$("#burst_threshold").val(b[2]);
$("#burst_time").val(b[3]);
$("#burst_priority").val(b[4]);
$("#burst_limit_at").val(b[5]);
var a = b[0].split("/");
$("#rate_down").val(a[0].replace('M',''));
$("#rate_up").val(a[1].replace('M',''));
$('#rate_down_unit').val('Mbps');
$('#rate_up_unit').val('Mbps');
window.scrollTo(0, 0);
}
</script>
{include file="sections/footer.tpl"}

View File

@ -47,7 +47,7 @@
class="btn btn-sm btn-warning">{Lang::T('Edit')}</a>
<a href="{$_url}bandwidth/delete/{$ds['id']}" id="{$ds['id']}"
class="btn btn-danger btn-sm"
onclick="return confirm('{Lang::T('Delete')}?')"><i
onclick="return ask(this, '{Lang::T('Delete')}?')"><i
class="glyphicon glyphicon-trash"></i></a>
</td>
</tr>
@ -57,8 +57,8 @@
</div>
{include file="pagination.tpl"}
<div class="bs-callout bs-callout-info" id="callout-navbar-role">
<h4>{Lang::T('Create Bandwidth Plan for expired Internet Plan')}</h4>
<p>{Lang::T('When customer expired, you can move it to Expired Internet Plan')}</p>
<h4>{Lang::T('Create Bandwidth Package for expired Internet Package')}</h4>
<p>{Lang::T('When customer expired, you can move it to Expired Internet Package')}</p>
</div>
</div>
</div>

View File

@ -6,6 +6,7 @@
<div class="panel-heading">{Lang::T('Change Password')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/change-password-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Current Password')}</label>
<div class="col-md-6">

153
ui/ui/coupons-add.tpl Normal file
View File

@ -0,0 +1,153 @@
{include file="sections/header.tpl"}
<!-- coupon-add -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('Add Coupon')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}coupons/add-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<!-- Coupon Code -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Coupon Code')}</label>
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" name="code" id="code" maxlength="50" required>
<span class="input-group-btn">
<button type="button" class="btn btn-info btn-flat" onclick="generateRandomCode()">{Lang::T('Random')}</button>
</span>
</div>
<p class="help-block"><small>{Lang::T('Unique code for the coupon')}</small></p>
</div>
</div>
<!-- Type -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Type')}</label>
<div class="col-md-6">
<select class="form-control" name="type" id="type" required onchange="updateValueInput()">
<option value="fixed">{Lang::T('Fixed Discount')}</option>
<option value="percent">{Lang::T('Percent Discount')}</option>
</select>
</div>
</div>
<!-- Value -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Discount Value')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="value" id="value" step="0.01" placeholder="Enter amount" required>
<p class="help-block"><small id="value-help">{Lang::T('Value of the discount (amount or percentage)')}</small></p>
</div>
</div>
<!-- Description -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Description')}</label>
<div class="col-md-6">
<textarea class="form-control" name="description" required></textarea>
<p class="help-block"><small>{Lang::T('Brief explanation of the coupon')}</small></p>
</div>
</div>
<!-- Max Usage -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Max Usage')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="max_usage" value="0" required placeholder="0 is Unlimited">
<p class="help-block"><small>{Lang::T('Maximum number of times this coupon can be used 0 is Unlimited')}</small></p>
</div>
</div>
<!-- Minimum Order Amount -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Minimum Order Amount')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="min_order_amount" step="0.01" required>
<p class="help-block"><small>{Lang::T('Minimum cart total required to use this coupon')}</small></p>
</div>
</div>
<!-- Max Discount Amount -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Max Discount Amount')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="max_discount_amount" step="0.01">
<p class="help-block"><small>{Lang::T('Maximum discount amount applicable (for percent type)')}</small></p>
</div>
</div>
<!-- Status -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Status')}</label>
<div class="col-md-6">
<label class="radio-inline">
<input type="radio" name="status" value="active" checked> {Lang::T('Active')}
</label>
<label class="radio-inline">
<input type="radio" name="status" value="inactive"> {Lang::T('Inactive')}
</label>
</div>
</div>
<!-- Start Date -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Start Date')}</label>
<div class="col-md-6">
<input type="date" class="form-control" name="start_date" required>
</div>
</div>
<!-- Expiry Date -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('End Date')}</label>
<div class="col-md-6">
<input type="date" class="form-control" name="end_date" required>
</div>
</div>
<!-- Submit Button -->
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary" type="submit">
{Lang::T('Save')}
</button>
Or <a href="{$_url}coupons/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
function updateValueInput() {
const type = document.getElementById('type').value;
const valueInput = document.getElementById('value');
const helpText = document.getElementById('value-help');
if (type === 'percent') {
valueInput.setAttribute('max', '100');
valueInput.setAttribute('placeholder', 'Enter percentage');
helpText.textContent = '{Lang::T('Value of the discount (percentage, max 100)')}';
} else {
valueInput.removeAttribute('max');
valueInput.setAttribute('placeholder', 'Enter amount');
helpText.textContent = '{Lang::T('Value of the discount (amount)')}';
}
}
function generateRandomCode() {
const codeInput = document.getElementById('code');
const randomCode = Math.random().toString(36).substring(2, 10).toUpperCase();
codeInput.value = randomCode;
}
</script>
{include file="sections/footer.tpl"}

149
ui/ui/coupons-edit.tpl Normal file
View File

@ -0,0 +1,149 @@
{include file="sections/header.tpl"}
<!-- Coupon-edit -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('Edit Coupon')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}coupons/edit-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<!-- Coupon Code -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Coupon Code')}</label>
<div class="col-md-6">
<input type="text" class="form-control" name="code" id="code" maxlength="50" readonly
value="{$coupon['code']}">
<p class="help-block"><small>{Lang::T('Unique code for the coupon')}</small></p>
</div>
</div>
<!-- Type -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Type')}</label>
<div class="col-md-6">
<select class="form-control" name="type" id="type" required onchange="updateValueInput()">
<option value="fixed" {if $coupon['type']== 'fixed'} selected {/if}>{Lang::T('Fixed Discount')}</option>
<option value="percent" {if $coupon['type']== 'percent'} selected {/if}>{Lang::T('Percent Discount')}</option>
</select>
</div>
</div>
<!-- Value -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Discount Value')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="value" id="value" step="0.01"
placeholder="Enter amount" required value="{$coupon['value']}">
<p class="help-block"><small id="value-help">{Lang::T('Value of the discount (amount or
percentage)')}</small></p>
</div>
</div>
<!-- Description -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Description')}</label>
<div class="col-md-6">
<textarea class="form-control" name="description" required>{$coupon['description']}</textarea>
<p class="help-block"><small>{Lang::T('Brief explanation of the coupon')}</small></p>
</div>
</div>
<!-- Max Usage -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Max Usage')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="max_usage" required value="{$coupon['max_usage']}">
<p class="help-block"><small>{Lang::T('Maximum number of times this coupon can be used 0 is Unlimited')}</small></p>
</div>
</div>
<!-- Minimum Order Amount -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Minimum Order Amount')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="min_order_amount" step="0.01" required value="{$coupon['min_order_amount']}">
<p class="help-block"><small>{Lang::T('Minimum cart total required to use this
coupon')}</small></p>
</div>
</div>
<!-- Max Discount Amount -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Max Discount Amount')}</label>
<div class="col-md-6">
<input type="number" class="form-control" name="max_discount_amount" step="0.01" value="{$coupon['max_discount_amount']}">
<p class="help-block"><small>{Lang::T('Maximum discount amount applicable (for percent
type)')}</small></p>
</div>
</div>
<!-- Status -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Status')}</label>
<div class="col-md-6">
<label class="radio-inline">
<input type="radio" name="status" value="active" {if $coupon['status']== 'active'} checked {/if}> {Lang::T('Active')}
</label>
<label class="radio-inline">
<input type="radio" name="status" value="inactive" {if $coupon['status']== 'inactive'} checked {/if}> {Lang::T('Inactive')}
</label>
</div>
</div>
<!-- Start Date -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Start Date')}</label>
<div class="col-md-6">
<input type="date" class="form-control" name="start_date" required value="{$coupon['start_date']}">
</div>
</div>
<!-- Expiry Date -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('End Date')}</label>
<div class="col-md-6">
<input type="date" class="form-control" name="end_date" required value="{$coupon['end_date']}">
</div>
</div>
<!-- Submit Button -->
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary" type="submit">
{Lang::T('Save')}
</button>
Or <a href="{$_url}coupons/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
function updateValueInput() {
const type = document.getElementById('type').value;
const valueInput = document.getElementById('value');
const helpText = document.getElementById('value-help');
if (type === 'percent') {
valueInput.setAttribute('max', '100');
valueInput.setAttribute('placeholder', 'Enter percentage');
helpText.textContent = '{Lang::T('Value of the discount(percentage, max 100)')}';
} else {
valueInput.removeAttribute('max');
valueInput.setAttribute('placeholder', 'Enter amount');
helpText.textContent = '{Lang::T('Value of the discount(amount)')}';
}
}
</script>
{include file="sections/footer.tpl"}

363
ui/ui/coupons.tpl Normal file
View File

@ -0,0 +1,363 @@
{include file="sections/header.tpl"}
<style>
/* Styles for overall layout and responsiveness */
body {
background-color: #f8f9fa;
font-family: 'Arial', sans-serif;
padding: 0;
margin: 0;
}
.container {
margin-top: 20px;
background-color: #d8dfe5;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 98%;
overflow-x: auto;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
/* Styles for table and pagination */
.table {
width: 100%;
margin-bottom: 1rem;
background-color: #fff;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.table th {
vertical-align: middle;
border-color: #dee2e6;
background-color: #343a40;
color: #fff;
}
.table td {
vertical-align: middle;
border-color: #dee2e6;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, 0.05);
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.075);
color: #333;
font-weight: bold;
transition: background-color 0.3s, color 0.3s;
}
.pagination .page-item .page-link {
color: #007bff;
background-color: #fff;
border: 1px solid #dee2e6;
margin: 0 2px;
padding: 6px 12px;
transition: background-color 0.3s, color 0.3s;
}
.pagination .page-item .page-link:hover {
background-color: #e9ecef;
color: #0056b3;
}
.pagination .page-item.active .page-link {
z-index: 1;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
display: inline-block;
padding: 5px 10px;
margin-right: 5px;
border: 1px solid #ccc;
background-color: #fff;
color: #333;
cursor: pointer;
}
.hidden-field {
display: none;
}
</style>
<style>
.btn-group-flex {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
}
.btn-group-flex .btn {
flex: 1 1 auto;
/* Allow buttons to shrink/grow as needed */
max-width: 150px;
/* Optional: Limit button width */
}
</style>
<form id="" method="post" action="">
<div class="input-group">
<div class="input-group-addon">
<a href=""><span class="fa fa-refresh"></span></a>
</div>
<input type="text" name="search" class="form-control" value="{$search}" placeholder="{Lang::T('Search')}...">
<div class="input-group-btn">
<button class="btn btn-success" type="submit">{Lang::T('Search Coupons')}</button>
</div>
</div>
</form>
<br>
<!-- coupon -->
<div class="row" style="padding: 5px">
<div class="col-lg-3 col-lg-offset-9">
<div class="btn-group btn-group-justified" role="group">
<div class="btn-group" role="group">
<a href="{$_url}coupons/add" class="btn btn-primary">
{Lang::T('Add Coupon')}</a>
</div>
</div>
</div>
</div>
<div class="panel panel-hovered mb20 panel-primary">
<div class="panel-heading">
&nbsp;
</div>
<div class="container">
<table id="datatable" class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>{Lang::T('Code')}</th>
<th>{Lang::T('Type')}</th>
<th>{Lang::T('Value')}</th>
<th>{Lang::T('Description')}</th>
<th>{Lang::T('Max Usage')}</th>
<th>{Lang::T('Usage Count')}</th>
<th>{Lang::T('Status')}</th>
<th>{Lang::T('Min Order')}</th>
<th>{Lang::T('Max Discount')}</th>
<th>{Lang::T('Start Date')}</th>
<th>{Lang::T('End Date')}</th>
<th>{Lang::T('Created Date')}</th>
<th>{Lang::T('Updated Date')}</th>
<th>{Lang::T('Action')}</th>
</tr>
</thead>
<tbody>
{if $coupons}
{foreach $coupons as $coupon}
<tr>
<td><input type="checkbox" name="coupon_ids[]" value="{$coupon['id']}"></td>
<td style="background-color: black; color: black;"
onmouseleave="this.style.backgroundColor = 'black';"
onmouseenter="this.style.backgroundColor = 'white';">
{$coupon['code']}
</td>
<td>{$coupon['type']}</td>
<td>{$coupon['value']}</td>
<td>{$coupon['description']}</td>
<td>{$coupon['max_usage']}</td>
<td>{$coupon['usage_count']}</td>
<td>
{if $coupon['status'] == 'inactive'}
<span class="label label-danger">{Lang::T('Inactive')}</span>
{elseif $coupon['status'] == 'active'}
<span class="label label-success">{Lang::T('Active')}</span>
{else}
<span class="label label-primary">{Lang::T('Unknown')}</span>
{/if}
</td>
<td>{$coupon['min_order_amount']}</td>
<td>{$coupon['max_discount_amount']}</td>
<td>{$coupon['start_date']}</td>
<td>{$coupon['end_date']}</td>
<td>{$coupon['created_at']}</td>
<td>{$coupon['updated_at']}</td>
<!-- <td>{if $coupon['admin_name']}
<a href="{$_url}settings/users-view/{$coupon['generated_by']}">{$coupon['admin_name']}</a>
{else} -
{/if}
</td> -->
<td colspan="10" style="text-align: center;">
<div style="display: flex; justify-content: center; gap: 10px; flex-wrap: wrap;">
<a href="{$_url}coupons/edit/{$coupon['id']}&token={$csrf_token}" id="{$coupon['id']}"
class="btn btn-success btn-xs">{Lang::T('Edit')}</a>
{if $coupon['status'] neq 'inactive'}
<a href="javascript:void(0);"
onclick="confirmAction('{$_url}coupons/status/&coupon_id={$coupon['id']}&status=inactive&csrf_token={$csrf_token}', '{Lang::T('Block')}')"
id="{$coupon['id']}" class="btn btn-danger btn-xs">
{Lang::T('Block')}
</a>
{else}
<a href="javascript:void(0);"
onclick="confirmAction('{$_url}coupons/status/&coupon_id={$coupon['id']}&status=active&csrf_token={$csrf_token}', '{Lang::T('Unblock')}')"
id="{$coupon['id']}" class="btn btn-warning btn-xs">
{Lang::T('Unblock')}
</a>
{/if}
</div>
</td>
</tr>
{/foreach}
{else}
<tr>
<td colspan="11" style="text-align: center;">
{Lang::T('No coupons found.')}
</td>
</tr>
{/if}
</tbody>
</table>
{include file="pagination.tpl"}
<div class="row" style="padding: 5px">
<div class="col-lg-3 col-lg-offset-9">
<div class="btn-group btn-group-justified" role="group">
<div class="btn-group" role="group">
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<button id="deleteSelectedCoupons" class="btn btn-danger">{Lang::T('Delete
Selected')}</button>
{/if}
</div>
</div>
</div>
</div>
</div>
&nbsp;
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function deleteCoupons(couponIds) {
if (couponIds.length > 0) {
Swal.fire({
title: 'Are you sure?',
text: 'You won\'t be able to revert this!',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it!',
cancelButtonText: 'Cancel'
}).then((result) => {
if (result.isConfirmed) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '{$_url}coupons/delete', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
if (xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
if (response.status === 'success') {
Swal.fire({
title: 'Deleted!',
text: response.message,
icon: 'success',
confirmButtonText: 'OK'
}).then(() => {
location.reload(); // Reload the page after confirmation
});
} else {
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
confirmButtonText: 'OK'
});
}
} else {
Swal.fire({
title: 'Error!',
text: 'Failed to delete coupons. Please try again.',
icon: 'error',
confirmButtonText: 'OK'
});
}
};
xhr.send('couponIds=' + JSON.stringify(couponIds));
}
});
} else {
Swal.fire({
title: 'Error!',
text: 'No coupons selected to delete.',
icon: 'error',
confirmButtonText: 'OK'
});
}
}
// Example usage for selected coupons
document.getElementById('deleteSelectedCoupons').addEventListener('click', function () {
var selectedCoupons = [];
document.querySelectorAll('input[name="coupon_ids[]"]:checked').forEach(function (checkbox) {
selectedCoupons.push(checkbox.value);
});
if (selectedCoupons.length > 0) {
deleteCoupons(selectedCoupons);
} else {
Swal.fire({
title: 'Error!',
text: 'Please select at least one coupon to delete.',
icon: 'error',
confirmButtonText: 'OK'
});
}
});
// Example usage for single coupon deletion
document.querySelectorAll('.delete-coupon').forEach(function (button) {
button.addEventListener('click', function () {
var couponId = this.getAttribute('data-id');
deleteCoupons([couponId]);
});
});
// Select or deselect all checkboxes
document.getElementById('select-all').addEventListener('change', function () {
var checkboxes = document.querySelectorAll('input[name="coupon_ids[]"]');
for (var checkbox of checkboxes) {
checkbox.checked = this.checked;
}
});
</script>
{literal}
<script>
function confirmAction(url, action) {
Swal.fire({
title: 'Are you sure?',
text: `Do you really want to ${action.toLowerCase()} this coupon?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, proceed!',
cancelButtonText: 'No, cancel!'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = url;
}
});
}
</script>
{/literal}
<script>
const $j = jQuery.noConflict();
$j(document).ready(function () {
$j('#datatable').DataTable({
"pagingType": "full_numbers",
"order": [
[1, 'desc']
]
});
});
</script>
{include file="sections/footer.tpl"}

View File

@ -1,4 +1,5 @@
{include file="user-ui/header.tpl"}
{include file="customer/header-public.tpl"}
<div class="hidden-xs" style="height:100px"></div>
<div class="page page-err clearfix">
<div class="err-container">
@ -6,5 +7,4 @@
<p class="text-desc mb20">{Lang::T('Coming Soon! Next Version...')}</p>
</div>
</div>
{include file="user-ui/footer.tpl"}
{include file="customer/footer-public.tpl"}

View File

@ -1,18 +1,18 @@
{include file="user-ui/header.tpl"}
{include file="customer/header.tpl"}
<!-- user-activation-list -->
<div class="row">
<div class="col-sm-12">
<div class="panel mb20 panel-hovered panel-primary">
<div class="panel-heading">{Lang::T('List Activated Voucher')}</div>
<div class="panel-heading">{Lang::T('Transaction History List')}</div>
<div class="panel-body">
<div class="table-responsive">
<table id="datatable" class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Invoice')}</th>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Package Name')}</th>
<th>{Lang::T('Package Price')}</th>
<th>{Lang::T('Type')}</th>
<th>{Lang::T('Created On')}</th>
<th>{Lang::T('Expires On')}</th>
@ -41,4 +41,4 @@
</div>
{include file="user-ui/footer.tpl"}
{include file="customer/footer.tpl"}

View File

@ -1,4 +1,4 @@
{include file="user-ui/header.tpl"}
{include file="customer/header.tpl"}
<!-- user-activation -->
<div class="row">
@ -38,4 +38,4 @@
</div>
</div>
{include file="user-ui/footer.tpl"}
{include file="customer/footer.tpl"}

View File

@ -1,4 +1,4 @@
{include file="user-ui/header.tpl"}
{include file="customer/header.tpl"}
<!-- user-change-password -->
<div class="row">
@ -7,6 +7,7 @@
<div class="panel-heading">{Lang::T('Change Password')}</div>
<div class="panel-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}accounts/change-password-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Current Password')}</label>
<div class="col-md-6">
@ -40,4 +41,4 @@
</div>
</div>
{include file="user-ui/footer.tpl"}
{include file="customer/footer.tpl"}

View File

@ -0,0 +1,40 @@
{if $register}
<div class="form-group">
<label>{ucwords(str_replace('_',' ', $field['name']))}</label>
{if $field['type'] == 'option'}
<select class="form-control" {if $field['required'] == 1} required{/if} name="{$field['name']}" style="width: 100%">
{assign var="opts" value=explode(',', $field['value'])}
{foreach $opts as $opt}
<option value="{$opt}">{$opt}</option>
{/foreach}
</select>
{elseif $field['image'] == 'image'}
<input type="file" class="form-control" {if $field['required'] == 1} required{/if} name="{$field['name']}"
style="width: 100%" placeholder="{$field['placeholder']}" accept="image/*">
{else}
<input type="{$field['type']}" class="form-control" {if $field['required'] == 1} required{/if}
name="{$field['name']}" value="{$field['value']}" style="width: 100%" placeholder="{$field['placeholder']}">
{/if}
</div>
{else}
<div class="form-group">
<label class="col-md-3 control-label">{ucwords(str_replace('_',' ', $field['name']))}</label>
<div class="col-md-9">
{if $field['type'] == 'option'}
<select class="form-control" {if $field['required'] == 1} required{/if} name="{$field['name']}"
style="width: 100%">
{assign var="opts" value=explode(',', $field['value'])}
{foreach $opts as $opt}
<option value="{$opt}" {if $attrs[$field['name']] == $opt}selected{/if}>{$opt}</option>
{/foreach}
</select>
{elseif $field['image'] == 'image'}
<input type="file" class="form-control" {if $field['required'] == 1} required{/if} name="{$field['name']}"
style="width: 100%" placeholder="{$field['placeholder']}" accept="image/*">
{else}
<input type="{$field['type']}" class="form-control" {if $field['required'] == 1} required{/if}
name="{$field['name']}" value="{$attrs[$field['name']]}" style="width: 100%" placeholder="{$field['placeholder']}">
{/if}
</div>
</div>
{/if}

View File

@ -0,0 +1,446 @@
{include file="customer/header.tpl"}
<!-- user-dashboard -->
<div class="row">
<div class="col col-md-6 col-md-push-6">
{if $unpaid }
<div class="box box-danger box-solid">
<div class="box-header">
<h3 class="box-title">{Lang::T('Unpaid Order')}</h3>
</div>
<div style="margin-left: 5px; margin-right: 5px;">
<table class="table table-condensed table-bordered table-striped table-hover"
style="margin-bottom: 0px;">
<tbody>
<tr>
<td>{Lang::T('expired')}</td>
<td>{Lang::dateTimeFormat($unpaid['expired_date'])} </td>
</tr>
<tr>
<td>{Lang::T('Package Name')}</td>
<td>{$unpaid['plan_name']}</td>
</tr>
<tr>
<td>{Lang::T('Package Price')}</td>
<td>{$unpaid['price']}</td>
</tr>
<tr>
<td>{Lang::T('Routers')}</td>
<td>{$unpaid['routers']}</td>
</tr>
</tbody>
</table> &nbsp;
</div>
<div class="box-footer p-2">
<div class="btn-group btn-group-justified mb15">
<div class="btn-group">
<a href="{$_url}order/view/{$unpaid['id']}/cancel" class="btn btn-danger btn-sm"
onclick="return ask(this, '{Lang::T('Cancel it?')}')">
<span class="glyphicon glyphicon-trash"></span>
{Lang::T('Cancel')}
</a>
</div>
<div class="btn-group">
<a class="btn btn-success btn-block btn-sm" href="{$_url}order/view/{$unpaid['id']}">
<span class="icon"><i class="ion ion-card"></i></span>
<span>{Lang::T('PAY NOW')}</span>
</a>
</div>
</div>
</div>&nbsp;&nbsp;
</div>
{/if}
<div class="box box-info box-solid">
<div class="box-header">
<h3 class="box-title">{Lang::T('Announcement')}</h3>
</div>
<div class="box-body">
{$Announcement_Customer = "{$PAGES_PATH}/Announcement_Customer.html"}
{if file_exists($Announcement_Customer)}
{include file=$Announcement_Customer}
{/if}
</div>
</div>
</div>
<div class="col col-md-6 col-md-pull-6">
<div class="box box-primary box-solid">
<div class="box-header">
<h3 class="box-title">{Lang::T('Your Account Information')}</h3>
</div>
<div style="margin-left: 5px; margin-right: 5px;">
<table class="table table-bordered table-striped table-bordered table-hover mb-0"
style="margin-bottom: 0px;">
<tr>
<td class="small text-success text-uppercase text-normal">{Lang::T('Usernames')}</td>
<td class="small mb15">{$_user['username']}</td>
</tr>
<tr>
<td class="small text-success text-uppercase text-normal">{Lang::T('Password')}</td>
<td class="small mb15"><input type="password" value="{$_user['password']}"
style="width:100%; border: 0px;" onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'" onclick="this.select()"></td>
</tr>
<tr>
<td class="small text-success text-uppercase text-normal">{Lang::T('Service Type')}</td>
<td class="small mb15">
{if $_user.service_type == 'Hotspot'}
Hotspot
{elseif $_user.service_type == 'PPPoE'}
PPPoE
{elseif $_user.service_type == 'VPN'}
VPN
{elseif $_user.service_type == 'Others' || $_user.service_type == null}
Others
{/if}
</td>
</tr>
{if $_c['enable_balance'] == 'yes'}
<tr>
<td class="small text-warning text-uppercase text-normal">{Lang::T('Yours Balance')}</td>
<td class="small mb15 text-bold">
{Lang::moneyFormat($_user['balance'])}
{if $_user['auto_renewal'] == 1}
<a class="label label-success pull-right" href="{$_url}home&renewal=0"
onclick="return ask(this, '{Lang::T('Disable auto renewal?')}')">{Lang::T('Auto Renewal
On')}</a>
{else}
<a class="label label-danger pull-right" href="{$_url}home&renewal=1"
onclick="return ask(this, '{Lang::T('Enable auto renewal?')}')">{Lang::T('Auto Renewal
Off')}</a>
{/if}
</td>
</tr>
{/if}
</table>&nbsp;&nbsp;
</div>
{if $abills && count($abills)>0}
<div class="box-header">
<h3 class="box-title">{Lang::T('Additional Billing')}</h3>
</div>
<div style="margin-left: 5px; margin-right: 5px;">
<table class="table table-bordered table-striped table-bordered table-hover mb-0"
style="margin-bottom: 0px;">
{assign var="total" value=0}
{foreach $abills as $k => $v}
<tr>
<td class="small text-success text-uppercase text-normal">{str_replace(' Bill', '', $k)}</td>
<td class="small mb15">
{if strpos($v, ':') === false}
{Lang::moneyFormat($v)}
<sup title="recurring">∞</sup>
{assign var="total" value=$v+$total}
{else}
{assign var="exp" value=explode(':',$v)}
{Lang::moneyFormat($exp[0])}
<sup title="{$exp[1]} more times">{if $exp[1]==0}{Lang::T('paid
off')}{else}{$exp[1]}x{/if}</sup>
{if $exp[1]>0}
{assign var="total" value=$exp[0]+$total}
{/if}
{/if}
</td>
</tr>
{/foreach}
<tr>
<td class="small text-success text-uppercase text-normal"><b>{Lang::T('Total')}</b></td>
<td class="small mb15"><b>
{if $total==0}
{ucwords(Lang::T('paid off'))}
{else}
{Lang::moneyFormat($total)}
{/if}
</b></td>
</tr>
</table>
</div> &nbsp;&nbsp;
{/if}
</div>
{if $_bills}
<div class="box box-primary box-solid">
{foreach $_bills as $_bill}
{if $_bill['routers'] != 'radius'}
<div class="box-header">
<h3 class="box-title">{$_bill['routers']}</h3>
<div class="btn-group pull-right">
{if $_bill['type'] == 'Hotspot'}
{if $_c['hotspot_plan']==''}Hotspot Plan{else}{$_c['hotspot_plan']}{/if}
{else if $_bill['type'] == 'PPPOE'}
{if $_c['pppoe_plan']==''}PPPOE Plan{else}{$_c['pppoe_plan']}{/if}
{else if $_bill['type'] == 'VPN'}
{if $_c['pppoe_plan']==''}VPN Plan{else}{$_c['vpn_plan']}{/if}
{/if}
</div>
</div>
{else}
<div class="box-header">
<h3 class="box-title">{if $_c['radius_plan']==''}Radius Plan{else}{$_c['radius_plan']}{/if}</h3>
</div>
{/if}
<div style="margin-left: 5px; margin-right: 5px;">
<table class="table table-bordered table-striped table-bordered table-hover"
style="margin-bottom: 0px;">
<tr>
<td class="small text-primary text-uppercase text-normal">{Lang::T('Package Name')}</td>
<td class="small mb15">
{$_bill['namebp']}
{if $_bill['status'] != 'on'}
<a class="label label-danger pull-right"
href="{$_url}order/package">{Lang::T('Expired')}</a>
{/if}
</td>
</tr>
{if $_c['show_bandwidth_plan'] == 'yes'}
<tr>
<td class="small text-primary text-uppercase text-normal">{Lang::T('Bandwidth')}</td>
<td class="small mb15">
{$_bill['name_bw']}
</td>
</tr>
{/if}
<tr>
<td class="small text-info text-uppercase text-normal">{Lang::T('Created On')}</td>
<td class="small mb15">
{if $_bill['time'] ne
''}{Lang::dateAndTimeFormat($_bill['recharged_on'],$_bill['recharged_time'])}
{/if}&nbsp;</td>
</tr>
<tr>
<td class="small text-danger text-uppercase text-normal">{Lang::T('Expires On')}</td>
<td class="small mb15 text-danger">
{if $_bill['time'] ne
''}{Lang::dateAndTimeFormat($_bill['expiration'],$_bill['time'])}{/if}&nbsp;
</td>
</tr>
<tr>
<td class="small text-success text-uppercase text-normal">{Lang::T('Type')}</td>
<td class="small mb15 text-success">
<b>{if $_bill['prepaid'] eq yes}Prepaid{else}Postpaid{/if}</b>
{$_bill['plan_type']}
</td>
</tr>
{if $_bill['type'] == 'VPN' && $_bill['routers'] == $vpn['routers']}
<tr>
<td class="small text-success text-uppercase text-normal">{Lang::T('Public IP')}</td>
<td class="small mb15">{$vpn['public_ip']} / {$vpn['port_name']}</td>
</tr>
<tr>
<td class="small text-success text-uppercase text-normal">{Lang::T('Private IP')}</td>
<td class="small mb15">{$_user['pppoe_ip']}</td>
</tr>
{foreach $cf as $tcf}
<tr>
{if $tcf['field_name'] == 'Winbox' or $tcf['field_name'] == 'Api' or $tcf['field_name'] == 'Web'}
<td class="small text-info text-uppercase text-normal">{$tcf['field_name']} - Port</td>
<td class="small mb15"><a href="http://{$vpn['public_ip']}:{$tcf['field_value']}"
target="_blank">{$tcf['field_value']}</a></td>
</tr>
{/if}
{/foreach}
{/if}
{if $nux_ip neq ''}
<tr>
<td class="small text-primary text-uppercase text-normal">{Lang::T('Current IP')}</td>
<td class="small mb15">{$nux_ip}</td>
</tr>
{/if}
{if $nux_mac neq ''}
<tr>
<td class="small text-primary text-uppercase text-normal">{Lang::T('Current MAC')}</td>
<td class="small mb15">{$nux_mac}</td>
</tr>
{/if}
{if $_bill['type'] == 'Hotspot' && $_bill['status'] == 'on' && $_bill['routers'] != 'radius' &&
$_c['hs_auth_method'] != 'hchap'}
<tr>
<td class="small text-primary text-uppercase text-normal">{Lang::T('Login Status')}</td>
<td class="small mb15" id="login_status_{$_bill['id']}">
<img src="ui/ui/images/loading.gif">
</td>
</tr>
{/if}
{if $_bill['type'] == 'Hotspot' && $_bill['status'] == 'on' && $_c['hs_auth_method'] == 'hchap'}
<tr>
<td class="small text-primary text-uppercase text-normal">{Lang::T('Login Status')}</td>
<td class="small mb15">
{if $logged == '1'}
<a href="http://{$hostname}/status" class="btn btn-success btn-xs btn-block">{Lang::T('You
are
Online, Check Status')}</a>
{else}
<a href="{$_url}home&mikrotik=login"
onclick="return ask(this, '{Lang::T('Connect to Internet')}')"
class="btn btn-danger btn-xs btn-block">{Lang::T('Not Online, Login now?')}</a>
{/if}
</td>
</tr>
{/if}
<tr>
<td class="small text-primary text-uppercase text-normal">
{if $_bill['status'] == 'on' && $_bill['prepaid'] != 'YES'}
<a href="{$_url}home&deactivate={$_bill['id']}"
onclick="return ask(this, '{Lang::T('Deactivate')}?')" class="btn btn-danger btn-xs"><i
class="glyphicon glyphicon-trash"></i></a>
{/if}
</td>
<td class="small row">
{if $_bill['status'] != 'on' && $_bill['prepaid'] != 'yes' && $_c['extend_expired']}
<a class="btn btn-warning text-black btn-sm"
href="{$_url}home&extend={$_bill['id']}&stoken={App::getToken()}"
onclick="return ask(this, '{Text::toHex($_c['extend_confirmation'])}')">{Lang::T('Extend')}</a>
{/if}
<a class="btn btn-primary pull-right btn-sm"
href="{$_url}home&recharge={$_bill['id']}&stoken={App::getToken()}"
onclick="return ask(this, '{Lang::T('Recharge')}?')">{Lang::T('Recharge')}</a>
<a class="btn btn-warning text-black pull-right btn-sm"
href="{$_url}home&sync={$_bill['id']}&stoken={App::getToken()}"
onclick="return ask(this, '{Lang::T('Sync account if you failed login to internet')}?')"
data-toggle="tooltip" data-placement="top"
title="{Lang::T('Sync account if you failed login to internet')}"><span
class="glyphicon glyphicon-refresh" aria-hidden="true"></span> {Lang::T('Sync')}</a>
</td>
</tr>
</table>
</div>
&nbsp;&nbsp;
{/foreach}
</div>
{/if}
{if $_c['disable_voucher'] == 'yes'}
<div class="box-footer">
{if $_c['payment_gateway'] != 'none' or $_c['payment_gateway'] == '' }
<a href="{$_url}order/package" class="btn btn-primary btn-block">
<i class="ion ion-ios-cart"></i>
{Lang::T('Order Package')}
</a>
{/if}
</div>
{/if}
{if $_bills}
{foreach $_bills as $_bill}
{if $_bill['type'] == 'Hotspot' && $_bill['status'] == 'on' && $_c['hs_auth_method'] != 'hchap'}
<script>
setTimeout(() => {
$.ajax({
url: "?_route=autoload_user/isLogin/{$_bill['id']}",
cache: false,
success: function(msg) {
$("#login_status_{$_bill['id']}").html(msg);
}
});
}, 2000);
</script>
{/if}
{/foreach}
{/if}
{if $_c['enable_balance'] == 'yes' && $_c['allow_balance_transfer'] == 'yes'}
<div class="box box-primary box-solid mb30">
<div class="box-header">
<h4 class="box-title">{Lang::T("Transfer Balance")}</h4>
</div>
<div class="box-body p-0">
<form method="post" onsubmit="return askConfirm()" role="form" action="{$_url}home">
<div class="form-group">
<div class="col-sm-5">
<input type="text" id="username" name="username" class="form-control" required
placeholder="{Lang::T('Friend Usernames')}">
</div>
<div class="col-sm-5">
<input type="number" id="balance" name="balance" autocomplete="off" class="form-control"
required placeholder="{Lang::T('Balance Amount')}">
</div>
<div class="form-group col-sm-2" align="center">
<button class="btn btn-success btn-block" id="sendBtn" type="submit" name="send"
onclick="return ask(this, '{Lang::T(" Are You Sure?")}')" value="balance"><i
class="glyphicon glyphicon-send"></i></button>
</div>
</div>
</form>
<script>
function askConfirm() {
if (confirm('{Lang::T('Send yours balance ? ')}')) {
setTimeout(() => {
document.getElementById('sendBtn').setAttribute('disabled', '');
}, 1000);
return true;
}
return false;
}
</script>
</div>
<div class="box-header">
<h4 class="box-title">{Lang::T("Recharge a friend")}</h4>
</div>
<div class="box-body p-0">
<form method="post" role="form" action="{$_url}home">
<div class="form-group">
<div class="col-sm-10">
<input type="text" id="username" name="username" class="form-control" required
placeholder="{Lang::T('Usernames')}">
</div>
<div class="form-group col-sm-2" align="center">
<button class="btn btn-success btn-block" id="sendBtn" type="submit" name="send"
onclick="return ask(this, '{Lang::T(" Are You Sure?")}')" value="plan"><i
class="glyphicon glyphicon-send"></i></button>
</div>
</div>
</form>
</div>
</div>
{/if}
<br>
{if $_c['disable_voucher'] != 'yes'}
<div class="box box-primary box-solid mb30">
<div class="box-header">
<h3 class="box-title">{Lang::T('Voucher Activation')}</h3>
</div>
<div class="box-body">
<form method="post" role="form" class="form-horizontal" action="{$_url}voucher/activation-post">
<div class="input-group">
<span class="input-group-btn">
<a class="btn btn-default" href="{APP_URL}/scan/?back={urlencode($_url)}{urlencode("
home&code=")}"><i class="glyphicon glyphicon-qrcode"></i></a>
</span>
<input type="text" id="code" name="code" class="form-control"
placeholder="{Lang::T('Enter voucher code here')}" value="{$code}">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">{Lang::T('Recharge')}</button>
</span>
</div>
</form>
</div>
<div class="box-body">
<div class="btn-group btn-group-justified" role="group">
<a class="btn btn-default" href="{$_url}voucher/activation">
<i class="ion ion-ios-cart"></i>
{Lang::T('Order Voucher')}
</a>
{if $_c['payment_gateway'] != 'none' or $_c['payment_gateway'] == '' }
<a href="{$_url}order/package" class="btn btn-default">
<i class="ion ion-ios-cart"></i>
{Lang::T('Order Package')}
</a>
{/if}
</div>
</div>
</div>
{/if}
</div>
</div>
{if isset($hostname) && $hchap == 'true' && $_c['hs_auth_method'] == 'hchap'}
<script type="text/javascript" src="/ui/ui/scripts/md5.js"></script>
<script type="text/javascript">
var hostname = "http://{$hostname}/login";
var user = "{$_user['username']}";
var pass = "{$_user['password']}";
var dst = "{$apkurl}";
var authdly = "2";
var key = hexMD5('{$key1}' + pass + '{$key2}');
var auth = hostname + '?username=' + user + '&dst=' + dst + '&password=' + key;
document.write('<meta http-equiv="refresh" target="_blank" content="' + authdly + '; url=' + auth + '">');
</script>
{/if}
{include file="customer/footer.tpl"}

View File

@ -1,4 +1,4 @@
{include file="user-ui/header.tpl"}
{include file="customer/header.tpl"}
<!-- user-phone-update -->
@ -19,6 +19,7 @@
</div>
</div>
<form method="post" role="form" action="{$_url}accounts/email-update-otp">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('New Email')}</label>
<div class="col-md-6">
@ -34,6 +35,7 @@
</div>
</form>
<form method="post" role="form" action="{$_url}accounts/email-update-post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<!-- Form 2 -->
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('OTP')}</label>
@ -76,4 +78,4 @@
</div>
</div>
</div>
{include file="user-ui/footer.tpl"}
{include file="customer/footer.tpl"}

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