Replace exception getMessage() exposure with generic error messages to prevent internal information disclosure. Errors are now logged with full details while clients receive sanitized responses. Affected endpoints: - add_comment, update_comment, delete_comment - update_ticket, export_tickets - generate_api_key, revoke_api_key - manage_templates, manage_workflows, manage_recurring - custom_fields, get_users Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
162 lines
5.3 KiB
PHP
162 lines
5.3 KiB
PHP
<?php
|
|
/**
|
|
* Recurring Tickets Management API
|
|
* CRUD operations for recurring_tickets table
|
|
*/
|
|
|
|
ini_set('display_errors', 0);
|
|
error_reporting(E_ALL);
|
|
|
|
require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php';
|
|
RateLimitMiddleware::apply('api');
|
|
|
|
try {
|
|
require_once dirname(__DIR__) . '/config/config.php';
|
|
require_once dirname(__DIR__) . '/helpers/Database.php';
|
|
require_once dirname(__DIR__) . '/models/RecurringTicketModel.php';
|
|
|
|
// Check authentication
|
|
session_start();
|
|
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
|
|
http_response_code(401);
|
|
echo json_encode(['success' => false, 'error' => 'Authentication required']);
|
|
exit;
|
|
}
|
|
|
|
// Check admin privileges
|
|
if (!$_SESSION['user']['is_admin']) {
|
|
http_response_code(403);
|
|
echo json_encode(['success' => false, 'error' => 'Admin privileges required']);
|
|
exit;
|
|
}
|
|
|
|
$currentUserId = $_SESSION['user']['user_id'];
|
|
|
|
// CSRF Protection for write operations
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
|
require_once dirname(__DIR__) . '/middleware/CsrfMiddleware.php';
|
|
$csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
|
if (!CsrfMiddleware::validateToken($csrfToken)) {
|
|
http_response_code(403);
|
|
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Use centralized database connection
|
|
$conn = Database::getConnection();
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
$model = new RecurringTicketModel($conn);
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
|
$action = isset($_GET['action']) ? $_GET['action'] : null;
|
|
|
|
switch ($method) {
|
|
case 'GET':
|
|
if ($id) {
|
|
$recurring = $model->getById($id);
|
|
echo json_encode(['success' => (bool)$recurring, 'recurring' => $recurring]);
|
|
} else {
|
|
$all = $model->getAll(true);
|
|
echo json_encode(['success' => true, 'recurring_tickets' => $all]);
|
|
}
|
|
break;
|
|
|
|
case 'POST':
|
|
if ($action === 'toggle' && $id) {
|
|
$result = $model->toggleActive($id);
|
|
echo json_encode($result);
|
|
} else {
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Calculate next run time
|
|
$nextRun = calculateNextRun(
|
|
$data['schedule_type'],
|
|
$data['schedule_day'] ?? null,
|
|
$data['schedule_time'] ?? '09:00'
|
|
);
|
|
|
|
$data['next_run_at'] = $nextRun;
|
|
$data['is_active'] = isset($data['is_active']) ? (int)$data['is_active'] : 1;
|
|
$data['created_by'] = $currentUserId;
|
|
|
|
$result = $model->create($data);
|
|
echo json_encode($result);
|
|
}
|
|
break;
|
|
|
|
case 'PUT':
|
|
if (!$id) {
|
|
echo json_encode(['success' => false, 'error' => 'ID required']);
|
|
exit;
|
|
}
|
|
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Recalculate next run time if schedule changed
|
|
$nextRun = calculateNextRun(
|
|
$data['schedule_type'],
|
|
$data['schedule_day'] ?? null,
|
|
$data['schedule_time'] ?? '09:00'
|
|
);
|
|
$data['next_run_at'] = $nextRun;
|
|
$data['is_active'] = isset($data['is_active']) ? (int)$data['is_active'] : 1;
|
|
|
|
$result = $model->update($id, $data);
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'DELETE':
|
|
if (!$id) {
|
|
echo json_encode(['success' => false, 'error' => 'ID required']);
|
|
exit;
|
|
}
|
|
|
|
$result = $model->delete($id);
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
default:
|
|
http_response_code(405);
|
|
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Recurring tickets API error: " . $e->getMessage());
|
|
http_response_code(500);
|
|
echo json_encode(['success' => false, 'error' => 'An internal error occurred']);
|
|
}
|
|
|
|
function calculateNextRun($scheduleType, $scheduleDay, $scheduleTime) {
|
|
$now = new DateTime();
|
|
$time = $scheduleTime ?: '09:00';
|
|
|
|
switch ($scheduleType) {
|
|
case 'daily':
|
|
$next = new DateTime('tomorrow ' . $time);
|
|
break;
|
|
|
|
case 'weekly':
|
|
$days = ['', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
|
$dayName = $days[$scheduleDay] ?? 'Monday';
|
|
$next = new DateTime("next {$dayName} " . $time);
|
|
break;
|
|
|
|
case 'monthly':
|
|
$day = max(1, min(28, (int)$scheduleDay));
|
|
$next = new DateTime();
|
|
$next->modify('first day of next month');
|
|
$next->setDate($next->format('Y'), $next->format('m'), $day);
|
|
list($h, $m) = explode(':', $time);
|
|
$next->setTime((int)$h, (int)$m, 0);
|
|
break;
|
|
|
|
default:
|
|
$next = new DateTime('tomorrow ' . $time);
|
|
}
|
|
|
|
return $next->format('Y-m-d H:i:s');
|
|
}
|