583 lines
18 KiB
PHP
583 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Helper Functions for Telvero Talpa Planning System
|
|
* Contains utility functions for color generation, time calculations, and data formatting
|
|
*/
|
|
|
|
/**
|
|
* Generate a distinct color that doesn't exist in the provided array
|
|
* @param array $existingColors Array of existing hex color codes
|
|
* @return string Hex color code
|
|
*/
|
|
function generateDistinctColor($existingColors = []) {
|
|
// Predefined palette with good contrast and visibility
|
|
$colorPalette = [
|
|
'#3498db', // Blue
|
|
'#e74c3c', // Red
|
|
'#9b59b6', // Purple
|
|
'#2ecc71', // Green
|
|
'#f39c12', // Orange
|
|
'#1abc9c', // Turquoise
|
|
'#e67e22', // Dark Orange
|
|
'#34495e', // Dark Gray
|
|
'#16a085', // Sea Green
|
|
'#c0392b', // Dark Red
|
|
'#8e44ad', // Dark Purple
|
|
'#27ae60', // Dark Green
|
|
'#d35400', // Pumpkin
|
|
'#2980b9', // Belize Blue
|
|
'#8e44ad', // Wisteria
|
|
'#16a085', // Green Sea
|
|
'#c0392b', // Pomegranate
|
|
'#f39c12', // Sun Flower
|
|
];
|
|
|
|
// Find first color not in existing colors
|
|
foreach ($colorPalette as $color) {
|
|
if (!in_array($color, $existingColors)) {
|
|
return $color;
|
|
}
|
|
}
|
|
|
|
// If all predefined colors are used, generate random distinct color
|
|
return generateRandomColor($existingColors);
|
|
}
|
|
|
|
/**
|
|
* Generate a random color with good saturation and lightness
|
|
* @param array $existingColors Array of existing colors to avoid
|
|
* @return string Hex color code
|
|
*/
|
|
function generateRandomColor($existingColors = []) {
|
|
$maxAttempts = 50;
|
|
$attempt = 0;
|
|
|
|
while ($attempt < $maxAttempts) {
|
|
$hue = rand(0, 360);
|
|
$saturation = rand(60, 90); // Good saturation
|
|
$lightness = rand(45, 65); // Good visibility
|
|
|
|
$color = hslToHex($hue, $saturation, $lightness);
|
|
|
|
// Check if color is distinct enough from existing colors
|
|
if (isColorDistinct($color, $existingColors)) {
|
|
return $color;
|
|
}
|
|
|
|
$attempt++;
|
|
}
|
|
|
|
// Fallback to a random color
|
|
return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
|
|
}
|
|
|
|
/**
|
|
* Check if a color is distinct from existing colors
|
|
* @param string $newColor Hex color code
|
|
* @param array $existingColors Array of existing hex color codes
|
|
* @return bool True if distinct enough
|
|
*/
|
|
function isColorDistinct($newColor, $existingColors, $threshold = 50) {
|
|
if (empty($existingColors)) {
|
|
return true;
|
|
}
|
|
|
|
list($r1, $g1, $b1) = sscanf($newColor, "#%02x%02x%02x");
|
|
|
|
foreach ($existingColors as $existingColor) {
|
|
list($r2, $g2, $b2) = sscanf($existingColor, "#%02x%02x%02x");
|
|
|
|
// Calculate color distance (Euclidean distance in RGB space)
|
|
$distance = sqrt(
|
|
pow($r1 - $r2, 2) +
|
|
pow($g1 - $g2, 2) +
|
|
pow($b1 - $b2, 2)
|
|
);
|
|
|
|
if ($distance < $threshold) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Convert HSL to Hex color
|
|
* @param int $h Hue (0-360)
|
|
* @param int $s Saturation (0-100)
|
|
* @param int $l Lightness (0-100)
|
|
* @return string Hex color code
|
|
*/
|
|
function hslToHex($h, $s, $l) {
|
|
$h /= 360;
|
|
$s /= 100;
|
|
$l /= 100;
|
|
|
|
if ($s == 0) {
|
|
$r = $g = $b = $l;
|
|
} else {
|
|
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
|
|
$p = 2 * $l - $q;
|
|
|
|
$r = hueToRgb($p, $q, $h + 1/3);
|
|
$g = hueToRgb($p, $q, $h);
|
|
$b = hueToRgb($p, $q, $h - 1/3);
|
|
}
|
|
|
|
return sprintf("#%02x%02x%02x", round($r * 255), round($g * 255), round($b * 255));
|
|
}
|
|
|
|
/**
|
|
* Helper function for HSL to RGB conversion
|
|
*/
|
|
function hueToRgb($p, $q, $t) {
|
|
if ($t < 0) $t += 1;
|
|
if ($t > 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;
|
|
}
|