Files
app/api/admin/handlers/company-settings-handlers.php
Simon 758be819c3 feat: P4 backend kvalita - SELECT * fix, overdue konsolidace, Validator
- SELECT * nahrazen explicitnimi sloupci ve 22 PHP souborech (69+ vyskytu)
- users-handlers.php: password_hash explicitne vyloucen z dotazu
- Overdue detekce presunuta do invoices.php routeru (1x pred dispatch misto 3x v handlerech)
- Validator.php: validacni helper s pravidly required, string, int, email, in, numeric
- PaginationHelper: PHPStan typy opraveny

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:42:42 +01:00

254 lines
8.7 KiB
PHP

<?php
declare(strict_types=1);
/**
* @param bool $includeLogo false = bez logo_data BLOBu
* @return array<string, mixed>
*/
function getOrCreateSettings(PDO $pdo, bool $includeLogo = false): array
{
if ($includeLogo) {
$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, logo_data, uuid, modified_at, sync_version,
order_type_code, invoice_type_code, is_deleted, require_2fa
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<mixed>|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<mixed>|null $currentRaw */
$currentRaw = !empty($settings['custom_fields'])
? json_decode($settings['custom_fields'], true)
: null;
if (is_array($currentRaw) && !isset($currentRaw['fields'])) {
/** @var array<string, mixed> $stored */
$stored = ['fields' => $currentRaw, 'field_order' => null];
} elseif (is_array($currentRaw) && isset($currentRaw['fields'])) {
/** @var array<string, mixed> $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();
}