diff --git a/calendar_events.php b/calendar_events.php index ad1a2370..85c11277 100644 --- a/calendar_events.php +++ b/calendar_events.php @@ -25,6 +25,7 @@ require_once "calendar_event_add_modal.php"; require_once "calendar_add_modal.php"; + //loop through IDs and create a modal for each $sql = mysqli_query($mysqli, "SELECT * FROM events LEFT JOIN calendars ON event_calendar_id = calendar_id"); while ($row = mysqli_fetch_array($sql)) { @@ -40,18 +41,16 @@ while ($row = mysqli_fetch_array($sql)) { $client_id = intval($row['event_client_id']); require "calendar_event_edit_modal.php"; - } ?> +?> @@ -183,4 +206,4 @@ while ($row = mysqli_fetch_array($sql)) { // Update the end date field document.getElementById("event_add_end").value = new_end; } - + \ No newline at end of file diff --git a/cron_mail_queue.php b/cron_mail_queue.php index 0827c092..6b13de1d 100644 --- a/cron_mail_queue.php +++ b/cron_mail_queue.php @@ -24,7 +24,7 @@ if ($config_enable_cron == 0) { } // Check Cron Key -if ( $argv[1] !== $config_cron_key ) { +if ($argv[1] !== $config_cron_key) { exit("Cron Key invalid -- Quitting.."); } @@ -37,7 +37,7 @@ $lock_file_path = "{$temp_dir}/itflow_mail_queue_{$installation_id}.lock"; // Check for lock file to prevent concurrent script runs if (file_exists($lock_file_path)) { $file_age = time() - filemtime($lock_file_path); - + // If file is older than 10 minutes (600 seconds), delete and continue if ($file_age > 600) { unlink($lock_file_path); @@ -61,7 +61,7 @@ file_put_contents($lock_file_path, "Locked"); // Get Mail Queue that has status of Queued and send it to the function sendSingleEmail() located in functions.php -$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()"); +$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0"); if (mysqli_num_rows($sql_queue) > 0) { while ($row = mysqli_fetch_array($sql_queue)) { @@ -74,6 +74,7 @@ if (mysqli_num_rows($sql_queue) > 0) { $email_content = $row['email_content']; $email_queued_at = $row['email_queued_at']; $email_sent_at = $row['email_sent_at']; + $email_ics_str = $row['email_cal_str']; // Sanitized Input $email_recipient_logging = sanitizeInput($row['email_recipient']); @@ -96,7 +97,8 @@ if (mysqli_num_rows($sql_queue) > 0) { $email_recipient, $email_recipient_name, $email_subject, - $email_content + $email_content, + $email_ics_str ); if ($mail !== true) { @@ -109,7 +111,7 @@ if (mysqli_num_rows($sql_queue) > 0) { // Update Message mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = 1 WHERE email_id = $email_id"); } - } + } } } @@ -129,6 +131,7 @@ if (mysqli_num_rows($sql_failed_queue) > 0) { $email_content = $row['email_content']; $email_queued_at = $row['email_queued_at']; $email_sent_at = $row['email_sent_at']; + $email_ics_str = $row['email_cal_str']; // Increment the attempts $email_attempts = intval($row['email_attempts']) + 1; @@ -153,7 +156,8 @@ if (mysqli_num_rows($sql_failed_queue) > 0) { $email_recipient, $email_recipient_name, $email_subject, - $email_content + $email_content, + $email_ics_str ); if ($mail !== true) { @@ -166,7 +170,7 @@ if (mysqli_num_rows($sql_failed_queue) > 0) { // Update Message mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id"); } - } + } } } diff --git a/dashboard.php b/dashboard.php index 38957989..27feae1c 100644 --- a/dashboard.php +++ b/dashboard.php @@ -34,7 +34,8 @@ $sql_years_select = mysqli_query( UNION DISTINCT SELECT YEAR(client_created_at) FROM clients UNION DISTINCT SELECT YEAR(user_created_at) FROM users ORDER BY all_years DESC -"); +" +); ?> @@ -50,27 +51,33 @@ $sql_years_select = mysqli_query( if (empty($year_select)) { $year_select = date('Y'); } - ?> - + ?> + - -
- > - -
- +
+ > + +
+ - = 2 && $config_module_enable_ticketing == 1) { ?> -
- > - -
- + = 2 && $config_module_enable_ticketing == 1) { ?> +
+ > + +
+ @@ -78,459 +85,489 @@ $sql_years_select = mysqli_query( if ($user_config_dashboard_financial_enable == 1) { -// Enforce accountant / admin role for the financial dashboard -if ($_SESSION['user_role'] != 3 && $_SESSION['user_role'] != 1) { - exit(''); -} + // Enforce accountant / admin role for the financial dashboard + if ($_SESSION['user_role'] != 3 && $_SESSION['user_role'] != 1) { + exit(''); + } -//Define var so it doesnt throw errors in logs -$largest_income_month = 0; + //Define var so it doesnt throw errors in logs + $largest_income_month = 0; -//Get Total income -$sql_total_payments_to_invoices = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS total_payments_to_invoices FROM payments WHERE YEAR(payment_date) = $year"); -$row = mysqli_fetch_array($sql_total_payments_to_invoices); -$total_payments_to_invoices = floatval($row['total_payments_to_invoices']); -//Do not grab transfer payment as these have a category_id of 0 -$sql_total_revenues = mysqli_query($mysqli, "SELECT SUM(revenue_amount) AS total_revenues FROM revenues WHERE YEAR(revenue_date) = $year AND revenue_category_id > 0"); -$row = mysqli_fetch_array($sql_total_revenues); -$total_revenues = floatval($row['total_revenues']); + //Get Total income + $sql_total_payments_to_invoices = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS total_payments_to_invoices FROM payments WHERE YEAR(payment_date) = $year"); + $row = mysqli_fetch_array($sql_total_payments_to_invoices); + $total_payments_to_invoices = floatval($row['total_payments_to_invoices']); + //Do not grab transfer payment as these have a category_id of 0 + $sql_total_revenues = mysqli_query($mysqli, "SELECT SUM(revenue_amount) AS total_revenues FROM revenues WHERE YEAR(revenue_date) = $year AND revenue_category_id > 0"); + $row = mysqli_fetch_array($sql_total_revenues); + $total_revenues = floatval($row['total_revenues']); -$total_income = $total_payments_to_invoices + $total_revenues; + $total_income = $total_payments_to_invoices + $total_revenues; -//Get Total expenses and do not grab transfer expenses as these have a vendor of 0 -$sql_total_expenses = mysqli_query($mysqli, "SELECT SUM(expense_amount) AS total_expenses FROM expenses WHERE expense_vendor_id > 0 AND YEAR(expense_date) = $year"); -$row = mysqli_fetch_array($sql_total_expenses); -$total_expenses = floatval($row['total_expenses']); + //Get Total expenses and do not grab transfer expenses as these have a vendor of 0 + $sql_total_expenses = mysqli_query($mysqli, "SELECT SUM(expense_amount) AS total_expenses FROM expenses WHERE expense_vendor_id > 0 AND YEAR(expense_date) = $year"); + $row = mysqli_fetch_array($sql_total_expenses); + $total_expenses = floatval($row['total_expenses']); -//Total up all the Invoices that are not draft or cancelled -$sql_invoice_totals = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS invoice_totals FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Cancelled' AND YEAR(invoice_date) = $year"); -$row = mysqli_fetch_array($sql_invoice_totals); -$invoice_totals = floatval($row['invoice_totals']); + //Total up all the Invoices that are not draft or cancelled + $sql_invoice_totals = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS invoice_totals FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Cancelled' AND YEAR(invoice_date) = $year"); + $row = mysqli_fetch_array($sql_invoice_totals); + $invoice_totals = floatval($row['invoice_totals']); -//Quaeries from Receivables -$sql_total_payments_to_invoices_all_years = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS total_payments_to_invoices_all_years FROM payments"); -$row = mysqli_fetch_array($sql_total_payments_to_invoices_all_years); -$total_payments_to_invoices_all_years = floatval($row['total_payments_to_invoices_all_years']); + //Quaeries from Receivables + $sql_total_payments_to_invoices_all_years = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS total_payments_to_invoices_all_years FROM payments"); + $row = mysqli_fetch_array($sql_total_payments_to_invoices_all_years); + $total_payments_to_invoices_all_years = floatval($row['total_payments_to_invoices_all_years']); -$sql_invoice_totals_all_years = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS invoice_totals_all_years FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Cancelled'"); -$row = mysqli_fetch_array($sql_invoice_totals_all_years); -$invoice_totals_all_years = floatval($row['invoice_totals_all_years']); + $sql_invoice_totals_all_years = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS invoice_totals_all_years FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Cancelled'"); + $row = mysqli_fetch_array($sql_invoice_totals_all_years); + $invoice_totals_all_years = floatval($row['invoice_totals_all_years']); -$receivables = $invoice_totals_all_years - $total_payments_to_invoices_all_years; + $receivables = $invoice_totals_all_years - $total_payments_to_invoices_all_years; -$profit = $total_income - $total_expenses; + $profit = $total_income - $total_expenses; -$sql_accounts = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC"); + $sql_accounts = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC"); -$sql_latest_invoice_payments = mysqli_query( - $mysqli, - "SELECT * FROM payments, invoices, clients + $sql_latest_invoice_payments = mysqli_query( + $mysqli, + "SELECT * FROM payments, invoices, clients WHERE payment_invoice_id = invoice_id AND invoice_client_id = client_id ORDER BY payment_id DESC LIMIT 5" -); + ); -$sql_latest_expenses = mysqli_query( - $mysqli, - "SELECT * FROM expenses, vendors, categories + $sql_latest_expenses = mysqli_query( + $mysqli, + "SELECT * FROM expenses, vendors, categories WHERE expense_vendor_id = vendor_id AND expense_category_id = category_id ORDER BY expense_id DESC LIMIT 5" -); + ); -//Get Yearly Recurring Income Total -$sql_recurring_yearly_total = mysqli_query($mysqli, "SELECT SUM(recurring_amount) AS recurring_yearly_total FROM recurring WHERE recurring_status = 1 AND recurring_frequency = 'year' AND YEAR(recurring_created_at) <= $year"); -$row = mysqli_fetch_array($sql_recurring_yearly_total); -$recurring_yearly_total = floatval($row['recurring_yearly_total']); + //Get Yearly Recurring Income Total + $sql_recurring_yearly_total = mysqli_query($mysqli, "SELECT SUM(recurring_amount) AS recurring_yearly_total FROM recurring WHERE recurring_status = 1 AND recurring_frequency = 'year' AND YEAR(recurring_created_at) <= $year"); + $row = mysqli_fetch_array($sql_recurring_yearly_total); + $recurring_yearly_total = floatval($row['recurring_yearly_total']); -//Get Monthly Recurring Income Total -$sql_recurring_monthly_total = mysqli_query($mysqli, "SELECT SUM(recurring_amount) AS recurring_monthly_total FROM recurring WHERE recurring_status = 1 AND recurring_frequency = 'month' AND YEAR(recurring_created_at) <= $year"); -$row = mysqli_fetch_array($sql_recurring_monthly_total); -$recurring_monthly_total = floatval($row['recurring_monthly_total']) + ($recurring_yearly_total / 12); + //Get Monthly Recurring Income Total + $sql_recurring_monthly_total = mysqli_query($mysqli, "SELECT SUM(recurring_amount) AS recurring_monthly_total FROM recurring WHERE recurring_status = 1 AND recurring_frequency = 'month' AND YEAR(recurring_created_at) <= $year"); + $row = mysqli_fetch_array($sql_recurring_monthly_total); + $recurring_monthly_total = floatval($row['recurring_monthly_total']) + ($recurring_yearly_total / 12); -//Get Yearly Recurring Expenses Total -$sql_recurring_expense_yearly_total = mysqli_query($mysqli, "SELECT SUM(recurring_expense_amount) AS recurring_expense_yearly_total FROM recurring_expenses WHERE recurring_expense_status = 1 AND recurring_expense_frequency = 2 AND YEAR(recurring_expense_created_at) <= $year"); -$row = mysqli_fetch_array($sql_recurring_expense_yearly_total); -$recurring_expense_yearly_total = floatval($row['recurring_expense_yearly_total']); + //Get Yearly Recurring Expenses Total + $sql_recurring_expense_yearly_total = mysqli_query($mysqli, "SELECT SUM(recurring_expense_amount) AS recurring_expense_yearly_total FROM recurring_expenses WHERE recurring_expense_status = 1 AND recurring_expense_frequency = 2 AND YEAR(recurring_expense_created_at) <= $year"); + $row = mysqli_fetch_array($sql_recurring_expense_yearly_total); + $recurring_expense_yearly_total = floatval($row['recurring_expense_yearly_total']); -//Get Monthly Recurring Expenses Total -$sql_recurring_expense_monthly_total = mysqli_query($mysqli, "SELECT SUM(recurring_expense_amount) AS recurring_expense_monthly_total FROM recurring_expenses WHERE recurring_expense_status = 1 AND recurring_expense_frequency = 1 AND YEAR(recurring_expense_created_at) <= $year"); -$row = mysqli_fetch_array($sql_recurring_expense_monthly_total); -$recurring_expense_monthly_total = floatval($row['recurring_expense_monthly_total']) + ($recurring_expense_yearly_total / 12); + //Get Monthly Recurring Expenses Total + $sql_recurring_expense_monthly_total = mysqli_query($mysqli, "SELECT SUM(recurring_expense_amount) AS recurring_expense_monthly_total FROM recurring_expenses WHERE recurring_expense_status = 1 AND recurring_expense_frequency = 1 AND YEAR(recurring_expense_created_at) <= $year"); + $row = mysqli_fetch_array($sql_recurring_expense_monthly_total); + $recurring_expense_monthly_total = floatval($row['recurring_expense_monthly_total']) + ($recurring_expense_yearly_total / 12); -//Get Total Miles Driven -$sql_miles_driven = mysqli_query($mysqli, "SELECT SUM(trip_miles) AS total_miles FROM trips WHERE YEAR(trip_date) = $year"); -$row = mysqli_fetch_array($sql_miles_driven); -$total_miles = floatval($row['total_miles']); + //Get Total Miles Driven + $sql_miles_driven = mysqli_query($mysqli, "SELECT SUM(trip_miles) AS total_miles FROM trips WHERE YEAR(trip_date) = $year"); + $row = mysqli_fetch_array($sql_miles_driven); + $total_miles = floatval($row['total_miles']); -//Get Total Recurring Invoices added -$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('recurring_id') AS recurring_invoices_added FROM recurring WHERE YEAR(recurring_created_at) = $year")); -$recurring_invoices_added = intval($row['recurring_invoices_added']); + if ($config_module_enable_ticketing && $config_module_enable_accounting) { + //Get Unbilled, closed tickets + $sql_unbilled_tickets = mysqli_query($mysqli, "SELECT COUNT('ticket_id') AS unbilled_tickets FROM tickets WHERE ticket_status = 'Closed' AND ticket_billable = 1 AND ticket_invoice_id = 0 AND YEAR(ticket_created_at) = $year"); + $row = mysqli_fetch_array($sql_unbilled_tickets); + $unbilled_tickets = intval($row['unbilled_tickets']); + } else { + //Get Total Recurring Invoices added + $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('recurring_id') AS recurring_invoices_added FROM recurring WHERE YEAR(recurring_created_at) = $year")); + $recurring_invoices_added = intval($row['recurring_invoices_added']); + } -//Get Total Clients added -$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('client_id') AS clients_added FROM clients WHERE YEAR(client_created_at) = $year AND client_archived_at IS NULL")); -$clients_added = intval($row['clients_added']); -//Get Total Vendors added -$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('vendor_id') AS vendors_added FROM vendors WHERE YEAR(vendor_created_at) = $year AND vendor_client_id = 0 AND vendor_template = 0 AND vendor_archived_at IS NULL")); -$vendors_added = intval($row['vendors_added']); + + + //Get Total Clients added + $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('client_id') AS clients_added FROM clients WHERE YEAR(client_created_at) = $year AND client_archived_at IS NULL")); + $clients_added = intval($row['clients_added']); + + //Get Total Vendors added + $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('vendor_id') AS vendors_added FROM vendors WHERE YEAR(vendor_created_at) = $year AND vendor_client_id = 0 AND vendor_template = 0 AND vendor_archived_at IS NULL")); + $vendors_added = intval($row['vendors_added']); ?> - -
-
- - -
-

-

Income

-
- Receivables: -
-
- -
-
-
- - -
- - -
-

-

Expenses

-
-
- -
-
-
- - -
- - -
-

-

Profit

-
-
- -
-
-
- - -
- - -
-

-

Monthly Recurring Income

-
-
- -
-
-
- - -
- - -
-

-

Monthly Recurring Expense

-
-
- -
-
-
- - -
- - -
-

-

Recurring Invoices Added

-
-
- -
-
-
- - -
- - -
-

-

New Clients

-
-
- -
-
-
- - -
- - -
-

-

New Vendors

-
-
- -
-
-
- - -
- - -
-

-

Miles Traveled

-
-
- -
-
-
- - -
-
-
-

Cash Flow

-
- - - - + +
+ + + + + + + + + + + + + + + + + + -
- + + + + + + + + + + + + + + + + +
+
+
+

Cash Flow

+
+ + + + +
+
+
+ +
-
-
-
-
-

Income by Category (Top 5)

-
- +
+
+
+

Income by Category (Top 5)

+
+ +
+
+
+
-
-
-
-
-
-
-
-

Expenses by Category (Top 5)

-
- +
+
+
+

Expenses by Category (Top 5)

+
+ +
+
+
+
-
-
-
-
-
-
-
-

Expenses by Vendor (Top 5)

-
- +
+
+
+

Expenses by Vendor (Top 5)

+
+ +
+
+
+
-
-
-
-
-
-
-
-

Account Balances

-
- +
+
+
+

Account Balances

+
+ +
-
-
- - - - - +
+
+ + + + + + + - - - - -
-
-
-
-
-
-
-

Latest Income

-
- + +
-
- - - - - - - - - - - - - - - - - - - -
DateCustomerInvoiceAmount
-
-
-
-
-
-
-

Latest Expenses

-
- +
+
+
+
+

Latest Income

+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + +
DateCustomerInvoiceAmount
-
- - - - - - - - - - - +
+
+
+

Latest Expenses

+
+ +
+
+
+
DateVendorCategoryAmount
+ + + + + + + + + + - - - - - - - - -
DateVendorCategoryAmount
-
-
-
-
-
-
-

Trip Flow

-
- - - - + ?> + + + + + + + + +
-
- +
+
+
+
+

Trip Flow

+
+ + + + +
+
+
+ +
-
-
+
@@ -540,280 +577,282 @@ $vendors_added = intval($row['vendors_added']); if ($user_config_dashboard_technical_enable == 1) { -// Get Total Clients added -$sql_clients = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('client_id') AS clients_added FROM clients + // Get Total Clients added + $sql_clients = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('client_id') AS clients_added FROM clients WHERE YEAR(client_created_at) = $year" -)); -$clients_added = $sql_clients['clients_added']; + )); + $clients_added = $sql_clients['clients_added']; -// Get Total contacts added -$sql_contacts = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('contact_id') AS contacts_added FROM contacts + // Get Total contacts added + $sql_contacts = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('contact_id') AS contacts_added FROM contacts WHERE YEAR(contact_created_at) = $year" -)); -$contacts_added = $sql_contacts['contacts_added']; + )); + $contacts_added = $sql_contacts['contacts_added']; -// Get Total assets added -$sql_assets = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('asset_id') AS assets_added FROM assets + // Get Total assets added + $sql_assets = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('asset_id') AS assets_added FROM assets WHERE YEAR(asset_created_at) = $year" -)); -$assets_added = $sql_assets['assets_added']; + )); + $assets_added = $sql_assets['assets_added']; -// Ticket count -$sql_tickets = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('ticket_id') AS active_tickets + // Ticket count + $sql_tickets = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('ticket_id') AS active_tickets FROM tickets WHERE ticket_status != 'Closed'" -)); -$active_tickets = $sql_tickets['active_tickets']; + )); + $active_tickets = $sql_tickets['active_tickets']; -// Your Ticket count -$sql_your_tickets = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('ticket_id') AS your_tickets + // Your Ticket count + $sql_your_tickets = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('ticket_id') AS your_tickets FROM tickets WHERE ticket_status != 'Closed' AND ticket_assigned_to = $session_user_id" -)); -$your_tickets = $sql_your_tickets['your_tickets']; + )); + $your_tickets = $sql_your_tickets['your_tickets']; -// Expiring domains (but not ones that have already expired) -$sql_domains_expiring = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('domain_id') as expiring_domains + // Expiring domains (but not ones that have already expired) + $sql_domains_expiring = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('domain_id') as expiring_domains FROM domains WHERE domain_expire IS NOT NULL AND domain_expire > CURRENT_DATE AND domain_expire < CURRENT_DATE + INTERVAL 30 DAY AND domain_archived_at IS NULL" -)); -$expiring_domains = $sql_domains_expiring['expiring_domains']; + )); + $expiring_domains = $sql_domains_expiring['expiring_domains']; -// Expiring Certificates (but not ones that have already expired) -$sql_certs_expiring = mysqli_fetch_assoc(mysqli_query( - $mysqli, - "SELECT COUNT('certificate_id') as expiring_certs + // Expiring Certificates (but not ones that have already expired) + $sql_certs_expiring = mysqli_fetch_assoc(mysqli_query( + $mysqli, + "SELECT COUNT('certificate_id') as expiring_certs FROM certificates WHERE certificate_expire IS NOT NULL AND certificate_expire > CURRENT_DATE AND certificate_expire < CURRENT_DATE + INTERVAL 30 DAY AND certificate_archived_at IS NULL" -)); -$expiring_certificates = $sql_certs_expiring['expiring_certs']; + )); + $expiring_certificates = $sql_certs_expiring['expiring_certs']; -$sql_your_tickets = mysqli_query( - $mysqli, - "SELECT * FROM tickets + $sql_your_tickets = mysqli_query( + $mysqli, + "SELECT * FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN contacts ON ticket_contact_id = contact_id WHERE ticket_assigned_to = $session_user_id AND ticket_status != 'Closed' ORDER BY ticket_number DESC" -); + ); ?> - -
+ + - - -
-
-
-
-

Your Open Tickets

-
- + -
- - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+

Your Open Tickets

+
+ +
+
+
+
NumberSubjectClientContactPriorityStatusLast Response
+ + + + + + + + + + + + + Never

"; + } else { + $ticket_updated_at_display = "

Never

"; + } + } else { + $ticket_updated_at_display = "$ticket_updated_at_time_ago"; + } + $client_id = intval($row['ticket_client_id']); + $client_name = nullable_htmlentities($row['client_name']); + $contact_id = intval($row['ticket_contact_id']); + $contact_name = nullable_htmlentities($row['contact_name']); + if ($ticket_status == "Pending-Assignment") { + $ticket_status_color = "danger"; + } elseif ($ticket_status == "Assigned") { + $ticket_status_color = "primary"; + } elseif ($ticket_status == "In-Progress") { + $ticket_status_color = "success"; + } elseif ($ticket_status == "Closed") { + $ticket_status_color = "dark"; + } else { + $ticket_status_color = "secondary"; + } + + if ($ticket_priority == "High") { + $ticket_priority_color = "danger"; + } elseif ($ticket_priority == "Medium") { + $ticket_priority_color = "warning"; + } else { + $ticket_priority_color = "info"; + } + + if (empty($contact_name)) { + $contact_display = "-"; + } else { + $contact_display = "$contact_name"; + } + + ?> + + "> + + + + + + + + + + Never

"; - } else { - $ticket_updated_at_display = "

Never

"; } - } else { - $ticket_updated_at_display = "$ticket_updated_at_time_ago"; - } - $client_id = intval($row['ticket_client_id']); - $client_name = nullable_htmlentities($row['client_name']); - $contact_id = intval($row['ticket_contact_id']); - $contact_name = nullable_htmlentities($row['contact_name']); - if ($ticket_status == "Pending-Assignment") { - $ticket_status_color = "danger"; - } elseif ($ticket_status == "Assigned") { - $ticket_status_color = "primary"; - } elseif ($ticket_status == "In-Progress") { - $ticket_status_color = "success"; - } elseif ($ticket_status == "Closed") { - $ticket_status_color = "dark"; - } else{ - $ticket_status_color = "secondary"; - } - if ($ticket_priority == "High") { - $ticket_priority_color = "danger"; - } elseif ($ticket_priority == "Medium") { - $ticket_priority_color = "warning"; - } else{ - $ticket_priority_color = "info"; - } + ?> - if (empty($contact_name)) { - $contact_display = "-"; - } else { - $contact_display = "$contact_name"; - } + +
NumberSubjectClientContactPriorityStatusLast Response
+ + + +
+
- ?> +
- "> - - - - - - - - - - - - - - - - -
-
- -
- - + @@ -822,7 +861,7 @@ $sql_your_tickets = mysqli_query( +?> + \ No newline at end of file diff --git a/database_updates.php b/database_updates.php index 7e5c9e1e..f6841515 100644 --- a/database_updates.php +++ b/database_updates.php @@ -1559,6 +1559,8 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.0.2'"); } + + if (CURRENT_DATABASE_VERSION == '1.0.2') { //Insert queries here required to update to DB version 1.0.3 mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_stripe_expense_vendor` INT(11) NOT NULL DEFAULT 0 AFTER `config_stripe_account`"); @@ -1586,10 +1588,21 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { // Please add this same comment block to the bottom of this file, and update the version number. // Uncomment Below Lines, to add additional database updates // - // if (CURRENT_DATABASE_VERSION == '1.0.4') { - // // Insert queries here required to update to DB version 1.0.5 + + if (CURRENT_DATABASE_VERSION == '1.0.4') { + //Insert queries here required to update to DB version 1.0.5 + mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_schedule` DATETIME DEFAULT NULL AFTER `ticket_billable`"); + mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_onsite` TINYINT(1) NOT NULL DEFAULT 0 AFTER `ticket_schedule`"); + mysqli_query($mysqli, "ALTER TABLE `email_queue` ADD `email_cal_str` VARCHAR(1024) DEFAULT NULL AFTER `email_content`"); + + // Then, update the database to the next sequential version + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.0.5'"); + } + + // if (CURRENT_DATABASE_VERSION == '1.0.5') { + // // Insert queries here required to update to DB version 1.0.6 // // Then, update the database to the next sequential version - // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.0.5'"); + // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.0.6'"); // } } else { diff --git a/database_version.php b/database_version.php index 6fa7aaba..ec6dcac8 100644 --- a/database_version.php +++ b/database_version.php @@ -5,5 +5,5 @@ * It is used in conjunction with database_updates.php */ -DEFINE("LATEST_DATABASE_VERSION", "1.0.4"); +DEFINE("LATEST_DATABASE_VERSION", "1.0.5"); diff --git a/db.sql b/db.sql index 4dfd81fc..43f948b6 100644 --- a/db.sql +++ b/db.sql @@ -1573,6 +1573,7 @@ CREATE TABLE `tickets` ( `ticket_priority` varchar(200) DEFAULT NULL, `ticket_status` varchar(200) NOT NULL, `ticket_billable` tinyint(1) NOT NULL DEFAULT 0, + `ticket_schedule` DATETIME DEFAULT NULL, `ticket_vendor_ticket_number` varchar(255) DEFAULT NULL, `ticket_feedback` varchar(200) DEFAULT NULL, `ticket_created_at` datetime NOT NULL DEFAULT current_timestamp(), diff --git a/functions.php b/functions.php index ca3ddd24..d42d0c07 100644 --- a/functions.php +++ b/functions.php @@ -15,7 +15,8 @@ use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; // Function to generate both crypto & URL safe random strings -function randomString($length = 16) { +function randomString($length = 16) +{ // Generate some cryptographically safe random bytes // Generate a little more than requested as we'll lose some later converting $random_bytes = random_bytes($length + 5); @@ -32,12 +33,13 @@ function randomString($length = 16) { } // Older keygen function - only used for TOTP currently -function key32gen() { +function key32gen() +{ $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $chars .= "234567"; while (1) { $key = ''; - srand((double) microtime() * 1000000); + srand((float) microtime() * 1000000); for ($i = 0; $i < 32; $i++) { $key .= substr($chars, (rand() % (strlen($chars))), 1); } @@ -46,11 +48,13 @@ function key32gen() { return $key; } -function nullable_htmlentities($unsanitizedInput) { +function nullable_htmlentities($unsanitizedInput) +{ return htmlentities($unsanitizedInput ?? ''); } -function initials($str) { +function initials($str) +{ if (!empty($str)) { $ret = ''; foreach (explode(' ', $str) as $word) @@ -60,7 +64,8 @@ function initials($str) { } } -function removeDirectory($path) { +function removeDirectory($path) +{ if (!file_exists($path)) { return; } @@ -72,11 +77,13 @@ function removeDirectory($path) { rmdir($path); } -function getUserAgent() { +function getUserAgent() +{ return $_SERVER['HTTP_USER_AGENT']; } -function getIP() { +function getIP() +{ if (defined("CONST_GET_IP_METHOD")) { if (CONST_GET_IP_METHOD == "HTTP_X_FORWARDED_FOR") { $ip = getenv('HTTP_X_FORWARDED_FOR'); @@ -94,7 +101,8 @@ function getIP() { return $ip; } -function getWebBrowser($user_browser) { +function getWebBrowser($user_browser) +{ $browser = "Unknown Browser"; $browser_array = array( '/msie/i' => " Internet Explorer", @@ -112,7 +120,8 @@ function getWebBrowser($user_browser) { return $browser; } -function getOS($user_os) { +function getOS($user_os) +{ $os_platform = "Unknown OS"; $os_array = array( '/windows nt 10/i' => " Windows 10", @@ -139,7 +148,8 @@ function getOS($user_os) { return $os_platform; } -function getDevice() { +function getDevice() +{ $tablet_browser = 0; $mobile_browser = 0; if (preg_match('/(tablet|ipad|playbook)|(android(?!.*(mobi|opera mini)))/i', strtolower($_SERVER['HTTP_USER_AGENT']))) { @@ -153,22 +163,23 @@ function getDevice() { } $mobile_ua = strtolower(substr(getUserAgent(), 0, 4)); $mobile_agents = array( - 'w3c ','acs-','alav','alca','amoi','audi','avan','benq','bird','blac', - 'blaz','brew','cell','cldc','cmd-','dang','doco','eric','hipt','inno', - 'ipaq','java','jigs','kddi','keji','leno','lg-c','lg-d','lg-g','lge-', - 'maui','maxo','midp','mits','mmef','mobi','mot-','moto','mwbp','nec-', - 'newt','noki','palm','pana','pant','phil','play','port','prox', - 'qwap','sage','sams','sany','sch-','sec-','send','seri','sgh-','shar', - 'sie-','siem','smal','smar','sony','sph-','symb','t-mo','teli','tim-', - 'tosh','tsm-','upg1','upsi','vk-v','voda','wap-','wapa','wapi','wapp', - 'wapr','webc','winw','winw','xda ','xda-'); + 'w3c ', 'acs-', 'alav', 'alca', 'amoi', 'audi', 'avan', 'benq', 'bird', 'blac', + 'blaz', 'brew', 'cell', 'cldc', 'cmd-', 'dang', 'doco', 'eric', 'hipt', 'inno', + 'ipaq', 'java', 'jigs', 'kddi', 'keji', 'leno', 'lg-c', 'lg-d', 'lg-g', 'lge-', + 'maui', 'maxo', 'midp', 'mits', 'mmef', 'mobi', 'mot-', 'moto', 'mwbp', 'nec-', + 'newt', 'noki', 'palm', 'pana', 'pant', 'phil', 'play', 'port', 'prox', + 'qwap', 'sage', 'sams', 'sany', 'sch-', 'sec-', 'send', 'seri', 'sgh-', 'shar', + 'sie-', 'siem', 'smal', 'smar', 'sony', 'sph-', 'symb', 't-mo', 'teli', 'tim-', + 'tosh', 'tsm-', 'upg1', 'upsi', 'vk-v', 'voda', 'wap-', 'wapa', 'wapi', 'wapp', + 'wapr', 'webc', 'winw', 'winw', 'xda ', 'xda-' + ); if (in_array($mobile_ua, $mobile_agents)) { $mobile_browser++; } if (strpos(strtolower(getUserAgent()), 'opera mini') > 0) { $mobile_browser++; //Check for tablets on Opera Mini alternative headers - $stock_ua = strtolower(isset($_SERVER['HTTP_X_OPERAMINI_PHONE_UA'])?$_SERVER['HTTP_X_OPERAMINI_PHONE_UA']:(isset($_SERVER['HTTP_DEVICE_STOCK_UA'])?$_SERVER['HTTP_DEVICE_STOCK_UA']:'')); + $stock_ua = strtolower(isset($_SERVER['HTTP_X_OPERAMINI_PHONE_UA']) ? $_SERVER['HTTP_X_OPERAMINI_PHONE_UA'] : (isset($_SERVER['HTTP_DEVICE_STOCK_UA']) ? $_SERVER['HTTP_DEVICE_STOCK_UA'] : '')); if (preg_match('/(tablet|ipad|playbook)|(android(?!.*mobile))/i', $stock_ua)) { $tablet_browser++; } @@ -176,59 +187,58 @@ function getDevice() { if ($tablet_browser > 0) { //do something for tablet devices return 'Tablet'; - } - else if ($mobile_browser > 0) { + } else if ($mobile_browser > 0) { //do something for mobile devices return 'Mobile'; - } - else { + } else { //do something for everything else return 'Computer'; } } -function truncate($text, $chars) { +function truncate($text, $chars) +{ if (strlen($text) <= $chars) { return $text; } - $text = $text." "; + $text = $text . " "; $text = substr($text, 0, $chars); $lastSpacePos = strrpos($text, ' '); if ($lastSpacePos !== false) { $text = substr($text, 0, $lastSpacePos); } - return $text."..."; + return $text . "..."; } -function formatPhoneNumber($phoneNumber) { - $phoneNumber = $phoneNumber ? preg_replace('/[^0-9]/', '', $phoneNumber): ""; +function formatPhoneNumber($phoneNumber) +{ + $phoneNumber = $phoneNumber ? preg_replace('/[^0-9]/', '', $phoneNumber) : ""; if (strlen($phoneNumber) > 10) { - $countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10); + $countryCode = substr($phoneNumber, 0, strlen($phoneNumber) - 10); $areaCode = substr($phoneNumber, -10, 3); $nextThree = substr($phoneNumber, -7, 3); $lastFour = substr($phoneNumber, -4, 4); - $phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour; - } - else if (strlen($phoneNumber) == 10) { + $phoneNumber = '+' . $countryCode . ' (' . $areaCode . ') ' . $nextThree . '-' . $lastFour; + } else if (strlen($phoneNumber) == 10) { $areaCode = substr($phoneNumber, 0, 3); $nextThree = substr($phoneNumber, 3, 3); $lastFour = substr($phoneNumber, 6, 4); - $phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour; - } - else if (strlen($phoneNumber) == 7) { + $phoneNumber = '(' . $areaCode . ') ' . $nextThree . '-' . $lastFour; + } else if (strlen($phoneNumber) == 7) { $nextThree = substr($phoneNumber, 0, 3); $lastFour = substr($phoneNumber, 3, 4); - $phoneNumber = $nextThree.'-'.$lastFour; + $phoneNumber = $nextThree . '-' . $lastFour; } return $phoneNumber; } -function mkdirMissing($dir) { +function mkdirMissing($dir) +{ if (!is_dir($dir)) { mkdir($dir); } @@ -236,7 +246,8 @@ function mkdirMissing($dir) { // Called during initial setup // Encrypts the master key with the user's password -function setupFirstUserSpecificKey($user_password, $site_encryption_master_key) { +function setupFirstUserSpecificKey($user_password, $site_encryption_master_key) +{ $iv = randomString(); $salt = randomString(); @@ -254,7 +265,8 @@ function setupFirstUserSpecificKey($user_password, $site_encryption_master_key) * New Users: Requires the admin setting up their account have a Specific/Session key configured * Password Changes: Will use the current info in the session. */ -function encryptUserSpecificKey($user_password) { +function encryptUserSpecificKey($user_password) +{ $iv = randomString(); $salt = randomString(); @@ -273,7 +285,6 @@ function encryptUserSpecificKey($user_password) { $ciphertext = openssl_encrypt($site_encryption_master_key, 'aes-128-cbc', $user_password_kdhash, 0, $iv); return $salt . $iv . $ciphertext; - } // Given a ciphertext (incl. IV) and the user's password, returns the site master key @@ -313,8 +324,8 @@ function generateUserSessionKey($site_encryption_master_key) include 'config.php'; if ($config_https_only) { - setcookie("user_encryption_session_key", "$user_encryption_session_key", ['path' => '/','secure' => true,'httponly' => true,'samesite' => 'None']); - } else{ + setcookie("user_encryption_session_key", "$user_encryption_session_key", ['path' => '/', 'secure' => true, 'httponly' => true, 'samesite' => 'None']); + } else { setcookie("user_encryption_session_key", $user_encryption_session_key, 0, "/"); $_SESSION['alert_message'] = "Unencrypted connection flag set: Using non-secure cookies."; } @@ -338,7 +349,6 @@ function decryptLoginEntry($login_password_ciphertext) // Decrypt the login password using the master key return openssl_decrypt($login_ciphertext, 'aes-128-cbc', $site_encryption_master_key, 0, $login_iv); - } // Encrypts a website/asset login password @@ -361,7 +371,8 @@ function encryptLoginEntry($login_password_cleartext) } // Get domain expiration date -function getDomainExpirationDate($name) { +function getDomainExpirationDate($name) +{ // Only run if we think the domain is valid if (!filter_var($name, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) { @@ -376,11 +387,9 @@ function getDomainExpirationDate($name) { if ($response) { if (is_array($response['expiration_date'])) { $expiry = new DateTime($response['expiration_date'][1]); - } - elseif (isset($response['expiration_date'])) { + } elseif (isset($response['expiration_date'])) { $expiry = new DateTime($response['expiration_date']); - } - else { + } else { return "NULL"; } @@ -392,7 +401,8 @@ function getDomainExpirationDate($name) { } // Get domain general info (whois + NS/A/MX records) -function getDomainRecords($name) { +function getDomainRecords($name) +{ $records = array(); @@ -417,7 +427,8 @@ function getDomainRecords($name) { // Used to automatically attempt to get SSL certificates as part of adding domains // The logic for the fetch (sync) button on the client_certificates page is in ajax.php, and allows ports other than 443 -function getSSL($name) { +function getSSL($name) +{ $certificate = array(); $certificate['success'] = false; @@ -452,7 +463,8 @@ function getSSL($name) { return $certificate; } -function strtoAZaz09($string) { +function strtoAZaz09($string) +{ // Gets rid of non-alphanumerics return preg_replace('/[^A-Za-z0-9_-]/', '', $string); @@ -460,11 +472,11 @@ function strtoAZaz09($string) { // Cross-Site Request Forgery check for sensitive functions // Validates the CSRF token provided matches the one in the users session -function validateCSRFToken($token) { +function validateCSRFToken($token) +{ if (hash_equals($token, $_SESSION['csrf_token'])) { return true; - } - else{ + } else { $_SESSION['alert_type'] = "warning"; $_SESSION['alert_message'] = "CSRF token verification failed. Try again, or log out to refresh your token."; header("Location: index.php"); @@ -479,7 +491,8 @@ function validateCSRFToken($token) { * Accountant - 1 */ -function validateAdminRole() { +function validateAdminRole() +{ if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] != 3) { $_SESSION['alert_type'] = "danger"; $_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED; @@ -489,7 +502,8 @@ function validateAdminRole() { } // Validates a user is a tech (or admin). Stops page load and attempts to direct away from the page if not (i.e. user is an accountant) -function validateTechRole() { +function validateTechRole() +{ if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] == 1) { $_SESSION['alert_type'] = "danger"; $_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED; @@ -499,7 +513,8 @@ function validateTechRole() { } // Validates a user is an accountant (or admin). Stops page load and attempts to direct away from the page if not (i.e. user is a tech) -function validateAccountantRole() { +function validateAccountantRole() +{ if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] == 2) { $_SESSION['alert_type'] = "danger"; $_SESSION['alert_message'] = WORDING_ROLECHECK_FAILED; @@ -509,7 +524,8 @@ function validateAccountantRole() { } // Send a single email to a single recipient -function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_password, $config_smtp_encryption, $config_smtp_port, $from_email, $from_name, $to_email, $to_name, $subject, $body) { +function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_password, $config_smtp_encryption, $config_smtp_port, $from_email, $from_name, $to_email, $to_name, $subject, $body, $ics_str) +{ $mail = new PHPMailer(true); @@ -539,23 +555,72 @@ function sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_ // Content $mail->isHTML(true); // Set email format to HTML $mail->Subject = "$subject"; // Subject - $mail->Body = "$body"; // Content + $mail->Body = " + + + + + + + + "; // Content // Attachments - todo //$mail->addAttachment('/var/tmp/file.tar.gz'); // Add attachments //$mail->addAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name + if (!empty($ics_str)) { + $mail->addStringAttachment($ics_str, 'Scheduled_ticket.ics', 'base64', 'text/calendar'); + } + // Send $mail->send(); // Return true if this was successful return true; - } - - catch(Exception $e) { + } catch (Exception $e) { // If we couldn't send the message return the error, so we can log it in the database (truncated) error_log("ITFlow - Failed to send email: " . $mail->ErrorInfo); - return substr("Mailer Error: $mail->ErrorInfo", 0, 150)."..."; + return substr("Mailer Error: $mail->ErrorInfo", 0, 150) . "..."; } } @@ -609,7 +674,7 @@ function getInvoiceBadgeColor($invoice_status) $invoice_badge_color = "success"; } elseif ($invoice_status == "Cancelled") { $invoice_badge_color = "danger"; - } else{ + } else { $invoice_badge_color = "secondary"; } @@ -617,7 +682,8 @@ function getInvoiceBadgeColor($invoice_status) } // Pass $_FILE['file'] to check an uploaded file before saving it -function checkFileUpload($file, $allowed_extensions) { +function checkFileUpload($file, $allowed_extensions) +{ // Variables $name = $file['name']; $tmp = $file['tmp_name']; @@ -656,7 +722,8 @@ function checkFileUpload($file, $allowed_extensions) { return $secureFilename; } -function sanitizeInput($input) { +function sanitizeInput($input) +{ global $mysqli; // Remove HTML and PHP tags @@ -672,14 +739,16 @@ function sanitizeInput($input) { return $input; } -function sanitizeForEmail($data) { - $sanitized = htmlspecialchars($data); - $sanitized = strip_tags($sanitized); - $sanitized = trim($sanitized); - return $sanitized; +function sanitizeForEmail($data) +{ + $sanitized = htmlspecialchars($data); + $sanitized = strip_tags($sanitized); + $sanitized = trim($sanitized); + return $sanitized; } -function timeAgo($datetime) { +function timeAgo($datetime) +{ $time = strtotime($datetime); $difference = $time - time(); // Changed to handle future dates @@ -711,11 +780,13 @@ function timeAgo($datetime) { } // Function to remove Emojis in messages, this seems to break the mail queue -function removeEmoji($text){ +function removeEmoji($text) +{ return preg_replace('/\x{1F3F4}\x{E0067}\x{E0062}(?:\x{E0077}\x{E006C}\x{E0073}|\x{E0073}\x{E0063}\x{E0074}|\x{E0065}\x{E006E}\x{E0067})\x{E007F}|(?:\x{1F9D1}\x{1F3FF}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FF}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FF}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FE}]|(?:\x{1F9D1}\x{1F3FE}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FE}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FE}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FD}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FD}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FD}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FC}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FC}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FC}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FB}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FB}\x{200D}\x{1FAF2})[\x{1F3FC}-\x{1F3FF}]|\x{1F468}(?:\x{1F3FB}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{1F91D}\x{200D}\x{1F468}[\x{1F3FC}-\x{1F3FF}]|[\x{2695}\x{2696}\x{2708}]\x{FE0F}|[\x{2695}\x{2696}\x{2708}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]))?|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F468}|[\x{1F468}\x{1F469}]\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FE}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FE}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FD}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FC}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])\x{FE0F}|\x{200D}(?:[\x{1F468}\x{1F469}]\x{200D}[\x{1F466}\x{1F467}]|[\x{1F466}\x{1F467}])|\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{200D}[\x{2695}\x{2696}\x{2708}])?|(?:\x{1F469}(?:\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])))|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]\x{200D}\x{1F91D}\x{200D}\x{1F9D1})[\x{1F3FB}-\x{1F3FF}]|\x{1F469}\x{200D}\x{1F469}\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F469}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F9D1}(?:\x{200D}(?:\x{1F91D}\x{200D}\x{1F9D1}|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}[\x{1F466}\x{1F467}]|\x{1F469}\x{200D}\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|(?:\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}])\x{FE0F}|\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F3F3}\x{FE0F}?\x{200D}\x{1F308}|\x{1F469}\x{200D}\x{1F467}|\x{1F469}\x{200D}\x{1F466}|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F635}\x{200D}\x{1F4AB}|\x{1F62E}\x{200D}\x{1F4A8}|\x{1F415}\x{200D}\x{1F9BA}|\x{1FAF1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F9D1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F469}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|\x{1F1FD}\x{1F1F0}|\x{1F1F6}\x{1F1E6}|\x{1F1F4}\x{1F1F2}|\x{1F408}\x{200D}\x{2B1B}|\x{2764}(?:\x{FE0F}\x{200D}[\x{1F525}\x{1FA79}]|\x{200D}[\x{1F525}\x{1FA79}])|\x{1F441}\x{FE0F}?|\x{1F3F3}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|\x{1F1FF}[\x{1F1E6}\x{1F1F2}\x{1F1FC}]|\x{1F1FE}[\x{1F1EA}\x{1F1F9}]|\x{1F1FC}[\x{1F1EB}\x{1F1F8}]|\x{1F1FB}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1EE}\x{1F1F3}\x{1F1FA}]|\x{1F1FA}[\x{1F1E6}\x{1F1EC}\x{1F1F2}\x{1F1F3}\x{1F1F8}\x{1F1FE}\x{1F1FF}]|\x{1F1F9}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1ED}\x{1F1EF}-\x{1F1F4}\x{1F1F7}\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FF}]|\x{1F1F8}[\x{1F1E6}-\x{1F1EA}\x{1F1EC}-\x{1F1F4}\x{1F1F7}-\x{1F1F9}\x{1F1FB}\x{1F1FD}-\x{1F1FF}]|\x{1F1F7}[\x{1F1EA}\x{1F1F4}\x{1F1F8}\x{1F1FA}\x{1F1FC}]|\x{1F1F5}[\x{1F1E6}\x{1F1EA}-\x{1F1ED}\x{1F1F0}-\x{1F1F3}\x{1F1F7}-\x{1F1F9}\x{1F1FC}\x{1F1FE}]|\x{1F1F3}[\x{1F1E6}\x{1F1E8}\x{1F1EA}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F4}\x{1F1F5}\x{1F1F7}\x{1F1FA}\x{1F1FF}]|\x{1F1F2}[\x{1F1E6}\x{1F1E8}-\x{1F1ED}\x{1F1F0}-\x{1F1FF}]|\x{1F1F1}[\x{1F1E6}-\x{1F1E8}\x{1F1EE}\x{1F1F0}\x{1F1F7}-\x{1F1FB}\x{1F1FE}]|\x{1F1F0}[\x{1F1EA}\x{1F1EC}-\x{1F1EE}\x{1F1F2}\x{1F1F3}\x{1F1F5}\x{1F1F7}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1EF}[\x{1F1EA}\x{1F1F2}\x{1F1F4}\x{1F1F5}]|\x{1F1EE}[\x{1F1E8}-\x{1F1EA}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}]|\x{1F1ED}[\x{1F1F0}\x{1F1F2}\x{1F1F3}\x{1F1F7}\x{1F1F9}\x{1F1FA}]|\x{1F1EC}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EE}\x{1F1F1}-\x{1F1F3}\x{1F1F5}-\x{1F1FA}\x{1F1FC}\x{1F1FE}]|\x{1F1EB}[\x{1F1EE}-\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1F7}]|\x{1F1EA}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1ED}\x{1F1F7}-\x{1F1FA}]|\x{1F1E9}[\x{1F1EA}\x{1F1EC}\x{1F1EF}\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1FF}]|\x{1F1E8}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1EE}\x{1F1F0}-\x{1F1F5}\x{1F1F7}\x{1F1FA}-\x{1F1FF}]|\x{1F1E7}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EF}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1E6}[\x{1F1E8}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F2}\x{1F1F4}\x{1F1F6}-\x{1F1FA}\x{1F1FC}\x{1F1FD}\x{1F1FF}]|[#\*0-9]\x{FE0F}?\x{20E3}|\x{1F93C}[\x{1F3FB}-\x{1F3FF}]|\x{2764}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]?|\x{1F3F4}|[\x{270A}\x{270B}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F57A}\x{1F595}\x{1F596}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}][\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270C}\x{270D}\x{1F574}\x{1F590}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270A}-\x{270D}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F408}\x{1F415}\x{1F43B}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F574}\x{1F57A}\x{1F590}\x{1F595}\x{1F596}\x{1F62E}\x{1F635}\x{1F636}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F93C}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}]|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}]|[\x{23E9}-\x{23EC}\x{23F0}\x{23F3}\x{25FD}\x{2693}\x{26A1}\x{26AB}\x{26C5}\x{26CE}\x{26D4}\x{26EA}\x{26FD}\x{2705}\x{2728}\x{274C}\x{274E}\x{2753}-\x{2755}\x{2757}\x{2795}-\x{2797}\x{27B0}\x{27BF}\x{2B50}\x{1F0CF}\x{1F18E}\x{1F191}-\x{1F19A}\x{1F201}\x{1F21A}\x{1F22F}\x{1F232}-\x{1F236}\x{1F238}-\x{1F23A}\x{1F250}\x{1F251}\x{1F300}-\x{1F320}\x{1F32D}-\x{1F335}\x{1F337}-\x{1F37C}\x{1F37E}-\x{1F384}\x{1F386}-\x{1F393}\x{1F3A0}-\x{1F3C1}\x{1F3C5}\x{1F3C6}\x{1F3C8}\x{1F3C9}\x{1F3CF}-\x{1F3D3}\x{1F3E0}-\x{1F3F0}\x{1F3F8}-\x{1F407}\x{1F409}-\x{1F414}\x{1F416}-\x{1F43A}\x{1F43C}-\x{1F43E}\x{1F440}\x{1F444}\x{1F445}\x{1F451}-\x{1F465}\x{1F46A}\x{1F479}-\x{1F47B}\x{1F47D}-\x{1F480}\x{1F484}\x{1F488}-\x{1F48E}\x{1F490}\x{1F492}-\x{1F4A9}\x{1F4AB}-\x{1F4FC}\x{1F4FF}-\x{1F53D}\x{1F54B}-\x{1F54E}\x{1F550}-\x{1F567}\x{1F5A4}\x{1F5FB}-\x{1F62D}\x{1F62F}-\x{1F634}\x{1F637}-\x{1F644}\x{1F648}-\x{1F64A}\x{1F680}-\x{1F6A2}\x{1F6A4}-\x{1F6B3}\x{1F6B7}-\x{1F6BF}\x{1F6C1}-\x{1F6C5}\x{1F6D0}-\x{1F6D2}\x{1F6D5}-\x{1F6D7}\x{1F6DD}-\x{1F6DF}\x{1F6EB}\x{1F6EC}\x{1F6F4}-\x{1F6FC}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F90D}\x{1F90E}\x{1F910}-\x{1F917}\x{1F920}-\x{1F925}\x{1F927}-\x{1F92F}\x{1F93A}\x{1F93F}-\x{1F945}\x{1F947}-\x{1F976}\x{1F978}-\x{1F9B4}\x{1F9B7}\x{1F9BA}\x{1F9BC}-\x{1F9CC}\x{1F9D0}\x{1F9E0}-\x{1F9FF}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}]/u', '', $text); } -function shortenClient($client) { +function shortenClient($client) +{ // Pre-process by removing any non-alphanumeric characters except for certain punctuations. $client = html_entity_decode($client); // Decode any HTML entities $client = str_replace("'", "", $client); // Removing all occurrences of ' @@ -756,7 +827,8 @@ function shortenClient($client) { return strtoupper(substr($shortened, 0, 3)); } -function roundToNearest15($time) { +function roundToNearest15($time) +{ // Validate the input time format if (!preg_match('/^(\d{2}):(\d{2}):(\d{2})$/', $time, $matches)) { return false; // or throw an exception @@ -785,7 +857,8 @@ function roundToNearest15($time) { } // Get the value of a setting from the database -function getSettingValue($mysqli, $setting_name) { +function getSettingValue($mysqli, $setting_name) +{ //if starts with config_ then get from config table if (substr($setting_name, 0, 7) == "config_") { $sql = mysqli_query($mysqli, "SELECT $setting_name FROM settings"); @@ -800,7 +873,8 @@ function getSettingValue($mysqli, $setting_name) { } } -function getMonthlyTax($tax_name, $month, $year, $mysqli) { +function getMonthlyTax($tax_name, $month, $year, $mysqli) +{ // SQL to calculate monthly tax $sql = "SELECT SUM(item_tax) AS monthly_tax FROM invoice_items LEFT JOIN invoices ON invoice_items.item_invoice_id = invoices.invoice_id @@ -812,7 +886,8 @@ function getMonthlyTax($tax_name, $month, $year, $mysqli) { return $row['monthly_tax'] ?? 0; } -function getQuarterlyTax($tax_name, $quarter, $year, $mysqli) { +function getQuarterlyTax($tax_name, $quarter, $year, $mysqli) +{ // Calculate start and end months for the quarter $start_month = ($quarter - 1) * 3 + 1; $end_month = $start_month + 2; @@ -828,7 +903,8 @@ function getQuarterlyTax($tax_name, $quarter, $year, $mysqli) { return $row['quarterly_tax'] ?? 0; } -function getTotalTax($tax_name, $year, $mysqli) { +function getTotalTax($tax_name, $year, $mysqli) +{ // SQL to calculate total tax $sql = "SELECT SUM(item_tax) AS total_tax FROM invoice_items LEFT JOIN invoices ON invoice_items.item_invoice_id = invoices.invoice_id @@ -841,14 +917,16 @@ function getTotalTax($tax_name, $year, $mysqli) { } //Get account currency code -function getAccountCurrencyCode($mysqli, $account_id) { +function getAccountCurrencyCode($mysqli, $account_id) +{ $sql = mysqli_query($mysqli, "SELECT account_currency_code FROM accounts WHERE account_id = $account_id"); $row = mysqli_fetch_array($sql); $account_currency_code = nullable_htmlentities($row['account_currency_code']); return $account_currency_code; } -function calculateAccountBalance($mysqli, $account_id) { +function calculateAccountBalance($mysqli, $account_id) +{ $sql_account = mysqli_query($mysqli, "SELECT * FROM accounts LEFT JOIN account_types ON accounts.account_type = account_types.account_type_id WHERE account_archived_at IS NULL AND account_id = $account_id ORDER BY account_name ASC; "); $row = mysqli_fetch_array($sql_account); $opening_balance = floatval($row['opening_balance']); @@ -876,7 +954,8 @@ function calculateAccountBalance($mysqli, $account_id) { } -function generateReadablePassword($security_level) { +function generateReadablePassword($security_level) +{ // Arrays of words $adjectives = ['Smart', 'Swift', 'Secure', 'Stable', 'Digital', 'Virtual', 'Active', 'Dynamic', 'Innovative', 'Efficient', 'Portable', 'Wireless', 'Rapid', 'Intuitive', 'Automated', 'Robust', 'Reliable', 'Sleek', 'Modern', 'Happy', 'Funny', 'Quick', 'Bright', 'Clever', 'Gentle', 'Brave', 'Calm', 'Eager', 'Fierce', 'Kind', 'Lucky', 'Proud', 'Silly', 'Witty', 'Bold', 'Curious', 'Elated', 'Gracious', 'Honest', 'Jolly', 'Merry', 'Noble', 'Optimistic', 'Playful', 'Quirky', 'Rustic', 'Steady', 'Tranquil', 'Upbeat']; $nouns = ['Computer', 'Laptop', 'Tablet', 'Server', 'Router', 'Software', 'Hardware', 'Pixel', 'Byte', 'App', 'Network', 'Cloud', 'Firewall', 'Email', 'Database', 'Folder', 'Document', 'Interface', 'Program', 'Gadget', 'Dinosaur', 'Tiger', 'Elephant', 'Kangaroo', 'Monkey', 'Unicorn', 'Dragon', 'Puppy', 'Kitten', 'Parrot', 'Lion', 'Bear', 'Fox', 'Wolf', 'Rabbit', 'Deer', 'Owl', 'Hedgehog', 'Turtle', 'Frog', 'Butterfly', 'Panda', 'Giraffe', 'Zebra', 'Peacock', 'Koala', 'Raccoon', 'Squirrel', 'Hippo', 'Rhino', 'Book', "Monitor"]; @@ -892,7 +971,7 @@ function generateReadablePassword($security_level) { // Combine to create a base password - if ($security_level > 2 ) { + if ($security_level > 2) { $password = "The" . $adj . $noun . $adv . $verb; } else { $password = $adj . $noun . $verb; @@ -911,7 +990,7 @@ function generateReadablePassword($security_level) { if ($security_level > 4) { $password = strtr($password, $mappings); } else { - // Randomly replace characters based on mappings + // Randomly replace characters based on mappings for ($i = 0; $i < strlen($password); $i++) { if (array_key_exists($password[$i], $mappings) && rand(0, 1)) { $password[$i] = $mappings[$password[$i]]; @@ -927,7 +1006,8 @@ function generateReadablePassword($security_level) { return $password; } -function addToMailQueue($mysqli, $data) { +function addToMailQueue($mysqli, $data) +{ foreach ($data as $email) { $from = strval($email['from']); @@ -936,6 +1016,7 @@ function addToMailQueue($mysqli, $data) { $recipient_name = strval($email['recipient_name']); $subject = strval($email['subject']); $body = strval($email['body']); + $cal_str = mysqli_escape_string($mysqli,$email['cal_str']); // Check if 'email_queued_at' is set and not empty if (isset($email['queued_at']) && !empty($email['queued_at'])) { @@ -945,14 +1026,14 @@ function addToMailQueue($mysqli, $data) { $queued_at = date('Y-m-d H:i:s'); } - mysqli_query($mysqli, "INSERT INTO email_queue SET email_recipient = '$recipient', email_recipient_name = '$recipient_name', email_from = '$from', email_from_name = '$from_name', email_subject = '$subject', email_content = '$body', email_queued_at = '$queued_at'"); + mysqli_query($mysqli, "INSERT INTO email_queue SET email_recipient = '$recipient', email_recipient_name = '$recipient_name', email_from = '$from', email_from_name = '$from_name', email_subject = '$subject', email_content = '$body', email_queued_at = '$queued_at', email_cal_str = '$cal_str'"); } return true; - } -function calculateInvoiceBalance($mysqli, $invoice_id) { +function calculateInvoiceBalance($mysqli, $invoice_id) +{ $invoice_id_int = intval($invoice_id); $sql_invoice = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_id = $invoice_id_int"); $row = mysqli_fetch_array($sql_invoice); @@ -962,7 +1043,8 @@ function calculateInvoiceBalance($mysqli, $invoice_id) { $mysqli, "SELECT SUM(payment_amount) AS total_payments FROM payments WHERE payment_invoice_id = $invoice_id - "); + " + ); $row = mysqli_fetch_array($sql_payments); $total_payments = floatval($row['total_payments']); @@ -974,6 +1056,32 @@ function calculateInvoiceBalance($mysqli, $invoice_id) { } return $balance; - - +} + +function createiCalStr($datetime, $title, $description, $location) +{ + require_once "plugins/zapcal/zapcallib.php"; + + // Create the iCal object + $cal_event = new ZCiCal(); + $event = new ZCiCalNode("VEVENT", $cal_event->curnode); + + + // Set the method to REQUEST to indicate an invite + $event->addNode(new ZCiCalDataNode("METHOD:REQUEST")); + $event->addNode(new ZCiCalDataNode("SUMMARY:" . $title)); + $event->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime($datetime))); + // Assuming the end time is the same as start time. + // Todo: adjust this for actual duration + $event->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime($datetime))); + $event->addNode(new ZCiCalDataNode("DTSTAMP:" . ZCiCal::fromSqlDateTime())); + $uid = date('Y-m-d-H-i-s') . "@" . $_SERVER['SERVER_NAME']; + $event->addNode(new ZCiCalDataNode("UID:" . $uid)); + $event->addNode(new ZCiCalDataNode("LOCATION:" . $location)); + $event->addNode(new ZCiCalDataNode("DESCRIPTION:" . $description)); + // Todo: add organizer details + // $event->addNode(new ZCiCalDataNode("ORGANIZER;CN=Organizer Name:MAILTO:organizer@example.com")); + + // Return the iCal string + return $cal_event->export(); } diff --git a/js/scheduled_tickets_edit_modal.js b/js/scheduled_tickets_edit_modal.js index f934cf27..67525ad6 100644 --- a/js/scheduled_tickets_edit_modal.js +++ b/js/scheduled_tickets_edit_modal.js @@ -15,7 +15,7 @@ function populateScheduledTicketEditModal(client_id, ticket_id) { const assets = response.assets; // Populate the scheduled ticket modal fields - document.getElementById("editHeader").innerText = " Edit Scheduled ticket: " + ticket.scheduled_ticket_subject; + document.getElementById("editHeader").innerText = " Edit Recurring ticket: " + ticket.scheduled_ticket_subject; document.getElementById("editTicketId").value = ticket_id; document.getElementById("editClientId").value = client_id; document.getElementById("editTicketSubject").value = ticket.scheduled_ticket_subject; diff --git a/plugins/zapcal/README.md b/plugins/zapcal/README.md new file mode 100644 index 00000000..11e64f6a --- /dev/null +++ b/plugins/zapcal/README.md @@ -0,0 +1,127 @@ +# Zap Calendar iCalendar Library + +(https://github.com/zcontent/icalendar) + +The Zap Calendar iCalendar Library is a PHP library for supporting the iCalendar (RFC 5545) standard. + +This PHP library is for reading and writing iCalendar formatted feeds and +files. Features of the library include: + +- Read AND write support for iCalendar files +- Object based creation and manipulation of iCalendar files +- Supports expansion of RRULE to a list of repeating dates +- Supports adding timezone info to iCalendar file + +All iCalendar data is stored in a PHP object tree. +This allows any property to be added to the iCalendar feed without +requiring specialized library function calls. +With power comes responsibility. Missing or invalid properties can cause +the resulting iCalendar file to be invalid. Visit [iCalendar.org](http://icalendar.org) to view valid +properties and test your feed using the site's [iCalendar validator tool](http://icalendar.org/validator.html). + +Library API documentation can be found at http://icalendar.org/zapcallibdocs + +See the examples folder for programs that read and write iCalendar +files. At its simpliest, you need to include the library at the top of your program: + +```php +require_once($path_to_library . "/zapcallib.php"); +``` + +Create an ical object using the ZCiCal object: + +```php +$icalobj = new ZCiCal(); +``` + +Add an event object: + +```php +$eventobj = new ZCiCalNode("VEVENT", $icalobj->curnode); +``` + +Add a start and end date to the event: + +```php +// add start date +$eventobj->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime("2020-01-01 12:00:00"))); + +// add end date +$eventobj->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime("2020-01-01 13:00:00"))); +``` + +Write the object in iCalendar format using the export() function call: + +```php +echo $icalobj->export(); +``` + +This example will not validate since it is missing some required elements. +Look at the simpleevent.php example for the minimum # of elements +needed for a validated iCalendar file. + +To create a multi-event iCalendar file, simply create multiple event objects. For example: + +```php +$icalobj = new ZCiCal(); +$eventobj1 = new ZCiCalNode("VEVENT", $icalobj->curnode); +$eventobj1->addNode(new ZCiCalDataNode("SUMMARY:Event 1")); +... +$eventobj2 = new ZCiCalNode("VEVENT", $icalobj->curnode); +$eventobj2->addNode(new ZCiCalDataNode("SUMMARY:Event 2")); +... +``` + +To read an existing iCalendar file/feed, create the ZCiCal object with a string representing the contents of the iCalendar file: + +```php +$icalobj = new ZCiCal($icalstring); +``` + +Large iCalendar files can be read in chunks to reduce the amount of memory needed to hold the iCalendar feed in memory. This example reads 500 events at a time: + +```php +$icalobj = null; +$eventcount = 0; +$maxevents = 500; +do +{ + $icalobj = newZCiCal($icalstring, $maxevents, $eventcount); + ... + $eventcount +=$maxevents; +} +while($icalobj->countEvents() >= $eventcount); +``` + +You can read the events from an imported (or created) iCalendar object in this manner: + +```php +foreach($icalobj->tree->child as $node) +{ + if($node->getName() == "VEVENT") + { + foreach($node->data as $key => $value) + { + if($key == "SUMMARY") + { + echo "event title: " . $value->getValues() . "\n"; + } + } + } +} +``` + +## Known Limitations + +- Since the library utilizes objects to read and write iCalendar data, the +size of the iCalendar data is limited to the amount of available memory on the machine. +The ZCiCal() object supports reading a range of events to minimize memory +space. +- The library ignores timezone info when importing files, instead utilizing PHP's timezone +library for calculations (timezones are supported when exporting files). +Imported timezones need to be aliased to a [PHP supported timezone](http://php.net/manual/en/timezones.php). +- At this time, the library does not support the "BYSETPOS" option in RRULE items. +- At this time, the maximum date supported is 2036 to avoid date math issues +with 32 bit systems. +- Repeating events are limited to a maximum of 5,000 dates to avoid memory or infinite loop issues + diff --git a/plugins/zapcal/includes/date.php b/plugins/zapcal/includes/date.php new file mode 100644 index 00000000..dba86e42 --- /dev/null +++ b/plugins/zapcal/includes/date.php @@ -0,0 +1,568 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Zap Calendar Date Helper Class + * + * Helper class for various date functions + */ +class ZDateHelper { + + /** + * Find the number of days in a month + * + * @param int $month Month is between 1 and 12 inclusive + * + * @param int $year is between 1 and 32767 inclusive + * + * @return int + */ + static function DayInMonth($month, $year) { + $daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + if ($month != 2) return $daysInMonth[$month - 1]; + return (checkdate($month, 29, $year)) ? 29 : 28; + } + + /** + * Is given date today? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isToday($date, $tzid = "UTC") { + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now); + } + + /** + * Is given date before today? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isBeforeToday($date, $tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) > + mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now)); + } + + /** + * Is given date after today? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isAfterToday($date, $tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) < + mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now)); + } + + /** + * Is given date tomorrow? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isTomorrow($date, $tzid = "UTC") { + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now + 60 * 60 * 24); + } + + /** + * Is given date in the future? + * + * This routine differs from isAfterToday() in that isFuture() will + * return true for date-time values later in the same day. + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isFuture($date, $tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return $date > $now; + } + + /** + * Is given date in the past? + * + * This routine differs from isBeforeToday() in that isPast() will + * return true for date-time values earlier in the same day. + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isPast($date, $tzid = "UTC") { + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return $date < $now; + } + + /** + * Return current Unix timestamp in local timezone + * + * @param string $tzid PHP recognized timezone + * + * @return int + */ + static function now($tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return $now; + } + + /** + * Is given date fall on a weekend? + * + * @param int $date Unix timestamp + * + * @return bool + */ + static function isWeekend($date) { + $dow = gmdate('w',$date); + return $dow == 0 || $dow == 6; + } + + /** + * Format Unix timestamp to SQL date-time + * + * @param int $t Unix timestamp + * + * @return string + */ + static function toSqlDateTime($t = 0) + { + date_default_timezone_set('GMT'); + if($t == 0) + return gmdate('Y-m-d H:i:s',self::now()); + return gmdate('Y-m-d H:i:s', $t); + } + + /** + * Format Unix timestamp to SQL date + * + * @param int $t Unix timestamp + * + * @return string + */ + static function toSqlDate($t = 0) + { + date_default_timezone_set('GMT'); + if($t == 0) + return gmdate('Y-m-d',self::now()); + return gmdate('Y-m-d', $t); + } + + /** + * Format iCal date-time string to Unix timestamp + * + * @param string $datetime in iCal time format ( YYYYMMDD or YYYYMMDDTHHMMSS or YYYYMMDDTHHMMSSZ ) + * + * @return int Unix timestamp + */ + static function fromiCaltoUnixDateTime($datetime) { + // first check format + $formats = array(); + $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/"; + $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]/"; + $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z/"; + $ok = false; + foreach($formats as $format){ + if(preg_match($format,$datetime)){ + $ok = true; + break; + } + } + if(!$ok) + return null; + $year = substr($datetime,0,4); + $month = substr($datetime,4,2); + $day = substr($datetime,6,2); + $hour = 0; + $minute = 0; + $second = 0; + if(strlen($datetime) > 8 && $datetime[8] == "T") { + $hour = substr($datetime,9,2); + $minute = substr($datetime,11,2); + $second = substr($datetime,13,2); + } + return gmmktime($hour, $minute, $second, $month, $day, $year); + } + + /** + * Format Unix timestamp to iCal date-time string + * + * @param int $datetime Unix timestamp + * + * @return string + */ + static function fromUnixDateTimetoiCal($datetime){ + date_default_timezone_set('GMT'); + return gmdate("Ymd\THis",$datetime); + } + + /** + * Convert iCal duration string to # of seconds + * + * @param string $duration iCal duration string + * + * return int + */ + static function iCalDurationtoSeconds($duration) { + $secs = 0; + if($duration[0] == "P") { + $duration = str_replace(array("H","M","S","T","D","W","P"),array("H,","M,","S,","","D,","W,",""),$duration); + $dur2 = explode(",",$duration); + foreach($dur2 as $dur){ + $val=intval($dur); + if(strlen($dur) > 0){ + switch($dur[strlen($dur) - 1]) { + case "H": + $secs += 60*60 * $val; + break; + case "M": + $secs += 60 * $val; + break; + case "S": + $secs += $val; + break; + case "D": + $secs += 60*60*24 * $val; + break; + case "W": + $secs += 60*60*24*7 * $val; + break; + } + } + } + } + return $secs; + } + + /** + * Check if day falls within date range + * + * @param int $daystart start of day in Unix timestamp format + * + * @param int $begin Unix timestamp of starting date range + * + * @param int $end Unix timestamp of end date range + * + * @return bool + */ + static function inDay($daystart, $begin, $end) + { + //$dayend = $daystart + 60*60*24 - 60; + // add 1 day to determine end of day + // don't use 24 hours, since twice a year DST Sundays are 23 hours and 25 hours in length + // adding 1 day takes this into account + $dayend = self::addDate($daystart, 0,0,0,0,1,0); + + $end = max($begin, $end); // $end can't be less than $begin + $inday = + ($daystart <= $begin && $begin < $dayend) + ||($daystart < $end && $end < $dayend) + ||($begin <= $daystart && $end > $dayend) + ; + return $inday; + } + + /** + * Convert SQL date or date-time to Unix timestamp + * + * @param string $datetime SQL date or date-time + * + * @return int Unix date-time timestamp + */ + static function toUnixDate($datetime) + { + $year = substr($datetime,0,4); + $month = substr($datetime,5,2); + $day = substr($datetime,8,2); + + return mktime(0, 0, 0, $month, $day, $year); + } + + /** + * Convert SQL date or date-time to Unix date timestamp + * + * @param string $datetime SQL date or date-time + * + * @return int Unix timestamp + */ + static function toUnixDateTime($datetime) + { + // convert to absolute dates if neccessary + $datetime = self::getAbsDate($datetime); + $year = substr($datetime,0,4); + $month = substr($datetime,5,2); + $day = substr($datetime,8,2); + $hour = 0; + $minute = 0; + $second = 0; + if(strlen($datetime) > 10) { + $hour = substr($datetime,11,2); + $minute = substr($datetime,14,2); + $second = substr($datetime,17,2); + } + return gmmktime($hour, $minute, $second, $month, $day, $year); + } + + /** + * Date math: add or substract from current date to get a new date + * + * @param int $date date to add or subtract from + * + * @param int $hour add or subtract hours from date + * + * @param int $min add or subtract minutes from date + * + * @param int $sec add or subtract seconds from date + * + * @param int $month add or subtract months from date + * + * @param int $day add or subtract days from date + * + * @param int $year add or subtract years from date + * + * @param string $tzid PHP recognized timezone (default is UTC) + */ + static function addDate($date, $hour, $min, $sec, $month, $day, $year, $tzid = "UTC") { + date_default_timezone_set($tzid); + $sqldate = self::toSQLDateTime($date); + $tdate = array(); + $tdate["year"] = substr($sqldate,0,4); + $tdate["mon"] = substr($sqldate,5,2); + $tdate["mday"] = substr($sqldate,8,2); + $tdate["hours"] = substr($sqldate,11,2); + $tdate["minutes"] = substr($sqldate,14,2); + $tdate["seconds"] = substr($sqldate,17,2); + $newdate=mktime($tdate["hours"] + $hour, $tdate["minutes"] + $min, $tdate["seconds"] + $sec, $tdate["mon"] + $month, $tdate["mday"] + $day, $tdate["year"] + $year); + date_default_timezone_set("UTC"); + //echo self::toSQLDateTime($date) . " => " . self::toSQLDateTime($newdate) . " ($hour:$min:$sec $month/$day/$year)
\n"; + return $newdate; + } + + /** + * Date math: get date from week and day in specifiec month + * + * This routine finds actual dates for the second Tuesday of the month, last Friday of the month, etc. + * For second Tuesday, use $week = 1, $wday = 2 + * for last Friday, use $week = -1, $wday = 5 + * + * @param int $date Unix timestamp + * + * @param int $week week number, 0 is first week, -1 is last + * + * @param int $wday day of week, 0 is Sunday, 6 is Saturday + * + * @param string $tzid PHP supported timezone + * + * @return int Unix timestamp + */ + static function getDateFromDay($date, $week, $wday,$tzid="UTC") { + //echo "getDateFromDay(" . self::toSqlDateTime($date) . ",$week,$wday)
\n"; + // determine first day in month + $tdate = getdate($date); + $monthbegin = gmmktime(0,0,0, $tdate["mon"],1,$tdate["year"]); + $monthend = self::addDate($monthbegin, 0,0,0,1,-1,0,$tzid); // add 1 month and subtract 1 day + $day = self::addDate($date,0,0,0,0,1 - $tdate["mday"],0,$tzid); + $month = array(array()); + while($day <= $monthend) { + $tdate=getdate($day); + $month[$tdate["wday"]][]=$day; + //echo self::toSQLDateTime($day) . "
\n"; + $day = self::addDate($day, 0,0,0,0,1,0,$tzid); // add 1 day + } + $dayinmonth=0; + if($week >= 0) + $dayinmonth = $month[$wday][$week]; + else + $dayinmonth = $month[$wday][count($month[$wday]) - 1]; + //echo "return " . self::toSQLDateTime($dayinmonth); + //exit; + return $dayinmonth; + } + + /** + * Convert UTC date-time to local date-time + * + * @param string $sqldate SQL date-time string + * + * @param string $tzid PHP recognized timezone (default is "UTC") + * + * @return string SQL date-time string + */ + static function toLocalDateTime($sqldate, $tzid = "UTC" ){ + try + { + $timezone = new DateTimeZone($tzid); + } + catch(Exception $e) + { + // bad time zone specified + return $sqldate; + } + $udate = self::toUnixDateTime($sqldate); + $daydatetime = new DateTime("@" . $udate); + $tzoffset = $timezone->getOffset($daydatetime); + return self::toSqlDateTime($udate + $tzoffset); + } + + /** + * Convert local date-time to UTC date-time + * + * @param string $sqldate SQL date-time string + * + * @param string $tzid PHP recognized timezone (default is "UTC") + * + * @return string SQL date-time string + */ + static function toUTCDateTime($sqldate, $tzid = "UTC" ){ + + date_default_timezone_set("UTC"); + try + { + $date = new DateTime($sqldate, $tzid); + } + catch(Exception $e) + { + // bad time zone specified + return $sqldate; + } + $offset = $date->getOffsetFromGMT(); + if($offset >= 0) + $date->sub(new DateInterval("PT".$offset."S")); + else + $date->add(new DateInterval("PT".abs($offset)."S")); + return $date->toSql(true); + } + + /** + * Convert from a relative date to an absolute date + * + * Examples of relative dates are "-2y" for 2 years ago, "18m" + * for 18 months after today. Relative date uses "y", "m" and "d" for + * year, month and day. Relative date can be combined into comma + * separated list, i.e., "-1y,-1d" for 1 year and 1 day ago. + * + * @param string $date relative date string (i.e. "1y" for 1 year from today) + * + * @param string $rdate reference date, or blank for current date (in SQL date-time format) + * + * @return string in SQL date-time format + */ + static function getAbsDate($date,$rdate = ""){ + if(str_replace(array("y","m","d","h","n"),"",strtolower($date)) != strtolower($date)){ + date_default_timezone_set("UTC"); + if($rdate == "") + $udate = time(); + else + $udate = self::toUnixDateTime($rdate); + $values=explode(",",strtolower($date)); + $y = 0; + $m = 0; + $d = 0; + $h = 0; + $n = 0; + foreach($values as $value){ + $rtype = substr($value,strlen($value)-1); + $rvalue = intval(substr($value,0,strlen($value) - 1)); + switch($rtype){ + case 'y': + $y = $rvalue; + break; + case 'm': + $m = $rvalue; + break; + case 'd': + $d = $rvalue; + break; + case 'h': + $h = $rvalue; + break; + case 'n': + $n = $rvalue; + break; + } + // for "-" values, move to start of day , otherwise, move to end of day + if($rvalue[0] == '-') + $udate = mktime(0,0,0,date('m',$udate),date('d',$udate),date('Y',$udate)); + else + $udate = mktime(0,-1,0,date('m',$udate),date('d',$udate)+1,date('Y',$udate)); + $udate = self::addDate($udate,$h,$n,0,$m,$d,$y); + } + $date = self::toSqlDateTime($udate); + } + return $date; + } + + /** + * Format Unix timestamp to iCal date-time format + * + * @param int $datetime Unix timestamp + * + * @return string iCal date-time string + */ + static function toiCalDateTime($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return gmdate("Ymd\THis",$datetime); + } + + /** + * Format Unix timestamp to iCal date format + * + * @param int $datetime Unix timestamp + * + * @return string iCal date-time string + */ + static function toiCalDate($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return gmdate("Ymd",$datetime); + } +} diff --git a/plugins/zapcal/includes/framework.php b/plugins/zapcal/includes/framework.php new file mode 100644 index 00000000..69df54e4 --- /dev/null +++ b/plugins/zapcal/includes/framework.php @@ -0,0 +1,32 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * set MAXYEAR to 2036 for 32 bit systems, can be higher for 64 bit systems + * + * @var integer + */ +define('_ZAPCAL_MAXYEAR', 2036); + +/** + * set MAXREVENTS to maximum # of repeating events + * + * @var integer + */ +define('_ZAPCAL_MAXREVENTS', 5000); + +require_once(_ZAPCAL_BASE . '/includes/date.php'); +require_once(_ZAPCAL_BASE . '/includes/recurringdate.php'); +require_once(_ZAPCAL_BASE . '/includes/ical.php'); +require_once(_ZAPCAL_BASE . '/includes/timezone.php'); diff --git a/plugins/zapcal/includes/ical.php b/plugins/zapcal/includes/ical.php new file mode 100644 index 00000000..6712bb61 --- /dev/null +++ b/plugins/zapcal/includes/ical.php @@ -0,0 +1,986 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Object for storing an unfolded iCalendar line + * + * The ZCiCalDataNode class contains data from an unfolded iCalendar line + * + */ +class ZCiCalDataNode { + /** + * The name of the node + * + * @var string + */ + var $name = ""; + + /** + * Node parameters (before the colon ":") + * + * @var array + */ + var $parameter=array(); + + /** + * Node values (after the colon ":") + * + * @var array + */ + var $value=array(); + + /** + * Create an object from an unfolded iCalendar line + * + * @param string $line An unfolded iCalendar line + * + * @return void + * + */ + function __construct( $line ) { + //echo "ZCiCalDataNode($line)
\n"; + //separate line into parameters and value + // look for colon separating name or parameter and value + // first change any escaped colons temporarily to make it easier + $tline = str_replace("\\:", "`~", $line); + // see if first colon is inside a quoted string + $i = 0; + $datafind = false; + $inquotes = false; + while(!$datafind && ($i < strlen($tline))) { + //echo "$i: " . $tline[$i] . ", ord() = " . ord($tline[$i]) . "
\n"; + if(!$inquotes && $tline[$i] == ':') + $datafind=true; + else{ + $i += 1; + if(substr($tline,$i,1) == '"') + $inquotes = !$inquotes; + } + } + if($datafind){ + $value = str_replace("`~","\\:",substr($line,$i+1)); + // fix escaped characters (don't see double quotes in spec but Apple apparently uses it in iCal) + $value = str_replace(array('\\N' , '\\n', '\\"' ), array("\n", "\n" , '"'), $value); + $tvalue = str_replace("\\,", "`~", $value); + //echo "value: " . $tvalue . "
\n"; + $tvalue = explode(",",$tvalue); + $value = str_replace("`~","\\,",$tvalue); + $this->value = $value; + } + + $parameter = trim(substr($line,0,$i)); + + $parameter = str_replace("\\;", "`~", $parameter); + $parameters = explode(";", $parameter); + $parameters = str_replace("`~", "\\;", $parameters); + $this->name = array_shift($parameters); + foreach($parameters as $parameter){ + $pos = strpos($parameter,"="); + if($pos > 0){ + $param = substr($parameter,0,$pos); + $paramvalue = substr($parameter,$pos+1); + $tvalue = str_replace("\\,", "`~", $paramvalue); + //$tvalue = explode(",",$tvalue); + $paramvalue = str_replace("`~","\\,",$tvalue); + $this->parameter[strtolower($param)] = $paramvalue; + //$this->paramvalue[] = $paramvalue; + } + } + } + +/** + * getName() + * + * Return the name of the object + * + * @return string + */ + function getName(){ + return $this->name; + } + +/** + * Get $ith parameter from array + * @param int $i + * + * @return var + */ + function getParameter($i){ + return $this->parameter[$i]; + } + +/** + * Get parameter array + * + * @return array + */ + function getParameters(){ + return $this->parameter; + } + +/** + * Get comma separated values + * + * @return string + */ + function getValues(){ + return implode(",",$this->value); + } +} + +/** + * Object for storing a list of unfolded iCalendar lines (ZCiCalDataNode objects) + * + * @property object $parentnode Parent of this node + * + * @property array $child Array of children for this node + * + * @property data $data Array of data for this node + * + * @property object $next Next sibling of this node + * + * @property object $prev Previous sibling of this node + */ + +class ZCiCalNode { + /** + * The name of the node + * + * @var string + */ + var $name=""; + + /** + * The parent of this node + * + * @var object + */ + var $parentnode=null; + + /** + * Array of children for this node + * + * @var array + */ + var $child= array(); + + /** + * Array of $data for this node + * + * @var array + */ + var $data= array(); + + + /** + * Next sibling of this node + * + * @var object + */ + var $next=null; + + /** + * Previous sibling of this node + * + * @var object + */ + var $prev=null; + + /** + * Create ZCiCalNode + * + * @param string $_name Name of node + * + * @param object $_parent Parent node for this node + * + * @param bool $first Is this the first child for this parent? + */ + function __construct( $_name, & $_parent, $first = false) { + $this->name = $_name; + $this->parentnode = $_parent; + if($_parent != null){ + if(count($this->parentnode->child) > 0) { + if($first) + { + $first = & $this->parentnode->child[0]; + $first->prev = & $this; + $this->next = & $first; + } + else + { + $prev =& $this->parentnode->child[count($this->parentnode->child)-1]; + $prev->next =& $this; + $this->prev =& $prev; + } + } + if($first) + { + array_unshift($this->parentnode->child, $this); + } + else + { + $this->parentnode->child[] =& $this; + } + } + /* + echo "creating " . $this->getName(); + if($_parent != null) + echo " child of " . $_parent->getName() . "/" . count($this->parentnode->child); + echo "
"; + */ + } + + /** + * Return the name of the object + * + * @return string + */ + function getName() { + return $this->name; + } + + /** + * Add node to list + * + * @param object $node + * + */ + function addNode($node) { + if(array_key_exists($node->getName(), $this->data)) + { + if(!is_array($this->data[$node->getName()])) + { + $this->data[$node->getName()] = array($this->data[$node->getName()]); + } + $this->data[$node->getName()][] = $node; + } + else + { + $this->data[$node->getName()] = $node; + } + } + + /** + * Get Attribute + * + * @param int $i array id of attribute to get + * + * @return string + */ + function getAttrib($i) { + return $this->attrib[$i]; + } + + /** + * Set Attribute + * + * @param string $value value of attribute to set + * + */ + function setAttrib($value) { + $this->attrib[] = $value; + } + + /** + * Get the parent object of this object + * + * @return object parent of this object + */ + function &getParent() { + return $this->parentnode; + } + + /** + * Get the first child of this object + * + * @return object The first child + */ + function &getFirstChild(){ + static $nullguard = null; + if(count($this->child) > 0) { + //echo "moving from " . $this->getName() . " to " . $this->child[0]->getName() . "
"; + return $this->child[0]; + } + else + return $nullguard; + } + + /** + * Print object tree in HTML for debugging purposes + * + * @param object $node select part of tree to print, or leave blank for full tree + * + * @param int $level Level of recursion (usually leave this blank) + * + * @return string - HTML formatted display of object tree + */ + function printTree(& $node=null, $level=1){ + $level += 1; + $html = ""; + if($node == null) + $node = $this->parentnode; + if($level > 5) + { + die("levels nested too deep
\n"); + //return; + } + for($i = 0 ; $i < $level; $i ++) + $html .= "+"; + $html .= $node->getName() . "
\n"; + foreach ($node->child as $c){ + $html .= $node->printTree($c,$level); + } + $level -= 1; + return $html; + } + + /** + * export tree to icalendar format + * + * @param object $node Top level node to export + * + * @param int $level Level of recursion (usually leave this blank) + * + * @return string iCalendar formatted output + */ + function export(& $node=null, $level=0){ + $txtstr = ""; + if($node == null) + $node = $this; + if($level > 5) + { + //die("levels nested too deep
\n"); + throw new Exception("levels nested too deep"); + } + $txtstr .= "BEGIN:" . $node->getName() . "\r\n"; + if(property_exists($node,"data")) + foreach ($node->data as $d){ + if(is_array($d)) + { + foreach ($d as $c) + { + //$txtstr .= $node->export($c,$level + 1); + $p = ""; + $params = @$c->getParameters(); + if(count($params) > 0) + { + foreach($params as $key => $value){ + $p .= ";" . strtoupper($key) . "=" . $value; + } + } + $txtstr .= $this->printDataLine($c, $p); + } + } + else + { + $p = ""; + $params = @$d->getParameters(); + if(count($params) > 0) + { + foreach($params as $key => $value){ + $p .= ";" . strtoupper($key) . "=" . $value; + } + } + $txtstr .= $this->printDataLine($d, $p); + /* + $values = $d->getValues(); + // don't think we need this, Sunbird does not like it in the EXDATE field + //$values = str_replace(",", "\\,", $values); + + $line = $d->getName() . $p . ":" . $values; + $line = str_replace(array("
","
","
","
0) { + $linewidth = ($linecount == 0? 75 : 74); + $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); + if($linecount > 0) + $txtstr .= " "; + $txtstr .= substr($line,0,$linesize) . "\r\n"; + $linecount += 1; + $line = substr($line,$linewidth); + } + */ + } + //echo $line . "\n"; + } + if(property_exists($node,"child")) + foreach ($node->child as $c){ + $txtstr .= $node->export($c,$level + 1); + } + $txtstr .= "END:" . $node->getName() . "\r\n"; + return $txtstr; + } + + /** + * print an attribute line + + * @param object $d attributes + * @param object $p properties + * + */ + function printDataLine($d, $p) + { + $txtstr = ""; + + $values = $d->getValues(); + // don't think we need this, Sunbird does not like it in the EXDATE field + //$values = str_replace(",", "\\,", $values); + + $line = $d->getName() . $p . ":" . $values; + $line = str_replace(array("
","
","
","
0) { + $linewidth = ($linecount == 0? 75 : 74); + $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); + if($linecount > 0) + $txtstr .= " "; + $txtstr .= substr($line,0,$linesize) . "\r\n"; + $linecount += 1; + $line = substr($line,$linewidth); + } + return $txtstr; + } +} + +/** + * + * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. + * +*/ +class ZCiCal { + /** + * The root node of the object tree + * + * @var object + */ + var $tree=null; + /** + * The most recently created node in the tree + * + * @var object + */ + var $curnode=null; + +/** + * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. + * + * use maxevents and startevent to read events in multiple passes (to save memory) + * + * @param string $data icalendar feed string (empty if creating new feed) + * + * @param int $maxevents maximum # of events to read + * + * @param int $startevent starting event to read + * + * @return void + * +* +*/ +function __construct($data = "", $maxevents = 1000000, $startevent = 0) { + + if($data != ""){ + // unfold lines + // first change all eol chars to "\n" + $data = str_replace(array("\r\n", "\n\r", "\n", "\r"), "\n", $data); + // now unfold lines + //$data = str_replace(array("\n ", "\n "),"!?", $data); + $data = str_replace(array("\n ", "\n "),"", $data); + // replace special iCal chars + $data = str_replace(array("\\\\","\,"),array("\\",","), $data); + + // parse each line + $lines = explode("\n", $data); + + $linecount = 0; + $eventcount = 0; + $eventpos = 0; + foreach($lines as $line) { + //$line = str_replace("!?", "\n", $line); // add nl back into descriptions + // echo ($linecount + 1) . ": " . $line . "
"; + if(substr($line,0,6) == "BEGIN:") { + // start new object + $name = substr($line,6); + if($name == "VEVENT") + { + if($eventcount < $maxevents && $eventpos >= $startevent) + { + $this->curnode = new ZCiCalNode($name, $this->curnode); + if($this->tree == null) + $this->tree = $this->curnode; + } + } + else + { + $this->curnode = new ZCiCalNode($name, $this->curnode); + if($this->tree == null) + $this->tree = $this->curnode; + } + //echo "new node: " . $this->curnode->name . "
\n"; + /* + if($this->curnode->getParent() != null) + echo "parent of " . $this->curnode->getName() . " is " . $this->curnode->getParent()->getName() . "
"; + else + echo "parent of " . $this->curnode->getName() . " is null
"; + */ + } + else if(substr($line,0,4) == "END:") { + $name = substr($line,4); + if($name == "VEVENT") + { + if($eventcount < $maxevents && $eventpos >= $startevent) + { + $eventcount++; + if($this->curnode->getName() != $name) { + //panic, mismatch in iCal structure + //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + } + if($this->curnode->getParent() != null) { + //echo "moving up from " . $this->curnode->getName() ; + $this->curnode = & $this->curnode->getParent(); + //echo " to " . $this->curnode->getName() . "
"; + //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; + } + } + $eventpos++; + } + else + { + if($this->curnode->getName() != $name) { + //panic, mismatch in iCal structure + //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + } + if($this->curnode->getParent() != null) { + //echo "moving up from " . $this->curnode->getName() ; + $this->curnode = & $this->curnode->getParent(); + //echo " to " . $this->curnode->getName() . "
"; + //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; + } + } + } + else { + $datanode = new ZCiCalDataNode($line); + if($this->curnode->getName() == "VEVENT") + { + if($eventcount < $maxevents && $eventpos >= $startevent) + { + if($datanode->getName() == "EXDATE") + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; + } + } + else + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $tnode = $this->curnode->data[$datanode->getName()]; + $this->curnode->data[$datanode->getName()] = array(); + $this->curnode->data[$datanode->getName()][] = $tnode; + $this->curnode->data[$datanode->getName()][] = $datanode; + } + } + } + } + else + { + if($datanode->getName() == "EXDATE") + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; + } + } + else + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $tnode = $this->curnode->data[$datanode->getName()]; + $this->curnode->data[$datanode->getName()] = array(); + $this->curnode->data[$datanode->getName()][] = $tnode; + $this->curnode->data[$datanode->getName()][] = $datanode; + } + } + } + } + $linecount++; + } + } + else { + $name = "VCALENDAR"; + $this->curnode = new ZCiCalNode($name, $this->curnode); + $this->tree = $this->curnode; + $datanode = new ZCiCalDataNode("VERSION:2.0"); + $this->curnode->data[$datanode->getName()] = $datanode; + + $datanode = new ZCiCalDataNode("PRODID:-//ZContent.net//ZapCalLib 1.0//EN"); + $this->curnode->data[$datanode->getName()] = $datanode; + $datanode = new ZCiCalDataNode("CALSCALE:GREGORIAN"); + $this->curnode->data[$datanode->getName()] = $datanode; + $datanode = new ZCiCalDataNode("METHOD:PUBLISH"); + $this->curnode->data[$datanode->getName()] = $datanode; + } +} + +/** + * CountEvents() + * + * Return the # of VEVENTs in the object + * + * @return int + */ + +function countEvents() { + $count = 0; + if(isset($this->tree->child)){ + foreach($this->tree->child as $child){ + if($child->getName() == "VEVENT") + $count++; + } + } + return $count; +} + +/** + * CountVenues() + * + * Return the # of VVENUEs in the object + * + * @return int + */ + +function countVenues() { + $count = 0; + if(isset($this->tree->child)){ + foreach($this->tree->child as $child){ + if($child->getName() == "VVENUE") + $count++; + } + } + return $count; +} + +/** + * Export object to string + * + * This function exports all objects to an iCalendar string + * + * @return string an iCalendar formatted string + */ + +function export() { + return $this->tree->export($this->tree); +} + +/** + * Get first event in object list + * Use getNextEvent() to navigate through list + * + * @return object The first event, or null + */ +function &getFirstEvent() { + static $nullguard = null; + if ($this->countEvents() > 0){ + $child = $this->tree->child[0]; + $event=false; + while(!$event && $child != null){ + if($child->getName() == "VEVENT") + $event = true; + else + $child = $child->next; + } + return $child; + } + else + return $nullguard; +} + +/** + * Get next event in object list + * + * @param object $event The current event object + * + * @return object Returns the next event or null if past last event + */ +function &getNextEvent($event){ + do{ + $event = $event->next; + } while($event != null && $event->getName() != "VEVENT"); + return $event; +} + +/** + * Get first venue in object list + * Use getNextVenue() to navigate through list + * + * @return object The first venue, or null + */ +function &getFirstVenue() { + static $nullguard = null; + if ($this->countVenues() > 0){ + $child = $this->tree->child[0]; + $event=false; + while(!$event && $child != null){ + if($child->getName() == "VVENUE") + $event = true; + else + $child = $child->next; + } + return $child; + } + else + return $nullguard; +} + +/** + * Get next venue in object list + * + * @param object $venue The current venue object + * + * @return object Returns the next venue or null if past last venue + */ +function &getNextVenue($venue){ + do{ + $venue = $venue->next; + } while($venue != null && $venue->getName() != "VVENUE"); + return $venue; +} + +/** + * Get first child in object list + * Use getNextSibling() and getPreviousSibling() to navigate through list + * + * @param object $thisnode The parent object + * + * @return object The child object + */ +function &getFirstChild(& $thisnode){ + $nullvalue = null; + if(count($thisnode->child) > 0) { + //echo "moving from " . $thisnode->getName() . " to " . $thisnode->child[0]->getName() . "
"; + return $thisnode->child[0]; + } + else + return $nullvalue; +} + +/** + * Get next sibling in object list + * + * @param object $thisnode The current object + * + * @return object Returns the next sibling + */ +function &getNextSibling(& $thisnode){ + return $thisnode->next; +} + +/** + * Get previous sibling in object list + * + * @param object $thisnode The current object + * + * @return object Returns the previous sibling + */ +function &getPrevSibling(& $thisnode){ + return $thisnode->prev; +} + +/** + * Read date/time in iCal formatted string + * + * @param string iCal formated date/time string + * + * @return int Unix timestamp + * @deprecated Use ZDateHelper::toUnixDateTime() instead + */ + +function toUnixDateTime($datetime){ + $year = substr($datetime,0,4); + $month = substr($datetime,4,2); + $day = substr($datetime,6,2); + $hour = 0; + $minute = 0; + $second = 0; + if(strlen($datetime) > 8 && $datetime[8] == "T") { + $hour = substr($datetime,9,2); + $minute = substr($datetime,11,2); + $second = substr($datetime,13,2); + } + $d1 = mktime($hour, $minute, $second, $month, $day, $year); + +} + +/** + * fromUnixDateTime() + * + * Take Unix timestamp and format to iCal date/time string + * + * @param int $datetime Unix timestamp, leave blank for current date/time + * + * @return string formatted iCal date/time string + * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead + */ + +static function fromUnixDateTime($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return date("Ymd\THis",$datetime); +} + + +/** + * fromUnixDate() + * + * Take Unix timestamp and format to iCal date string + * + * @param int $datetime Unix timestamp, leave blank for current date/time + * + * @return string formatted iCal date string + * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead + */ + +static function fromUnixDate($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return date("Ymd",$datetime); +} + +/** + * Format into iCal time format from SQL date or SQL date-time format + * + * @param string $datetime SQL date or SQL date-time string + * + * @return string iCal formatted string + * @deprecated Use ZDateHelper::fromSqlDateTime() instead + */ +static function fromSqlDateTime($datetime = ""){ + if($datetime == "") + $datetime = ZDateHelper::toSqlDateTime(); + if(strlen($datetime) > 10) + return sprintf('%04d%02d%02dT%02d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), + substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); + else + return sprintf('%04d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); +} + +/** + * Format iCal time format to either SQL date or SQL date-time format + * + * @param string $datetime icalendar formatted date or date-time + * @return string SQL date or SQL date-time string + * @deprecated Use ZDateHelper::toSqlDateTime() instead + */ +static function toSqlDateTime($datetime = ""){ + if($datetime == "") + return ZDateHelper::toSqlDateTime(); + if(strlen($datetime) > 10) + return sprintf('%04d-%02d-%02d %02d:%02d:%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), + substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); + else + return sprintf('%04d-%02d-%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); +} + +/** + * Pull timezone data from node and put in array + * + * Returning array contains the following array keys: tzoffsetfrom, tzoffsetto, tzname, dtstart, rrule + * + * @param array $node timezone object + * + * @return array + */ +static function getTZValues($node){ + $tzvalues = array(); + + $tnode = @$node->data['TZOFFSETFROM']; + if($tnode != null){ + $tzvalues["tzoffsetfrom"] = $tnode->getValues(); + } + + $tnode = @$node->data['TZOFFSETTO']; + if($tnode != null){ + $tzvalues["tzoffsetto"] = $tnode->getValues(); + } + + $tnode = @$node->data['TZNAME']; + if($tnode != null){ + $tzvalues["tzname"] = $tnode->getValues(); + } + else + $tzvalues["tzname"] = ""; + + $tnode = @$node->data['DTSTART']; + if($tnode != null){ + $tzvalues["dtstart"] = ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues()); + } + + $tnode = @$node->data['RRULE']; + if($tnode != null){ + $tzvalues["rrule"] = $tnode->getValues(); + //echo "rule: " . $tzvalues["rrule"] . "
\n"; + } + else{ + // no rule specified, let's create one from based on the date + $date = getdate($tzvalues["dtstart"]); + $month = $date["mon"]; + $day = $date["mday"]; + $tzvalues["rrule"] = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day"; + } + + return $tzvalues; +} + +/** + * Escape slashes, commas and semicolons in strings + * + * @param string $content + * + * @return string + */ +static function formatContent($content) +{ + $content = str_replace(array('\\' , ',' , ';' ), array('\\\\' , '\\,' , '\\;' ),$content); + return $content; +} + +} + +?> diff --git a/plugins/zapcal/includes/index.html b/plugins/zapcal/includes/index.html new file mode 100644 index 00000000..53a7f242 --- /dev/null +++ b/plugins/zapcal/includes/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/zapcal/includes/recurringdate.php b/plugins/zapcal/includes/recurringdate.php new file mode 100644 index 00000000..9f6920fc --- /dev/null +++ b/plugins/zapcal/includes/recurringdate.php @@ -0,0 +1,796 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Zap Calendar Recurring Date Helper Class + * + * Class to expand recurring rule to a list of dates + */ +class ZCRecurringDate { + /** + * rules string + * + * @var string + */ + var $rules = ""; + + /** + * start date in Unix Timestamp format (local timezone) + * + * @var integer + */ + var $startdate = null; + + /** + * repeating frequency type (i.e. "y" for yearly, "m" for monthly) + * + * @var string + */ + var $freq = null; + + /** + * timezone of event (using PHP timezones) + * + * @var string + */ + var $tzid = null; + + /** + * repeat mode ('c': count, 'u': until) + * + * @var string + */ + var $repeatmode=null; + + /** + * repeat until date (in UTC Unix Timestamp format) + * + * @var integer + */ + var $until=null; + + /** + * repeat count when repeat mode is 'c' + * + * @var integer + */ + var $count=0; + + /** + * array of repeat by seconds values + * + * @var array + */ + var $bysecond=array(); + + /** + * array of repeat by minutes values + * + * @var array + */ + var $byminute=array(); + + /** + * array of repeat by hour values + * + * @var array + */ + var $byhour=array(); + + /** + * array of repeat by day values + * + * @var array + */ + var $byday=array(); + + /** + * array of repeat by month day values + * + * @var array + */ + var $bymonthday=array(); + + /** + * array of repeat by month values + * + * @var array + */ + var $bymonth=array(); + + /** + * array of repeat by year values + * + * @var array + */ + var $byyear=array(); + + /** + * array of repeat by setpos values + * + * @var array + */ + var $bysetpos=array(); + + /** + * inteval of repeating event (i.e. every 2 weeks, every 6 months) + * + * @var integer + */ + var $interval = 1; + + /** + * debug level (for testing only) + * + * @var integer + */ + var $debug = 0; + + /** + * error string (future use) + * + * @var string + */ + var $error; + + /** + * array of exception dates in Unix Timestamp format (UTC dates) + * + * @var array + */ + var $exdates=array(); + +/** + * Expand recurring rule to a list of dates + * + * @param string $rules iCalendar rules string + * @param integer $startdate start date in Unix Timestamp format + * @param array $exdates array of exception dates + * @param string $tzid timezone of event (using PHP timezones) + */ + function __construct($rules, $startdate, $exdates = array(),$tzid = "UTC"){ + if(strlen($rules) > 0){ + //move exdates to event timezone for comparing with event date + for($i = 0; $i < count($exdates); $i++) + { + $exdates[$i] = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($exdates[$i]),$tzid)); + } + + $rules=str_replace("\'","",$rules); + $this->rules = $rules; + if($startdate == null){ + // if not specified, use start date of beginning of last year + $tdate=getdate(); + $startdate=mktime(0,0,0,1,1,$tdate["year"] - 1); + } + $this->startdate = $startdate; + $this->tzid = $tzid; + $this->exdates = $exdates; + + $rules=explode(";", $rules); + $ruletype = ""; + foreach($rules as $rule){ + $item=explode("=",$rule); + //echo $item[0] . "=" . $item[1] . "
\n"; + switch($item[0]){ + case "FREQ": + switch($item[1]){ + case "YEARLY": + $this->freq="y"; + break; + case "MONTHLY": + $this->freq="m"; + break; + case "WEEKLY": + $this->freq="w"; + break; + case "DAILY": + $this->freq="d"; + break; + case "HOURLY": + $this->freq="h"; + break; + case "MINUTELY": + $this->freq="i"; + break; + case "SECONDLY": + $this->freq="s"; + break; + } + break; + case "INTERVAL": + $this->interval = $item[1]; + break; + case "BYSECOND": + $this->bysecond = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYMINUTE": + $this->byminute = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYHOUR": + $this->byhour = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYDAY": + $this->byday = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYMONTHDAY": + $this->bymonthday = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYMONTH": + $this->bymonth = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYYEAR": + $this->byyear = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "COUNT": + $this->count = intval($item[1]); + $this->repeatmode = "c"; + break; + case "BYSETPOS": + $this->bysetpos = explode(",",$item[1]); + break; + case "UNTIL": + $this->until = ZDateHelper::fromiCaltoUnixDateTime($item[1]); + $this->repeatmode = "u"; + break; + } + } + if(count($this->bysetpos) > 0){ + switch($ruletype){ + case "BYYEAR": + $this->byyear = $this->bySetPos($this->byyear,$this->bysetpos); + break; + case "BYMONTH": + $this->bymonth = $this->bySetPos($this->bymonth,$this->bysetpos); + break; + case "BYMONTHDAY": + $this->bymonthday = $this->bySetPos($this->bymonthday,$this->bysetpos); + break; + case "BYDAY": + $this->byday = $this->bySetPos($this->byday,$this->bysetpos); + break; + case "BYHOUR": + $this->byhour = $this->bySetPos($this->byhour,$this->bysetpos); + break; + case "BYMINUTE": + $this->byminute = $this->bySetPos($this->byminute,$this->bysetpos); + break; + case "BYSECOND": + $this->bysecond = $this->bySetPos($this->bysecond,$this->bysetpos); + break; + } + } + } + } + +/** + * bysetpos rule support + * + * @param array $bytype + * @param array $bysetpos + * + * @return array + */ + function bySetPos($bytype, $bysetpos){ + $result = array(); + for($i=0; $i < count($bysetpos); $i++){ + for($j=0; $j < count($bytype); $j++){ + $result[] = $bysetpos[$i] . $bytype[$j]; + } + } + return $result; + } + +/** + * save error + * + * @param string $msg + */ + function setError($msg){ + $this->error = $msg; + } + +/** + * get error message + * + * @return string error message + */ + function getError(){ + return $this->error; + } + +/** + * set debug level (0: none, 1: minimal, 2: more output) + * + * @param integer $level + * + */ + function setDebug($level) + { + $this->debug = $level; + } + +/** + * display debug message + * + * @param integer $level + * @param string $msg + */ + function debug($level, $msg){ + if($this->debug >= $level) + echo $msg . "
\n"; + } + +/** + * Get repeating dates by year + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byYear($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byYear(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->byyear) > 0){ + foreach($this->byyear as $year){ + $t = getdate($startdate); + $wdate = mktime($t[hours],$t[minutes],$t[seconds],$t[month],$t[mday],$year); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byMonth($wdate, $enddate, $rdates, $tzid); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byMonth($startdate, $enddate, $rdates, $tzid); + self::debug(1,"byYear() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by month + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byMonth($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byMonth(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->bymonth) > 0){ + foreach($this->bymonth as $month){ + $t = getdate($startdate); + $wdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$month,$t["mday"],$t["year"]); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byMonthDay($wdate, $enddate, $rdates, $tzid); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byMonthDay($startdate, $enddate, $rdates, $tzid); + self::debug(1,"byMonth() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by month day + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byMonthDay($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byMonthDay(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate)); + if(count($this->bymonthday) > 0){ + foreach($this->bymonthday as $day){ + $day = intval($day); + $t = getdate($startdate); + $wdate = mktime($t['hours'],$t['minutes'],$t['seconds'],$t['mon'],$day,$t['year']); + self::debug(2,"mktime(" . $t['hours'] . ", " . $t['minutes'] + . ", " . $t['mon'] . ", " . $day . ", " . $t['year'] . ") returned $wdate"); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byDay($wdate, $enddate, $rdates, $tzid); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) { + self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate)); + $count = $this->byDay($startdate, $enddate, $rdates, $tzid); + } + self::debug(1,"byMonthDay() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by day + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byDay($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byDay(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $days = array( + "SU" => 0, + "MO" => 1, + "TU" => 2, + "WE" => 3, + "TH" => 4, + "FR" => 5, + "SA" => 6); + $idays = array( + 0 => "SU", + 1 => "MO", + 2 => "TU", + 3 => "WE", + 4 => "TH", + 5 => "FR", + 6 => "SA"); + + $count = 0; + if(count($this->byday) > 0){ + if(empty($this->byday[0])) + { + $this->byday[0] = $idays[date("w",$startdate)]; + } + foreach($this->byday as $tday){ + $t = getdate($startdate); + $day = substr($tday,strlen($tday) - 2); + if(strlen($day) < 2) + { + // missing start day, use current date for DOW + $day = $idays[date("w",$startdate)]; + } + if(strlen($tday) > 2) { + $imin = 1; + $imax = 5; // max # of occurances in a month + if(strlen($tday) > 2) + $imin = $imax = substr($tday,0,strlen($tday) - 2); + self::debug(2,"imin: $imin, imax: $imax, tday: $tday, day: $day, daynum: {$days[$day]}"); + for($i = $imin; $i <= $imax; $i++){ + $wdate = ZDateHelper::getDateFromDay($startdate,$i-1,$days[$day],$tzid); + self::debug(2,"getDateFromDay(" . ZDateHelper::toSqlDateTime($startdate) + . ",$i,{$days[$day]}) returned " . ZDateHelper::toSqlDateTime($wdate)); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byHour($wdate, $enddate, $rdates); + if($count == 0){ + $rdates[] = $wdate; + $count++; + //break; + } + } + } + } + else { + // day of week version + $startdate_dow = date("w",$startdate); + $datedelta = $days[$day] - $startdate_dow; + self::debug(2, "start_dow: $startdate_dow, datedelta: $datedelta"); + if($datedelta >= 0) + { + $wdate = ZDateHelper::addDate($startdate,0,0,0,0,$datedelta,0,$this->tzid); + self::debug(2, "wdate: " . ZDateHelper::toSqlDateTime($wdate)); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byHour($wdate, $enddate, $rdates); + if($count == 0){ + $rdates[] = $wdate; + $count++; + self::debug(2,"adding date " . ZDateHelper::toSqlDateTime($wdate) ); + } + } + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byHour($startdate, $enddate, $rdates); + self::debug(1,"byDay() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by hour + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byHour($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byHour(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->byhour) > 0){ + foreach($this->byhour as $hour){ + $t = getdate($startdate); + $wdate = mktime($hour,$t["minutes"],$t["seconds"],$t["mon"],$t["mday"],$t["year"]); + self::debug(2,"checking date/time " . ZDateHelper::toSqlDateTime($wdate)); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byMinute($wdate, $enddate, $rdates); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byMinute($startdate, $enddate, $rdates); + self::debug(1,"byHour() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by minute + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byMinute($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byMinute(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->byminute) > 0){ + foreach($this->byminute as $minute){ + $t = getdate($startdate); + $wdate = mktime($t["hours"],$minute,$t["seconds"],$t["mon"],$t["mday"],$t["year"]); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->bySecond($wdate, $enddate, $rdates); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->bySecond($startdate, $enddate, $rdates); + self::debug(1,"byMinute() returned " . $count ); + return $count; + } +/** + * Get repeating dates by second + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function bySecond($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"bySecond(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->bysecond) > 0){ + foreach($this->bysecond as $second){ + $t = getdate($startdate); + $wdate = mktime($t["hours"],$t["minutes"],$second,$t["mon"],$t["mday"],$t["year"]); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $rdates[] = $wdate; + $count++; + } + } + } + self::debug(1,"bySecond() returned " . $count ); + return $count; + } + +/** + * Determine if the loop has reached the end date + * + * @param array $rdates array of repeating dates + * + * @return boolean + */ + private function maxDates($rdates){ + if($this->repeatmode == "c" && count($rdates) >= $this->count) + return true; // exceeded count + else if(count($rdates) > 0 && $this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){ + return true; //past date + } + return false; + } + +/** + * Get array of dates from recurring rule + * + * @param $maxdate integer maximum date to appear in repeating dates in Unix timestamp format + * + * @return array + */ + public function getDates($maxdate = null){ + //$this->debug = 2; + self::debug(1,"getDates()"); + $nextdate = $enddate = $this->startdate; + $rdates = array(); + $done = false; + $eventcount = 0; + $loopcount = 0; + self::debug(2,"freq: " . $this->freq . ", interval: " . $this->interval); + while(!$done){ + self::debug(1,"*** Frequency ({$this->freq}) loop pass $loopcount ***"); + switch($this->freq){ + case "y": + if($eventcount > 0) + { + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,0,$this->interval,$this->tzid); + self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate)); + if(!empty($this->byday)){ + $t = getdate($nextdate); + $nextdate = gmmktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]); + } + self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); + } + $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,0,1); + break; + case "m": + if($eventcount > 0) + { + + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid); + self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate)); + } + if(count($this->byday) > 0) + { + $t = getdate($nextdate); + if($t["mday"] > 28) + { + //check for short months when using month by day, make sure we do not overshoot the counter and skip a month + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid); + $t2 = getdate($nextdate); + if($t2["mday"] < $t["mday"]) + { + // oops, skipped a month, backup to previous month + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$t2["mday"] - $t["mday"],0,$this->tzid); + } + } + $t = getdate($nextdate); + $nextdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]); + } + self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); + $enddate=ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0); + break; + case "w": + if($eventcount == 0) + $nextdate=$nextdate; + else { + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0,$this->tzid); + if(count($this->byday) > 0){ + $dow = date("w", $nextdate); + // move to beginning of week (Sunday) + $bow = 0; + $diff = $bow - $dow; + if($diff > 0) + $diff = $diff - 7; + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$diff,0); + } + self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); + } + $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0); + break; + case "d": + $nextdate=($eventcount==0?$nextdate: + ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval,0,$this->tzid)); + $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,1,0); + break; + } + + $count = $this->byYear($nextdate,$enddate,$rdates,$this->tzid); + $eventcount += $count; + if($maxdate > 0 && $maxdate < $nextdate) + { + array_pop($rdates); + $done = true; + } + else if($count == 0 && !$this->maxDates($rdates)){ + $rdates[] = $nextdate; + $eventcount++; + } + if($this->maxDates($rdates)) + $done = true; + + $year = date("Y", $nextdate); + if($year > _ZAPCAL_MAXYEAR) + { + $done = true; + } + $loopcount++; + if($loopcount > _ZAPCAL_MAXYEAR){ + $done = true; + throw new Exception("Infinite loop detected in getDates()"); + } + } + if($this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){ + // erase last item + array_pop($rdates); + } + $count1 = count($rdates); + $rdates = array_unique($rdates); + $count2 = count($rdates); + $dups = $count1 - $count2; + $excount = 0; + + foreach($this->exdates as $exdate) + { + if($pos = array_search($exdate,$rdates)) + { + array_splice($rdates,$pos,1); + $excount++; + } + } + self::debug(1,"getDates() returned " . count($rdates) . " dates, removing $dups duplicates, $excount exceptions"); + + + if($this->debug >= 2) + { + self::debug(2,"Recurring Dates:"); + foreach($rdates as $rdate) + { + $d = getdate($rdate); + self::debug(2,ZDateHelper::toSQLDateTime($rdate) . " " . $d["wday"] ); + } + self::debug(2,"Exception Dates:"); + foreach($this->exdates as $exdate) + { + self::debug(2, ZDateHelper::toSQLDateTime($exdate)); + } + //exit; + } + + return $rdates; + } +} diff --git a/plugins/zapcal/includes/timezone.php b/plugins/zapcal/includes/timezone.php new file mode 100644 index 00000000..5369f05a --- /dev/null +++ b/plugins/zapcal/includes/timezone.php @@ -0,0 +1,142 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Zap Calendar Time Zone Helper Class + * + * Class to help create timezone section of iCalendar file + * + * @copyright Copyright (C) 2006 - 2016 by Dan Cogliano + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +class ZCTimeZoneHelper { + + /** + * getTZNode creates VTIMEZONE section in an iCalendar file + * + * @param @startyear int start year of date range + * + * @param @endyear int end year of date range + * + * @param $tzid string PHP timezone, use underscore for multiple words (i.e. "New_York" for "New York") + * + * @param $parentnode object iCalendar object where VTIMEZONE will be created + * + * @return object return VTIMEZONE object + */ + static function getTZNode($startyear, $endyear, $tzid, $parentnode) + { + $tzmins = array(); + $tzmaxs = array(); + if(!array_key_exists($tzid,$tzmins) || $tzmins[$tzid] > $startyear) + { + $tzmins[$tzid] = $startyear; + } + if(!array_key_exists($tzid,$tzmaxs) || $tzmaxs[$tzid] < $endyear) + { + $tzmaxs[$tzid] = $endyear; + } + + foreach(array_keys($tzmins) as $tzid) + { + $tmin = $tzmins[$tzid] - 1; + if(array_key_exists($tzid,$tzmaxs)) + { + $tmax = $tzmaxs[$tzid] + 1; + } + else + { + $tmax = $tzmins[$tzid] + 1; + } + $tstart = gmmktime(0,0,0,1,1,$tmin); + $tend = gmmktime(23,59,59,12,31,$tmax); + $tz = new DateTimeZone($tzid); + $transitions = $tz->getTransitions($tstart,$tend); + $tzobj = new ZCiCalNode("VTIMEZONE", $parentnode, true); + $datanode = new ZCiCalDataNode("TZID:" . str_replace("_"," ",$tzid)); + $tzobj->data[$datanode->getName()] = $datanode; + $count = 0; + $lasttransition = null; + if(count($transitions) == 1) + { + // not enough transitions found, probably UTC + // lets add fake transition at end for those systems that need it (i.e. Outlook) + + $t2 = array(); + $t2["isdst"] = $transitions[0]["isdst"]; + $t2["offset"] = $transitions[0]["offset"]; + $t2["ts"] = $tstart; + $t2["abbr"] = $transitions[0]["abbr"]; + $transitions[] = $t2; + } + foreach($transitions as $transition) + { + $count++; + if($count == 1) + { + $lasttransition = $transition; + continue; // skip first item + } + if($transition["isdst"] == 1) + { + $tobj = new ZCiCalNode("DAYLIGHT", $tzobj); + } + else + { + $tobj = new ZCiCalNode("STANDARD", $tzobj); + } + //$tzobj->data[$tobj->getName()] == $tobj; + + // convert timestamp to local time zone + $ts = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($transition["ts"]),$tzid)); + $datanode = new ZCiCalDataNode("DTSTART:".ZDateHelper::toICalDateTime($ts)); + $tobj->data[$datanode->getName()] = $datanode; + //echo $ts . " => " . ZDateHelper::toICalDateTime($ts) . "
\n"; exit; + $toffset = $lasttransition["offset"]; + $thours = intval($toffset/60/60); + $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60; + if($thours < 0) + { + $offset = sprintf("%03d%02d",$thours,$tmins); + } + else + { + $offset = sprintf("+%02d%02d",$thours,$tmins); + } + $datanode = new ZCiCalDataNode("TZOFFSETFROM:".$offset); + $tobj->data[$datanode->getName()] = $datanode; + + $toffset = $transition["offset"]; + $thours = intval($toffset/60/60); + $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60; + if($thours < 0) + { + $offset = sprintf("%03d%02d",$thours,$tmins); + } + else + { + $offset = sprintf("+%02d%02d",$thours,$tmins); + } + $datanode = new ZCiCalDataNode("TZOFFSETTO:".$offset); + $tobj->data[$datanode->getName()] = $datanode; + + $datanode = new ZCiCalDataNode("TZNAME:".$transition["abbr"]); + $tobj->data[$datanode->getName()] = $datanode; + + $lasttransition = $transition; + } + } + return $tzobj; + } +} diff --git a/plugins/zapcal/zapcallib.php b/plugins/zapcal/zapcallib.php new file mode 100644 index 00000000..bbad6f04 --- /dev/null +++ b/plugins/zapcal/zapcallib.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +/** + * used by ZapCalLib + * @var integer + */ +define('_ZAPCAL',1); + +if(!defined('_ZAPCAL_BASE')) +{ + /** + * the base folder of the library + * @var string + */ + define('_ZAPCAL_BASE',__DIR__); +} + +require_once(_ZAPCAL_BASE . '/includes/framework.php'); + diff --git a/post/ticket.php b/post/ticket.php index efc40150..df111860 100644 --- a/post/ticket.php +++ b/post/ticket.php @@ -18,7 +18,7 @@ if (isset($_POST['add_ticket'])) { $contact = intval($_POST['contact']); $subject = sanitizeInput($_POST['subject']); $priority = sanitizeInput($_POST['priority']); - $details = mysqli_real_escape_string($mysqli,$_POST['details']); + $details = mysqli_real_escape_string($mysqli, $_POST['details']); $vendor_ticket_number = sanitizeInput($_POST['vendor_ticket_number']); $vendor_id = intval($_POST['vendor']); $asset_id = intval($_POST['asset']); @@ -27,7 +27,7 @@ if (isset($_POST['add_ticket'])) { // Add the primary contact as the ticket contact if "Use primary contact" is checked if ($use_primary_contact == 1) { - $sql = mysqli_query($mysqli,"SELECT contact_id FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1"); + $sql = mysqli_query($mysqli, "SELECT contact_id FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1"); $row = mysqli_fetch_array($sql); $contact = intval($row['contact_id']); } @@ -41,24 +41,24 @@ if (isset($_POST['add_ticket'])) { //Get the next Ticket Number and add 1 for the new ticket number $ticket_number = $config_ticket_next_number; $new_config_ticket_next_number = $config_ticket_next_number + 1; - + // Sanitize Config Vars from get_settings.php and Session Vars from check_login.php $config_ticket_prefix = sanitizeInput($config_ticket_prefix); $config_ticket_from_name = sanitizeInput($config_ticket_from_name); $config_ticket_from_email = sanitizeInput($config_ticket_from_email); $config_base_url = sanitizeInput($config_base_url); - mysqli_query($mysqli,"UPDATE settings SET config_ticket_next_number = $new_config_ticket_next_number WHERE company_id = 1"); + mysqli_query($mysqli, "UPDATE settings SET config_ticket_next_number = $new_config_ticket_next_number WHERE company_id = 1"); - mysqli_query($mysqli,"INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_billable = '$billable', ticket_status = '$ticket_status', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_vendor_id = $vendor_id, ticket_asset_id = $asset_id, ticket_created_by = $session_user_id, ticket_assigned_to = $assigned_to, ticket_contact_id = $contact, ticket_client_id = $client_id, ticket_invoice_id = 0"); + mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_billable = '$billable', ticket_status = '$ticket_status', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_vendor_id = $vendor_id, ticket_asset_id = $asset_id, ticket_created_by = $session_user_id, ticket_assigned_to = $assigned_to, ticket_contact_id = $contact, ticket_client_id = $client_id, ticket_invoice_id = 0"); $ticket_id = mysqli_insert_id($mysqli); // Add Watchers if (!empty($_POST['watchers'])) { - foreach($_POST['watchers'] as $watcher) { + foreach ($_POST['watchers'] as $watcher) { $watcher_email = sanitizeInput($watcher); - mysqli_query($mysqli,"INSERT INTO ticket_watchers SET watcher_email = '$watcher_email', watcher_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_watchers SET watcher_email = '$watcher_email', watcher_ticket_id = $ticket_id"); } } @@ -66,7 +66,7 @@ if (isset($_POST['add_ticket'])) { if (!empty($config_smtp_host) && $config_ticket_client_general_notifications == 1) { // Get contact/ticket details - $sql = mysqli_query($mysqli,"SELECT contact_name, contact_email, ticket_prefix, ticket_number, ticket_category, ticket_subject, ticket_details, ticket_priority, ticket_status, ticket_created_by, ticket_assigned_to, ticket_client_id FROM tickets + $sql = mysqli_query($mysqli, "SELECT contact_name, contact_email, ticket_prefix, ticket_number, ticket_category, ticket_subject, ticket_details, ticket_priority, ticket_status, ticket_created_by, ticket_assigned_to, ticket_client_id FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN contacts ON ticket_contact_id = contact_id WHERE ticket_id = $ticket_id"); @@ -86,7 +86,7 @@ if (isset($_POST['add_ticket'])) { $ticket_assigned_to = intval($row['ticket_assigned_to']); // Get Company Phone Number - $sql = mysqli_query($mysqli,"SELECT company_name, company_phone FROM companies WHERE company_id = 1"); + $sql = mysqli_query($mysqli, "SELECT company_name, company_phone FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); $company_name = sanitizeInput($row['company_name']); $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'])); @@ -107,7 +107,7 @@ if (isset($_POST['add_ticket'])) { 'recipient' => $contact_email, 'recipient_name' => $contact_name, 'subject' => $subject, - 'body' => $body + 'body' => $body ]; // Also Email all the watchers @@ -117,7 +117,7 @@ if (isset($_POST['add_ticket'])) { $watcher_email = sanitizeInput($row['watcher_email']); // Queue Mail - $data[] = [ + $data[] = [ 'from' => $config_ticket_from_email, 'from_name' => $config_ticket_from_name, 'recipient' => $watcher_email, @@ -125,19 +125,17 @@ if (isset($_POST['add_ticket'])) { 'subject' => $subject, 'body' => $body ]; - } addToMailQueue($mysqli, $data); } } // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Create', log_description = '$session_name created ticket $config_ticket_prefix$ticket_number - $ticket_subject', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Create', log_description = '$session_name created ticket $config_ticket_prefix$ticket_number - $ticket_subject', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "You created Ticket $ticket_subject $config_ticket_prefix$ticket_number"; header("Location: ticket.php?ticket_id=" . $ticket_id); - } if (isset($_POST['edit_ticket'])) { @@ -149,7 +147,7 @@ if (isset($_POST['edit_ticket'])) { $subject = sanitizeInput($_POST['subject']); $billable = intval($_POST['billable']); $priority = sanitizeInput($_POST['priority']); - $details = mysqli_real_escape_string($mysqli,$_POST['details']); + $details = mysqli_real_escape_string($mysqli, $_POST['details']); $vendor_ticket_number = sanitizeInput($_POST['vendor_ticket_number']); $vendor_id = intval($_POST['vendor']); $asset_id = intval($_POST['asset']); @@ -157,15 +155,14 @@ if (isset($_POST['edit_ticket'])) { $client_id = intval($_POST['client_id']); $ticket_number = intval($_POST['ticket_number']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_subject = '$subject', ticket_priority = '$priority', ticket_billable = $billable, ticket_details = '$details', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_contact_id = $contact_id, ticket_vendor_id = $vendor_id, ticket_asset_id = $asset_id WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_subject = '$subject', ticket_priority = '$priority', ticket_billable = $billable, ticket_details = '$details', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_contact_id = $contact_id, ticket_vendor_id = $vendor_id, ticket_asset_id = $asset_id WHERE ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name modified ticket $ticket_number - $subject', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name modified ticket $ticket_number - $subject', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket $ticket_number updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_priority'])) { @@ -176,15 +173,14 @@ if (isset($_POST['edit_ticket_priority'])) { $priority = sanitizeInput($_POST['priority']); $client_id = intval($_POST['client_id']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_priority = '$priority' WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_priority = '$priority' WHERE ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name edited ticket priority', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name edited ticket priority', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket priority updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_contact'])) { @@ -196,15 +192,14 @@ if (isset($_POST['edit_ticket_contact'])) { $client_id = intval($_POST['client_id']); $ticket_number = sanitizeInput($_POST['ticket_number']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_contact_id = $contact_id WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_contact_id = $contact_id WHERE ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name changed contact for ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name changed contact for ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket $ticket_number contact updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['add_ticket_watcher'])) { @@ -216,15 +211,14 @@ if (isset($_POST['add_ticket_watcher'])) { $ticket_number = sanitizeInput($_POST['ticket_number']); $watcher_email = sanitizeInput($_POST['watcher_email']); - mysqli_query($mysqli,"INSERT INTO ticket_watchers SET watcher_email = '$watcher_email', watcher_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_watchers SET watcher_email = '$watcher_email', watcher_ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name added watcher $watcher_email to ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name added watcher $watcher_email to ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "You added $watcher_email as a watcher to Ticket $ticket_number"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_watchers'])) { @@ -239,22 +233,21 @@ if (isset($_POST['edit_ticket_watchers'])) { if (!empty($_POST['watchers'])) { // Remove all watchers first - mysqli_query($mysqli,"DELETE FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id"); + mysqli_query($mysqli, "DELETE FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id"); //Add the Watchers - foreach($_POST['watchers'] as $watcher) { + foreach ($_POST['watchers'] as $watcher) { $watcher_email = sanitizeInput($watcher); - mysqli_query($mysqli,"INSERT INTO ticket_watchers SET watcher_email = '$watcher_email', watcher_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_watchers SET watcher_email = '$watcher_email', watcher_ticket_id = $ticket_id"); } } //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name added watchers to ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name added watchers to ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket $ticket_number watchers updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_GET['delete_ticket_watcher'])) { @@ -263,13 +256,12 @@ if (isset($_GET['delete_ticket_watcher'])) { $watcher_id = intval($_GET['delete_ticket_watcher']); - mysqli_query($mysqli,"DELETE FROM ticket_watchers WHERE watcher_id = $watcher_id"); + mysqli_query($mysqli, "DELETE FROM ticket_watchers WHERE watcher_id = $watcher_id"); $_SESSION['alert_message'] = "You removed a ticket watcher"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_asset'])) { @@ -281,15 +273,14 @@ if (isset($_POST['edit_ticket_asset'])) { $client_id = intval($_POST['client_id']); $ticket_number = sanitizeInput($_POST['ticket_number']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_asset_id = $asset_id WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_asset_id = $asset_id WHERE ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name edited asset for ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name edited asset for ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket $ticket_number asset updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_vendor'])) { @@ -301,15 +292,14 @@ if (isset($_POST['edit_ticket_vendor'])) { $client_id = intval($_POST['client_id']); $ticket_number = sanitizeInput($_POST['ticket_number']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_vendor_id = $vendor_id WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_vendor_id = $vendor_id WHERE ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name edited vendor for ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name edited vendor for ticket $ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket $ticket_number vendor updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_priority'])) { @@ -320,15 +310,14 @@ if (isset($_POST['edit_ticket_priority'])) { $priority = sanitizeInput($_POST['priority']); $client_id = intval($_POST['client_id']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_priority = '$priority' WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_priority = '$priority' WHERE ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name edited ticket priority', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Modify', log_description = '$session_name edited ticket priority', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_message'] = "Ticket priority updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['assign_ticket'])) { @@ -340,7 +329,7 @@ if (isset($_POST['assign_ticket'])) { $ticket_id = intval($_POST['ticket_id']); $assigned_to = intval($_POST['assigned_to']); $ticket_status = sanitizeInput($_POST['ticket_status']); - if($ticket_status == 'Pending-Assignment' && $assigned_to > 0){ + if ($ticket_status == 'Pending-Assignment' && $assigned_to > 0) { $ticket_status = 'Assigned'; } @@ -383,19 +372,19 @@ if (isset($_POST['assign_ticket'])) { } // Update ticket & insert reply - mysqli_query($mysqli,"UPDATE tickets SET ticket_assigned_to = $assigned_to, ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_assigned_to = $assigned_to, ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name reassigned ticket $ticket_prefix$ticket_number - $ticket_subject to $agent_name', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name reassigned ticket $ticket_prefix$ticket_number - $ticket_subject to $agent_name', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); // Notification if ($session_user_id != $assigned_to && $assigned_to != 0) { // App Notification - mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Ticket', notification = 'Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject has been assigned to you by $session_name', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $assigned_to"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = 'Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject has been assigned to you by $session_name', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $assigned_to"); // Email Notification if (!empty($config_smtp_host)) { @@ -422,13 +411,11 @@ if (isset($_POST['assign_ticket'])) { ]; addToMailQueue($mysqli, $data); } - } $_SESSION['alert_message'] = "Ticket $ticket_prefix$ticket_number assigned to $agent_name"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_GET['delete_ticket'])) { @@ -438,7 +425,7 @@ if (isset($_GET['delete_ticket'])) { $ticket_id = intval($_GET['delete_ticket']); // Get Ticket and Client ID for logging and alert message - $sql = mysqli_query($mysqli,"SELECT ticket_prefix, ticket_number, ticket_subject, ticket_status, ticket_client_id FROM tickets WHERE ticket_id = $ticket_id"); + $sql = mysqli_query($mysqli, "SELECT ticket_prefix, ticket_number, ticket_subject, ticket_status, ticket_client_id FROM tickets WHERE ticket_id = $ticket_id"); $row = mysqli_fetch_array($sql); $ticket_prefix = sanitizeInput($row['ticket_prefix']); $ticket_number = sanitizeInput($row['ticket_number']); @@ -447,23 +434,22 @@ if (isset($_GET['delete_ticket'])) { $client_id = intval($row['ticket_client_id']); if ($ticket_status !== 'Closed') { - mysqli_query($mysqli,"DELETE FROM tickets WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "DELETE FROM tickets WHERE ticket_id = $ticket_id"); // Delete all ticket replies - mysqli_query($mysqli,"DELETE FROM ticket_replies WHERE ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "DELETE FROM ticket_replies WHERE ticket_reply_ticket_id = $ticket_id"); // Delete all ticket views - mysqli_query($mysqli,"DELETE FROM ticket_views WHERE view_ticket_id = $ticket_id"); + mysqli_query($mysqli, "DELETE FROM ticket_views WHERE view_ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Delete', log_description = '$session_name deleted ticket $ticket_prefix$ticket_number - $ticket_subject along with all replies', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Delete', log_description = '$session_name deleted ticket $ticket_prefix$ticket_number - $ticket_subject along with all replies', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $_SESSION['alert_type'] = "error"; $_SESSION['alert_message'] = "Ticket $ticket_prefix$ticket_number along with all replies deleted"; header("Location: tickets.php"); } - } if (isset($_POST['bulk_assign_ticket'])) { @@ -476,12 +462,12 @@ if (isset($_POST['bulk_assign_ticket'])) { // Get a Ticket Count $ticket_count = count($_POST['ticket_ids']); - + // Assign Tech to Selected Tickets if (!empty($_POST['ticket_ids'])) { - foreach($_POST['ticket_ids'] as $ticket_id) { + foreach ($_POST['ticket_ids'] as $ticket_id) { $ticket_id = intval($ticket_id); - + $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id"); $row = mysqli_fetch_array($sql); @@ -491,7 +477,7 @@ if (isset($_POST['bulk_assign_ticket'])) { $ticket_subject = sanitizeInput($row['ticket_subject']); $client_id = intval($row['ticket_client_id']); - if($ticket_status == 'Pending-Assignment' && $assign_to > 0){ + if ($ticket_status == 'Pending-Assignment' && $assign_to > 0) { $ticket_status = 'Assigned'; } @@ -518,22 +504,21 @@ if (isset($_POST['bulk_assign_ticket'])) { } // Update ticket & insert reply - mysqli_query($mysqli,"UPDATE tickets SET ticket_assigned_to = $assign_to, ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); - - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_assigned_to = $assign_to, ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); + + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name reassigned ticket $ticket_prefix$ticket_number - $ticket_subject to $agent_name', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name reassigned ticket $ticket_prefix$ticket_number - $ticket_subject to $agent_name', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); $tickets_assigned_body .= "$ticket_prefix$ticket_number - $ticket_subject
"; - } // End For Each Ticket ID Loop // Notification if ($session_user_id != $assign_to && $assign_to != 0) { // App Notification - mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Ticket', notification = '$ticket_count Tickets have been assigned to you by $session_name', notification_action = 'tickets.php?status=Open&assigned=$assign_to', notification_client_id = $client_id, notification_user_id = $assign_to"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = '$ticket_count Tickets have been assigned to you by $session_name', notification_action = 'tickets.php?status=Open&assigned=$assign_to', notification_client_id = $client_id, notification_user_id = $assign_to"); // Agent Email Notification if (!empty($config_smtp_host)) { @@ -560,14 +545,12 @@ if (isset($_POST['bulk_assign_ticket'])) { ]; addToMailQueue($mysqli, $data); } - } } $_SESSION['alert_message'] = "You assigned $ticket_count Tickets to $agent_name"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['bulk_edit_ticket_priority'])) { @@ -580,12 +563,12 @@ if (isset($_POST['bulk_edit_ticket_priority'])) { // Get a Ticket Count $ticket_count = count($_POST['ticket_ids']); - + // Assign Tech to Selected Tickets if (!empty($_POST['ticket_ids'])) { - foreach($_POST['ticket_ids'] as $ticket_id) { + foreach ($_POST['ticket_ids'] as $ticket_id) { $ticket_id = intval($ticket_id); - + $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id"); $row = mysqli_fetch_array($sql); @@ -597,20 +580,18 @@ if (isset($_POST['bulk_edit_ticket_priority'])) { $client_id = intval($row['ticket_client_id']); // Update ticket & insert reply - mysqli_query($mysqli,"UPDATE tickets SET ticket_priority = '$priority' WHERE ticket_id = $ticket_id"); - - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = '$session_name updated the priority from $current_ticket_priority to $priority', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_priority = '$priority' WHERE ticket_id = $ticket_id"); + + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$session_name updated the priority from $current_ticket_priority to $priority', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name updated the priority on ticket $ticket_prefix$ticket_number - $ticket_subject from $current_ticket_priority to $priority', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); - + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Edit', log_description = '$session_name updated the priority on ticket $ticket_prefix$ticket_number - $ticket_subject from $current_ticket_priority to $priority', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); } // End For Each Ticket ID Loop } $_SESSION['alert_message'] = "You updated the priority for $ticket_count Tickets to $priority"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['bulk_close_tickets'])) { @@ -621,7 +602,7 @@ if (isset($_POST['bulk_close_tickets'])) { // POST variables $details = mysqli_escape_string($mysqli, $_POST['bulk_details']); $private_note = intval($_POST['bulk_private_note']); - if($private_note == 1){ + if ($private_note == 1) { $ticket_reply_type = 'Internal'; } else { $ticket_reply_type = 'Public'; @@ -629,12 +610,12 @@ if (isset($_POST['bulk_close_tickets'])) { // Get a Ticket Count $ticket_count = count($_POST['ticket_ids']); - + // Assign Tech to Selected Tickets if (!empty($_POST['ticket_ids'])) { - foreach($_POST['ticket_ids'] as $ticket_id) { + foreach ($_POST['ticket_ids'] as $ticket_id) { $ticket_id = intval($ticket_id); - + $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id"); $row = mysqli_fetch_array($sql); @@ -646,18 +627,18 @@ if (isset($_POST['bulk_close_tickets'])) { $client_id = intval($row['ticket_client_id']); // Update ticket & insert reply - mysqli_query($mysqli,"UPDATE tickets SET ticket_status = 'Closed' WHERE ticket_id = $ticket_id"); - - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = '$details', ticket_reply_type = '$ticket_reply_type', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 'Closed' WHERE ticket_id = $ticket_id"); + + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$details', ticket_reply_type = '$ticket_reply_type', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Close', log_description = '$session_name closed $ticket_prefix$ticket_number - $ticket_subject in a bulk action', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Close', log_description = '$session_name closed $ticket_prefix$ticket_number - $ticket_subject in a bulk action', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_id"); // Client notification email if (!empty($config_smtp_host) && $config_ticket_client_general_notifications == 1 && $private_note == 0) { // Get Contact details - $ticket_sql = mysqli_query($mysqli,"SELECT contact_name, contact_email FROM tickets + $ticket_sql = mysqli_query($mysqli, "SELECT contact_name, contact_email FROM tickets LEFT JOIN contacts ON ticket_contact_id = contact_id WHERE ticket_id = $ticket_id "); @@ -672,7 +653,7 @@ if (isset($_POST['bulk_close_tickets'])) { $base_url = sanitizeInput($config_base_url); // Get Company Info - $sql = mysqli_query($mysqli,"SELECT company_name, company_phone FROM companies WHERE company_id = 1"); + $sql = mysqli_query($mysqli, "SELECT company_name, company_phone FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); $company_name = sanitizeInput($row['company_name']); $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'])); @@ -722,7 +703,6 @@ if (isset($_POST['bulk_close_tickets'])) { $_SESSION['alert_message'] = "You closed $ticket_count Tickets"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['bulk_ticket_reply'])) { @@ -734,7 +714,7 @@ if (isset($_POST['bulk_ticket_reply'])) { $ticket_reply = mysqli_escape_string($mysqli, $_POST['bulk_reply_details']); $ticket_status = sanitizeInput($_POST['bulk_status']); $private_note = intval($_POST['bulk_private_reply']); - if($private_note == 1){ + if ($private_note == 1) { $ticket_reply_type = 'Internal'; } else { $ticket_reply_type = 'Public'; @@ -742,10 +722,10 @@ if (isset($_POST['bulk_ticket_reply'])) { // Get a Ticket Count $ticket_count = count($_POST['ticket_ids']); - + // Loop Through Tickets and Add Reply along with Email notifications if (!empty($_POST['ticket_ids'])) { - foreach($_POST['ticket_ids'] as $ticket_id) { + foreach ($_POST['ticket_ids'] as $ticket_id) { $ticket_id = intval($ticket_id); $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id"); @@ -758,18 +738,20 @@ if (isset($_POST['bulk_ticket_reply'])) { $client_id = intval($row['ticket_client_id']); // Add reply - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_time_worked = '00:01:00', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_time_worked = '00:01:00', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); $ticket_reply_id = mysqli_insert_id($mysqli); // Update Ticket Status - mysqli_query($mysqli,"UPDATE tickets SET ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Create', log_description = '$session_name replied to ticket $ticket_prefix$ticket_number - $ticket_subject and was a $ticket_reply_type reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Create', log_description = '$session_name replied to ticket $ticket_prefix$ticket_number - $ticket_subject and was a $ticket_reply_type reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); // Get Contact Details - $sql = mysqli_query($mysqli,"SELECT contact_name, contact_email, ticket_created_by, ticket_assigned_to + $sql = mysqli_query( + $mysqli, + "SELECT contact_name, contact_email, ticket_created_by, ticket_assigned_to FROM tickets LEFT JOIN contacts ON ticket_contact_id = contact_id WHERE ticket_id = $ticket_id" @@ -787,7 +769,7 @@ if (isset($_POST['bulk_ticket_reply'])) { $from_email = sanitizeInput($config_ticket_from_email); $base_url = sanitizeInput($config_base_url); - $sql = mysqli_query($mysqli,"SELECT company_name, company_phone FROM companies WHERE company_id = 1"); + $sql = mysqli_query($mysqli, "SELECT company_name, company_phone FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); $company_name = sanitizeInput($row['company_name']); $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'])); @@ -828,7 +810,6 @@ if (isset($_POST['bulk_ticket_reply'])) { 'subject' => $subject, 'body' => $body ]; - } } addToMailQueue($mysqli, $data); @@ -837,15 +818,14 @@ if (isset($_POST['bulk_ticket_reply'])) { // Notification for assigned ticket user if ($session_user_id != $ticket_assigned_to && $ticket_assigned_to != 0) { - mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that is assigned to you', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_assigned_to"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that is assigned to you', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_assigned_to"); } // Notification for user that opened the ticket if ($session_user_id != $ticket_created_by && $ticket_created_by != 0) { - mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that you opened', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_created_by"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that you opened', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_created_by"); } - } // End Ticket Lopp } @@ -853,7 +833,6 @@ if (isset($_POST['bulk_ticket_reply'])) { $_SESSION['alert_message'] = "You updated $ticket_count tickets"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['add_ticket_reply'])) { @@ -861,7 +840,7 @@ if (isset($_POST['add_ticket_reply'])) { validateTechRole(); $ticket_id = intval($_POST['ticket_id']); - $ticket_reply = mysqli_real_escape_string($mysqli,$_POST['ticket_reply']); + $ticket_reply = mysqli_real_escape_string($mysqli, $_POST['ticket_reply']); $ticket_status = sanitizeInput($_POST['status']); // Handle the time inputs for hours, minutes, and seconds $hours = intval($_POST['hours']); @@ -883,19 +862,19 @@ if (isset($_POST['add_ticket_reply'])) { } // Add reply - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_time_worked = '$ticket_reply_time_worked', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_time_worked = '$ticket_reply_time_worked', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); $ticket_reply_id = mysqli_insert_id($mysqli); // Update Ticket Last Response Field - mysqli_query($mysqli,"UPDATE tickets SET ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_status = '$ticket_status' WHERE ticket_id = $ticket_id"); if ($ticket_status == 'Closed') { - mysqli_query($mysqli,"UPDATE tickets SET ticket_closed_at = NOW() WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_closed_at = NOW() WHERE ticket_id = $ticket_id"); } // Get Ticket Details - $ticket_sql = mysqli_query($mysqli,"SELECT contact_name, contact_email, ticket_prefix, ticket_number, ticket_subject, ticket_client_id, ticket_created_by, ticket_assigned_to + $ticket_sql = mysqli_query($mysqli, "SELECT contact_name, contact_email, ticket_prefix, ticket_number, ticket_subject, ticket_client_id, ticket_created_by, ticket_assigned_to FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN contacts ON ticket_contact_id = contact_id @@ -918,7 +897,7 @@ if (isset($_POST['add_ticket_reply'])) { $config_ticket_from_email = sanitizeInput($config_ticket_from_email); $config_base_url = sanitizeInput($config_base_url); - $sql = mysqli_query($mysqli,"SELECT company_name, company_phone FROM companies WHERE company_id = 1"); + $sql = mysqli_query($mysqli, "SELECT company_name, company_phone FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); $company_name = sanitizeInput($row['company_name']); $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'])); @@ -933,15 +912,12 @@ if (isset($_POST['add_ticket_reply'])) { if ($ticket_status == 'Closed') { $subject = "Ticket closed - [$ticket_prefix$ticket_number] - $ticket_subject | (do not reply)"; $body = "Hello $contact_name,

Your ticket regarding $ticket_subject has been closed.

--------------------------------
$ticket_reply
--------------------------------

We hope the issue was resolved to your satisfaction. If you need further assistance, please raise a new ticket using the below details. Please do not reply to this email.

Ticket: $ticket_prefix$ticket_number
Subject: $ticket_subject
Portal: https://$config_base_url/portal/ticket.php?id=$ticket_id

--
$company_name - Support
$config_ticket_from_email
$company_phone"; - } elseif ($ticket_status == 'Auto Close') { $subject = "Ticket update - [$ticket_prefix$ticket_number] - $ticket_subject | (pending closure)"; $body = "##- Please type your reply above this line -##

Hello $contact_name,

Your ticket regarding $ticket_subject has been updated and is pending closure.

--------------------------------
$ticket_reply
--------------------------------

If your issue is resolved, you can ignore this email. If you need further assistance, please respond!

Ticket: $ticket_prefix$ticket_number
Subject: $ticket_subject
Status: $ticket_status
Portal: https://$config_base_url/portal/ticket.php?id=$ticket_id

--
$company_name - Support
$config_ticket_from_email
$company_phone"; - } else { $subject = "Ticket update - [$ticket_prefix$ticket_number] - $ticket_subject"; $body = "##- Please type your reply above this line -##

Hello $contact_name,

Your ticket regarding $ticket_subject has been updated.

--------------------------------
$ticket_reply
--------------------------------

Ticket: $ticket_prefix$ticket_number
Subject: $ticket_subject
Status: $ticket_status
Portal: https://$config_base_url/portal/ticket.php?id=$ticket_id

--
$company_name - Support
$config_ticket_from_email
$company_phone"; - } $data = []; @@ -972,7 +948,6 @@ if (isset($_POST['add_ticket_reply'])) { 'subject' => $subject, 'body' => $body ]; - } addToMailQueue($mysqli, $data); } @@ -982,22 +957,21 @@ if (isset($_POST['add_ticket_reply'])) { // Notification for assigned ticket user if ($session_user_id != $ticket_assigned_to && $ticket_assigned_to != 0) { - mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that is assigned to you', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_assigned_to"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that is assigned to you', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_assigned_to"); } // Notification for user that opened the ticket if ($session_user_id != $ticket_created_by && $ticket_created_by != 0) { - mysqli_query($mysqli,"INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that you opened', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_created_by"); + mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Ticket', notification = '$session_name updated Ticket $ticket_prefix$ticket_number - Subject: $ticket_subject that you opened', notification_action = 'ticket.php?ticket_id=$ticket_id', notification_client_id = $client_id, notification_user_id = $ticket_created_by"); } // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Create', log_description = '$session_name replied to ticket $ticket_prefix$ticket_number - $ticket_subject and was a $ticket_reply_type reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Create', log_description = '$session_name replied to ticket $ticket_prefix$ticket_number - $ticket_subject and was a $ticket_reply_type reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); $_SESSION['alert_message'] = "Ticket $ticket_prefix$ticket_number has been updated with your reply and was $ticket_reply_type"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_ticket_reply'])) { @@ -1005,20 +979,19 @@ if (isset($_POST['edit_ticket_reply'])) { validateTechRole(); $ticket_reply_id = intval($_POST['ticket_reply_id']); - $ticket_reply = mysqli_real_escape_string($mysqli,$_POST['ticket_reply']); + $ticket_reply = mysqli_real_escape_string($mysqli, $_POST['ticket_reply']); $ticket_reply_time_worked = sanitizeInput($_POST['time']); $client_id = intval($_POST['client_id']); - mysqli_query($mysqli,"UPDATE ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_time_worked = '$ticket_reply_time_worked' WHERE ticket_reply_id = $ticket_reply_id AND ticket_reply_type != 'Client'") or die(mysqli_error($mysqli)); + mysqli_query($mysqli, "UPDATE ticket_replies SET ticket_reply = '$ticket_reply', ticket_reply_time_worked = '$ticket_reply_time_worked' WHERE ticket_reply_id = $ticket_reply_id AND ticket_reply_type != 'Client'") or die(mysqli_error($mysqli)); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Modify', log_description = '$session_name modified ticket reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Modify', log_description = '$session_name modified ticket reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); $_SESSION['alert_message'] = "Ticket reply updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_GET['archive_ticket_reply'])) { @@ -1027,16 +1000,15 @@ if (isset($_GET['archive_ticket_reply'])) { $ticket_reply_id = intval($_GET['archive_ticket_reply']); - mysqli_query($mysqli,"UPDATE ticket_replies SET ticket_reply_archived_at = NOW() WHERE ticket_reply_id = $ticket_reply_id"); + mysqli_query($mysqli, "UPDATE ticket_replies SET ticket_reply_archived_at = NOW() WHERE ticket_reply_id = $ticket_reply_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Archive', log_description = '$session_name arhived ticket reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket Reply', log_action = 'Archive', log_description = '$session_name arhived ticket reply', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id, log_entity_id = $ticket_reply_id"); $_SESSION['alert_type'] = "error"; $_SESSION['alert_message'] = "Ticket reply archived"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['merge_ticket'])) { @@ -1078,19 +1050,18 @@ if (isset($_POST['merge_ticket'])) { } //Update current ticket - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = 'Ticket $ticket_prefix$ticket_number merged into $ticket_prefix$merge_into_ticket_number. Comment: $merge_comment', ticket_reply_time_worked = '00:01:00', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id") or die(mysqli_error($mysqli)); - mysqli_query($mysqli,"UPDATE tickets SET ticket_status = 'Closed', ticket_closed_at = NOW() WHERE ticket_id = $ticket_id") or die(mysqli_error($mysqli)); + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Ticket $ticket_prefix$ticket_number merged into $ticket_prefix$merge_into_ticket_number. Comment: $merge_comment', ticket_reply_time_worked = '00:01:00', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id") or die(mysqli_error($mysqli)); + mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 'Closed', ticket_closed_at = NOW() WHERE ticket_id = $ticket_id") or die(mysqli_error($mysqli)); //Update new ticket - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = 'Ticket $ticket_prefix$ticket_number was merged into this ticket with comment: $merge_comment.
$ticket_subject
$ticket_details', ticket_reply_time_worked = '00:01:00', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $merge_into_ticket_id") or die(mysqli_error($mysqli)); + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Ticket $ticket_prefix$ticket_number was merged into this ticket with comment: $merge_comment.
$ticket_subject
$ticket_details', ticket_reply_time_worked = '00:01:00', ticket_reply_type = '$ticket_reply_type', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $merge_into_ticket_id") or die(mysqli_error($mysqli)); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Merged', log_description = 'Merged ticket $ticket_prefix$ticket_number into $ticket_prefix$merge_into_ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Merged', log_description = 'Merged ticket $ticket_prefix$ticket_number into $ticket_prefix$merge_into_ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id"); $_SESSION['alert_message'] = "Ticket merged into $ticket_prefix$merge_into_ticket_number"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['change_client_ticket'])) { @@ -1113,7 +1084,6 @@ if (isset($_POST['change_client_ticket'])) { $_SESSION['alert_message'] = "Ticket client updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_GET['close_ticket'])) { @@ -1122,18 +1092,18 @@ if (isset($_GET['close_ticket'])) { $ticket_id = intval($_GET['close_ticket']); - mysqli_query($mysqli,"UPDATE tickets SET ticket_status = 'Closed', ticket_closed_at = NOW(), ticket_closed_by = $session_user_id WHERE ticket_id = $ticket_id") or die(mysqli_error($mysqli)); + mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 'Closed', ticket_closed_at = NOW(), ticket_closed_by = $session_user_id WHERE ticket_id = $ticket_id") or die(mysqli_error($mysqli)); - mysqli_query($mysqli,"INSERT INTO ticket_replies SET ticket_reply = 'Ticket closed.', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Ticket closed.', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); //Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Ticket', log_action = 'Closed', log_description = 'Ticket ID $ticket_id Closed', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id, log_entity_id = $ticket_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Ticket', log_action = 'Closed', log_description = 'Ticket ID $ticket_id Closed', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id, log_entity_id = $ticket_id"); // Client notification email if (!empty($config_smtp_host) && $config_ticket_client_general_notifications == 1) { // Get details - $ticket_sql = mysqli_query($mysqli,"SELECT contact_name, contact_email, ticket_prefix, ticket_number, ticket_subject FROM tickets + $ticket_sql = mysqli_query($mysqli, "SELECT contact_name, contact_email, ticket_prefix, ticket_number, ticket_subject FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN contacts ON ticket_contact_id = contact_id WHERE ticket_id = $ticket_id @@ -1156,7 +1126,7 @@ if (isset($_GET['close_ticket'])) { $config_base_url = sanitizeInput($config_base_url); // Get Company Info - $sql = mysqli_query($mysqli,"SELECT company_name, company_phone FROM companies WHERE company_id = 1"); + $sql = mysqli_query($mysqli, "SELECT company_name, company_phone FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); $company_name = sanitizeInput($row['company_name']); $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'])); @@ -1199,13 +1169,11 @@ if (isset($_GET['close_ticket'])) { } addToMailQueue($mysqli, $data); } - } //End Mail IF $_SESSION['alert_message'] = "Ticket Closed, this cannot not be reopened but you may start another one"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['add_invoice_from_ticket'])) { @@ -1216,7 +1184,9 @@ if (isset($_POST['add_invoice_from_ticket'])) { $category = intval($_POST['category']); $scope = sanitizeInput($_POST['scope']); - $sql = mysqli_query($mysqli, "SELECT * FROM tickets + $sql = mysqli_query( + $mysqli, + "SELECT * FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN contacts ON ticket_contact_id = contact_id LEFT JOIN assets ON ticket_asset_id = asset_id @@ -1252,12 +1222,12 @@ if (isset($_POST['add_invoice_from_ticket'])) { //Get the last Invoice Number and add 1 for the new invoice number $invoice_number = $config_invoice_next_number; $new_config_invoice_next_number = $config_invoice_next_number + 1; - mysqli_query($mysqli,"UPDATE settings SET config_invoice_next_number = $new_config_invoice_next_number WHERE company_id = 1"); + mysqli_query($mysqli, "UPDATE settings SET config_invoice_next_number = $new_config_invoice_next_number WHERE company_id = 1"); //Generate a unique URL key for clients to access $url_key = randomString(156); - mysqli_query($mysqli,"INSERT INTO invoices SET invoice_prefix = '$config_invoice_prefix', invoice_number = $invoice_number, invoice_scope = '$scope', invoice_date = '$date', invoice_due = DATE_ADD('$date', INTERVAL $client_net_terms day), invoice_currency_code = '$session_company_currency', invoice_category_id = $category, invoice_status = 'Draft', invoice_url_key = '$url_key', invoice_client_id = $client_id"); + mysqli_query($mysqli, "INSERT INTO invoices SET invoice_prefix = '$config_invoice_prefix', invoice_number = $invoice_number, invoice_scope = '$scope', invoice_date = '$date', invoice_due = DATE_ADD('$date', INTERVAL $client_net_terms day), invoice_currency_code = '$session_company_currency', invoice_category_id = $category, invoice_status = 'Draft', invoice_url_key = '$url_key', invoice_client_id = $client_id"); $invoice_id = mysqli_insert_id($mysqli); } @@ -1271,7 +1241,7 @@ if (isset($_POST['add_invoice_from_ticket'])) { $subtotal = $price * $qty; if ($tax_id > 0) { - $sql = mysqli_query($mysqli,"SELECT * FROM taxes WHERE tax_id = $tax_id"); + $sql = mysqli_query($mysqli, "SELECT * FROM taxes WHERE tax_id = $tax_id"); $row = mysqli_fetch_array($sql); $tax_percent = floatval($row['tax_percent']); $tax_amount = $subtotal * $tax_percent / 100; @@ -1281,25 +1251,25 @@ if (isset($_POST['add_invoice_from_ticket'])) { $total = $subtotal + $tax_amount; - mysqli_query($mysqli,"INSERT INTO invoice_items SET item_name = '$item_name', item_description = '$item_description', item_quantity = $qty, item_price = $price, item_subtotal = $subtotal, item_tax = $tax_amount, item_total = $total, item_order = 1, item_tax_id = $tax_id, item_invoice_id = $invoice_id"); + mysqli_query($mysqli, "INSERT INTO invoice_items SET item_name = '$item_name', item_description = '$item_description', item_quantity = $qty, item_price = $price, item_subtotal = $subtotal, item_tax = $tax_amount, item_total = $total, item_order = 1, item_tax_id = $tax_id, item_invoice_id = $invoice_id"); //Update Invoice Balances - $sql = mysqli_query($mysqli,"SELECT * FROM invoices WHERE invoice_id = $invoice_id"); + $sql = mysqli_query($mysqli, "SELECT * FROM invoices WHERE invoice_id = $invoice_id"); $row = mysqli_fetch_array($sql); $new_invoice_amount = floatval($row['invoice_amount']) + $total; - mysqli_query($mysqli,"UPDATE invoices SET invoice_amount = $new_invoice_amount WHERE invoice_id = $invoice_id"); + mysqli_query($mysqli, "UPDATE invoices SET invoice_amount = $new_invoice_amount WHERE invoice_id = $invoice_id"); - mysqli_query($mysqli,"INSERT INTO history SET history_status = 'Draft', history_description = 'Invoice created from Ticket $ticket_prefix$ticket_number', history_invoice_id = $invoice_id"); + mysqli_query($mysqli, "INSERT INTO history SET history_status = 'Draft', history_description = 'Invoice created from Ticket $ticket_prefix$ticket_number', history_invoice_id = $invoice_id"); // Add internal note to ticket, and link to invoice in database mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Created invoice $config_invoice_prefix$invoice_number for this ticket.', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:01:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); mysqli_query($mysqli, "UPDATE tickets SET ticket_invoice_id = $invoice_id WHERE ticket_id = $ticket_id"); // Logging - mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Invoice', log_action = 'Create', log_description = '$config_invoice_prefix$invoice_number created from Ticket $ticket_prefix$ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id"); + mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Invoice', log_action = 'Create', log_description = '$config_invoice_prefix$invoice_number created from Ticket $ticket_prefix$ticket_number', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id"); $_SESSION['alert_message'] = "Invoice created from ticket"; @@ -1313,12 +1283,12 @@ if (isset($_POST['export_client_tickets_csv'])) { $client_id = intval($_POST['client_id']); //get records from database - $sql = mysqli_query($mysqli,"SELECT * FROM clients WHERE client_id = $client_id"); + $sql = mysqli_query($mysqli, "SELECT * FROM clients WHERE client_id = $client_id"); $row = mysqli_fetch_array($sql); $client_name = $row['client_name']; - $sql = mysqli_query($mysqli,"SELECT * FROM tickets WHERE ticket_client_id = $client_id ORDER BY ticket_number ASC"); + $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_client_id = $client_id ORDER BY ticket_number ASC"); if ($sql->num_rows > 0) { $delimiter = ","; $filename = $client_name . "-Tickets-" . date('Y-m-d') . ".csv"; @@ -1331,7 +1301,7 @@ if (isset($_POST['export_client_tickets_csv'])) { fputcsv($f, $fields, $delimiter); //output each row of the data, format line as csv and write to file pointer - while($row = $sql->fetch_assoc()) { + while ($row = $sql->fetch_assoc()) { $lineData = array($row['ticket_number'], $row['ticket_priority'], $row['ticket_status'], $row['ticket_subject'], $row['ticket_created_at'], $row['ticket_closed_at']); fputcsv($f, $lineData, $delimiter); } @@ -1347,7 +1317,6 @@ if (isset($_POST['export_client_tickets_csv'])) { fpassthru($f); } exit; - } if (isset($_POST['add_scheduled_ticket'])) { @@ -1360,7 +1329,7 @@ if (isset($_POST['add_scheduled_ticket'])) { // If no contact is selected automatically choose the primary contact for the client if ($client_id > 0 && $contact_id == 0) { - $sql = mysqli_query($mysqli,"SELECT contact_id FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1"); + $sql = mysqli_query($mysqli, "SELECT contact_id FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1"); $row = mysqli_fetch_array($sql); $contact_id = intval($row['contact_id']); } @@ -1376,7 +1345,6 @@ if (isset($_POST['add_scheduled_ticket'])) { $_SESSION['alert_message'] = "Scheduled ticket $subject - $frequency created"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_POST['edit_scheduled_ticket'])) { @@ -1390,7 +1358,7 @@ if (isset($_POST['edit_scheduled_ticket'])) { // If no contact is selected automatically choose the primary contact for the client if ($client_id > 0 && $contact_id == 0) { - $sql = mysqli_query($mysqli,"SELECT contact_id FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1"); + $sql = mysqli_query($mysqli, "SELECT contact_id FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1"); $row = mysqli_fetch_array($sql); $contact_id = intval($row['contact_id']); } @@ -1404,7 +1372,6 @@ if (isset($_POST['edit_scheduled_ticket'])) { $_SESSION['alert_message'] = "Scheduled ticket $subject - $frequency updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); - } if (isset($_GET['delete_scheduled_ticket'])) { @@ -1455,21 +1422,21 @@ if (isset($_POST['bulk_delete_scheduled_tickets'])) { mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Scheduled Ticket', log_action = 'Delete', log_description = '$session_name bulk deleted $count scheduled tickets', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_user_id = $session_user_id"); $_SESSION['alert_message'] = "Deleted $count scheduled ticket(s)"; - } header("Location: " . $_SERVER["HTTP_REFERER"]); } -if(isset($_POST['set_billable_status'])) { +if (isset($_POST['set_billable_status'])) { validateTechRole(); $ticket_id = intval($_POST['ticket_id']); $billable_status = sanitizeInput($_POST['billable_status']); - mysqli_query($mysqli, - "UPDATE tickets SET + mysqli_query( + $mysqli, + "UPDATE tickets SET ticket_billable = '$billable_status' WHERE ticket_id = $ticket_id" ); @@ -1485,10 +1452,182 @@ if(isset($_POST['set_billable_status'])) { log_user_agent = '$session_user_agent', log_user_id = $session_user_id, log_entity_id = $ticket_id" - ); + ); $_SESSION['alert_message'] = "Ticket billable status updated"; header("Location: " . $_SERVER["HTTP_REFERER"]); +} -} \ No newline at end of file +if (isset($_POST['edit_ticket_schedule'])) { + + + validateTechRole(); + + $ticket_id = intval($_POST['ticket_id']); + $onsite = intval($_POST['onsite']); + $schedule = sanitizeInput($_POST['scheduled_date_time']); + $ticket_link = "ticket.php?ticket_id=$ticket_id"; + $full_ticket_url = "https://$config_base_url/portal/ticket.php?ticket_id=$ticket_id"; + $ticket_link_html = "$ticket_link"; + + mysqli_query( + $mysqli, + "UPDATE tickets SET + ticket_schedule = '$schedule', + ticket_status = 'Scheduled' + WHERE ticket_id = $ticket_id" + ); + + + // Check for other conflicting scheduled items based on 2 hr window + //TODO make this configurable + $start = date('Y-m-d H:i:s', strtotime($schedule) - 7200); + $end = date('Y-m-d H:i:s', strtotime($schedule) + 7200); + $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_schedule BETWEEN '$start' AND '$end' AND ticket_id != $ticket_id AND ticket_status = 'Scheduled'"); + if (mysqli_num_rows($sql) > 0) { + $conflicting_tickets = []; + while ($row = mysqli_fetch_array($sql)) { + $conflicting_tickets[] = $row['ticket_id'] . " - " . $row['ticket_subject'] . " @ " . $row['ticket_schedule']; + } + } + $sql = mysqli_query($mysqli, "SELECT * FROM tickets + LEFT JOIN clients ON ticket_client_id = client_id + LEFT JOIN contacts ON ticket_contact_id = contact_id + LEFT JOIN locations on contact_location_id = location_id + LEFT JOIN users ON ticket_assigned_to = user_id + WHERE ticket_id = $ticket_id + "); + + + $row = mysqli_fetch_array($sql); + + $client_id = intval($row['ticket_client_id']); + $client_name = sanitizeInput($row['client_name']); + $ticket_details = sanitizeInput($row['ticket_details']); + $contact_name = sanitizeInput($row['contact_name']); + $contact_email = sanitizeInput($row['contact_email']); + $ticket_prefix = sanitizeInput($row['ticket_prefix']); + $ticket_number = intval($row['ticket_number']); + $ticket_subject = sanitizeInput($row['ticket_subject']); + $user_name = sanitizeInput($row['user_name']); + $user_email = sanitizeInput($row['user_email']); + $cal_subject = $ticket_number . ": " . $client_name . " - " . $ticket_subject; + $ticket_details_truncated = substr($ticket_details, 0, 100); + $cal_description = $ticket_details_truncated . " - " . $full_ticket_url; + $cal_location = sanitizeInput($row["location_address"]); + $email_datetime = date('l, F j, Y \a\t g:ia', strtotime($schedule)); + + /// Create iCal event + $cal_str = createiCalStr($schedule, $cal_subject, $cal_description, $cal_location); + + $data = [ + [ //Client Contact Email + 'from' => $config_ticket_from_email, + 'from_name' => $config_ticket_from_name, + 'recipient' => $contact_email, + 'recipient_name' => $contact_name, + 'subject' => "Ticket Scheduled - [$ticket_prefix$ticket_number] - $ticket_subject", + 'body' => mysqli_escape_string($mysqli, "
+ Hello, $contact_name +
+ Your ticket regarding $ticket_subject has been scheduled for $email_datetime. +

+ Access your ticket here +

+ Please do not reply to this email. +

+ Ticket: $ticket_prefix$ticket_number
+ Subject: $ticket_subject
+

+ +
+ This is an automated message. Please do not reply directly to this email. +
"), + 'cal_str' => $cal_str + ], + [ + // User Email + 'from' => $config_ticket_from_email, + 'from_name' => $config_ticket_from_name, + 'recipient' => $user_email, + 'recipient_name' => $user_name, + 'subject' => "Ticket Scheduled - [$ticket_prefix$ticket_number] - $ticket_subject", + 'body' => "Hello, " . $user_name . "

The ticket regarding $ticket_subject has been scheduled for $email_datetime.

--------------------------------
$ticket_link
--------------------------------

Please do not reply to this email.

Ticket: $ticket_prefix$ticket_number
Subject: $ticket_subject
Portal: https://$config_base_url/ticket.php?id=$ticket_id

~
$session_company_name
Support Department
$config_ticket_from_email", + 'cal_str' => $cal_str + ] + ]; + + //Send all watchers an email + $sql_watchers = mysqli_query($mysqli, "SELECT watcher_email FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id"); + + while ($row = mysqli_fetch_array($sql_watchers)) { + $watcher_email = sanitizeInput($row['watcher_email']); + $data[] = [ + 'from' => $config_ticket_from_email, + 'from_name' => $config_ticket_from_name, + 'recipient' => $watcher_email, + 'recipient_name' => $watcher_email, + 'subject' => "Ticket Scheduled - [$ticket_prefix$ticket_number] - $ticket_subject", + 'body' => mysqli_escape_string($mysqli, nullable_htmlentities("
+ Hello, +
+ Your ticket regarding $ticket_subject has been scheduled for $email_datetime. +

+ $ticket_link +

+ Please do not reply to this email. +

+ Ticket: $ticket_prefix$ticket_number
+ Subject: $ticket_subject
+ Portal: Access your ticket here +

+ +
+ This is an automated message. Please do not reply directly to this email. +
")), + 'cal_str' => $cal_str + ]; + } + + $response = addToMailQueue($mysqli, $data); + + + // Update ticket reply + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = 'Ticket scheduled for $schedule', ticket_reply_type = 'Internal', ticket_reply_time_worked = '00:05:00', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + + //Logging + mysqli_query( + $mysqli, + "INSERT INTO logs SET + log_type = 'Ticket', + log_action = 'Modify', + log_description = '$session_name modified ticket schedule', + log_ip = '$session_ip', + log_user_agent = '$session_user_agent', + log_user_id = $session_user_id, + log_entity_id = $ticket_id" + ); + + + if (empty($conflicting_tickets)) { + $_SESSION['alert_message'] = "Ticket scheduled for $email_datetime"; + header("Location: " . $_SERVER["HTTP_REFERER"]); + } else { + $_SESSION['alert_type'] = "error"; + $_SESSION['alert_message'] = "Ticket scheduled for $email_datetime. Yet there are conflicting tickets scheduled for the same time:
" . implode(",
", $conflicting_tickets); + header("Location: calendar_events.php"); + } + + exit; +} diff --git a/scheduled_ticket_add_modal.php b/scheduled_ticket_add_modal.php index 918df9bc..4f6dd74a 100644 --- a/scheduled_ticket_add_modal.php +++ b/scheduled_ticket_add_modal.php @@ -2,7 +2,7 @@ -
+
\ No newline at end of file diff --git a/scheduled_ticket_edit_modal.php b/scheduled_ticket_edit_modal.php index e7ad4600..f1eed8bc 100644 --- a/scheduled_ticket_edit_modal.php +++ b/scheduled_ticket_edit_modal.php @@ -100,4 +100,4 @@
-
+
\ No newline at end of file diff --git a/scheduled_tickets.php b/scheduled_tickets.php index fd552f8f..77b41b15 100644 --- a/scheduled_tickets.php +++ b/scheduled_tickets.php @@ -23,59 +23,60 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); ?> -
-
-

Scheduled Tickets

-
-
- -
+
+
+

Recurring Tickets

+
+
+
+
-
+
-
-
+ +
-
-
- -
- -
+
+
+ +
+
-
- - - -
- -
+
-
+ -
- +
+
+ +
- - "> +
+ +
+ + +
+ "> - + - + + ?> @@ -133,8 +133,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); + -
@@ -89,9 +90,9 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
Next Run Date Action
@@ -116,8 +117,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - )"> + )">
+ - - -
- - +
-
- - + + +
+
+ + +
+ +
@@ -362,16 +375,16 @@ if (isset($_GET['ticket_id'])) {
- + -
-
-
- - +
+
+
+ + +
-
@@ -421,9 +434,15 @@ if (isset($_GET['ticket_id'])) { AND ticket_attachment_ticket_id = $ticket_id" ); - ?> + ?> -
mb-3"> +
mb-3">

@@ -439,7 +458,9 @@ if (isset($_GET['ticket_id'])) {
- +
@@ -487,11 +508,9 @@ if (isset($_GET['ticket_id'])) {
- @@ -566,7 +585,7 @@ if (isset($_GET['ticket_id'])) { $prev_ticket_id = intval($prev_ticket_row['ticket_id']); $prev_ticket_subject = nullable_htmlentities($prev_ticket_row['ticket_subject']); $prev_ticket_status = nullable_htmlentities($prev_ticket_row['ticket_status']); - ?> + ?>
@@ -577,7 +596,7 @@ if (isset($_GET['ticket_id'])) { Status:
- +
@@ -602,11 +621,10 @@ if (isset($_GET['ticket_id'])) { while ($ticket_watcher_row = mysqli_fetch_array($sql_ticket_watchers)) { $watcher_id = intval($ticket_watcher_row['watcher_id']); $ticket_watcher_email = nullable_htmlentities($ticket_watcher_row['watcher_email']); - ?> + ?> @@ -642,15 +660,25 @@ if (isset($_GET['ticket_id'])) {
Feedback:
- + + if (!empty($ticket_scheduled_for)) { ?> +
+ Scheduled for: +
+ +
+ Scheduled for: Add +
+
Total time worked:
- + + if ($config_module_enable_accounting) { ?>
Billable: @@ -680,7 +708,7 @@ if (isset($_GET['ticket_id'])) {
- +
@@ -744,12 +772,12 @@ if (isset($_GET['ticket_id'])) { $service_ticket_status = nullable_htmlentities($row['ticket_status']); $service_ticket_created_at = nullable_htmlentities($row['ticket_created_at']); $service_ticket_updated_at = nullable_htmlentities($row['ticket_updated_at']); - ?> + ?>

Ticket: $service_ticket_subject ($service_ticket_status)"; ?>

-
@@ -761,9 +789,11 @@ if (isset($_GET['ticket_id'])) {
- + - +
@@ -811,7 +841,8 @@ if (isset($_GET['ticket_id'])) {

- +
@@ -824,18 +855,24 @@ if (isset($_GET['ticket_id'])) {
- > - +
- +
@@ -859,7 +896,7 @@ if (isset($_GET['ticket_id'])) {
- -if ($ticket_status !== "Closed") { ?> + + + + @@ -898,5 +942,4 @@ if ($ticket_status !== "Closed") { ?> - - + \ No newline at end of file diff --git a/ticket_edit_schedule_modal.php b/ticket_edit_schedule_modal.php new file mode 100644 index 00000000..caa02ca0 --- /dev/null +++ b/ticket_edit_schedule_modal.php @@ -0,0 +1,43 @@ + diff --git a/tickets.php b/tickets.php index 1c63302a..20046ae6 100644 --- a/tickets.php +++ b/tickets.php @@ -39,7 +39,7 @@ if (isset($_GET['assigned']) & !empty($_GET['assigned'])) { if ($_GET['assigned'] == 'unassigned') { $ticket_assigned_filter = 'AND ticket_assigned_to = 0'; } else { - $ticket_assigned_filter = 'AND ticket_assigned_to = '.intval($_GET['assigned']); + $ticket_assigned_filter = 'AND ticket_assigned_to = ' . intval($_GET['assigned']); } } else { // Default - any @@ -77,6 +77,11 @@ $sql_total_tickets_closed = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS to $row = mysqli_fetch_array($sql_total_tickets_closed); $total_tickets_closed = intval($row['total_tickets_closed']); +//Get Total Scheduled tickets +$sql_total_scheduled_tickets = mysqli_query($mysqli, "SELECT COUNT(scheduled_ticket_id) AS total_scheduled_tickets FROM scheduled_tickets"); +$row = mysqli_fetch_array($sql_total_scheduled_tickets); +$total_scheduled_tickets = intval($row['total_scheduled_tickets']); + //Get Unassigned tickets $sql_total_tickets_unassigned = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS total_tickets_unassigned FROM tickets WHERE ticket_assigned_to = '0' AND ticket_status != 'Closed'"); $row = mysqli_fetch_array($sql_total_tickets_unassigned); @@ -112,10 +117,11 @@ $user_active_assigned_tickets = intval($row['total_tickets_assigned']);
- +
- +
@@ -134,7 +140,11 @@ $user_active_assigned_tickets = intval($row['total_tickets_assigned']); Unassigned Tickets | - + + + Recurring Tickets | + +
- +
-
" id="advancedFilter"> +
" id="advancedFilter">
@@ -223,14 +235,28 @@ $user_active_assigned_tickets = intval($row['total_tickets_assigned']);
- + + + + + + +
@@ -238,17 +264,23 @@ $user_active_assigned_tickets = intval($row['total_tickets_assigned']);
- "> - - - - - - - "> + + + + + + + + } + ?> - - - - + + + + - + - Never

"; + while ($row = mysqli_fetch_array($sql)) { + $ticket_id = intval($row['ticket_id']); + $ticket_prefix = nullable_htmlentities($row['ticket_prefix']); + $ticket_number = intval($row['ticket_number']); + $ticket_subject = nullable_htmlentities($row['ticket_subject']); + $ticket_priority = nullable_htmlentities($row['ticket_priority']); + $ticket_status = nullable_htmlentities($row['ticket_status']); + $ticket_billable = intval($row['ticket_billable']); + $ticket_vendor_ticket_number = nullable_htmlentities($row['ticket_vendor_ticket_number']); + $ticket_created_at = nullable_htmlentities($row['ticket_created_at']); + $ticket_created_at_time_ago = timeAgo($row['ticket_created_at']); + $ticket_updated_at = nullable_htmlentities($row['ticket_updated_at']); + $ticket_updated_at_time_ago = timeAgo($row['ticket_updated_at']); + if (empty($ticket_updated_at)) { + if ($ticket_status == "Closed") { + $ticket_updated_at_display = "

Never

"; + } else { + $ticket_updated_at_display = "

Never

"; + } } else { - $ticket_updated_at_display = "

Never

"; + $ticket_updated_at_display = "$ticket_updated_at_time_ago
$ticket_updated_at"; } - } else { - $ticket_updated_at_display = "$ticket_updated_at_time_ago
$ticket_updated_at"; - } - $ticket_closed_at = nullable_htmlentities($row['ticket_closed_at']); - $client_id = intval($row['ticket_client_id']); - $client_name = nullable_htmlentities($row['client_name']); - $contact_id = intval($row['ticket_contact_id']); - $contact_name = nullable_htmlentities($row['contact_name']); - $contact_title = nullable_htmlentities($row['contact_title']); - $contact_email = nullable_htmlentities($row['contact_email']); - $contact_phone = formatPhoneNumber($row['contact_phone']); - $contact_extension = nullable_htmlentities($row['contact_extension']); - $contact_mobile = formatPhoneNumber($row['contact_mobile']); - if ($ticket_status == "Pending-Assignment") { - $ticket_status_color = "danger"; - } elseif ($ticket_status == "Assigned") { - $ticket_status_color = "primary"; - } elseif ($ticket_status == "In-Progress") { - $ticket_status_color = "success"; - } elseif ($ticket_status == "Closed") { - $ticket_status_color = "dark"; - } elseif ($ticket_status == "Auto Close") { - $ticket_status_color = "dark"; - } elseif ($ticket_status == "Client-Replied") { - $ticket_status_color = "warning"; - } else{ - $ticket_status_color = "secondary"; - } - - if ($ticket_priority == "High") { - $ticket_priority_color = "danger"; - } elseif ($ticket_priority == "Medium") { - $ticket_priority_color = "warning"; - } else{ - $ticket_priority_color = "info"; - } - $ticket_assigned_to = intval($row['ticket_assigned_to']); - if (empty($ticket_assigned_to)) { - if ($ticket_status == "Closed") { - $ticket_assigned_to_display = "

Not Assigned

"; + $ticket_closed_at = nullable_htmlentities($row['ticket_closed_at']); + $client_id = intval($row['ticket_client_id']); + $client_name = nullable_htmlentities($row['client_name']); + $contact_id = intval($row['ticket_contact_id']); + $contact_name = nullable_htmlentities($row['contact_name']); + $contact_title = nullable_htmlentities($row['contact_title']); + $contact_email = nullable_htmlentities($row['contact_email']); + $contact_phone = formatPhoneNumber($row['contact_phone']); + $contact_extension = nullable_htmlentities($row['contact_extension']); + $contact_mobile = formatPhoneNumber($row['contact_mobile']); + if ($ticket_status == "Pending-Assignment") { + $ticket_status_color = "danger"; + } elseif ($ticket_status == "Assigned") { + $ticket_status_color = "primary"; + } elseif ($ticket_status == "In-Progress") { + $ticket_status_color = "success"; + } elseif ($ticket_status == "Closed") { + $ticket_status_color = "dark"; + } elseif ($ticket_status == "Auto Close") { + $ticket_status_color = "dark"; + } elseif ($ticket_status == "Client-Replied") { + $ticket_status_color = "warning"; } else { - $ticket_assigned_to_display = "

Not Assigned

"; + $ticket_status_color = "secondary"; } - } else { - $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); - } - if (empty($contact_name)) { - $contact_display = "-"; - } else { - $contact_display = "$contact_name
$contact_email"; - } + if ($ticket_priority == "High") { + $ticket_priority_color = "danger"; + } elseif ($ticket_priority == "Medium") { + $ticket_priority_color = "warning"; + } else { + $ticket_priority_color = "info"; + } + $ticket_assigned_to = intval($row['ticket_assigned_to']); + if (empty($ticket_assigned_to)) { + if ($ticket_status == "Closed") { + $ticket_assigned_to_display = "

Not Assigned

"; + } else { + $ticket_assigned_to_display = "

Not Assigned

"; + } + } else { + $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); + } - $asset_id = intval($row['ticket_asset_id']); - $vendor_id = intval($row['ticket_vendor_id']); + if (empty($contact_name)) { + $contact_display = "-"; + } else { + $contact_display = "$contact_name
$contact_email"; + } + + $asset_id = intval($row['ticket_asset_id']); + $vendor_id = intval($row['ticket_vendor_id']); ?> - "> - - - - "> + + + + - - + + $"; - } else { - echo "X"; } - ?> - - - - - - - + ?> + + + + + + + ?>
-
- -
-
Number - Subject - Client / Contact - Billable +
+
+ +
+
Number Subject + Client / Contact + Billable + Priority - Status - Assigned - Last Response - Created - Priority + Status + Assigned + Last Response + Created +
- -
- -
- -
- - - - - - - +
+ +
+ +
+ +
+ + + + + + + -
-
- +
+
+ + $"; + } else { + echo "X"; + } + ?> + - -
- -
+ +
+ +