Kora CMS je redakční systém v čistém PHP bez frameworku. Je určený pro osobní weby, blogy, obce, spolky, menší firmy a komunitní projekty. Klade důraz na stabilní provoz, přístupnost (WCAG 2.2 AA) a praktickou administraci.
- Proč Kora CMS?
- Požadavky
- Instalace
- Konfigurace
- Aktualizace
- Plánované úlohy (cron.php)
- Přehled modulů
- Šablony a vzhled
- Uživatelské role
- HTML editor a snippety
- Navigace webu
- Widgety
- Bezpečnost
- Přístupnost
- Zálohování a údržba
- Vývoj a CI
- Vývoj nových modulů
- Řešení problémů
- Nginx
- Ověření po změnách
- Licence
- Veřejný changelog
- Další dokumentace
Název Kora je krátký, dobře zapamatovatelný a záměrně českému uchu blízký. Evokuje kůru stromu: pevnou ochrannou vrstvu, která drží živý obsah pohromadě, ale nepřekáží růstu. Stejně má Kora CMS chránit a uspořádat obsah webu, zůstat stabilní, čitelný a přístupný, a přitom nevnucovat zbytečně složitý framework tam, kde stačí poctivě napsané PHP.
| Komponenta | Minimální verze | Doporučená verze |
|---|---|---|
| PHP | 8.0 | 8.4 |
| MySQL | 5.7 | 8.0+ |
| MariaDB | 10.3 | 11+ |
| Apache | 2.4 | 2.4+ |
| Nginx | podporováno | aktuální |
Vyžadovaná PHP rozšíření:
pdo+pdo_mysqlmbstringfileinfogdzip
Volitelná rozšíření:
curl– povinné pro bezpečný downloader fotografií z eStránek; ostatní části CMS jej nevyžadují
Zkopírujte obsah projektu do kořenového adresáře webu nebo do podsložky.
Pro lokální vývoj doporučujeme Laragon s PHP 8.4.
CREATE DATABASE nazev_databaze CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;cp config.sample.php config.phpVyplňte přístup k databázi a základní URL:
$server = 'localhost';
$user = 'root';
$pass = '';
$database = 'nazev_databaze';
define('BASE_URL', '');Pokud web běží v podsložce, nastavte například:
define('BASE_URL', '/koracms');http://vas-web.cz/install.php
Při instalaci zvolte profil webu:
- Osobní web – osobní stránky s blogem
- Blog / magazín – zaměřeno na články a autory
- Obec / spolek – vývěska, události, dokumenty
- Služby / firma – prezentace služeb a kontakt
- Vlastní profil – neutrální stav pro ruční nastavení
Profily přednastaví doporučené moduly, homepage widgety, navigaci a šablonu.
Po instalaci soubor odstraňte ze serveru.
http://vas-web.cz/admin/login.php
Veškerá konfigurace je v souboru config.php. Vzorový soubor config.sample.php obsahuje všechny dostupné konstanty s komentáři.
Základní vývojová kontrola composer ci:basic hlídá přes build/config_sample_audit.php a jeho self-test build/config_sample_audit_selftest.php, aby tento vzor neztratil databázové proměnné, hlavní runtime konstanty, bezpečné prázdné tokeny ani vysvětlení pro privátní úložiště, SMTP, GitHub issue bridge a cron token.
Kora CMS používá e-maily pro registraci, obnovu hesla, newsletter, rezervace, formuláře a interní notifikace. Vzorový config.sample.php používá bezpečný lokální default localhost:25 bez autentizace; pro produkci hodnoty nahraďte údaji od svého poskytovatele SMTP.
define('SMTP_HOST', 'smtp.example.com');
define('SMTP_PORT', 587);
define('SMTP_USER', 'uzivatel@example.com');
define('SMTP_PASS', 'heslo-nebo-app-password');
define('SMTP_SECURE', 'tls'); // '', 'tls', 'ssl'Pro dobrou doručitelnost doporučujeme mít na doméně nastavené záznamy SPF, DKIM a DMARC.
Citlivé přílohy formulářů, importní dávky, snapshot integrity a denní zálohy databáze se ukládají mimo webroot. Výchozí cesta je ../kora_storage. Vlastní cestu nastavíte přes:
define('KORA_STORAGE_DIR', '/cesta/mimo/webroot/kora_storage');Propojení formulářových odpovědí s GitHub Issues vyžaduje fine-grained token s oprávněním Issues: Read and write:
define('GITHUB_ISSUES_TOKEN', 'ghp_...');Repozitář se nastavuje v administraci v sekci Obecná nastavení.
GA4 Measurement ID zadejte v administraci: Obecná nastavení → Google Analytics a vlastní kód. CMS automaticky vloží gtag.js do hlavičky webu.
V administraci: Obecná nastavení → Provoz webu → Zapnout režim údržby. Návštěvníci uvidí stránku s vlastním textem a HTTP kódem 503. Přihlášení administrátoři vidí web normálně.
Pokud nemáte CLI přístup a musíte používat webový cron, nastavte token:
define('CRON_TOKEN', 'dlouhy-nahodny-retezec');Podrobnosti viz Plánované úlohy.
Přepište stávající soubory novou verzí. config.php nepřepisujte.
http://vas-web.cz/migrate.php
Přihlaste se jako superadmin. Migrace doplní nové tabulky, sloupce a výchozí nastavení bez přepisu existujících dat. Čerstvá instalace přes install.php má aktuální schéma a migrate.php po ní není potřeba.
Po dokončení migrace soubor odstraňte.
Endpoint cron.php zajišťuje pravidelné úlohy na pozadí:
- zrušení publikace obsahu podle naplánovaného data
- úklid dočasných souborů, starých audit logů a starých CSP report logů
- automatickou denní zálohu databáze
*/5 * * * * php /cesta/k/webu/cron.phpNa Windows Serveru použijte Plánovač úloh:
php C:\cesta\k\webu\cron.php
Vyžaduje nastavení CRON_TOKEN v config.php:
curl "https://vas-web.cz/cron.php?token=VAS_CRON_TOKEN"Po nastavení doporučujeme spustit cron jednou ručně z příkazové řádky a zkontrolovat výstup.
Moduly se zapínají a vypínají v administraci: Obecná nastavení → Správa modulů.
| Modul | Co umí |
|---|---|
| Blogy | Více blogů v jedné instalaci, týmy blogů, články, kategorie a štítky s veřejnými landing stránkami a SEO poli, čisté měsíční archivy, komentáře, plánované publikování, série článků, ručně řízené související články, veřejní autoři, autorský obsahový hub, globální, per-blog i kategoriové/štítkové RSS feedy |
| Novinky | Krátké zprávy s autorem, slug URL, veřejným hledáním, autorským filtrem, plánovaným skrytím a SEO fallbacky |
| Události | Přehled akcí s typy, místy konání, opakovanými termíny, detailem a ICS exportem do kalendáře |
| Galerie | Alba a fotografie s detailovými URL, hledáním, stránkováním, revizemi, fotografickými metadaty a bezpečným media endpointem |
| Podcasty | Více pořadů, hledání a kategoriové filtrování, epizody a sezóny, odkazy na poslechové platformy, hosté a tvůrci, přepisy, kapitoly Podcasting 2.0, stabilní RSS GUID, kontrola feedu a revize |
| Zajímavá místa | Adresář s typem místa, adresou, GPS a otevírací dobou |
| Ke stažení | Katalog dokumentů a software s kategoriemi, landing stránkami, sériemi verzí, kompatibilitou, historií změn, veřejnými filtry a oddělenými statistikami lokálních i externích zdrojů |
| Appmarket | Vlastní katalog Android aplikací s historií vydání, soukromým úložištěm APK, kontrolou podpisu, anonymním update API a bezpečným publikačním workflow |
| Jídelní lístek | Karty jídel a nápojů s platností, denními nabídkami, strukturovanými položkami, cenami, alergeny, nutričními údaji, obrázky, veřejnými filtry, objednávkovými poptávkami, archivem, hledáním a revizemi |
| Recepty | Strukturované recepty s přesnými množstvími, přepočtem porcí, nákupním seznamem, vratnou historií postupu, veřejnými filtry, Recipe JSON-LD a kuchařkou EPUB |
| Ankety | Jedno- i vícevýběrové hlasování s plánováním, řízenou viditelností výsledků, CSV exportem, fulltextem, slug URL, SEO fallbacky a revizemi |
| Znalostní báze | FAQ s veřejnými kategoriemi, hledáním, stránkováním, SEO, zpětnou vazbou a FAQPage strukturovanými daty |
| Formuláře | Form Builder s přílohami, podmínkami, helpdesk workflow, webhooky a GitHub issue bridge |
| Vývěska | Úřední deska s typem položky, datem vyvěšení, připnutím, filtrováním, archivem, kategoriovými landing stránkami, evidencí zveřejnění a bezpečným odběrem |
| Rezervace | Zdroje, kategorie, lokality, kalendáře, schvalování, připomínky, ICS pozvánky, historie změn a storno přes token |
| Statické stránky | Vlastní stránky se slug URL, volitelným zobrazením v navigaci a blogovými stránkami se slugem unikátním jen v rámci konkrétního blogu |
| Kontakt | Kontaktní formulář s tématy dotazů, CAPTCHA, honeypotem, rate limitingem, referenčními kódy a odpověďmi z administrace |
| Chat | Moderovaná veřejná nástěnka s tématy, připnutými zprávami, vlákny a soukromým podpůrným inboxem |
| Newsletter | Odběr e-mailem s potvrzením, odhlášením a historií rozesílek |
README drží jen vysokou úroveň: co CMS umí, jak se instaluje, konfiguruje a provozuje. Podrobné administrační workflow, volby formulářů, podcastů a multiblogu jsou záměrně v docs/admin-guide.md.
Modul Ke stažení pokrývá praktičtější katalogový scénář: doporučené položky, datum vydání, domovskou stránku projektu, požadavky a kompatibilitu, SHA-256 checksum, oddělené počítání lokálních stažení a otevření externích zdrojů, historii revizí a veřejné filtrování podle kategorie, typu, platformy a zdroje. Položka může být lokální soubor, externí odkaz například na GitHub Releases, nebo obojí zároveň, takže vlastní software není nutné duplikovat do CMS; externí URL lze zadat jako http:///https:// adresu nebo doménu bez schématu, kterou CMS uloží jako https://. Veřejné externí odkazy procházejí bezpečným přesměrováním, jehož cíl se vždy načte z uložené položky; čítač vyjadřuje otevření externího zdroje, nikoli potvrzené dokončení downloadu na cizím webu. Kategorie mají čisté landing URL /downloads/kategorie/{slug}, volitelný popis a SEO metadata. Sériová vydání se spravují přes samostatné série/verze s URL /downloads/serie/{slug}; detail starší položky umí upozornit na aktuální verzi a starý series_key zůstává kompatibilní pro importy i starší data. Smazání kategorie nebo série nejdřív ukáže dopad na navázané položky a vyžaduje potvrzení; položky zůstanou zachované, jen se zruší dané zařazení.
Modul Appmarket je specializovaný na distribuci produkčních Android APK a aktualizace aplikací. Není omezený na konkrétní projekty ani počet aplikací: každá aplikace má stabilní applicationId, automaticky generovatelný slug, veřejnou stránku /aplikace/{slug}, historii vydání a anonymní endpoint /api/appmarket/v1/update, kterému klient posílá jen package ID a svůj versionCode. APK se ukládají do privátního úložiště mimo webroot a stahují se přes serverový endpoint, který při každém požadavku znovu ověří velikost i SHA-256 a podporuje GET/HEAD a jeden HTTP Range bez dlouhodobé neměnné cache. Obecný lokální publisher tools/appmarket-publish.ps1 je součástí release ZIPu a umí v libovolném čistém Android Git projektu najít produkční release APK, ověřit jej nástroji apkanalyzer a apksigner, podepsat přesný manifest samostatným RSA-3072 publisher klíčem a odeslat koncept na /api/appmarket/v1/releases. Android SDK tedy potřebuje pouze důvěryhodný lokální počítač; hosting vyžaduje běžné PHP rozšíření OpenSSL a nezávisle ověří podpis manifestu, jeho čerstvost, vazbu na publikační token, velikost i SHA-256 APK. Token a cestu k privátnímu publisher klíči přijímá skript pouze z procesních proměnných KORA_APPMARKET_TOKEN a KORA_APPMARKET_SIGNING_KEY; soukromý zdrojový repozitář, Android keystore ani privátní publisher klíč se do CMS neposílají. Server žádné vydání nepublikuje automaticky: správce nejprve schválí podpisový certifikát APK a na samostatné kontrolní obrazovce porovná identitu, podpis, oprávnění, hash a bezpečně vykreslený seznam změn. Zneplatnění certifikátu okamžitě stáhne všechna jeho veřejná vydání.
Modul Události podporuje spravované typy akcí s veřejnou adresou /events/typ/{slug}, popisem a SEO poli. Událost lze volitelně navázat na veřejné místo z modulu Zajímavá místa a detail pak zobrazí kartu místa, odkaz na detail i mapu, pokud jsou dostupné. Při vytváření nové události lze jednorázově vygenerovat opakované denní, týdenní nebo měsíční termíny; CMS z nich vytvoří samostatné události se společnou skupinou opakování, takže pozdější úprava jednoho termínu se automaticky nepropíše do ostatních.
Modul Znalostní báze umí veřejné hledání, filtrování podle kategorie, stránkování, přepínání karty / rozbalené odpovědi, per-FAQ SEO metadata, redirecty při změně slugu a FAQPage strukturovaná data pro vyhledávače. Kategorie FAQ mají vlastní landing stránky /faq/kategorie/{slug} s volitelným popisem a SEO poli; staré filtry ?kat= zůstávají funkční. Detail otázky může od návštěvníků sbírat jednoduchou zpětnou vazbu Pomohla vám tato odpověď?, kterou správce vidí jen v administraci jako redakční signál.
Modul Novinky nově drží stejný publikační model jako ostatní obsahové moduly: respektuje unpublish_at, podporuje veřejné fulltextové hledání, admin stránkování, redirecty po změně slugu, širší revize a volitelná SEO pole meta title a meta description.
Veřejné profily autorů fungují jako obsahové huby. Na /author/slug-autora se zobrazuje medailonek autora a publikovaný obsah napříč zapnutými moduly Blogy a Novinky; návštěvník může přepínat Vše, Články a Novinky. Přehled /authors/ ukazuje souhrn obsahu autora podle zapnutých modulů a novinky podporují filtr news/index.php?autor=slug.
Modul Jídelní lístek nově rozlišuje platné nyní / připravované / archivní lístky podle Platí od / do, podporuje veřejné hledání, scope filtry, stránkování archivu, redirecty při změně slugu, historii revizí a structured data pro detail lístku. Správce může u konkrétního lístku spravovat strukturované sekce a položky včetně ceny, měny, poznámky k ceně, alergenů 1-14, dietních štítků, dostupnosti, nutričních údajů a volitelného obrázku z knihovny médií. Sekce mohou nést datum a čas podávání, takže detail i archiv umí denní nabídky přes filtr den=YYYY-MM-DD. Veřejný web umí filtrovat strukturované položky podle dietních štítků, vyloučených alergenů a dostupnosti a u viditelných položek zobrazuje alergenovou legendu. Pokud strukturované položky existují, veřejný web je zobrazí jako hlavní menu a původní HTML obsah použije jako doplňkové poznámky; bez položek zůstává starý HTML obsah kompatibilním fallbackem. Lístek může volitelně povolit nezávazné objednávkové poptávky chráněné captchou, honeypotem a rate-limitem. Administrace umožní sledovat detail poptávky a měnit její stav; stavová změna před uložením ukáže referenční kód, aktuální stav a interní dopad, vyžádá si samostatné potvrzení a výslovně upozorní, že zákazníkovi automaticky neposílá e-mail.
Modul Recepty poskytuje strukturovanou kuchařku bez nutnosti skládat recept z volného HTML. Správce nejprve uloží základní údaje jako koncept, potom v samostatném jednoduchém editoru doplní skupiny ingrediencí, přesná množství nebo rozsahy a řazené kroky postupu. Publikace je povolená až s aktivní kategorií, alespoň jednou ingrediencí a jedním krokem. Volitelné porce, časy a kalorie se zobrazí jen tehdy, když je správce skutečně zadá; CMS je neodhaduje. Před změnou struktury vzniká obnovitelná verze a celý recept lze po výslovném potvrzení duplikovat jako nový koncept.
Veřejný katalog /recipes nabízí hledání včetně ingrediencí, filtry náročnosti, dietních vlastností, maximálního celkového času a vyloučených alergenů, kategoriové landing stránky /recipes/kategorie/{slug}, Recipe JSON-LD, tisk detailu a stažení celé nebo kategoriové kuchařky EPUB 3. Na detailu lze přepočítat pouze přesně zadaná číselná množství na 1–100 porcí; slovní množství se nemění. Vybrané recepty lze přidat do soukromého session-only nákupního seznamu /recipes/nakupni-seznam, který zachová recepty oddělené, nic neodhaduje a neposílá seznam do databáze. EPUB používá český jazyk, sémantické nadpisy, seznamy, navigaci na kategorie i jednotlivé recepty a přístupnostní metadata. Běžný tisk stránky do PDF zůstává funkcí prohlížeče a bez samostatného ověření se neoznačuje jako přístupné tagované PDF.
Modul Galerie nově chrání neveřejná alba i fotografie i na úrovni detailu, vyhledávání a sitemapy, používá bezpečný media endpoint místo přímých /uploads/gallery/ cest, podporuje redirecty po změně slugu, historii revizí, veřejné hledání, stránkování alb i detailu a structured data pro alba i fotografie. Fotografie mohou mít samostatný alt text, viditelný popisek, delší popis, kredit, licenci, datum a místo pořízení; alba mohou nastavit výchozí kredit a licenci pro nově nahrané fotografie a veřejný detail metadata zobrazí v přístupné sekci.
Modul Chat funguje jako moderovaná veřejná nástěnka se strukturovanými tématy. Nové veřejné zprávy se nejdřív ukládají ke schválení, schválené zprávy lze připnout nahoru a každá veřejná zpráva má vlastní detail /chat/zprava/{id} s moderovanými odpověďmi. Témata mají čisté URL /chat/tema/{slug} a v administraci vlastní správu. Stejný veřejný formulář umí i soukromý dotaz správci: vyžaduje e-mail pro odpověď, uloží referenční kód CHT-YYYYMMDD-XXXX a nikdy se nezobrazí ve veřejném chatu ani sitemapě.
Modul Kontakt nově funguje jako lehké kontaktní centrum: správce může vytvořit témata dotazů s vlastním popisem a volitelným cílovým e-mailem, návštěvník po odeslání uvidí referenční kód zprávy a administrátor může odpovědět e-mailem přímo z detailu kontaktní zprávy. Bez aktivních témat zůstává veřejný formulář jednoduchý jako dříve.
Individuální odpovědi z detailu Kontaktu, soukromého Chatu a Form Builderu používají jednotnou kontrolu odeslání. Administrace předem ukáže příjemce a dopad, u Form Builderu také interní historii a případný webhook reply_sent, zachová rozepsaný předmět i text po chybě a server bez dynamického potvrzovacího checkboxu e-mail ani navazující zápisy neprovede.
Modul Vývěska nově podporuje důvěryhodnější veřejný archiv: detail položky ukazuje evidenci zveřejnění včetně změn URL, příloh a SHA-256 otisku souboru, kategorie mají čisté URL /board/kategorie/{slug} s popisem a SEO metadata a samostatný odběr vývěsky je oddělený od newsletteru. Přihlášení k odběru probíhá na /board/subscribe.php, vyžaduje captcha, rate-limit a potvrzení e-mailem.
Modul Rezervace kromě zdrojů, kategorií, lokalit, schvalování a storna přes token podporuje kalendářové .ics pozvánky a e-mailové připomínky před potvrzeným termínem. Správce je zapíná u konkrétního zdroje, volí počet hodin předem a může doplnit vlastní text. Editor zdroje zobrazuje klientské chyby generátoru slotů a blokovaných dnů jako inline textové alerty místo prohlížečových modálních oken. Administrace u rezervace uchovává provozní historii změn; změny stavů jako schválení, zamítnutí, zrušení, dokončení a no-show vyžadují review text a serverově ověřený potvrzovací checkbox. Veřejná část Moje rezervace nabízí u budoucích potvrzených termínů bezpečné tokenové stažení kalendářového souboru. Export/import přenáší konfiguraci rezervačních zdrojů, ale ne osobní rezervace ani jejich historii.
Knihovna Média nově rozlišuje veřejné a soukromé soubory, odmítá nové SVG uploady, podporuje WebVTT soubory pro titulky i zvukový popis ve video snippetech, používá canonical media helpery místo ručně skládaných /uploads/media/... URL, blokuje mazání používaných souborů a u mazání nepoužitého souboru vyžaduje review dopadu a potvrzení confirm_media_delete_<id>. Podporuje také náhradu souboru, kolekce médií, rozšířená metadata, licenční údaje i hromadné akce v administraci. Kolekce mohou mít výchozí viditelnost, kredit a licenci pro nové uploady; přehled médií umí filtrovat podle kolekce i chybějících metadat. Maximální velikost běžných uploadů lze nastavit v obecných nastaveních webu a CMS ji používá i jako výchozí limit nových souborových polí ve Form Builderu. Souborové přesuny, náhrady a úklid miniatur se logují strukturovaně bez fyzických cest. Admin obrazovka médií používá pro upload, filtry, grid, hromadné akce a detail metadat sdílenou CSS vrstvu bez lokálních style atributů.
Modul Ankety používá stejný publikační helper jako widgety, sitemapa a vyhledávání, takže aktivní a archivní ankety drží konzistentní veřejnou viditelnost. Součástí modulu je veřejné fulltextové hledání, stránkování indexu, redirect při změně slugu, širší revize a volitelná SEO pole meta title a meta description. Správce může zvolit režim jedna možnost nebo více možností, nastavit limit vybraných odpovědí, rozhodnout, kdy se veřejně ukážou výsledky, a v administraci stáhnout bezpečný CSV export agregovaných výsledků bez raw hashů hlasujících až po review tématu ankety a potvrzení oprávnění ke stažení.
V multiblog administraci nově umí přesun článků mezi blogy vedle automatického vyrovnání taxonomií i ruční mapování chybějících kategorií a štítků na existující taxonomie cílového blogu. Tato volba je dostupná jen uživatelům, kteří smějí spravovat taxonomie cílového blogu. Stejné principy nově používá i běžná editace jednoho článku: po změně blogu editor automaticky předvyplní odpovídající taxonomie cílového blogu, dovolí ručně vybrat jiné existující a správcům taxonomií umí přímo z editoru vytvořit chybějící kategorii nebo štítky.
Blogy nově podporují i volitelný alternativní text loga. Pokud ho správce vyplní, použije se na veřejném indexu blogu pro čtečky obrazovky; pokud zůstane prázdný, logo se dál bere jako dekorativní a asistivní technologie ho přeskočí.
Veřejný index blogu zobrazuje doporučený článek v přirozenějším pořadí: nejdřív nadpis bloku, potom název článku jako hlavní odkaz a až pod ním datum, přibližnou dobu čtení, počet přečtení a autora.
Editor článku umožňuje ručně vybrat související články ze stejného blogu. Veřejný detail článku zobrazí ruční výběr jako první a pokud je položek méně, doplní zbytek automaticky podle kategorie, štítků a novosti. Při změně blogu se ruční výběr validuje proti cílovému blogu, takže se do detailu nepropíše odkaz na článek z cizího blogu.
Blogy mají také tematické série článků. Správa série patří ke konkrétnímu blogu, umožňuje název, slug, popis, aktivní stav a ruční pořadí článků. Editor článku pak nabídne zařazení jen do sérií cílového blogu, textově oznamuje počet vybraných sérií a umožní zařazení jedním tlačítkem odebrat ze všech sérií. Čtenář na detailu článku uvidí blok Tento článek je součástí série, aktuální díl je označený pro asistivní technologie a veřejná stránka série má URL /{blog-slug}/serie/{series-slug}.
Hromadné odebrání článků v přehledu je vratný přesun do Koše. Správce před odesláním vidí, že se zachovají obrázky, komentáře, štítky, série, související články, revize a redirecty, a musí potvrdit confirm_article_bulk_delete. Server ověří celý výběr včetně autorského a blogového oprávnění; pokud jediný článek není dostupný, neprovede se žádná část akce. Potvrzená změna nastaví transakčně jen deleted_at, vrátí textový PRG stav a článek lze se všemi vazbami obnovit z Koše.
Uložený článek má v editoru sekci Sdílený náhled článku. Náhled lze samostatně aktivovat, obnovit nebo zneplatnit; každá změna vyžaduje kontrolní potvrzení a respektuje autora i přístup do konkrétního blogu. Obnovením se vytvoří nový tajný odkaz a všechny staré odkazy přestanou fungovat. Zneplatněný náhled běžné uložení článku znovu neaktivuje. Náhledové odpovědi se necachují, neindexují a neposílají token jako HTTP referrer.
Delší blogové články dostanou na veřejném detailu automatickou osnovu V tomto článku, pokud obsah obsahuje alespoň dva viditelné nadpisy h2 nebo h3. Kora CMS doplní stabilní kotvy k nadpisům, ručně zadaná id zachová a odkazy v osnově pomáhají čtenářům i čtečkám obrazovky rychle přeskakovat mezi částmi článku.
Kategorie a štítky blogu nejsou jen interní filtry. Správce k nim může vyplnit veřejný slug, popis, meta title a meta description. Veřejné stránky mají čisté adresy /{blog-slug}/kategorie/{category-slug} a /{blog-slug}/stitky/{tag-slug}, zobrazují popis nad výpisem článků a používají vlastní canonical/SEO metadata. Staré query odkazy ?kat= a ?tag= zůstávají kompatibilní. Každá landing stránka nabízí vlastní pojmenovaný RSS kanál jen pro danou kategorii nebo štítek a zároveň zachovává samostatný odkaz na RSS celého blogu.
Blog zároveň automaticky chrání staré veřejné adresy. Když se změní slug nebo blog publikovaného článku, slug kategorie, slug štítku nebo slug aktivní série, CMS uloží trvalé 301 přesměrování ze staré URL na nový canonical tvar přes běžnou správu přesměrování. Při destruktivním smazání článku se redirecty mířící na zaniklou článkovou URL uklidí, aby nevznikaly slepé odkazy. Při vratném hromadném přesunu do Koše i při převodu zůstávají redirecty spolu se zdrojovým článkem zachované a lze je znovu využít po jeho obnovení.
Statické stránky přiřazené k blogu používají adresu /{blog-slug}/stranka/{page-slug}, proto musí být slug stránky jedinečný jen v rámci daného blogu. Stejný slug lze použít v jiném blogu bez kolize; globální statické stránky mimo blog zůstávají unikátní mezi sebou.
Přehled blogů v administraci nově nabízí přímé odkazy na články, kategorie, štítky a stránky konkrétního blogu. Převodové akce Článek → Stránka a Stránka → Článek zároveň ponechávají šipku jen jako vizuální pomůcku; čtečky obrazovky teď hlásí jen samotný název akce bez dekorativní šipky. Převod nejdřív otevře serverovou kontrolu zdroje, cíle, změny veřejné adresy a přenášených dat a vyžaduje samostatné potvrzení. Cíl se vytvoří transakčně a původní záznam se přesune do Koše místo fyzického smazání; komentáře a článkové vazby zůstávají u obnovitelného zdroje. Obnovení zdroje samo neodstraní nově vytvořený cílový záznam.
Ruční i automaticky ukládané 301/302 redirecty ověřují starou adresu jako interní cestu webu a novou adresu jako interní cestu nebo čistou http/https URL bez přihlašovacích údajů. Nejednoznačné cíle, nebezpečná schémata a CRLF znaky se odmítnou ještě před odesláním hlavičky Location.
Externí URL zadávané v administraci pro widgety sociálních sítí, podcasty, zajímavá místa a položky ke stažení používají sdílenou validaci: běžné domény bez schématu se doplní na https://, ale interní cesty, protocol-relative adresy, řídicí znaky, nebezpečná schémata a URL s přihlašovacími údaji se odmítnou. Webhooky formulářů zůstávají přísnější: vyžadují explicitní veřejné https:// URL a dál procházejí ochranou proti privátním nebo lokálním hostům.
Stejný bezpečnostní základ používá i web autora. Pole typu URL ve veřejných formulářích jsou záměrně přísnější: návštěvník musí zadat úplnou adresu začínající na http:// nebo https://, ne interní cestu ani holou doménu, aby se do odpovědí nedostaly nejednoznačné nebo nebezpečné odkazy.
CMS automaticky generuje XML sitemapu (sitemap.xml) ze všech publikovaných veřejných stránek. Sitemapa je čistě čtecí endpoint a podporuje jen metody GET a HEAD.
Součástí veřejného provozu je také dynamický robots.txt, který zakazuje indexaci administrace a citlivých upload adresářů a odkazuje na XML sitemapu. Veřejné stránky, které předávají kanonickou URL do SEO metadat, zároveň generují <link rel="canonical">; canonical helper přijímá jen interní cesty nebo platné http:// / https:// adresy bez přihlašovacích údajů, řídicích znaků a protocol-relative tvaru.
Součástí CMS jsou čtyři šablony: default, civic, editorial a modern-service.
V administraci lze:
- aktivovat šablonu nebo spustit živý náhled bez ostré aktivace
- upravit barvy, akcenty, typografii a šířku obsahu
- měnit variantu hlavičky
- importovat a po potvrzení kontroly exportovat portable ZIP balíčky šablon
Import ZIP balíčku šablony používá sdílenou upload validaci ještě před tím, než CMS balíček rozbalí a zkontroluje manifest, povolené statické soubory a limity velikosti. Export ZIP balíčku šablony před stažením ukazuje review vybrané šablony, upozorní na přenos vizuální konfigurace a statických assetů a vyžaduje potvrzení oprávnění ke stažení.
Default šablona má vlastní theme view audit v composer ci:basic. Ten hlídá, aby PHP view soubory zůstaly čistou prezentační vrstvou bez přímé práce s request inputem, session/server stavem, databází, souborovými zápisy, runtime časem, inline styly, inline event handlery nebo skripty bez CSP nonce. Zároveň kontroluje statická duplicitní id, statické vazby aria-labelledby / aria-describedby / aria-controls, statické label for, formulářová pole bez labelu nebo ARIA názvu, pojmenování veřejných sekcí, landmarků, všech veřejných <article> prvků a <figure> bloků přes skutečný cílový prvek, přítomnost figcaption, alt u obrázků, title u iframe, explicitní type u tlačítek a rel="noopener noreferrer" i přístupný název oznamující nové okno u odkazů s target="_blank", aby se do šablon nevrátily nefunkční popisky pro čtečky obrazovky, nechtěná implicitní formulářová tlačítka nebo rizikové odkazy. Společné hodnoty jako přihlášený administrátor, aktuální datum a aktuální URL šablona dostává přes view data z renderPublicPage(). Součástí CI je i self-test auditu nad dočasnými fixtures, takže se ověřuje i to, že guardrail opravdu umí selhat na zakázaných vzorech.
Pokud aktivní šablona neobsahuje konkrétní view, systém automaticky použije default.
Administrace používá capability model – uživatelé vidí jen to, co potřebují.
| Role | Přístup |
|---|---|
| Veřejný uživatel | Bez přístupu do administrace |
| Autor | Vlastní články a novinky |
| Editor | Širší práce s obsahem a schvalováním |
| Moderátor | Komentáře, chat, kontaktní zprávy |
| Správce rezervací | Rezervace, zdroje a lokality |
| Admin | Plná správa webu |
Fronta Ke schválení sjednocuje čekající obsah, komentáře a rezervace na jednom místě.
Administrace má globální hledání dostupné v hlavním admin layoutu. Pole Hledat v administraci funguje i bez JavaScriptu jako běžná stránka výsledků, klávesová zkratka Ctrl+K otevře přístupnou command paletu. Výsledky zahrnují administrační obrazovky, bezpečné navigační rychlé akce a editovatelný obsah napříč zapnutými moduly. Každý výsledek respektuje roli, capability a stav modulu, takže uživatel vidí jen položky, ke kterým má přístup.
Vybrané položky lze připnout jako osobní zkratky. Zobrazí se na dashboardu v bloku Moje zkratky a ukládají se podle item_type + item_key, nikoli podle URL poslané z formuláře. CMS při připnutí položku znovu dohledá v interním registru a uloží jen platný interní admin cíl.
Rezervační administrace pro přehled, ruční vytvoření, detail rezervace i editor zdrojů používá sdílenou admin CSS vrstvu bez lokálních <style> bloků, style atributů nebo JS mutací element.style, takže drží stejnou CSP a údržbovou hygienu jako ostatní stabilizované administrační obrazovky.
Kora CMS podporuje dva editory:
- HTML textarea – výchozí, přístupnější varianta
- WYSIWYG (Quill) – volitelný vizuální režim
Pro HTML obsah je k dispozici content picker – přístupný dialog pro vložení interních odkazů, galerií, médií, formulářů, anket, PDF náhledu z knihovny médií a hotových HTML bloků. Vedle něj je v čistém HTML režimu nástroj Jazyk části textu, který obalí vybraný cizojazyčný úsek do <span lang="…">. Stejný helper je dostupný i u veřejně renderovaných taxonomických popisů, například u kategorií/štítků/sérií blogu, kategorií vývěsky, typů akcí a kategorií/sérií Ke stažení.
| Snippet | Výstup |
|---|---|
[audio]https://example.test/audio.mp3[/audio] |
HTML5 audio přehrávač |
[video]https://example.test/video.mp4[/video] |
HTML5 video přehrávač pro přímý video soubor |
[video src="/uploads/media/video.mp4" captions="/uploads/media/video.cs.vtt" srclang="cs" descriptions="/uploads/media/video-popis.cs.vtt"][/video] |
HTML5 video s WebVTT titulky a zvukovým popisem |
[video]https://www.youtube.com/watch?v=ID_VIDEA[/video] |
Vložený YouTube přehrávač s odkazem na samostatné otevření |
[pdf]https://example.test/dokument.pdf[/pdf] |
Náhled PDF s odkazem na samostatné otevření |
[code]echo "Ahoj";[/code] |
Kopírovatelný blok obsahu s tlačítkem Kopírovat do schránky |
[gallery]slug-alba[/gallery] |
Vložená galerie podle slugu |
[form]slug-formulare[/form] |
Živý embed veřejného formuláře |
[poll]slug-ankety[/poll] |
Živý embed veřejné ankety |
[download]slug-polozky[/download] |
Teaser karta položky ke stažení |
[podcast]slug-poradu[/podcast] |
Teaser karta podcastového pořadu |
[podcast_episode]slug-poradu/slug-epizody[/podcast_episode] |
Teaser karta epizody podcastu |
[place]slug-mista[/place] |
Teaser karta zajímavého místa |
[event]slug-udalosti[/event] |
Teaser karta události |
[board]slug-oznameni[/board] |
Teaser karta položky vývěsky |
Snippety fungují ve všech HTML polích, která CMS veřejně renderuje přes renderContent(). Formuláře a ankety se vkládají jako živé interaktivní embedy, PDF z knihovny médií se nově vykresluje přes interní same-origin preview endpoint a ostatní snippety jako sjednocené obsahové karty nebo kopírovatelné bloky obsahu. Tyto vložené bloky mají skrytý nadpis napojený přes aria-labelledby, takže jsou dohledatelné i navigací čtečkou obrazovky podle nadpisů bez vizuální změny obsahu. Video snippet podporuje přímé video soubory i běžné YouTube URL (watch, youtu.be, shorts, embed) a YouTube vkládá přes privacy-friendly youtube-nocookie.com. Přímý video snippet umí atribut captions s WebVTT souborem (.vtt), srclang a caption_label; pro zvukový popis důležitých vizuálních informací umí také descriptions, description_lang a description_label. Audio i video snippet umí atribut transcript s odkazem na přepis a volitelný transcript_label. Knihovna médií přijímá .vtt soubory, aby šly titulky i popisové stopy spravovat spolu s ostatními veřejnými médii. URL ve snippetech pro audio, video, titulky, popisové stopy, přepisy a PDF musí být buď úplná http:// / https:// adresa bez přihlašovacích údajů, nebo interní absolutní cesta začínající jedním lomítkem, například /uploads/media/soubor.pdf; protocol-relative adresy //example.com/... se odmítají. Při vložení obrázku přes content/media picker se do HTML zachová alt atribut, ale nevkládá se automatický figcaption z názvu média; když médium nemá vyplněný alternativní text, picker vloží alt="", který lze v editoru ručně upravit. Nápověda HTML editoru připomíná i lang atribut a sdílený helper umí vložit <span lang="en">…</span> pro označení cizojazyčné části textu v hlavním obsahu i veřejných taxonomických popisech. Externí iframe a externí audio/video embedy jsou na veřejném webu podporované přes CSP, pokud je cílový zdroj sám dovolí.
Kora CMS používá jedno rozhraní pro správu pořadí navigace. V administraci (Navigace webu) lze řadit:
- moduly
- blogy
- veřejné formuláře
- externí a interní odkazy
- statické stránky
Položky lze libovolně kombinovat – stránka může být mezi moduly, formulář vedle blogu a externí odkaz třeba mezi dvěma stránkami. U odkazu se nastavuje název, cílová adresa, volitelný popis pro čtečky obrazovky, který se ve veřejné navigaci přidá jako skrytý text za viditelný název odkazu, a bezpečné otevření v novém okně. Stejný princip platí i pro blogové stránky: každý blog může mít vlastní statické stránky a vlastní odkazy v samostatném pořadí nad výpisem článků.
Trvalé smazání vlastního odkazu se provádí až z jeho editace. Administrace před odesláním ukáže název, cílovou adresu, stav a dopad na globální nebo blogové pořadí a vyžaduje potvrzovací checkbox; server bez potvrzení odkaz nesmaže. Globální a blogové odkazy mají oddělený scope, potvrzená změna uklidí příslušné pořadí a vrátí textový stav přes PRG.
Správa navigace u veřejných formulářů nově používá stejnou dostupnostní logiku jako samotný veřejný web. Pokud je formulář aktivní, zveřejněný a označený pro navigaci, admin už ho neoznačuje jako skrytý a hlavní navigace ho vykreslí stejným pravidlem jako ve veřejné části.
Homepage, sidebar i footer se skládají přes widgetový systém. V administraci lze přidávat widgety do tří zón, měnit jejich pořadí a nastavovat parametry.
Widgety pokrývají typické potřeby: úvodní text, nejnovější články, novinky, události, anketa, newsletter, ke stažení, FAQ, místa, podcasty, galerie, vybraný formulář, vyhledávání, kontaktní údaje, sociální sítě, statistiky návštěvnosti a vlastní HTML.
Administrace i veřejný widget používají u základních statistik stejné pořadí a popisky Online / Dnes / Měsíc / Celkem. Detailní administrace statistik navíc za zvolené období ukazuje nejčtenější statické stránky, včetně blogových stránek, externí odkazující stránky a blok Výkon obsahu. Ten používá dlouhodobé denní agregace bez IP hashů, user-agentů a raw referrerů, umí souhrn podle modulů, nejčtenější obsah, největší nárůsty proti předchozímu stejně dlouhému období, filtr podle modulu a CSV export agregovaných výsledků přes samostatný review-and-confirm krok. Lazy agregace obsahových statistik dopočítává konkrétní nesouladné dny v omezené dávce, aby první otevření statistik po nových historických návštěvách nepřepočítávalo zbytečně velký souvislý rozsah. Kvůli soukromí se u referrerů ukládá jen schéma, host a cesta bez query stringu a fragmentu; interní přechody v rámci vlastního webu se do referrer přehledu nepočítají. Samostatná sekce Ke stažení navíc ukazuje dosavadní lokální downloady, otevření externích zdrojů a jejich součet u aktuálně zveřejněných položek. Tato čísla nejsou omezena filtrem období, neobsahují identitu návštěvníka a otevření externího zdroje neprokazuje dokončené stažení na cílovém webu. Měřený odkaz vždy zobrazuje cílovou doménu.
Widget Náhled galerie vykresluje poslední veřejné fotografie jako responzivní náhledový grid. Na homepage, v sidebaru i ve footeru používá stejné šablonové CSS třídy, takže výstup zůstává konzistentní a bez inline layout stylů.
Widget Nejnovější články zobrazuje u každého článku odkaz a pod ním metadata článku: datum a čas publikace, přibližnou dobu čtení a počet přečtení. Metadata se počítají ze stejného obsahu článku jako běžné veřejné výpisy, takže sidebar, footer i homepage widget dávají návštěvníkům konzistentní informaci.
Widget Úvodní text je nově jediný podporovaný způsob, jak spravovat hlavní úvodní blok domovské stránky. Samostatné pole v Obecných nastaveních už neexistuje. Intro widget umí HTML i běžné snippety z HTML editoru a pokud zůstane prázdný, na webu se vůbec nevykreslí. Na homepage má zároveň skrytý nadpis pro čtečky obrazovky, takže se dá najít i navigací po nadpisech bez změny vizuálního vzhledu.
Widgety respektují stav modulů i skutečnou dostupnost obsahu. Vypnutý modul se nenabízí a aktivní widget se na webu nevyrenderuje ani tehdy, když pro něj není obsah, je navázaný na neexistující formulář nebo má prázdnou konfiguraci. Správa widgetů na to nově umí přímo upozornit textem Na webu se teď nezobrazí: ... a používá sdílenou admin CSS vrstvu bez lokálního <style> bloku.
Odebrání widgetu má stejný error-prevention princip jako ostatní destruktivní administrační akce. Řádkový formulář před smazáním ukáže název, typ, zónu a dopad na veřejné zobrazení, vyžaduje checkbox confirm_widget_delete_<id> a server nepotvrzený POST odmítne bez smazání widgetu i bez audit logu.
Také obrazovka Obecná nastavení používá sdílenou admin CSS vrstvu pro navigaci sekcí, profilové karty, vlastní kód a branding náhledy. Nemá tedy vlastní lokální <style> blok ani prezentační style atributy.
Footer už neobsahuje natvrdo zapsané odkazy na sociální sítě, vyhledávání ani odběr novinek. Sociální sítě se nově nastavují přímo v widgetu Sociální sítě, widget Vyhledávání vykresluje hledací pole a widget Newsletter zobrazuje bezpečný odkaz na samostatnou stránku odběru. Vyhledávací widget pojmenovává svůj role="search" formulář přes skrytý legend, takže search landmark zůstává dohledatelný i mimo okolní widgetovou sekci. Newsletter widget neodesílá e-mail přímo z každé stránky; návštěvníka vede na /subscribe.php, kde přihlášení chrání CSRF, honeypot, rate limiting a serverově ověřená captcha s přístupnými chybami u polí. Sociální odkazy otevírané v novém okně mají bezpečné rel="noopener noreferrer" a oznámení pro čtečky obrazovky jako skrytý text přímo uvnitř odkazu, ne jen v samostatném atributu.
Dialog Nastavení u widgetů nově používá skutečné skupiny polí přes fieldset a legend, navázané help texty a bezpečnější focus trap jen pro viditelné prvky. Stejný přístup používá i správa blogů, kde jsou create/edit formuláře rozdělené do sekcí Základní údaje, Obsah a metadata a Logo a zobrazení; dialog úprav blogu, náhled loga a modální stav se stylují přes sdílenou admin vrstvu bez lokálního <style> bloku.
Kompletní seznam widgetů: docs/admin-guide.md
Kora CMS používá:
- volitelné 2FA přes TOTP
- CSRF ochranu
- rate limiting
- honeypot pole
- CAPTCHA
- prepared statements
- bezpečné hashování hesel
- CSP nonce
- HSTS při HTTPS
- ochranu
.enva.git/ - audit log a kontrolu integrity souborů
Koš v administraci funguje jako review krok před nevratným odstraněním. Běžné mazání přesune obsah do koše, trvalé smazání v koši zobrazuje typ, název a datum smazání v řádku, vyžaduje samostatné potvrzení checkboxem u konkrétní položky a server odmítne purge bez tohoto potvrzení. Chybějící potvrzení se vrací na konkrétní položku s textovým alertem, aria-invalid a field-level chybou u checkboxu. HTTP integrace hlídá, že nepotvrzené trvalé smazání položku neodstraní ani nezapíše audit log a potvrzené smazání projde s PRG stavem.
Modul Místa používá tento model i pro jednotlivý a hromadný přesun. Server vyžaduje item-specific nebo hromadné potvrzení, transakčně nastaví pouze deleted_at a zachová obrázek, revize, redirecty i vazby událostí pro obnovu. Místo v Koši není veřejné ani nabízené v běžných admin výběrech; existující událost si ale interní vazbu ponechá a po obnovení se místo znovu zobrazí. Teprve potvrzený trvalý výmaz v Koši odpojí události, uklidí metadata a odstraní obrázek. JSON export/import přenáší i stav deleted_at.
Přihlášení do administrace, veřejné přihlášení i obnovení hesla používají kombinovaný rate limiting. Systém hlídá počet pokusů podle IP adresy a zároveň podle hashovaného účtu nebo tokenu. Do databáze se neukládá e-mail ani token v čitelné podobě, pouze odvozený SHA-256 klíč. Veřejná registrace a žádost o obnovu hesla nevyžadují matematickou CAPTCHA; proti automatizovaným pokusům zůstávají chráněné přes CSRF, rate limit a honeypot. Auth formuláře zároveň používají autocomplete="username", current-password, new-password a pro 2FA one-time-code s numeric patternem; chybové stavy standalone admin loginu a 2FA jsou text-backed alerty pro čtečky obrazovky. Výchozí 429 odpověď posílá Retry-After, explicitní HTML typ a necacheovací/noindex/no-referrer hlavičky, aby ji prohlížeč ani mezilehlá cache neukládaly a klient věděl, kdy má požadavek zkusit znovu. HTML odpověď má skutečný nadpis a kód požadavku pro podporu, stejně jako globální chybová stránka.
Veřejný kontakt, food objednávkové poptávky, guest rezervace, veřejný chat, blogové komentáře a Form Builder formuláře poskytují autocomplete metadata pro běžná osobní pole. Form Builder používá sdílený helper pro email, tel, url, zjevné jmenné textové pole, organizaci, pracovní pozici, datum narození a jasně pojmenovaná adresní pole jako ulice, PSČ, město, kraj a země; runtime audit a HTTP integrace hlídají, aby se tato input-purpose metadata z veřejných formulářů neztratila.
Veřejné formuláře, které stále používají matematickou ověřovací otázku proti spamu, zobrazují u chybné odpovědi field-level text s konkrétní opravou: přepočítat příklad a zadat jen číslo. Runtime audit i HTTP integrace hlídají kontakt, newsletter subscribe, odběr vývěsky, Food objednávky, guest rezervace a Form Builder.
Form Builder má stejný pattern i pro vlastní veřejná pole. Povinná textová, výběrová, souhlasová a souborová pole radí konkrétní akci; e-mail radí úplnou adresu ve tvaru jmeno@example.cz, URL radí http/https bez přihlašovacích údajů, výběrová pole radí použít jen nabídnutou možnost a upload připomíná povolený typ a velikost u pole. HTTP integrace hlídá required, e-mail, URL, select i upload chybové stavy.
Administrace postupně zpřesňuje validační texty tak, aby vedle identifikace chyby nabízely i opravu. URL pole u zajímavých míst a podcastů například vysvětlují, že lze zadat http:///https:// adresu nebo doménu bez schématu, kterou CMS uloží jako https://, případně volitelné pole nechat prázdné.
Editor Form Builderu má stejný pattern u hlavních chyb ukládání. Název, slug, notifikační e-mail, pole pro potvrzení odesílateli a webhook URL mají souhrnný alert i field-level text s konkrétní opravou, například doplnit název, nechat slug prázdný pro automatické vytvoření, zadat úplný e-mail nebo použít veřejně dostupnou HTTPS webhook adresu.
Editor statických stránek navazuje u chyb názvu, slugu, přiřazeného blogu a plánovacích datumů. Alert nahoře správci řekne, které pole opravit, a field-level text radí konkrétní akci: doplnit název, zvolit jiný slug, vybrat dostupný blog nebo znovu vybrat datum a čas.
Editor FAQ stejný pattern používá pro povinnou otázku, odpověď a slug veřejné stránky. Souhrnný alert je textový a atomický, konkrétní field-level nápověda radí doplnit otázku tak, jak ji návštěvník hledá, doplnit odpověď nebo zadat unikátní slug z malých písmen, číslic a pomlček.
Editor FAQ kategorií pokračuje u názvu, slugu a meta title. Field-level nápověda radí krátký srozumitelný název, slug s písmenem nebo číslem, jiný unikátní slug nebo zkrácení meta title na 160 znaků.
Mazání FAQ kategorií používá stejný review-and-confirm princip. Řádkový formulář popíše počet navázaných otázek a podkategorií, vyžaduje checkbox confirm_faq_category_delete_<id> a server nepotvrzené smazání odmítne bez změny vazeb, přesunu podkategorií, smazání kategorie nebo audit logu.
Editory blogových kategorií a štítků používají stejný princip u povinného názvu a duplicitního slugu. Souhrnný alert je atomický a field-level text radí krátký srozumitelný název, jiný unikátní slug nebo prázdné slug pole pro automatické vytvoření.
Mazání blogových kategorií, štítků a sérií článků používá stejný review-and-confirm princip. Řádkový formulář popíše počet navázaných článků a u kategorie i počet podkategorií, vyžaduje checkbox confirm_blog_category_delete_<id>, confirm_blog_tag_delete_<id> nebo confirm_blog_series_delete_<id> a server nepotvrzené smazání odmítne bez změny vazeb nebo audit logu.
Mazání celého blogu má samostatný review-and-confirm krok. Přehled blogů popíše počet dotčených článků, kategorií, štítků, sérií a týmových přiřazení, vyžaduje checkbox confirm_blog_delete_<id> a server nepotvrzené smazání odmítne bez přesunu obsahu, odstranění sérií, zrušení týmu, smazání blogu nebo audit logu.
Kategorie a série ke stažení mají stejný field-level pattern pro hlavní validační chyby taxonomií. Prázdný název, nepoužitelný nebo duplicitní slug a příliš dlouhý meta title kategorie se oznámí souhrnným alertem i textem u konkrétního pole; HTTP integrace hlídá render těchto stavů v reálném admin HTML.
Mazání kategorií a sérií Ke stažení navazuje na stejný error-prevention princip. Řádkový formulář popíše počet navázaných položek, u série i dopad na aktuální verzi, vyžaduje checkbox confirm_download_category_delete_<id> nebo confirm_download_series_delete_<id> a server nepotvrzené smazání odmítne bez změny vazeb nebo audit logu.
Mazání kategorií vývěsky a typů akcí používá stejný review-and-confirm princip. Řádkový formulář ukáže počet navázaných položek vývěsky a odběrů nebo počet událostí, vyžaduje checkbox confirm_board_category_delete_<id> nebo confirm_event_type_delete_<id> a server nepotvrzené smazání odmítne bez změny taxonomie, vazeb nebo audit logu.
Mazání témat kontaktu a chatu používá stejný review-and-confirm princip. Řádkový formulář popíše dopad na veřejný kontaktní formulář nebo chat a existující zprávy, vyžaduje checkbox confirm_contact_topic_delete_<id> nebo confirm_chat_topic_delete_<id> a server nepotvrzené smazání odmítne bez změny tématu, vazby zpráv nebo audit logu.
Mazání rezervačních kategorií a míst navazuje stejným principem. Řádkový formulář popíše počet navázaných rezervačních zdrojů a dopad na existující zdroje/rezervace, vyžaduje checkbox confirm_res_category_delete_<id> nebo confirm_res_location_delete_<id> a server nepotvrzené smazání odmítne bez změny číselníku, vazby zdroje nebo audit logu.
Mazání rezervačního zdroje má vlastní review-and-confirm krok. Přehled zdrojů ukáže počet budoucích nezrušených rezervací, vazeb na místa, pravidel otevírací doby, slotů a blokovaných dnů; server bez confirm_res_resource_delete_<id> nezruší rezervace, nesmaže dostupnost, zdroj ani audit log.
Stejný pattern používají i další administrační taxonomie: kategorie vývěsky, typy akcí, témata kontaktu/chatu a rezervační kategorie/místa. Povinný název, nepoužitelný nebo duplicitní slug a příliš dlouhý meta title se naváže na konkrétní pole přes existující aria-describedby; u rezervačních číselníků se po chybě zachová rozepsaný název i adresa.
Stejný pattern se rozšířil i na další běžná administrační workflow mimo číselníky: série článků, externí odkazy blogu, základní chyby rezervačního zdroje, podcastové pořady a newsletter composer. Souhrnné alerty jsou textové a atomické, field-level texty radí doplnit název, opravit URL, slug, kapacitu, RSS limit, cover obrázek nebo doplnit předmět a text rozesílky.
Editory podcastových kapitol, osob a platforem navazují stejnou smlouvou. Jejich serverově renderované chyby zachovávají rozepsané hodnoty a radí konkrétní čas kapitoly, příklad názvu, http/https nebo doménový tvar URL, prázdné volitelné URL pole či úpravu existující duplicitní položky; runtime audit a HTTP integrace hlídají field-level vazby pro WCAG 3.3.3.
Editor strukturovaných sekcí a položek jídelních lístků používá stejný field-level pattern pro názvy, datum a čas podávání, sekci, cenu, výživové údaje a obrázek. Trvalé smazání položky nebo celé sekce navíc ukáže rozsah odstraňovaných dat, u sekce počet položek, vyžádá serverově kontrolovaný checkbox a zachová média; bez potvrzení se data ani audit log nezmění.
Stejné field-level guardraily platí i pro hlavní navigační externí odkazy, přesměrování, alba galerie, fotografie galerie a ruční vytvoření rezervace v administraci. Chyba vždy zůstává u konkrétního pole přes existující aria-describedby, nastaví aria-invalid a radí praktický další krok: doplnit název, opravit interní cestu nebo URL, zvolit jiný slug, vybrat platné nadřazené album, opravit licenci či datum pořízení, případně doplnit zdroj, uživatele/hosta, datum a čas rezervace nebo vyřešit konflikt termínu.
Hlavní obsahové editory navazují stejnou smlouvou. Editor článku, správa blogů, položky vývěsky, jídelní/nápojové lístky a ankety používají atomický souhrnný alert a field-level texty pro chybějící titulek nebo název, nepoužitelný slug, cílové taxonomie, související články, série, datum vyvěšení, možnosti ankety a vícevýběrové limity.
Editory novinek, událostí, míst, položek ke stažení a podcastových epizod používají stejný field-level error pattern jako hlavní obsahové editory. Souhrnný alert je atomický, dotčená pole dostanou aria-invalid, aria-describedby míří na existující nápovědu nebo chybový text a lokální hláška správci radí konkrétní opravu místo obecného „neplatné“.
Profil správce, správa uživatelských účtů, nastavení webu a správa šablon používají stejný guardrail i pro účetní a konfigurační chyby. E-mail, heslo, kontrolní heslo, veřejný autor, TOTP kód, název webu, veřejný název vývěsky, GitHub repozitář, logo/favicona, aktivní šablona, theme settings a theme ZIP import/export dostávají atomický souhrnný alert i field-level text s praktickou opravou; HTTP integrace admin_validation_a11y_http renderuje tyto stavy jako regresní důkaz.
Změna role existujícího uživatelského účtu má samostatný review krok. Formulář ukazuje aktuální a novou roli, vyžaduje potvrzovací checkbox a server nepotvrzenou změnu odmítne bez uložení oprávnění; HTTP integrace ověřuje odmítnutí i potvrzený průchod pro WCAG 3.3.4.
Mazání uživatelského účtu používá stejný review-and-confirm princip přímo v přehledu uživatelů. Řádkový formulář popíše dopad na přístup do CMS a osobní administrační zkratky, vyžaduje checkbox confirm_user_delete_<id> a server nepotvrzené smazání odmítne bez změny účtu, zkratek nebo audit logu. Potvrzené smazání uklidí osobní zkratky účtu a zapíše audit log user_delete.
Mazání nepoužitého média používá stejný error-prevention princip v knihovně médií. Karta souboru popíše odstranění souboru, metadat a odvozených miniatur, vyžaduje checkbox confirm_media_delete_<id> a server nepotvrzený požadavek odmítne před smazáním DB záznamu, fyzického souboru nebo audit logu; HTTP integrace hlídá i potvrzený cleanup souboru.
Newsletter composer používá stejný error-prevention princip před rozesláním e-mailu odběratelům. Formulář ukazuje počet potvrzených a čekajících odběratelů, vyžaduje potvrzovací checkbox a server validní rozesílku bez potvrzení odmítne, aniž ji zapíše do historie; HTTP integrace hlídá odmítnutí pro WCAG 3.3.4.
Hromadné akce nad odběrateli newsletteru navazují stejnou pojistkou. Potvrzení odběrů, znovuodeslání potvrzovacích e-mailů i mazání vybraných odběratelů vyžaduje kontrolní checkbox a server nepotvrzenou akci odmítne před změnou dat.
Individuální mazání odběratele newsletteru používá stejný review-and-confirm pattern v přehledu i detailu odběratele. Formulář popisuje dopad na aktivní odběr a zachování historie rozesílek, vyžaduje confirm_newsletter_subscriber_delete_<id> a server bez potvrzení nesmaže odběratele ani nezapíše audit log.
Hromadné akce nad odpověďmi Form Builderu používají stejný review-and-confirm pattern. Změna stavu i trvalé smazání vybraných odpovědí vyžaduje potvrzení kontroly výběru a zvolené akce; server nepotvrzený požadavek odmítne před změnou dat, odstraněním příloh/historie nebo zápisem audit logu.
Individuální mazání odpovědi Form Builderu má stejný guardrail v přehledu odpovědí i detailu odpovědi. Akce ukazuje referenci, stav, počet záznamů interní historie a počet nahraných souborů uložených v odpovědi, vyžaduje confirm_form_submission_delete_<id> a server bez potvrzení neodstraní odpověď, historii, přílohy ani audit log; HTTP integrace ověřuje přehled, detail, chybový stav i potvrzený cleanup.
Mazání celého Form Builder formuláře má vlastní řádkový review krok v přehledu formulářů. Správce před smazáním vidí veřejnou URL, počet polí, odpovědí, záznamů historie odpovědí a upozornění na nahrané soubory v odpovědích; bez confirm_form_delete_<id> server formulář, pole, odpovědi, historii, přílohy ani audit log nezmění.
Hromadné akce nad alby galerie mají stejný guardrail. Smazání vybraných alb i ZIP export vyžadují potvrzení kontroly výběru a zvolené akce; server nepotvrzené smazání odmítne před odstraněním alb, fotografií a revizí a nepotvrzený ZIP export před odesláním attachmentu nebo zápisem audit logu.
Sdílené hromadné mazání v běžných administračních přehledech používá obecný confirm_bulk_delete guardrail. Přehled zobrazí review dopadu mazání, správce potvrdí kontrolu výběru a server nepotvrzený požadavek odmítne před cleanupem, smazáním dat nebo audit logem.
Trvalé mazání blogových komentářů používá vlastní review-and-confirm guardrail pro jednotlivé řádky i hromadný výběr. Správce před odesláním vidí dopad na text, údaje autora a stav moderace, potvrzuje confirm_comment_delete_<id> nebo confirm_comment_bulk_delete a server bez potvrzení komentář ani audit log nezmění. Stejnou kontrolu má i historický přímý endpoint, takže aktuální formulář nejde obejít starší URL; HTTP integrace ověřuje odmítnutí i potvrzený PRG průchod.
Trvalé mazání kontaktních a chatových zpráv používá stejnou serverovou ochranu v přehledu, detailu, hromadné akci i historickém přímém endpointu. Samostatné potvrzení vyžaduje také smazání chatové odpovědi. Bez checkboxu zůstávají zprávy, odpovědi, chatová historie i audit log beze změny; potvrzené smazání celé chatové zprávy odstraní související data v jedné transakci a vrátí přesný PRG stav.
Mazání jednotlivého přesměrování používá stejný princip v řádkové akci. Už nejde o stav měnící GET odkaz; správce vidí review staré a nové cesty, musí potvrdit dopad na veřejnou URL a server bez confirm_redirect_delete_<id> záznam nesmaže ani nezapíše audit log.
Změny stavu rezervace používají stejný error-prevention princip. Detail rezervace pro schválení, zamítnutí, zrušení, dokončení a no-show ukazuje review dopadu, vyžaduje potvrzovací checkbox a server nepotvrzený požadavek odmítne dřív, než změní stav, zapíše historii nebo odešle notifikaci.
Ruční SQL záloha databáze má vlastní review krok pro citlivý export. Formulář popisuje, že záloha obsahuje kompletní data CMS, vyžaduje potvrzení oprávnění ke stažení a server bez potvrzení neodešle soubor ani nezapíše audit log; HTTP integrace hlídá odmítnutí i potvrzený download pro WCAG 3.3.4.
Hlavní JSON export CMS používá stejný error-prevention princip. Správce nejdřív projde review citlivosti exportu, včetně zpráv, komentářů, odběratelů a tokenů odběru; bez potvrzení oprávnění server zůstane na HTML chybě u checkboxu a neodešle soubor ani nezapíše audit log.
CSV export statistik obsahu ve správě statistik používá stejný error-prevention princip. Správce nejdřív zkontroluje období, filtr modulu, počet řádků a upozornění na interní výkon obsahu; bez potvrzení oprávnění server neodešle attachment ani nezapíše audit log.
CSV export audit logu používá stejný error-prevention princip pro provozní a bezpečnostní záznamy administrace. Správce nejdřív zkontroluje aktuální filtr, počet řádků a citlivost detailů akcí; bez confirm_audit_log_csv_export server neodešle attachment ani nezapíše audit log.
Detaily kontaktních zpráv, chat zpráv a odpovědí Form Builderu používají stejný pattern i pro e-mailovou odpověď správce. Pokud chybí předmět nebo text odpovědi, souhrnný alert doplní field-level chyby u obou polí přes aria-describedby a konkrétní rada říká, co má správce doplnit.
Detail odpovědi Form Builderu navazuje stejný pattern i na GitHub issue bridge. Při chybě vytvoření issue dostanou konkrétní nápovědu pole repozitář, název a tělo issue; při chybě ručního napojení dostane nápovědu URL existující issue. Runtime audit a HTTP integrace hlídají, že se nevrátí pouze obecná hláška.
E-mailová pole ve vývěsce, událostech, jídelních lístcích, Form Builderu, tématech kontaktu, místech, podcastech, nastavení, profilu a správě uživatelů radí zadat úplnou adresu ve tvaru jmeno@example.cz; u volitelných polí připomínají možnost nechat pole prázdné a u přihlašovacího e-mailu jedinečnost adresy.
Běžná datumová pole ve vývěsce, jídelních lístcích a položkách ke stažení radí použít kalendářní datum, volitelné pole nechat prázdné nebo opravit pořadí od/do. Datum vydání u downloadů má field-level chybu napojenou přes aria-describedby.
Zdroj, externí odkaz, domovská stránka projektu, upload souboru a SHA-256 checksum u položek ke stažení mají field-level chyby s konkrétní opravou. Texty vysvětlují lokální soubor versus externí URL, povolený http/https nebo doménový tvar, prázdné volitelné URL pole a přesný 64znakový checksum; runtime audit hlídá, aby se nevrátily obecné validační hlášky.
Vybrané obrazové uploady v administraci článků, vývěsky, událostí, míst a položek ke stažení radí povolené formáty JPEG, PNG, GIF nebo WebP, výslovně říkají, že SVG a jiné formáty CMS nepřijímá, a připomínají možnost nechat volitelné obrazové pole prázdné. Runtime audit hlídá i field-level napojení náhledového obrázku u downloadů.
Vybrané neobrazové uploady pokračují stejným směrem: příloha vývěsky radí povolené dokumentové a archivní formáty a podcastová epizoda radí MP3/OGG/WAV/M4A/AAC i možnost použít místo uploadu externí audio odkaz. Runtime audit hlídá, aby se tyto chyby nevrátily k obecnému „nepodařilo se uložit“.
Knihovna médií navazuje field-level chybou i vlastní upload a náhradu souboru. Po PRG redirectu zůstává form-level alert, ale file input má zároveň aria-invalid, aria-describedby na existující chybový text a návrh opravy: podporovaný formát do nastaveného limitu uploadu, zákaz SVG, u náhrady stejná MIME rodina a u veřejných souborů stejná přípona.
Stejný princip platí pro plánování publikace a časové rozsahy. U článků, stránek, novinek, událostí, anket, rezervačních zdrojů a podcastových epizod chybové hlášky vysvětlují, že má správce vybrat platnou hodnotu v poli datum/čas, volitelné plánování nechat prázdné, odstranit prázdný řádek nebo opravit pořadí začátku a konce.
Návrat po přihlášení do administrace používá bezpečný redirect jen pro interní administrační cíle a potvrzení migrace. Veřejné interní cesty, externí URL, protocol-relative URL i pokusy o smyčku zpět na login nebo 2FA se zahodí a použije se dashboard administrace. Unit testy i runtime audit hlídají, aby se z tohoto helperu nestal otevřený redirect.
Technické recoverable chyby se postupně zapisují přes strukturovaný koraLog() formát s request_id, metodou a cestou. Globální neošetřené chyby ukládají jen název souboru a hash cesty, ne plnou lokální cestu; chybová stránka návštěvníkovi ukáže bezpečný kód požadavku pro podporu a odpověď je necacheovatelná. Administrační ukládání článků, přesun článků mezi blogy, transakční přesun článků do Koše, ukládání anket, cleanup šablon, mazání prezentačních souborů, hromadné akce galerie, import fotek z eStránek i dílčí selhání přehledů na dashboardu už nepoužívají surové error_log() zprávy bez kontextu nebo s plnými lokálními cestami. Dashboard u počítadel loguje jen omezený kontext, například sekci a krátký hash dotazu, nikoli celý SQL text.
V nastavení webu už nové uploady loga a favicony nepřijímají SVG. Backend současně hlídá i velikost branding souborů a používá sdílenou upload validaci, takže se do veřejně servírovaných assetů nedostane aktivní obsah ani přehnaně velké soubory.
Náhledové obrázky článků, přílohy vývěsky, lokální soubory ke stažení a fotografie galerie používají stejnou sdílenou upload validaci pro stav PHP uploadu, MIME typ nebo bezpečnou příponu a finální uložení. Příprava cílového adresáře, nahrazení existujícího souboru i finální přesun uploadu se logují strukturovaně bez fyzických cest, takže se hostingové chyby dají dohledat podle request_id. Při výměně nebo odebrání obrázku se uklízí i staré miniatury, WebP a responsive varianty, aby se ve veřejných upload adresářích nehromadily nepoužívané soubory. Apache konfigurace v uploads/.htaccess navíc blokuje běžné skriptové přípony v upload adresáři, například .php, .phtml, .phar, .cgi, .pl, .py, .rb, .sh, .asp, .aspx a .jsp; release ZIP obsahuje jen tento ochranný soubor, ne lokální uživatelská média.
Veřejná default šablona nepoužívá pro potvrzení akcí a tisk inline onclick handlery. Potvrzení běží přes data-confirm a tisk přes js-print-page v nonce skriptu layoutu, což snižuje závislost na CSP fallbacku unsafe-inline.
V administraci používají jednoduché potvrzovací formuláře stejný data-confirm vzor i pro událost submit, takže potvrzení funguje také při odeslání klávesnicí a není vázané na inline onsubmit JavaScript.
Dlouho běžící administrační formuláře, například import z WordPressu nebo eStránek, používají data-submit-once. Sdílený nonce skript po odeslání změní text tlačítka a zablokuje opakované kliknutí bez inline onclick handleru.
XML/WXR soubory pro import z WordPressu a eStránek používají stejnou sdílenou upload validaci jako ostatní citlivější nahrávání. CMS ověří stav PHP uploadu, dočasný soubor a prázdný soubor dřív, než ho předá parseru; WordPress náhled se navíc ukládá do uploads/tmp přes bezpečný upload helper. Downloader fotografií z eStránek vyžaduje cURL a přijme jen finální veřejnou http/https URL na standardním portu. DNS cíl připne na ověřenou veřejnou IP, blokuje interní a rezervované adresy, nepovoluje přesměrování, ověřuje TLS, omezuje odpověď nastaveným upload limitem a uloží jen skutečný JPEG, PNG, GIF nebo WebP. Pokud původní URL přesměrovává, je potřeba zadat její konečnou adresu.
Importní formuláře navazují tyto chyby i na konkrétní pole. JSON import, WordPress WXR, XML import z eStránek a downloader fotografií z eStránek používají field-level chyby přes aria-describedby; chybový panel má role="alert" a text radí správný exportní soubor, platné UTF-8 nebo http/https/doménovou URL webu.
Datově dopadající importy používají navíc error-prevention potvrzení. JSON import, finální krok WordPress WXR importu, XML import z eStránek i dávkové stahování fotografií z eStránek vyžadují kontrolní checkbox; server nepotvrzený požadavek odmítne před zápisem obsahu, nastavení, galerií nebo souborů.
Tokenové odkazy, které mění stav přes tajný GET odkaz, jsou metodově omezené. Potvrzení e-mailu, potvrzení nebo odhlášení newsletteru a veřejné i administrační odhlášení odmítají POST, HEAD a další nečekané metody pomocí 405 a Allow: GET, takže kontrolní nebo chybné HTTP požadavky nemají měnit účet, odběr ani session.
Editor anket v administraci používá pro přidávání a odebírání možností odpovědi datové atributy a delegovaný listener v nonce skriptu formuláře, ne inline onclick handlery.
Rezervační formuláře v administraci používají stejný princip pro přepínání typu zákazníka, práci se sloty a blokovanými dny. Runtime audit navíc hlídá, aby se do admin PHP souborů nevracely inline onclick, onchange, onsubmit ani oninput atributy.
Editor formulářů v administraci používá sdílené admin CSS třídy pro základní nastavení formuláře, potvrzovací e-mail, webhooky, editor polí i náhled potvrzení. Form builder tak zůstává bez lokálních style atributů a runtime audit hlídá, aby se tento vzor nevrátil.
Administrátorský dashboard používá sdílené panely, karty a sémantický <progress> pro mini graf návštěvnosti místo lokálních style atributů. Runtime audit tím drží i hlavní přehled administrace v linii postupného zpřísňování CSP.
Společný layout administrace načítá základní administrační styly ze statického a verzovaného admin/assets/layout.css?v=... místo generovaného inline <style> bloku v admin/layout.php. Tím se další velká část administrace posouvá k přísnější CSP bez ztráty sdílených utility tříd a změny přístupnostních CSS se po nasazení neblokují starou cache prohlížeče.
Přihlašovací obrazovky administrace včetně 2FA načítají sdílený statický stylesheet admin/assets/login.css místo generovaného inline <style> helperu. Tím zůstává skip link, viditelný focus i TOTP pole konzistentní bez dalšího inline stylového driftu.
Kontrastní baseline pro výchozí šablonu, administrační layout a přihlašovací obrazovky je hlídaný přes runtime sekci contrast_focus_guardrails. Ta měří textové páry, stavové hlášky, informační panely, inline štítky, skip link, focus tokeny a hranice inputů/tlačítek; administrační CSS má navíc forced-colors fallback a disabled ovládací prvky se nedimují přes ztrátovou průhlednost. Custom theme, hover, ikony a progress stavy zůstávají předmětem ručního testovacího protokolu.
Text spacing baseline hlídá runtime sekce text_spacing_guardrails. Core CSS nesmí používat záporné letter-spacing, textový ořez přes text-overflow: ellipsis nebo line clamp ani !important zámky na text-spacing vlastnostech; administrační SEO preview dlouhé titulky a popisy zalamuje místo ořezu.
Mobilní baseline administrace hlídá runtime sekce admin_mobile_reflow_guardrails. Sdílený admin stylesheet na malé šířce skládá navigaci nad obsah, zmenšuje padding hlavní části, nechává datové tabulky scrollovat lokálně přes .table-responsive, skládá media/Form Builder/statistics/theme/checkbox gridy do jednoho sloupce, zabraňuje min-content roztažení dlouhých fieldsetů a dorovnává malé řadicí ovladače, běžné button prvky, checkbox/radio ovladače, sekundární action odkazy i přímé akční odkazy v odstavcích na minimální target size. Browser ověření při 320 px pokrývá media, widgets, statistics, Form Builder, přehled formulářů, comments, contact, chat, reservations, food, downloads, gallery, importy, content picker, reprezentativní dlouhé formuláře a podcastové přehledy; tabulkové moduly mají wrappery tak, aby neroztahovaly celý viewport.
Společný layout administrace pojmenovává hlavní administrační navigaci skutečným nadpisem Administrace přes aria-labelledby. Serverové stavové a chybové hlášky si zároveň ponechávají vlastní role a skrytý live region slouží jen pro doplňková klientská oznámení. Sdílené administrační řazení oznamuje úspěšné uložení i selhání AJAX požadavku textovou inline hláškou místo prohlížečového alertu.
Veřejný admin bar a vybrané pomocné navigace v administraci, například filtry komentářů, kontaktních a chatových zpráv, odpovědí formulářů, newsletteru, fronty ke schválení, nastavení webu a stránkování rezervací, používají skutečné skryté nadpisy přes aria-labelledby. Čtečky obrazovky je tak najdou jako landmarky i při navigaci po nadpisech, bez vizuální změny rozhraní.
Stejný vzor používají i vybrané skupinové prvky mimo navigaci: jídelní taby, rezervační časové sloty, výsledky anket, veřejný chat a souhrny návštěvnosti jsou pojmenované přes aria-labelledby napojené na skutečný nadpis nebo legendu.
Stabilizované administrační odkazy otevírané v novém okně nepřepisují viditelný název přes aria-label; informaci o novém okně vkládají jako skrytý text přímo do odkazu, aby čtečky obrazovky i vizuální rozhraní pracovaly se stejným názvem.
Živý náhled šablony a veřejný rezervační kalendář také používají skutečný textový název: preview banner je napojený na vlastní text v banneru a kalendářová tabulka má skrytý <caption>. Stavová potvrzení ve veřejné autentizační, účtové, newsletterové, formulářové, komentářové, anketní a rezervační části používají role="status" nebo role="alert" s vlastním textovým uzlem přes aria-labelledby, aby potvrzení, chyby a informační stavy nebyly pro čtečky obrazovky anonymní.
Veřejné chybové alerty u přihlášení, registrace, obnovy hesla, profilu, komentářů, chatu, kontaktu a Form Builderu mají skrytý textový nadpis přímo uvnitř hlášky. U vložitelných formulářů se ID hlášky odvozuje od konkrétního formuláře, aby se při více embedech na jedné stránce neduplikovalo.
Sdílené veřejné stavové stránky, například potvrzení e-mailu nebo newsletterové potvrzení a odhlášení, používají stejný princip: pokud mají být oznámeny čtečce obrazovky, stavová zpráva má aria-atomic="true" a aria-labelledby napojené na první textový odstavec zprávy.
Content/media picker v administraci načítá sdílený statický stylesheet admin/assets/content-reference-picker.css pro styly dialogu, překryvu, toolbaru a výsledků. Samotný picker tak zůstává bez lokálního i generovaného <style> bloku a runtime audit hlídá, aby se do něj nevracely prezentační inline styly ani JS mutace element.style.
Editor článku blogu používá sdílené admin CSS třídy pro zámek obsahu, taxonomie při změně blogu, plánování publikace, SEO, interní poznámku, stav článku, akční řádek i WYSIWYG wrapper. Díky tomu už samotný formulář článku nepotřebuje lokální style atributy ani JS mutace element.style.
Kontrola integrity a detail odpovědi formuláře v administraci používají sdílené administrační CSS třídy místo vlastních lokálních <style> bloků. Runtime audit hlídá, aby se tyto čistě prezentační styly nevracely přímo do jednotlivých obrazovek.
Veřejná hlavička šablony přidává CSP nonce i k dynamickému <style> bloku s theme CSS proměnnými. Díky tomu se vlastní barevné nastavení šablon drží stejného bezpečnostního režimu jako ostatní interní inline styly.
Veřejné šablony už pro skip link, .sr-only, cookie lištu a veřejný admin bar nepotřebují generovaný inline a11y <style> helper; tyto styly se načítají ze sdíleného themes/default/assets/public-core.css před CSS aktivní šablony. Samostatné systémové obrazovky install.php, migrate.php a maintenance.php jsou nezávislé na šabloně, ale používají společný statický stylesheet assets/standalone.css místo lokálních inline <style> bloků.
Nouzová chybová stránka používá assets/error.css a antispamový honeypot sdílenou třídu .honeypot-field, takže ani tyto pomocné výstupy nepotřebují lokální inline styly.
Veřejná JSON-LD strukturovaná data se vykreslují přes sdílený helper s CSP nonce, takže SEO metadata pro místa, podcasty, jídelní lístky, galerii, novinky, události a FAQ nejsou závislá na inline-script fallbacku.
CSP allowlist obsahuje také explicitní zdroje, které CMS samo vkládá pro Google Analytics a volitelný Quill editor. Externí GA/Quill skripty se renderují s nonce a runtime audit hlídá, aby se nové CDN skripty nepřidávaly bez stejné ochrany.
Projekt cílí na WCAG 2.2 Level AA:
- skip link na obsah
- viditelný focus stav
- sémantické HTML
- formuláře přes
label,fieldset,legend - vyhledávací, filtrační, drobečkové, stránkovací, obsahové embed bloky a další pomocné navigační landmarky pojmenované skutečnými nadpisy přes
aria-labelledby - helper texty přes
aria-describedby - přístupné dialogy s návratem fokusu
- bezpečné předvyplnění nebo opětovné použití kontaktních údajů přes
currentUserContactDefaults()ve veřejném kontaktu, Food objednávce, veřejném chatu, blogových komentářích a přihlášených veřejných rezervacích - měřený kontrast textu, focusu, skip linku a hranic ovládacích prvků ve výchozí, administrační a přihlašovací vrstvě
- mobilní baseline administrace pro stackovanou navigaci, scrollovatelné datové tabulky, jednosloupcové komplexní gridy a ovladatelné action rows
- ochrana dlouhých administračních editací před ztrátou rozepsaného obsahu při re-auth: autosave recovery při submitu, heartbeat 401 oznámení a návratový status na loginu
- klávesnicová ovladatelnost i tam, kde je drag & drop
- průběžný audit přes
build/runtime_audit.php
Pro dlouhodobé vyhodnocování vznikla sada dokumentů v docs/accessibility/: WCAG 2.2 AA matice, VPAT/ACR draft, backlog oprav, ruční testovací protokol, redakční checklist přístupného obsahu a modulové přílohy, například docs/accessibility/modules/blog.md a docs/accessibility/modules/recipes.md. Administrace má ve spodní navigaci stabilní stránku Nápověda a podpora, která sjednocuje orientaci, kontaktní workflow, redakční checklist a odkazy na conformance dokumentaci; veřejná šablonová vrstva má ve footeru stabilní navigaci Pomoc a kontakt pro moduly Kontakt a Chat. Portable/static theme balíčky dědí sdílený PHP layout a footer, takže mohou měnit assety a nastavení, ale nemohou přes import přepsat help navigaci vlastním PHP. Runtime audit, unit testy a HTTP integrace hlídají dostupnost, pořadí, module gating a ne-default theme render těchto help mechanismů. Report hodnotí Kora CMS jako produkt a odděluje odpovědnost CMS od ručně vloženého obsahu autora, například kvality titulků médií, alt textů nebo vlastního HTML v editoru. Podcastové epizody mají samostatné pole pro přepis epizody, které se veřejně vykreslí jako textová alternativa audia a zůstává součástí exportu/importu; audio/video snippety navíc podporují přímé odkazy na přepis a přímá videa WebVTT titulky i audio-description stopy. Čistý HTML editor má helper pro označení cizojazyčné části přes <span lang="…">. Veřejný kontakt, Food objednávka, veřejný chat a blogové komentáře předvyplňují u přihlášeného veřejného uživatele známé kontaktní údaje přes currentUserContactDefaults() a HTTP integrace hlídá, že se jméno, e-mail a tam, kde se sbírá, i telefon skutečně vykreslí. Přihlášené veřejné rezervace stejný helper používají při uložení kontaktního snapshotu, takže nevyžadují opakované zadání kontaktu ani captchu. Checklist docs/accessibility/author-content-checklist.md dává editorům kontrolu obrázků, médií, jazyka částí, odkazů, nadpisů, tabulek, barev a externích embedů před publikací. Stejné dokumenty jsou povinný vstup i pro návrh nových modulů: nový modul má předem určit, zda mění některý předpoklad conformance reportu, zda vyžaduje ruční testovací scénář a zda je potřeba aktualizovat backlog.
build/accessibility_conformance_audit.php udržuje úplný seznam 55 kritérií WCAG 2.2 A/AA, shodu stavů mezi maticí a ACR, návaznost všech stavů Partially Supports na backlog a ruční protokol i aktuální verzi, datum a souhrn ACR. Nad diffem navíc vyžaduje, aby změna formulářů, CSS, ovládání, médií, autentizace, exportů nebo datových akcí měla ve stejném commitu accessibility impact review a aktualizovaný automatizovaný důkaz; rozhodnutí beze změny stavu se zapisuje do docs/accessibility/a11y-impact-decisions.md. Audit i jeho negativní selftest jsou součástí composer ci:basic, a tedy také composer ci:module-ready.
Datově dopadající převod článku a stránky používá pro WCAG 3.3.4 serverový review-and-confirm krok i vratnost přes Koš. Runtime audit zakazuje fyzické mazání zdroje a článkových vazeb při převodu a HTTP integrace ověřuje oprávnění, nulovou změnu bez potvrzení, obě potvrzené cesty a auditní stopu.
Individuální i hromadné odebrání blogových článků používá stejný vratný model. Přehled předem popíše zachování obrázků, komentářů, štítků, sérií, souvisejících článků, revizí a redirectů, server vyžaduje potvrzení konkrétní akce a transakčně nastaví jen deleted_at. Přesunutý publikovaný článek vrací textovou 404 na běžné i preview URL, ale v Koši jej lze obnovit se všemi vazbami. Až samostatně potvrzený trvalý purge transakčně uklidí databázová data a po commitu všechny obrazové varianty.
Změny týmu blogu a jednorázové doplnění zakladatele používají stejný WCAG 3.3.4 review-and-confirm princip. Server odmítne chybějící potvrzení, neplatnou roli, cizí blog i zastaralý formulář bez částečného zápisu; potvrzený tým nebo auditní údaj zakladatele uloží pod zámkem v transakci a zapíše audit log. Doplnění zakladatele samo nemění členství ani přístup uživatele.
Cron každý den vytvoří SQL zálohu databáze do privátního úložiště (../kora_storage/backups/). Zálohy se uchovávají 7 dní. Ruční export v administraci i automatická cron záloha používají stejný SQL dump helper, který povoluje jen CMS tabulky s bezpečným názvem a exportuje řádky explicitně jako asociativní data.
V administraci: Import / Export → Záloha databáze. Stránka nejdřív zobrazí review citlivosti exportu a vyžaduje potvrzení oprávnění ke stažení. Bez potvrzení server vrátí textovou chybu u checkboxu a SQL soubor neodešle; po potvrzení stáhne aktuální SQL export.
Automatické i ruční SQL zálohy validují názvy tabulek a sloupců přes allowlist identifikátorů. Exportují se jen očekávané tabulky CMS s názvem cms_*.
V administraci: Import / Export lze obsah exportovat i znovu importovat jako JSON.
- JSON export z Kora CMS používá UTF-8 a zachovává českou diakritiku bez escapování do
\usekvencí - před stažením JSON exportu se zobrazí review citlivosti exportovaných dat a server vyžaduje potvrzení oprávnění; bez potvrzení se soubor nestáhne
- CSV export odpovědí Form Builderu používá stejný review krok: přehled odpovědí nejdřív ukáže citlivost dat a aktuální filtr, stažení vyžaduje potvrzení oprávnění a server bez potvrzení neodešle soubor ani nezapíše audit log
- CSV export výsledků anket používá stejný princip pro agregovaná data: kontrolní stránka ukáže téma ankety, počty hlasujících a odpovědí, stažení vyžaduje
confirm_poll_results_csv_exporta server bez potvrzení neodešle soubor ani nezapíše audit log - CSV export audit logu používá stejný princip pro provozní a bezpečnostní záznamy administrace: kontrolní stránka ukáže aktuální filtr, počet záznamů a citlivost detailů akcí, stažení vyžaduje
confirm_audit_log_csv_exporta server bez potvrzení neodešle soubor ani nezapíše audit log - JSON import nově odmítne soubor s neplatným UTF-8, aby se texty neuložily poškozeně
- pokud obnovujete ruční SQL dump mimo administraci, používejte při importu klientské spojení
utf8mb4; špatný charset může změnit české znaky na?
V administraci: Integrita souborů. Porovná SHA-256 otisky PHP souborů a ochranných .htaccess s uloženým snímkem v privátním úložišti ../kora_storage/integrity/snapshot.json. Kontrola zahrnuje také veřejné PHP šablony v themes/ a případné PHP či .htaccess soubory v uploads/, aby nevznikal slepý bod pro změnu renderu nebo nahraný webshell.
Vytvoření nebo obnova důvěryhodné baseline vyžaduje kontrolu aktuálního nasazení a výslovné serverově ověřené potvrzení. Selhání zápisu se zobrazí jako chyba a původní snapshot zůstane zachovaný; starý .integrity_snapshot.json ve webrootu se už nenačítá a po úspěšném bezpečném nahrazení se odstraní.
V administraci: Obecná nastavení → Provoz webu. Zapne stránku údržby s HTTP 503 pro návštěvníky. Přihlášení administrátoři vidí web normálně.
JSON provozní endpointy pro monitoring a CSP reporty posílají vedle Content-Type: application/json také X-Content-Type-Options: nosniff, aby prohlížeč ani mezilehlá vrstva nehádaly jiný typ obsahu. Stejně jako citlivé veřejné tokenové akce jsou necacheované, neindexované přes X-Robots-Tag: noindex, nofollow, noarchive a posílají Referrer-Policy: no-referrer, aby monitoring a CSP sběr zbytečně neputovaly přes cache, index nebo referrer. Tyto provozní JSON hlavičky i odmítnutí nepovolených metod skládají sdílené helpery, aby se health.php a csp-report.php nerozjely do dvou mírně odlišných variant a každá 405 odpověď zůstala dohledatelná přes request_id.
Endpoint health.php vrací minimální JSON stav instalace pro monitoring:
- databázové připojení
- zapisovatelnost privátního úložiště
- orientační stav a čas poslední SQL zálohy
- orientační čerstvost posledního běhu cronu
Endpoint podporuje metody GET a HEAD, nezobrazuje cesty, hesla ani detailní chyby. Při jiné metodě vrací sdílenou JSON 405 odpověď s Allow: GET, HEAD, bezpečnostními/no-store hlavičkami a request_id. Při zdravé instalaci vrací HTTP 200, při selhání kritické kontroly HTTP 503. Stav cronu je informační: čerstvá instalace bez prvního běhu cronu zůstane unknown, pozdější běh uloží cron_last_run_at a health check ho označí jako ok nebo stale. Odpověď se posílá s Cache-Control: no-store, X-Robots-Tag: noindex, nofollow, noarchive a Referrer-Policy: no-referrer, aby monitoring nedostal zastaralý stav z cache a provozní URL se neposílala dál.
Každý HTTP request zároveň dostává hlavičku X-Request-ID. Pokud proxy nebo hosting pošle vlastní bezpečné X-Request-ID, Kora CMS ho převezme; jinak vytvoří nové náhodné ID. Stejné ID se zapisuje i do strukturovaných JSON záznamů technických chyb, takže lze konkrétní problém spárovat mezi odpovědí, PHP logem a monitoringem. U neošetřené chyby se stejný kód zobrazí i na chybové stránce. Strukturovaně se logují i dílčí obnovitelné chyby veřejného blogu, detailu článku, vyhledávání, sitemapy, veřejných formulářů, chatu, kontaktu, stažení souboru a newsletterových potvrzovacích akcí, kde má stránka pokračovat ve vykreslení, ale provozní log musí jasně ukázat selhaný zdroj. Stejný zápis používají i vybrané administrační přehledy, například vyhledávání obsahu pro media picker, formuláře a statistiky, bez ukládání hledaného textu nebo obsahu zpráv do kontextu logu. Strukturované logování mají i sdílené helpery pro zámky obsahu, revize, widgety, použití médií, formulářové webhooky, e-mailové notifikace a souborové operace včetně uploadů, přesunů, úklidu knihovny médií a cron cleanupu starých temp souborů, CSP reportů nebo záloh; do logu ukládají jen technický kontext typu operace, entity, zóny, interní tabulky, webhook eventu, hostu endpointu, HTTP stavu, domény příjemce, SMTP fáze, hashe cesty nebo přípony souboru, ne celé webhook URL, tělo odpovědi protistrany, celou e-mailovou adresu, surovou SMTP odpověď ani fyzickou cestu k souboru.
Session vrstva používá cookies-only režim, strict mode a vypnuté session ID v URL. Přihlašovací flow zároveň po úspěšném přihlášení regeneruje session ID, cookie má HttpOnly a SameSite=Strict a odhlášení ji maže se stejným cookie kontextem, aby se snížilo riziko session fixation a úniku session přes odkazy nebo referrery. Dlouhé administrační formuláře mají lokální autosave a při odeslání ukládají recovery kopii; pokud mezitím vyprší session, content-lock heartbeat vrátí JSON 401, live region oznámí nutnost znovu se přihlásit a standalone login uživatele po přihlášení vrátí na původní administrační stránku s možností obnovit lokální záložní koncept.
Běžné administrační HTML odpovědi včetně loginu, 2FA a potvrzení migrace posílají Cache-Control: no-store, max-age=0, Pragma: no-cache, Expires: 0, X-Robots-Tag: noindex, nofollow, noarchive a Referrer-Policy: no-referrer. Administrace se tak zbytečně neuchovává v prohlížeči nebo mezicache, nemá se indexovat a adresa administrační stránky se neposílá dál jako HTTP referer, zatímco veřejné sociální náhledy mají dál vlastní krátce cacheovatelnou výjimku.
Citlivé veřejné tokenové a odhlašovací endpointy, například potvrzení e-mailu, potvrzení nebo odhlášení newsletteru, reset hesla, zrušení rezervace přes e-mailový token a veřejné odhlášení, používají stejné no-store, noindex a Referrer-Policy: no-referrer hlavičky. Pokud na ně omylem přijde sociální crawler, cache výjimka pro sdílené články se nepoužije. Běžné sociální náhledy naopak dostávají krátce cacheovatelnou odpověď s Vary: User-Agent, aby sdílená cache nemíchala crawler variantu s běžným návštěvníkem. Rezervační token se zároveň nepropíše do SEO metadat a tokenová adresa se neposílá dál jako HTTP referer. Nepovolené metody u těchto citlivých endpointů procházejí sdíleným requireHttpMethods() a vrací jednotnou 405 odpověď s přesným Allow, Cache-Control: no-store, max-age=0, X-Robots-Tag, Referrer-Policy, X-Content-Type-Options: nosniff a textovým typem odpovědi.
Stejnou necacheovatelnou ochranu používá také historický endpoint newsletter widgetu. Ten kvůli kompatibilitě zůstává dostupný, ale už neukládá odběratele ani neposílá potvrzovací e-mail; pouze přesměruje na zabezpečenou stránku odběru s captchou.
Odhlášení navíc posílá Clear-Site-Data: "cache", aby prohlížeč zahodil cache webu po ukončení session. CMS záměrně nepoužívá agresivnější varianty pro cookies nebo storage, protože session cookie se maže cíleně a lokální autosave koncepty v administraci ani cookie preference nemají mizet překvapivě.
Veřejné i administrační odpovědi posílají také bezpečnostní hlavičky Permissions-Policy, Cross-Origin-Opener-Policy: same-origin, Origin-Agent-Cluster: ?1, X-XSS-Protection: 0, X-Download-Options: noopen a X-Permitted-Cross-Domain-Policies: none. CMS tím explicitně zakazuje prohlížečové schopnosti, které nepoužívá, například kameru, mikrofon, geolokaci, platební API, USB nebo browsing topics, izoluje top-level okna i runtime podle originu, vypíná zastaralý XSS auditor ve starších prohlížečích, omezuje otevírání stažených souborů v kontextu webu a odmítá staré cross-domain policy soubory. Záměrně neblokuje clipboard ani fullscreen, aby zůstala funkční kopírovací tlačítka a legitimní embedy; nezavádí ani COEP/CORP, aby nerozbíjel legitimní externí iframe, audio/video a sociální náhledy.
Veřejné odpovědi posílají také Content-Security-Policy-Report-Only s interním endpointem csp-report.php. Prohlížeče na něj mohou posílat porušení CSP bez blokování běžného provozu; CMS ukládá jen očištěné JSONL záznamy do privátního úložiště logs/csp_reports-YYYY-MM-DD.jsonl. Běžné inline styly, které historická administrace i některé helpery zatím používají, jsou v CSP výslovně povolené přes style-src-elem a style-src-attr; pokud dorazí starší inline-style report, endpoint ho potichu přijme, ale nezapíše ho do JSONL, aby logy neplnil očekávaný šum. Endpoint přijímá jen POST, nepovolené metody odmítá sdílenou JSON 405 odpovědí s Allow: POST, chybové JSON odpovědi doplňuje o request_id, neposílá cacheovatelný obsah a má vlastní rate limit; při překročení vrací stručnou JSON odpověď rate_limited. Cron zároveň maže CSP report soubory starší než 30 dní, aby se privátní logy nehromadily donekonečna.
Soubor robots.txt je generovaný přes robots.php, podporuje jen GET a HEAD, zakazuje indexaci administrace a citlivých upload adresářů a odkazuje na aktuální sitemapu. Stejné čtecí omezení metod používají také XML sitemapa, globální, blogové i podcastové RSS feedy, ICS export událostí, veřejné souborové/media endpointy a read-only administrační endpointy včetně agregovaných statistik CSV, příloh formulářů a vyhledávání obsahu pro media picker. Hlavní JSON export CMS, CSV export odpovědí Form Builderu a CSV export výsledků anket používají samostatný GET review krok a potvrzený POST download. Discovery výstupy jako robots.txt, sitemapa, RSS feedy a ICS posílají Content-Type, X-Content-Type-Options: nosniff, případný Content-Disposition a HEAD odpovědi přes sdílený helper, aby je prohlížeč neinterpretoval mimo deklarovaný textový, XML, RSS nebo kalendářový typ. Statické inline soubory, například thumby, galerie, místa a podcastové obrázky nebo obaly, používají sdílený souborový helper pro MIME typ, cache, Content-Disposition s ASCII fallbackem i UTF-8 filename*, ETag, Last-Modified, podmíněné 304 Not Modified, nosniff a HEAD odpovědi; chráněné souborové downloady i PDF preview posílají stejný formát názvu souboru a nosniff přes download helper. Podcastové audio zůstává specializované kvůli podpoře Range, ale používá stejný UTF-8 Content-Disposition, ETag, Last-Modified, nosniff, HEAD odpovědi a pro nerange veřejné požadavky také podmíněné 304 Not Modified. U souborů vrací HEAD jen hlavičky, bez přenosu těla souboru. Nepodporované metody pro tyto read-only endpointy procházejí stejným requireHttpMethods() jako citlivé tokenové akce a vrací jednotnou 405 odpověď s Allow: GET, HEAD, Cache-Control: no-store, max-age=0, X-Robots-Tag: noindex, nofollow, noarchive, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff a textovým typem odpovědi.
Pokud read-only discovery endpoint nemůže vrátit požadovaný výstup, například chybějící blogový RSS feed, podcastový RSS feed nebo ICS export události, používá sdílený sendReadOnlyNotFoundResponse(). Odpověď zůstává textová, necacheovaná, neindexovaná, neposílá referrer, má nosniff a u HEAD vrací jen hlavičky.
Chybějící nebo nepřístupné soubory z veřejných souborových a media endpointů vrací textovou 404 odpověď se stejnými no-store, noindex, no-referrer a nosniff hlavičkami, takže se podvržené nebo soukromé souborové URL nemají ukládat v cache ani indexovat.
Chybějící nebo nepřístupné přílohy formulářových odpovědí v administraci používají stejný bezpečný souborový fallback: odpověď zůstává textová, necacheovatelná, neindexovaná, neposílá referrer, má nosniff a u HEAD vrací jen hlavičky. Samotné stažení přílohy sdílí UTF-8 Content-Disposition helper s ostatními downloady, takže české názvy souborů zůstávají čitelné i v administračních exportech.
Běžné veřejné HTML 404 stránky pro chybějící obsah používají sdílený renderPublicNotFoundPage(). Díky tomu mají detailové moduly jednotné Content-Type: text/html; charset=UTF-8, Cache-Control: no-store, X-Robots-Tag: noindex, nofollow, noarchive, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff a přístupný nadpis z veřejné not-found šablony.
Interní administrační JSON akce, které mění stav přes AJAX, jsou POST-only. Při jiné metodě procházejí sdíleným requireJsonHttpMethods() a vrací 405 s Allow: POST, JSON tělem, request_id a stejnými no-store, noindex, no-referrer a nosniff hlavičkami jako běžné administrační JSON odpovědi. Vlastní JSON odpovědi provozních a administračních endpointů používají sdílený sendJsonResponse(), takže status code, request_id, UTF-8 bezpečné kódování a ukončení odpovědi zůstávají konzistentní. Prohlížeč ani mezilehlá cache tak nepracují se zastaralým stavem, administrační JSON odpovědi nejsou indexované nebo referrerem propisované dál a chyba v administraci jde spárovat s technickým logem.
Read-only JSON endpoint pro vyhledávání obsahu v media pickeru používá stejný administrační JSON helper a vrací stejné diagnostické request_id i u prázdných výsledků, takže lze dohledat konkrétní hledání bez ukládání hledaného textu do logového kontextu.
Administrační stažení citlivějších exportů, například potvrzený JSON export CMS, potvrzený CSV export odpovědí formulářů, potvrzený CSV export výsledků anket, potvrzený CSV export audit logu, přílohy formulářových odpovědí, SQL záloha databáze, potvrzený ZIP export galerie nebo potvrzený ZIP export šablony, posílají přes sdílený attachment/download helper Cache-Control: no-store, max-age=0, Pragma: no-cache, X-Robots-Tag: noindex, nofollow, noarchive, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff a jednotný Content-Disposition s ASCII fallbackem i UTF-8 filename*. JSON export, CSV export odpovědí Form Builderu, CSV export výsledků anket, CSV export audit logu, SQL záloha, ZIP export galerie a ZIP export šablony navíc před odesláním attachmentu vyžadují review citlivosti nebo dopadu exportu a potvrzení oprávnění. Exporty se tím zbytečně neukládají v mezicache, neindexují se, neposílají administrační URL jako referrer, české názvy souborů zůstávají čitelné a prohlížeč je nemá interpretovat jako jiný typ obsahu.
Produkční běh Kora CMS zůstává bez Composer závislostí. Composer je použitý pouze pro vývojové nástroje v require-dev.
Release ZIP se vytváří bez adresářů vendor/, node_modules/, lokálních AI/editor metadat typu .codex/, .cursor/, .claude/ a bez vývojových metadata souborů jako composer.json, composer.lock, phpstan.neon.dist nebo .php-cs-fixer.dist.php; pokud je v lokálním checkoutu máte po composer install nebo při práci v editoru, slouží jen pro lokální vývoj a CI. Instalační ZIP i source archive zároveň povinně obsahují root .htaccess, README.md, CHANGELOG.md, VERSION, LICENSE, NOTICE.md, config.sample.php, install.php, migrate.php, docs/admin-guide.md, základní default šablonu, kritické CSS assety a ochranný uploads/.htaccess se stabilním casingem názvů. Samotné balení ZIPu používá explicitní ZipArchive průchod soubory včetně dotfiles, aby ochranný .htaccess nevypadl z artefaktu na Linux runneru. Release skript před vytvořením verze spouští statický release package audit i composer ci:basic, volitelně přes -FullCi také composer ci:full, aby se pravidla balíčku, .gitignore/.gitattributes ochrana lokálních artefaktů ani quality gate nerozbily potichu, a k ZIPu generuje také .sha256 checksum. Součástí základní CI je i self-test release package auditu, který nad dočasnou kopií release guardů ověřuje, že audit opravdu selže na návratu vendor, lokálních AI metadat, node_modules, Compress-Archive, rozbitých release smoke kontrolách a chybějících pravidlech .gitattributes nebo .gitignore. Přepínač -DryRun projde stejný preflight a vytvoří ZIP se checksumem a náhledem nové verze, ale nemění pracovní VERSION ani CHANGELOG.md, nevytváří commit/tag/push a nezakládá GitHub release; náhled changelogu stejně jako ostrý release zachová nahoře novou prázdnou sekci Unreleased pro další vývoj.
Soubor VERSION je jediný zdroj pravdy pro runtime hodnotu KORA_VERSION i pro release balíčky. Základní CI to hlídá přes build/version_metadata_audit.php, aby se lokální verze, dry-run ZIP a source archive nerozjely do různých hodnot. Release preflight ještě před změnou souborů ověří budoucí hodnotu Version evaluated v ACR draftu; ostrý release ji synchronizuje a přidá do stejného commitu jako VERSION a changelog, zatímco dry-run ji pouze ověří.
Základní lokální kontrola:
composer install
composer ci:basiccomposer ci:basic spustí:
- PHP lint přes
build/lint_php.phpa jeho self-testbuild/lint_php_selftest.php, který ověřuje zachycení syntakticky rozbitého PHP souboru i ignorovánívendor,distauploads - HTTP server router self-test přes
build/http_server_router_selftest.php, který v dočasné mini-instalaci ověřuje clean URL routování pro Full CI, statické soubory, chráněné cesty, query parametry a 404 fallback - HTTP test helpery přes
build/http_test_helpers_selftest.php, který nad dočasným PHP serverem ověřuje GET/POST/raw/multipart požadavky, redirecty, cookies, parser skrytých polí a refresh testovací CSRF session - self-test DB audit locku přes
build/test_run_lock_selftest.php, který ve dvou procesech ověřuje, že druhý audit čeká na uvolnění sdíleného file locku místo souběžného zápisu do lokálních testovacích nastavení - repository guardrails audit přes
build/repository_guardrails_audit.phpa jeho self-testbuild/repository_guardrails_audit_selftest.php, které hlídají rezervované DB připojovací proměnné v souborech načítajícíchdb.phpneboconfig.phpa zároveň blokují nechtěně verzované lokální konfigurace,.envsoubory,vendor,node_modules,dist, IDE/AI metadata a uživatelské uploady mimo ochranné.htaccesssoubory - config sample audit přes
build/config_sample_audit.phpa jeho self-testbuild/config_sample_audit_selftest.php, které hlídají, žeconfig.sample.phpzůstává sladěný s hlavní runtime konfigurací a instalačními komentáři - version metadata audit přes
build/version_metadata_audit.phpa jeho self-testbuild/version_metadata_audit_selftest.php, které hlídají platný SemVer vVERSION, načítáníKORA_VERSIONz tohoto souboru a release dry-run práci s verzí v ZIP/source archive - schema parity audit přes
build/schema_parity_audit.phpa jeho self-testbuild/schema_parity_audit_selftest.php, které hlídají kritické sloupce používané veřejnými endpointy proti driftu meziinstall.php,migrate.phpa aktuálním kódem - redirect guardrails audit přes
build/redirect_guardrails_audit.phpa jeho self-testbuild/redirect_guardrails_audit_selftest.php, které hlídají, že requestem nebo formulářem dodané návratové cíle typuredirect,redirect_target,nextareturn_urlzůstávají validované přes sdílený bezpečný helper - audit GitHub Actions workflow přes
build/workflow_audit.phpa jeho self-testbuild/workflow_audit_selftest.php, které hlídají základní a plný CI běh včetně oprávnění, timeoutů, souběhu, připnutých actions, zakázaných write/secrets vzorů a runtime bootstrapu pro HTTP kontroly - accessibility conformance audit přes
build/accessibility_conformance_audit.phpa jeho negativní self-test, které hlídají úplnost 55 WCAG 2.2 A/AA kritérií, shodu matice s ACR, traceability, aktuální metadata a povinné impact review s automatizovaným důkazem u přístupnostně citlivého diffu - source encoding audit přes
build/source_encoding_audit.phpa jeho self-testbuild/source_encoding_audit_selftest.php, které hlídají platné UTF-8 ve verzovaných textových zdrojích a nepovolený UTF-8 BOM - mojibake audit přes
build/mojibake_audit.phpa jeho self-testbuild/mojibake_audit_selftest.php, které hlídají typické zkomolené UTF-8 sekvence v českých textech a povolují jen zdokumentované legacy opravy - whitespace audit přes
build/whitespace_audit.phpa jeho self-testbuild/whitespace_audit_selftest.php, které nad verzovanými textovými zdroji hlídají koncové mezery a chybějící finální nový řádek - úzký PSR-12 smoke check přes
composer format:checka navazující build/test dávky nad postupně rozšiřovanou stabilní sadou helperů; pro lokální dorovnání stejné sady lze použítcomposer format:fix, nyní včetně release smoke testů, HTTP test helperů, unit test harnessu a stabilních sdílených knihoven - PHPStan na levelu 6 nad rozšiřovanou sadou stabilních helperů podle
phpstan.neon.dist; používábuild/phpstan_bootstrap.phpascanFiles, takže zná sdílené symboly bez načítání DB/session side efektů. Self-testbuild/phpstan_bootstrap_selftest.phphlídá, že bootstrap nenačítá databázi, autentizaci, session ani runtime konfiguraci a že zachovává už definované bezpečné konstanty. PHPStan zároveň hlídá i release/testovací build nástroje proti návratu PHP 8.1+ typů do PHP 8.0 platformy - samostatné PHPStan level 6 smoke checky přes
composer analyse:stricta navazující dávky; vedle lint/bootstrap helperů aktuálně pokrývají 245 stabilizovaných souborů včetně veřejných entrypointů, sdílených knihoven, workflow auditu, redirect guardrailů a rozšiřované sady admin workflow pro blogy, stránky, média, formuláře, podcasty, FAQ, události, ankety, místa, rezervace, widgety, komentáře, kontakty, chat, novinky, soubory ke stažení, jídelní a nápojové lístky, kategorie, newsletter, uživatele, galerii, převod obsahu, reorder endpointy a jednoduché akční endpointy - validaci
composer.jsonacomposer.lockpřescomposer validate --strict, takže lokálníci:basichlídá stejný Composer kontrakt jako GitHub Actions - statický release package audit včetně self-testu
build/release_package_audit_selftest.php, který hlídá, že instalační balíček a source archivy zůstávají bez vývojových nástrojů, lokálních metadat, citlivých konfigurací a uživatelských uploadů - theme view audit pro default šablonu včetně self-testu, který hlídá oddělení prezentační vrstvy od requestu, session/server stavu, runtime času, databáze a souborových side effectů i statická duplicitní
id, neexistující cílearia-labelledby/aria-describedby/aria-controls, neplatné statickélabel for, veřejné<section>,<nav>,<aside>,role="search", všechny veřejné<article>prvky bezaria-labelledbya<figure>bloky bezaria-labelledbynebofigcaption, formulářová pole bez labelu nebo ARIA názvu,<fieldset>bez<legend>, obrázky bezalt, iframe beztitle, tlačítka bez explicitníhotype, veřejné tabulky bez<caption>neboaria-labelledbyatarget="_blank"odkazy bezrel="noopener noreferrer"nebo bez oznámení nového okna v přístupném názvu - runtime audit, který ve zdrojích i u skutečně vyrenderovaných veřejných a administračních odpovědí hlídá také POST formuláře bez
csrf_token, iframe beztitlea odkazy starget="_blank"bezrel="noopener noreferrer"nebo bez přístupného oznámení nového okna - runtime guardrail pro explicitní typy formulářových prvků, který vícerádkově hlídá PHP zdroje v
admin/,lib/athemes/i reálné HTML odpovědi, aby se do administrace ani šablon nevrátily<button>nebo<input>beztype - runtime guardrail pro administraci, který hlídá, že odkazy otevírané v novém okně v
admin/*.phppoužívají bezpečnérel="noopener noreferrer"a přístupný text oznamující nové okno; stabilizované administrační obrazovky používají přednostně skrytý text přímo uvnitř odkazu, aby se nepřepisoval viditelný název přesaria-label, dynamicky vytvořené odkazy mají stejnou ochranu i přístupný popis awindow.open()používánoopener,noreferrer - izolovaný release smoke test, který v dočasném snapshot repozitáři skutečně spustí
build/release.ps1 -DryRun -SkipCi, ověří čistý git stav po běhu, zkontroluje obsah release ZIPu i checksum a navíc ověří skutečnýgit archivesource balíček podle.gitattributes; oba artefakty musí obsahovat kritické instalační soubory a nesmí obsahovatvendor,node_modules,dist, lokální IDE/AI metadata, citlivé konfigurace ani uploadovaný obsah mimouploads/.htaccess - unit testy přes
build/unit_tests.php
composer ci:full navíc po ci:basic sekvenčně spustí ještě php build/runtime_audit.php a php build/http_integration.php, takže se hodí před releasem nebo po větší sadě změn. Pokud tyto dva audity spustíte ručně paralelně nad stejnou lokální databází, sdílený lock v build/test_run_lock.php druhý běh pozdrží, protože oba používají dočasná testovací nastavení; jeho self-test je psaný tak, aby správně četl exit code child procesu i na Linux runneru GitHub Actions. Stejný plný balík lze vyžádat i při release přes build/release.ps1 -FullCi; bezpečnou zkoušku bez zásahu do gitu spustíte přes build/release.ps1 -DryRun.
composer ci:module-ready je záměrně delší lokální gate pro větší modulové změny: po základní CI sadě přidává modulový kontrakt, runtime audit a HTTP integraci. Při spuštění přes automation, Codex nebo jiný nástroj nastavte timeout alespoň na 15 minut (900000 ms), aby se běh nepřerušil po několika minutách těsně před koncem a nemusel se opakovat.
Modulové guardy jsou záměrně dvouvrstvé. Hlavní administrační přehledy modulů jsou uvedené v coreModuleDefinitions() jako admin_paths a mají vlastní requireModuleEnabled(). Formulářové, detailové a stav měnící admin endpointy modulů navíc chrání centrální mapa adminRouteModuleRequirement() v auth.php, takže vypnutý modul nejde obejít přímým POSTem na save/delete/action soubor. Text 403 hlášky se odvozuje ze společného admin_label přes moduleAdminLabel(), takže nový modul volá requireModuleEnabled() bez druhého parametru a neopisuje vlastní variantu „Modul … není povolen“ v každé routě. Reprezentativní routy z této mapy kryjí unit testy, modulový audit ověřuje existenci uvedených admin souborů, jejich login guard, absenci duplicitních disabled hlášek i provázání manifestových admin_paths a HTTP integrace ověřuje reálný 403 scénář pro vypnutý modul. Kód, který potřebuje zjistit modul podle cesty, používá sdílené mapy modulePublicPathModuleMap() a moduleAdminPathModuleMap(), ne ruční lokální seznamy.
GitHub Actions workflow v .github/workflows/ci.yml spouští stejný základní balík na push a pull_request do main. Samostatný workflow .github/workflows/full-ci.yml drží plný composer ci:full balík pro ruční spuštění (workflow_dispatch) a pravidelný noční běh; před spuštěním si připraví MySQL, config.php, vestavěný PHP server a čerstvou instalaci CMS. Oba workflow používají aktuální actions/checkout@v6, minimální contents: read oprávnění, řízení souběhu a timeouty, aby CI neběželo na deprecated Node 20 checkout akci a nezůstávalo zbytečně viset.
Pokud administrace po uložení obsahu vrátí formulář zpět s chybou u data nebo času, jde nově o záměrné ochranné chování. CMS přísněji validuje plánované publikování, zrušení publikace, platnost lístků, termíny anket a rezervační časy, aby se neukládaly neplatné nebo nejednoznačné hodnoty.
Totéž platí i pro veřejné rezervace: rezervační formulář nově odmítne neexistující kalendářní datum místo toho, aby ho server tiše převedl na jiný den.
| Příznak | Co zkontrolovat |
|---|---|
| E-maily nedorazí | Ověřte SMTP konstanty v config.php. Zkontrolujte DNS záznamy SPF, DKIM a DMARC na doméně. |
| Bílá stránka po aktualizaci | Spusťte migrate.php. Zkontrolujte PHP error log (php -l soubor.php pro kontrolu syntaxe). |
| Modul se nezobrazuje v navigaci | Ověřte, že je modul zapnutý v Obecná nastavení → Správa modulů. |
| Upload souborů nefunguje | Zkontrolujte PHP rozšíření gd a fileinfo. Zvyšte upload_max_filesize a post_max_size v php.ini a podle hostingu upravte také limit v obecných nastaveních CMS. |
| Cron nefunguje | Ověřte cestu k PHP a oprávnění. Zkuste ruční spuštění: php /cesta/k/webu/cron.php. |
| Web ukazuje stránku údržby | Zkontrolujte nastavení Obecná nastavení → Provoz webu – režim údržby může být zapnutý. |
Kora CMS obsahuje .htaccess pro Apache. Při nasazení na Nginx použijte jako základ tuto konfiguraci:
server {
listen 80;
server_name vas-web.cz;
root /cesta/k/webu;
index index.php;
# Bezpečnostní hlavičky
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header X-Download-Options "noopen" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header Referrer-Policy "same-origin" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Origin-Agent-Cluster "?1" always;
add_header Permissions-Policy "accelerometer=(), browsing-topics=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;
# Zakázané soubory
location ~ ^/(config|db|auth)\.php$ { deny all; }
location ~ ^/(composer\.(json|lock)|phpstan\.neon\.dist|\.php-cs-fixer\.dist\.php)$ { deny all; }
location = /.integrity_snapshot.json { deny all; }
location ~ \.(inc|log|sql|bak|sh|cfg)$ { deny all; }
location ~ /\.env { deny all; }
location ~ /\.git { deny all; }
location ^~ /.github/ { deny all; }
location ^~ /.claude/ { deny all; }
location ^~ /.codex/ { deny all; }
location ^~ /.cursor/ { deny all; }
location ^~ /vendor/ { deny all; }
location ^~ /node_modules/ { deny all; }
# Chráněné adresáře
location ^~ /uploads/forms/ { deny all; }
location ^~ /uploads/backups/ { deny all; }
location ~* ^/uploads/.*\.(php[0-9]?|phtml|phar|cgi|pl|py|rb|sh|asp|aspx|jsp)$ { deny all; }
# Čisté URL – moduly
location = /api/appmarket/v1/update { rewrite ^ /appmarket/update.php last; }
location = /api/appmarket/v1/releases { rewrite ^ /appmarket/publish.php last; }
location = /aplikace { rewrite ^ /appmarket/index.php last; }
location ~ ^/aplikace/([a-z0-9\-]+)/stahnout/([0-9]+)/?$ {
rewrite ^/aplikace/(.+?)/stahnout/([0-9]+)/?$ /appmarket/download.php?slug=$1&version_code=$2 last;
}
location ~ ^/aplikace/([a-z0-9\-]+)/verze/([0-9]+)/?$ {
rewrite ^/aplikace/(.+?)/verze/([0-9]+)/?$ /appmarket/release.php?slug=$1&version_code=$2 last;
}
location ~ ^/aplikace/([a-z0-9\-]+)/?$ {
rewrite ^/aplikace/(.+?)/?$ /appmarket/app.php?slug=$1 last;
}
location ~ ^/authors/?$ { rewrite ^ /authors/index.php last; }
location ~ ^/author/([a-z0-9\-]+)/?$ { rewrite ^/author/(.+?)/?$ /author.php?slug=$1 last; }
location ~ ^/blog/([a-z0-9\-]+)/?$ { rewrite ^/blog/(.+?)/?$ /blog/article.php?slug=$1 last; }
location ~ ^/board/kategorie/([a-z0-9\-]+)/?$ { rewrite ^/board/kategorie/(.+?)/?$ /board/index.php?category_slug=$1 last; }
location ~ ^/board/([a-z0-9\-]+)/?$ { rewrite ^/board/(.+?)/?$ /board/document.php?slug=$1 last; }
location ~ ^/chat/tema/([a-z0-9\-]+)/?$ { rewrite ^/chat/tema/(.+?)/?$ /chat/index.php?topic_slug=$1 last; }
location ~ ^/chat/zprava/([0-9]+)/?$ { rewrite ^/chat/zprava/([0-9]+)/?$ /chat/message.php?id=$1 last; }
location ~ ^/downloads/kategorie/([a-z0-9\-]+)/?$ { rewrite ^/downloads/kategorie/(.+?)/?$ /downloads/index.php?category_slug=$1 last; }
location ~ ^/downloads/serie/([a-z0-9\-]+)/?$ { rewrite ^/downloads/serie/(.+?)/?$ /downloads/series.php?slug=$1 last; }
location ~ ^/downloads/([a-z0-9\-]+)/?$ { rewrite ^/downloads/(.+?)/?$ /downloads/item.php?slug=$1 last; }
location ~ ^/events/typ/([a-z0-9\-]+)/?$ { rewrite ^/events/typ/(.+?)/?$ /events/index.php?type_slug=$1 last; }
location ~ ^/events/([a-z0-9\-]+)/?$ { rewrite ^/events/(.+?)/?$ /events/event.php?slug=$1 last; }
location ~ ^/faq/kategorie/([a-z0-9\-]+)/?$ { rewrite ^/faq/kategorie/(.+?)/?$ /faq/index.php?category_slug=$1 last; }
location ~ ^/faq/([a-z0-9\-]+)/?$ { rewrite ^/faq/(.+?)/?$ /faq/item.php?slug=$1 last; }
location ~ ^/forms/([a-z0-9\-]+)/?$ { rewrite ^/forms/(.+?)/?$ /forms/index.php?slug=$1 last; }
location ~ ^/food/card/([a-z0-9\-]+)/?$ { rewrite ^/food/card/(.+?)/?$ /food/card.php?slug=$1 last; }
location ~ ^/gallery/album/([a-z0-9\-]+)/?$ { rewrite ^/gallery/album/(.+?)/?$ /gallery/album.php?slug=$1 last; }
location ~ ^/gallery/photo/([a-z0-9\-]+)/?$ { rewrite ^/gallery/photo/(.+?)/?$ /gallery/photo.php?slug=$1 last; }
location ~ ^/news/([a-z0-9\-]+)/?$ { rewrite ^/news/(.+?)/?$ /news/article.php?slug=$1 last; }
location ~ ^/polls/([a-z0-9\-]+)/?$ { rewrite ^/polls/(.+?)/?$ /polls/index.php?slug=$1 last; }
location ~ ^/places/([a-z0-9\-]+)/?$ { rewrite ^/places/(.+?)/?$ /places/place.php?slug=$1 last; }
location ~ ^/podcast/([a-z0-9\-]+)/([a-z0-9\-]+)/?$ { rewrite ^/podcast/(.+?)/(.+?)/?$ /podcast/episode.php?show=$1&slug=$2 last; }
location ~ ^/podcast/([a-z0-9\-]+)/?$ { rewrite ^/podcast/(.+?)/?$ /podcast/show.php?slug=$1 last; }
location ~ ^/recipes/?$ { rewrite ^ /recipes/index.php last; }
location = /recipes/kucharka.epub { rewrite ^ /recipes/cookbook.php last; }
location ~ ^/recipes/kucharka/([a-z0-9\-]+)\.epub$ { rewrite ^/recipes/kucharka/(.+?)\.epub$ /recipes/cookbook.php?category_slug=$1 last; }
location ~ ^/recipes/kategorie/([a-z0-9\-]+)/?$ { rewrite ^/recipes/kategorie/(.+?)/?$ /recipes/index.php?category_slug=$1 last; }
location = /recipes/nakupni-seznam { rewrite ^ /recipes/shopping.php last; }
location ~ ^/recipes/([a-z0-9\-]+)/?$ { rewrite ^/recipes/(.+?)/?$ /recipes/recipe.php?slug=$1 last; }
# XML sitemap
location = /robots.txt { rewrite ^ /robots.php last; }
location = /sitemap.xml { rewrite ^ /sitemap.php last; }
# Multi-blog catch-all (musí být poslední)
location ~ ^/([a-z0-9\-]+)/archiv/([0-9]{4})/(0[1-9]|1[0-2])/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&archive_year=$2&archive_month=$3&$args;
}
location ~ ^/([a-z0-9\-]+)/kategorie/([a-z0-9\-]+)/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&category_slug=$2&$args;
}
location ~ ^/([a-z0-9\-]+)/stitky/([a-z0-9\-]+)/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&tag_slug=$2&$args;
}
location ~ ^/([a-z0-9\-]+)/stranka/([a-z0-9\-]+)/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&page_slug=$2&$args;
}
location ~ ^/([a-z0-9\-]+)/serie/([a-z0-9\-]+)/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&series_slug=$2&$args;
}
location ~ ^/([a-z0-9\-]+)/([a-z0-9\-]+)/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&slug=$2&$args;
}
location ~ ^/([a-z0-9\-]+)/?$ {
try_files $uri $uri/ /blog_router.php?blog_slug=$1&$args;
}
# PHP zpracování
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000; # upravte podle svého prostředí
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
include fastcgi_params;
}
}Toto je výchozí šablona. Upravte cesty, server_name a fastcgi_pass podle svého prostředí a konfiguraci otestujte před nasazením. Parametr HTTP_AUTHORIZATION je potřebný pro bezpečný Bearer token lokálního Appmarket publisheru; Apache variantu řeší přibalený .htaccess.
Před nasazením změn spusťte:
composer ci:basic
php build/runtime_audit.php
php build/http_integration.phpStejný balík lze nově pustit i jednou zkratkou:
composer ci:fullPři větších zásazích doplňte i PHP lint:
php build/lint_php.phpHlavní administrační zkratka i základní položka v navigaci se pro nový modul odvozují z manifestového admin_paths a admin_capability. Pokud modul zatím nemá vlastní ručně zařazenou navigační skupinu, administrace ho nabídne ve fallback sekci Další moduly.
Veřejné PHP endpointy modulu patří do manifestového public_paths. Modulový audit kromě existujícího souboru, modulového guardu a statické coverage kontroluje také top-level PHP soubory ve veřejné složce modulu, aby nový detail, feed, souborový endpoint nebo formulář nezůstal mimo centrální kontrakt.
Nový modul v Kora CMS je zatím součást jádra, ne samostatný balíček. Před návrhem modulu proto používejte návrhovou šablonu docs/module-proposal-template.md a checklist docs/developer-modules.md, které shrnují povinné integrační body pro databázi, migrace, administraci, veřejné routy, navigaci, bezpečnost, WCAG 2.2, testy a release dokumentaci. Module key, labely, admin_label, admin_capability, výchozí module_* hodnota, hlavní admin URL, typy pro content picker, typy výsledků veřejného vyhledávání, sitemap sekce, typy měřeného obsahu pro statistiky a příznaky pro nastavení, profily, veřejnou navigaci, public_paths a widgety patří do centrálního manifestu coreModuleDefinitions() v lib/definitions.php; install.php i migrate.php z něj doplňují modulové defaulty a audit z něj odvozuje známé module keys, takže nový modul nevyžaduje další hardcoded seznam v auditních nástrojích. Guardrail build/module_contract_audit.php hlídá, aby se nové moduly nerozjely mezi instalací, migrací, nastavením, navigací, widgety, content pickerem, veřejným vyhledáváním, sitemapou, statistikami, theme závislostmi a administračními entrypointy, včetně platnosti hodnot jako settings_default, veřejná cesta, existence veřejného PHP entrypointu, pořadí navigace, odpovídající isModuleEnabled('...') brána veřejného entrypointu, pokrytí veřejné cesty v analyse:strict / format:check skriptech, manifestové public_paths, admin_paths, admin_label, admin_capability, jedinečné a pojmenované content_reference_types, search_result_types, sitemap_sections, stats_page_types, requireModuleEnabled() brána admin entrypointu bez ruční disabled hlášky, sdílená mapa stav měnících admin rout adminRouteModuleRequirements(), manifestově odvozené 403 hlášky přes moduleAdminLabel(), pokrytí těchto admin rout v analyse:strict / format:check skriptech, literálové isModuleEnabled('...') odkazy a literálové getSetting('module_...')/saveSetting('module_...') odkazy v aplikačním kódu. Hlavní administrační přehledy modulů mají používat sdílený requireModuleEnabled() helper, aby přímá URL vypnutého modulu skončila 403; dynamický scénář admin_disabled_modules_http bere jejich cesty přímo z manifestu a unit testy automaticky procházejí všechny položky adminRouteModuleRequirements(). Admin command centrum má základní fallback zkratku odvozenou z prvního admin_paths a admin_capability, takže nový modul nezůstane skrytý jen proto, že nemá ručně přidanou položku palety. Veřejně navigovatelný modul navíc musí projít dynamickým HTTP smoke scénářem public_module_navigation_http, který bere cesty přímo z manifestu a ověřuje vypnutý i zapnutý stav modulu. Každý veřejný PHP endpoint řízený modulem, tedy i detail, feed, soubor nebo obrázek, patří do public_paths; audit ověřuje existenci souboru, modulový gate i statickou coverage. Pokud modul přidává zdroj do content pickeru, deklaruje jeho request type a popisek v manifestovém content_reference_types; scénář content_reference_disabled_modules_http ověřuje, že se po vypnutí nezobrazí v pickeru, snippetech ani výsledcích search endpointu. Pokud modul přidává výsledky do veřejného search.php, deklaruje jejich typy a popisky v search_result_types; audit hlídá i URL větve v resultUrl(). Pokud modul přidává URL do XML sitemapu, deklaruje interní sekce a popisky v sitemap_sections; audit hlídá, že každá modulová větev v sitemap.php odpovídá manifestu. Pokud veřejný detail modulu zapisuje obsahové návštěvy přes trackPageView(), deklaruje své page_type hodnoty v stats_page_types; dlouhodobé obsahové trendy pak modul zařadí bez ruční mapy ve statistikách. Sdílené helpery moduleDefinition(), knownModuleKey(), moduleSettingKey(), modulePublicPathModuleMap(), moduleAdminPathModuleMap(), modulePrimaryAdminPath(), moduleAdminCapability() a moduleStatsPageTypeMap() slouží jako jediný zdroj pro další modulové lookupy mimo manifest. Modulový audit nově hlídá i to, aby tento přehled, developer checklist, návrhová šablona a administrátorská příručka neztratily klíčové integrační body pro nový modul. Před větší modulovou změnou spusťte composer ci:module-ready.
Součástí návrhu nového modulu je také kontrola dokumentů docs/accessibility/wcag-22-aa-conformance.md, docs/accessibility/a11y-remediation-backlog.md, docs/accessibility/manual-test-protocol.md a docs/accessibility/a11y-impact-decisions.md. Pokud modul zavádí nový typ formuláře, dialogu, uploadu, média, embedu, captcha/auth flow, tabulky, widgetu nebo autorem dodávaného obsahu, musí rovnou určit dopad na accessibility conformance report a doplnit matici, backlog či ruční testovací scénář, nebo výslovně zaznamenat rozhodnutí beze změny stavu. Součástí stejné změny je odpovídající automatizovaný důkaz.
Databázové tabulky vlastněné modulem se deklarují v manifestovém database_tables. Sdílené tabulky jádra se modulům nepřiřazují; moduleDatabaseTables() a moduleDatabaseTableModuleMap() poskytují centrální lookup bez ručních seznamů. Modulový audit kontroluje platný název, jednoznačné vlastnictví a přítomnost každé deklarované tabulky v install.php i migrate.php, takže nový modul nemůže projít composer ci:module-ready s chybějící instalační nebo migrační cestou.
Kora CMS je svobodný software licencovaný pod GPL-3.0-or-later. Plné znění licence je v souboru LICENSE a projektové copyright/licenční oznámení v NOTICE.md. Tato licence se vztahuje na zdrojový kód Kora CMS, přibalené šablony, skripty, styly a runtime assety distribuované s projektem, pokud konkrétní soubor neuvádí jinak.
Obsah vytvořený administrátory, autory nebo návštěvníky webu, nahraná média, databázové záznamy a site-specific data nejsou automaticky licencované pod GPL jen proto, že běží v Kora CMS; jejich licenci určuje vlastník webu nebo autor obsahu.
Veřejný seznam změn CMS je dostupný na /changelog. Stránka vykresluje celý CHANGELOG.md v běžné veřejné šabloně, včetně sekce Unreleased, aby návštěvníci nemuseli otevírat surový Markdown soubor. Odkaz se automaticky nepřidává do navigace ani sitemapu; pokud jej chcete návštěvníkům nabídnout viditelně, přidejte /changelog do menu, patičky nebo obsahu ručně.
- CHANGELOG.md – historie verzí
- docs/admin-guide.md – detailní práce v administraci: Form Builder, podcasty, multiblog, widgety a content picker
- docs/developer-modules.md – checklist pro návrh a implementaci nového modulu
- docs/module-proposal-template.md – vyplnitelná šablona návrhu nového modulu nebo větší modulové funkce
- docs/accessibility/wcag-22-aa-conformance.md – draft WCAG 2.2 AA conformance matice pro Kora CMS
- docs/accessibility/acr-vpat-wcag-draft.md – VPAT/ACR draft odvozený z WCAG matice
- docs/accessibility/a11y-remediation-backlog.md – prioritizovaný backlog přístupnostních oprav
- docs/accessibility/manual-test-protocol.md – ruční testovací protokol pro klávesnici, čtečky, zoom a mobilní šířky
- docs/accessibility/a11y-impact-decisions.md – auditní deník přístupnostně citlivých změn bez změny ACR stavu
- docs/accessibility/author-content-checklist.md – redakční checklist pro alt texty, titulky, přepisy, jazyk částí, odkazy a externí embedy
- docs/accessibility/modules/blog.md – modulová příloha pro Blog: veřejná i administrační workflow, důkazy, ruční scénáře a backlog
- docs/ux-audit-framework.md – framework pro UX a přístupnostní audit