Platformă pentru blog. Scris în PHP, fără framework, fără dependențe externe. Funcționează pe orice hosting cu PHP și SQLite.
  • PHP 82.6%
  • CSS 11.3%
  • JavaScript 5.8%
  • Dockerfile 0.3%
Find a file
2026-05-16 14:01:38 +03:00
data add Written Whisper platform 2026-05-07 09:35:55 +03:00
deploy add fediverse & fix error 2026-05-16 09:28:51 +03:00
public fix error fediverse 2026-05-16 14:01:38 +03:00
src fix error fediverse 2026-05-16 14:01:38 +03:00
tools add Written Whisper platform 2026-05-07 09:35:55 +03:00
.env.example add Written Whisper platform 2026-05-07 09:35:55 +03:00
.gitignore add Written Whisper platform 2026-05-07 09:35:55 +03:00
.htaccess add Written Whisper platform 2026-05-07 09:35:55 +03:00
LICENSE add Written Whisper platform 2026-05-07 09:35:55 +03:00
README.md fix error fediverse 2026-05-16 13:41:41 +03:00

Written Whisper

Blog personal simplu, rapid si sigur. Scris in PHP pur, fara framework, fara dependente externe. Functioneaza pe orice hosting cu PHP si SQLite.

Suporta trei tipuri de continut: articole, note scurte si fotografii.


Cuprins


Cerinte

  • PHP 8.0 sau mai nou
  • Extensia PDO SQLite (activa implicit pe majoritatea hostingurilor)
  • Extensia fileinfo (pentru validarea uploadurilor de imagini)
  • Apache cu mod_rewrite sau nginx

Structura proiectului

written-whisper/
|
|- public/                   <- DOCUMENT ROOT (singurul director expus web)
|  |- index.php              <- pagina principala cu feed
|  |- router.php             <- router pentru URL-uri curate (/slug) + 404 custom
|  |- search.php             <- cautare
|  |- setup.php              <- configurare initiala din browser
|  |- random.php             <- redirect spre articol aleatoriu
|  |- arhiva.php             <- arhiva completa grupata pe luna/an
|  |- 404.php                <- pagina 404 custom cu articole aleatorii
|  |- webmention.php         <- endpoint primire webmentions
|  |- media.php              <- serveste imaginile din data/uploads/
|  |- feed.php               <- RSS
|  |- atom.php               <- Atom feed
|  |- feed-json.php          <- JSON Feed 1.1
|  |- sitemap.php            <- sitemap XML pentru Google
|  |- post.php               <- redirect permanent spre URL curat
|  |- page.php               <- redirect permanent spre URL curat
|  |- favicon.svg            <- favicon implicit (inlocuibil din admin)
|  |- logo.svg
|  |- .htaccess              <- URL-uri curate + securitate + ErrorDocument 404
|  |- uploads/               <- logo si favicon (trebuie sa fie writable)
|  |- assets/
|  |  |- css/
|  |  |  |- main.css         <- stiluri site public (teme, layout, scrollbar, lightbox)
|  |  |  `- admin.css        <- stiluri panou admin
|  |  `- js/
|  |     |- upload.js        <- upload imagini cu compresie (WebP + fallback JPEG)
|  |     |- theme.js         <- comutator de teme (light/dark)
|  |     `- main.js          <- scroll-to-top, bara progres, lightbox, copy link
|  |- partials/
|  |  |- header.php          <- header site (logo + titlu + navigare + teme)
|  |  `- footer.php          <- footer site (feed-uri + contor online)
|  `- admin/
|     |- index.php           <- panou de administrare
|     `- upload.php          <- endpoint AJAX upload imagini
|
|- src/                      <- cod PHP (NU este accesibil din browser)
|  |- config.php             <- configurare site + incarcare settings.json
|  |- db.php                 <- baza de date, CRUD, comentarii, statistici
|  |- security.php           <- CSRF, sesiuni, rate limiting, validare upload
|  |- helpers.php            <- h(), fmt_date() (RO), body_to_html(), reading_time(), highlight_code(), webmention
|  `- markdown.php           <- parser Markdown complet (GFM + extensii)
|
|- data/                     <- stocare date (NU este accesibil din browser)
|  |- blog.db                <- baza de date SQLite (creat automat)
|  |- posts/                 <- fisiere .md exportate automat la salvare postari
|  |- pages/                 <- fisiere .md exportate automat la salvare pagini
|  |- uploads/               <- imaginile din articole/note/fotografii (trebuie sa fie writable)
|  `- settings.json          <- setari editabile din admin
|
|- tools/
|  |- README.md                       <- documentatie unelte CLI
|  |- generate_hash.php               <- genereaza hash bcrypt pentru parola
|  |- import_markdown.php             <- importa orice fisiere .md cu YAML front matter
|  |- migrate_pureblog.php            <- migrare dedicata Pure Blog -> Written Whisper
|  |- export_markdown.php             <- genereaza fisiere .md din baza de date
|  `- import_pureblog_comments.php    <- importa comentariile din PureComments
|
|- deploy/
|  |- docker/                <- Dockerfile, docker-compose.yml, php.ini
|  |- vps/                   <- nginx.conf, apache.conf
|  `- shared/
|     `- .htaccess           <- .htaccess pentru shared hosting (root)
|
|- .htaccess                 <- redirect spre public/ (shared hosting)
`- .env.example              <- template pentru variabile de mediu

Instalare pe shared hosting

Metoda cea mai simpla, compatibila cu orice hosting cPanel/Plesk.

Nota ActivityPub: public/.htaccess include deja o directiva care dezactiveaza ModSecurity pentru endpoint-urile ActivityPub (/inbox, /outbox, /actor). Fara aceasta, unele hosturi blocheaza POST-urile cu corp JSON de la servere federate (Mastodon, Pixelfed), impiedicand urmaritorii sa se conecteze.

Pasul 1 - Incarca fisierele

Prin FTP sau File Manager incarca pe server:

public/   ->  in webroot-ul domeniului (ex: public_html/public/)
src/      ->  UN NIVEL DEASUPRA webroot-ului (ex: public_html/src/)
data/     ->  UN NIVEL DEASUPRA webroot-ului (ex: public_html/data/)

Structura finala pe server:

public_html/
|- .htaccess      <- fisierul .htaccess din radacina proiectului
|- public/
|- src/
`- data/

Pasul 2 - Incarca .htaccess-ul din radacina

Incarca fisierul .htaccess din radacina proiectului direct in public_html/. Acesta redirecteaza automat toate cererile spre directorul public/.

Alternativa mai buna: In cPanel mergi la Domains si schimba Document Root din public_html in public_html/public. Astfel nu mai ai nevoie de .htaccess-ul din radacina.

Pasul 3 - Permisiuni

Asigura-te ca aceste directoare au permisiunile 755:

  • public_html/data/
  • public_html/data/uploads/
  • public_html/public/uploads/

In cPanel File Manager: click dreapta pe director → Change Permissions → 755.

Pasul 4 - Configureaza din browser

Mergi la https://domeniultau.ro/setup.php si completeaza:

  • URL site - adresa completa a blogului
  • Utilizator admin - numele de login
  • Parola - minim 8 caractere (salvata criptat automat)

Dupa salvare esti redirectionat automat la panoul de admin.

Daca preferi configurarea manuala, editeaza src/config.php via FTP (vezi sectiunea Configurare avansata).

Pasul 5 - Gata

Mergi la https://domeniultau.ro - blog-ul functioneaza. Admin: https://domeniultau.ro/admin/


Instalare pe VPS cu nginx

1. Copiaza proiectul pe server

git clone https://github.com/user/written-whisper.git /var/www/written-whisper
# sau copiaza prin SCP/SFTP

2. Permisiuni

chown -R www-data:www-data /var/www/written-whisper/data
chown -R www-data:www-data /var/www/written-whisper/public/uploads
chmod 755 /var/www/written-whisper/data
chmod 755 /var/www/written-whisper/data/uploads
chmod 755 /var/www/written-whisper/public/uploads

3. Configureaza nginx

cp deploy/vps/nginx.conf /etc/nginx/sites-available/written-whisper
nano /etc/nginx/sites-available/written-whisper
ln -s /etc/nginx/sites-available/written-whisper /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Configuratia esentiala:

server {
    listen 80;
    server_name domeniultau.ro;
    root /var/www/written-whisper/public;
    index index.php;

    location / {
        try_files $uri $uri/ /router.php?_slug=$uri;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    }
}

4. SSL cu Let's Encrypt

apt install certbot python3-certbot-nginx
certbot --nginx -d domeniultau.ro -d www.domeniultau.ro

5. Configureaza

Mergi la https://domeniultau.ro/setup.php pentru configurare initiala.


Instalare pe VPS cu Apache

1. Module necesare

a2enmod rewrite headers expires
systemctl restart apache2

2. VirtualHost

cp deploy/vps/apache.conf /etc/apache2/sites-available/written-whisper.conf
nano /etc/apache2/sites-available/written-whisper.conf
a2ensite written-whisper
systemctl reload apache2

Configuratia esentiala:

<VirtualHost *:80>
    ServerName domeniultau.ro
    DocumentRoot /var/www/written-whisper/public

    <Directory /var/www/written-whisper/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

3. SSL

certbot --apache -d domeniultau.ro

Instalare cu Docker

Rapid (development)

git clone https://github.com/user/written-whisper.git
cd written-whisper
cp .env.example .env
# Editeaza .env cu datele tale
docker compose -f deploy/docker/docker-compose.yml up -d

Acceseaza: http://localhost:8080

Variabile de mediu (.env)

SITE_TITLE=Written Whisper
SITE_DESCRIPTION=Blogul meu personal
SITE_URL=http://localhost:8080
ADMIN_USER=admin
ADMIN_PASS=$2y$12$hash_generat_cu_tools/generate_hash.php
PORT=8080

Productie cu Docker

# Genereaza hash pentru parola
docker run --rm php:8.2-cli php -r "echo password_hash('parola_ta', PASSWORD_BCRYPT);"

# Porneste containerul
docker compose -f deploy/docker/docker-compose.yml up -d

Configurare din browser

setup.php - configurare initiala

La prima instalare, mergi la /setup.php. Pagina cere:

Camp Descriere
URL site Adresa completa a blogului (ex: https://example.com)
Utilizator admin Numele de login in panoul de admin
Parola Minim 8 caractere - salvata automat ca hash bcrypt

Dupa salvare, /setup.php redirectioneaza automat la /admin/ si nu mai este accesibil odata ce credentialele sunt configurate.

Daca rulezi cu variabile de mediu (ADMIN_USER setat in .env sau pe server), setup.php redirectioneaza direct la admin.

Schimbarea setarilor din admin

Din /admin/Setari (trei sub-pagini):

Platformă (?section=settings):

Camp Descriere
Logo Incarca o imagine noua
Favicon Incarca un favicon (recomandat: png patrat 64×64 px)
Numele blogului Titlul afisat in browser si in header
Descriere Apare sub logo si in meta tags
URL site Adresa blogului, folosita in RSS/Atom si Open Graph
Data lansarii Afisata in footer ca „Online din X mai 2026 · N zile"
Postari pe pagina Numarul de postari afisate pe pagina principala (implicit 10)

Fediverse (?section=settings-fedi):

Camp Descriere
Banner Imagine de antet pentru profilul Fediverse (1500×500px)
Avatar Imagine rotunda de profil; fallback pe logo-ul platformei
Username Handle-ul in Fediverse (ex: blog) — handle complet: @blog@domeniu.ro
Bio Descriere de profil in Mastodon/Pixelfed, independenta de descrierea blogului
Profil extern Mastodon URL profil extern, pentru rel="me" si fediverse:creator

Sub formularul Fediverse apare lista abonatilor actuali cu avatarul, numele si data abonarii.

Credentiale (?section=settings-creds):

Schimba utilizatorul si parola fara acces FTP - parola curenta este ceruta ca verificare.


Configurare avansata

Fisierul src/config.php

data/settings.json are prioritate fata de valorile din config.php si fata de variabilele de mediu - configurarea din browser suprascrie fisierul.

define('SITE_TITLE',       'Numele blogului');
define('SITE_DESCRIPTION', 'O descriere scurta');
define('SITE_URL',         'https://example.com');
define('ADMIN_USER',       'admin');
define('ADMIN_PASS',       '$2y$12$abc...');  // hash bcrypt
define('POSTS_PER_PAGE',   10);

Ordinea prioritatilor

data/settings.json  (cel mai prioritar - configurat din browser)
    ↓
variabile de mediu  (SITE_URL, ADMIN_USER, ADMIN_PASS etc.)
    ↓
valorile hardcodate din src/config.php  (fallback)

Generarea unui hash bcrypt pentru parola

php tools/generate_hash.php parola-ta-secreta

Utilizare

URL-uri publice

URL Descriere
/ Feed principal cu toate postarile
/titlul-articolului-1 Articol individual (URL curat)
/nota-42 Nota individuala
/foto-43 Fotografie individuala
/despre Pagina custom
/arhiva.php Arhiva completa grupata pe luna si an
/random.php Redirectioneaza spre un articol aleatoriu
/search.php?q=cuvant Cautare
/webmention.php Endpoint primire webmentions (POST)
/feed.php RSS
/atom.php Atom feed
/feed-json.php JSON Feed 1.1
/sitemap.php Sitemap XML pentru Google

Schema URL-urilor

Slug-urile sunt generate automat cu transliterare - diacriticele romanesti sunt convertite la ASCII (ș→s, ț→t, â→a, î→i, ă→a):

  • Articole: /titlul-articolului-1 (slug din titlu + ID)
  • Note: /nota-42 (prefix nota- + ID)
  • Fotografii: /foto-43 (prefix foto- + ID)
  • Pagini custom: /despre (slug din titlu)

Panoul de admin (/admin/)

Postari

Trei tipuri de continut:

  • Articol - titlu + text lung cu suport Markdown complet
  • Nota - text scurt, ca un tweet sau un gand
  • Foto - o imagine cu descriere optionala

Fiecare postare poate fi:

  • Publicata - vizibila pe site
  • Draft - salvata dar invizibila; buton previzualizare disponibil doar pentru admin
  • Programata - se publica automat la data si ora stabilita; pana atunci e invizibila

Previzualizare draft

Cand esti logat ca admin, poti accesa URL-ul unui draft direct din browser sau prin butonul previzualizare din tabelul de postari. Vizitatorii obisnuiti primesc 404. Pe pagina de previzualizare, butonul Inapoi la admin duce inapoi la dashboard.

Pagini

Pagini statice configurabile din admin. Campuri disponibile:

Camp Descriere
Titlu Titlul paginii si textul din meniu
Slug URL-ul paginii (generat automat din titlu)
Continut Text cu suport Markdown complet
Ordine meniu Numarul de ordine in navigare (0, 1, 2...)
Afiseaza in meniu Bifat = pagina apare in navigare; debifat = pagina exista dar nu apare in meniu

Paginile ascunse din meniu sunt marcate cu [ascuns din meniu] in lista din admin. La fiecare salvare, pagina este exportata automat si ca fisier .md in data/pages/.

Comentarii

Pagina Comentarii are trei sub-pagini:

  • Platformă (?section=comments) — comentariile lasate direct pe blog; butoane Aproba / Raspunde / Sterge; raspunsul adminului apare auto-aprobat
  • Webmention (?section=comments-wm) — webmention-uri primite; Aproba / Sterge; afiseaza sursa, autorul si un extras
  • Fediverse (?section=comments-fedi) — reply-uri primite din Mastodon/Pixelfed/etc.; acelasi flux + livrare AP automata la raspuns

Badge-ul din bara de navigare arata suma tuturor comentariilor in asteptare din toate sursele.

Statistici

Vizitatori unici per IP, fara boți. Filtrati automat: Googlebot, Bingbot, crawlerele SEO (SEMrush, Ahrefs), scanerele de securitate (Shodan, Censys), monitorele de uptime, cererile fara User-Agent.

Dashboard (sectiunea Postari) - carduri rezumative:

Card Descriere
Vizitatori azi IP-uri unice in ziua curenta
Vizitatori 30 zile IP-uri unice in ultimele 30 de zile
Vizitatori 1 an IP-uri unice in ultimul an
Vizitatori total Toate IP-urile unice din istoricul complet

Pagina Statistici (?section=stats) - detalii pe ultimele 30 de zile:

Coloana Continut
Sursa Top referreri (domeniul din care a venit vizitatorul), cu favicon
Post Top postari dupa numarul de vizualizari

Datele nu expira niciodata.

Header

  • Logo - in stanga, linking spre pagina principala
  • Titlu si descriere - centrate, pe acelasi rand cu logo-ul
  • - buton articol aleatoriu (langa butoanele de tema)
  • ☾ / ☀ - comuta intre dark si light
  • - picker cu toate temele disponibile

Inserarea imaginilor

In articole, note si pagini, apasa Incarca imagine. Imaginea este comprimata automat in browser inainte de upload:

  • Desktop / Android: comprima in WebP (max 1920px, calitate 82%)
  • iOS: fallback automat la JPEG (WebP encoding nu e suportat de iOS)

Sintaxa Markdown pentru imagini: ![descriere](url-imagine)


Markdown

Parserul Markdown este scris intern, fara dependente externe. Suporta sintaxa standard plus extensiile GFM (GitHub Flavored Markdown).

Formatare de baza

Sintaxa Rezultat
# Titlu Heading H1
## Subtitlu Heading H2 (pana la H6)
Titlu\n====== Heading H1 (stil Setext)
Titlu\n------ Heading H2 (stil Setext)
**bold** sau __bold__ bold
*italic* sau _italic_ italic
***bold italic*** sau ___bold italic___ bold italic
~~taiat~~ taiat
`cod inline` cod inline
[text](url) link (extern = tab nou, intern = acelasi tab)
![alt](url) imagine inline
--- sau *** sau ___ linie orizontala

Blocuri

Sintaxa Rezultat
```limba\ncod\n``` bloc de cod cu evidentierea limbii
~~~limba\ncod\n~~~ bloc de cod (varianta cu tilde)
> citat blockquote
> > citat nested blockquote imbricat
- element sau * element lista neordonata
1. element lista ordonata
- sub-element lista nested (indentare 2+ spatii)
- [ ] sarcina task list nebifata
- [x] sarcina task list bifata

Tabele GFM

| Coloana 1 | Coloana 2 | Coloana 3 |
|:----------|:---------:|----------:|
| stanga    |  centru   |   dreapta |
| text      |  text     |     text  |

Alinierea se seteaza cu : in randul separator: :--- stanga, :---: centru, ---: dreapta.

Altele

Sintaxa Rezultat
Doua spatii la sfarsit de linie line break dur (<br>)
\ la sfarsit de linie line break dur (<br>)
\*, \_, \\ etc. caracter literal (backslash escape)
&copy;, &nbsp;, &#160; entitati HTML (trec nemodificate)
<https://example.com> autolink
![alt](url) singur pe linie figura cu legenda
[^1] in text + [^1]: text pe linie separata nota de subsol (footnote) cu back-link ↩

Link-urile externe (http://, https://) se deschid automat in tab nou. Link-urile interne (cu /) raman in acelasi tab.


Teme

Site-ul detecteaza automat preferinta de tema a sistemului de operare (light/dark).

Comutator manual

In header-ul site-ului apar trei butoane:

  • - articol aleatoriu
  • ☾ / ☀ - comuta intre dark si light
  • - deschide picker-ul cu toate temele

Picker-ul se pozitioneaza automat in functie de locul butonului pe ecran, fara a iesi din viewport pe mobile.

Teme disponibile

Tema Descriere
Auto Urmeaza preferinta OS
Luminos Fundal deschis, clasic
Cald Tonuri calde, crem
Hartie Aspect de ziar vechi
E-ink Gri monocrom, stil ecran electronic
Intunecat Dark mode standard
Adanc Dark mode profund (GitHub-like)
Terminal Verde pe negru

Preferinta se salveaza in localStorage si persista intre vizite.


Fediverse / ActivityPub

Written Whisper implementeaza protocolul ActivityPub complet, transformand blogul intr-un nod federat in Fediverse. Postarile ajung direct la cititorii care te urmaresc din Mastodon, Pixelfed, Lemmy, Loops sau orice platforma compatibila ActivityPub.

Handle Fediverse

Handle-ul tau in Fediverse este @username@domeniu.ro, configurabil din Admin → Setari → Fediverse → Username.

Oricine poate sa te urmeze tastand handle-ul complet in bara de cautare a platformei lor (Mastodon, Pixelfed etc.). Cautarea dupa username scurt (fara @domeniu) functioneaza doar pe instantele care au descoperit deja contul anterior.

Endpoint-uri ActivityPub

URL Descriere
/.well-known/webfinger Descoperire cont (RFC 7033)
/.well-known/nodeinfo Descoperire server (NodeInfo 2.0)
/.well-known/host-meta Descoperire OStatus/Diaspora (XML)
/.well-known/host-meta.json Descoperire OStatus/Diaspora (JSON)
/nodeinfo/2.0 Informatii server (software, protocoale, postari)
/actor Profilul Actor JSON-LD
/actor/followers Colectie abonati
/actor/following Colectie urmariți (goala - blogul nu urmareste pe nimeni)
/actor/featured Postari fixate (momentan goala)
/inbox Primire activitati ActivityPub (Follow, Undo, Create)
/outbox Servire activitati publice (postari ca Create Note)

Ce se federalizeaza

Tip postare Format in Fediverse
Nota Note — postare text standard
Articol Note cu titlul bold ca prim paragraf
Fotografie Note cu Document attachment (imagine)

Cand publici o postare → activitate Create livrata tuturor abonatilor. Cand editezi → Update. Cand stergi → Delete.

Profil Fediverse

Configureaza din Admin → Setari → Fediverse:

Camp Descriere
Username Handle-ul contului (@username@domeniu)
Bio Descriere de profil, independenta de descrierea blogului
Avatar Imagine rotunda de profil; fallback pe logo-ul platformei
Banner Imagine de antet (1500×500px recomandat)
Profil extern URL profil Mastodon extern, pentru rel="me" si fediverse:creator

Securitate ActivityPub

  • HTTP Signatures — toate cererile outgoing sunt semnate cu cheia RSA privata
  • Verificare semnatura — Follow-urile primite sunt verificate criptografic
  • Chei RSA 2048-bit — generate automat la prima rulare, stocate in data/ap_keys.json

Comentarii din Fediverse

Reply-urile primite din Mastodon/Pixelfed/etc. la postarile tale apar automat ca comentarii in sectiunea Comentarii → Fediverse din admin, cu avatarul si linkul actorului. Se modereaza la fel ca comentariile locale (Aproba/Sterge).

Cand raspunzi unui comentariu Fediverse din admin, raspunsul este livrat automat ca activitate Create Note (cu inReplyTo si Mention) in inbox-ul celui care a comentat.


Comentarii

Pe site

Formularul de comentarii apare sub fiecare articol, nota si fotografie.

Campuri:

  • Nume (obligatoriu)
  • Email (optional, nu se afiseaza public)
  • Mesaj (obligatoriu, minim 3 caractere)

Suporta raspunsuri la comentarii (un nivel de imbricare).

Moderare

Comentariile intra ca in asteptare si nu apar pe site pana la aprobare. Adminul le aproba sau sterge din sectiunea Comentarii a adminului. Adminul poate raspunde direct din admin, raspunsul apare auto-aprobat.

Stocare

Comentariile sunt salvate doar in baza de date (data/blog.db), in tabelul comments. Nu exista fisiere .md pentru comentarii - spre deosebire de postari si pagini, nu pot fi reconstituite din disc. La backup, data/blog.db este singurul fisier care le contine.

Protectie anti-spam

  • Token CSRF pe fiecare formular
  • Camp honeypot invizibil (botii il completeaza)
  • Maxim 5 comentarii pe ora per adresa IP

Import din alte platforme

Orice fisier .md cu YAML front matter poate fi importat in Written Whisper.

# Orice director cu fisiere .md:
php tools/import_markdown.php /cale/catre/posts

# Cu copiere imagini (Pure Blog, Jekyll etc.):
php tools/import_markdown.php /blog/content/posts \
    --images /blog/content/images \
    --images-prefix /content/images

# Dry-run (preview fara a scrie):
php tools/import_markdown.php /cale/posts --dry-run

# Re-importa postari deja existente:
php tools/import_markdown.php /cale/posts --force

# Migrare dedicata Pure Blog:
php tools/migrate_pureblog.php /cale/pureblog/content --dry-run
php tools/migrate_pureblog.php /cale/pureblog/content

# Import comentarii din PureComments:
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite --dry-run
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite

Front matter recunoscut automat:

Camp Variante acceptate
Titlu title
Slug slug, permalink, link, url
Data date, created_at, published_at, ISO 8601
Status status: draft/published, draft: true, published: false
Tip type: article/note/photo (default: article)

Campurile tags, categories, description, excerpt, layout sunt ignorate. Importul este idempotent - postarile deja existente sunt sarite (sau actualizate cu --force).

Export automat Markdown

Fiecare postare creata sau editata din admin este salvata automat ca fisier .md in data/posts/ cu YAML front matter complet.

Fiecare pagina creata sau editata din admin este salvata automat ca fisier .md in data/pages/. La stergere, fisierul este sters automat. La schimbarea slug-ului, fisierul vechi este sters si creat unul nou.

Fisierele sunt portabile si pot fi importate in alte platforme care suporta Markdown.


Unelte CLI

Cinci scripturi PHP care ruleaza din linia de comanda. Nu sunt accesibile din browser. Documentatie completa in tools/README.md.

generate_hash.php

Genereaza un hash bcrypt dintr-o parola in text clar.

php tools/generate_hash.php parola_mea_secreta
# Output: $2y$12$abc123...

import_markdown.php

Importa orice fisiere .md cu YAML front matter. Compatibil cu Pure Blog, Jekyll, Hugo, Ghost sau fisiere scrise manual.

php tools/import_markdown.php /cale/posts [--images <dir>] [--dry-run] [--force]

migrate_pureblog.php

Wrapper specializat pentru migrarea din Pure Blog. Configureaza automat directoarele si prefixurile specifice Pure Blog.

php tools/migrate_pureblog.php /cale/pureblog/content [--dry-run]

export_markdown.php

Genereaza fisiere .md in data/posts/ pentru postarile din baza de date care nu au inca fisier fizic. Util dupa o migrare sau pentru reconstituirea data/posts/ dintr-un blog.db existent.

php tools/export_markdown.php           # doar postarile fara fisier
php tools/export_markdown.php --force   # rescrie toate fisierele
php tools/export_markdown.php --dry-run # simulare fara a scrie nimic

import_pureblog_comments.php

Importa comentariile din baza de date PureComments in Written Whisper. Rezolva automat diferentele de slug si remapaeza relatiile parent_id.

php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite --dry-run
php tools/import_pureblog_comments.php /cale/PureComments/db/comments.sqlite

Functionalitati

Continut si publicare:

  • Configurare din browser - setup.php pentru configurare initiala, fara FTP
  • Credentiale din admin - schimba utilizator si parola din panoul de admin
  • Draft cu previzualizare - drafts vizibile doar de admin, cu buton de previzualizare
  • Postari programate - status "scheduled" cu data/ora de publicare; auto-publish fara cron
  • Pagini custom - pagini statice cu optiune de afisare/ascundere din meniu
  • Upload imagini - compresie WebP pe desktop, fallback JPEG automat pe iOS
  • Export Markdown postari - fiecare postare salvata automat ca .md in data/posts/
  • Export Markdown pagini - fiecare pagina salvata automat ca .md in data/pages/
  • Sync automat - fisierele .md noi detectate in data/posts/ sunt importate automat la deschiderea adminului

Navigare si experienta vizitator:

  • Arhiva - /arhiva.php cu toate postarile grupate pe luna si an, luni in romana
  • Articol aleatoriu - buton ⚄ in header redirectioneaza la o postare aleatorie
  • Bara de progres citire - linie subtire in partea de sus, urmareste pozitia in articol
  • Timp de citire estimat - calculat automat din numarul de cuvinte, afisat in meta post
  • Lightbox imagini - click pe imaginile din articole le deschide marite, fara pagina noua
  • Buton copy link - copiaza URL-ul postului in clipboard
  • Pagina 404 custom - mesaj amuzant + 5 articole aleatorii recomandate
  • Cautare - cauta in titlu, continut si descriere
  • Buton sus - scroll to top, apare dupa 400px scroll
  • Paginare - pe feed si in admin (20 postari/pagina in admin)

Markdown:

  • Markdown complet - headinguri ATX si Setext, bold, italic, strikethrough, tabele GFM, liste nested, task lists, blockquote nested, line breaks dure, backslash escapes, entitati HTML
  • Footnotes - [^1] inline cu definitii [^1]: text, sectiune de note de subsol cu back-link ↩
  • Syntax highlighting - colorarea codului server-side fara JS; suporta bash, php, python, yaml, json, html, css, javascript
  • Linkuri externe in tab nou - linkurile interne raman in acelasi tab

Comentarii si interactiuni:

  • Comentarii - sistem complet pe articole, note si fotografii, cu moderare, raspunsuri, anti-spam
  • Comentarii Fediverse - reply-urile din Mastodon/Pixelfed apar ca comentarii moderate; raspuns din admin livrat AP
  • Webmention - primire (endpoint /webmention.php cu validare sursa), trimitere automata la publicare, moderare in admin, afisare pe post

Fediverse / ActivityPub:

  • Actor complet - Person cu publicKey, discoverable, indexable, featured, featuredTags, endpoints.sharedInbox
  • WebFinger - descoperire cont (/.well-known/webfinger)
  • NodeInfo 2.0 - descoperire server (/.well-known/nodeinfo, /nodeinfo/2.0)
  • host-meta - compatibilitate OStatus/Diaspora/Friendica (XML + JSON)
  • HTTP Signatures - semnare cereri outgoing, verificare cereri incoming
  • Livrare automata - Create la publicare, Update la editare, Delete la stergere
  • Update profil - schimbarea setarilor Fediverse trimite Update actorului catre toti abonații
  • Continut negociat - /slug returneaza JSON-LD pentru clienti AP, HTML pentru browsere
  • Redirect domeniu canonic - hostname-urile alternative sunt redirectionate (301) spre domeniul principal

Feed si indexare:

  • RSS / Atom / JSON Feed - trei formate; includ articole, note si fotografii cu continut HTML complet
  • Sitemap XML - pentru indexare Google
  • SEO - Open Graph cu imagine (prima imagine din post sau logo), Twitter Card, meta description per pagina
  • rel="me" - pentru verificarea profilului Fediverse (Mastodon etc.)
  • fediverse:creator - meta tag pentru atribuire continut in Fediverse

Aspect:

  • Teme - 8 teme (4 light + 3 dark + auto), comutare manuala, persistenta in localStorage
  • Scrollbar - urmeaza culorile temei active (border/muted)
  • Font monospaced - Cascadia Code / Fira Mono / system monospace
  • Luni in romana - datele apar in romana (ian., feb., mai, iun. etc.)
  • Responsive - functioneaza pe orice ecran (mobil, tableta, desktop)
  • Dark mode auto - urmeaza preferinta sistemului de operare

Statistici:

  • Vizitatori unici - azi / 30 zile / 1 an / total, fara boți, fara expirare
  • Graf vizite zilnice - bar chart SVG cu vizitele din ultimele 30 de zile
  • Top referreri - domeniile din care au venit vizitatorii, cu favicon
  • Top postari - articolele cele mai citite dupa vizualizari
  • URL-uri curate - /titlul-articolului, /nota-42, /foto-43, /despre
  • Slug ASCII - diacriticele romanesti sunt transliterate automat (ș→s, ț→t etc.)
  • Contor online - footer arata de cand este blog-ul online (configurat din Setari)
  • Setari din admin - titlu, descriere, logo, favicon, URL site, data lansarii, profil Fediverse, postari pe pagina
  • Imagini separate - imaginile din articole in data/uploads/, logo si favicon in public/uploads/

Import si migrare:

  • Import Markdown - importa orice .md cu YAML front matter
  • Migrare Pure Blog - unealta dedicata pentru migrarea din Pure Blog
  • Import comentarii Pure Blog - importa comentariile din PureComments cu rezolvare automata de slug-uri si remapare parent_id

Tehnic:

  • Securitate - CSRF pe toate formularele (inclusiv login), actiuni destructive doar prin POST, SSRF blocat, CSP diferentiat admin/public, rate limiting, sesiuni sigure, validare MIME
  • Parole bcrypt - hash automat la salvare, fara plaintext niciodata
  • SQLite - baza de date intr-un singur fisier, zero configurare
  • Portabil - shared hosting, VPS, bare metal, Docker
  • Fara dependente - PHP pur, nicio biblioteca externa, niciun build step
  • Navigare admin bazata pe URL - fiecare sectiune si sub-sectiune are URL propriu, back/forward functioneaza

Backup si restaurare

Tot continutul este in:

data/blog.db       <- baza de date (postari, pagini, comentarii, statistici, vizitatori)
data/settings.json <- setarile din admin (titlu, logo, favicon, credentiale, data lansarii)
data/posts/        <- fisiere .md exportate automat (backup portabil postari)
data/pages/        <- fisiere .md exportate automat (backup portabil pagini)
data/uploads/      <- imaginile din articole, note si fotografii
public/uploads/    <- logo si favicon

Comentariile sunt salvate doar in blog.db - nu exista fisiere .md pentru ele. Daca blog.db se pierde, comentariile nu pot fi reconstituite din disc.

Backup

Copiaza data/ si public/uploads/ pe calculatorul tau. In cPanel poti descarca direct prin File Manager sau Archive Manager.

Restaurare

  1. Incarca blog.db si settings.json in directorul data/
  2. Incarca imaginile in data/uploads/
  3. Incarca logo si favicon in public/uploads/
  4. Gata - toate postarile, paginile, imaginile si setarile sunt restaurate

Securitate

  • CSRF - token unic per sesiune pe toate formularele, inclusiv login; actiunile destructive (stergere, aprobare) accepta doar POST
  • Rate limiting login - maxim 5 incercari, blocare cu fereastra fixa per IP (nu rolling)
  • Rate limiting comentarii - maxim 5 comentarii pe ora per IP
  • Rate limiting webmention - maxim 10 webmentions pe ora per IP
  • Honeypot - camp ascuns pentru detectarea botilor la comentarii
  • Session hijacking - verificare IP si User-Agent la fiecare cerere admin
  • Upload - validare MIME type real (nu doar extensie), max 10 MB; SVG exclus
  • Parole - hash bcrypt cu cost 12, generat automat la setup si la schimbare
  • setup.php - accesibil doar inainte de configurare; blocat automat dupa
  • Filtrare boți - Googlebot, Bingbot, crawlerele SEO, scanerele de securitate si cererile fara User-Agent sunt ignorate din statistici
  • SSRF blocat - endpoint webmention valideaza schema (http/https), respinge IP-uri private si rezervate
  • CSP - Content Security Policy diferentiat: strict (script-src 'self') pentru paginile publice, permisiv pentru admin (inline scripts necesare)
  • Headere HTTP - X-Content-Type-Options, Referrer-Policy, frame-ancestors 'none'
  • Directoare protejate - src/ si data/ in afara webroot-ului
  • PRG pattern - redirect dupa POST la comentarii (previne duplicatele la refresh)
  • Timezone UTC - toate datele stocate si comparate in UTC pentru consistenta

Cerinte tehnice

Componenta Versiune minima
PHP 8.0
PDO SQLite inclus in PHP
fileinfo inclus in PHP
Apache 2.4+ cu mod_rewrite
nginx 1.18+ cu PHP-FPM
Docker 20.10+

Realizat cu

Proiect construit cu Claude Code de la Anthropic.