CMS motorCMS engine
Saját PHP tartalomkezelő: webes telepítő, admin, bejegyzések és oldalak, két téma, médiafeltöltés. Egyetlen FRST-futás, 3 kör, $0,60.A custom PHP content management system: web installer, admin, posts and pages, two themes, media upload. One FRST run, 3 rounds, $0.60.
Saját, hálózat nélküli konténert kapsz, 15 percre (5 perc tétlenség után lejár). Fájlfeltöltés nincs. Amit írsz, csak te látod.You get your own network-less container for 15 minutes (it expires after 5 idle minutes). No file uploads. Whatever you write, only you can see.
Admin belépésAdmin login: demo / demo-frst-2026 (/admin/login)
A feladatThe task
Pontosan ezt kapta az FRST bemenetként:This is exactly what FRST got as input:
Keszits egy egyszeru, sajat tartalomkezelo motort (CMS) PHP 8-ban, KULSO FUGGOSEG NELKUL (nincs composer, nincs framework), ami egy sima megosztott tarhelyre felmasolva is fut. Resze: webes telepito, admin felulet bejelentkezessel, tartalom (bejegyzesek es oldalak), cserelheto temak es mediafeltoltes. Menu es hozzaszolas NEM kell.
INTERFESZ (pontosan igy - a birtokbaveteli tesztek erre epulnek)
FUTTATAS
- A tesztek a PHP beepitett szerverevel futnak: php -S 127.0.0.1:PORT -t <projekt gyokere>, ROUTER-SZKRIPT NELKUL. Minden nem letezo fajlra mutato utvonal a gyoker index.php-hoz fut be (front controller); az utvonalat a $_SERVER['REQUEST_URI'] PATH reszebol vedd (a query stringet vagd le). A letezo fajlokat (pl. /themes/alap/style.css, /uploads/...) a szerver maga szolgalja ki.
- Adatbazis: PDO + SQLite, a fajl: data/app.db.
- Megosztott tarhelyen (Apache) a gyoker .htaccess fajlja iranyit minden nem letezo fajlra vagy mappara mutato utvonalat az index.php-ra (RewriteEngine On, RewriteCond %{REQUEST_FILENAME} !-f, RewriteCond %{REQUEST_FILENAME} !-d, RewriteRule ^ index.php [L]).
ALTALANOS SZABALYOK
- Minden HTML valasz teljes dokumentum: <!DOCTYPE html>, <html>, <head> benne <meta charset="utf-8"> es <title>.
- Atiranyitas: 302 statusz + Location fejlec, RELATIV utvonallal, pontosan ugy, ahogy lent all (pl. Location: /admin/login).
- CSRF: MINDEN urlapban legyen egy <input type="hidden" name="csrf" value="..."> mezo, a munkamenethez kotott tokennel. Minden POST keres (a telepito es a bejelentkezes is) hianyzo vagy hibas csrf mezovel 403-at ad es SEMMIT nem valtoztat.
- Minden felhasznaloi adat htmlspecialchars($x, ENT_QUOTES, 'UTF-8') kodolassal kerul a HTML-be.
TELEPITO
- Amig nincs config.php a projekt gyokereben, MINDEN utvonal 302 -> /install (kiveve magat a /install-t).
- GET /install: urlap a kovetkezo mezokkel: site_title, admin_user, admin_pass, csrf.
- POST /install: ha a site_title vagy az admin_user ures, vagy az admin_pass 8 karakternel rovidebb -> 422 es az urlap ujra, config.php NEM jon letre. Kulonben: letrehozza a data/app.db adatbazist a schema.sql alapjan, felveszi az admin felhasznalot (password_hash), elmenti a site_title-t es az aktiv temat ("alap"), kiirja a config.php-t, majd 302 -> /admin/login.
- Telepites utan a GET /install es a POST /install is 404.
- A config.php-t a TELEPITO irja; a forrasban NEM szerepelhet.
BELEPES
- GET /admin/login: urlap: username, password, csrf.
- POST /admin/login: jo adatokkal uj munkamenet-azonosito (session_regenerate_id(true)) es 302 -> /admin. Rossz adatokkal 401 es az urlap ujra.
- Bejelentkezes nelkul minden mas /admin... utvonal (GET es POST is) 302 -> /admin/login. (Ez a csrf-ellenorzes ELOTT tortenik.)
- GET /admin: 200, vezerlopult.
- MINDEN admin oldalon (a login kivetelevel) legyen kilepes-urlap: POST /admin/logout (csrf mezovel) -> munkamenet megszuntetese, 302 -> /admin/login. Utana a GET /admin ujra 302 -> /admin/login.
TARTALOM
- Ket tipus: post (bejegyzes) es page (oldal). Mezok: type (post|page), title, slug, body, status (draft|published). A slug EGYEDI az osszes tartalom kozott.
- A slug szabalya: ha az urlapon ures, a cimbol keszul. Mindket esetben normalizalni kell: kisbetu, a magyar ekezetes betuk ekezet nelkul (á->a, é->e, í->i, ó/ö/ő->o, ú/ü/ű->u, a nagybetuk is), minden mas nem [a-z0-9] karaktersorozat egyetlen kotojel, a vegekrol a kotojel levagva. Pelda: "Árvíztűrő tükörfúrógép! 2026" -> "arvizturo-tukorfurogep-2026".
- GET /admin/content: az osszes tartalom listaja; minden tetelnel szerkeszto link (/admin/content/{id}/edit) es torlo urlap (POST /admin/content/{id}/delete, csrf mezovel).
- GET /admin/content/new: urlap: type, title, slug, body, status, csrf.
- POST /admin/content/new: ures (csak szokozokbol allo) title -> 422; foglalt slug -> 422; ervenytelen type vagy status -> 422 (hiba eseten az urlap ujra, semmi nem jon letre). Siker: 302 -> /admin/content/{id}/edit.
- GET /admin/content/{id}/edit: az urlap kitoltve. POST /admin/content/{id}/edit: ugyanazok a mezok es szabalyok (a sajat slugjanak megtartasa NEM utkozes); siker: 302 -> /admin/content/{id}/edit. Nem letezo id -> 404.
- POST /admin/content/{id}/delete: torles, 302 -> /admin/content.
- published_at: az ELSO publikalas ideje (draft -> published valtaskor, vagy letrehozaskor, ha mar published).
NYILVANOS OLDALAK (az aktiv tema sablonjaival)
- GET /: a publikalt BEJEGYZESEK (oldalak nem, piszkozatok nem) listaja, published_at szerint csokkeno, azonos idonel id szerint csokkeno sorrendben, oldalankent 5. A kovetkezo oldalak: GET /?page=2, /?page=3 ... Minden tetel cime link: <a href="/post/{slug}">. Ervenytelen oldalszam (nem pozitiv egesz, vagy nagyobb mint az utolso oldal; bejegyzes nelkul is letezik az 1. oldal) -> 404.
- GET /post/{slug}: publikalt bejegyzes -> 200 a cimmel es a torzzsel. Piszkozat, oldal vagy nem letezo -> 404.
- GET /{slug}: publikalt OLDAL -> 200. Minden mas (bejegyzes, piszkozat, nem letezo) -> 404.
- A body sima szoveg: kodolva jelenik meg, az ures sorral elvalasztott bekezdesek kulon-kulon <p> elembe kerulnek.
- 404: az aktiv tema 404.php sablonja, 404-es statusszal.
- Minden nyilvanos oldal <title> elemeben szerepel a site_title.
TEMAK
- Egy tema = egy mappa a themes/ alatt, benne: layout.php, home.php, single.php (bejegyzes es oldal), 404.php, style.css. Ket tema keszuljon: "alap" es "sotet" (kulonbozo megjelenessel).
- A home.php, a single.php es a 404.php kimenete a tema layout.php-jaba agyazva jelenik meg (a 404 is).
- A tema layout-janak <body> eleme a class attributumaban tartalmazza: theme-<a tema mappajanak neve> (pl. <body class="theme-alap">).
- Az aktiv tema es a site_title a settings tablaban van (kulcs-ertek).
- GET /admin/settings: urlap: site_title, theme (select, az ertekek a themes/ alatti tema-mappak nevei), csrf. POST /admin/settings: ures site_title -> 422; olyan theme ertek, ami nem a themes/ egy letezo tema-mappaja (pl. "../templates", "nincs", ures) -> 422; siker: mentes es 302 -> /admin/settings.
- Uj tema = uj mappa a themes/ alatt a fenti fajlokkal: KODVALTOZTATAS NELKUL meg kell jelennie a valasztoban es hasznalhatonak kell lennie.
MEDIA
- GET /admin/media: feltolto urlap (multipart/form-data, mezok: file, csrf) es a feltoltott fajlok listaja; minden tetelnel a nyilvanos URL linkkent, PONTOSAN igy: <a href="/uploads/EEEE/HH/<nev>"> (egy perjellel - a //uploads/... protokoll-relativ URL, a bongeszo mas hosztra menne), es torlo urlap (POST /admin/media/{id}/delete, csrf mezovel).
- POST /admin/media: engedelyezett kiterjesztes (kisbetusitve): jpg, jpeg, png, gif, webp, pdf - ES a fajl valodi tartalmanak MIME tipusa (finfo) is egyezzen a kiterjeszteshez tartozoval (image/jpeg, image/png, image/gif, image/webp, application/pdf). Legfeljebb 5 MB. Siker: a fajl az uploads/EEEE/HH/<legalabb 16 hexa karakteres veletlen nev>.<kiterjesztes kisbetuvel> helyre kerul (az eredeti fajlnev SOHA nem kerul a celutvonalba), bejegyzes a media tablaba, 302 -> /admin/media. Barmely szabalysertes -> 422, es SEMMI nem kerul az uploads/ ala.
- A feltoltott fajl a nyilvanos URL-jen valtozatlan tartalommal letoltheto.
- POST /admin/media/{id}/delete: a fajl es a bejegyzes torlese, 302 -> /admin/media. Utana az URL 404.
- uploads/.htaccess (a forrasban): tiltja a PHP-fajlok kiszolgalasat: <FilesMatch "\.(php|phtml|phar)$"> Require all denied </FilesMatch>.
- data/.htaccess (a forrasban): Require all denied.
FAJLOK (kotelezo szerkezet)
- index.php - a front controller.
- .htaccess - az Apache atiranyitas az index.php-ra (lasd FUTTATAS).
- schema.sql - a tablak: users, settings, content, media.
- src/Db.php, src/Auth.php, src/Csrf.php, src/Content.php, src/Media.php, src/Theme.php, src/Installer.php - a logika, felelossegi korok szerint.
- templates/admin/ - az admin sablonok. assets/admin.css - az admin stilusa.
- themes/alap/ es themes/sotet/ - a ket tema (lasd TEMAK).
- uploads/.htaccess, data/.htaccess.
- README.md - telepites megosztott tarhelyre, 3-6 lepesben.
- tests/ - a sajat tesztjeid.
MérésMeasurement
24/24
24 független, rejtett HTTP-teszt: telepítő, belépés, CSRF, kódolás, lapozás, témák, feltöltés-szűrés, törlés. A csapat ezeket soha nem látta.24 independent, hidden HTTP tests: installer, login, CSRF, escaping, pagination, themes, upload filtering, deletion. The team never saw them.
Ítélet: OK – az FRST sikert jelentett, és a független teszt is átment. Hogyan mérünk →Verdict: OK – FRST reported success and the independent test passed too. How we measure →
A futásThe run
| KörRound | SzerepRole | ModellModel | KöltségCost | IdőTime |
|---|---|---|---|---|
| 1 | Architect (Architect) | gemini-3.6-flash | $0.0460 | 22.8 s |
| 1 | PHP Developer | gemini-3.8-flash | $0.0439 | 26.7 s |
| 1 | Frontend Developer | gemini-3.8-flash | $0.0491 | 25.4 s |
| 1 | Tester | gemini-3.8-flash | $0.0364 | 21.0 s |
| 1 | tests ✗ 6 teszt bol 1 bukott. ASSERT: Public upload URL follows pattern /uploads/YYYY/MM/<hex>.png with single leading slash | EQ: Downloaded file content matches uploaded bytes exactly - vart: <binaris adat, 323 bajt> kapott: '<!DOCTYPE html> | <html lang="hu"> | <head> | <meta charset="utf-8"> | <title>Dark Portal CMS</title> | |||
| 2 | PHP Developer | gemini-3.8-flash | $0.1606 | 161.5 s |
| 2 | Frontend Developer | gemini-3.8-flash | $0.0175 | 3.4 s |
| 2 | Tester | gemini-3.8-flash | $0.0720 | 63.1 s |
| 2 | tests ✗ 6 teszt bol 1 bukott. ASSERT: Public upload URL follows pattern /uploads/YYYY/MM/<hex>.png with single leading slash | EQ: Downloaded file content matches uploaded bytes exactly - vart: <binaris adat, 323 bajt> kapott: '<!DOCTYPE html> | <html lang="hu"> | <head> | <meta charset="utf-8"> | <title>Dark Portal CMS</title> | |||
| 3 | PHP Developer | gemini-3.8-flash | $0.0402 | 17.8 s |
| 3 | Frontend Developer | gemini-3.8-flash | $0.0203 | 5.8 s |
| 3 | Tester | gemini-3.8-flash | $0.0421 | 21.0 s |
| 3 | Gatekeeper | gemini-3.6-flash | $0.0681 | 29.6 s |
| 3 | Git commit msg | gemini-3.6-flash | $0.0085 | 4.1 s |
A ✗ sorok a determinisztikus kapuk bukásai: ezekből kapták a munkások a célzott visszajelzést a következő körre.The ✗ rows are deterministic gate failures: the workers got targeted feedback from these for the next round.
CommitokCommits
- 2026-09-25 22:53 commit (initial): FRST alap: a futas elotti allapot
- 2026-09-25 22:55 commit: FRST kor 1 (kapu bukas): tests BUKOTT (6 teszt bol 1 bukott. ASSERT: Public upload URL follows pattern /uploads/YYYY/MM/<hex>.png with single leading sl
- 2026-09-25 23:00 commit: feat: implement custom PHP 8 CMS with admin, themes, media and tests