Projekt
Eget CMS
Et selvhostet PHP-CMS som rygrad i denne portfolio: én kodebase til den offentlige site, medlems- og adminområder og den daglige drift. Det er ikke et generisk “page builder”-produkt—det er systemet, jeg bruger til sites, hvor datamodel, rettigheder og deployment skal forblive forståelige år senere. Klare skillelinjer betyder noget: migrationer du kan stole på, capabilities du kan gennemgå, temaer du kan skifte, og oversættelsesfiler du kan diff’e i Git.
Hvad er det?
CMS’et er applikationen, der leverer HTML til besøgende og driver alt bag login: brugere, roller, grupper, interne beskeder, kalendere, e-mail-værktøjer, plugins og valgfrie apps under apps/. Det offentlige lag er bevidst slankt: PHP-skabeloner, fælles header og footer samt assets til det aktive tema under content/. De tungere emner—autentifikation, autorisation, database og forretningslogik—ligger i et forudsigeligt PHP-lag med PDO mod MySQL eller MariaDB.
Den opdeling betyder noget i det daglige. Du kan redesigne forsiden eller udrulle et dokumentations-tema uden at røre password-hashing. Omvendt kan du hærde sessions, tilføje to-faktor-login eller udvide aktivitetsloggen uden at omskrive alle skabeloner. Funktioner bliver ofte lokale: en ny bloktype, en ny admin-side, et API-endpoint eller en migrationsfil—ikke et kvartalsvis framework-løft.
Denne portfolio-side er også reference-installationen: det du læser her, er det der kører i produktion. Den “dogfooding” holder prioriteterne ærlige—hvis oversættelser er besværlige at vedligeholde eller migrationer er skræmmende at køre, mærker jeg det med det samme.
Skærmbillederne i teksten deler mappe med galleriet nederst: tilføj JPG, PNG eller WebP under img/projects/eget_cms/, så vises de begge steder automatisk. Afsnittene nedenfor gennemgår arkitektur, sprog, admin-workflows, moduler, temaer og sikkerhed mere detaljeret.
Arkitektur og data
Det relationelle skema er ikke primært “rettet i hånden via phpMyAdmin”. Ændringer går gennem et migrationslag: hvert trin er kode, der kan reviewes, testes på en kopi af produktion og køres i rækkefølge. Det giver reproducerbare opgraderinger fra ny installation til en langlivet instans og et spor af, hvad der ændrede sig mellem versioner.
Installations- og bootstrap-scripts supplerer migrationer: første opsætning kan så nødvendige tabeller, standardindstillinger og admin-brugere i en kendt tilstand. Når “hvordan databasen skal se ud” ligger i versionsstyring, mindskes problemet med “det virker på min maskine”, når du kloner repoet eller tilføjer staging. Rollback er stadig bevidst arbejde—som i ethvert relationelt system—but fremadrettede migrationer med backup er langt lettere at drive end ad hoc ALTER-sætninger.
Konfiguration er samlet ét sted: sitetitel, meta, kontaktoplysninger, cookie-samtykke og analyse-hooks kan styres fra indstillings-tabeller og admin-skærme, så drift ikke leder efter PHP-konstanter ved rutineændringer. Cache og indstillinger følger genkendelige mønstre (fx dedikerede settings_*-tabeller), så nye funktioner ikke opfinder ny lagring hver gang.
// Kør afventende migrationer i rækkefølge (forenklet eksempel)
$runner = new MigrationRunner($pdo);
$runner->runPending();
- Migrationer er versioneret og kører i fast rækkefølge; nye miljøer konvergerer mod samme struktur.
- Indstillinger og cache følger genkendelige mønstre (fx dedikerede settings_*-tabeller) i stedet for specialcases.
- Kerne-PHP, JSON/API-endpoints og tema-assets er adskilt, så hver del kan ændre i sit eget tempo.
- Valgfrie apps under apps/ kan registrere menuer, routes og admin-UI uden at fork’e kernen.
- Samme PDO-forbindelse og transaktionsdisciplin understøtter både offentlige læsninger og privilegerede skrivninger.
- Fil-integritet og sikkerheds-dashboards kan overvåge kritiske stier, når modulerne er aktiveret.
Sprog og oversættelser
Den offentlige site og admin-backoffice understøtter dansk, engelsk og tysk. Tekster ligger i PHP-oversættelsesfiler under translations/ (og tilsvarende under admin/translations/ til dashboardet). Nøgler bruger punktnotation—nav.home, project_cms.hero_lead—så relateret copy holdes samlet og er let at søge i.
Besøgende skifter sprog i headeren: valget gemmes i en cookie og kan også sendes som ?lang=da|en|de til delbare links. Admin-brugere kan gemme foretrukket sprog på profilen, som indlæses før cookie-fallback, når de arbejder i /admin/.
Valgfrie apps kan have egne oversættelsesmapper; når du åbner en app-skærm, merges dens strenge ind i det aktive sprog. Det holder kernen slank, mens fx en projektleder- eller kursus-app stadig kan levere lokaliseret UI uden at redigere kerne-filer.
- Tre sprog fra start: da (standard), en og de—med samme nøglestruktur i alle filer.
- Portfolio, FAQ, juridisk tekst og projektbeskrivelser oversættes på samme måde som navigationslabels.
- Manglende nøgler kan falde tilbage til en standardstreng i koden, hvilket hjælper trinvis oversættelse.
- Tema-indhold kan redigeres pr. sprog, hvor editoren gemmer sprogspecifikke felter.
Admin og roller
Backoffice er session-baseret: efter login re-valideres hver admin-forespørgsel, og der indlæses rolle og capabilities. Rettigheder udtrykkes som capabilities knyttet til roller—ikke som spredte if-sætninger pr. side. Det gør det realistisk at tilføje et nyt REST-endpoint eller en ny admin-modul og bruge samme regler overalt—fra HTML-formularer til fremtidige JSON-forbrugere.
CMS’et sælges ikke som headless-first, men det betyder ikke, at API’er mangler. Hvor det gavner—analytics, progressive forbedringer eller en fremtidig SPA-skal—kan afgrænsede JSON-endpoints eksponere data med samme auth- og CSRF-antagelser som den klassiske admin-UI. Målet er en kontrolleret overflade: eksplicitte routes, payloads og tjek.
Redaktører får strukturerede værktøjer: indholds-editor med tema-skift, mappe-bevidste sider, rich text hvor det giver mening og pickers til billeder, dokumenter, brugere og grupper. Tekniske operatører får dashboards til fejl, e-mail-køer, integrationer og sikkerhedshændelser, når modulerne er aktive.
Medlemmer med lettere roller kan bruge et separat medlemsområde (dashboard, profil), mens administratorer bliver i den fulde admin-shell—samme auth-stack, forskellige menuer og capabilities. Det undgår at give alle loggede brugere nøgler til migrationer eller brugeradministration.
Moduler du faktisk bruger
Ud over “sider og brugere” har CMS’et driftsmoduler, som små teams ofte sætter sammen af plugins: samlet storage-browser til billeder, video, lyd og dokumenter; interne beskeder; kalender; e-mail-design og sendelog; indholdsanalyse; samt uploadbare plugins eller apps, der udvider menuen.
Hvert modul er i princippet valgfrit—capabilities og indstillinger afgør, hvem der ser hvad—but at have dem i én kodebase giver fælles auth, logging og oversættelsesmønstre. Du skal ikke forene fem leverandørers idé om en “brugerrolle”.
- Storage-bibliotek med bulk-handlinger, previews og API til picker-modaler i editoren.
- Bruger-, rolle-, gruppe- og tag-administration med pickers genbrugt på tværs af skærme.
- E-mail: skabeloner, blocklists, kø-inspektion og lifecycle-log for integrationer.
- Sikkerheds-dashboard: IP-styring, malware-scan hooks, fil-integritet og live angrebsvisninger når aktiveret.
- Plugins og apps kan uploades eller udvikles under apps/ med egne routes og oversættelser.
Indhold og tema
Offentlige sider samles fra temaer: hvert tema leverer sektioner (hero, services, FAQ, …) og mindre blokke (tekst, billede, galleri) beskrevet med schema og renderet med PHP-skabeloner. Det ligger tættere på et komponentbibliotek end på uoverskuelig HTML i ét WYSIWYG-felt. Redaktører vælger struktur; udviklere styrer præsentation og tilgængelighed.
Fordi temaer ligger under content/, kan du vedligeholde et portfolio-tema og et separat dokumentations-tema eller forgrene et tema til en kampagne uden at blande uvedkommende CSS ind i kernen. Tema-skift er konfiguration plus stier til assets—ikke omskrivning af forretningslogik—så design-iteration bliver billigere.
Editoren forstår temastruktur: sektioner og blokke beskrives med schema, renderes med PHP-skabeloner og kan redigeres visuelt eller via struktureret JSON/kode for power users. Sådan forbliver denne projektside vedligeholdelig—lang tekst i oversættelsesfiler, layout i temaet, skærmbilleder fra en simpel billedmappe.
Sikkerhed og privatliv
Sikkerheds-byggesten er indbygget, ikke limet på bagefter: CSRF-tokens på tilstandsskabende requests, hærdet password-håndtering, valgfrit 2FA og struktureret logging af hændelser der er værd at auditering. Cookie-samtykke og analyse-hooks følger samme konfigurationsmodel som resten af sitet, så indsamling kan holdes slået til kun når politik og besøgenes valg tillader det.
Filosofien er bevidst minimalisme: intet afhængighedstræ større end produktet kræver, og ingen bagvedliggende magi du ikke kan følge i PHP. Når noget går galt—fejlede logins, en langsom forespørgsel—vil du have logs og kode-stier du kan læse. Stacken forbliver med vilje kedelig, så “sikker nok til en rigtig site” ikke kræver et dedikeret platform-hold.
Privatlivsbevidst analyse og cookie-samtykke følger samme indstillingsmodel som resten af sitet, så du kan dokumentere indsamling, respektere besøgendes valg og stadig give redaktører trafikindsigt, når politikken tillader det. Aktivitets- og sikkerhedslogs hjælper med “hvem ændrede dette?” uden at eksportere data til et tredjeparts-SIEM for en portfolio-installation.