= 2 ? (intval($_POST['billable'] ?? 0) ? 1 : 0) : intval($current['recurring_ticket_billable'] ?? 0);
return [
'subject' => escapeSql(mb_substr($subject, 0, 500)),
'details' => mysqli_real_escape_string($mysqli, (string) ($_POST['details'] ?? '')),
'priority' => escapeSql(ticketPriority($_POST['priority'] ?? 'Medium')),
'frequency' => $frequency,
'billable' => $billable,
'assigned_to' => ticketAgentId($_POST['assigned_to'] ?? 0),
'contact_id' => ticketClientOwnedId('contact', $_POST['contact_id'] ?? 0, $client_id),
'asset_id' => ticketClientOwnedId('asset', $_POST['asset_id'] ?? 0, $client_id),
'category_id' => ticketCategoryId($_POST['category_id'] ?? 0),
'template_id' => $template_id,
'fill_ins' => $fill_ins,
];
}
/** Replace a schedule's other assets with these (already checked) ids. */
function recurringTicketSetOtherAssets($recurring_ticket_id, array $asset_ids) {
global $mysqli;
$recurring_ticket_id = intval($recurring_ticket_id);
mysqli_query($mysqli, "DELETE FROM recurring_ticket_assets WHERE recurring_ticket_id = $recurring_ticket_id");
foreach ($asset_ids as $asset_id) {
mysqli_query($mysqli, "INSERT IGNORE INTO recurring_ticket_assets SET recurring_ticket_id = $recurring_ticket_id, asset_id = " . intval($asset_id));
}
}
/** Replace a schedule's tasks with the ones the form posted. */
function recurringTicketSetTasks($recurring_ticket_id) {
global $mysqli;
$recurring_ticket_id = intval($recurring_ticket_id);
mysqli_query($mysqli, "DELETE FROM recurring_ticket_tasks WHERE recurring_ticket_task_recurring_ticket_id = $recurring_ticket_id");
foreach (parseSubmittedTasks() as $task) {
mysqli_query($mysqli, "INSERT INTO recurring_ticket_tasks SET recurring_ticket_task_name = '{$task['name']}', recurring_ticket_task_order = {$task['order']}, recurring_ticket_task_completion_estimate = {$task['estimate']}, recurring_ticket_task_recurring_ticket_id = $recurring_ticket_id");
}
}
/** Tell an agent some recurring tickets are theirs now - not themselves, not nobody. $subjects are raw. */
function recurringTicketNotifyAgent($user_id, array $subjects) {
global $mysqli, $session_user_id, $session_name;
$user_id = intval($user_id);
if (!$user_id || $user_id == $session_user_id || !$subjects) {
return;
}
$count = count($subjects);
$what = $count === 1 ? 'Recurring ticket ' . escapeSql($subjects[0]) . ' has' : "$count recurring tickets have";
mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = 'Recurring Ticket', notification = '$what been assigned to you by " . escapeSql($session_name) . "', notification_action = 'recurring_tickets.php?assigned_agent=$user_id', notification_user_id = $user_id");
recurringTicketMailAssigned($user_id, $subjects, stripslashes($session_name));
}
if (isset($_POST['add_recurring_ticket'])) {
validateCSRFToken();
enforceUserPermission('module_support', 2);
$client_id = intval($_POST['client_id'] ?? 0);
if (!$client_id || !mysqli_num_rows(mysqli_query($mysqli, "SELECT 1 FROM clients WHERE client_id = $client_id"))) {
flashAlert('Pick the client it is for.', 'error');
redirect();
}
enforceClientAccess();
$start_date = recurringTicketDate($_POST['start_date'] ?? '');
if (!$start_date || $start_date < date('Y-m-d')) {
flashAlert('Pick when it starts - today or later.', 'error');
redirect();
}
$f = recurringTicketFromPost($client_id);
mysqli_query($mysqli, "INSERT INTO recurring_tickets SET recurring_ticket_subject = '{$f['subject']}', recurring_ticket_details = '{$f['details']}',
recurring_ticket_priority = '{$f['priority']}', recurring_ticket_frequency = '{$f['frequency']}', recurring_ticket_billable = {$f['billable']},
recurring_ticket_start_date = '$start_date', recurring_ticket_next_run = '$start_date', recurring_ticket_assigned_to = {$f['assigned_to']},
recurring_ticket_created_by = $session_user_id, recurring_ticket_client_id = $client_id, recurring_ticket_contact_id = {$f['contact_id']},
recurring_ticket_asset_id = {$f['asset_id']}, recurring_ticket_category = {$f['category_id']}, recurring_ticket_ticket_template_id = {$f['template_id']}");
$recurring_ticket_id = mysqli_insert_id($mysqli);
recurringTicketSetOtherAssets($recurring_ticket_id, ticketClientOwnedAssetIds($_POST['additional_assets'] ?? [], $client_id, $f['asset_id']));
recurringTicketSetTasks($recurring_ticket_id);
if ($f['fill_ins'] !== null) {
ticketFillInsStore('recurring_ticket', $recurring_ticket_id, $f['fill_ins']);
}
if ($f['assigned_to']) {
recurringTicketNotifyAgent($f['assigned_to'], [trim($_POST['subject'])]);
}
logAudit("Recurring Ticket", "Create", "$session_name created recurring ticket {$f['subject']} - {$f['frequency']}", $client_id, $recurring_ticket_id);
flashAlert("Recurring ticket " . escapeHtml(trim($_POST['subject'])) . " created - first run " . escapeHtml(date('M j, Y', strtotime($start_date))));
redirect();
}
if (isset($_POST['edit_recurring_ticket'])) {
validateCSRFToken();
$row = recurringTicketForRequest($_POST['recurring_ticket_id'] ?? 0, 2);
$recurring_ticket_id = intval($row['recurring_ticket_id']);
// The next run - a date in the past only when it was already (a missed run stays missed until moved)
$next_run = recurringTicketDate($_POST['next_date'] ?? '');
$current_next_run = substr((string) $row['recurring_ticket_next_run'], 0, 10);
if (!$next_run || ($next_run < date('Y-m-d') && $next_run !== $current_next_run)) {
flashAlert('Pick when it runs next - today or later.', 'error');
redirect();
}
$f = recurringTicketFromPost($client_id, $row);
// A run moved by hand is the new anchor - monthly ones keep to its day of the month from here
$anchor_sql = $next_run !== $current_next_run ? ", recurring_ticket_start_date = '$next_run'" : '';
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_subject = '{$f['subject']}', recurring_ticket_details = '{$f['details']}',
recurring_ticket_priority = '{$f['priority']}', recurring_ticket_frequency = '{$f['frequency']}', recurring_ticket_billable = {$f['billable']},
recurring_ticket_next_run = '$next_run' $anchor_sql, recurring_ticket_assigned_to = {$f['assigned_to']}, recurring_ticket_asset_id = {$f['asset_id']},
recurring_ticket_contact_id = {$f['contact_id']}, recurring_ticket_category = {$f['category_id']}, recurring_ticket_ticket_template_id = {$f['template_id']}
WHERE recurring_ticket_id = $recurring_ticket_id");
// The other assets go with the asset field - an empty multi-select posts nothing
if (isset($_POST['asset_id'])) {
recurringTicketSetOtherAssets($recurring_ticket_id, ticketClientOwnedAssetIds($_POST['additional_assets'] ?? [], $client_id, $f['asset_id']));
}
if (isset($_POST['tasks_submitted'])) {
recurringTicketSetTasks($recurring_ticket_id);
}
if ($f['fill_ins'] !== null) {
ticketFillInsStore('recurring_ticket', $recurring_ticket_id, $f['fill_ins']);
}
if ($f['assigned_to'] && $f['assigned_to'] !== intval($row['recurring_ticket_assigned_to'])) {
recurringTicketNotifyAgent($f['assigned_to'], [trim($_POST['subject'])]);
}
triggerCustomAction('recurring_ticket_update', $recurring_ticket_id);
logAudit("Recurring Ticket", "Edit", "$session_name edited recurring ticket {$f['subject']}", $client_id, $recurring_ticket_id);
flashAlert("Recurring ticket " . escapeHtml(trim($_POST['subject'])) . " updated");
redirect();
}
// Raise it now - its next run moves on, so this one takes that one's place
if (isset($_GET['force_recurring_ticket'])) {
validateCSRFToken();
$row = recurringTicketForRequest($_GET['force_recurring_ticket'], 2);
$ticket_id = recurringTicketRunNow($row, $session_name);
if (!$ticket_id) {
flashAlert("The ticket could not be raised.", 'error');
redirect();
}
$ticket = ticketRow($ticket_id);
flashAlert("Ticket " . escapeHtml(ticketReference($ticket)) . " raised from " . escapeHtml($row['recurring_ticket_subject']) . "");
redirect();
}
if (isset($_POST['bulk_force_recurring_tickets'])) {
validateCSRFToken();
$count = 0;
foreach (recurringTicketsForBulk(2) as $row) {
if (recurringTicketRunNow($row, $session_name)) {
$count++;
}
}
flashAlert("Raised $count ticket" . ($count === 1 ? '' : 's'));
redirect();
}
// Pause - kept, raising nothing until resumed
if (isset($_GET['pause_recurring_ticket']) || isset($_GET['resume_recurring_ticket'])) {
validateCSRFToken();
$pause = isset($_GET['pause_recurring_ticket']);
$row = recurringTicketForRequest($pause ? $_GET['pause_recurring_ticket'] : $_GET['resume_recurring_ticket'], 2);
$recurring_ticket_id = intval($row['recurring_ticket_id']);
$subject = escapeSql($row['recurring_ticket_subject']);
if ($pause) {
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_paused_at = COALESCE(recurring_ticket_paused_at, NOW()) WHERE recurring_ticket_id = $recurring_ticket_id");
} else {
// Runs it missed while paused are not raised now - it picks up from today
$next_run = substr((string) $row['recurring_ticket_next_run'], 0, 10);
if ($next_run < date('Y-m-d')) {
$next_run = recurringTicketNextRun($next_run, $row['recurring_ticket_frequency'], $row['recurring_ticket_start_date'], date('Y-m-d', strtotime('-1 day')));
}
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_paused_at = NULL, recurring_ticket_next_run = '$next_run' WHERE recurring_ticket_id = $recurring_ticket_id");
}
logAudit("Recurring Ticket", "Edit", "$session_name " . ($pause ? 'paused' : 'resumed') . " recurring ticket $subject", $client_id, $recurring_ticket_id);
flashAlert("Recurring ticket " . escapeHtml($row['recurring_ticket_subject']) . " " . ($pause ? 'paused' : 'resumed'));
redirect();
}
if (isset($_POST['bulk_pause_recurring_tickets']) || isset($_POST['bulk_resume_recurring_tickets'])) {
validateCSRFToken();
$pause = isset($_POST['bulk_pause_recurring_tickets']);
$count = 0;
foreach (recurringTicketsForBulk(2) as $recurring_ticket_id => $row) {
if ($pause === recurringTicketPaused($row)) {
continue;
}
if ($pause) {
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_paused_at = NOW() WHERE recurring_ticket_id = $recurring_ticket_id");
} else {
$next_run = substr((string) $row['recurring_ticket_next_run'], 0, 10);
if ($next_run < date('Y-m-d')) {
$next_run = recurringTicketNextRun($next_run, $row['recurring_ticket_frequency'], $row['recurring_ticket_start_date'], date('Y-m-d', strtotime('-1 day')));
}
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_paused_at = NULL, recurring_ticket_next_run = '$next_run' WHERE recurring_ticket_id = $recurring_ticket_id");
}
logAudit("Recurring Ticket", "Edit", "$session_name " . ($pause ? 'paused' : 'resumed') . " recurring ticket " . escapeSql($row['recurring_ticket_subject']), intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
$count++;
}
flashAlert(($pause ? 'Paused' : 'Resumed') . " $count recurring ticket" . ($count === 1 ? '' : 's'));
redirect();
}
if (isset($_GET['delete_recurring_ticket'])) {
validateCSRFToken();
$row = recurringTicketForRequest($_GET['delete_recurring_ticket'], 3);
$recurring_ticket_id = intval($row['recurring_ticket_id']);
$subject = escapeSql($row['recurring_ticket_subject']);
// Its tasks and other assets go with it (ON DELETE CASCADE); tickets it raised stay
mysqli_query($mysqli, "DELETE FROM recurring_tickets WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Delete", "$session_name deleted recurring ticket $subject", $client_id, $recurring_ticket_id);
flashAlert("Recurring ticket " . escapeHtml($row['recurring_ticket_subject']) . " deleted", 'error');
redirect();
}
if (isset($_POST['bulk_delete_recurring_tickets'])) {
validateCSRFToken();
$count = 0;
foreach (recurringTicketsForBulk(3) as $recurring_ticket_id => $row) {
mysqli_query($mysqli, "DELETE FROM recurring_tickets WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Delete", "$session_name deleted recurring ticket " . escapeSql($row['recurring_ticket_subject']), intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
$count++;
}
logAudit("Recurring Ticket", "Bulk Delete", "$session_name deleted $count recurring ticket(s)");
flashAlert("Deleted $count recurring ticket" . ($count === 1 ? '' : 's'), 'error');
redirect();
}
if (isset($_POST['bulk_assign_recurring_ticket'])) {
validateCSRFToken();
$assign_to = ticketAgentId($_POST['assign_to'] ?? 0);
if (intval($_POST['assign_to'] ?? 0) && !$assign_to) {
flashAlert('Pick an active agent.', 'error');
redirect();
}
$agent_name = $assign_to ? (string) getFieldById('users', $assign_to, 'user_name') : 'No one';
$count = 0;
$subjects = [];
foreach (recurringTicketsForBulk(2) as $recurring_ticket_id => $row) {
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_assigned_to = $assign_to WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Edit", "$session_name assigned recurring ticket " . escapeSql($row['recurring_ticket_subject']) . " to " . escapeSql($agent_name), intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
triggerCustomAction('recurring_ticket_update', $recurring_ticket_id);
if (intval($row['recurring_ticket_assigned_to']) !== $assign_to) {
$subjects[] = $row['recurring_ticket_subject'];
}
$count++;
}
recurringTicketNotifyAgent($assign_to, $subjects);
flashAlert("Assigned $count recurring ticket" . ($count === 1 ? '' : 's') . " to " . escapeHtml($agent_name) . "");
redirect();
}
if (isset($_POST['bulk_edit_recurring_ticket_priority'])) {
validateCSRFToken();
if (!in_array($_POST['bulk_priority'] ?? '', TICKET_PRIORITIES, true)) {
flashAlert('Pick a priority.', 'error');
redirect();
}
$priority = $_POST['bulk_priority'];
$count = 0;
foreach (recurringTicketsForBulk(2) as $recurring_ticket_id => $row) {
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_priority = '$priority' WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Edit", "$session_name changed the priority of recurring ticket " . escapeSql($row['recurring_ticket_subject']) . " from " . escapeSql($row['recurring_ticket_priority']) . " to $priority", intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
triggerCustomAction('recurring_ticket_update', $recurring_ticket_id);
$count++;
}
flashAlert("Priority set to $priority on $count recurring ticket" . ($count === 1 ? '' : 's'));
redirect();
}
if (isset($_POST['bulk_edit_recurring_ticket_category'])) {
validateCSRFToken();
$category_id = ticketCategoryId($_POST['bulk_category'] ?? 0);
$category_name = $category_id ? (string) getFieldById('categories', $category_id, 'category_name') : 'Not categorized';
$count = 0;
foreach (recurringTicketsForBulk(2) as $recurring_ticket_id => $row) {
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_category = $category_id WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Edit", "$session_name set the category of recurring ticket " . escapeSql($row['recurring_ticket_subject']) . " to " . escapeSql($category_name), intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
triggerCustomAction('recurring_ticket_update', $recurring_ticket_id);
$count++;
}
flashAlert("Category set to " . escapeHtml($category_name) . " on $count recurring ticket" . ($count === 1 ? '' : 's'));
redirect();
}
if (isset($_POST['bulk_edit_recurring_ticket_billable'])) {
validateCSRFToken();
enforceUserPermission('module_sales', 2);
$billable = intval($_POST['billable'] ?? 0) ? 1 : 0;
$billable_wording = $billable ? 'Billable' : 'Not billable';
$count = 0;
foreach (recurringTicketsForBulk(2) as $recurring_ticket_id => $row) {
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_billable = $billable WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Edit", "$session_name marked recurring ticket " . escapeSql($row['recurring_ticket_subject']) . " $billable_wording", intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
triggerCustomAction('recurring_ticket_update', $recurring_ticket_id);
$count++;
}
flashAlert("$count recurring ticket" . ($count === 1 ? '' : 's') . " marked $billable_wording");
redirect();
}
if (isset($_POST['bulk_edit_recurring_ticket_next_run_date'])) {
validateCSRFToken();
$next_run = recurringTicketDate($_POST['next_run_date'] ?? '');
if (!$next_run || $next_run < date('Y-m-d')) {
flashAlert('Pick when they run next - today or later.', 'error');
redirect();
}
$count = 0;
foreach (recurringTicketsForBulk(2) as $recurring_ticket_id => $row) {
// A run moved by hand is the new anchor for the day of the month
mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_next_run = '$next_run', recurring_ticket_start_date = '$next_run' WHERE recurring_ticket_id = $recurring_ticket_id");
logAudit("Recurring Ticket", "Edit", "$session_name moved the next run of recurring ticket " . escapeSql($row['recurring_ticket_subject']) . " from " . escapeSql($row['recurring_ticket_next_run']) . " to $next_run", intval($row['recurring_ticket_client_id']), $recurring_ticket_id);
triggerCustomAction('recurring_ticket_update', $recurring_ticket_id);
$count++;
}
flashAlert("Next run set to " . escapeHtml(date('M j, Y', strtotime($next_run))) . " on $count recurring ticket" . ($count === 1 ? '' : 's'));
redirect();
}