*/ function getOrCreateSettings(PDO $pdo, bool $includeLogo = false): array { if ($includeLogo) { $stmt = $pdo->query('SELECT * FROM company_settings LIMIT 1'); } else { $stmt = $pdo->query(' SELECT id, company_name, company_id, vat_id, street, city, postal_code, country, quotation_prefix, default_currency, default_vat_rate, custom_fields, uuid, modified_at, sync_version, order_type_code, invoice_type_code, is_deleted, CASE WHEN logo_data IS NOT NULL AND LENGTH(logo_data) > 0 THEN 1 ELSE 0 END as has_logo FROM company_settings LIMIT 1 '); } $settings = $stmt->fetch(); if (!$settings) { $uuid = sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0x0fff) | 0x4000, random_int(0, 0x3fff) | 0x8000, random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff) ); $pdo->prepare( "INSERT INTO company_settings (id, company_name, quotation_prefix, default_currency, default_vat_rate, uuid, modified_at, sync_version) VALUES (1, '', 'N', 'EUR', 21.0, ?, NOW(), 1)" )->execute([$uuid]); return getOrCreateSettings($pdo, $includeLogo); } return $settings; } function handleGetOffersSettings(PDO $pdo): void { $settings = getOrCreateSettings($pdo, false); /** @var array|null $cfRaw */ $cfRaw = !empty($settings['custom_fields']) ? json_decode($settings['custom_fields'], true) : null; if (is_array($cfRaw) && !isset($cfRaw['fields'])) { $settings['custom_fields'] = $cfRaw; $settings['supplier_field_order'] = null; } elseif (is_array($cfRaw) && isset($cfRaw['fields'])) { $settings['custom_fields'] = $cfRaw['fields']; $settings['supplier_field_order'] = $cfRaw['field_order'] ?? $cfRaw['fieldOrder'] ?? null; } else { $settings['custom_fields'] = []; $settings['supplier_field_order'] = null; } $settings['has_logo'] = (bool)($settings['has_logo'] ?? false); successResponse($settings); } function handleUpdateOffersSettings(PDO $pdo): void { $input = getJsonInput(); $settings = getOrCreateSettings($pdo); // Delkove limity $maxLengths = [ 'company_name' => 255, 'street' => 255, 'city' => 255, 'postal_code' => 20, 'country' => 100, 'company_id' => 50, 'vat_id' => 50, 'default_currency' => 5, ]; foreach ($maxLengths as $f => $max) { if (isset($input[$f]) && mb_strlen(trim((string)$input[$f])) > $max) { errorResponse("Pole $f je příliš dlouhé (max $max znaků)"); } } // Validace meny if (isset($input['default_currency']) && !in_array($input['default_currency'], ['EUR', 'USD', 'CZK', 'GBP'])) { errorResponse('Neplatná měna'); } $fields = [ 'company_name', 'street', 'city', 'postal_code', 'country', 'company_id', 'vat_id', 'quotation_prefix', 'default_currency', 'order_type_code', 'invoice_type_code', ]; $setClauses = []; $params = []; foreach ($fields as $field) { if (array_key_exists($field, $input)) { $setClauses[] = "$field = ?"; $params[] = $input[$field]; } } // custom_fields + SupplierFieldOrder - ulozeny dohromady jako JSON if (array_key_exists('custom_fields', $input) || array_key_exists('supplier_field_order', $input)) { /** @var array|null $currentRaw */ $currentRaw = !empty($settings['custom_fields']) ? json_decode($settings['custom_fields'], true) : null; if (is_array($currentRaw) && !isset($currentRaw['fields'])) { /** @var array $stored */ $stored = ['fields' => $currentRaw, 'field_order' => null]; } elseif (is_array($currentRaw) && isset($currentRaw['fields'])) { /** @var array $stored */ $stored = $currentRaw; } else { $stored = ['fields' => [], 'field_order' => null]; } if (array_key_exists('custom_fields', $input) && is_array($input['custom_fields'])) { $stored['fields'] = $input['custom_fields']; } if (array_key_exists('supplier_field_order', $input)) { $stored['field_order'] = is_array($input['supplier_field_order']) ? $input['supplier_field_order'] : null; } // Odstranit stary klic unset($stored['fieldOrder']); $setClauses[] = 'custom_fields = ?'; $params[] = json_encode($stored, JSON_UNESCAPED_UNICODE); } // Validace prefixu if (isset($input['quotation_prefix']) && !preg_match('/^[A-Za-z0-9]{0,10}$/', $input['quotation_prefix'])) { errorResponse('Prefix nabídky může obsahovat pouze alfanumerické znaky (max 10)'); } if (isset($input['order_type_code']) && !preg_match('/^[0-9]{0,10}$/', $input['order_type_code'])) { errorResponse('Typový kód objednávek může obsahovat pouze čísla (max 10)'); } if (isset($input['invoice_type_code']) && !preg_match('/^[0-9]{0,10}$/', $input['invoice_type_code'])) { errorResponse('Typový kód faktur může obsahovat pouze čísla (max 10)'); } $numericFields = ['default_vat_rate']; foreach ($numericFields as $field) { if (array_key_exists($field, $input)) { $val = is_numeric($input[$field]) ? floatval($input[$field]) : 0; if ($val < 0 || $val > 100) { errorResponse('Sazba DPH musí být mezi 0 a 100'); } $setClauses[] = "$field = ?"; $params[] = $val; } } if (empty($setClauses)) { errorResponse('Žádná data k aktualizaci'); } $setClauses[] = 'modified_at = NOW()'; $setClauses[] = 'sync_version = sync_version + 1'; $sql = 'UPDATE company_settings SET ' . implode(', ', $setClauses) . ' WHERE id = ?'; $params[] = $settings['id']; $stmt = $pdo->prepare($sql); $stmt->execute($params); AuditLog::logUpdate('company_settings', (int)$settings['id'], [], $input, 'Aktualizováno nastavení firmy'); successResponse(null, 'Nastavení bylo uloženo'); } function handleUploadLogo(PDO $pdo): void { if (!isset($_FILES['logo']) || $_FILES['logo']['error'] !== UPLOAD_ERR_OK) { errorResponse('Nebyl nahrán žádný soubor'); } $file = $_FILES['logo']; $allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp']; $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $file['tmp_name']); finfo_close($finfo); if (!in_array($mimeType, $allowedTypes)) { errorResponse('Nepodporovaný formát obrázku. Povolené: PNG, JPEG, GIF, WebP'); } if ($file['size'] > 5 * 1024 * 1024) { errorResponse('Soubor je příliš velký (max 5 MB)'); } $logoData = file_get_contents($file['tmp_name']); $settings = getOrCreateSettings($pdo); $stmt = $pdo->prepare( 'UPDATE company_settings SET logo_data = ?, modified_at = NOW(), sync_version = sync_version + 1 WHERE id = ?' ); $stmt->execute([$logoData, $settings['id']]); AuditLog::logUpdate( 'company_settings', (int)$settings['id'], [], ['logo' => 'uploaded'], 'Aktualizováno logo firmy' ); successResponse(null, 'Logo bylo nahráno'); } function handleGetLogo(PDO $pdo): void { $stmt = $pdo->query('SELECT logo_data FROM company_settings LIMIT 1'); $row = $stmt->fetch(); if (!$row || empty($row['logo_data'])) { http_response_code(404); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['success' => false, 'error' => 'Logo nenalezeno']); exit(); } $logoData = $row['logo_data']; // Detect image type from binary data $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_buffer($finfo, $logoData); finfo_close($finfo); header('Content-Type: ' . $mimeType); header('Content-Length: ' . strlen($logoData)); header('Cache-Control: public, max-age=3600'); echo $logoData; exit(); }