Instalace a spuštění#
Požadavky#
| Co | Verze / poznámka |
|---|---|
| PHP | ≥ 8.3 (composer.json); ověřeno na 8.4 a 8.5, viz PHP 8.5 |
| MySQL / MariaDB | |
| Composer | |
| Node.js a npm | pro sestavení assetů |
| Redis | doporučené, dá se vypnout |
| Elasticsearch | volitelné — bez něj hledání spadne na SQL |
ffmpeg / ffprobe |
pro video v galeriích — bez ffprobe se video odmítne, obrázky fungují dál |
memory_limit |
PHP-FPM aspoň 256M (pro obrázky do 40 Mpx kolem 320M), CLI víc nebo -1; viz Provozní požadavky |
Postup: rozjíždím projekt poprvé#
- Závislosti
- Konfigurace — zkopíruj vzor a vyplň skutečné hodnoty:
Do local.neon patří přístup k databázi (mariadb.* — jediný zdroj pravdy,
používá ho Doctrine i nette/database) a jwt.secret. Includuje se jako
poslední v config/common.neon, takže přebíjí všechny placeholdery.
-
Databáze — základní schéma je v
docs/sql/. Změny schématu jsou ručně psané opakovatelné skripty vsql/; na nasazeném serveru je při nasazení pouštíbin/migrate.php(viz Checklist nasazení). Na vlastní databázi, kde evidence ještě není, pusť první běh ručně jakophp bin/migrate.php --baseline: založí evidenci a starší skripty zapíše jako hotové. Databázi je proto nutné před ním dorovnat ručně. Bez--baselineskript na databázi bez evidence skončí chybou. -
Redis — na hostingu bez Redisu odkomentuj v
local.neon:
redis:
storage: false
sessions: false
jwt:
useRedis: false # deny-list půjde do DB (potřebuje cron cleanup)
ratelimit:
useRedis: false
- Assety
Front téma se sestavuje ze své složky — viz Témata frontendu.
-
Práva k zápisu —
temp/,log/,var/a složky pro nahrané soubory. -
Cron token nastav v administraci (
1/cron.token) dřív, než zapojíš crony — bez něj vrací všechny úlohy 403. -
Naplánované úlohy podle Checklistu nasazení a Naplánovaných úloh.
local.neon nepatří do gitu
Obsahuje hesla k databázi a JWT secret. V .gitignore je (/config/local.neon
i /config/Shared/local.neon) — zkontroluj to dřív, než něco commitneš.
validateClass není migrace
Ověření entity proti schématu jen řekne, co nesedí. Změnu ve schématu musíš
provést sám skriptem v docs/sql/.
Postup: zapínám ladicí režim na vývojovém stroji#
Nebo přes proměnnou prostředí NETTE_DEBUG=1, která má přednost. Produkční
výchozí stav je vypnuto; podrobně Checklist nasazení.
Ladicí režim se nenastavuje v local.neon
Rozhoduje se o něm v App\Bootstrap::resolveDebugMode(), tedy před načtením
konfigurace. Cokoli napsaného do NEONu nemá na debug žádný vliv.
Konfigurace#
| Soubor | Obsah |
|---|---|
config/common.neon |
kostra, includuje zbytek |
config/Shared/*.neon |
sdílená konfigurace po oblastech (25 souborů) |
config/Shared/local.neon |
🔴 tajemství a lokální přepisy, mimo git |
config/debug.neon |
co platí jen v ladicím režimu |
config/Admin/, config/System/ |
konfigurace jednotlivých sekcí |
Část nastavení je v databázi, ne v souborech — čte je SettingManager a v runtime
SettingContext.
SettingContext má dvě vrstvy
setSettings() je pro managery no-op. Ve skriptech musíš zapsat i
setServerSettings(), jinak nastavení nedorazí tam, kam čekáš.
| Vrstva | Plní ji | Obsah | Čte ji |
|---|---|---|---|
klientská (setSettings()) |
presenter při startu | na frontu jen veřejná podmnožina | getPublicSettings() — jediné, co smí do šablony nebo po drátě |
serverová (setServerSettings()) |
Api SettingManager ve svém konstruktoru |
plná mapa: neon params + publicParams + všechny řádky DB |
get() a getSettings(); serverová vrstva vyhrává, klientská je záloha |
Proč dvě: settingContext je DI singleton sdílený frontovým presenterem a Api managery, které
se v tomtéž požadavku volají přes ApiBridge::internalCall() (a jejich startup() neběží).
Serverový kód z něj čte SMTP údaje, appTempDir, emailTemplatePath nebo feedsDir. Hranice,
kterou se tajemství nesmí dostat ven, je HTTP odpověď (endpoint getPublicMerged a Latte
proměnná $settings), ne paměť serverového procesu.
Kontext nezužuj na veřejnou podmnožinu
Kdyby serverová vrstva obsahovala jen veřejné klíče, přestane odcházet pošta a spadne nahrávání přes dropzone — chybějící SMTP údaje a cesty se projeví až v jiném modulu.
Na front (klientovi) jde jen veřejná podmnožina — SettingManager::getAllPublic(),
endpoint :Api:System:Setting:getPublicMerged:
| Zdroj | Na front |
|---|---|
neon publicParams (config/Shared/parameters.neon) |
ano — krátký whitelist |
neon params (adresáře, absolutní cesty) |
nikdy |
řádek cms_system_settings s is_public = 1 |
ano |
řádek cms_system_settings s is_public = 0 |
ne, jen serverová vrstva |
Příznak is_public je fail-closed: v PHP i v DB (DEFAULT 0) je výchozí false
a upsert přes SettingManager u nového řádku zapíše 0, u existujícího příznak nemění.
Nový klíč je tedy soukromý, dokud ho někdo vědomě nezveřejní. Výjimka: nový řádek jazykové
varianty (<klíč>.<alias jazyka>, např. site.name.de) zdědí is_public základního klíče,
jinak by front u nového jazyka tiše ukazoval výchozí jazyk.
Klíč, který potřebuje šablona nebo JS, zveřejni v migraci
Bez is_public = 1 front hodnotu nedostane a čte prázdno — bez chyby. Obráceně:
zveřejnit klíč s heslem nebo tokenem (email.smtp.password) znamená poslat ho
každému návštěvníkovi. getMerged/getAll jsou jen pro administraci a příznak
nečtou.
Druhá vrstva proti úniku: SettingSecrecy. Latte proměnná $settings se plní i v Admin,
Api a Cron kontextu, kde mapa tajemství legitimně obsahuje. SettingInitTrait ji proto pouští
přes SettingSecrecy::stripSecrets(), který vyhodí každý klíč odpovídající vzoru
secret|password|token|salt|credential. Není to autorizace — autoritou zůstává is_public;
že jsou obě vrstvy nezávislé, hlídá tests/Unit/Security/SettingPublicExposureTest.php.
| Vzor | Kde | Navíc |
|---|---|---|
SECRET_KEY_PATTERN |
stripSecrets(), test |
— |
CREDENTIAL_KEY_PATTERN |
jen migrační shim v SettingManager (rozhoduje bez is_public) |
smtp — email.smtp.host a email.smtp.username jsou části týchž přihlašovacích údajů |
Užší vzor nerozšiřuj o smtp
SECRET_KEY_PATTERN je kontrakt testu i stripSecrets(). Se smtp by se nedalo odlišit
hlídané jméno od vědomě širší heuristiky a ze šablon by zmizely i neškodné klíče SMTP.
Jazykové varianty klíče#
cms_system_settings (module, key, value) nemá sloupec jazyka. Jazykově závislá hodnota
(identita webu: site.name, site.tagline) proto leží ve vlastním řádku s příponou aliasu
jazyka (site.name.en); základní klíč bez přípony nese výchozí jazyk a je fallbackem pro
ostatní. Klíč je v celém systému plochý řetězec ($map[$module][$key]), tečka nic nevnořuje.
LocalizedSettingKey::resolve() čte takto:
- neprázdná hodnota
<klíč>.<alias>jazyka stránky, - jinak základní
<klíč>, - jinak
''.
Prázdná jazyková hodnota = nepřeloženo
Vypnout text jen v jednom jazyce nejde. Je to záměr: prázdné pole v jazykovém tabu
administrace nesmí na /en/ tiše smazat název webu. Jazykové varianty dostávají jen klíče,
které vyjmenuje volající (identita webu), ne libovolný klíč.
Klíč s vlastní obrazovkou nastavení#
Obecná obrazovka Nastavení → Nastavení webu (SettingPresenter) vypíše každý řádek
cms_system_settings modulů 1 a 2 jako pole formuláře; klíč, který nezná, dá do sekce
„ostatní“ pod syrovým jménem. Klíče, které potřebují popisky, upload nebo validaci, mají vlastní
záložku (provozovatel, identita webu, vzhled webu, nastavení administrace) a z obecné obrazovky
jsou vyřazené. Každý klíč se tak mění právě na jednom místě.
| Kde je klíč vyřazený | Kdy |
|---|---|
SettingPresenter::HIDDEN_KEYS |
výčet klíčů, které edituje jiná záložka |
SettingPresenter::HIDDEN_LOCALIZED_BASE_KEYS |
základní klíč, jehož jazykové varianty (site.name.en) se skrývají podle předpony; jazyky přibývají v databázi, výčet by se tiše rozešel |
Některé klíče tvoří dvojici, která musí sedět: šablona a její cesta (theme.frontend +
theme.frontend.path, theme.admin + theme.admin.path). Cesta proto nemá vlastní pole, dopočítá
se z vybrané šablony a ukládá se s ní najednou.
Téma administrace se načítá ještě před přihlášením (viz Admin presenter),
takže na přihlašovací stránce není identita a admin-only get-merged odpoví zamítnutím.
ThemeRepository proto čte theme.admin* z veřejné mapy (get-public-merged) a bere je natvrdo
z modulu 1. Volitelný klíč (theme.admin.skin) chybět smí, do kontroly úplnosti se nepočítá.
Postup: přidávám klíče s vlastní záložkou nastavení
- Řádky založte migrací. Klíč, který čte front nebo bootstrap administrace, dostane
is_public = 1už v migraci; uložení formuláře příznak u existujícího řádku nezmění. - Klíče přidejte do
SettingPresenter::HIDDEN_KEYS(jazykové varianty přesHIDDEN_LOCALIZED_BASE_KEYS). - Závislou hodnotu (cestu) nedávejte do formuláře, dopočítejte ji a uložte s hlavní hodnotou.
- Záložku přidejte do
app/UI/Admin/System/Templates/@settingsTabs.latte. - Čte-li klíč bootstrap administrace, nechte ho v modulu 1 a ověřte přihlašovací stránku v odhlášeném prohlížeči.
Překlep v dvojici theme.admin + theme.admin.path = 500 na celé administraci
ThemeMapper z ní skládá cestu k šablonám; při rozporu skončí DefaultThemeResolver::resolve()
na variantExists() on null na každé admin stránce včetně přihlášení, takže chybu nejde
opravit z administrace. Stejně dopadne klíč tématu přesunutý mimo modul 1 a klíč bez
is_public = 1 na přihlašovací stránce, jakmile se RBAC vynucuje.
Klíč mimo HIDDEN_KEYS jde měnit ze dvou míst
Obecná obrazovka ho ukáže jako holé textové pole bez validace vlastní záložky; hodnota uložená odtamtud obejde kontrolu, na kterou se zbytek kódu spoléhá.
Obrázek v nastavení#
Logo, favicona, sdílecí obrázek i fotka provozovatele (site.*.assetId,
web.operator.avatar.assetId) se v nastavení drží jen jako id assetu; obrázek je běžný
záznam cms_system_images a ukládá ho Api ImageManager, takže je vidět i v administraci
„Obrázky“ a dostane varianty podle svého použití.
Uložení záložky (SiteIdentityFormTrait, OperatorFormTrait) rozhoduje u každého obrázkového
pole jednu ze tří cest a výsledek nese jako trojici id / failed / release:
| Cesta | Asset | Hodnota nastavení |
|---|---|---|
| beze změny | nic | dosavadní id (<pole>CurrentId) |
| nový soubor | zapíše se do téhož assetu, nový vznikne jen když žádný nebyl | id assetu |
| „Odebrat“ | smaže se až po uložení nastavení | prázdná |
| zápis obrázku selhal | nic | failed = true: celé uložení se zruší, i texty |
Všechny klíče záložky jdou jednou dávkou SettingManager::saveBatch() v jedné transakci.
Selže-li dávka, smaže se asset, který v tomhle požadavku teprve vznikl.
null nesmí znamenat zároveň „odebrat“ i „selhalo“
Kdyby vracela cesta jen ?int, selhaný upload by zapsal prázdnou hodnotu a odpojil
dosavadní logo, zatímco formulář hlásí úspěch. Proto trojice a při selhání původní id.
Výměna obrázku = tentýž asset
Nový asset při každé výměně by v cms_system_images nechával osiřelé řádky. Api starému
záznamu přegeneruje varianty podle plánu použití.
Postup: změnil jsem konfiguraci a nic se nestalo#
- Smaž
temp/cache. Nette drží zkompilovaný DI kontejner. - U nového jazyka navíc
temp/cache/translation/— katalog překladů se sám neregeneruje. - U nové třídy může být problém zastaralá mapa tříd.
- Teprve pak hledej chybu v tom, co jsi napsal.
Zastaralá mapa tříd shodí CELÝ web
Ne srozumitelnou hláškou — chybou 500 na všech stránkách naráz. Po přidání jazyka,
nového modulu nebo změně konfigurace vždycky smaž temp/cache.
Ověření po instalaci#
- Titulní stránka.
- Přihlášení do administrace.
- Jeden výpis a jeden formulář v administraci.
- Odeslání jednoho formuláře na frontu.
log/— přibylo něco?
Živý proklik odhalí to, co harness ne
Testovací běh přes CLI obchází spoustu vrstev. Než něco prohlásíš za hotové, proklikej to v prohlížeči — podrobně Testy a nástroje.
Ověř si, ke které databázi jsi připojený
Než začneš cokoli zkoušet, podívej se do config/Shared/local.neon, kam míří
připojení k databázi. Vývoj patří na vlastní kopii dat, ne na databázi běžícího webu.
Kam sáhnout#
| Chci | Kde |
|---|---|
| vzor lokální konfigurace | config/Shared/local.neon.example |
| rozhodnutí o ladicím režimu | app/Bootstrap.php → resolveDebugMode() |
| SQL skripty schématu | docs/sql/ |
| obecná obrazovka nastavení a skryté klíče | app/UI/Admin/System/Presenters/SettingPresenter.php |
| čtení tématu administrace | app/UI/Admin/System/Models/Repositories/Api/ThemeRepository.php |
| příkazy sestavení | package.json, composer.json |
| checklist nasazení | Checklist nasazení |
Navazující kapitoly: Checklist nasazení · Naplánované úlohy · Struktura projektu · Testy a nástroje