| Server IP : 193.86.120.172 / Your IP : 216.73.216.215 Web Server : Apache/2.4.63 (Unix) System : Linux JServices 3.10.108 #86003 SMP Wed Oct 22 13:20:46 CST 2025 x86_64 User : kubec ( 1026) PHP Version : 8.2.28 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /volume1/web/jservices_cz/ |
Upload File : |
<?php
declare(strict_types=1);
/**
* J Services - pomocné funkce
* Načteno automaticky přes config.php
*/
function nacti_nastaveni(PDO $pdo, string $klic, string $vychozi = ''): string
{
$stmt = $pdo->prepare('SELECT hodnota FROM nastaveni WHERE klic = :k');
$stmt->execute(['k' => $klic]);
$hodnota = $stmt->fetchColumn();
return $hodnota !== false ? (string) $hodnota : $vychozi;
}
function nacti_vsechna_nastaveni(PDO $pdo): array
{
$vysledek = [];
$stmt = $pdo->query('SELECT klic, hodnota FROM nastaveni');
foreach ($stmt->fetchAll() as $radek) {
$vysledek[$radek['klic']] = $radek['hodnota'];
}
return $vysledek;
}
function ulozit_nastaveni(PDO $pdo, string $klic, string $hodnota): void
{
$stmt = $pdo->prepare(
'INSERT INTO nastaveni (klic, hodnota) VALUES (:k, :h)
ON DUPLICATE KEY UPDATE hodnota = :h2'
);
$stmt->execute(['k' => $klic, 'h' => $hodnota, 'h2' => $hodnota]);
}
/** Bezpečný výstup textu do HTML */
function e(?string $text): string
{
return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
}
/** Je admin přihlášený? */
function je_prihlasen(): bool
{
return !empty($_SESSION['admin_id']);
}
/** Vyžaduje přihlášení, jinak přesměruje na admin.html */
function vyzaduj_prihlaseni(): void
{
if (!je_prihlasen()) {
header('Location: admin.html');
exit;
}
}
/** Vrátí SVG ikonu podle názvu (jednotný styl napříč webem) */
function icon(string $name, int $size = 20): string
{
$attr = 'width="' . $size . '" height="' . $size . '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
$paths = [
'headset' => '<path d="M3 18v-6a9 9 0 0 1 18 0v6"/><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3z"/><path d="M3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"/>',
'ticket' => '<rect x="3" y="6" width="18" height="12" rx="2"/><line x1="9" y1="6" x2="9" y2="18" stroke-dasharray="3 3"/>',
'calendar' => '<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
'check' => '<polyline points="20 6 9 17 4 12"/>',
'code' => '<polyline points="8 6 3 12 8 18"/><polyline points="16 6 21 12 16 18"/>',
'desktop' => '<rect x="3" y="4" width="18" height="13" rx="1"/><path d="M8 21h8M12 17v4"/>',
'database' => '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3"/>',
'mail' => '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/>',
'automation'=> '<circle cx="12" cy="12" r="3"/><path d="M12 1v6m0 10v6M4.22 4.22l4.24 4.24m6.36 6.36l4.24 4.24M1 12h6m10 0h6M4.22 19.78l4.24-4.24m6.36-6.36l4.24-4.24"/>',
'pin' => '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/>',
'arrow' => '<line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/>',
'users' => '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
'refresh' => '<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>',
'shield' => '<path d="M12 2l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V6l8-4z"/><path d="M9 12l2 2 4-4"/>',
'phone' => '<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/>',
];
if (!isset($paths[$name])) {
return '';
}
return '<svg ' . $attr . ' aria-hidden="true">' . $paths[$name] . '</svg>';
}
/** Vrátí plnou žlutou hvězdičku (hodnocení) */
function icon_hvezda(int $size = 14): string
{
return '<svg width="' . $size . '" height="' . $size . '" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">'
. '<path d="M12 2l2.9 6.4 7.1.6-5.4 4.6 1.6 7-6.2-3.8L6 20.6l1.6-7L2.2 9l7.1-.6z"/></svg>';
}
/** Ikony sociálních sítí */
function icon_socialni(string $site, int $size = 18): string
{
$attr = 'width="' . $size . '" height="' . $size . '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
if ($site === 'facebook') {
return '<svg ' . $attr . ' aria-hidden="true"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>';
}
if ($site === 'instagram') {
return '<svg ' . $attr . ' aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="0.8" fill="currentColor" stroke="none"/></svg>';
}
return '';
}
/**
* Centrální definice editovatelných textů veřejné stránky.
* klíč => [label pro administraci, výchozí text, typ pole, skupina]
*/
function definice_obsahu(): array
{
return [
'hero_badge' => ['label' => 'Odznak', 'vychozi' => 'IT partner pro firmy bez vlastního IT', 'typ' => 'text', 'skupina' => 'Hero'],
'hero_nadpis' => ['label' => 'Nadpis', 'vychozi' => 'IT, na které se můžete spolehnout', 'typ' => 'text', 'skupina' => 'Hero'],
'hero_popis' => ['label' => 'Popis pod nadpisem', 'vychozi' => 'Správa IT, webové stránky i rezervační systémy na míru pro malé a střední firmy. Osobně i na dálku, dlouhodobě.', 'typ' => 'textarea', 'skupina' => 'Hero'],
'sluzby_nadpis' => ['label' => 'Nadpis sekce', 'vychozi' => 'Co dalšího nabízíme', 'typ' => 'text', 'skupina' => 'Služby'],
'helpdesk_nadpis' => ['label' => 'Nadpis sekce', 'vychozi' => 'Jak funguje náš helpdesk', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'krok1_nadpis' => ['label' => 'Krok 1 – nadpis', 'vychozi' => 'Zadáte tiket', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'krok1_popis' => ['label' => 'Krok 1 – popis', 'vychozi' => 'Přes náš online helpdesk', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'krok2_nadpis' => ['label' => 'Krok 2 – nadpis', 'vychozi' => 'Převezmeme ho', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'krok2_popis' => ['label' => 'Krok 2 – popis', 'vychozi' => 'Vždy jeden z nás dvou', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'krok3_nadpis' => ['label' => 'Krok 3 – nadpis', 'vychozi' => 'Vyřešíme', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'krok3_popis' => ['label' => 'Krok 3 – popis', 'vychozi' => 'Vzdáleně nebo u vás', 'typ' => 'text', 'skupina' => 'Helpdesk'],
'procmy_nadpis' => ['label' => 'Nadpis sekce', 'vychozi' => 'Proč my', 'typ' => 'text', 'skupina' => 'Proč my'],
'procmy1_tucne' => ['label' => 'Řádek 1 – tučná část', 'vychozi' => 'Vždy mluvíte s tím, kdo to řeší', 'typ' => 'text', 'skupina' => 'Proč my'],
'procmy1_text' => ['label' => 'Řádek 1 – zbytek věty', 'vychozi' => '– žádné call centrum', 'typ' => 'text', 'skupina' => 'Proč my'],
'procmy2_tucne' => ['label' => 'Řádek 2 – tučná část', 'vychozi' => 'Dlouhodobá spolupráce', 'typ' => 'text', 'skupina' => 'Proč my'],
'procmy2_text' => ['label' => 'Řádek 2 – zbytek věty', 'vychozi' => '– poznáme vaši infrastrukturu', 'typ' => 'text', 'skupina' => 'Proč my'],
'procmy3_tucne' => ['label' => 'Řádek 3 – tučná část', 'vychozi' => 'Bezpečnost a zálohy', 'typ' => 'text', 'skupina' => 'Proč my'],
'procmy3_text' => ['label' => 'Řádek 3 – zbytek věty', 'vychozi' => 'jako samozřejmost', 'typ' => 'text', 'skupina' => 'Proč my'],
'reference_nadpis' => ['label' => 'Nadpis sekce', 'vychozi' => 'Co o nás říkají zákazníci', 'typ' => 'text', 'skupina' => 'Reference'],
'kontakt_nadpis' => ['label' => 'Nadpis', 'vychozi' => 'Pojďme probrat vaše IT', 'typ' => 'text', 'skupina' => 'Kontakt'],
'kontakt_popis' => ['label' => 'Popis', 'vychozi' => 'Bez závazků, do druhého dne se ozveme.', 'typ' => 'text', 'skupina' => 'Kontakt'],
];
}
/** Vrátí pole klíč => aktuální hodnota (uložená v DB, nebo výchozí text) pro všechny editovatelné texty */
function nacti_obsah(PDO $pdo): array
{
$definice = definice_obsahu();
$ulozeno = nacti_vsechna_nastaveni($pdo);
$vysledek = [];
foreach ($definice as $klic => $info) {
if (array_key_exists($klic, $ulozeno) && $ulozeno[$klic] !== '') {
$vysledek[$klic] = $ulozeno[$klic];
} else {
$vysledek[$klic] = $info['vychozi'];
}
}
return $vysledek;
}
/** Zpracuje nahraný obrázek, zmenší ho a uloží do assets/img/uploads/
* Vrátí relativní cestu k souboru (assets/img/uploads/xxx.png) nebo '' při chybě.
* $maxW/$maxH = max rozměry výsledku v pixelech
*/
function zpracuj_nahrani_obrazku(array $soubor, string $prefix, int $maxW = 64, int $maxH = 64): string
{
if (!isset($soubor['tmp_name']) || $soubor['error'] !== UPLOAD_ERR_OK) {
return '';
}
$povolene = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
$mime = mime_content_type($soubor['tmp_name']);
if (!in_array($mime, $povolene, true)) {
return '';
}
$slozka = __DIR__ . '/assets/img/uploads/';
if (!is_dir($slozka)) {
mkdir($slozka, 0755, true);
}
$pripona = $mime === 'image/svg+xml' ? 'svg' : 'png';
$nazev = $prefix . '_' . uniqid() . '.' . $pripona;
$cil = $slozka . $nazev;
// SVG ukládáme přímo bez změny velikosti
if ($mime === 'image/svg+xml') {
if (!move_uploaded_file($soubor['tmp_name'], $cil)) {
return '';
}
return 'assets/img/uploads/' . $nazev;
}
// Rastrové obrázky zmenšíme přes GD
if (!extension_loaded('gd')) {
// GD není k dispozici - uložíme as-is
if (!move_uploaded_file($soubor['tmp_name'], $cil)) {
return '';
}
return 'assets/img/uploads/' . $nazev;
}
$zdroj = match($mime) {
'image/jpeg' => imagecreatefromjpeg($soubor['tmp_name']),
'image/gif' => imagecreatefromgif($soubor['tmp_name']),
'image/webp' => imagecreatefromwebp($soubor['tmp_name']),
default => imagecreatefrompng($soubor['tmp_name']),
};
if (!$zdroj) {
return '';
}
$sirkaSrc = imagesx($zdroj);
$vyskaSrc = imagesy($zdroj);
$pomer = min($maxW / $sirkaSrc, $maxH / $vyskaSrc, 1.0);
$novaS = (int) round($sirkaSrc * $pomer);
$novaV = (int) round($vyskaSrc * $pomer);
$vystup = imagecreatetruecolor($novaS, $novaV);
// Průhlednost pro PNG
imagealphablending($vystup, false);
imagesavealpha($vystup, true);
$pruhledna = imagecolorallocatealpha($vystup, 0, 0, 0, 127);
imagefilledrectangle($vystup, 0, 0, $novaS, $novaV, $pruhledna);
imagecopyresampled($vystup, $zdroj, 0, 0, 0, 0, $novaS, $novaV, $sirkaSrc, $vyskaSrc);
imagepng($vystup, $cil, 9);
imagedestroy($zdroj);
imagedestroy($vystup);
return 'assets/img/uploads/' . $nazev;
}
/** Smaže soubor z uploads složky, pokud existuje */
function smaz_soubor_obrazku(string $relativniCesta): void
{
if ($relativniCesta === '') return;
$plnaCesta = __DIR__ . '/' . $relativniCesta;
if (file_exists($plnaCesta) && str_starts_with(realpath($plnaCesta), __DIR__ . '/assets/img/uploads')) {
unlink($plnaCesta);
}
}
function seznam_ikon_sluzeb(): array
{
return [
'code' => 'Kód (web)',
'desktop' => 'Počítač',
'database' => 'Databáze / zálohy',
'mail' => 'Obálka (e-mail)',
'automation' => 'Ozubené kolo (automatizace)',
'headset' => 'Sluchátka (podpora)',
'pin' => 'Pin (poloha)',
'shield' => 'Štít (zabezpečení)',
'refresh' => 'Cyklus / obnova',
'users' => 'Lidé',
'phone' => 'Telefon',
'calendar' => 'Kalendář',
'ticket' => 'Tiket',
'check' => 'Fajfka',
];
}
/**
* Rate limiting – max 3 odeslání formuláře z jedné IP za hodinu.
* Záznamy se ukládají do tabulky antispam_log.
* Vrátí true pokud je IP blokována.
*/
function je_ip_blokovana(PDO $pdo, string $ip): bool
{
// Vyčistíme staré záznamy (starší než 1 hodina)
$pdo->prepare('DELETE FROM antispam_log WHERE cas < NOW() - INTERVAL 1 HOUR')->execute();
$stmt = $pdo->prepare('SELECT COUNT(*) FROM antispam_log WHERE ip = :ip');
$stmt->execute(['ip' => $ip]);
$pocet = (int) $stmt->fetchColumn();
return $pocet >= 3;
}
function zaznamenat_odeslani(PDO $pdo, string $ip): void
{
$pdo->prepare('INSERT INTO antispam_log (ip) VALUES (:ip)')->execute(['ip' => $ip]);
}
/**
* Detekce spamu v textu – vrátí true pokud zpráva vypadá jako spam.
*/
function je_spam(string $jmeno, string $zprava, string $email): bool
{
$logSoubor = __DIR__ . '/spam_debug.log';
$log = function(string $zprava) use ($logSoubor) {
file_put_contents($logSoubor, date('Y-m-d H:i:s') . ' ' . $zprava . "\n", FILE_APPEND);
};
if (preg_match('/https?:\/\//i', $zprava)) { $log('SPAM: URL v zprave: ' . $zprava); return true; }
if (str_contains($jmeno, '@')) { $log('SPAM: email v jmenu: ' . $jmeno); return true; }
if (mb_strlen(trim($zprava)) < 5) { $log('SPAM: kratka zprava: ' . $zprava); return true; }
if (preg_match('/[\x{0370}-\x{03FF}\x{0400}-\x{04FF}\x{0600}-\x{06FF}\x{4E00}-\x{9FFF}]/u', $zprava)) { $log('SPAM: cizi znaky v zprave: ' . $zprava); return true; }
if (preg_match('/^\+?\d{8,}$/', preg_replace('/\s/', '', $jmeno))) { $log('SPAM: tel cislo jako jmeno: ' . $jmeno); return true; }
$spamSlova = ['casino', 'viagra', 'cialis', 'poker', 'loan', 'credit', 'crypto', 'bitcoin', 'investment', 'prize', 'winner', 'click here', 'buy now'];
$textKontrolovat = strtolower($jmeno . ' ' . $zprava);
foreach ($spamSlova as $slovo) {
if (str_contains($textKontrolovat, $slovo)) { $log('SPAM: klic. slovo: ' . $slovo); return true; }
}
$log('OK: jmeno=' . $jmeno . ' zprava=' . mb_substr($zprava, 0, 50));
return false;
}