1) $t -= 1; if ($t < 1/6) return $p + ($q - $p) * 6 * $t; if ($t < 1/2) return $q; if ($t < 2/3) return $p + ($q - $p) * (2/3 - $t) * 6; return $p; } /** * Calculate the next start time based on previous transmissions in a block * @param PDO $db Database connection * @param string $blockDate Date in Y-m-d format * @param string $channel Channel name * @param string $blockStartTime Optional block start time override * @return string Time in H:i:s format */ function calculateNextStartTime($db, $blockDate, $channel, $blockStartTime = null) { // Get all transmissions for this block, ordered by time $stmt = $db->prepare(" SELECT start_time, duration FROM transmissions WHERE start_date = ? AND channel = ? ORDER BY start_time ASC "); $stmt->execute([$blockDate, $channel]); $transmissions = $stmt->fetchAll(PDO::FETCH_ASSOC); // If no transmissions, return block start time if (empty($transmissions)) { if ($blockStartTime) { return $blockStartTime; } // Get block start time from daily_blocks or template $stmt = $db->prepare(" SELECT actual_start_time FROM daily_blocks WHERE block_date = ? AND channel = ? LIMIT 1 "); $stmt->execute([$blockDate, $channel]); $time = $stmt->fetchColumn(); return $time ?: '07:00:00'; // Default fallback } // Calculate end time of last transmission $lastTx = end($transmissions); $startTime = new DateTime($lastTx['start_time']); // Add duration to get end time list($h, $m, $s) = explode(':', $lastTx['duration']); $interval = new DateInterval("PT{$h}H{$m}M{$s}S"); $startTime->add($interval); return $startTime->format('H:i:s'); } /** * Convert time string to seconds * @param string $time Time in HH:MM:SS format * @return int Seconds */ function timeToSeconds($time) { list($h, $m, $s) = explode(':', $time); return ($h * 3600) + ($m * 60) + $s; } /** * Convert seconds to time string * @param int $seconds Seconds * @return string Time in HH:MM:SS format */ function secondsToTime($seconds) { $hours = floor($seconds / 3600); $minutes = floor(($seconds % 3600) / 60); $secs = $seconds % 60; return sprintf('%02d:%02d:%02d', $hours, $minutes, $secs); } /** * Add time duration to a time string * @param string $startTime Time in HH:MM:SS format * @param string $duration Duration in HH:MM:SS format * @return string Resulting time in HH:MM:SS format */ function addTimeToTime($startTime, $duration) { $startSeconds = timeToSeconds($startTime); $durationSeconds = timeToSeconds($duration); $totalSeconds = $startSeconds + $durationSeconds; return secondsToTime($totalSeconds); } /** * Check if two time ranges overlap * @param string $start1 Start time 1 * @param string $end1 End time 1 * @param string $start2 Start time 2 * @param string $end2 End time 2 * @return bool True if overlap exists */ function timeRangesOverlap($start1, $end1, $start2, $end2) { $s1 = timeToSeconds($start1); $e1 = timeToSeconds($end1); $s2 = timeToSeconds($start2); $e2 = timeToSeconds($end2); return ($s1 < $e2) && ($e1 > $s2); } /** * Get or create daily blocks for a specific date range * @param PDO $db Database connection * @param string $startDate Start date * @param string $endDate End date * @return array Array of daily blocks */ function ensureDailyBlocks($db, $startDate, $endDate) { $start = new DateTime($startDate); $end = new DateTime($endDate); $interval = new DateInterval('P1D'); $period = new DatePeriod($start, $interval, $end->modify('+1 day')); $dayMap = [ 'Monday' => 'ma', 'Tuesday' => 'di', 'Wednesday' => 'wo', 'Thursday' => 'do', 'Friday' => 'vr', 'Saturday' => 'za', 'Sunday' => 'zo' ]; foreach ($period as $date) { $dateStr = $date->format('Y-m-d'); $dayOfWeek = $dayMap[$date->format('l')]; // Get active templates for this day $stmt = $db->prepare(" SELECT * FROM block_templates WHERE is_active = 1 AND (day_of_week = ? OR day_of_week = 'all') "); $stmt->execute([$dayOfWeek]); $templates = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($templates as $template) { // Check if daily block already exists for this template $stmt = $db->prepare(" SELECT id FROM daily_blocks WHERE block_date = ? AND channel = ? AND template_id = ? "); $stmt->execute([ $dateStr, $template['channel'], $template['id'] ]); if (!$stmt->fetch()) { // Create daily block from template $stmt = $db->prepare(" INSERT IGNORE INTO daily_blocks (template_id, channel, block_date, actual_start_time, actual_end_time) VALUES (?, ?, ?, ?, ?) "); $stmt->execute([ $template['id'], $template['channel'], $dateStr, $template['default_start_time'], $template['default_end_time'] ]); } } } } /** * Format date for display in Dutch * @param string $date Date string * @return string Formatted date */ function formatDateDutch($date) { $dt = new DateTime($date); $dayNames = ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za']; $monthNames = [ '', 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december' ]; $dayOfWeek = $dayNames[$dt->format('w')]; $day = $dt->format('j'); $month = $monthNames[(int)$dt->format('n')]; $year = $dt->format('Y'); return ucfirst($dayOfWeek) . ' ' . $day . ' ' . $month . ' ' . $year; } /** * Get database connection * @return PDO Database connection */ function getDbConnection() { static $db = null; if ($db === null) { $db = new PDO( "mysql:host={$_ENV['DB_HOST']};dbname={$_ENV['DB_NAME']};charset=utf8mb4", $_ENV['DB_USER'], $_ENV['DB_PASS'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } return $db; } /** * Send JSON response * @param mixed $data Data to send * @param int $statusCode HTTP status code */ function jsonResponse($data, $statusCode = 200) { http_response_code($statusCode); header('Content-Type: application/json'); echo json_encode($data); exit; } /** * Validate time format (HH:MM:SS or HH:MM) * @param string $time Time string * @return bool True if valid */ function isValidTime($time) { return preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/', $time); } /** * Validate date format (YYYY-MM-DD) * @param string $date Date string * @return bool True if valid */ function isValidDate($date) { $d = DateTime::createFromFormat('Y-m-d', $date); return $d && $d->format('Y-m-d') === $date; } /** * Update all transmissions in a block to Talpa after a change * This function recalculates start times and syncs to Talpa * * @param PDO $db Database connection * @param TalpaApi $api Talpa API instance * @param string $date Block date (Y-m-d) * @param string $channel Channel name * @param int $blockId Block ID * @return array Result with success status and details */ function updateBlockTransmissionsToTalpa($db, $api, $date, $channel, $blockId) { $results = [ 'success' => true, 'updated' => 0, 'failed' => 0, 'errors' => [] ]; try { // Get block info $stmt = $db->prepare(" SELECT actual_start_time, actual_end_time FROM daily_blocks WHERE id = ? "); $stmt->execute([$blockId]); $block = $stmt->fetch(); if (!$block) { $results['success'] = false; $results['errors'][] = 'Block not found'; return $results; } $blockStart = $block['actual_start_time']; $blockEnd = $block['actual_end_time'] ?? '23:59:59'; // Get all transmissions in this block, ordered by start time $stmt = $db->prepare(" SELECT t.*, c.content_id, c.duration as infomercial_duration FROM transmissions t JOIN infomercials c ON t.infomercial_id = c.id WHERE t.start_date = ? AND t.channel = ? AND t.start_time >= ? AND t.start_time < ? ORDER BY t.start_time ASC "); $stmt->execute([$date, $channel, $blockStart, $blockEnd]); $transmissions = $stmt->fetchAll(); // Recalculate start times from block start $currentTime = $blockStart; foreach ($transmissions as $tx) { // Update local start time if it changed if ($tx['start_time'] !== $currentTime) { $updateStmt = $db->prepare(" UPDATE transmissions SET start_time = ?, api_status = 'pending' WHERE id = ? "); $updateStmt->execute([$currentTime, $tx['id']]); } // Sync to Talpa if transmission has talpa_transmission_id if (!empty($tx['talpa_transmission_id'])) { try { $requestData = [ "startDate" => $date, "startTime" => $currentTime, "duration" => $tx['duration'] ]; $res = $api->updateTransmission($tx['talpa_transmission_id'], $requestData); // Check if update was successful $status = 'error'; if (isset($res['statusCode']) && ($res['statusCode'] == 200 || $res['statusCode'] == '200')) { $status = 'synced'; $results['updated']++; } elseif (isset($res['message']) && strpos($res['message'], 'updated') !== false) { $status = 'synced'; $results['updated']++; } else { $results['failed']++; $results['errors'][] = [ 'transmission_id' => $tx['id'], 'error' => $res['message'] ?? 'Unknown error' ]; } // Update status $updateStmt = $db->prepare(" UPDATE transmissions SET api_status = ?, api_response = ? WHERE id = ? "); $updateStmt->execute([$status, json_encode($res), $tx['id']]); } catch (Exception $e) { $results['failed']++; $results['errors'][] = [ 'transmission_id' => $tx['id'], 'error' => $e->getMessage() ]; } } // Move to next time slot $currentTime = addTimeToTime($currentTime, $tx['duration']); } } catch (Exception $e) { $results['success'] = false; $results['errors'][] = $e->getMessage(); } return $results; } /** * Sync a single transmission to Talpa (create or update) * * @param PDO $db Database connection * @param TalpaApi $api Talpa API instance * @param array $transmission Transmission data * @return array Result with success status and talpa_transmission_id */ function syncTransmissionToTalpa($db, $api, $transmission) { $result = [ 'success' => false, 'talpa_transmission_id' => $transmission['talpa_transmission_id'] ?? null, 'error' => null ]; try { $requestData = [ "channel" => $transmission['channel'], "template" => $transmission['template'], "startDate" => $transmission['start_date'], "startTime" => $transmission['start_time'], "duration" => $transmission['duration'], "contentId" => $transmission['content_id'] ]; // Determine if this is a create or update if (!empty($transmission['talpa_transmission_id'])) { // Update existing transmission $res = $api->updateTransmission($transmission['talpa_transmission_id'], $requestData); if (isset($res['statusCode']) && ($res['statusCode'] == 200 || $res['statusCode'] == '200')) { $result['success'] = true; } elseif (isset($res['message']) && strpos($res['message'], 'updated') !== false) { $result['success'] = true; } else { $result['error'] = $res['message'] ?? 'Update failed'; } } else { // Create new transmission $res = $api->createTransmission($requestData); if (isset($res['id'])) { $result['success'] = true; $result['talpa_transmission_id'] = $res['id']; } elseif (isset($res['statusCode']) && $res['statusCode'] == 201) { $result['success'] = true; $result['talpa_transmission_id'] = $res['id'] ?? null; } else { $result['error'] = $res['message'] ?? 'Create failed'; } } // Update database $status = $result['success'] ? 'synced' : 'error'; $updateStmt = $db->prepare(" UPDATE transmissions SET api_status = ?, api_response = ?, talpa_transmission_id = ? WHERE id = ? "); $updateStmt->execute([ $status, json_encode($res), $result['talpa_transmission_id'], $transmission['id'] ]); } catch (Exception $e) { $result['error'] = $e->getMessage(); } return $result; }