258 Commits

Author SHA1 Message Date
johnnyq
d13104bb5a Update changelog order 2026-03-10 14:06:34 -04:00
johnnyq
5d85810e58 Update Changelog 2026-03-10 14:01:59 -04:00
johnnyq
b00014771d tickets: Add Tooltip to Approve task 2026-03-10 13:58:39 -04:00
johnnyq
b133f63736 Ticket: Fix Task Approval 2026-03-10 13:54:54 -04:00
johnnyq
e45563ad66 Update Changelog 2026-03-10 13:25:19 -04:00
johnnyq
cf639cbf98 Update Changelog 2026-03-10 13:22:22 -04:00
johnnyq
b365b7c810 Rename unarchive to restore in assets 2026-03-09 15:33:32 -04:00
johnnyq
b394f484d3 Fix Vars add empty value placeholder for Ticket Frequency 2026-03-09 15:29:22 -04:00
johnnyq
ada336ceea Update var names 2026-03-09 15:16:29 -04:00
johnnyq
26ebac450f Setup: Fix config_base_url if not using the script so /setup is not appended 2026-03-09 12:04:21 -04:00
johnnyq
6e33c071ef Hide New Invoice, Recurring Invoices, Tickets, revenues, products for appropriate permissions 2026-03-09 11:44:45 -04:00
johnnyq
5bd21a2bd4 add .unifi to the allowed extensions on file uploads 2026-03-09 11:22:46 -04:00
johnnyq
e2547122a6 Update Var naming schemes to match the post var name 2026-03-08 14:25:12 -04:00
johnnyq
edad83cc37 Fix Regression of chnage post name from client to client_id Add / Edit Expense 2026-03-08 13:43:46 -04:00
johnnyq
b0190f462c Invoice: Fix Regression caused by renaming post var clien to client_id 2026-03-08 13:33:36 -04:00
johnnyq
c936823912 Update Changelog 2026-03-08 12:54:58 -04:00
johnnyq
7acaf55331 Bump App Version and Update Changelog 2026-03-08 12:47:02 -04:00
johnnyq
6122efc870 Updated all Queries related to invoice items to reflect their new tables quote, invoice, recurring invoice items 2026-03-07 19:35:44 -05:00
johnnyq
7f180eb6d9 Migrate Invoice Quote Recurring Invoice Items to their new respecting tables 2026-03-07 18:47:28 -05:00
johnnyq
72091670fd Bump DB to to include tables for quote items and recurring invoice item for future splitting of line items 2026-03-07 18:23:19 -05:00
johnnyq
2b6ebca4c3 Fix Project templates listing to not open edit modal when project is clicked 2026-03-07 18:00:32 -05:00
johnnyq
e44d571331 Allow Project template ticket templates to be Drag and drop sorted instead of manually typing in the order 2026-03-07 17:56:39 -05:00
johnnyq
526fa1aff5 Bump stripe-php from 19.0.0 to 19.4.1 2026-03-07 17:19:48 -05:00
johnnyq
11ba077726 Bump fullcalendar from 6.1.19 to 6.1.20 2026-03-07 17:09:01 -05:00
johnnyq
ffb97006ec Bump TCPDF from 6.10.1 to 6.11.2 2026-03-07 17:01:53 -05:00
johnnyq
8dbbef37bb Fix file upload missing client_id 2026-03-07 16:50:11 -05:00
johnnyq
92b98b95ee Better field arrangedment for better workflow for rack edit add 2026-03-07 16:39:07 -05:00
johnnyq
083262fedb Dont require gateway on network edit also make vlan field numeric 2026-03-07 16:31:39 -05:00
johnnyq
fe9bc05937 Rearrange fields so they make a better workflow in software edit and add 2026-03-07 16:17:34 -05:00
johnnyq
726b1cd42b Fix Stripe Add Payment in client portal regression on CSRF check 2026-03-07 13:17:24 -05:00
johnnyq
8de7b20ba1 Add CSRF Checks in the client portal 2026-03-07 13:11:08 -05:00
johnnyq
6d2cb0aea3 Split out item edits into seperate posts and modals for editing invoice, quote and recurring invoice items 2026-03-07 12:12:27 -05:00
johnnyq
c8273b2052 Client related modal and post move client_id get after post 2026-03-07 11:48:56 -05:00
johnnyq
60b6c245ef Change expense client to client_id post var 2026-03-06 18:40:01 -05:00
johnnyq
fcf098f494 use client_id as post var name not client 2026-03-06 18:33:25 -05:00
johnnyq
74ce8f4e98 Trips: enforceClientAccess only if client is referenced 2026-03-06 18:26:14 -05:00
johnnyq
6e3a36d8c4 payments: enforceClientAccess 2026-03-06 18:20:58 -05:00
johnnyq
6f0a2a5a73 quotes: enforceClientAccess 2026-03-06 18:11:35 -05:00
johnnyq
111a30f13c recurring invoices: enforceClientAccess 2026-03-06 18:01:20 -05:00
johnnyq
202f55c2ff invoices: remove client_id post from edit and link modals as it should get the client_id in post, enforceClientAccess 2026-03-06 17:53:44 -05:00
johnnyq
8ad8fd07b3 files, folders, documents: remove client_id post from edit and link modals as it should get the client_id in post, enforceClientAccess 2026-03-06 17:16:04 -05:00
johnnyq
a1931f59f8 services: remove client_id post from edit service modal as it should get the client_id in post, enforceClientAccess 2026-03-06 17:01:34 -05:00
johnnyq
e7b70c7992 domains: remove client_id post from edit domain modal as it should get the client_id in post, enforceClientAccess 2026-03-06 16:59:15 -05:00
johnnyq
7563148182 certificates: remove client_id post from edit certificate modal as it should get the client_id in post, enforceClientAccess 2026-03-06 16:53:20 -05:00
johnnyq
a252ff717e Racks: remove client_id post from edit rack and add rack device modal as it should get the client_id in post, enforceClientAccess 2026-03-06 16:43:56 -05:00
johnnyq
7d41782013 Credential: remove client_id post from edit credential modal as it should get the client_id in post, enforceClientAccess 2026-03-06 16:34:01 -05:00
johnnyq
91976cc415 Software: remove client_id post from edit software modal as it should get the client_id in post, enforceClientAccess 2026-03-06 16:23:53 -05:00
johnnyq
8afa2dbf75 Assets: remove client_id from edit asset modal as it should get the client_id in post, enforceClientAccess 2026-03-06 16:13:56 -05:00
johnnyq
3983c45cac Calendar Events: use client_id instead of client in POST and enforceClientAccess if client is assigned to an event 2026-03-06 15:59:18 -05:00
johnnyq
df5c350765 Vendors: enforceClientAccess in POST only if vendor is assigned a client, as vendor_client_id 0 is for global vendors 2026-03-06 15:43:44 -05:00
johnnyq
de8b9df4da Projects: enforceClientAccess in POST only if a client is assigned to the project 2026-03-06 15:25:30 -05:00
johnnyq
3be815c749 Recurring tickets: enforceClientAccess in POST 2026-03-06 14:35:19 -05:00
johnnyq
71b19e125e Locations and tickets enforceClientAccess in POST fix a few missing client_id vars which were bale to be used for logging too 2026-03-06 14:09:51 -05:00
johnnyq
8fc3dfed1f New Function enforceClientAccess() and added to contact post and client inc all This enforces user client access if set at post and in other places easily 2026-03-06 13:05:11 -05:00
johnnyq
30357b9cf7 Add CSRF Checks to notifications and ensure the user dismissing the notification is their own notification 2026-03-05 17:51:20 -05:00
johnnyq
a81edc122d Add CSRF Checks on Agent ajax endpoints that would update / delete or add something to the db 2026-03-05 17:12:44 -05:00
johnnyq
994526e2c8 Asset details Modal: Reduce top bar Icon Size, Add IPv6 to interfaces, rearrange interfaces to match full page asset details and cleanup code 2026-03-04 19:48:51 -05:00
johnnyq
203b161e82 Add Billing Time Increment Option in Client Ticket Time Detail Report, this option will later be available globally 2026-03-04 18:33:08 -05:00
johnnyq
47b8ec6f96 Simple Calendar naming 2026-03-04 17:18:54 -05:00
johnnyq
302914c789 Add New Calendar Hint 2026-03-04 17:13:02 -05:00
johnnyq
f77bd4f0c6 Calendar: Add Delete Calendar, rename System calendar to Built-in calendar with builtin calendar names, moved calendar select on top for both edit / add event 2026-03-04 17:09:13 -05:00
johnnyq
af4327ff27 Show Full Event title in calendar on hover 2026-03-04 16:27:22 -05:00
johnnyq
5cbed128e6 Use new custom CSS Class for Column Checkboxes so they are a static 40px 2026-03-04 11:56:20 -05:00
johnnyq
dd5fde4069 Client Overview: Cleaned up UI shrunk table sizes and card headers 2026-03-03 16:11:52 -05:00
johnnyq
220e1d4e24 Make new ticket first in client top head 2026-03-03 15:51:50 -05:00
johnnyq
3481431eb5 Add New ticket button in client top header button, spruce up credential favorites on overview 2026-03-03 15:50:14 -05:00
johnnyq
b94604ae71 Assets List: Major UI / UX Update removed some columns to reduce space and add them as a secondary row instead 2026-03-03 15:20:11 -05:00
johnnyq
fe406487de UI Update for Role Listing 2026-03-03 11:44:22 -05:00
johnnyq
99218d02ad Document Templates: UI Enhancement / Cleanup Document templates 2026-03-03 11:16:36 -05:00
johnnyq
8cd1668ff2 Cleanup Ticket Template Details header 2026-03-03 09:50:27 -05:00
johnnyq
c0f26204c2 Swapped Location and Contact Columns in Clients Listing for better scannabilty 2026-03-02 23:59:17 -05:00
johnnyq
ef3a7d0490 Rearrange columns for network interfaces make better logicl sense 2026-03-02 23:52:01 -05:00
johnnyq
f9558d4701 Assets: Added IPv6 Address to assets list and interface list, Updated UI of add / edit network interfaces updated icons wording and bettr arrangement 2026-03-02 23:40:53 -05:00
johnnyq
9d9ebe7b9e Added Bulk Net Terms editing in clients 2026-03-02 22:43:14 -05:00
johnnyq
918b40afbe Add missing CSRF Checks in admin area and settings 2026-03-02 22:15:36 -05:00
johnnyq
6da8821f2c Add hidden form field to folder modals as well 2026-03-02 20:51:12 -05:00
johnnyq
90171064a8 document and files and folders: Add missing CSRF checks, add missing permission checks 2026-03-02 20:49:35 -05:00
johnnyq
6bbe887f8b services: Add missing CSRF checks rename unarchive to restore 2026-03-02 20:34:55 -05:00
johnnyq
b5fb14ec96 domains: Add missing CSRF checks rename unarchive to restore 2026-03-02 20:32:20 -05:00
johnnyq
dd2ac00aa2 certificates: Add missing CSRF checks rename unarchive to restore 2026-03-02 20:26:43 -05:00
johnnyq
e7b5e7120a racks: Add missing CSRF checks rename unarchive to restore 2026-03-02 20:22:36 -05:00
johnnyq
8bac4f9e53 networks: Add missing CSRF checks rename unarchive to restore 2026-03-02 20:17:26 -05:00
johnnyq
023cb4ff11 credentials: Add missing CSRF checks 2026-03-02 20:11:56 -05:00
johnnyq
1740599b61 software licenses: Add missing CSRF checks, add missing permission checks 2026-03-02 19:37:27 -05:00
johnnyq
24b244b612 assets: Add missing CSRF checks, add missing permission checks, renamed unarchive to restore 2026-03-02 19:32:45 -05:00
johnnyq
c71d1f190e locations: Add missing CSRF checks, add missing permission checks, renamed unarchive to restore 2026-03-02 19:15:52 -05:00
johnnyq
18e180eca5 Vendors: Check if client_id is set to enforce the right permission check. If client vendor then check client module otherwise check financial module 2026-03-02 18:40:07 -05:00
johnnyq
d936339f07 Contacts: Add missing CSRF checks, add missing permission checks, renamed unarchive to restore 2026-03-02 18:28:53 -05:00
johnnyq
ad16e92763 Use true Decimal minute reporting in Client Time Audit Detail Report 2026-03-02 18:06:54 -05:00
johnnyq
8a1335174d Trips: Add missing CSRF checks, we may need another permission module check for trips for now only admin and financial lv3 can delete a trip 2026-03-02 17:50:04 -05:00
johnnyq
7b438e2889 Transfers: Add missing CSRF checks 2026-03-02 17:38:20 -05:00
johnnyq
550980719e Accounts: Add missing CSRF checks 2026-03-02 17:35:18 -05:00
johnnyq
3d80d1519e Recurring Expenses: Add missing CSRF, Add missing permission checks 2026-03-02 17:32:18 -05:00
johnnyq
1d5fceeecd Expenses: Add missing CSRF, Add missing perms 2026-03-02 17:27:56 -05:00
johnnyq
5b49908438 Vendors: Add missing CSRF, need to update the permissions as a vendor can be client or global and permissions need to be set based off if the referal url has client_id or not 2026-03-02 17:20:00 -05:00
johnnyq
7e515afb79 Payments: Add missing CSRF and additonal perm check 2026-03-02 16:38:17 -05:00
wrongecho
dee5085f4a Allow associating a quote with a ticket 2026-03-02 10:54:44 +00:00
wrongecho
795144b288 Fix documents => files redirect after deletion (since we merged these) 2026-03-02 08:12:07 +00:00
wrongecho
5c3ff91f21 Lower autoclose minimum allowed value to 24 hours (this still works off cron so is not exact) 2026-03-02 08:07:43 +00:00
johnnyq
36ac006438 Products: Add missing CSRF checks change wording from unarchive to restore 2026-03-01 22:50:04 -05:00
johnnyq
af1ebfea41 Revenues: Add missing CSRF checks 2026-03-01 22:42:37 -05:00
johnnyq
f653752026 Recurring Invoice: Add missing CSRF checks and missing permissions in POST 2026-03-01 22:37:51 -05:00
johnnyq
4440581f14 Invoice: Add missing CSRF checks and missing permissions 2026-03-01 22:22:21 -05:00
johnnyq
2c47001b19 Quote: Add missing CSRF checks and missing permission on export quote pdf 2026-03-01 22:02:43 -05:00
johnnyq
e1daa14087 Calendar: Add missing CSRF checks 2026-03-01 21:52:28 -05:00
johnnyq
308dc6e550 Ticket Tasks: Add missing CSRF checks and other CSRF checks missed in he previous commits 2026-03-01 21:45:26 -05:00
johnnyq
54638428e3 rename unarchive project to restore project 2026-03-01 21:32:24 -05:00
johnnyq
1508e6b055 Project: Add missing CSRF checks 2026-03-01 21:30:41 -05:00
johnnyq
9213249f7b Recurring Ticket: Add missing CSRF checks 2026-03-01 21:21:37 -05:00
johnnyq
63f6faf1e8 Ticket: Add missing CSRF checks 2026-03-01 21:14:58 -05:00
johnnyq
c2cbfc5558 Add Hidden Inpout csrf token to client download pdf 2026-03-01 20:43:44 -05:00
johnnyq
ef1b65de09 Client: Add missing CSRF checks, and missing enforcements 2026-03-01 20:42:18 -05:00
johnnyq
ae81092112 Updated Placeholders to set better formal expectations 2026-02-28 20:52:53 -05:00
johnnyq
f09be24188 Rearrange add/edit Asset field Order, updated icons, minor ui work 2026-02-28 20:12:51 -05:00
johnnyq
10c37946b1 UI: Update icoms for description inputs 2026-02-28 19:14:00 -05:00
johnnyq
adb43703ed Update Networks, remove subnet mask in favor of CIDR, Global View show clients in first column, allow for IPv6 and provide IPv6 placeholder examples, gateway no longer required reword DHCP Range to IP Range moved DNS to the end, chnaged out some icons 2026-02-28 18:41:03 -05:00
johnnyq
92fba49a91 Revert "Add new optional beta email parser thats based on ImapEngine instead of Webklex"
This reverts commit 9cb1ff7330.
2026-02-26 16:44:49 -05:00
johnnyq
ac4bb32081 Revert "Beta Mail Parser Add inline images as attachments keeps the DB clean"
This reverts commit 87fd23f443.
2026-02-26 16:44:39 -05:00
johnnyq
7927312f4f Revert "Revert "Beta Mail Parser Add inline images as attachments keeps the DB clean""
This reverts commit 04a74b5a34.
2026-02-26 16:41:50 -05:00
johnnyq
04a74b5a34 Revert "Beta Mail Parser Add inline images as attachments keeps the DB clean"
This reverts commit 87fd23f443.
2026-02-26 16:41:03 -05:00
johnnyq
87fd23f443 Beta Mail Parser Add inline images as attachments keeps the DB clean 2026-02-26 16:30:29 -05:00
johnnyq
9cb1ff7330 Add new optional beta email parser thats based on ImapEngine instead of Webklex 2026-02-26 16:11:49 -05:00
wrongecho
1ba19cc249 New Ticket Parsing - Anyone CC'ed onto the original email that created the ticket is added as a ticket watcher 2026-02-26 12:49:32 +00:00
wrongecho
702d9689d6 clarify PR stance 2026-02-26 10:24:44 +00:00
johnnyq
8efa354fdb Fix Task Sorting in ticket templates 2026-02-21 14:49:46 -05:00
johnnyq
129e95fd3a Added Net 45 Days, removed 14 Days as this isnt typically used anywhere 2026-02-19 12:34:30 -05:00
johnnyq
ef7794c411 Added non payment invoice reminder / Late fee if enabled after 1 day an invoice is overdue. currently it was jumping right to 30,60,90... now its 1,30,90... 2026-02-19 12:20:26 -05:00
johnnyq
12b3775041 Update Changelog 2026-02-14 15:50:27 -05:00
johnnyq
52b509fdc5 Update Changelog 2026-02-14 15:49:40 -05:00
johnnyq
f9d0a8bf43 Ticket: Dont display Updated at if null, Move Subject from top ticket bar to header in ticket details, add additional comments about the client_permission_overide 2026-02-14 15:48:40 -05:00
johnnyq
2349ef33d1 Remove 0 in access_permission_query 2026-02-14 15:07:42 -05:00
johnnyq
ccaf15268c Add a 0 IN ticket_client_id for no client tickets so agents that are restricted to certain clients and still view no clients in tickets list by using a , also restrict change clients on tickets for restricted agents 2026-02-14 15:06:17 -05:00
johnnyq
616635f951 client edit modal: Restrict Access to client restricted agents 2026-02-14 13:17:15 -05:00
johnnyq
ce7daaf1cf ticket edit modal: Restrict Access to client restricted agents 2026-02-14 12:55:34 -05:00
johnnyq
fb9f5d986d ticket Details: Restrict Access to ticket details by restricted agents 2026-02-14 12:51:58 -05:00
johnnyq
3148906cfe ticket Details: Restrict Access to ticket details by restricted Agents 2026-02-14 12:30:49 -05:00
johnnyq
6c057f4fd0 Restrict Tickets presented in ticket listing to client restricted Agents 2026-02-14 12:16:22 -05:00
johnnyq
3014bba66d Update Changelog, Bump App Release 2026-02-14 12:01:49 -05:00
johnnyq
97958d5e22 Missing Generate Password js code in Create Credential modal 2026-02-13 14:43:34 -05:00
johnnyq
1a7452cc27 Restrict Agents that are restricted to view certain clients from calendar events. 2026-02-13 14:28:45 -05:00
johnnyq
34ba5d0570 Properly get the new ticket number for logging in ticket merge 2026-02-13 14:14:01 -05:00
johnnyq
c0fe9813dc Fix Ticket Merging regressed from ticket select now use ticket_id instead of ticket_number 2026-02-13 14:09:05 -05:00
johnnyq
fd29eb7c15 Fix Transfer Asset to client due to field change from asset_important to asset_favorite 2026-02-13 13:39:41 -05:00
johnnyq
1bee085b33 Add Report for Client Detail Time Auditing 2026-02-13 13:35:02 -05:00
johnnyq
1d06e6d9c8 Revert to old ajax-modal js code for now, Fix Assets not lising in create ticket. 2026-02-11 13:33:28 -05:00
wrongecho
bdf8038148 Merge pull request #1265 from MydsiIversen/Time-API
feat: Add API endpoint to retrieve time worked by technicians
2026-02-09 14:23:02 +00:00
Mads Iversen
62fb2c91a1 feat: Add API endpoint to retrieve time worked by technicians on tickets with filtering by date and technician. 2026-02-09 12:09:56 +00:00
johnnyq
d1eeba67fc Add bulk Fav / unfav creds and added fav creds to the client overview page 2026-02-07 18:42:53 -05:00
johnnyq
2ca8112daf Revert Git change test to ticket_email_parser.php 2026-02-06 15:14:37 -05:00
johnnyq
546f10bc82 Testing Git changes on ticket_email_parser.php 2026-02-06 15:13:54 -05:00
johnnyq
ec7f1d561d Fix Version 2026-02-06 13:12:34 -05:00
johnnyq
8d54bc3a2f Another change 2026-02-06 13:03:07 -05:00
johnnyq
1d8f77f799 Update Changelog for 26.2 Release 2026-02-06 13:01:37 -05:00
johnnyq
1c6795dd55 Bump app version 2026-02-06 12:09:32 -05:00
Johnny
4d895a56e4 Merge pull request #1261 from cs2000/develop
Add full Microsoft 365 and Google OAuth mail support
2026-02-05 14:04:22 -05:00
johnnyq
bdd56c91f7 Update Credential Important to Favorite 2026-02-05 12:24:21 -05:00
johnnyq
be172b5bd9 Add star-toggle class to add asset 2026-02-05 12:15:41 -05:00
johnnyq
d224d71f59 Bump Datatables from 2.3.4 to 2.3.7 2026-02-05 11:50:33 -05:00
johnnyq
065630b975 Bump PHPMailer from 7.0.1 to 7.0.2 2026-02-05 11:46:28 -05:00
johnnyq
decea604ab Bump TinyMCE 8.2.2 to 8.3.2 2026-02-05 11:38:22 -05:00
johnnyq
004b3b2bdc Draft of ticket_by_client report v2 not yet in the side menu of reports 2026-02-05 11:31:40 -05:00
cs2000
dca0cc34e9 Revert to original file from dev branch
Accidentally comitted an older file.
2026-02-05 16:08:04 +00:00
cs2000
9afb165fc4 SonarCube Suggestion
Remove duplicates of https://login.microsoftonline.com/ and make them use a constant.
2026-02-05 10:26:51 +00:00
cs2000
6a6eb4a714 SonarCube Suggestion
Define a constant "MICROSOFT_OAUTH_BASE_URL" instead of duplicating "https://login.microsoftonline.com/" multiple times in the same file.
2026-02-05 10:22:06 +00:00
cs2000
629f4e0c81 SonarCube Suggestion
Defined a constant $settings_mail_path at the top of the file instead of duplicating "/admin/settings_mail.php" in the code multiple times.
2026-02-05 10:19:13 +00:00
cs2000
7cbe9bf7fc SonarCube Suggestion
Removed two instances of unecessary double parentheses.
2026-02-05 10:17:28 +00:00
cs2000
fc33312e79 SonarCube Suggestion
Rename multiple functions to conform to project naming rules (^[a-z][a-zA-Z0-9]*$). This is a non-functional refactor for style/CI compliance and consistency.
2026-02-05 10:15:52 +00:00
cs2000
563c0ea9c4 Changes for M365 oAuth using dev code
My bad, my changes were made previously using my installed version and not the latest changes in the dev branch, i have pulled the dev branch and merged my changes with the latest codebase.
2026-02-05 09:38:09 +00:00
cs2000
10d1a902d9 Changes for M365 oAuth now using latest dev code
My bad, my changes were made previously using my installed version and not the latest changes in the dev branch, i have pulled the dev branch and merged my changes with the latest codebase.
2026-02-05 09:37:22 +00:00
johnnyq
4079257739 Update UI/UX for Adding and editing roles, Permissions can be set upon role add nice blocker style radios buttons instead of select boxes 2026-02-04 13:12:24 -05:00
johnnyq
91aba13c0d Fix Empty col in client overview if no fabvorite assets, fix creds page rename var from important to favorite 2026-02-04 11:11:12 -05:00
cs2000
2cca4f2f0e Changes for M365 oAuth
- Added null-safe guard around folder path logging during message move failure to prevent property_exists() fatal when folder object is null.
2026-02-04 13:26:52 +00:00
cs2000
d2e8dc1439 Changes for M365 oAuth
- Fixed email queue gating for OAuth SMTP setups by treating configured config_smtp_provider as mail-enabled, even when config_smtp_host is blank.
- Restores queueing for public ticket reply emails (including “Public Comment & Email”) and related ticket notification paths.
2026-02-04 13:26:17 +00:00
cs2000
f3f9d0dd71 Changes for M365 oAuth
- Added OAuth token lifecycle helpers (expiry check, refresh, persistence).
- Updated SMTP XOAUTH2 send path to automatically refresh expired/missing access tokens for Microsoft/Google providers before sending queued mail.
2026-02-04 13:25:32 +00:00
cs2000
f6845a046f Changes for M365 oAuth
- New callback endpoint to complete Microsoft OAuth web flow.
- Validates admin session + OAuth state, exchanges authorization code for tokens, stores refresh/access tokens and expiry, and redirects with success/error feedback.
2026-02-04 13:24:50 +00:00
cs2000
a50a4f274f Changes for M365 oAuth
- Added handler to start Microsoft OAuth Authorization Code flow (oauth_connect_microsoft_mail) with state generation/validation prep.
- Added handler to test OAuth token refresh from admin UI and persist refreshed tokens/expiry.
- Updated IMAP test handler to support OAuth token refresh + XOAUTH2 authentication (in addition to legacy LOGIN).
2026-02-04 13:23:53 +00:00
cs2000
6b6d847756 Changes for M365 oAuth
- Added web-based Microsoft OAuth onboarding UI in Mail settings, including a Connect Microsoft 365 button and auto-generated callback URI display.
- Added Test OAuth Token Refresh UI section.
- Updated visibility logic so Test Email Sending and Test IMAP Connection show correctly for OAuth-based configs (not only host/password configs).
2026-02-04 13:23:03 +00:00
johnnyq
65d1f59e9b Feature: Favorites added to assets, Bulk Fav/unfav, adds favs to client overview 2026-02-03 22:23:20 -05:00
johnnyq
f39e6ccbc9 Remove old ticket merge code, rename kanban_v2 to to just kanban 2026-02-03 17:20:19 -05:00
johnnyq
747b62d78c Remove extra double quote 2026-02-03 17:09:07 -05:00
johnnyq
af0f3ac25f Update ticket Merge bulk with a dropdown select box to select a ticket instead of entering a number manually 2026-02-03 16:35:20 -05:00
johnnyq
6aefe99c2f Update ticket Merge with a dropdown select box to select a ticket instead of entering a number manually 2026-02-03 16:08:33 -05:00
johnnyq
61c5595a68 Fix Get type on add asset 2026-02-02 16:05:38 -05:00
johnnyq
3d11611699 Attempt to Fix duplicate data entries by fast clicking submit in modals using ajax_modal.js include 2026-01-31 15:14:01 -05:00
johnnyq
38b4ed4b96 Tidy check_login for client portal 2026-01-30 14:24:34 -05:00
johnnyq
34308a5f9a check if archived and active for client login as well along with loading extended client user session vars in /client/includes/check_login.php 2026-01-30 14:10:59 -05:00
johnnyq
118cc10804 During load user session check if user is archived and active, prevents users from making further actions if they are disabled or archived but are still logged in 2026-01-30 13:36:18 -05:00
johnnyq
ae3386f2d5 Fix Vendor phone formatting in Ticket details 2026-01-29 16:36:10 -05:00
johnnyq
bf1ccc62f5 Fix Collected Tax Report, was not adding totals OCT-Dec was misaligned and Grand Total wasnt displaying 2026-01-29 14:02:50 -05:00
johnnyq
1ce4dd932e Updated PL report - changed Gross Profit to Gross Revenue as is more accurate for taxation 2026-01-29 13:55:09 -05:00
johnnyq
ff0bb49926 Tidy up Ticket to Invoice Description 2026-01-29 13:13:24 -05:00
johnnyq
018642cbb8 Fix Ticket View / filtering 2026-01-28 23:33:12 -05:00
johnnyq
bf1d0655c4 Update Ticket list ui smaller badge for ticket and smaller task progress bar and more natural dates 2026-01-28 22:52:56 -05:00
johnnyq
874b07f4d7 More UI/UX updated to ticket details shrink padding on right cards, update wordings, changed header paddings, made unassigned a clickable badge no longer red, swapped trash cans for times icons, much cleaner overall 2026-01-28 22:06:50 -05:00
johnnyq
19fa210743 Merge branch 'develop' of github.com:itflow-org/itflow into develop 2026-01-28 18:16:54 -05:00
johnnyq
c8a6513d1c Fix new times on new date time displays in ticket details 2026-01-28 18:16:38 -05:00
wrongecho
01288c8452 Merge branch 'develop' of https://github.com/itflow-org/itflow into develop 2026-01-28 23:10:54 +00:00
johnnyq
a60d3bb3a0 More Ticket Details UI/UX Sprucing better label naming etc 2026-01-28 17:41:14 -05:00
johnnyq
0470159c55 Added function formatDuration to format times like worked etc, updated Time worked in Ticket details to use the new function and some other minor ui/ux cleanups in ticket details 2026-01-28 16:08:39 -05:00
wrongecho
e1a93035fd Undo client scoping uses = instead of LIKE in tickets resolve.php --- see forum thread 2667 2026-01-28 14:57:28 +00:00
wrongecho
c0b7a26905 API bugfixes before release:
- apiEncryptLoginEntry should be apiEncryptCredentialEntry
- Client scoping uses = instead of LIKE in tickets resolve.php
2026-01-28 14:48:41 +00:00
johnnyq
704d770ec2 Fix Icon to Chevron down if Expand all folders is clicked 2026-01-27 16:32:17 -05:00
johnnyq
d42d2d99b1 Remember folders expanded by Passing folders_expanded var in breadcrumb links and search in files 2026-01-27 16:27:04 -05:00
johnnyq
ebfcc15927 Feature: File Folders are collapsed by default with a global option to exapand all folders or expand individual folder by click 2026-01-27 16:15:18 -05:00
johnnyq
4693aa3c93 Render root in display_folders function in files instead of it being seperate 2026-01-27 15:25:20 -05:00
wrongecho
07df657848 Bugfix: newline char showing in parsed emails 2026-01-27 17:25:53 +00:00
wrongecho
ef9436a1fb Bugfix: Ticket edit modal not setting assigned agent 2026-01-27 12:32:41 +00:00
johnnyq
9318f42ce0 Remove Folder option in edit document modal as this is reserved for move to folder modal 2026-01-24 01:37:00 -05:00
johnnyq
85c37d78a8 Do not update document_updated_at field if document is moved, toggle visibity, renamed, archived or restored same with bulk 2026-01-24 01:27:50 -05:00
johnnyq
ff02ab0cea Show Document Updated at instead of created at. This fixes where it should show the initial document creation date instead of the most updated date, also renamed uploaded column to updated 2026-01-23 13:06:06 -05:00
johnnyq
bd8bf026f0 Allow svg email attachemment for mail parser 2026-01-23 11:24:26 -05:00
johnnyq
644dc95380 Merge branch 'develop' of github.com:itflow-org/itflow into develop 2026-01-21 12:40:43 -05:00
johnnyq
f4935efed3 Remove deprecated quick adds to the client overview 2026-01-21 12:40:30 -05:00
wrongecho
1712d34846 Kanban - move to v2 (rewritten style) 2026-01-21 13:15:23 +00:00
johnnyq
f0f1134be6 Fix phone number formatting is both asset details page and modal 2026-01-19 17:26:40 -05:00
johnnyq
e0f7460e08 If not client_id then do not include &client_id= in ticket uris as this would cause ticket viewing to break if client_id is 0. 2026-01-19 17:01:38 -05:00
johnnyq
ae1d71dcd7 append client_id to the uri for agent ticket links so that the when clicked will take them to the client section and show client header along with the bread crumbs link of client tickets 2026-01-19 16:24:31 -05:00
johnnyq
3cfe38c948 updated UI for contact details so menu is on the left instead of top. Also load the first tab based off the count 2026-01-17 17:35:05 -05:00
johnnyq
c1f0b63101 Fix login not passing master key if agent is client and agent and if MFA is enabled 2026-01-17 17:07:22 -05:00
johnnyq
0a658d7cab Fix Document Update API 2026-01-17 13:17:17 -05:00
johnnyq
3ed2582a9b Remove TaxID from packing slip 2026-01-15 14:09:03 -05:00
johnnyq
2ea68776f6 Merge branch 'develop' of github.com:itflow-org/itflow into develop 2026-01-15 13:13:25 -05:00
johnnyq
c1ff22298f Remove xcustom from .gitignore as its been superceded by custom 2026-01-15 13:13:15 -05:00
wrongecho
e94d2f93ea Merge branch 'develop' of https://github.com/itflow-org/itflow into develop 2026-01-15 17:35:35 +00:00
wrongecho
e7f6f8a4c7 Mail Parser - NDR Parsing 2026-01-15 17:35:13 +00:00
johnnyq
4ffe75683b Merge branch 'develop' of github.com:itflow-org/itflow into develop 2026-01-15 12:28:34 -05:00
johnnyq
687920743d Fix user_id not being set in audit logs for agent and client logins, also fix issie where user_id wasnt being set in client portal. 2026-01-15 12:28:20 -05:00
wrongecho
1a93149643 Basic asset history tracking (transfers) - See https://tasks.dev.itflow.org/task_details.php?task_id=19 2026-01-15 14:58:23 +00:00
wrongecho
11d6654763 Basic asset history tracking - See https://tasks.dev.itflow.org/task_details.php?task_id=19 2026-01-15 14:49:41 +00:00
wrongecho
512d65c17a Keep asset PO/ref when copying 2026-01-15 14:22:22 +00:00
wrongecho
5a274061f3 Invoice - add a basic picking/packaging slip 2026-01-15 13:58:53 +00:00
wrongecho
a398ac7a8d Mail Parser - Support flowed text 2026-01-15 11:54:37 +00:00
wrongecho
41df4c4b9f API Keys - Revoke then Delete 2026-01-15 11:37:17 +00:00
wrongecho
7e7909cec1 Mail Parser - Do not automatically send new ticket notifications to noreply/donotreply addresses 2026-01-15 10:38:25 +00:00
wrongecho
7322bd66df Invoices - Show agent names in history 2026-01-15 10:19:56 +00:00
wrongecho
39affa5528 Show trips on main nav for everyone if accounting enabled 2026-01-15 10:04:48 +00:00
wrongecho
374111c88d Add digiBandit as sponsor 2026-01-15 09:28:52 +00:00
wrongecho
72fc0015bf Add digiBandit as sponsor 2026-01-15 09:28:03 +00:00
johnnyq
7ab406b3f5 Convert mysqli_fetch_assoc to mysqli_fetch_row in setup for table import 2026-01-14 18:02:11 -05:00
johnnyq
df280cd574 Fix missing isset in view in tickets.php 2026-01-14 17:36:06 -05:00
johnnyq
0a30300bde replace all instances of mysqli_fetch_array with mysqli_fetch_assoc for better performance and memory usage 2026-01-14 17:30:23 -05:00
johnnyq
cb8b99d6ae up the permissions to added edit Payment 2026-01-12 18:12:20 -05:00
johnnyq
1d3f29d385 added edit Payment 2026-01-12 18:11:41 -05:00
johnnyq
c154113474 Do not add Signature on ticket comment if Public only when reply is emailed 2026-01-12 15:18:22 -05:00
johnnyq
e02a6fc5e6 Use mysqli_fetch_assoc in login 2026-01-12 12:30:12 -05:00
johnnyq
78040573d1 Fix Remove Powered by ITFlow on Login Screen when WhiteLabel is enabled 2026-01-12 12:27:44 -05:00
wrongecho
981f9ace04 TinyMCE - add all options to mobile (we may regret this) 2026-01-12 16:12:06 +00:00
wrongecho
208f7ac8f0 Tickets Kanban
- Keep the selected view (kanban/list) when applying other filters
- Allow filtering tickets by project (main and kanban v2)
2026-01-12 16:11:36 +00:00
wrongecho
661f8db10b Tickets Kanban
- Keep the selected view (kanban/list) when applying other filters
- Allow filtering tickets by project (main and kanban v2)
2026-01-12 16:04:02 +00:00
wrongecho
b48168ffec Rewrite kanban in more procedural code to match existing codebase (as a v2, delete old and rename once we're happy) 2026-01-12 15:36:06 +00:00
wrongecho
908ebb46d9 Merge pull request #1258 from itflow-org/ticket-task-approvals
Ticket task approvals
2026-01-12 12:27:24 +00:00
johnnyq
1de023f9df Fix Role Archiving, Roles can only be archived if no users are assigned to the role 2026-01-11 16:46:53 -05:00
johnnyq
5815ef2f75 Added bulk delete, archive and restore and invidual file/document restore functions to files 2026-01-10 20:38:04 -05:00
johnnyq
e5dab8b1ca Update files to use mysqli_fetch_assoc for better performance and memory optimization. Add title for List View and Grid View 2026-01-10 19:12:22 -05:00
johnnyq
56c4b7fbe6 Add show archived logic to files 2026-01-10 19:03:28 -05:00
johnnyq
365b65e5b2 fix Bulk Delete Documents and Files 2026-01-10 16:27:12 -05:00
johnnyq
2193cd8d3e switched from mysqli_fetch_array to mysqli_fetch_assoc in client modals 2026-01-10 16:09:03 -05:00
johnnyq
8b221bc055 switched from mysqli_fetch_array to mysqli_fetch_assoc in clients listing page. This will have a major query speed and memory optimization impact 2026-01-10 15:54:19 -05:00
959 changed files with 19646 additions and 11937 deletions

6
.gitignore vendored
View File

@@ -32,10 +32,6 @@ plugins/htmlpurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/URI/*
plugins/htmlpurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/*
!plugins/htmlpurifier/standalone/HTMLPurifier/DefinitionCache/Serializer/CSS/.gitkeep
.vscode/settings.json
xcustom/*
!xcustom/readme.php
post/xcustom
!post/xcustom/readme.php
admin/custom/*
!admin/custom/readme.php
agent/custom/*
@@ -53,5 +49,3 @@ setup/custom/*
api/v1/custom/*
!api/v1/custom/readme.php
.zed

View File

@@ -2,6 +2,113 @@
This file documents all notable changes made to ITFlow.
## [26.03] Stable Release
### Bug Fixes
- Ticket Templates: Fix Task Sorting.
- Ticket: Lower autoclose setting minimum value from 48 to 24 Hours.
- Ticket: Fix Task Approval.
- Recurring Ticket: add empty value placeholder for Ticket Frequency.
- Documents/Files: Fix redirect after File Upload to redirect to files instead of the non existent documents.
- Setup: Fix base url tacking on /setup when not installing via script.
### New Features & Updates
- Clients: Net Terms: Added common 45 and 15 Days, removed 14 Days not as common.
- Clients: Bulk Action Set Net Terms Added.
- Clients: Swapped location and contact column, add PopOver with Details such as created, abbreviation, DB ID instead of taking up space underneath client, rounded tag pills and increased padding, removed info badges and added one info badge that displays a popover with details.
- Clients: Added New Ticket to Client Top Header Menu.
- Clients: Client Overview: UI Sprucing.
- Invoice: Send reminder 1 day after due date.
- Invoices/Quotes/Recurring Invoices: Split Items tables into their own POST logic and Modal UIs and tables (quote_items, invoice_items, recurring_items).
- Tickets: New Ticket Parsing - Anyone CC'ed onto the original email that created the ticket is added as a ticket watcher.
- Ticket/Quotes: Quotes can now be associated with a ticket.
- Networks: Removed Subnet Mask Field, Use CIDR instead.
- Networks: Rearranged fields, Updated placeholders, Add/Edit/list for better flow.
- Networks: Renamed DHCP to IP Range to allow for you use of both DHCP and or Usable IPs.
- Assets: Rearranged fields, Updated placeholders, Add/Edit/list for better flow.
- Assets: Added IPv6 if available under IP, Make and Model are now one line with Serial Underneath. Added OS under Type. use pill for status.
- Calendar: Event thats are cut off can now be viewed as a tooltip on hover.
- Calendar: Renamed System Calendars to built-in calendars and added the names and color dot for reference.
- Calendar: You can now delete a custom calendar.
- Report: Client Ticket Time Detail Audit: Selectable Billing Time Increment, will later be avauilable globally.
- Roles/Permissions: Now complete and is out of beta all permission roles are strictly enforced, except for in Trips and Calendar, new enforce modules will be added for these at a later date.
- Project Templates: Ticket Template order can now be dragged and dropped.
- Global: Introduced new checkbox class to all Checkbox select columns to keep consistency and reduce space and enhance ui.
- Global: CSRF Checks everywhere instead of just deletion calls.
- Global: Renamed the rest of the unarchive post and label calls to restore.
- Files: Allow upload of .unifi extension.
- Bump Libraries:
- stripe-php from 19.0.0 to 19.4.1.
- fullcalendar from 6.1.19 to 6.1.20
- TCPDF from 6.10.1 to 6.11.2
## [26.02.1] Maint Release
### Bug Fixes
- Credentials: Fix Password Generator.
- Calendar: Restrict Events for client restricted agents.
- Ticket Merge: Fix.
- Asset Transfer: Fix.
- Ticket Listing: Restrict Tickets presented in ticket list view from client restricted agents.
- Ticket Details: Deny access to client restricted agents to view tickets without client_id in uri.
- Tickets: Allow agents with restricted client access to view and edit tickets without a client.
- Ticket Change client: Limit selection for agents with restricted client access.
- Ticket Details: Don't display updated at when null.
### New Features & Updates
- Report: Added Client Detail Auditing.
- API: Added Endpoint to retrieve time worked by agent.
- ajax-modal: Revert to previous JS implementation before 26.02 release.
- Ticket: Move Subject from Ticket main ticket header to ticket details card header.
## [26.02] Stable Release
### Bug Fixes
- Mail Parser - Do not automatically send new ticket notifications to noreply/donotreply addresses.
- Ticket: removed newline \n on Parsed emails.
- Show Trips for everyone if accounting module is enabled.
- Fix Invoice Exporting.
- Fix Billable Column not sorting correctly in tickets.
- Fix Login flow where user agent and client user exists and agent has MFA but will not let them continue.
- Fix passing missing user_id var in client portal.
- Fix Ticket Templates not auto filling when selected.
- Fix Invoices not being sent to all billings contacts when manaully sent.
- Fix Documents and Files not able to be bulk deleted.
- Fix Role Archiving, can be archived as long as no users are assigned to the role.
- Fix showing Powered By ITFlow visibility on the login screen when Whitelabel is enabled.
- Missing username in audit log on successful login due to missing passed user_id to logging.
- API: Fix updating all documents instead of the intended document.
- Documents: Fix Document created at not showing the correct creation date of the master document.
- Ticket: Fixed Using edit ticket modal agent was not able to be set.
- Always check if a user is archived and or disabled instead of just during login.
- Report: Fix Collected tax report not totalling all tax categories.
### New Features & Updates
- Task Approval System for ticket tasks: Once an approval is requested, the task cannot be marked as complete until approved. Internal Approvals Any other technician, or Specific technician, Client Approvals Anyone (usually the requestor) Tech contacts Billing contacts.
- Printable Invoice Packing Slips now available.
- Drastic Performance Bump: Up to 50% faster queries accross the board and reduced server memory usage by 40% by switching Database Query method from mysqli_fetch_array to mysqli_fetch_assoc.
- Added Connect to Microsoft 365 Button to mail settings.
- OAUTH2 support for Microsoft 365 and Google Workspaces is now considered stable and working.
- Favorites: Assets and Credentials now can be favorited singly or by Bulk action. Favorited items appear in the client overview now.
- Files/Documents: Collapsable folders feature, collapsed by default with a button to expand all.
- URL Keys and such are now set to a more manageable 32 Characters by default.
- Various UI/UX Updates throughout the app, with focus oin ticket details, contact details modal etc.
- Added Show Archived files and documents to the files section.
- Added Bulk Archive and restore options to files and documents.
- Rewrite of the Kanban Ticket view to match our procedural style of coding.
- All options are available in TinyMCE now in Mobile mode.
- Agent names appear now in Invoice History section.
- Mail Parser: Support flowed text.
- Assets: Keep Purchase reference when copying.
- Assets: Add basic tracking history: Archiving, restoring, name changes, transferimg to new clients.
- Mail Parser: NDR Parsing.
- Allow SVG files in mail attachments.
- Tickets: Use a more friendly time worked instead of 02:41:00 translates to 2h 41m.
- Update wording on ticket to invoice item details.
- Merge Tickets: Now wth a ticket merge dropdown list of tickets instead of a text field.
- Role Permissions can now be set during role creation, update Permission UI to use radio buttons instead of select boxes.
- Bump TinyMCE 8.2.2 to 8.3.2.
- Bump PHPMailer from 7.0.1 to 7.0.2.
- Bump Datatables from 2.3.4 to 2.3.7.
## [25.12.1] Maint Release
### Major Changes

View File

@@ -82,7 +82,7 @@ We track the implementation of confirmed features and bugs via [TaskFlow](https:
For help using ITFlow, bugs, feature requests, and general ideas / discussions please use the community [forum](https://forum.itflow.org).
### Contributing
If you want to improve ITFlow, feel free to fork the repo and create a pull reques. Make sure to discuss significant changes or new features with fellow contributors on the forum first. This helps ensure that your contributions are aligned with project goals, and saves time for everyone. All contributions should follow our [code standards](https://docs.itflow.org/code_standards). See the [contributing guide](https://docs.itflow.org/contribute).
We have temporarily paused PRs from the community.
#### Contributors
<a href="https://github.com/itflow-org/itflow/graphs/contributors">
@@ -93,6 +93,7 @@ If you want to improve ITFlow, feel free to fork the repo and create a pull requ
Were incredibly grateful to the organizations and individuals who support the project - a big thank you to:
- CompuMatter
- F1 for HELP
- digiBandit
- JetBrains (PhpStorm)
## License

View File

@@ -58,7 +58,7 @@ $num_rows = mysqli_num_rows($sql);
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['ai_provider_id']);
$provider_name = nullable_htmlentities($row['ai_provider_name']);
$model_id = intval($row['ai_model_id']);

View File

@@ -48,7 +48,7 @@ $num_rows = mysqli_num_rows($sql);
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['ai_provider_id']);
$provider_name = nullable_htmlentities($row['ai_provider_name']);
$url = nullable_htmlentities($row['ai_provider_api_url']);

View File

@@ -49,7 +49,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<div class="dropdown-menu">
<button class="dropdown-item text-danger text-bold"
type="submit" form="bulkActions" name="bulk_delete_api_keys">
<i class="fas fa-fw fa-trash mr-2"></i>Revoke
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</button>
</div>
</div>
@@ -105,7 +105,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$api_key_id = intval($row['api_key_id']);
$api_key_name = nullable_htmlentities($row['api_key_name']);
$api_key_secret = nullable_htmlentities("************" . substr($row['api_key_secret'], -4));
@@ -139,9 +139,16 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_api_key=<?php echo $api_key_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times mr-2"></i>Revoke
</a>
<?php if ($api_key_expire > date("Y-m-d H:i:s")) { ?>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?revoke_api_key=<?php echo $api_key_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times mr-2"></i>Revoke
</a>
<?php } ?>
<?php if ($api_key_expire < date("Y-m-d H:i:s")) { ?>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_api_key=<?php echo $api_key_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-times mr-2"></i>Delete
</a>
<?php } ?>
</div>
</div>
</td>
@@ -164,4 +171,3 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
require_once "../includes/footer.php";

View File

@@ -66,7 +66,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
$sql_types_filter = mysqli_query($mysqli, "SELECT DISTINCT app_log_type FROM app_logs ORDER BY app_log_type ASC");
while ($row = mysqli_fetch_array($sql_types_filter)) {
while ($row = mysqli_fetch_assoc($sql_types_filter)) {
$log_type = nullable_htmlentities($row['app_log_type']);
?>
<option <?php if ($type_filter == $log_type) { echo "selected"; } ?>><?php echo $log_type; ?></option>
@@ -85,7 +85,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
$sql_categories_filter = mysqli_query($mysqli, "SELECT DISTINCT app_log_category FROM app_logs ORDER BY app_log_category ASC");
while ($row = mysqli_fetch_array($sql_categories_filter)) {
while ($row = mysqli_fetch_assoc($sql_categories_filter)) {
$log_category = nullable_htmlentities($row['app_log_category']);
?>
<option <?php if ($category_filter == $log_category) { echo "selected"; } ?>><?php echo $log_category; ?></option>
@@ -141,7 +141,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$log_id = intval($row['app_log_id']);
$log_type = nullable_htmlentities($row['app_log_type']);
$log_category = nullable_htmlentities($row['app_log_category']);

View File

@@ -80,7 +80,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</div>
<div class="col-sm-2">
<div class="input-group mb-3 mb-md-0">
<select class="form-control select2" name="client" onchange="this.form.submit()">
@@ -88,7 +88,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
$sql_clients_filter = mysqli_query($mysqli, "SELECT * FROM clients ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql_clients_filter)) {
while ($row = mysqli_fetch_assoc($sql_clients_filter)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);
?>
@@ -108,7 +108,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
$sql_users_filter = mysqli_query($mysqli, "SELECT * FROM users ORDER BY user_name ASC");
while ($row = mysqli_fetch_array($sql_users_filter)) {
while ($row = mysqli_fetch_assoc($sql_users_filter)) {
$user_id = intval($row['user_id']);
$user_name = nullable_htmlentities($row['user_name']);
?>
@@ -128,7 +128,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
$sql_types_filter = mysqli_query($mysqli, "SELECT DISTINCT log_type FROM logs ORDER BY log_type ASC");
while ($row = mysqli_fetch_array($sql_types_filter)) {
while ($row = mysqli_fetch_assoc($sql_types_filter)) {
$log_type = nullable_htmlentities($row['log_type']);
?>
<option <?php if ($type_filter == $log_type) { echo "selected"; } ?>><?php echo $log_type; ?></option>
@@ -147,7 +147,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
$sql_actions_filter = mysqli_query($mysqli, "SELECT DISTINCT log_action FROM logs ORDER BY log_action ASC");
while ($row = mysqli_fetch_array($sql_actions_filter)) {
while ($row = mysqli_fetch_assoc($sql_actions_filter)) {
$log_action = nullable_htmlentities($row['log_action']);
?>
<option <?php if ($action_filter == $log_action) { echo "selected"; } ?>><?php echo $log_action; ?></option>
@@ -225,7 +225,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$log_id = intval($row['log_id']);
$log_type = nullable_htmlentities($row['log_type']);
$log_action = nullable_htmlentities($row['log_action']);
@@ -280,4 +280,3 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php
require_once "../includes/footer.php";

View File

@@ -111,7 +111,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$category_id = intval($row['category_id']);
$category_name = nullable_htmlentities($row['category_name']);
$category_color = nullable_htmlentities($row['category_color']);
@@ -135,11 +135,11 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if ($archived) {
?>
<a class="dropdown-item text-success confirm-link"
href="post.php?unarchive_category=<?php echo $category_id; ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Unarchive
href="post.php?restore_category=<?php echo $category_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Restore
</a>
<a class="dropdown-item text-danger confirm-link"
href="post.php?delete_category=<?php echo $category_id; ?>">
href="post.php?delete_category=<?php echo $category_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php
@@ -150,7 +150,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<a class="dropdown-item text-danger confirm-link"
href="post.php?archive_category=<?php echo $category_id; ?>">
href="post.php?archive_category=<?php echo $category_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Archive
</a>
<?php

View File

@@ -59,7 +59,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$id = intval($row['contract_template_id']);
$name = nullable_htmlentities($row['contract_template_name']);
$type = nullable_htmlentities($row['contract_template_type']);

View File

@@ -72,7 +72,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$custom_link_id = intval($row['custom_link_id']);
$custom_link_name = nullable_htmlentities($row['custom_link_name']);
$custom_link_uri = nullable_htmlentities($row['custom_link_uri']);
@@ -123,7 +123,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_custom_link=<?php echo $custom_link_id; ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_custom_link=<?php echo $custom_link_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>

View File

@@ -791,7 +791,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
// Copy primary_location and primary_contact to their new vars in their own respecting tables
$sql = mysqli_query($mysqli, "SELECT * FROM clients");
while($row = mysqli_fetch_array($sql)) {
while($row = mysqli_fetch_assoc($sql)) {
$primary_contact = $row['primary_contact'];
$primary_location = $row['primary_location'];
@@ -1666,7 +1666,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
if (CURRENT_DATABASE_VERSION == '1.3.9') {
// Migrate all Network Info from Assets to Interface Table and make it primary interface
$sql = mysqli_query($mysqli, "SELECT * FROM assets");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$asset_id = intval($row['asset_id']);
$mac = sanitizeInput($row['asset_mac']);
$ip = sanitizeInput($row['asset_ip']);
@@ -1945,7 +1945,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
if (CURRENT_DATABASE_VERSION == '1.5.7') {
// Create Users for contacts that have logins enabled and that are not archived
$contacts_sql = mysqli_query($mysqli, "SELECT * FROM `contacts` WHERE contact_archived_at IS NULL AND (contact_auth_method = 'local' OR contact_auth_method = 'azure')");
while($row = mysqli_fetch_array($contacts_sql)) {
while($row = mysqli_fetch_assoc($contacts_sql)) {
$contact_id = intval($row['contact_id']);
$contact_name = mysqli_real_escape_string($mysqli, $row['contact_name']);
$contact_email = mysqli_real_escape_string($mysqli, $row['contact_email']);
@@ -2325,7 +2325,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
`interface_link_status` VARCHAR(50) NULL,
`interface_link_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`interface_link_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `fk_interface_a`
FOREIGN KEY (`interface_a_id`)
REFERENCES `asset_interfaces` (`interface_id`)
@@ -3701,8 +3701,8 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
`ai_model_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
`ai_model_ai_provider_id` INT(11) NOT NULL,
PRIMARY KEY (`ai_model_id`),
FOREIGN KEY (`ai_model_ai_provider_id`)
REFERENCES `ai_providers`(`ai_provider_id`)
FOREIGN KEY (`ai_model_ai_provider_id`)
REFERENCES `ai_providers`(`ai_provider_id`)
ON DELETE CASCADE
)
");
@@ -3769,7 +3769,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
}
if (CURRENT_DATABASE_VERSION == '2.2.3') {
mysqli_query($mysqli, "CREATE TABLE `credits` (
`credit_id` INT(11) NOT NULL AUTO_INCREMENT,
`credit_amount` DECIMAL(15,2) NOT NULL,
@@ -3817,19 +3817,19 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "ALTER TABLE `credits` ADD INDEX (`credit_client_id`)");
mysqli_query($mysqli, "ALTER TABLE `credits` ADD INDEX (`credit_invoice_id`)");
mysqli_query($mysqli, "ALTER TABLE `credits` ADD INDEX (`credit_created_at`)");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.7'");
}
if (CURRENT_DATABASE_VERSION == '2.2.7') {
mysqli_query($mysqli, "ALTER TABLE `user_settings` ADD `user_config_theme_dark` TINYINT(1) NOT NULL DEFAULT 0 AFTER `user_config_signature`");
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_theme_dark`");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.8'");
}
if (CURRENT_DATABASE_VERSION == '2.2.8') {
mysqli_query($mysqli, "ALTER TABLE `products` ADD `product_type` ENUM('service', 'product') NOT NULL DEFAULT 'service' AFTER `product_name`");
mysqli_query($mysqli, "ALTER TABLE `products` ADD `product_code` VARCHAR(200) DEFAULT NULL AFTER `product_description`");
mysqli_query($mysqli, "ALTER TABLE `products` ADD `product_location` VARCHAR(250) DEFAULT NULL AFTER `product_code`");
@@ -3844,7 +3844,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
`stock_product_id` INT(11) NOT NULL,
PRIMARY KEY (`stock_id`)
)");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.9'");
}
@@ -3853,7 +3853,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
// Get Current Stripe Settings
$sql_stripe_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
$row = mysqli_fetch_array($sql_stripe_settings);
$row = mysqli_fetch_assoc($sql_stripe_settings);
$config_stripe_enable = intval($row['config_stripe_enable']);
if ($config_stripe_enable === 1) {
$config_stripe_publishable = mysqli_real_escape_string($mysqli, $row['config_stripe_publishable']);
@@ -3879,7 +3879,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
// Migrate Clients and Payment Method over
$sql_stripe_clients = mysqli_query($mysqli, "SELECT * FROM client_stripe WHERE stripe_pm IS NOT NULL AND stripe_pm != ''");
while ($row = mysqli_fetch_array($sql_stripe_clients)) {
while ($row = mysqli_fetch_assoc($sql_stripe_clients)) {
$client_id = intval($row['client_id']);
$stripe_id = mysqli_real_escape_string($mysqli, $row['stripe_id']);
$stripe_pm = mysqli_real_escape_string($mysqli, $row['stripe_pm']);
@@ -3931,13 +3931,13 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
if (CURRENT_DATABASE_VERSION == '2.3.0') {
// Migrate Payment Methods from Categories Table to new payment_methods table
$sql_categories = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_type = 'Payment Method' AND category_name != 'Stripe' AND category_archived_at IS NULL");
while ($row = mysqli_fetch_array($sql_categories)) {
while ($row = mysqli_fetch_assoc($sql_categories)) {
$category_name = sanitizeInput($row['category_name']);
mysqli_query($mysqli,"INSERT INTO payment_methods SET payment_method_name = '$category_name'");
}
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.1'");
}
@@ -3971,7 +3971,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
if (CURRENT_DATABASE_VERSION == '2.3.2') {
mysqli_query($mysqli, "ALTER TABLE settings
mysqli_query($mysqli, "ALTER TABLE settings
ADD `config_imap_provider` ENUM('standard_imap','google_oauth','microsoft_oauth') NULL DEFAULT NULL AFTER `config_mail_from_name`,
ADD `config_mail_oauth_client_id` VARCHAR(255) NULL AFTER `config_imap_provider`,
ADD `config_mail_oauth_client_secret` VARCHAR(255) NULL AFTER `config_mail_oauth_client_id`,
@@ -3986,7 +3986,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
if (CURRENT_DATABASE_VERSION == '2.3.3') {
mysqli_query($mysqli, "ALTER TABLE settings
mysqli_query($mysqli, "ALTER TABLE settings
ADD `config_smtp_provider` ENUM('standard_smtp','google_oauth','microsoft_oauth') NULL DEFAULT NULL AFTER `config_start_page`
");
@@ -4026,7 +4026,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.5'");
}
if (CURRENT_DATABASE_VERSION == '2.3.5') {
mysqli_query($mysqli, "ALTER TABLE `settings` CHANGE `config_smtp_provider` `config_smtp_provider` VARCHAR(200) DEFAULT NULL");
mysqli_query($mysqli, "ALTER TABLE `settings` CHANGE `config_imap_provider` `config_imap_provider` VARCHAR(200) DEFAULT NULL");
@@ -4109,7 +4109,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
FOREIGN KEY (`contract_client_id`) REFERENCES `clients`(`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.7'");
}
@@ -4130,7 +4130,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.8'");
}
@@ -4154,10 +4154,191 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.9'");
}
// if (CURRENT_DATABASE_VERSION == '2.3.9') {
// // Insert queries here required to update to DB version 2.4.0
if (CURRENT_DATABASE_VERSION == '2.3.9') {
mysqli_query($mysqli, "ALTER TABLE `clients` ADD `client_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `client_notes`");
mysqli_query($mysqli, "ALTER TABLE `locations` ADD `location_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `location_notes`");
mysqli_query($mysqli, "ALTER TABLE `vendors` ADD `vendor_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `vendor_notes`");
mysqli_query($mysqli, "ALTER TABLE `software` ADD `software_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `software_notes`");
mysqli_query(
$mysqli,
"ALTER TABLE `credentials`
CHANGE `credential_important` `credential_favorite`
TINYINT(1) NOT NULL DEFAULT 0
AFTER `credential_note`"
);
mysqli_query($mysqli, "ALTER TABLE `assets` DROP `asset_important`");
mysqli_query($mysqli, "ALTER TABLE `assets` ADD `asset_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `asset_notes`");
mysqli_query($mysqli, "ALTER TABLE `documents` DROP `document_important`");
mysqli_query($mysqli, "ALTER TABLE `documents` ADD `document_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `document_client_visible`");
mysqli_query($mysqli, "ALTER TABLE `racks` ADD `rack_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `rack_notes`");
mysqli_query($mysqli, "ALTER TABLE `files` DROP `file_important`");
mysqli_query($mysqli, "ALTER TABLE `files` ADD `file_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `file_mime_type`");
mysqli_query($mysqli, "ALTER TABLE `networks` ADD `network_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `network_notes`");
mysqli_query($mysqli, "ALTER TABLE `domains` ADD `domain_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `domain_notes`");
mysqli_query($mysqli, "ALTER TABLE `certificates` ADD `certificate_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `certificate_notes`");
mysqli_query($mysqli, "ALTER TABLE `services` ADD `service_favorite` TINYINT(1) NOT NULL DEFAULT '0' AFTER `service_notes`");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.4.0'");
}
if (CURRENT_DATABASE_VERSION == '2.4.0') {
mysqli_query($mysqli, "
CREATE TABLE `quote_items` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(200) NOT NULL,
`item_description` text DEFAULT NULL,
`item_quantity` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_price` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_subtotal` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_tax` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_total` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_order` int(11) NOT NULL DEFAULT 0,
`item_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`item_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`item_archived_at` datetime DEFAULT NULL,
`item_tax_id` int(11) NOT NULL DEFAULT 0,
`item_product_id` int(11) NOT NULL DEFAULT 0,
`item_quote_id` int(11) NOT NULL,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
mysqli_query($mysqli, "
CREATE TABLE `recurring_invoice_items` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(200) NOT NULL,
`item_description` text DEFAULT NULL,
`item_quantity` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_price` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_subtotal` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_tax` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_total` decimal(15,2) NOT NULL DEFAULT 0.00,
`item_order` int(11) NOT NULL DEFAULT 0,
`item_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`item_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`item_archived_at` datetime DEFAULT NULL,
`item_tax_id` int(11) NOT NULL DEFAULT 0,
`item_product_id` int(11) NOT NULL DEFAULT 0,
`item_recurring_invoice_id` int(11) NOT NULL,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.4.1'");
}
if (CURRENT_DATABASE_VERSION == '2.4.1') {
// Migrate Items
mysqli_query($mysqli, "
INSERT INTO `recurring_invoice_items` (
`item_name`,
`item_description`,
`item_quantity`,
`item_price`,
`item_subtotal`,
`item_tax`,
`item_total`,
`item_order`,
`item_created_at`,
`item_updated_at`,
`item_archived_at`,
`item_tax_id`,
`item_product_id`,
`item_recurring_invoice_id`
)
SELECT
`item_name`,
`item_description`,
`item_quantity`,
`item_price`,
`item_subtotal`,
`item_tax`,
`item_total`,
`item_order`,
`item_created_at`,
`item_updated_at`,
`item_archived_at`,
`item_tax_id`,
`item_product_id`,
`item_recurring_invoice_id`
FROM `invoice_items`
WHERE `item_recurring_invoice_id` != 0
");
mysqli_query($mysqli, "
INSERT INTO `quote_items` (
`item_name`,
`item_description`,
`item_quantity`,
`item_price`,
`item_subtotal`,
`item_tax`,
`item_total`,
`item_order`,
`item_created_at`,
`item_updated_at`,
`item_archived_at`,
`item_tax_id`,
`item_product_id`,
`item_quote_id`
)
SELECT
`item_name`,
`item_description`,
`item_quantity`,
`item_price`,
`item_subtotal`,
`item_tax`,
`item_total`,
`item_order`,
`item_created_at`,
`item_updated_at`,
`item_archived_at`,
`item_tax_id`,
`item_product_id`,
`item_quote_id`
FROM `invoice_items`
WHERE `item_quote_id` != 0
");
mysqli_query($mysqli, "
DELETE FROM `invoice_items`
WHERE `item_recurring_invoice_id` != 0
");
mysqli_query($mysqli, "
DELETE FROM `invoice_items`
WHERE `item_quote_id` != 0
");
mysqli_query($mysqli, "
ALTER TABLE `invoice_items`
DROP COLUMN `item_quote_id`,
DROP COLUMN `item_recurring_invoice_id`
");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.4.2'");
}
//
// // if (CURRENT_DATABASE_VERSION == '2.4.2') {
// // Insert queries here required to update to DB version 2.4.3
// // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.4.0'");
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.4.3'");
// }
} else {

View File

@@ -20,7 +20,7 @@
<div class="card card-dark">
<div class="card-header py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file mr-2"></i>Document Templates</h3>
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file-alt mr-2"></i>Document Templates</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/document_template/document_template_add.php" data-modal-size="xl">
<i class="fas fa-plus mr-2"></i>New Template
@@ -66,21 +66,30 @@
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$document_template_id = intval($row['document_template_id']);
$document_template_name = nullable_htmlentities($row['document_template_name']);
$document_template_description = nullable_htmlentities($row['document_template_description']);
$document_template_content = nullable_htmlentities($row['document_template_content']);
$document_template_created_by_name = nullable_htmlentities($row['user_name']);
$document_template_created_at = nullable_htmlentities($row['document_template_created_at']);
$document_template_updated_at = nullable_htmlentities($row['document_template_updated_at']);
$document_template_updated_at = nullable_htmlentities(getFallback($row['document_template_updated_at']));
?>
<tr>
<td>
<a class="text-bold" href="document_template_details.php?document_template_id=<?php echo $document_template_id; ?>"><i class="fas fa-fw fa-file-alt text-dark"></i> <?php echo $document_template_name; ?></a>
<div class="mt-1 text-secondary"><?php echo $document_template_description; ?></div>
<a class="text-dark ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<div class="media">
<i class="fas fa-fw fa-2x fa-file-alt mr-2"></i>
<div class="media-body">
<div><?= $document_template_name ?></div>
<div><small class="text-secondary"><?= $document_template_description ?></small></div>
</div>
</div>
</a>
</td>
<td>
<?php echo $document_template_created_at; ?>
@@ -93,13 +102,17 @@
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="document_template_details.php?document_template_id=<?= $document_template_id ?>">
<i class="fas fa-fw fa-eye mr-2"></i>View
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item ajax-modal" href="#"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold" href="post.php?delete_document_template=<?php echo $document_template_id; ?>">
<a class="dropdown-item text-danger text-bold" href="post.php?delete_document_template=<?php echo $document_template_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>

View File

@@ -23,7 +23,7 @@ if (mysqli_num_rows($sql_document) == 0) {
exit();
}
$row = mysqli_fetch_array($sql_document);
$row = mysqli_fetch_assoc($sql_document);
$document_template_name = nullable_htmlentities($row['document_template_name']);
$document_template_description = nullable_htmlentities($row['document_template_description']);
@@ -43,19 +43,19 @@ $document_template_updated_at = nullable_htmlentities($row['document_template_up
<li class="breadcrumb-item">
<a href="document_template.php">Document Templates</a>
</li>
<li class="breadcrumb-item active"><i class="fas fa-file mr-2"></i><?php echo $document_template_name; ?></li>
<li class="breadcrumb-item active"><i class="fas fa-file-alt mr-2"></i><?php echo $document_template_name; ?></li>
</ol>
<div class="card card-dark">
<div class="card-header py-2">
<div class="card-header">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-file mr-2"></i><?php echo $document_template_name; ?></h3>
<h3 class="card-title mt-1"><i class="fa fa-fw fa-file-alt mr-2"></i><?php echo $document_template_name; ?></h3>
<div class="card-tools">
<button type="button" class="btn btn-primary ajax-modal"
<button type="button" class="btn btn-tool ajax-modal"
data-modal-size="xl"
data-modal-url="modals/document_template/document_template_edit.php?id=<?= $document_template_id ?>">
<i class="fas fa-edit mr-2"></i>Edit
<i class="fas fa-edit mr-2"></i>
</button>
</div>
</div>

View File

@@ -139,7 +139,7 @@
</li>
<li class="nav-item">
<a href="/admin/document_template.php" class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['document_template.php', 'document_template_details.php']) ? 'active' : ''); ?>">
<i class="nav-icon fas fa-file"></i>
<i class="nav-icon fas fa-file-alt"></i>
<p>Document Templates</p>
</a>
</li>
@@ -293,7 +293,7 @@
ORDER BY custom_link_order ASC, custom_link_name ASC"
);
while ($row = mysqli_fetch_array($sql_custom_links)) {
while ($row = mysqli_fetch_assoc($sql_custom_links)) {
$custom_link_name = nullable_htmlentities($row['custom_link_name']);
$custom_link_uri = sanitize_url($row['custom_link_uri']);
$custom_link_icon = nullable_htmlentities($row['custom_link_icon']);

View File

@@ -116,7 +116,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$email_id = intval($row['email_id']);
$email_from = nullable_htmlentities($row['email_from']);
$email_from_name = nullable_htmlentities($row['email_from_name']);
@@ -163,12 +163,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<!-- Show force resend if all retries have failed -->
<?php if ($email_status == 2 && $email_attempts > 3) { ?>
<a class="btn btn-sm btn-success" href="post.php?send_failed_mail=<?php echo $email_id; ?>"><i class="fas fa-fw fa-paper-plane"></i></a>
<a class="btn btn-sm btn-success" href="post.php?send_failed_mail=<?php echo $email_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-paper-plane"></i></a>
<?php } ?>
<!-- Allow cancelling a message if it hasn't yet been picked up (e.g. stuck/bugged) -->
<?php if ($email_status !== 3) { ?>
<a class="btn btn-sm btn-danger confirm-link" href="post.php?cancel_mail=<?php echo $email_id; ?>"><i class="fas fa-fw fa-trash"></i></a>
<a class="btn btn-sm btn-danger confirm-link" href="post.php?cancel_mail=<?php echo $email_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"><i class="fas fa-fw fa-trash"></i></a>
<?php } ?>
</td>

View File

@@ -27,7 +27,7 @@ ob_start();
<option value="">- Select an AI Provider -</option>
<?php
$sql_ai_providers = mysqli_query($mysqli, "SELECT * FROM ai_providers");
while ($row = mysqli_fetch_array($sql_ai_providers)) {
while ($row = mysqli_fetch_assoc($sql_ai_providers)) {
$ai_provider_id = intval($row['ai_provider_id']);
$ai_provider_name = nullable_htmlentities($row['ai_provider_name']);

View File

@@ -6,7 +6,7 @@ $model_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models WHERE ai_model_id = $model_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$ai_model_ai_provider_id = intval($row['ai_model_ai_provider_id']);
$model_id = intval($row['ai_model_id']);
$model_name = nullable_htmlentities($row['ai_model_name']);
@@ -39,7 +39,7 @@ ob_start();
<option value="">- Select an AI Provider -</option>
<?php
$sql_ai_providers = mysqli_query($mysqli, "SELECT * FROM ai_providers");
while ($row = mysqli_fetch_array($sql_ai_providers)) {
while ($row = mysqli_fetch_assoc($sql_ai_providers)) {
$ai_provider_id = intval($row['ai_provider_id']);
$ai_provider_name = nullable_htmlentities($row['ai_provider_name']);

View File

@@ -6,7 +6,7 @@ $provider_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM ai_providers WHERE ai_provider_id = $provider_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$provider_name = nullable_htmlentities($row['ai_provider_name']);
$url = nullable_htmlentities($row['ai_provider_api_url']);
$key = nullable_htmlentities($row['ai_provider_api_key']);

View File

@@ -64,7 +64,7 @@ ob_start();
<option value="0"> ALL CLIENTS </option>
<?php
$sql = mysqli_query($mysqli, "SELECT client_id, client_name FROM clients WHERE client_archived_at IS NULL ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']); ?>
<option value="<?php echo $client_id; ?>"><?php echo "$client_name (Client ID: $client_id)"; ?></option>

View File

@@ -6,7 +6,7 @@ $category_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_id = $category_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$category_name = nullable_htmlentities($row['category_name']);
$category_color = nullable_htmlentities($row['category_color']);
$category_type = nullable_htmlentities($row['category_type']);

View File

@@ -8,7 +8,7 @@ $update_frequency_array = ['Manual', 'Annually', '2 Year', '3 Year', '5 Year', '
// Fetch existing template
$sql = mysqli_query($mysqli, "SELECT * FROM contract_templates WHERE contract_template_id = $contract_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
// Assign locals
$name = nullable_htmlentities($row['contract_template_name']);

View File

@@ -13,6 +13,7 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="modal-body">

View File

@@ -5,7 +5,7 @@ require_once '../../../includes/modal_header.php';
$custom_link_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM custom_links WHERE custom_link_id = $custom_link_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$custom_link_name = nullable_htmlentities($row['custom_link_name']);
$custom_link_uri = nullable_htmlentities($row['custom_link_uri']);
$custom_link_icon = nullable_htmlentities($row['custom_link_icon']);
@@ -24,9 +24,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="custom_link_id" value="<?php echo $custom_link_id; ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -13,6 +13,8 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
@@ -39,12 +41,12 @@ ob_start();
<div class="form-group">
<input type="text" class="form-control" name="description" placeholder="Enter a short summary">
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_document_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Create</button>
<button type="submit" name="add_document_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>

View File

@@ -5,12 +5,11 @@ require_once '../../../includes/modal_header.php';
$document_template_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM document_templates WHERE document_template_id = $document_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$document_template_name = nullable_htmlentities($row['document_template_name']);
$document_template_description = nullable_htmlentities($row['document_template_description']);
$document_template_content = nullable_htmlentities($row['document_template_content']);
// Generate the HTML form content using output buffering.
ob_start();
?>
@@ -21,7 +20,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="document_template_id" value="<?php echo $document_template_id; ?>">
<div class="modal-body">
<div class="form-group">
@@ -38,7 +39,7 @@ ob_start();
</div>
<div class="modal-footer">
<button type="submit" name="edit_document_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
<button type="submit" name="edit_document_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save template</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>

View File

@@ -17,7 +17,7 @@ $purifier_config->set('URI.AllowedSchemes', ['data' => true, 'src' => true, 'htt
$purifier = new HTMLPurifier($purifier_config);
$sql = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_id = $email_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$email_from = nullable_htmlentities($row['email_from']);
$email_from_name = nullable_htmlentities($row['email_from_name']);

View File

@@ -6,7 +6,7 @@ $payment_method_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM payment_methods WHERE payment_method_id = $payment_method_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$payment_method_id = intval($row['payment_method_id']);
$payment_method_name = nullable_htmlentities($row['payment_method_name']);
$payment_method_description = nullable_htmlentities($row['payment_method_description']);

View File

@@ -79,7 +79,7 @@ ob_start();
<?php
$sql = mysqli_query($mysqli, "SELECT account_id, account_name FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']);
?>
@@ -125,7 +125,7 @@ ob_start();
<?php
$sql = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_client_id = 0 AND vendor_archived_at IS NULL ORDER BY vendor_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$vendor_id = intval($row['vendor_id']);
$vendor_name = nullable_htmlentities($row['vendor_name']);
?>
@@ -149,7 +149,7 @@ ob_start();
<?php
$sql = mysqli_query($mysqli, "SELECT category_id, category_name FROM categories WHERE category_type = 'Expense' AND category_archived_at IS NULL ORDER BY category_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$category_id = intval($row['category_id']);
$category_name = nullable_htmlentities($row['category_name']);
?>

View File

@@ -6,7 +6,7 @@ $provider_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM payment_providers WHERE payment_provider_id = $provider_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$provider_name = nullable_htmlentities($row['payment_provider_name']);
$public_key = nullable_htmlentities($row['payment_provider_public_key']);
$private_key = nullable_htmlentities($row['payment_provider_private_key']);
@@ -78,7 +78,7 @@ ob_start();
<?php
$sql = mysqli_query($mysqli, "SELECT account_id, account_name FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id_select = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']);
?>
@@ -117,7 +117,7 @@ ob_start();
<?php
$sql = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_client_id = 0 AND vendor_archived_at IS NULL ORDER BY vendor_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$vendor_id_select = intval($row['vendor_id']);
$vendor_name = nullable_htmlentities($row['vendor_name']);
?>
@@ -143,7 +143,7 @@ ob_start();
<?php
$sql_category = mysqli_query($mysqli, "SELECT category_id, category_name FROM categories WHERE category_type = 'Expense' AND category_archived_at IS NULL ORDER BY category_name ASC");
while ($row = mysqli_fetch_array($sql_category)) {
while ($row = mysqli_fetch_assoc($sql_category)) {
$category_id_select = intval($row['category_id']);
$category_name = nullable_htmlentities($row['category_name']);
?>

View File

@@ -12,6 +12,7 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -5,7 +5,7 @@ require_once '../../../includes/modal_header.php';
$project_template_id = intval($_GET['project_template_id']);
$sql = mysqli_query($mysqli, "SELECT * FROM project_templates WHERE project_template_id = $project_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$project_template_name = nullable_htmlentities($row['project_template_name']);
$project_template_description = nullable_htmlentities($row['project_template_description']);
@@ -20,6 +20,7 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="project_template_id" value="<?php echo $project_template_id; ?>">
<div class="modal-body">
@@ -44,7 +45,7 @@ ob_start();
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_project_template" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>

View File

@@ -15,7 +15,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="project_template_id" value="<?php echo $project_template_id; ?>">
<div class="modal-body">
<div class="form-group">
@@ -36,7 +38,7 @@ ob_start();
AND ticket_template_archived_at IS NULL
ORDER BY ticket_template_name ASC"
);
while ($row = mysqli_fetch_array($sql_ticket_templates_select)) {
while ($row = mysqli_fetch_assoc($sql_ticket_templates_select)) {
$ticket_template_id_select = intval($row['ticket_template_id']);
$ticket_template_name_select = nullable_htmlentities($row['ticket_template_name']);
?>

View File

@@ -11,52 +11,203 @@ ob_start();
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-role-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-role-permissions">Permissions</a>
</li>
</ul>
<hr>
<div class="tab-content">
<div class="form-group">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
<!-- DETAILS TAB -->
<div class="tab-pane fade show active" id="pills-role-details">
<div class="form-group">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<input type="text" class="form-control" name="role_name" placeholder="Role Name" maxlength="200" required>
</div>
<input type="text" class="form-control" name="role_name" placeholder="Role Name" maxlength="200" required>
</div>
<div class="form-group">
<label>Description <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-chevron-right"></i></span>
</div>
<input type="text" class="form-control" name="role_description" placeholder="Role Description" maxlength="200" required>
</div>
</div>
<div class="form-group">
<label>Admin Access <strong class="text-danger">*</strong></label>
<div class="custom-control custom-radio mb-2">
<input type="radio" class="custom-control-input" id="admin_no" name="role_is_admin" value="0" checked required>
<label class="custom-control-label" for="admin_no">
No - use permissions on the next tab
</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" class="custom-control-input" id="admin_yes" name="role_is_admin" value="1" required>
<label class="custom-control-label" for="admin_yes">
Yes - this role should have full admin access
</label>
</div>
</div>
</div>
<div class="form-group">
<label>Description <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-chevron-right"></i></span>
</div>
<input type="text" class="form-control" name="role_description" placeholder="Role Description" maxlength="200" required>
</div>
</div>
<!-- PERMISSIONS TAB -->
<div class="tab-pane fade" id="pills-role-permissions">
<div class="form-group">
<label>Admin Access <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tools"></i></span>
<?php
// Enumerate modules
$sql_modules = mysqli_query($mysqli, "SELECT * FROM modules");
while ($row_modules = mysqli_fetch_assoc($sql_modules)) {
$module_id = intval($row_modules['module_id']);
// raw for name, escaped for display
$module_name_raw = $row_modules['module_name'];
$module_name_display = ucfirst(str_replace("module_", "", $module_name_raw));
$module_name_display_safe = nullable_htmlentities($module_name_display);
$module_description = nullable_htmlentities($row_modules['module_description']);
// default for new role
$module_permission = 0;
$field_name = $module_id . "##" . $module_name_raw;
$group_id = "perm_group_$module_id";
?>
<div class="form-group">
<label><?= $module_name_display_safe ?> <strong class="text-danger">*</strong></label>
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons" role="group"
aria-label="Permissions for <?= $module_name_display_safe ?>">
<label class="btn btn-outline-secondary btn-sm active" title="No Access">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_0"
value="0"
autocomplete="off"
checked
required
>
None
</label>
<label class="btn btn-outline-primary btn-sm" title="Viewing Only">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_1"
value="1"
autocomplete="off"
>
<i class="fas fa-fw fa-eye mr-1"></i>Read
</label>
<label class="btn btn-outline-warning btn-sm" title="Read, Edit, Archive">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_2"
value="2"
autocomplete="off"
>
<i class="fas fa-fw fa-edit mr-1"></i>Modify
</label>
<label class="btn btn-outline-danger btn-sm" title="Read, Edit, Archive, Delete">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_3"
value="3"
autocomplete="off"
>
<i class="fas fa-fw fa-trash mr-1"></i>Full
</label>
</div>
<small class="form-text text-muted mt-2"><?= $module_description ?></small>
</div>
<select class="form-control select2" name="role_is_admin" required>
<option value="0">No - edit after creation to set permissions</option>
<option value="1">Yes - this role should have full admin access</option>
</select>
</div>
<?php } // end while ?>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="add_role" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
<button type="submit" name="add_role" class="btn btn-primary text-bold">
<i class="fas fa-check mr-2"></i>Create
</button>
<button type="button" class="btn btn-light" data-dismiss="modal">
<i class="fas fa-times mr-2"></i>Cancel
</button>
</div>
</form>
<script>
// Optional: when Admin Yes is selected, disable permission radios + switch to Details tab
(function () {
function setPermissionsEnabled(enabled) {
const permsTab = document.getElementById('pills-role-permissions');
if (!permsTab) return;
permsTab.querySelectorAll('input[type="radio"]').forEach(function (el) {
el.disabled = !enabled;
});
// also visually dim the tab content
permsTab.style.opacity = enabled ? '1' : '0.5';
}
const adminYes = document.getElementById('admin_yes');
const adminNo = document.getElementById('admin_no');
function refresh() {
const isAdmin = adminYes && adminYes.checked;
setPermissionsEnabled(!isAdmin);
if (isAdmin) {
// move user back to Details tab (avoids confusion)
const detailsTab = document.querySelector('a[href="#pills-role-details"]');
if (detailsTab) detailsTab.click();
}
}
if (adminYes && adminNo) {
adminYes.addEventListener('change', refresh);
adminNo.addEventListener('change', refresh);
refresh();
}
})();
</script>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -6,7 +6,7 @@ $role_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM user_roles WHERE role_id = $role_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$role_name = nullable_htmlentities($row['role_name']);
$role_description = nullable_htmlentities($row['role_description']);
$role_admin = intval($row['role_is_admin']);
@@ -31,36 +31,36 @@ if (empty($user_names_string)) {
$user_names_string = "-";
}
// Generate the HTML form content using output buffering.
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-user-shield mr-2"></i>Editing role:
<strong><?php echo $role_name; ?></strong></h5>
<strong><?= $role_name ?></strong></h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="role_id" value="<?php echo $role_id; ?>">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="role_id" value="<?= $role_id ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-role-details<?php echo $role_id; ?>">Details</a>
<a class="nav-link active" data-toggle="pill" href="#pills-role-details">Details</a>
</li>
<?php if (!$role_admin) { ?>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#pills-role-access<?php echo $role_id; ?>">Access</a>
<a class="nav-link" data-toggle="pill" href="#pills-role-permissions">Permissions</a>
</li>
<?php } ?>
</ul>
<hr>
<div class="tab-content">
<div class="tab-pane fade show active" id="pills-role-details<?php echo $role_id; ?>">
<div class="tab-pane fade show active" id="pills-role-details">
<div class="form-group">
<label>Name <strong class="text-danger">*</strong></label>
@@ -68,7 +68,7 @@ ob_start();
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user-shield"></i></span>
</div>
<input type="text" class="form-control" name="role_name" placeholder="Role Name" maxlength="200" value="<?php echo $role_name; ?>" required>
<input type="text" class="form-control" name="role_name" placeholder="Role Name" maxlength="200" value="<?= $role_name ?>" required>
</div>
</div>
@@ -78,27 +78,33 @@ ob_start();
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-chevron-right"></i></span>
</div>
<input type="text" class="form-control" name="role_description" placeholder="Role Description" maxlength="200" value="<?php echo $role_description; ?>" required>
<input type="text" class="form-control" name="role_description" placeholder="Role Description" maxlength="200" value="<?= $role_description ?>" required>
</div>
</div>
<div class="form-group">
<label>Admin Access <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-tools"></i></span>
</div>
<select class="form-control select2" name="role_is_admin" required>
<option value="1" <?php if ($role_admin) { echo 'selected'; } ?> >Yes - this role should have full admin access</option>
<option value="0" <?php if (!$role_admin) { echo 'selected'; } ?>>No - use permissions on the next tab</option>
</select>
<div class="custom-control custom-radio mb-2">
<input type="radio" class="custom-control-input" id="admin_yes" name="role_is_admin" value="1"
<?php if ($role_admin) { echo 'checked'; } ?> required>
<label class="custom-control-label" for="admin_yes">
Yes - this role should have full admin access
</label>
</div>
<div class="custom-control custom-radio">
<input type="radio" class="custom-control-input" id="admin_no" name="role_is_admin" value="0"
<?php if (!$role_admin) { echo 'checked'; } ?> required>
<label class="custom-control-label" for="admin_no">
No - use permissions on the next tab
</label>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-role-access<?php echo $role_id; ?>">
<?php if (!$role_admin) { ?>
<div class="tab-pane fade" id="pills-role-permissions">
<?php if ($role_admin) { ?>
<div class="alert alert-warning"><strong>Module permissions do not apply to Admins.</strong></div>
@@ -108,14 +114,14 @@ ob_start();
// Enumerate modules
$sql_modules = mysqli_query($mysqli, "SELECT * FROM modules");
while ($row_modules = mysqli_fetch_array($sql_modules)) {
while ($row_modules = mysqli_fetch_assoc($sql_modules)) {
$module_id = intval($row_modules['module_id']);
$module_name = nullable_htmlentities($row_modules['module_name']);
$module_name_display = ucfirst(str_replace("module_","",$module_name));
$module_description = nullable_htmlentities($row_modules['module_description']);
// Get permission level for module
$module_permission_row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT user_role_permission_level FROM user_role_permissions WHERE module_id = $module_id AND user_role_id = $role_id LIMIT 1"));
$module_permission_row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT user_role_permission_level FROM user_role_permissions WHERE module_id = $module_id AND user_role_id = $role_id LIMIT 1"));
$module_permission = 0;
if ($module_permission_row) {
$module_permission = $module_permission_row['user_role_permission_level'];
@@ -123,22 +129,73 @@ ob_start();
?>
<div class="form-group">
<label> <?php echo $module_name_display ?> <strong class="text-danger">*</strong></label>
<div class="input-group">
<select class="form-control select2" name="<?php echo "$module_id##$module_name" ?>" required>
<option value="0" <?php if ($module_permission == 0) { echo 'selected'; } ?> >None</option>
<option value="1" <?php if ($module_permission == 1) { echo 'selected'; } ?> >Read</option>
<option value="2" <?php if ($module_permission == 2) { echo 'selected'; } ?>>Modify (Read, Edit, Archive)</option>
<option value="3" <?php if ($module_permission == 3) { echo 'selected'; } ?>>Full (Read, Edit, Archive, Delete)</option>
</select>
<label> <?= $module_name_display ?> <strong class="text-danger">*</strong></label>
<?php
$field_name = "$module_id##$module_name";
$group_id = "perm_group_$module_id";
?>
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons" role="group" aria-label="Permissions for <?= $module_name_display ?>">
<label class="btn btn-outline-secondary btn-sm <?php if ($module_permission == 0) { echo 'active'; } ?>" title="No Access">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_0"
value="0"
autocomplete="off"
<?php if ($module_permission == 0) { echo 'checked'; } ?>
required
>
None
</label>
<label class="btn btn-outline-primary btn-sm <?php if ($module_permission == 1) { echo 'active'; } ?>" title="Viewing Only">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_1"
value="1"
autocomplete="off"
<?php if ($module_permission == 1) { echo 'checked'; } ?>
>
<i class="fas fa-fw fa-eye mr-1"></i>Read
</label>
<label class="btn btn-outline-warning btn-sm <?php if ($module_permission == 2) { echo 'active'; } ?>" title="Read, Edit, Archive">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_2"
value="2"
autocomplete="off"
<?php if ($module_permission == 2) { echo 'checked'; } ?>
>
<i class="fas fa-fw fa-edit mr-1"></i>Modify
</label>
<label class="btn btn-outline-danger btn-sm <?php if ($module_permission == 3) { echo 'active'; } ?>" title="Read, Edit, Archive, Delete">
<input
type="radio"
name="<?= $field_name ?>"
id="<?= $group_id ?>_3"
value="3"
autocomplete="off"
<?php if ($module_permission == 3) { echo 'checked'; } ?>
>
<i class="fas fa-fw fa-trash mr-1"></i>Full
</label>
</div>
<small class="form-text text-muted"><?php echo $module_description ?></small>
<small class="form-text text-muted mt-2"><?= $module_description ?></small>
</div>
<?php } // End while ?>
</div>
<?php } ?>
</div>

View File

@@ -13,6 +13,8 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -5,7 +5,7 @@ require_once '../../../includes/modal_header.php';
$software_template_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM software_templates WHERE software_template_id = $software_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$software_name = nullable_htmlentities($row['software_template_name']);
$software_version = nullable_htmlentities($row['software_template_version']);
$software_description = nullable_htmlentities($row['software_template_description']);
@@ -24,7 +24,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="software_template_id" value="<?php echo $software_template_id; ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -30,7 +30,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="type" value="<?php echo $type; ?>">
<div class="modal-body">
<div class="form-group">
<label>Name <strong class="text-danger">*</strong></label>
@@ -41,13 +43,13 @@ ob_start();
<input type="text" class="form-control" name="name" placeholder="Tag name" maxlength="200" required autofocus>
</div>
</div>
<?php if (isset($_GET['type'])) { ?>
<input type="hidden" name="type" value="<?= $type ?>">
<?php } else { ?>
<div class="form-group">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
@@ -64,7 +66,7 @@ ob_start();
</select>
</div>
</div>
<?php } ?>
<div class="form-group">

View File

@@ -6,7 +6,7 @@ $tag_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM tags WHERE tag_id = $tag_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$tag_name = nullable_htmlentities($row['tag_name']);
$tag_type = intval($row['tag_type']);
$tag_color = nullable_htmlentities($row['tag_color']);
@@ -35,7 +35,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="tag_id" value="<?php echo $tag_id; ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -5,7 +5,7 @@ require_once '../../../includes/modal_header.php';
$tax_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM taxes WHERE tax_id = $tax_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$tax_name = nullable_htmlentities($row['tax_name']);
$tax_percent = floatval($row['tax_percent']);

View File

@@ -10,6 +10,7 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -5,7 +5,7 @@ require_once '../../../includes/modal_header.php';
$ticket_status_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_id = $ticket_status_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$ticket_status_name = nullable_htmlentities($row['ticket_status_name']);
$ticket_status_color = nullable_htmlentities($row['ticket_status_color']);
$ticket_status_order = intval($row['ticket_status_order']);
@@ -22,7 +22,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<input type="hidden" name="ticket_status_id" value="<?php echo $ticket_status_id; ?>">
<div class="modal-body">
<div class="form-group">

View File

@@ -12,6 +12,8 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
@@ -59,7 +61,7 @@ ob_start();
<?php
$sql_project_templates = mysqli_query($mysqli, "SELECT * FROM project_templates WHERE project_template_archived_at IS NULL ORDER BY project_template_name ASC");
while ($row = mysqli_fetch_array($sql_project_templates)) {
while ($row = mysqli_fetch_assoc($sql_project_templates)) {
$project_template_id_select = intval($row['project_template_id']);
$project_template_name_select = nullable_htmlentities($row['project_template_name']); ?>
<option value="<?php echo $project_template_id_select; ?>"><?php echo $project_template_name_select; ?></option>

View File

@@ -9,7 +9,9 @@
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="ticket_template_id" value="<?php echo $ticket_template_id; ?>">
<div class="modal-body">
<div class="form-group">
@@ -45,7 +47,7 @@
<input type="text" class="form-control" name="description" value="<?php echo $ticket_template_description; ?>" placeholder="Short description">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" name="edit_ticket_template" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>

View File

@@ -6,7 +6,7 @@ $task_template_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM task_templates WHERE task_template_id = $task_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$task_template_name = nullable_htmlentities($row['task_template_name']);
$task_template_order = intval($row['task_template_order']);
$task_template_completion_estimate = intval($row['task_template_completion_estimate']);
@@ -24,8 +24,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="task_template_id" value="<?php echo $task_template_id; ?>">
<div class="modal-body">
<div class="form-group">
@@ -47,7 +48,7 @@ ob_start();
<input type="number" class="form-control" name="completion_estimate" placeholder="Estimated time to complete task in mins" value="<?php echo $task_template_completion_estimate; ?>">
</div>
</div>
</div>
<div class="modal-footer">

View File

@@ -76,7 +76,7 @@ ob_start();
<option value="">- Role -</option>
<?php
$sql_user_roles = mysqli_query($mysqli, "SELECT * FROM user_roles WHERE role_archived_at IS NULL");
while ($row = mysqli_fetch_array($sql_user_roles)) {
while ($row = mysqli_fetch_assoc($sql_user_roles)) {
$role_id = intval($row['role_id']);
$role_name = nullable_htmlentities($row['role_name']);
@@ -129,7 +129,7 @@ ob_start();
<?php
$sql_client_select = mysqli_query($mysqli, "SELECT * FROM clients WHERE client_archived_at IS NULL ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql_client_select)) {
while ($row = mysqli_fetch_assoc($sql_client_select)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);

View File

@@ -6,7 +6,7 @@ $user_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM users WHERE users.user_id = $user_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$user_name = nullable_htmlentities($row['user_name']);
$user_email = nullable_htmlentities($row['user_email']);
$user_avatar = nullable_htmlentities($row['user_avatar']);
@@ -60,7 +60,7 @@ ob_start();
<option value="0">No one</option>
<?php
$sql_users = mysqli_query($mysqli, "SELECT * FROM users WHERE user_type = 1 AND user_archived_at IS NULL");
while ($row = mysqli_fetch_array($sql_users)) {
while ($row = mysqli_fetch_assoc($sql_users)) {
$user_id_select = intval($row['user_id']);
$user_name_select = nullable_htmlentities($row['user_name']);

View File

@@ -4,12 +4,12 @@ require_once '../../../includes/modal_header.php';
$user_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM users
$sql = mysqli_query($mysqli, "SELECT * FROM users
LEFT JOIN user_settings ON users.user_id = user_settings.user_id
WHERE users.user_id = $user_id LIMIT 1"
);
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$user_name = nullable_htmlentities($row['user_name']);
$user_email = nullable_htmlentities($row['user_email']);
$user_avatar = nullable_htmlentities($row['user_avatar']);
@@ -114,7 +114,7 @@ ob_start();
<select class="form-control select2" name="role" required>
<?php
$sql_user_roles = mysqli_query($mysqli, "SELECT * FROM user_roles WHERE role_archived_at IS NULL");
while ($row = mysqli_fetch_array($sql_user_roles)) {
while ($row = mysqli_fetch_assoc($sql_user_roles)) {
$role_id = intval($row['role_id']);
$role_name = nullable_htmlentities($row['role_name']);
@@ -175,7 +175,7 @@ ob_start();
<?php
$sql_client_select = mysqli_query($mysqli, "SELECT * FROM clients WHERE client_archived_at IS NULL ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql_client_select)) {
while ($row = mysqli_fetch_assoc($sql_client_select)) {
$client_id_select = intval($row['client_id']);
$client_name_select = nullable_htmlentities($row['client_name']);

View File

@@ -6,7 +6,7 @@ $user_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM users WHERE user_id = $user_id AND user_archived_at IS NOT NULL LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$user_name = str_replace(" (archived)", "", $row['user_name']); //Removed (archived) from user_name
$user_name = nullable_htmlentities($user_name);
$user_email = nullable_htmlentities($row['user_email']);
@@ -64,7 +64,7 @@ ob_start();
<select class="form-control select2" name="role" required>
<?php
$sql_user_roles = mysqli_query($mysqli, "SELECT * FROM user_roles WHERE role_archived_at IS NULL");
while ($row = mysqli_fetch_array($sql_user_roles)) {
while ($row = mysqli_fetch_assoc($sql_user_roles)) {
$role_id = intval($row['role_id']);
$role_name = nullable_htmlentities($row['role_name']);

View File

@@ -13,6 +13,7 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">

View File

@@ -5,7 +5,7 @@ require_once '../../../includes/modal_header.php';
$vendor_template_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT * FROM vendor_templates WHERE vendor_template_id = $vendor_template_id LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$vendor_name = nullable_htmlentities($row['vendor_template_name']);
$vendor_description = nullable_htmlentities($row['vendor_template_description']);
$vendor_account_number = nullable_htmlentities($row['vendor_template_account_number']);
@@ -31,7 +31,9 @@ ob_start();
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="vendor_template_id" value="<?php echo $vendor_template_id; ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
@@ -233,7 +235,7 @@ ob_start();
<div class="form-group">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"><?php echo $vendor_notes; ?></textarea>
</div>
<div class="form-group">
<label>Update Notes Globally?</label>
<input type="checkbox" name="global_update_vendor_notes" value="1">

View File

@@ -57,7 +57,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$module_id = intval($row['module_id']);
$module_name = nullable_htmlentities($row['module_name']);
$module_description = nullable_htmlentities($row['module_description']);

View File

@@ -0,0 +1,103 @@
<?php
require_once "../config.php";
require_once "../functions.php";
require_once "../includes/check_login.php";
$settings_mail_path = '/admin/settings_mail.php';
if (!isset($session_is_admin) || !$session_is_admin) {
flash_alert("Admin access required.", 'error');
redirect($settings_mail_path);
}
$state = sanitizeInput($_GET['state'] ?? '');
$code = $_GET['code'] ?? '';
$error = sanitizeInput($_GET['error'] ?? '');
$error_description = sanitizeInput($_GET['error_description'] ?? '');
$session_state = $_SESSION['mail_oauth_state'] ?? '';
$session_state_expires = intval($_SESSION['mail_oauth_state_expires_at'] ?? 0);
unset($_SESSION['mail_oauth_state'], $_SESSION['mail_oauth_state_expires_at']);
if (!empty($error)) {
$msg = "Microsoft OAuth authorization failed: $error";
if (!empty($error_description)) {
$msg .= " ($error_description)";
}
flash_alert($msg, 'error');
redirect($settings_mail_path);
}
if (empty($state) || empty($code) || empty($session_state) || !hash_equals($session_state, $state) || time() > $session_state_expires) {
flash_alert("Microsoft OAuth callback validation failed. Please try connecting again.", 'error');
redirect($settings_mail_path);
}
if (empty($config_mail_oauth_client_id) || empty($config_mail_oauth_client_secret) || empty($config_mail_oauth_tenant_id)) {
flash_alert("Microsoft OAuth settings are incomplete. Please fill Client ID, Client Secret, and Tenant ID.", 'error');
redirect($settings_mail_path);
}
if (defined('BASE_URL') && !empty(BASE_URL)) {
$base_url = rtrim((string) BASE_URL, '/');
} else {
$base_url = 'https://' . rtrim((string) $config_base_url, '/');
}
$redirect_uri = $base_url . '/admin/oauth_microsoft_mail_callback.php';
$token_url = 'https://login.microsoftonline.com/' . rawurlencode($config_mail_oauth_tenant_id) . '/oauth2/v2.0/token';
$scope = 'offline_access openid profile https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send';
$ch = curl_init($token_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'client_id' => $config_mail_oauth_client_id,
'client_secret' => $config_mail_oauth_client_secret,
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $redirect_uri,
'scope' => $scope,
], '', '&'));
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
$raw_body = curl_exec($ch);
$curl_err = curl_error($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw_body === false || $http_code < 200 || $http_code >= 300) {
$reason = !empty($curl_err) ? $curl_err : "HTTP $http_code";
flash_alert("Microsoft OAuth token exchange failed: $reason", 'error');
redirect($settings_mail_path);
}
$json = json_decode($raw_body, true);
if (!is_array($json) || empty($json['refresh_token']) || empty($json['access_token'])) {
flash_alert("Microsoft OAuth token exchange failed: refresh token or access token missing.", 'error');
redirect($settings_mail_path);
}
$refresh_token = (string) $json['refresh_token'];
$access_token = (string) $json['access_token'];
$expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
$refresh_token_esc = mysqli_real_escape_string($mysqli, $refresh_token);
$access_token_esc = mysqli_real_escape_string($mysqli, $access_token);
$expires_at_esc = mysqli_real_escape_string($mysqli, $expires_at);
mysqli_query($mysqli, "UPDATE settings SET
config_imap_provider = 'microsoft_oauth',
config_smtp_provider = 'microsoft_oauth',
config_mail_oauth_refresh_token = '$refresh_token_esc',
config_mail_oauth_access_token = '$access_token_esc',
config_mail_oauth_access_token_expires_at = '$expires_at_esc'
WHERE company_id = 1
");
logAction("Settings", "Edit", "$session_name completed Microsoft OAuth connect flow for mail settings");
flash_alert("Microsoft OAuth connected successfully. Token expires at $expires_at.");
redirect($settings_mail_path);

View File

@@ -45,7 +45,7 @@ $num_rows = mysqli_num_rows($sql);
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$payment_method_id = intval($row['payment_method_id']);
$payment_method_name = nullable_htmlentities($row['payment_method_name']);
$payment_method_description = nullable_htmlentities($row['payment_method_description']);

View File

@@ -66,7 +66,7 @@ $num_rows = mysqli_num_rows($sql);
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$provider_id = intval($row['payment_provider_id']);
$provider_name = nullable_htmlentities($row['payment_provider_name']);
$provider_description = nullable_htmlentities($row['payment_provider_description']);

View File

@@ -31,6 +31,27 @@ if (isset($_POST['add_api_key'])) {
}
if (isset($_GET['revoke_api_key'])) {
validateCSRFToken($_GET['csrf_token']);
$api_key_id = intval($_GET['revoke_api_key']);
// Get API Key Name
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT api_key_name, api_key_client_id FROM api_keys WHERE api_key_id = $api_key_id"));
$api_key_name = sanitizeInput($row['api_key_name']);
$client_id = intval($row['api_key_client_id']);
mysqli_query($mysqli,"UPDATE api_keys SET api_key_expire = NOW() WHERE api_key_id = $api_key_id");
logAction("API Key", "Revoke", "$session_name revoked API key $name", $client_id);
flash_alert("API Key <strong>$name</strong> revoked", 'error');
redirect();
}
if (isset($_GET['delete_api_key'])) {
validateCSRFToken($_GET['csrf_token']);
@@ -38,7 +59,7 @@ if (isset($_GET['delete_api_key'])) {
$api_key_id = intval($_GET['delete_api_key']);
// Get API Key Name
$row = mysqli_fetch_array(mysqli_query($mysqli,"SELECT api_key_name, api_key_client_id FROM api_keys WHERE api_key_id = $api_key_id"));
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT api_key_name, api_key_client_id FROM api_keys WHERE api_key_id = $api_key_id"));
$api_key_name = sanitizeInput($row['api_key_name']);
$client_id = intval($row['api_key_client_id']);
@@ -64,9 +85,9 @@ if (isset($_POST['bulk_delete_api_keys'])) {
foreach ($_POST['api_key_ids'] as $api_key_id) {
$api_key_id = intval($api_key_id);
// Get API Key Name
$row = mysqli_fetch_array(mysqli_query($mysqli,"SELECT api_key_name, api_key_client_id FROM api_keys WHERE api_key_id = $api_key_id"));
$row = mysqli_fetch_assoc(mysqli_query($mysqli,"SELECT api_key_name, api_key_client_id FROM api_keys WHERE api_key_id = $api_key_id"));
$api_key_name = sanitizeInput($row['api_key_name']);
$client_id = intval($row['api_key_client_id']);

View File

@@ -307,11 +307,11 @@ if (isset($_POST['backup_master_key'])) {
$password = $_POST['password'];
$sql = mysqli_query($mysqli, "SELECT * FROM users WHERE user_id = $session_user_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
if (password_verify($password, $row['user_password'])) {
$site_encryption_master_key = decryptUserSpecificKey($row['user_specific_encryption_ciphertext'], $password);
logAction("Master Key", "Download", "$session_name retrieved the master encryption key");
appNotify("Master Key", "$session_name retrieved the master encryption key");
@@ -320,13 +320,12 @@ if (isset($_POST['backup_master_key'])) {
echo "<br>Master encryption key:<br>";
echo "<b>$site_encryption_master_key</b>";
echo "<br>==============================";
} else {
logAction("Master Key", "Download", "$session_name attempted to retrieve the master encryption key but failed");
flash_alert("Incorrect password.", 'error');
redirect();
}
}

View File

@@ -8,6 +8,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_category'])) {
validateCSRFToken($_POST['csrf_token']);
require_once 'category_model.php';
mysqli_query($mysqli,"INSERT INTO categories SET category_name = '$name', category_type = '$type', category_color = '$color'");
@@ -24,6 +26,8 @@ if (isset($_POST['add_category'])) {
if (isset($_POST['edit_category'])) {
validateCSRFToken($_POST['csrf_token']);
require_once 'category_model.php';
$category_id = intval($_POST['category_id']);
@@ -39,12 +43,14 @@ if (isset($_POST['edit_category'])) {
}
if (isset($_GET['archive_category'])) {
validateCSRFToken($_GET['csrf_token']);
$category_id = intval($_GET['archive_category']);
// Get Category Name and Type for logging
$sql = mysqli_query($mysqli,"SELECT category_name, category_type FROM categories WHERE category_id = $category_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$category_name = sanitizeInput($row['category_name']);
$category_type = sanitizeInput($row['category_type']);
@@ -58,33 +64,37 @@ if (isset($_GET['archive_category'])) {
}
if (isset($_GET['unarchive_category'])) {
$category_id = intval($_GET['unarchive_category']);
if (isset($_GET['restore_category'])) {
validateCSRFToken($_GET['csrf_token']);
$category_id = intval($_GET['retore_category']);
// Get Category Name and Type for logging
$sql = mysqli_query($mysqli,"SELECT category_name, category_type FROM categories WHERE category_id = $category_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$category_name = sanitizeInput($row['category_name']);
$category_type = sanitizeInput($row['category_type']);
mysqli_query($mysqli,"UPDATE categories SET category_archived_at = NULL WHERE category_id = $category_id");
logAction("Category", "Unarchive", "$session_name unarchived category $category_type $category_name", 0, $category_id);
logAction("Category", "Restore", "$session_name retored category $category_type $category_name", 0, $category_id);
flash_alert("Category $category_type <strong>$category_name</strong> unarchived");
flash_alert("Category $category_type <strong>$category_name</strong> restored");
redirect();
}
if (isset($_GET['delete_category'])) {
validateCSRFToken($_GET['csrf_token']);
$category_id = intval($_GET['delete_category']);
// Get Category Name and Type for logging
$sql = mysqli_query($mysqli,"SELECT category_name, category_type FROM categories WHERE category_id = $category_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$category_name = sanitizeInput($row['category_name']);
$category_type = sanitizeInput($row['category_type']);

View File

@@ -8,6 +8,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_custom_link'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$uri = sanitizeInput($_POST['uri']);
$new_tab = intval($_POST['new_tab'] ?? 0);
@@ -29,6 +31,8 @@ if (isset($_POST['add_custom_link'])) {
if (isset($_POST['edit_custom_link'])) {
validateCSRFToken($_POST['csrf_token']);
$custom_link_id = intval($_POST['custom_link_id']);
$name = sanitizeInput($_POST['name']);
$uri = sanitizeInput($_POST['uri']);
@@ -48,12 +52,14 @@ if (isset($_POST['edit_custom_link'])) {
}
if (isset($_GET['delete_custom_link'])) {
validateCSRFToken($_GET['csrf_token']);
$custom_link_id = intval($_GET['delete_custom_link']);
// Get Custom Link name and uri for logging
$sql = mysqli_query($mysqli,"SELECT custom_link_name, custom_link_uri FROM custom_links WHERE custom_link_id = $custom_link_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$custom_link_name = sanitizeInput($row['custom_link_name']);
$custom_link_uri = sanitizeInput($row['custom_link_uri']);

View File

@@ -6,11 +6,13 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_document_template'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
mysqli_query($mysqli,"INSERT INTO document_templates SET document_template_name = '$name', document_template_description = '$description', document_template_content = '', document_template_created_by = $session_user_id");
$document_template_id = mysqli_insert_id($mysqli);
$processed_content = mysqli_escape_string(
@@ -36,6 +38,8 @@ if (isset($_POST['add_document_template'])) {
if (isset($_POST['edit_document_template'])) {
validateCSRFToken($_POST['csrf_token']);
$document_template_id = intval($_POST['document_template_id']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
@@ -69,6 +73,8 @@ if (isset($_POST['edit_document_template'])) {
if (isset($_GET['delete_document_template'])) {
validateCSRFToken($_GET['csrf_token']);
$document_template_id = intval($_GET['delete_document_template']);
$document_template_name = sanitizeInput(getFieldById('document_templates', $document_template_id, 'document_template_name'));

View File

@@ -4,6 +4,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_GET['send_failed_mail'])) {
validateCSRFToken($_GET['csrf_token']);
$email_id = intval($_GET['send_failed_mail']);
mysqli_query($mysqli,"UPDATE email_queue SET email_status = 0, email_attempts = 3 WHERE email_id = $email_id");
@@ -18,6 +20,8 @@ if (isset($_GET['send_failed_mail'])) {
if (isset($_GET['cancel_mail'])) {
validateCSRFToken($_GET['csrf_token']);
$email_id = intval($_GET['cancel_mail']);
mysqli_query($mysqli,"UPDATE email_queue SET email_status = 2, email_attempts = 99, email_failed_at = NOW() WHERE email_id = $email_id");

View File

@@ -19,7 +19,7 @@ if (isset($_POST['add_payment_method'])) {
);
mysqli_stmt_bind_param($query, "ss", $name, $description);
mysqli_stmt_execute($query);
logAction("Payment Method", "Create", "$session_name created Payment Method $name");
@@ -33,15 +33,15 @@ if (isset($_POST['add_payment_method'])) {
if (isset($_POST['edit_payment_method'])) {
validateCSRFToken($_POST['csrf_token']);
$payment_method_id = intval($_POST['payment_method_id']);
$name = cleanInput($_POST['name']);
$description = cleanInput($_POST['description']);
$query = mysqli_prepare(
$mysqli,
"UPDATE payment_methods
SET payment_method_name = ?, payment_method_description = ?
"UPDATE payment_methods
SET payment_method_name = ?, payment_method_description = ?
WHERE payment_method_id = ?"
);
@@ -58,7 +58,9 @@ if (isset($_POST['edit_payment_method'])) {
}
if (isset($_GET['delete_payment_method'])) {
validateCSRFToken($_GET['csrf_token']);
$payment_method_id = intval($_GET['delete_payment_method']);
$payment_method_name = sanitizeInput(getFieldById('payment_methods', $payment_method_is, 'payment_method_name'));

View File

@@ -4,6 +4,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_project_template'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
@@ -21,6 +23,8 @@ if (isset($_POST['add_project_template'])) {
if (isset($_POST['edit_project_template'])) {
validateCSRFToken($_POST['csrf_token']);
$project_template_id = intval($_POST['project_template_id']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
@@ -37,6 +41,8 @@ if (isset($_POST['edit_project_template'])) {
if (isset($_POST['edit_ticket_template_order'])) {
validateCSRFToken($_POST['csrf_token']);
$ticket_template_id = intval($_POST['ticket_template_id']);
$project_template_id = intval($_POST['project_template_id']);
$order = intval($_POST['order']);
@@ -49,6 +55,8 @@ if (isset($_POST['edit_ticket_template_order'])) {
if (isset($_POST['add_ticket_template_to_project_template'])) {
validateCSRFToken($_POST['csrf_token']);
$project_template_id = intval($_POST['project_template_id']);
$ticket_template_id = intval($_POST['ticket_template_id']);
$order = intval($_POST['order']);
@@ -65,7 +73,8 @@ if (isset($_POST['add_ticket_template_to_project_template'])) {
if (isset($_POST['remove_ticket_template_from_project_template'])) {
validateTechRole();
validateCSRFToken($_POST['csrf_token']);
$ticket_template_id = intval($_POST['ticket_template_id']);
$project_template_id = intval($_POST['project_template_id']);
@@ -81,6 +90,8 @@ if (isset($_POST['remove_ticket_template_from_project_template'])) {
if (isset($_GET['delete_project_template'])) {
validateCSRFToken($_GET['csrf_token']);
$project_template_id = intval($_GET['delete_project_template']);
$project_template_name = sanitizeInput(getFieldById('project_templates', $project_template_id, 'project_template_name'));
@@ -95,5 +106,5 @@ if (isset($_GET['delete_project_template'])) {
flash_alert("Project Template <strong>$project_template_name</strong> and its associated ticket templates and tasks deleted", 'error');
redirect();
}

View File

@@ -18,9 +18,23 @@ if (isset($_POST['add_role'])) {
$role_id = mysqli_insert_id($mysqli);
// Insert role permissions (only if not admin)
if ($admin == 0) {
foreach ($_POST as $key => $value) {
if (str_contains($key, '##module_')) {
$module_id = intval(explode('##', $key)[0]);
$access_level = intval($value);
if ($access_level > 0) {
mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = $role_id, module_id = $module_id, user_role_permission_level = $access_level");
}
}
}
}
logAction("User Role", "Create", "$session_name created user role $name", 0, $role_id);
flash_alert("User Role <strong$name</strong> created");
flash_alert("User Role <strong>$name</strong> created");
redirect();
@@ -34,7 +48,7 @@ if (isset($_POST['edit_role'])) {
$name = sanitizeInput($_POST['role_name']);
$description = sanitizeInput($_POST['role_description']);
$admin = intval($_POST['role_is_admin']);
mysqli_query($mysqli, "UPDATE user_roles SET role_name = '$name', role_description = '$description', role_is_admin = $admin WHERE role_id = $role_id");
// Update role access levels
@@ -70,18 +84,18 @@ if (isset($_GET['archive_role'])) {
$role_user_count = mysqli_fetch_row($sql_role_user_count)[0];
if ($role_user_count != 0) {
flash_alert("Role must not in use to archive it", 'error');
redirect();
}
mysqli_query($mysqli, "UPDATE user_roles SET role_archived_at = NOW() WHERE role_id = $role_id");
$role_name = sanitizeInput(getFieldById('roles', $role_id, 'role_name'));
$role_name = sanitizeInput(getFieldById('user_roles', $role_id, 'role_name'));
logAction("User Role", "Archive", "$session_name archived user role $role_name", 0, $role_id);
flash_alert("User Role <strong>$role_name</strong> archived", 'error');
redirect();
}
}

View File

@@ -3,13 +3,13 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_GET['delete_saved_payment'])) {
validateCSRFToken($_GET['csrf_token']);
$saved_payment_id = intval($_GET['delete_saved_payment']);
$sql = mysqli_query($mysqli, "
SELECT
SELECT
client_saved_payment_methods.saved_payment_id,
client_saved_payment_methods.saved_payment_client_id,
client_saved_payment_methods.saved_payment_provider_id,
@@ -27,7 +27,7 @@ if (isset($_GET['delete_saved_payment'])) {
WHERE client_saved_payment_methods.saved_payment_id = $saved_payment_id"
);
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$client_id = intval($row['saved_payment_client_id']);
$provider_id = intval($row['saved_payment_provider_id']);
$payment_provider_name = nullable_htmlentities($row['payment_provider_name']);
@@ -62,9 +62,9 @@ if (isset($_GET['delete_saved_payment'])) {
// SQL Cascade delete will Remove All Associated Auto Payment Methods on recurring invoices in the recurring payments table.
logAction("Payment Provider", "Update", "$session_name deleted saved payment method $saved_payment_description (PM: $payment_method)", $client_id);
flash_alert("Payment method <strong>$saved_payment_description</strong> removed", 'error');
redirect();
}

View File

@@ -19,7 +19,7 @@ if (isset($_POST['edit_company'])) {
$tax_id = sanitizeInput($_POST['tax_id']);
$sql = mysqli_query($mysqli,"SELECT company_logo FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$existing_file_name = sanitizeInput($row['company_logo']);
// Company logo
@@ -54,8 +54,10 @@ if (isset($_POST['edit_company'])) {
if (isset($_GET['remove_company_logo'])) {
validateCSRFToken($_GET['csrf_token']);
$sql = mysqli_query($mysqli,"SELECT company_logo FROM companies");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_logo = $row['company_logo']; // FileSystem Operation Logo is already sanitized
unlink("../uploads/settings/$company_logo");

View File

@@ -1,287 +1,582 @@
<?php
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['edit_mail_smtp_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_smtp_provider = sanitizeInput($_POST['config_smtp_provider']);
$config_smtp_host = sanitizeInput($_POST['config_smtp_host']);
$config_smtp_port = intval($_POST['config_smtp_port'] ?? 0);
$config_smtp_encryption = sanitizeInput($_POST['config_smtp_encryption']);
$config_smtp_username = sanitizeInput($_POST['config_smtp_username']);
$config_smtp_password = sanitizeInput($_POST['config_smtp_password']);
// Shared OAuth fields
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
mysqli_query($mysqli, "
UPDATE settings SET
config_smtp_provider = '$config_smtp_provider',
config_smtp_host = '$config_smtp_host',
config_smtp_port = $config_smtp_port,
config_smtp_encryption = '$config_smtp_encryption',
config_smtp_username = '$config_smtp_username',
config_smtp_password = '$config_smtp_password',
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
WHERE company_id = 1
");
logAction("Settings", "Edit", "$session_name edited SMTP settings");
flash_alert("SMTP Mail Settings updated");
redirect();
}
if (isset($_POST['edit_mail_imap_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_imap_provider = sanitizeInput($_POST['config_imap_provider']);
$config_imap_host = sanitizeInput($_POST['config_imap_host']);
$config_imap_port = intval($_POST['config_imap_port'] ?? 0);
$config_imap_encryption = sanitizeInput($_POST['config_imap_encryption']);
$config_imap_username = sanitizeInput($_POST['config_imap_username']);
$config_imap_password = sanitizeInput($_POST['config_imap_password']);
// Shared OAuth fields
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
mysqli_query($mysqli, "
UPDATE settings SET
config_imap_provider = '$config_imap_provider',
config_imap_host = '$config_imap_host',
config_imap_port = $config_imap_port,
config_imap_encryption = '$config_imap_encryption',
config_imap_username = '$config_imap_username',
config_imap_password = '$config_imap_password',
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
WHERE company_id = 1
");
logAction("Settings", "Edit", "$session_name edited IMAP settings");
flash_alert("IMAP Mail Settings updated");
redirect();
}
if (isset($_POST['edit_mail_from_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_mail_from_email = sanitizeInput(filter_var($_POST['config_mail_from_email'], FILTER_VALIDATE_EMAIL));
$config_mail_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_mail_from_name']));
$config_invoice_from_email = sanitizeInput(filter_var($_POST['config_invoice_from_email'], FILTER_VALIDATE_EMAIL));
$config_invoice_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_invoice_from_name']));
$config_quote_from_email = sanitizeInput(filter_var($_POST['config_quote_from_email'], FILTER_VALIDATE_EMAIL));
$config_quote_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_quote_from_name']));
$config_ticket_from_email = sanitizeInput(filter_var($_POST['config_ticket_from_email'], FILTER_VALIDATE_EMAIL));
$config_ticket_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_ticket_from_name']));
mysqli_query($mysqli,"UPDATE settings SET config_mail_from_email = '$config_mail_from_email', config_mail_from_name = '$config_mail_from_name', config_invoice_from_email = '$config_invoice_from_email', config_invoice_from_name = '$config_invoice_from_name', config_quote_from_email = '$config_quote_from_email', config_quote_from_name = '$config_quote_from_name', config_ticket_from_email = '$config_ticket_from_email', config_ticket_from_name = '$config_ticket_from_name' WHERE company_id = 1");
logAction("Settings", "Edit", "$session_name edited mail from settings");
flash_alert("Mail From Settings updated");
redirect();
}
if (isset($_POST['test_email_smtp'])) {
validateCSRFToken($_POST['csrf_token']);
$test_email = intval($_POST['test_email']);
if($test_email == 1) {
$email_from = sanitizeInput($config_mail_from_email);
$email_from_name = sanitizeInput($config_mail_from_name);
} elseif ($test_email == 2) {
$email_from = sanitizeInput($config_invoice_from_email);
$email_from_name = sanitizeInput($config_invoice_from_name);
} elseif ($test_email == 3) {
$email_from = sanitizeInput($config_quote_from_email);
$email_from_name = sanitizeInput($config_quote_from_name);
} else {
$email_from = sanitizeInput($config_ticket_from_email);
$email_from_name = sanitizeInput($config_ticket_from_name);
}
$email_to = sanitizeInput($_POST['email_to']);
$subject = "Test email from ITFlow";
$body = "This is a test email from ITFlow. If you are reading this, it worked!";
$data = [
[
'from' => $email_from,
'from_name' => $email_from_name,
'recipient' => $email_to,
'recipient_name' => 'Chap',
'subject' => $subject,
'body' => $body
]
];
$mail = addToMailQueue($data);
if ($mail === true) {
flash_alert("Test email queued! <a class='text-bold text-light' href='mail_queue.php'>Check Admin > Mail queue</a>");
} else {
flash_alert("Failed to add test mail to queue", 'error');
}
redirect();
}
if (isset($_POST['test_email_imap'])) {
validateCSRFToken($_POST['csrf_token']);
$host = $config_imap_host;
$port = (int) $config_imap_port;
$encryption = strtolower(trim($config_imap_encryption)); // e.g. "ssl", "tls", "none"
$username = $config_imap_username;
$password = $config_imap_password;
// Build remote socket (implicit SSL vs plain TCP)
$transport = 'tcp';
if ($encryption === 'ssl') {
$transport = 'ssl';
}
$remote_socket = $transport . '://' . $host . ':' . $port;
// Stream context (you can tighten these if you want strict validation)
$contextOptions = [];
if (in_array($encryption, ['ssl', 'tls'], true)) {
$contextOptions['ssl'] = [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
];
}
$context = stream_context_create($contextOptions);
try {
$errno = 0;
$errstr = '';
// 10-second timeout, adjust as needed
$fp = @stream_socket_client(
$remote_socket,
$errno,
$errstr,
10,
STREAM_CLIENT_CONNECT,
$context
);
if (!$fp) {
throw new Exception("Could not connect to IMAP server: [$errno] $errstr");
}
stream_set_timeout($fp, 10);
// Read server greeting (IMAP servers send something like: * OK Dovecot ready)
$greeting = fgets($fp, 1024);
if ($greeting === false || strpos($greeting, '* OK') !== 0) {
fclose($fp);
throw new Exception("Invalid IMAP greeting: " . trim((string) $greeting));
}
// If you really want STARTTLS for "tls" (port 143), you can do it here
if ($encryption === 'tls' && stripos($greeting, 'STARTTLS') !== false) {
// Request STARTTLS
fwrite($fp, "A0001 STARTTLS\r\n");
$line = fgets($fp, 1024);
if ($line === false || stripos($line, 'A0001 OK') !== 0) {
fclose($fp);
throw new Exception("STARTTLS failed: " . trim((string) $line));
}
// Enable crypto on the stream
if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
fclose($fp);
throw new Exception("Unable to enable TLS encryption on IMAP connection.");
}
}
// --- Do LOGIN command ---
$tag = 'A0002';
// Simple quoting; this may fail with some special chars in username/password.
$loginCmd = sprintf(
"%s LOGIN \"%s\" \"%s\"\r\n",
$tag,
addcslashes($username, "\\\""),
addcslashes($password, "\\\"")
);
fwrite($fp, $loginCmd);
$success = false;
$errorLine = '';
while (!feof($fp)) {
$line = fgets($fp, 2048);
if ($line === false) {
break;
}
// Look for tagged response for our LOGIN
if (strpos($line, $tag . ' ') === 0) {
if (stripos($line, $tag . ' OK') === 0) {
$success = true;
} else {
$errorLine = trim($line);
}
break;
}
}
// Always logout / close
fwrite($fp, "A0003 LOGOUT\r\n");
fclose($fp);
if ($success) {
flash_alert("Connected successfully");
} else {
if (!$errorLine) {
$errorLine = 'Unknown IMAP authentication error';
}
throw new Exception($errorLine);
}
} catch (Exception $e) {
flash_alert("<strong>IMAP connection failed:</strong> " . htmlspecialchars($e->getMessage()), 'error');
}
redirect();
}
<?php
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (!defined('MICROSOFT_OAUTH_BASE_URL')) {
define('MICROSOFT_OAUTH_BASE_URL', 'https://login.microsoftonline.com/');
}
if (isset($_POST['oauth_connect_microsoft_mail'])) {
validateCSRFToken($_POST['csrf_token']);
// Save current IMAP/OAuth form values first so auth flow always uses latest inputs.
$config_imap_provider = sanitizeInput($_POST['config_imap_provider'] ?? '');
$config_imap_username = sanitizeInput($_POST['config_imap_username'] ?? '');
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id'] ?? '');
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret'] ?? '');
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id'] ?? '');
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token'] ?? '');
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token'] ?? '');
mysqli_query($mysqli, "UPDATE settings SET
config_imap_provider = '$config_imap_provider',
config_imap_username = '$config_imap_username',
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
WHERE company_id = 1
");
if ($config_imap_provider !== 'microsoft_oauth') {
flash_alert("Please set IMAP Provider to Microsoft 365 (OAuth) before connecting.", 'error');
redirect();
}
if (empty($config_mail_oauth_client_id) || empty($config_mail_oauth_client_secret) || empty($config_mail_oauth_tenant_id)) {
flash_alert("Missing Microsoft OAuth settings. Please provide Client ID, Client Secret, and Tenant ID first.", 'error');
redirect();
}
if (defined('BASE_URL') && !empty(BASE_URL)) {
$base_url = rtrim((string) BASE_URL, '/');
} else {
$base_url = 'https://' . rtrim((string) $config_base_url, '/');
}
$redirect_uri = $base_url . '/admin/oauth_microsoft_mail_callback.php';
try {
$state = bin2hex(random_bytes(32));
} catch (Throwable $e) {
$state = sha1(uniqid((string) mt_rand(), true));
}
$_SESSION['mail_oauth_state'] = $state;
$_SESSION['mail_oauth_state_expires_at'] = time() + 600;
$scope = 'offline_access openid profile https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send';
$authorize_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($config_mail_oauth_tenant_id) . '/oauth2/v2.0/authorize?'
. http_build_query([
'client_id' => $config_mail_oauth_client_id,
'response_type' => 'code',
'redirect_uri' => $redirect_uri,
'response_mode' => 'query',
'scope' => $scope,
'state' => $state,
'prompt' => 'consent',
], '', '&', PHP_QUERY_RFC3986);
logAction("Settings", "Edit", "$session_name started Microsoft OAuth connect flow for mail settings");
redirect($authorize_url);
}
if (isset($_POST['edit_mail_smtp_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_smtp_provider = sanitizeInput($_POST['config_smtp_provider']);
$config_smtp_host = sanitizeInput($_POST['config_smtp_host']);
$config_smtp_port = intval($_POST['config_smtp_port'] ?? 0);
$config_smtp_encryption = sanitizeInput($_POST['config_smtp_encryption']);
$config_smtp_username = sanitizeInput($_POST['config_smtp_username']);
$config_smtp_password = sanitizeInput($_POST['config_smtp_password']);
// Shared OAuth fields
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
mysqli_query($mysqli, "
UPDATE settings SET
config_smtp_provider = '$config_smtp_provider',
config_smtp_host = '$config_smtp_host',
config_smtp_port = $config_smtp_port,
config_smtp_encryption = '$config_smtp_encryption',
config_smtp_username = '$config_smtp_username',
config_smtp_password = '$config_smtp_password',
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
WHERE company_id = 1
");
logAction("Settings", "Edit", "$session_name edited SMTP settings");
flash_alert("SMTP Mail Settings updated");
redirect();
}
if (isset($_POST['edit_mail_imap_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_imap_provider = sanitizeInput($_POST['config_imap_provider']);
$config_imap_host = sanitizeInput($_POST['config_imap_host']);
$config_imap_port = intval($_POST['config_imap_port'] ?? 0);
$config_imap_encryption = sanitizeInput($_POST['config_imap_encryption']);
$config_imap_username = sanitizeInput($_POST['config_imap_username']);
$config_imap_password = sanitizeInput($_POST['config_imap_password']);
// Shared OAuth fields
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
mysqli_query($mysqli, "
UPDATE settings SET
config_imap_provider = '$config_imap_provider',
config_imap_host = '$config_imap_host',
config_imap_port = $config_imap_port,
config_imap_encryption = '$config_imap_encryption',
config_imap_username = '$config_imap_username',
config_imap_password = '$config_imap_password',
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
WHERE company_id = 1
");
logAction("Settings", "Edit", "$session_name edited IMAP settings");
flash_alert("IMAP Mail Settings updated");
redirect();
}
if (isset($_POST['edit_mail_from_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_mail_from_email = sanitizeInput(filter_var($_POST['config_mail_from_email'], FILTER_VALIDATE_EMAIL));
$config_mail_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_mail_from_name']));
$config_invoice_from_email = sanitizeInput(filter_var($_POST['config_invoice_from_email'], FILTER_VALIDATE_EMAIL));
$config_invoice_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_invoice_from_name']));
$config_quote_from_email = sanitizeInput(filter_var($_POST['config_quote_from_email'], FILTER_VALIDATE_EMAIL));
$config_quote_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_quote_from_name']));
$config_ticket_from_email = sanitizeInput(filter_var($_POST['config_ticket_from_email'], FILTER_VALIDATE_EMAIL));
$config_ticket_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_ticket_from_name']));
mysqli_query($mysqli,"UPDATE settings SET config_mail_from_email = '$config_mail_from_email', config_mail_from_name = '$config_mail_from_name', config_invoice_from_email = '$config_invoice_from_email', config_invoice_from_name = '$config_invoice_from_name', config_quote_from_email = '$config_quote_from_email', config_quote_from_name = '$config_quote_from_name', config_ticket_from_email = '$config_ticket_from_email', config_ticket_from_name = '$config_ticket_from_name' WHERE company_id = 1");
logAction("Settings", "Edit", "$session_name edited mail from settings");
flash_alert("Mail From Settings updated");
redirect();
}
if (isset($_POST['test_email_smtp'])) {
validateCSRFToken($_POST['csrf_token']);
$test_email = intval($_POST['test_email']);
if($test_email == 1) {
$email_from = sanitizeInput($config_mail_from_email);
$email_from_name = sanitizeInput($config_mail_from_name);
} elseif ($test_email == 2) {
$email_from = sanitizeInput($config_invoice_from_email);
$email_from_name = sanitizeInput($config_invoice_from_name);
} elseif ($test_email == 3) {
$email_from = sanitizeInput($config_quote_from_email);
$email_from_name = sanitizeInput($config_quote_from_name);
} else {
$email_from = sanitizeInput($config_ticket_from_email);
$email_from_name = sanitizeInput($config_ticket_from_name);
}
$email_to = sanitizeInput($_POST['email_to']);
$subject = "Test email from ITFlow";
$body = "This is a test email from ITFlow. If you are reading this, it worked!";
$data = [
[
'from' => $email_from,
'from_name' => $email_from_name,
'recipient' => $email_to,
'recipient_name' => 'Chap',
'subject' => $subject,
'body' => $body
]
];
$mail = addToMailQueue($data);
if ($mail === true) {
flash_alert("Test email queued! <a class='text-bold text-light' href='mail_queue.php'>Check Admin > Mail queue</a>");
} else {
flash_alert("Failed to add test mail to queue", 'error');
}
redirect();
}
if (isset($_POST['test_email_imap'])) {
validateCSRFToken($_POST['csrf_token']);
$provider = sanitizeInput($config_imap_provider ?? '');
$host = $config_imap_host;
$port = (int) $config_imap_port;
$encryption = strtolower(trim($config_imap_encryption)); // e.g. "ssl", "tls", "none"
$username = $config_imap_username;
$password = $config_imap_password;
// Shared OAuth fields
$config_mail_oauth_client_id = $config_mail_oauth_client_id ?? '';
$config_mail_oauth_client_secret = $config_mail_oauth_client_secret ?? '';
$config_mail_oauth_tenant_id = $config_mail_oauth_tenant_id ?? '';
$config_mail_oauth_refresh_token = $config_mail_oauth_refresh_token ?? '';
$config_mail_oauth_access_token = $config_mail_oauth_access_token ?? '';
$config_mail_oauth_access_token_expires_at = $config_mail_oauth_access_token_expires_at ?? '';
$is_oauth = ($provider === 'google_oauth' || $provider === 'microsoft_oauth');
if ($provider === 'google_oauth') {
if (empty($host)) {
$host = 'imap.gmail.com';
}
if (empty($port)) {
$port = 993;
}
if (empty($encryption)) {
$encryption = 'ssl';
}
} elseif ($provider === 'microsoft_oauth') {
if (empty($host)) {
$host = 'outlook.office365.com';
}
if (empty($port)) {
$port = 993;
}
if (empty($encryption)) {
$encryption = 'ssl';
}
}
if (empty($host) || empty($port) || empty($username)) {
flash_alert("<strong>IMAP connection failed:</strong> Missing host, port, or username.", 'error');
redirect();
}
$token_is_expired = function (?string $expires_at): bool {
if (empty($expires_at)) {
return true;
}
$ts = strtotime($expires_at);
if ($ts === false) {
return true;
}
return ($ts - 60) <= time();
};
$http_form_post = function (string $url, array $fields): array {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields, '', '&'));
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
$raw = curl_exec($ch);
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'ok' => ($raw !== false && $code >= 200 && $code < 300),
'body' => $raw,
'code' => $code,
'err' => $err,
];
};
if ($is_oauth) {
if (!empty($config_mail_oauth_access_token) && !$token_is_expired($config_mail_oauth_access_token_expires_at)) {
$password = $config_mail_oauth_access_token;
} else {
if (empty($config_mail_oauth_client_id) || empty($config_mail_oauth_client_secret) || empty($config_mail_oauth_refresh_token)) {
flash_alert("<strong>IMAP OAuth failed:</strong> Missing OAuth client credentials or refresh token.", 'error');
redirect();
}
if ($provider === 'google_oauth') {
$response = $http_form_post('https://oauth2.googleapis.com/token', [
'client_id' => $config_mail_oauth_client_id,
'client_secret' => $config_mail_oauth_client_secret,
'refresh_token' => $config_mail_oauth_refresh_token,
'grant_type' => 'refresh_token',
]);
} else {
if (empty($config_mail_oauth_tenant_id)) {
flash_alert("<strong>IMAP OAuth failed:</strong> Microsoft tenant ID is required.", 'error');
redirect();
}
$token_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($config_mail_oauth_tenant_id) . "/oauth2/v2.0/token";
$response = $http_form_post($token_url, [
'client_id' => $config_mail_oauth_client_id,
'client_secret' => $config_mail_oauth_client_secret,
'refresh_token' => $config_mail_oauth_refresh_token,
'grant_type' => 'refresh_token',
]);
}
if (!$response['ok']) {
flash_alert("<strong>IMAP OAuth failed:</strong> Could not refresh access token.", 'error');
redirect();
}
$json = json_decode($response['body'], true);
if (!is_array($json) || empty($json['access_token'])) {
flash_alert("<strong>IMAP OAuth failed:</strong> Token response did not include an access token.", 'error');
redirect();
}
$password = $json['access_token'];
$expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
$refresh_token_to_save = $json['refresh_token'] ?? null;
$token_esc = mysqli_real_escape_string($mysqli, $password);
$expires_at_esc = mysqli_real_escape_string($mysqli, $expires_at);
$refresh_sql = '';
if (!empty($refresh_token_to_save)) {
$refresh_token_esc = mysqli_real_escape_string($mysqli, $refresh_token_to_save);
$refresh_sql = ", config_mail_oauth_refresh_token = '{$refresh_token_esc}'";
}
mysqli_query($mysqli, "UPDATE settings SET config_mail_oauth_access_token = '{$token_esc}', config_mail_oauth_access_token_expires_at = '{$expires_at_esc}'{$refresh_sql} WHERE company_id = 1");
}
}
// Build remote socket (implicit SSL vs plain TCP)
$transport = 'tcp';
if ($encryption === 'ssl') {
$transport = 'ssl';
}
$remote_socket = $transport . '://' . $host . ':' . $port;
// Stream context (you can tighten these if you want strict validation)
$context_options = [];
if (in_array($encryption, ['ssl', 'tls'], true)) {
$context_options['ssl'] = [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
];
}
$context = stream_context_create($context_options);
try {
$errno = 0;
$errstr = '';
// 10-second timeout, adjust as needed
$fp = @stream_socket_client(
$remote_socket,
$errno,
$errstr,
10,
STREAM_CLIENT_CONNECT,
$context
);
if (!$fp) {
throw new Exception("Could not connect to IMAP server: [$errno] $errstr");
}
stream_set_timeout($fp, 10);
// Read server greeting (IMAP servers send something like: * OK Dovecot ready)
$greeting = fgets($fp, 1024);
if ($greeting === false || strpos($greeting, '* OK') !== 0) {
fclose($fp);
throw new Exception("Invalid IMAP greeting: " . trim((string) $greeting));
}
// If you really want STARTTLS for "tls" (port 143), you can do it here
if ($encryption === 'tls' && stripos($greeting, 'STARTTLS') !== false) {
fwrite($fp, "A0001 STARTTLS\r\n");
$line = fgets($fp, 1024);
if ($line === false || stripos($line, 'A0001 OK') !== 0) {
fclose($fp);
throw new Exception("STARTTLS failed: " . trim((string) $line));
}
if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
fclose($fp);
throw new Exception("Unable to enable TLS encryption on IMAP connection.");
}
}
$tag = 'A0002';
if ($is_oauth) {
$oauth_b64 = base64_encode("user={$username}\x01auth=Bearer {$password}\x01\x01");
$auth_cmd = sprintf("%s AUTHENTICATE XOAUTH2 %s\r\n", $tag, $oauth_b64);
fwrite($fp, $auth_cmd);
} else {
$login_cmd = sprintf(
"%s LOGIN \"%s\" \"%s\"\r\n",
$tag,
addcslashes($username, "\\\""),
addcslashes($password, "\\\"")
);
fwrite($fp, $login_cmd);
}
$success = false;
$error_line = '';
while (!feof($fp)) {
$line = fgets($fp, 2048);
if ($line === false) {
break;
}
if (strpos($line, $tag . ' ') === 0) {
if (stripos($line, $tag . ' OK') === 0) {
$success = true;
} else {
$error_line = trim($line);
}
break;
}
}
// Always logout / close
fwrite($fp, "A0003 LOGOUT\r\n");
fclose($fp);
if ($success) {
if ($is_oauth) {
flash_alert("Connected successfully using OAuth");
} else {
flash_alert("Connected successfully");
}
} else {
if (!$error_line) {
$error_line = 'Unknown IMAP authentication error';
}
throw new Exception($error_line);
}
} catch (Exception $e) {
flash_alert("<strong>IMAP connection failed:</strong> " . htmlspecialchars($e->getMessage()), 'error');
}
redirect();
}
if (isset($_POST['test_oauth_token_refresh'])) {
validateCSRFToken($_POST['csrf_token']);
$provider = sanitizeInput($_POST['oauth_provider'] ?? '');
if ($provider !== 'google_oauth' && $provider !== 'microsoft_oauth') {
flash_alert("OAuth token test failed: unsupported provider.", 'error');
redirect();
}
$oauth_client_id = sanitizeInput($config_mail_oauth_client_id ?? '');
$oauth_client_secret = sanitizeInput($config_mail_oauth_client_secret ?? '');
$oauth_tenant_id = sanitizeInput($config_mail_oauth_tenant_id ?? '');
$oauth_refresh_token = sanitizeInput($config_mail_oauth_refresh_token ?? '');
if (empty($oauth_client_id) || empty($oauth_client_secret) || empty($oauth_refresh_token)) {
flash_alert("OAuth token test failed: missing client ID, client secret, or refresh token.", 'error');
redirect();
}
if ($provider === 'microsoft_oauth' && empty($oauth_tenant_id)) {
flash_alert("OAuth token test failed: Microsoft tenant ID is required.", 'error');
redirect();
}
$token_url = 'https://oauth2.googleapis.com/token';
if ($provider === 'microsoft_oauth') {
$token_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($oauth_tenant_id) . "/oauth2/v2.0/token";
}
$post_fields = http_build_query([
'client_id' => $oauth_client_id,
'client_secret' => $oauth_client_secret,
'refresh_token' => $oauth_refresh_token,
'grant_type' => 'refresh_token',
]);
$ch = curl_init($token_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
$raw_body = curl_exec($ch);
$curl_err = curl_error($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw_body === false || $http_code < 200 || $http_code >= 300) {
$err_msg = !empty($curl_err) ? $curl_err : "HTTP $http_code";
flash_alert("OAuth token test failed: $err_msg", 'error');
redirect();
}
$json = json_decode($raw_body, true);
if (!is_array($json) || empty($json['access_token'])) {
flash_alert("OAuth token test failed: access token missing in provider response.", 'error');
redirect();
}
$new_access_token = sanitizeInput($json['access_token']);
$new_expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
$new_refresh_token = !empty($json['refresh_token']) ? sanitizeInput($json['refresh_token']) : '';
$new_access_token_esc = mysqli_real_escape_string($mysqli, $new_access_token);
$new_expires_at_esc = mysqli_real_escape_string($mysqli, $new_expires_at);
$refresh_sql = '';
if (!empty($new_refresh_token)) {
$new_refresh_token_esc = mysqli_real_escape_string($mysqli, $new_refresh_token);
$refresh_sql = ", config_mail_oauth_refresh_token = '$new_refresh_token_esc'";
}
mysqli_query($mysqli, "UPDATE settings SET config_mail_oauth_access_token = '$new_access_token_esc', config_mail_oauth_access_token_expires_at = '$new_expires_at_esc'$refresh_sql WHERE company_id = 1");
$provider_label = $provider === 'microsoft_oauth' ? 'Microsoft 365' : 'Google Workspace';
logAction("Settings", "Edit", "$session_name tested OAuth token refresh for $provider_label mail settings");
flash_alert("OAuth token refresh successful for $provider_label. Access token expires at $new_expires_at.");
redirect();
}

View File

@@ -4,6 +4,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['edit_module_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_module_enable_itdoc = intval($_POST['config_module_enable_itdoc'] ?? 0);
$config_module_enable_ticketing = intval($_POST['config_module_enable_ticketing'] ?? 0);
$config_module_enable_accounting = intval($_POST['config_module_enable_accounting'] ?? 0);

View File

@@ -3,7 +3,7 @@
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_GET['stripe_remove_pm'])) {
validateCSRFToken($_GET['csrf_token']);
if (!$config_stripe_enable) {
@@ -34,21 +34,21 @@ if (isset($_GET['stripe_remove_pm'])) {
// Remove Auto Pay on recurring invoices that are stripe
$sql_recurring_invoices = mysqli_query($mysqli, "SELECT recurring_invoice_id FROM recurring_invoices WHERE recurring_invoice_client_id = $client_id");
while ($row = mysqli_fetch_array($sql_recurring_invoices)) {
while ($row = mysqli_fetch_assoc($sql_recurring_invoices)) {
$recurring_invoice_id = intval($row['recurring_invoice_id']);
mysqli_query($mysqli, "DELETE FROM recurring_payments WHERE recurring_payment_method = 'Stripe' AND recurring_payment_recurring_invoice_id = $recurring_invoice_id");
}
logAction("Stripe", "Update", "$session_name deleted saved Stripe payment method (PM: $payment_method)", $client_id);
flash_alert("Payment method removed", 'error');
redirect();
}
if (isset($_GET['stripe_reset_customer'])) {
validateCSRFToken($_GET['csrf_token']);
$client_id = intval($_GET['client_id']);
@@ -59,7 +59,7 @@ if (isset($_GET['stripe_reset_customer'])) {
// Remove Auto Pay on recurring invoices that are stripe
$sql_recurring_invoices = mysqli_query($mysqli, "SELECT recurring_invoice_id FROM recurring_invoices WHERE recurring_invoice_client_id = $client_id");
while ($row = mysqli_fetch_array($sql_recurring_invoices)) {
while ($row = mysqli_fetch_assoc($sql_recurring_invoices)) {
$recurring_invoice_id = intval($row['recurring_invoice_id']);
mysqli_query($mysqli, "DELETE FROM recurring_payments WHERE recurring_payment_method = 'Stripe' AND recurring_payment_recurring_invoice_id = $recurring_invoice_id");
}
@@ -67,7 +67,7 @@ if (isset($_GET['stripe_reset_customer'])) {
logAction("Stripe", "Delete", "$session_name reset Stripe settings for client", $client_id);
flash_alert("Reset client Stripe settings", 'error');
redirect();
}

View File

@@ -52,6 +52,8 @@ if (isset($_POST['edit_favicon_settings'])) {
if (isset($_GET['reset_favicon'])) {
validateCSRFToken($_GET['csrf_token']);
if (file_exists("../uploads/favicon.ico")) {
unlink("../uploads/favicon.ico");
}

View File

@@ -4,6 +4,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['edit_ticket_settings'])) {
validateCSRFToken($_POST['csrf_token']);
$config_ticket_prefix = sanitizeInput($_POST['config_ticket_prefix']);
$config_ticket_next_number = intval($_POST['config_ticket_next_number']);
$config_ticket_email_parse = intval($_POST['config_ticket_email_parse'] ?? 0);
@@ -18,7 +20,7 @@ if (isset($_POST['edit_ticket_settings'])) {
$config_ticket_moving_columns = intval($_POST['config_ticket_moving_columns']);
$config_ticket_ordering = intval($_POST['config_ticket_ordering']);
$config_ticket_timer_autostart = intval($_POST['config_ticket_timer_autostart']);
mysqli_query($mysqli,"UPDATE settings SET config_ticket_prefix = '$config_ticket_prefix', config_ticket_next_number = $config_ticket_next_number, config_ticket_email_parse = $config_ticket_email_parse, config_ticket_email_parse_unknown_senders = $config_ticket_email_parse_unknown_senders, config_ticket_autoclose_hours = $config_ticket_autoclose_hours, config_ticket_new_ticket_notification_email = '$config_ticket_new_ticket_notification_email', config_ticket_default_billable = $config_ticket_default_billable, config_ticket_default_view = $config_ticket_default_view, config_ticket_moving_columns = $config_ticket_moving_columns, config_ticket_ordering = $config_ticket_ordering, config_ticket_timer_autostart = $config_ticket_timer_autostart WHERE company_id = 1");
logAction("Settings", "Edit", "$session_name edited ticket settings");

View File

@@ -6,6 +6,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_software_template'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$version = sanitizeInput($_POST['version']);
$description = sanitizeInput($_POST['description']);
@@ -27,6 +29,8 @@ if (isset($_POST['add_software_template'])) {
if (isset($_POST['edit_software_template'])) {
validateCSRFToken($_POST['csrf_token']);
$software_template_id = intval($_POST['software_template_id']);
$name = sanitizeInput($_POST['name']);
$version = sanitizeInput($_POST['version']);
@@ -47,11 +51,13 @@ if (isset($_POST['edit_software_template'])) {
if (isset($_GET['delete_software_template'])) {
validateCSRFToken($_GET['csrf_token']);
$software_template_id = intval($_GET['delete_software_template']);
// Get Software Template Name for logging and alert message
$sql = mysqli_query($mysqli,"SELECT software_template_name FROM software_templates WHERE software_template_id = $software_template_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$software_template_name = sanitizeInput($row['software_template_name']);
mysqli_query($mysqli,"DELETE FROM software_templates WHERE software_template_id = $software_template_id");

View File

@@ -8,6 +8,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_tag'])) {
validateCSRFToken($_POST['csrf_token']);
require_once 'tag_model.php';
mysqli_query($mysqli,"INSERT INTO tags SET tag_name = '$name', tag_type = $type, tag_color = '$color', tag_icon = '$icon'");
@@ -24,6 +26,8 @@ if (isset($_POST['add_tag'])) {
if (isset($_POST['edit_tag'])) {
validateCSRFToken($_POST['csrf_token']);
require_once 'post/tag_model.php';
$tag_id = intval($_POST['tag_id']);
@@ -39,9 +43,11 @@ if (isset($_POST['edit_tag'])) {
}
if (isset($_GET['delete_tag'])) {
validateCSRFToken($_GET['csrf_token']);
$tag_id = intval($_GET['delete_tag']);
$tag_name = sanitizeInput(getFieldById('tags', $tag_id, 'tag_name'));
mysqli_query($mysqli,"DELETE FROM tags WHERE tag_id = $tag_id");

View File

@@ -9,6 +9,7 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_tax'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$percent = floatval($_POST['percent']);
@@ -27,6 +28,7 @@ if (isset($_POST['add_tax'])) {
if (isset($_POST['edit_tax'])) {
validateCSRFToken($_POST['csrf_token']);
$tax_id = intval($_POST['tax_id']);
$name = sanitizeInput($_POST['name']);
$percent = floatval($_POST['percent']);
@@ -42,8 +44,9 @@ if (isset($_POST['edit_tax'])) {
}
if (isset($_GET['archive_tax'])) {
validateCSRFToken($_GET['csrf_token']);
$tax_id = intval($_GET['archive_tax']);
$tax_name = sanitizeInput(getFieldById('taxes', $tax_id, 'tax_name'));
@@ -59,7 +62,9 @@ if (isset($_GET['archive_tax'])) {
}
if (isset($_GET['delete_tax'])) {
validateCSRFToken($_GET['csrf_token']);
$tax_id = intval($_GET['delete_tax']);
$tax_name = sanitizeInput(getFieldById('taxes', $tax_id, 'tax_name'));

View File

@@ -4,6 +4,8 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_ticket_status'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$color = sanitizeInput($_POST['color']);
@@ -21,6 +23,8 @@ if (isset($_POST['add_ticket_status'])) {
if (isset($_POST['edit_ticket_status'])) {
validateCSRFToken($_POST['csrf_token']);
$ticket_status_id = intval($_POST['ticket_status_id']);
$name = sanitizeInput($_POST['name']);
$color = sanitizeInput($_POST['color']);

View File

@@ -10,6 +10,8 @@ require_once '../agent/post/task.php';
if (isset($_POST['add_ticket_template'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
$subject = sanitizeInput($_POST['subject']);
@@ -34,6 +36,8 @@ if (isset($_POST['add_ticket_template'])) {
if (isset($_POST['edit_ticket_template'])) {
validateCSRFToken($_POST['csrf_token']);
$ticket_template_id = intval($_POST['ticket_template_id']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
@@ -52,6 +56,8 @@ if (isset($_POST['edit_ticket_template'])) {
if (isset($_GET['delete_ticket_template'])) {
validateCSRFToken($_GET['csrf_token']);
$ticket_template_id = intval($_GET['delete_ticket_template']);
$ticket_template_name = sanitizeInput(getFieldById('ticket_templates', $ticket_template_id, 'ticket_template_name'));
@@ -72,6 +78,8 @@ if (isset($_GET['delete_ticket_template'])) {
if (isset($_POST['add_ticket_template_task'])) {
validateCSRFToken($_POST['csrf_token']);
$ticket_template_id = intval($_POST['ticket_template_id']);
$task_name = sanitizeInput($_POST['task_name']);
@@ -89,6 +97,8 @@ if (isset($_POST['add_ticket_template_task'])) {
if (isset($_GET['delete_task_template'])) {
validateCSRFToken($_GET['csrf_token']);
$task_template_id = intval($_GET['delete_task_template']);
$task_template_name = sanitizeInput(getFieldById('tags', $task_template_id, 'task_template_name'));
@@ -100,5 +110,5 @@ if (isset($_GET['delete_task_template'])) {
flash_alert("Task <strong>$task_template_name</strong> deleted", 'error');
redirect();
}

View File

@@ -21,7 +21,7 @@ if (isset($_GET['update'])) {
if ($config_telemetry > 0 OR $config_telemetry = 2) {
$sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_name = sanitizeInput($row['company_name']);
$website = sanitizeInput($row['company_website']);

View File

@@ -53,7 +53,7 @@ if (isset($_POST['add_user'])) {
mysqli_query($mysqli, "INSERT INTO user_settings SET user_id = $user_id, user_config_force_mfa = $force_mfa");
$sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_name = sanitizeInput($row['company_name']);
// Sanitize Config vars from load_global_settings.php
@@ -118,7 +118,7 @@ if (isset($_POST['edit_user'])) {
// Get current Avatar
$sql = mysqli_query($mysqli, "SELECT user_avatar FROM users WHERE user_id = $user_id");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$existing_file_name = sanitizeInput($row['user_avatar']);
$extended_log_description = '';
@@ -148,7 +148,7 @@ if (isset($_POST['edit_user'])) {
// Set Avatar
mysqli_query($mysqli, "UPDATE users SET user_avatar = '$new_file_name' WHERE user_id = $user_id");
$extended_alert_description = '. File successfully uploaded.';
}
}
@@ -353,8 +353,8 @@ if (isset($_POST['ir_reset_user_password'])) {
// Confirm logged-in user password, for security
$admin_password = $_POST['admin_password'];
$sql = mysqli_query($mysqli, "SELECT * FROM users WHERE user_id = $session_user_id");
$userRow = mysqli_fetch_array($sql);
$userRow = mysqli_fetch_assoc($sql);
if (!password_verify($admin_password, $userRow['user_password'])) {
flash_alert("Incorrect password.", 'error');
redirect();
@@ -364,7 +364,7 @@ if (isset($_POST['ir_reset_user_password'])) {
$sql_users = mysqli_query($mysqli, "SELECT * FROM users WHERE (user_archived_at IS NULL AND user_id != $session_user_id)");
// Reset passwords
while ($row = mysqli_fetch_array($sql_users)) {
while ($row = mysqli_fetch_assoc($sql_users)) {
$user_id = intval($row['user_id']);
$user_email = sanitizeInput($row['user_email']);
$new_password = randomString();

View File

@@ -9,6 +9,8 @@ require_once '../agent/post/vendor.php';
if (isset($_POST['add_vendor_template'])) {
validateCSRFToken($_POST['csrf_token']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
$account_number = sanitizeInput($_POST['account_number']);
@@ -37,6 +39,8 @@ if (isset($_POST['add_vendor_template'])) {
if (isset($_POST['edit_vendor_template'])) {
validateCSRFToken($_POST['csrf_token']);
$vendor_template_id = intval($_POST['vendor_template_id']);
$name = sanitizeInput($_POST['name']);
$description = sanitizeInput($_POST['description']);
@@ -140,7 +144,9 @@ if (isset($_POST['edit_vendor_template'])) {
}
if (isset($_GET['delete_vendor_template'])) {
validateCSRFToken($_GET['csrf_token']);
$vendor_template_id = intval($_GET['delete_vendor_template']);
$vendor_template_name = sanitizeInput(getFieldById('vendor_templates', $vendor_template_id, 'vendor_template_name'));

View File

@@ -61,7 +61,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while($row = mysqli_fetch_array($sql)){
while($row = mysqli_fetch_assoc($sql)){
$project_template_id = intval($row['project_template_id']);
$project_template_name = nullable_htmlentities($row['project_template_name']);
$project_template_description = nullable_htmlentities($row['project_template_description']);
@@ -87,16 +87,16 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<tr>
<td>
<a class="text-dark ajax-modal" href="#" data-modal-url="modals/project_template/project_template_edit.php?project_template_id=<?= $project_template_id ?>">
<a class="text-dark" href="project_template_details.php?project_template_id=<?= $project_template_id ?>">
<div class="media">
<i class="fa fa-fw fa-2x fa-project-diagram mr-3"></i>
<div class="media-body">
<div>
<a href="project_template_details.php?project_template_id=<?php echo $project_template_id; ?>">
<?php echo $project_template_name; ?>
</a>
<?= $project_template_name ?>
</div>
<div>
<small class="text-secondary"><?= $project_template_description ?></small>
</div>
<div><small class="text-secondary"><?php echo $project_template_description; ?></small></div>
</div>
</div>
</a>
@@ -114,7 +114,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</a>
<?php if($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_project_template=<?php echo $project_template_id; ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_project_template=<?php echo $project_template_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php } ?>
@@ -124,7 +124,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</tr>
<?php
}
?>

View File

@@ -19,7 +19,7 @@ if (isset($_GET['project_template_id'])) {
exit;
}
$row = mysqli_fetch_array($sql_project_templates);
$row = mysqli_fetch_assoc($sql_project_templates);
$project_template_name = nullable_htmlentities($row['project_template_name']);
$project_template_description = nullable_htmlentities($row['project_template_description']);
@@ -104,13 +104,13 @@ if (isset($_GET['project_template_id'])) {
</a>
<?php if ($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?archive_project_template=<?php echo $project_template_id; ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?archive_project_template=<?php echo $project_template_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-archive mr-2"></i>Archive (not yet implemented)
</a>
<?php } ?>
<?php if ($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_project_template=<?php echo $project_template_id; ?>">
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_project_template=<?php echo $project_template_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php } ?>
@@ -130,10 +130,9 @@ if (isset($_GET['project_template_id'])) {
<h5 class="text-secondary"><i class="fa fa-fw fa-life-ring mr-2"></i>Project Ticket Templates</h5>
<div class="table-responsive-sm">
<table class="table table-striped table-borderless table-hover">
<table class="table table-striped table-borderless table-hover" id="ticket_templates">
<thead class="text-dark">
<tr>
<th>Order</th>
<th>Template Name</th>
<th>Description</th>
<th>Ticket Subject</th>
@@ -143,7 +142,7 @@ if (isset($_GET['project_template_id'])) {
<tbody>
<?php
while ($row = mysqli_fetch_array($sql_ticket_templates)) {
while ($row = mysqli_fetch_assoc($sql_ticket_templates)) {
$ticket_template_id = intval($row['ticket_template_id']);
$ticket_template_order = intval($row['ticket_template_order']);
$ticket_template_name = nullable_htmlentities($row['ticket_template_name']);
@@ -154,16 +153,9 @@ if (isset($_GET['project_template_id'])) {
?>
<tr>
<td class="pr-0">
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="edit_ticket_template_order">
<input type="hidden" name="project_template_id" value="<?php echo $project_template_id; ?>">
<input type="hidden" name="ticket_template_id" value="<?php echo $ticket_template_id; ?>">
<input type="text" class="form-control pr-0" onchange="this.form.submit()" name="order" value="<?php echo $ticket_template_order; ?>">
</form>
</td>
<tr data-task-id="<?php echo $ticket_template_id; ?>">
<td>
<a href="#" class="drag-handle"><i class="fas fa-bars text-muted mr-2"></i></a>
<a href="ticket_template_details.php?ticket_template_id=<?php echo $ticket_template_id; ?>">
<?php echo $ticket_template_name; ?>
</a>
@@ -172,6 +164,7 @@ if (isset($_GET['project_template_id'])) {
<td><?php echo $ticket_template_subject; ?></td>
<td>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="project_template_id" value="<?php echo $project_template_id; ?>">
<input type="hidden" name="ticket_template_id" value="<?php echo $ticket_template_id; ?>">
<button type="submit" class="btn btn-default btn-sm confirm-link"
@@ -199,7 +192,7 @@ if (isset($_GET['project_template_id'])) {
<h5 class="text-secondary"><i class="fas fa-fw fa-tasks mr-2"></i>Project Task Templates</h5>
<table class="table">
<?php
while($row = mysqli_fetch_array($sql_task_templates)){
while($row = mysqli_fetch_assoc($sql_task_templates)){
$task_template_id = intval($row['task_template_id']);
$task_template_name = nullable_htmlentities($row['task_template_name']);
?>
@@ -219,6 +212,28 @@ if (isset($_GET['project_template_id'])) {
</div> <!-- End row -->
<script src="../plugins/SortableJS/Sortable.min.js"></script>
<script>
new Sortable(document.querySelector('table#ticket_templates tbody'), {
handle: '.drag-handle',
animation: 150,
onEnd: function (evt) {
const rows = document.querySelectorAll('table#ticket_templates tbody tr');
const positions = Array.from(rows).map((row, index) => ({
id: row.dataset.taskId,
order: index
}));
$.post('/agent/ajax.php', {
update_project_template_ticket_order: true,
csrf_token: '<?= $_SESSION['csrf_token'] ?>',
project_template_id: <?php echo $project_template_id; ?>,
positions: positions
});
}
});
</script>
<?php
}

View File

@@ -17,7 +17,6 @@ $sql = mysqli_query(
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="alert alert-info text-center"><strong>Roles are still in development. Permissions may not be fully enforced.</strong></div>
<div class="card card-dark">
<div class="card-header py-2">
@@ -65,7 +64,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$role_id = intval($row['role_id']);
$role_name = nullable_htmlentities($row['role_name']);
$role_description = nullable_htmlentities($row['role_description']);
@@ -95,10 +94,15 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<tr>
<td>
<a href="#" <?php if ($role_id !== 3) { ?> class="ajax-modal" data-modal-url="modals/role/role_edit.php?id=<?= $role_id ?>" <?php } ?>>
<strong class="text-dark"><?php echo $role_name; ?></strong>
<a class="<?php if ($role_id !== 3) { ?> ajax-modal" data-modal-url="modals/role/role_edit.php?id=<?= $role_id ?>" <?php } ?> href="#">
<div class="media">
<i class="fas fa-fw fa-2x fa-user-shield text-dark mr-2"></i>
<div class="media-body">
<div><?= $role_name ?></div>
<div><small class="text-secondary"><?= $role_description ?></small></div>
</div>
</div>
</a>
<div class="text-secondary"><?php echo $role_description; ?></div>
</td>
<td><?php echo $user_names_string; ?></td>
<td><?php echo $role_admin ? 'Yes' : 'No' ; ?></td>

View File

@@ -104,7 +104,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$saved_payment_id = intval($row['saved_payment_id']);
$client_id = intval($row['saved_payment_client_id']);
$client_name = nullable_htmlentities($row['client_name']);

View File

@@ -4,7 +4,7 @@ require_once "includes/inc_all_admin.php";
$sql = mysqli_query($mysqli,"SELECT * FROM companies, settings WHERE companies.company_id = settings.company_id AND companies.company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_id = intval($row['company_id']);
$company_name = nullable_htmlentities($row['company_name']);
$company_country = nullable_htmlentities($row['company_country']);
@@ -37,7 +37,7 @@ $company_initials = nullable_htmlentities(initials($company_name));
<div class="col-md-3 text-center">
<?php if ($company_logo) { ?>
<img class="img-thumbnail" src="<?php echo "../uploads/settings/$company_logo"; ?>">
<a href="post.php?remove_company_logo" class="btn btn-outline-danger btn-block">Remove Logo</a>
<a href="post.php?remove_company_logo&csrf_token=<?= $_SESSION['csrf_token'] ?>" class="btn btn-outline-danger btn-block">Remove Logo</a>
<hr>
<?php } ?>
<div class="form-group">

View File

@@ -66,13 +66,13 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$custom_field_id = intval($row['custom_field_id']);
$custom_field_label = nullable_htmlentities($row['custom_field_label']);
$custom_field_type = nullable_htmlentities($row['custom_field_type']);
$custom_field_location = intval($row['custom_field_location']);
$custom_field_order = intval($row['custom_field_order']);
?>
<tr>
<td><a class="text-dark" href="#" data-toggle="modal" data-target="#editCustomFieldModal<?php echo $custom_field_id; ?>"><?php echo $custom_field_label; ?></a></td>
@@ -118,4 +118,3 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
require_once "custom_field_create_modal.php";
require_once "../includes/footer.php";

View File

@@ -42,7 +42,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM calendars ORDER BY calendar_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$calendar_id = intval($row['calendar_id']);
$calendar_name = nullable_htmlentities($row['calendar_name']); ?>
<option <?php if ($config_default_calendar == $calendar_id) {
@@ -65,7 +65,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']); ?>
<option <?php if ($config_default_transfer_from_account == $account_id) {
@@ -88,7 +88,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']); ?>
<option <?php if ($config_default_transfer_to_account == $account_id) {
@@ -111,7 +111,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']); ?>
<option <?php if ($config_default_payment_account == $account_id) {
@@ -136,7 +136,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']); ?>
<option <?php if ($config_default_expense_account == $account_id) {
@@ -159,7 +159,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_type = 'Payment Method' ORDER BY category_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$payment_method = nullable_htmlentities($row['category_name']); ?>
<option <?php if ($config_default_payment_method == $payment_method) {
echo "selected";
@@ -181,7 +181,7 @@ require_once "includes/inc_all_admin.php";
<?php
$sql = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_type = 'Payment Method' ORDER BY category_name ASC");
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$payment_method = nullable_htmlentities($row['category_name']); ?>
<option <?php if ($config_default_expense_payment_method == $payment_method) {
echo "selected";

View File

@@ -4,7 +4,7 @@ require_once "includes/inc_all_admin.php";
$sql = mysqli_query($mysqli,"SELECT * FROM companies, settings WHERE companies.company_id = settings.company_id AND companies.company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_locale = nullable_htmlentities($row['company_locale']);
$company_currency = nullable_htmlentities($row['company_currency']);
@@ -76,4 +76,3 @@ $timezones = DateTimeZone::listIdentifiers();
<?php
require_once "../includes/footer.php";

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,7 @@ require_once "includes/inc_all_admin.php";
<button type="submit" name="edit_favicon_settings" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Upload Icon</button>
<?php if(file_exists("../uploads/favicon.ico")) { ?>
<a href="post.php?reset_favicon" class="btn btn-outline-danger"><i class="fas fa-redo-alt mr-2"></i>Reset Favicon</a>
<a href="post.php?reset_favicon&csrf_token=<?= $_SESSION['csrf_token'] ?>" class="btn btn-outline-danger"><i class="fas fa-redo-alt mr-2"></i>Reset Favicon</a>
<?php } ?>
</form>
</div>

View File

@@ -66,7 +66,7 @@ require_once "includes/inc_all_admin.php";
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-clock"></i></span>
</div>
<input type="number" min="72" class="form-control" name="config_ticket_autoclose_hours" placeholder="Delay in hours before a resolved ticket is fully closed" value="<?php echo intval($config_ticket_autoclose_hours); ?>">
<input type="number" min="24" class="form-control" name="config_ticket_autoclose_hours" placeholder="Delay in hours before a resolved ticket is fully closed" value="<?php echo intval($config_ticket_autoclose_hours); ?>">
</div>
</div>
@@ -97,7 +97,7 @@ require_once "includes/inc_all_admin.php";
<label>Kanban Settings</label>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="config_ticket_ordering" <?php if ($config_ticket_ordering == 1) { echo "checked"; } ?> value="1" id="ticketOrderingSwitch">
<label class="custom-control-label" for="ticketOrderingSwitch">Allow ticket ordering within its column<small class="text-secondary">(uncheked will result in ordering it by priority and id)</small></label>
<label class="custom-control-label" for="ticketOrderingSwitch">Allow ticket ordering within its column<small class="text-secondary"> (unchecked = order by priority and id)</small></label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="config_ticket_moving_columns" <?php if ($config_ticket_moving_columns == 1) { echo "checked"; } ?> value="1" id="ticketMovingColumnsSwitch">

View File

@@ -9,7 +9,7 @@ require_once "includes/inc_all_admin.php";
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS * FROM software_templates
WHERE software_template_name LIKE '%$q%' OR software_template_type LIKE '%$q%'
WHERE software_template_name LIKE '%$q%' OR software_template_type LIKE '%$q%'
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
@@ -68,7 +68,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while($row = mysqli_fetch_array($sql)){
while($row = mysqli_fetch_assoc($sql)){
$software_template_id = intval($row['software_template_id']);
$software_template_name = nullable_htmlentities($row['software_template_name']);
$software_template_version = nullable_htmlentities($row['software_template_version']);
@@ -103,7 +103,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</a>
<?php if($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_software_template=<?php echo $software_template_id; ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_software_template=<?php echo $software_template_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php } ?>

View File

@@ -117,7 +117,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$tag_id = intval($row['tag_id']);
$tag_name = nullable_htmlentities($row['tag_name']);
$tag_color = nullable_htmlentities($row['tag_color']);
@@ -142,7 +142,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fas fa-fw fa-edit mr-2"></i>Edit
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_tag=<?php echo $tag_id; ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_tag=<?php echo $tag_id; ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>

View File

@@ -45,7 +45,7 @@ $num_rows = mysqli_num_rows($sql);
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$tax_id = intval($row['tax_id']);
$tax_name = nullable_htmlentities($row['tax_name']);
$tax_percent = floatval($row['tax_percent']);

View File

@@ -67,7 +67,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$ticket_status_id = intval($row['ticket_status_id']);
$ticket_status_name = nullable_htmlentities($row['ticket_status_name']);
$ticket_status_color = nullable_htmlentities($row['ticket_status_color']);
@@ -81,7 +81,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<tr>
<td>
<a href="#"
<a href="#"
<?php if ( $ticket_status_id > 5 ) { ?>
class="ajax-modal" data-modal-url="modals/ticket_status/ticket_status_edit.php?id=<?= $ticket_status_id ?>"
<?php } ?>

View File

@@ -8,14 +8,14 @@ require_once "includes/inc_all_admin.php";
$sql = mysqli_query(
$mysqli,
"SELECT SQL_CALC_FOUND_ROWS *,
"SELECT SQL_CALC_FOUND_ROWS *,
COUNT(task_template_id) AS task_count
FROM ticket_templates
LEFT JOIN task_templates ON task_template_ticket_template_id = ticket_template_id
WHERE (ticket_template_name LIKE '%$q%' OR ticket_template_description LIKE '%$q%')
AND ticket_template_archived_at IS NULL
GROUP BY ticket_template_id
ORDER BY $sort $order
ORDER BY $sort $order
LIMIT $record_from, $record_to"
);
@@ -69,7 +69,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while($row = mysqli_fetch_array($sql)){
while($row = mysqli_fetch_assoc($sql)){
$ticket_template_id = intval($row['ticket_template_id']);
$ticket_template_name = nullable_htmlentities($row['ticket_template_name']);
$ticket_template_description = nullable_htmlentities($row['ticket_template_description']);
@@ -101,7 +101,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_ticket_template=<?= $ticket_template_id ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_ticket_template=<?= $ticket_template_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
</div>

View File

@@ -23,7 +23,7 @@ if (mysqli_num_rows($sql_ticket_template) == 0) {
exit();
}
$row = mysqli_fetch_array($sql_ticket_template);
$row = mysqli_fetch_assoc($sql_ticket_template);
$ticket_template_name = nullable_htmlentities($row['ticket_template_name']);
$ticket_template_description = nullable_htmlentities($row['ticket_template_description']);
@@ -51,26 +51,17 @@ $sql_task_templates = mysqli_query($mysqli, "SELECT * FROM task_templates WHERE
</ol>
<div class="row">
<div class="col-9">
<div class="col-md-9">
<div class="card card-dark">
<div class="card-header">
<h3 class="card-title mt-2">
<div class="media">
<i class="fa fa-fw fa-2x fa-life-ring mr-3"></i>
<div class="media-body">
<h3 class="mb-0"><?php echo $ticket_template_name; ?></h3>
<div><small class="text-secondary"><?php //echo $ticket_template_description; ?></small></div>
</div>
</div>
</h3>
<h3 class="card-title mt-1"><?php echo $ticket_template_name; ?></h3>
<div class="card-tools">
<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#editTicketTemplateModal">
<button type="button" class="btn btn-tool btn-sm" data-toggle="modal" data-target="#editTicketTemplateModal">
<i class="fas fa-edit"></i>
</button>
</div>
</div>
<h5><?php echo $ticket_template_subject; ?></h5>
<div class="card-body prettyContent">
<?php echo $ticket_template_details; ?>
</div>
@@ -78,7 +69,7 @@ $sql_task_templates = mysqli_query($mysqli, "SELECT * FROM task_templates WHERE
</div>
<div class="col-3">
<div class="col-md-3">
<div class="card card-dark">
<div class="card-header">
@@ -86,6 +77,7 @@ $sql_task_templates = mysqli_query($mysqli, "SELECT * FROM task_templates WHERE
</div>
<div class="card-body">
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="ticket_template_id" value="<?php echo $ticket_template_id; ?>">
<div class="form-group">
<div class="input-group input-group-sm">
@@ -98,7 +90,7 @@ $sql_task_templates = mysqli_query($mysqli, "SELECT * FROM task_templates WHERE
</form>
<table class="table table-sm" id="tasks">
<?php
while($row = mysqli_fetch_array($sql_task_templates)){
while($row = mysqli_fetch_assoc($sql_task_templates)){
$task_id = intval($row['task_template_id']);
$task_name = nullable_htmlentities($row['task_template_name']);
$task_completion_estimate = intval($row['task_template_completion_estimate']);
@@ -154,8 +146,9 @@ new Sortable(document.querySelector('table#tasks tbody'), {
order: index
}));
$.post('ajax.php', {
$.post('/agent/ajax.php', {
update_task_templates_order: true,
csrf_token: '<?= $_SESSION['csrf_token'] ?>',
ticket_template_id: <?php echo $ticket_template_id; ?>,
positions: positions
});

View File

@@ -104,7 +104,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$user_id = intval($row['user_id']);
$user_name = nullable_htmlentities($row['user_name']);
$user_email = nullable_htmlentities($row['user_email']);
@@ -139,7 +139,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if (mysqli_num_rows($sql_last_login) == 0) {
$last_login = "<span class='text-bold'>Never logged in</span>";
} else {
$row = mysqli_fetch_array($sql_last_login);
$row = mysqli_fetch_assoc($sql_last_login);
$log_created_at = nullable_htmlentities($row['log_created_at']);
$log_ip = nullable_htmlentities($row['log_ip']);
$log_user_agent = nullable_htmlentities($row['log_user_agent']);

View File

@@ -64,7 +64,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$vendor_template_id = intval($row['vendor_template_id']);
$vendor_template_name = nullable_htmlentities($row['vendor_template_name']);
$vendor_template_description = nullable_htmlentities($row['vendor_template_description']);
@@ -140,7 +140,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</a>
<?php if ($session_user_role == 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_vendor_template=<?= $vendor_template_id ?>">
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_vendor_template=<?= $vendor_template_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
<i class="fas fa-fw fa-trash mr-2"></i>Delete
</a>
<?php } ?>

View File

@@ -59,7 +59,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<tbody>
<?php
while ($row = mysqli_fetch_array($sql)) {
while ($row = mysqli_fetch_assoc($sql)) {
$account_id = intval($row['account_id']);
$account_name = nullable_htmlentities($row['account_name']);
$opening_balance = floatval($row['opening_balance']);
@@ -67,15 +67,15 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$account_notes = nullable_htmlentities($row['account_notes']);
$sql_payments = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS total_payments FROM payments WHERE payment_account_id = $account_id");
$row = mysqli_fetch_array($sql_payments);
$row = mysqli_fetch_assoc($sql_payments);
$total_payments = floatval($row['total_payments']);
$sql_revenues = mysqli_query($mysqli, "SELECT SUM(revenue_amount) AS total_revenues FROM revenues WHERE revenue_account_id = $account_id");
$row = mysqli_fetch_array($sql_revenues);
$row = mysqli_fetch_assoc($sql_revenues);
$total_revenues = floatval($row['total_revenues']);
$sql_expenses = mysqli_query($mysqli, "SELECT SUM(expense_amount) AS total_expenses FROM expenses WHERE expense_account_id = $account_id");
$row = mysqli_fetch_array($sql_expenses);
$row = mysqli_fetch_assoc($sql_expenses);
$total_expenses = floatval($row['total_expenses']);
$balance = $opening_balance + $total_payments + $total_revenues - $total_expenses;

View File

@@ -40,32 +40,10 @@ if (isset($_GET['certificate_fetch_parse_json_details'])) {
}
/*
* Looks up info on the ticket number provided, used to populate the ticket merge modal
*/
if (isset($_GET['merge_ticket_get_json_details'])) {
enforceUserPermission('module_support');
$merge_into_ticket_number = intval(preg_replace('/[^0-9]/', '', $_GET['merge_into_ticket_number']));
$sql = mysqli_query($mysqli, "SELECT ticket_id, ticket_number, ticket_prefix, ticket_subject, ticket_priority, ticket_status, ticket_status_name, client_name, contact_name FROM tickets
LEFT JOIN clients ON ticket_client_id = client_id
LEFT JOIN contacts ON ticket_contact_id = contact_id
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
WHERE ticket_number = $merge_into_ticket_number");
if (mysqli_num_rows($sql) == 0) {
//Do nothing.
echo "No ticket found!";
} else {
//Return ticket, client and contact details for the given ticket number
$response = mysqli_fetch_array($sql);
echo json_encode($response);
}
}
if (isset($_POST['client_set_notes'])) {
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_client', 2);
$client_id = intval($_POST['client_id']);
@@ -80,6 +58,9 @@ if (isset($_POST['client_set_notes'])) {
}
if (isset($_POST['contact_set_notes'])) {
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_client', 2);
$contact_id = intval($_POST['contact_id']);
@@ -89,7 +70,7 @@ if (isset($_POST['contact_set_notes'])) {
$sql = mysqli_query($mysqli,"SELECT contact_name, contact_client_id
FROM contacts WHERE contact_id = $contact_id"
);
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$contact_name = sanitizeInput($row['contact_name']);
$client_id = intval($row['contact_client_id']);
@@ -102,6 +83,9 @@ if (isset($_POST['contact_set_notes'])) {
}
if (isset($_POST['asset_set_notes'])) {
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_support', 2);
$asset_id = intval($_POST['asset_id']);
@@ -111,7 +95,7 @@ if (isset($_POST['asset_set_notes'])) {
$sql = mysqli_query($mysqli,"SELECT asset_name, asset_client_id
FROM assets WHERE asset_id = $asset_id"
);
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$asset_name = sanitizeInput($row['asset_name']);
$client_id = intval($row['asset_client_id']);
@@ -143,7 +127,7 @@ if (isset($_GET['ticket_query_views'])) {
$ticket_id = intval($_GET['ticket_id']);
$query = mysqli_query($mysqli, "SELECT user_name FROM ticket_views LEFT JOIN users ON view_user_id = user_id WHERE view_ticket_id = $ticket_id AND view_user_id != $session_user_id AND view_timestamp > DATE_SUB(NOW(), INTERVAL 2 MINUTE)");
while ($row = mysqli_fetch_array($query)) {
while ($row = mysqli_fetch_assoc($query)) {
$users[] = $row['user_name'];
}
@@ -168,6 +152,9 @@ if (isset($_GET['ticket_query_views'])) {
* Generates public/guest links for sharing credentials/docs
*/
if (isset($_GET['share_generate_link'])) {
validateCSRFToken($_GET['csrf_token']);
enforceUserPermission('module_support', 2);
$item_encrypted_username = ''; // Default empty
@@ -198,18 +185,18 @@ if (isset($_GET['share_generate_link'])) {
$item_key = randomString(32);
if ($item_type == "Document") {
$row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT document_name FROM documents WHERE document_id = $item_id AND document_client_id = $client_id LIMIT 1"));
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT document_name FROM documents WHERE document_id = $item_id AND document_client_id = $client_id LIMIT 1"));
$item_name = sanitizeInput($row['document_name']);
}
if ($item_type == "File") {
$row = mysqli_fetch_array(mysqli_query($mysqli, "SELECT file_name FROM files WHERE file_id = $item_id AND file_client_id = $client_id LIMIT 1"));
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT file_name FROM files WHERE file_id = $item_id AND file_client_id = $client_id LIMIT 1"));
$item_name = sanitizeInput($row['file_name']);
}
if ($item_type == "Credential") {
$credential = mysqli_query($mysqli, "SELECT credential_name, credential_username, credential_password FROM credentials WHERE credential_id = $item_id AND credential_client_id = $client_id LIMIT 1");
$row = mysqli_fetch_array($credential);
$row = mysqli_fetch_assoc($credential);
$item_name = sanitizeInput($row['credential_name']);
@@ -240,7 +227,7 @@ if (isset($_GET['share_generate_link'])) {
}
$sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_name = sanitizeInput($row['company_name']);
$company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'], $row['company_phone_country_code']));
@@ -298,7 +285,7 @@ if (isset($_GET['get_active_clients'])) {
ORDER BY client_accessed_at DESC"
);
while ($row = mysqli_fetch_array($client_sql)) {
while ($row = mysqli_fetch_assoc($client_sql)) {
$response['clients'][] = $row;
}
@@ -322,7 +309,7 @@ if (isset($_GET['get_client_contacts'])) {
ORDER BY contact_primary DESC, contact_technical DESC, contact_important DESC, contact_name"
);
while ($row = mysqli_fetch_array($contact_sql)) {
while ($row = mysqli_fetch_assoc($contact_sql)) {
$response['contacts'][] = $row;
}
@@ -344,10 +331,10 @@ if (isset($_GET['get_client_assets'])) {
LEFT JOIN contacts ON contact_id = asset_contact_id
WHERE assets.asset_archived_at IS NULL AND asset_client_id = $client_id
$access_permission_query
ORDER BY asset_important DESC, asset_name"
ORDER BY asset_favorite DESC, asset_name"
);
while ($row = mysqli_fetch_array($asset_sql)) {
while ($row = mysqli_fetch_assoc($asset_sql)) {
$response['assets'][] = $row;
}
@@ -371,7 +358,7 @@ if (isset($_GET['get_client_locations'])) {
ORDER BY location_primary DESC, location_name ASC"
);
while ($row = mysqli_fetch_array($locations_sql)) {
while ($row = mysqli_fetch_assoc($locations_sql)) {
$response['locations'][] = $row;
}
@@ -395,7 +382,7 @@ if (isset($_GET['get_client_vendors'])) {
ORDER BY vendor_name ASC"
);
while ($row = mysqli_fetch_array($vendors_sql)) {
while ($row = mysqli_fetch_assoc($vendors_sql)) {
$response['vendors'][] = $row;
}
@@ -502,7 +489,7 @@ if (isset($_POST['update_kanban_ticket'])) {
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
WHERE ticket_id = $ticket_id
");
$row = mysqli_fetch_array($ticket_sql);
$row = mysqli_fetch_assoc($ticket_sql);
$contact_name = sanitizeInput($row['contact_name']);
$contact_email = sanitizeInput($row['contact_email']);
@@ -521,7 +508,7 @@ if (isset($_POST['update_kanban_ticket'])) {
// Get Company Info
$sql = mysqli_query($mysqli, "SELECT company_name, company_phone, company_phone_country_code FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$company_name = sanitizeInput($row['company_name']);
$company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'], $row['company_phone_country_code']));
@@ -550,7 +537,7 @@ if (isset($_POST['update_kanban_ticket'])) {
// Also Email all the watchers
$sql_watchers = mysqli_query($mysqli, "SELECT watcher_email FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id");
$body .= "<br><br>----------------------------------------<br>YOU ARE A COLLABORATOR ON THIS TICKET";
while ($row = mysqli_fetch_array($sql_watchers)) {
while ($row = mysqli_fetch_assoc($sql_watchers)) {
$watcher_email = sanitizeInput($row['watcher_email']);
// Queue Mail
@@ -583,6 +570,9 @@ if (isset($_POST['update_kanban_ticket'])) {
if (isset($_POST['update_ticket_tasks_order'])) {
// Update multiple ticket tasks order
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_support', 2);
$positions = $_POST['positions'];
@@ -602,6 +592,9 @@ if (isset($_POST['update_ticket_tasks_order'])) {
if (isset($_POST['update_task_templates_order'])) {
// Update multiple task templates order
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_support', 2);
$positions = $_POST['positions'];
@@ -619,8 +612,32 @@ if (isset($_POST['update_task_templates_order'])) {
exit;
}
if (isset($_POST['update_project_template_ticket_order'])) {
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_support', 2);
$positions = $_POST['positions'];
$project_template_id = intval($_POST['project_template_id']);
foreach ($positions as $position) {
$id = intval($position['id']);
$order = intval($position['order']);
mysqli_query($mysqli, "UPDATE project_template_ticket_templates SET ticket_template_order = $order WHERE project_template_id = $project_template_id AND ticket_template_id = $id");
}
// return a response
echo json_encode(['status' => 'success']);
exit;
}
if (isset($_POST['update_quote_items_order'])) {
// Update multiple quote items order
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_sales', 2);
$positions = $_POST['positions'];
@@ -630,7 +647,7 @@ if (isset($_POST['update_quote_items_order'])) {
$id = intval($position['id']);
$order = intval($position['order']);
mysqli_query($mysqli, "UPDATE invoice_items SET item_order = $order WHERE item_quote_id = $quote_id AND item_id = $id");
mysqli_query($mysqli, "UPDATE quote_items SET item_order = $order WHERE item_quote_id = $quote_id AND item_id = $id");
}
// return a response
@@ -640,6 +657,9 @@ if (isset($_POST['update_quote_items_order'])) {
if (isset($_POST['update_invoice_items_order'])) {
// Update multiple invoice items order
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_sales', 2);
$positions = $_POST['positions'];
@@ -659,6 +679,9 @@ if (isset($_POST['update_invoice_items_order'])) {
if (isset($_POST['update_recurring_invoice_items_order'])) {
// Update multiple recurring invoice items order
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_sales', 2);
$positions = $_POST['positions'];
@@ -668,7 +691,7 @@ if (isset($_POST['update_recurring_invoice_items_order'])) {
$id = intval($position['id']);
$order = intval($position['order']);
mysqli_query($mysqli, "UPDATE invoice_items SET item_order = $order WHERE item_recurring_invoice_id = $recurring_invoice_id AND item_id = $id");
mysqli_query($mysqli, "UPDATE recurring_invoice_items SET item_order = $order WHERE item_recurring_invoice_id = $recurring_invoice_id AND item_id = $id");
}
// return a response
@@ -691,7 +714,7 @@ if (isset($_GET['client_duplicate_check'])) {
);
if (mysqli_num_rows($sql_clients) > 0) {
while ($row = mysqli_fetch_array($sql_clients)) {
while ($row = mysqli_fetch_assoc($sql_clients)) {
$response['message'] = "<i class='fas fa-fw fa-copy mr-2'></i> Potential duplicate: <i>" . nullable_htmlentities($row['client_name']) . "</i> already exists.";
}
}
@@ -713,7 +736,7 @@ if (isset($_GET['contact_email_check'])) {
// 1. Duplicate check
$sql_contacts = mysqli_query($mysqli, "SELECT contact_email FROM contacts WHERE contact_email = '$email' LIMIT 1");
if (mysqli_num_rows($sql_contacts) > 0) {
while ($row = mysqli_fetch_array($sql_contacts)) {
while ($row = mysqli_fetch_assoc($sql_contacts)) {
$response['message'] = "<i class='fas fa-fw fa-copy mr-2'></i> Potential duplicate: <i>" . nullable_htmlentities($row['contact_email']) . "</i> already exists.";
}
}
@@ -734,7 +757,7 @@ if (isset($_GET['ai_reword'])) {
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$model_name = $row['ai_model_name'];
$promptText = $row['ai_model_prompt'];
$url = $row['ai_provider_api_url'];
@@ -805,7 +828,7 @@ if (isset($_GET['ai_create_document_template'])) {
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$model_name = $row['ai_model_name'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];
@@ -861,7 +884,7 @@ if (isset($_GET['ai_ticket_summary'])) {
$sql = mysqli_query($mysqli, "SELECT * FROM ai_models LEFT JOIN ai_providers ON ai_model_ai_provider_id = ai_provider_id WHERE ai_model_use_case = 'General' LIMIT 1");
$row = mysqli_fetch_array($sql);
$row = mysqli_fetch_assoc($sql);
$model_name = $row['ai_model_name'];
$url = $row['ai_provider_api_url'];
$key = $row['ai_provider_api_key'];

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