| System | dataBAS — kreator aplikacji bazodanowych |
| Wersja systemu | 2.9.1 (DBS_VERSION w app/bootstrap.php) |
| Wersja schematu | 10 (Schema::VERSION w app/Schema.php) |
| Wersja dokumentu | 1.7 · 18.10.2026 |
| Autor | Arnold Basiński |
| Odbiorcy | programiści i administratorzy rozwijający lub wdrażający dataBAS |
Jak aktualizować ten dokument. Przy każdym wydaniu: zmień wersję systemu w tabeli powyżej, uzupełnij sekcje, których dotyczy zmiana (lista kontrolna — rozdz. 26), dopisz wiersz w Historii dokumentu. Opis funkcji dla użytkownika końcowego jest w
README.md— tutaj opisujemy jak to działa w środku.
Spis treści
- Założenia i ograniczenia
- Wymagania serwera
- Struktura katalogów
- Cykl życia żądania
- Konfiguracja —
config.php - Warstwa bazy danych —
Db - Tabele systemowe i migracje
- Metadane: tabele, pola, opcje, ustawienia
- Uwierzytelnianie, sesje, 2FA
- Uprawnienia i dostęp do wierszy
- Zapis i usuwanie rekordów, pliki
- Język wyrażeń —
Expr - Zdarzenia, haki, dziennik, blokady, powiadomienia
- Raporty, pulpity, import, wydruki
- 14.1 Eksport i import (przenoszenie) —
Transfer - 14.2 Formularze publiczne —
PublicForm - 14.3 Przypomnienia e-mail —
Reminders - 14.4 Harmonogram —
Scheduleritools/cron.php - 14.5 Kopie zapasowe —
Backup - 14.6 Szablony wydruku: podgląd i wysyłka e-mailem —
Tpl - 14.7 PDF na serwerze —
Pdf,PdfFont,HtmlPdf
- Wygląd i menu
- Tłumaczenia —
I18n - API JSON —
api.php - Licencjonowanie —
License - Tryb demonstracyjny —
Demo - Szablony aplikacji —
Templates
- Bezpieczeństwo — przegląd mechanizmów
- Integracje z WordPressem
- Wdrożenie, aktualizacja, kopie zapasowe
- Rozszerzanie systemu — przepisy
- Testowanie
- Wydanie nowej wersji — lista kontrolna
- Znane ograniczenia i dług techniczny
- Historia dokumentu
1. Założenia i ograniczenia
- Czysty PHP 8.1+, bez Composera, npm i bibliotek zewnętrznych — także w JS i CSS. Generator QR, wykresy SVG, parser formuł, czytnik XLSX, klient SMTP i TOTP są własne.
- Dwie bazy: MySQL/MariaDB albo SQLite. Różnice dialektów zamyka klasa
Db. - Konfiguracja aplikacji w bazie. Pliki to tylko kod +
config.php(połączenie) +uploads/. Kopia bazy = kopia całej aplikacji (z plikami zuploads/). - Bezpieczeństwo po stronie serwera. Każde uprawnienie, walidacja, warunek i filtr wierszy są sprawdzane w PHP. Interfejs (ukryte przyciski, JS) to tylko wygoda.
- Nazwy tabel i kolumn tylko z metadanych (biała lista) i zawsze cytowane (
Db::q). Wartości tylko przez parametry PDO. - Interfejs po polsku (angielski dla części użytkownika —
lang/en.php). Komentarze w kodzie po polsku. - Styl kodu: klasy
finalze statycznymi metodami (moduły), kontrolery jako funkcjemoduł_akcja(), widoki jako szablony PHP wviews/,declare(strict_types=1)w bootstrapie.
2. Wymagania serwera
| Element | Wymaganie | Uwagi |
|---|---|---|
| PHP | 8.1+ | instalator sprawdza wersję |
pdo + pdo_mysql lub pdo_sqlite |
wymagane | |
mbstring, fileinfo, json |
wymagane | fileinfo — weryfikacja typów przesyłanych plików |
sodium |
zalecane | weryfikacja tokenu licencji Ed25519 (bez niego zapasowo RSA przez openssl) |
openssl |
zalecane | zapasowa weryfikacja licencji (RSA), SMTP z TLS |
curl |
opcjonalne | połączenie z serwerem licencji (bez niego — strumienie PHP) |
gd |
opcjonalne | obrazy PNG/GIF/WEBP w plikach PDF (JPEG bez GD); bez GD obrazy inne niż JPEG są w PDF pomijane |
zlib |
zalecane | kompresja strumieni PDF (gzcompress) — standard w PHP |
zip (ZipArchive) + xmlreader/simplexml |
opcjonalne | import XLSX (bez nich tylko CSV); paczki ZIP eksportu i importu (bez zip — pojedyncze pliki JSON/CSV); moduły .dbsmod (bez zip — niedostępne) |
| Zapis | config.php (jednorazowo), data/, uploads/ |
|
| Serwer WWW | Apache (pliki .htaccess dołączone) albo nginx (reguły w README) |
3. Struktura katalogów
index.php front controller WWW: index.php?r=<moduł>/<akcja>
form.php formularz publiczny: form.php?f=<kod> (ustawia r=form | form/live i dołącza index.php)
api.php API JSON (wtyczka WordPress i inne zaufane aplikacje)
install.php instalator (blokuje się, gdy istnieje config.php)
config.sample.php wzór konfiguracji
.htaccess blokada config.php, README.md i katalogów wewnętrznych
app/
bootstrap.php start: stałe, include klas, config, sesja, nagłówki, połączenie, migracje, demo, retencja, haki
helpers.php funkcje globalne: e(), url(), redirect(), get()/post(), flash(), csrf_*, render(), abort(), client_ip(), field_types(), config()
Db.php PDO, dialekty MySQL/SQLite, cytowanie, introspekcja, DDL
Meta.php metadane tabel i pól, ustawienia, opcje tabel (topt), filtr przedrostków, synchronizacja, zmiana nazwy tabeli
Schema.php migracje tabel systemowych (numerowane)
Auth.php logowanie, sesja użytkownika, limity prób, grupy, uprawnienia (can/ownOnly)
Form.php pola formularza: HTML, normalizacja/walidacja, wyświetlanie, powiązania (lookup)
Rules.php Formula (pola wyliczane), Cond (pola warunkowe), RowFilter (filtry wierszy), Actions (przyciski akcji)
Expr.php język wyrażeń: tokenizer, parser, interpreter (bez eval)
Events.php Events (haki), DBS (API dla haków), Audit (dziennik), Locks (blokady), Notify (powiadomienia)
Reports.php Report (zapytania agregujące), Chart (SVG), Dash (pulpity)
Import.php odczyt CSV/XLSX, dopasowanie kolumn, konwersja wartości
Tpl.php szablony wydruku: czyszczenie HTML, znaczniki, pętle
Menu.php Brand (nazwa, logo, kolor, stopka) i Menu (automatyczne/własne, 6 układów)
I18n.php tłumaczenia — t('tekst polski')
Mailer.php wysyłka: mail() albo własny klient SMTP (STARTTLS/SSL)
Totp.php TOTP (RFC 6238) i kody zapasowe
Qr.php generator QR (SVG)
License.php wersja próbna, aktywacja, weryfikacja i odświeżanie tokenu
license.pub klucz publiczny i adres serwera licencji (JSON, ze sklepu)
Demo.php tryb demonstracyjny: ograniczenia, wzorzec, przywracanie
Templates.php szablony aplikacji (definicje + instalator; available() = wbudowane + moduły)
Modules.php moduły: odczyt i weryfikacja paczek .dbsmod, przydział do licencji, walidacja definicji, katalog i pobieranie ze sklepu
Transfer.php eksport i import: struktura JSON, dane CSV, paczki ZIP, podgląd i import
Pdf.php PdfFont (TrueType: cmap, szerokości, podzbiór) i Pdf (strony, tekst Unicode, grafika, obrazy, zapis pliku)
HtmlPdf.php układ HTML + CSS → PDF: kaskada CSS, bloki, wiersze tekstu, tabele, flex, łamanie stron
fonts/ LiberationSans-{Regular,Bold,Italic,BoldItalic}.ttf + LICENSE.txt (SIL OFL 1.1)
PublicForm.php formularze publiczne: konfiguracja, pola, token, limity, zapis zgłoszeń, e-maile, osadzanie
Reminders.php przypomnienia e-mail: reguły, okna dat, odbiorcy, treść, dziennik wysyłek
Scheduler.php harmonogram: cron / ruch na stronie, blokada, dziennik uruchomień
Backup.php kopie zapasowe całej aplikacji: tworzenie, lista, rotacja, przywracanie, wgrywanie
controllers/ auth.php, data.php, admin.php, report.php, import.php, print.php, brand.php, form.php (publiczny)
views/ szablony HTML (PHP); layout.php = ramka strony
assets/ app.css, app.js, favicon.svg
lang/en.php słownik angielski (klucz = tekst polski)
custom/ hooks.php (własny kod), hooks.example.php — zablokowany dla przeglądarki
tools/demo_reset.php CLI: wzorzec wersji demo (zapisz / przywroc / stan)
tools/cron.php CLI: harmonogram (bez argumentu) / kopia / przypomnienia / stan
tools/module.php CLI: budowanie paczek modułów (z-tabel / z-szablonu / pakuj / sprawdz / json)
tools/modules/ przykładowe definicje modułów (rezerwacje-sal.json, zgloszenia-usterek.json)
data/ baza SQLite, wzorzec demo, pliki tymczasowe importu CSV/XLSX, data/transfer/ (paczki eksportu i importu, czyszczone po dobie), data/backups/ (kopie zapasowe, własne .htaccess + index.html), data/modules/ (zainstalowane moduły <id>.dbsmod, własne .htaccess), data/cron.lock — zablokowany
uploads/ przesłane pliki: <tabela>/<RRRR>/<MM>/<losowe>_<nazwa>.<ext> — zablokowany, wydawane przez kontroler
4. Cykl życia żądania
4.1 index.php (interfejs WWW)
app/bootstrap.php- stałe
DBS_VERSION,ROOT;display_errors=0; strefa czasu;mb_internal_encoding('UTF-8'); requirewszystkich klas (bez autoloadera — kolejność ma znaczenie, patrz plik);- brak
config.php→ przekierowanie doinstall.php; - wczytanie konfiguracji (
$DBS_CONFIG),debug→display_errors=1,timezone; - (tylko WWW) sesja: nazwa
DATABAS_<hash ścieżki>,HttpOnly,SameSite=Lax,Secureprzy HTTPS (takżeX-Forwarded-Proto: https); nagłówkiX-Content-Type-Options,X-Frame-Options: SAMEORIGIN,Referrer-Policy: same-origin; Db::connect()— przy błędzie 500 (JSON dla API);Schema::migrate()— migracje (raz po aktualizacji);Meta::applyTableFilter()— filtr przedrostków tabel;Demo::maybeReset()— automatyczne przywracanie danych demo;Audit::purge()— retencja dziennika (raz na dobę);Events::loadHooks()—custom/hooks.php.- Routing:
$routeswindex.phpmapujer→[moduł, akcja]; nieznana trasa → 404. Kontroler to funkcjamoduł_akcja()zapp/controllers/<moduł>.php. - Strażnicy (w tej kolejności):
- brak zalogowania →
login(wyjątki:login,tfa,brand/logo, formularze publiczneform/form/live; przy dostępie publicznym — trasy z$publicRoutes); - wymagane 2FA, a nieskonfigurowane →
tfa_setup; - licencja nieważna → administrator do
admin/license, reszta: stronalockedz kodem 402; - konto demonstracyjne →
Demo::adminBlock(), blokada plików, blokada Kreatora dla zwykłych kont demo, blokadatfa_setup; - moduł
admintylko dlaAuth::isAdmin()(403); - każdy POST →
csrf_check()— poza formularzami publicznymi ($publicForm), które chroni podpisany token formularza (rozdz. 14.2). - Wykonanie kontrolera.
PDOException→ log + 500 (administrator widzi treść błędu).
Widok: render($view, $vars, $title) → views/<view>.php w ramce views/layout.php. Wszystkie dane w widokach przez e() (htmlspecialchars).
4.2 api.php
Definiuje DBS_API (bez sesji PHP i ciasteczek), dołącza controllers/data.php (wspólny rdzeń zapisu/usuwania). Kolejność: metoda → licencja (402) → klucz API → IP odwiedzającego → token użytkownika → akcja. Szczegóły w rozdz. 17.
4.3 CLI
tools/demo_reset.php, tools/cron.php, tools/module.php — uruchamiane tylko z wiersza poleceń (katalog tools/ zablokowany dla przeglądarki, a same skrypty zwracają 404 poza CLI). cron.php i module.php definiują DBS_API (bootstrap bez sesji i nagłówków); module.php bez config.php kończy się komunikatem (exit 1) zamiast przekierowania do instalatora.
Koniec żądania WWW: ostatnia linia bootstrap.php to Scheduler::maybeWeb() — może zarejestrować funkcję zamykającą, która po wysłaniu odpowiedzi uruchomi harmonogram (rozdz. 14.4).
5. Konfiguracja — config.php
Tworzy go instalator (var_export, uprawnienia 0640). Odczyt w kodzie: config('klucz', domyślna).
| Klucz | Typ | Opis |
|---|---|---|
driver |
mysql \| sqlite |
rodzaj bazy |
host, port, dbname, user, pass |
MySQL/MariaDB | |
sqlite_path |
ścieżka | plik SQLite (zalecane poza katalogiem publicznym) |
prefix |
np. dbs_ |
przedrostek tabel systemowych; [a-z][a-z0-9_]{0,15}_ |
app_name |
tekst | nazwa domyślna (nadpisywana ustawieniem app_name) |
upload_max_mb |
liczba | limit rozmiaru pliku |
timezone |
np. Europe/Warsaw |
|
debug |
bool | pokazuje błędy PHP — tylko na czas testów |
license_server |
URL | zapasowy adres serwera licencji, gdy brak go w app/license.pub |
demo |
tablica | tryb demonstracyjny — rozdz. 19 |
modules_dev |
bool | (2.9) tryb autora modułów: instalacja niepodpisanych paczek .dbsmod (Kreator pokazuje ostrzeżenie) — tylko serwer testowy, 20.1 |
Ustawienia zmieniane w interfejsie nie trafiają do config.php — są w tabeli <prefix>settings (8.5).
6. Warstwa bazy danych — Db
Statyczna klasa nad PDO (Db::$pdo, Db::$prefix, Db::$driver). Tryb błędów: wyjątki.
| Metoda | Opis |
|---|---|
connect(array $cfg) |
połączenie; MySQL w utf8mb4; SQLite z kluczami obcymi |
isMysql() |
dialekt |
q(string $ident) |
cytowanie identyfikatora (` ` / "`) — zawsze dla nazw z metadanych |
t(string $name) |
zacytowana nazwa tabeli systemowej z przedrostkiem, np. Db::t('users') |
run($sql, $params) / all / one / val |
zapytania przygotowane |
insert($table, $data) / update($table, $data, $where, $params) |
$table już zacytowana (Db::q/Db::t) |
like() / contains() |
wyszukiwanie bez rozróżniania wielkości liter i polskich znaków |
internalTables() / allTables() / tables() / matchesFilter() |
tabele systemowe, wszystkie, widoczne po filtrze przedrostków |
tableExists() / columns($t, $fresh) / primaryKey() / foreignKeys() |
introspekcja (z pamięcią podręczną: forgetColumns(), forgetTables()) |
idColumn() / tableSuffix() / sqlType($type) |
DDL zależny od dialektu (InnoDB + utf8mb4 dla MySQL) |
createTable() / addColumn() / dropColumn() / dropTable() |
DDL; dropColumn w SQLite przebudowuje tabelę |
Zasada: w zapytaniach do tabel aplikacji nazwa tabeli/kolumny pochodzi wyłącznie z Meta::table() / Meta::fields() / Db::columns() i przechodzi przez Db::q(). Wartości wyłącznie jako parametry.
7. Tabele systemowe i migracje
Przedrostek z config.php → prefix (domyślnie dbs_). Tabele systemowe nigdy nie są pokazywane jako dane.
7.1 Tabele tworzone przez instalator (Meta::createInternalTables)
| Tabela | Kolumny | Rola |
|---|---|---|
groups |
id, name UNIQUE |
grupy użytkowników |
users |
id, username UNIQUE, email, password_hash, group_id, is_admin, active, last_login, created_at (+ migracje: attrs, totp_secret, totp_enabled, recovery_codes, lang, totp_last) |
konta |
tables |
name PK, label, description, enabled, sort, pk_field, rows_per_page, order_field, order_dir, owner_field, icon |
metadane tabel aplikacji |
fields |
id, table_name, name, label, type, options (JSON), required, in_list, in_form, in_details, searchable, readonly, default_value, hint, sort; UNIQUE(table_name, name) |
metadane pól |
perms |
group_id, table_name (PK razem), can_browse, can_details, can_insert, can_edit, can_delete, can_export, own_only |
macierz uprawnień |
login_attempts |
id, ip, username, at |
nieudane logowania (limit) |
settings |
name PK, value |
ustawienia globalne |
7.2 Tabele z migracji (Schema::migrate)
| Migracja | Zmiana |
|---|---|
| 1 | audit (id, at, user_id, username, table_name, record_id, action, changes JSON, ip; indeks (table_name, record_id)), locks (PK table_name+record_id, user_id, username, locked_at, expires_at), table_opts (PK table_name+opt_key, value JSON) |
| 2 | user_groups (PK user_id+group_id), users.attrs (JSON atrybutów) |
| 3 | reports (id, name, table_name, config JSON, created_by, created_at), dashboards (id, name, config JSON, created_at), templates (id, table_name, name, mode, body, settings JSON, created_at) |
| 4 | users.totp_secret, users.totp_enabled, users.recovery_codes |
| 5 | users.lang |
| 6 | mail_log (id, at, recipients, subject, status, error) |
| 7 | users.totp_last (ostatni użyty krok TOTP — blokada powtórzeń) |
| 8 | ustawienie install_date (początek wersji próbnej; także dla aktualizowanych instalacji) |
| 9 | forms (id, table_name, slug UNIQUE, title, enabled, config JSON, created_at, updated_at), form_log (id, form_id, at, ip = skrót HMAC, record_id, consent; indeks (form_id, at)) |
| 10 | reminders (id, table_name, name, enabled, config JSON, last_run (uniksowy), created_at, updated_at), reminder_log (id, reminder_id, record_id (NULL = lista zbiorcza), rkey, at (uniksowy), recipients, status ok/error, error; indeks (reminder_id, record_id)) |
Poza migracjami: api_tokens (id, user_id, token_hash UNIQUE = SHA-256 tokenu, created_at, expires_at, last_used, client) — tworzona leniwie przez Meta::ensureApiTokens() przy pierwszym wywołaniu API.
7.3 Jak działają migracje
- Numer bieżący w ustawieniu
schema_version.migrate()wykonuje blokiif ($cur < N)i zapisujeSchema::VERSION. - Pomocnicze
$create(nazwa, kolumny)(pomija istniejące) i$addCol(tabela, kolumna, definicja)(pomija istniejące) — migracje są idempotentne. - Typy przez
$vc(n)(VARCHAR/TEXT) i$int— wspólne dla MySQL i SQLite. - Błąd migracji nie zatrzymuje aplikacji (log
dataBAS migrate:), więc nowa migracja musi być bezpieczna przy ponownym uruchomieniu.
Dodanie migracji: zwiększ Schema::VERSION, dopisz if ($cur < NOWY) { … } używając $create/$addCol, jeśli tabela ma być objęta wzorcem demo — dopisz ją do Db::internalTables(), uzupełnij tabelę 7.2.
8. Metadane: tabele, pola, opcje, ustawienia
8.1 Model
- Każda tabela aplikacji ma wiersz w
tables, każda kolumna — wfields.Meta::sync()/Meta::syncTable()dopasowuje metadane do rzeczywistej struktury bazy: wykrywa nowe tabele i kolumny, klucze główne i obce, zgaduje typ (Meta::guessType: FK →lookup,tinyint(1)→checkbox,date→date…) i pole etykiety (guessLabelField). - Odczyt:
Meta::tables(),Meta::table($t),Meta::fields($t)(z rozpakowanymopt= JSON zoptions),Meta::label($t). Pamięć podręczna w obrębie żądania —Meta::flush()po zmianach. - Filtr przedrostków (
table_prefixes) ogranicza widoczne tabele; konfiguracja tabel ukrytych filtrem jest zachowywana (Meta::hiddenByFilter()). - Zmiana nazwy tabeli:
Meta::checkNewTableName()+Meta::renameTable()— przenosi pola, uprawnienia, opcje, powiązania i katalog plików. - Relacje rodzic → dzieci:
Meta::childRelations($t); wyłączone panele w ustawieniurelations_off.
8.2 Typy pól (field_types() w helpers.php)
| Typ | Kolumna (MySQL) | Uwagi |
|---|---|---|
text |
VARCHAR(255) | maxlength, regex |
textarea |
TEXT | |
number |
INT | min, max |
decimal |
DECIMAL | przecinek albo kropka na wejściu |
date, datetime |
DATE, DATETIME | {today}, {now} jako domyślne |
checkbox |
TINYINT(1) | nigdy „wymagane” |
select |
VARCHAR | choices: linie `wartość |
lookup |
INT / jak PK celu | lookup_table, lookup_value, lookup_label (kilka pól po przecinku) |
email, url |
VARCHAR | walidacja formatu |
file, image |
VARCHAR (ścieżka względna w uploads/) |
biała lista rozszerzeń |
8.3 Opcje pola — JSON w fields.options
Biała lista zapisu: admin_field() w controllers/admin.php.
| Klucz | Znaczenie |
|---|---|
choices |
opcje listy (`wartość |
lookup_table, lookup_value, lookup_label |
powiązanie |
regex, regex_msg |
wyrażenie regularne i komunikat |
min, max, maxlength, placeholder |
walidacja i podpowiedź |
extensions |
własna lista rozszerzeń (pdf, docx) — rozszerzenia wykonywalne zawsze usuwane |
auto |
created_at \| updated_at \| user_id — kolumny systemowe |
unique |
block (blokada) \| warn (ostrzeżenie, „Zapisz mimo to”) |
formula |
wyrażenie pola wyliczanego (rozdz. 12) |
show_if, require_if |
warunki widoczności i wymagalności |
Ograniczenia formuł: nie dla file/image/lookup, nie dla klucza głównego, bez odwołania do własnego pola.
8.4 Opcje tabeli — table_opts (Meta::topt / Meta::setTopt, wartość JSON)
opt_key |
Format | Kto zapisuje |
|---|---|---|
notify |
[{events:[insert,update,delete], emails, groups:[id], field, owner, skip_self, fields:[pola wyzwalające], subject, body}] |
Automatyzacja → Powiadomienia |
unique_sets |
`[{fields:[…], mode:'block' | 'warn'}]` |
audit |
false = dziennik wyłączony dla tabeli; brak = włączony |
Automatyzacja |
actions |
`[{id, label, type:'set' | 'copy', icon, style:'' |
row_filters |
[{groups:[id], conds:[{field, op, value}]}]; op: eq ne lt le gt ge in contains empty notempty; value może zawierać {user.id}, {user.<atrybut>} |
Automatyzacja → Filtry wierszy |
menu_group |
nazwa grupy w menu automatycznym (szablony aplikacji) | Templates::install |
8.5 Ustawienia globalne — <prefix>settings
Odczyt Meta::setting('nazwa', domyślna), zapis Meta::setSetting('nazwa', wartość|null) (null = usunięcie).
| Obszar | Klucze |
|---|---|
| System | schema_version, install_date, base_url |
| Tabele | table_prefixes, strip_prefix, new_table_prefix, auto_prefix, relations_off, perm_template |
| Dostęp | public_access, tfa_required (none \| admins \| all) |
| Dziennik i blokady | audit_enabled, audit_keep_days, audit_purged_on, record_locking, lock_minutes |
| Poczta | mail_transport, mail_from, mail_from_name, smtp_host, smtp_port, smtp_secure, smtp_user, smtp_pass |
| Wygląd | app_name, logo, logo_mode, logo_height, accent, footer_text, footer_align, footer_version, menu (JSON), menu_layout, home_dashboard, default_lang |
| API | api_enabled, api_key |
| Licencja | license_key, license_token, license_error, license_try |
| Harmonogram | cron_mode (auto \| cron), cron_last, cron_source (cron \| web), cron_log (JSON, 30 ostatnich uruchomień), seen_base_url (adres aplikacji zapamiętany na stronach Przypomnień — do linków z crona) |
| Kopie zapasowe | backup_enabled, backup_freq (daily \| weekly), backup_hour, backup_keep, backup_files, backup_last |
| Formularze | form_secret (klucz HMAC tokenów formularzy i skrótów IP — tworzony automatycznie) |
| Moduły | module_catalog (JSON: ok, items, buy_url, at — katalog ze sklepu, ważny 12 h; czyszczony po pobraniu modułu) |
9. Uwierzytelnianie, sesje, 2FA
- Hasła:
password_hash(PASSWORD_DEFAULT), automatyczny rehash przy logowaniu. Porównanie w stałym czasie także dla nieistniejącego loginu. - Limit prób:
Auth::MAX_ATTEMPTS = 8wAuth::WINDOW = 900s na adres IP (login_attempts). Udane logowanie czyści licznik IP. - Sesja:
Auth::loginUser()regeneruje identyfikator sesji i zapisuje w niejuidoraz odcisk hasła (upw— 12 ostatnich znaków skrótu).Auth::user()przy każdym żądaniu sprawdzaactive = 1i zgodność odcisku — zmiana hasła lub zablokowanie konta wylogowuje wszystkie sesje. - 2FA (
Totp): RFC 6238, SHA-1, 6 cyfr, okres 30 s, okno ±1 krok, blokada powtórzeń przezusers.totp_last, 10 kodów zapasowych (tylko skróty SHA-256 wrecovery_codes). Klucz QR generujeQr::svg()lokalnie. - Przepływ WWW:
Auth::attempt()zwraca'tfa'→$_SESSION['tfa_uid'](+ czas, odcisk hasła) → trasatfa(5 prób) →loginUser. - Wymuszanie:
Totp::required($u)wgtfa_required; strażnik windex.phpkieruje dotfa_setup. - Wyjątek demo (2.3.1): konto demonstracyjne niebędące
is_adminnie jest pytane o kod (hasło demo jest publiczne); prawdziwi administratorzy zawsze podają kod. - API: logowanie przez
a=login(opcjonalnieotp), token 64 hex, w bazie tylko SHA-256, ważnośćAPI_TOKEN_TTL = 8 hprzedłużana przy użyciu. - CSRF:
csrf_token()w sesji,csrf_field()w formularzach,csrf_check()dla każdego POST windex.php.
10. Uprawnienia i dostęp do wierszy
10.1 Model
- Uprawnienia przypisane są grupom; użytkownik ma grupę główną (
users.group_id) i dodatkowe (user_groups) — uprawnienia się sumują. - Akcje:
browse,details,insert,edit,delete,export+ flagaown_only. is_admin = 1(oraz konto zdemo.admins) — pełny dostęp, bez filtrów wierszy.- Goście (bez logowania): specjalna grupa
Auth::GUEST_GROUP, tylkoAuth::GUEST_ACTIONS = browse, details, export, i tylko gdypublic_access = 1. - Szablon uprawnień (
perm_template, wiersz „✦ Nowe tabele”) — nadawany automatycznie nowo utworzonym/wykrytym tabelom (Auth::applyTemplate).
10.2 Auth::can($table, $action)
- tabela musi istnieć w metadanych;
- gość → tylko przy dostępie publicznym, tylko akcje gościa, tylko tabele włączone;
- tabela wyłączona → tylko administrator;
details/edit/deletewymagają klucza głównego (bez PK tabela jest tylko do odczytu);- administrator →
true; - bez
can_browse→false; w przeciwnym razie flagacan_<akcja>(suma grup).
10.3 Ograniczenia wierszy
Auth::ownOnly($t)— gdy grupa maown_onlyi tabela maowner_field: tylko rekordy, których właścicielem jest użytkownik.RowFilter— reguły zrow_filtersdla grup użytkownika, łączone w SQL (RowFilter::sql), stosowane wszędzie: lista, szczegóły, eksport, panele powiązań, listy wyboru (Form::lookupScope), raporty, API, wtyczka. Przy zapisieRowFilter::allows()— rekord nie może wyjść poza zakres;RowFilter::defaults()podpowiada wartości przy dodawaniu.- Wspólne miejsce:
data_scope($meta, &$params)wcontrollers/data.php(własne rekordy + filtry wierszy).
11. Zapis i usuwanie rekordów, pliki
11.1 data_process_save($meta, $id, $in, $remove, $opts) — wspólny rdzeń WWW, API i importu
Kolejność kroków (każdy może przerwać z kodem HTTP):
- edycja: rekord musi istnieć w zakresie użytkownika (
data_record— z filtrami) → 404; - dla każdego pola: kolumny systemowe (
auto), właściciel, pominięcie formuł, pól tylko do odczytu, autoinkrementacji; pliki →data_check_upload; reszta →Form::normalize(typ, zakres, długość, regex, istnienie powiązania); - pola warunkowe (
Cond::state): ukryte nie są zapisywane ani wymagane;require_ifdodaje wymagalność; brak wartości → „pole wymagane”; błędy → 422 z mapąerrors; - formuły (
Formula::compute) po uwzględnieniu ukrytych pól; - filtr wierszy
RowFilter::allows→ 403; - blokada rekordu przez kogoś innego (
Locks::heldByOther) → 423; konflikt wersji (opts.ver≠data_row_version($old)) → 409conflict; - duplikaty (
data_find_duplicates):block→ 422,warnbezopts.dup_ok→ 409dup_warn; opts.dry→ koniec (tryb „Sprawdź” importu);- haki
before_insert/before_update(mogą zmienić$dataalbo przerwać komunikatem) → 422; ponowne przeliczenie formuł; - zapis plików (
data_store_upload) i rekordu; przy błędzie — usunięcie zapisanych plików, 500; - usunięcie plików zastąpionych/skasowanych, zwolnienie blokady;
Events::after(after_insert|after_update)→ dziennik, przeliczenie formuł rodziców, powiadomienia (poza importem), haki „po”.
opts: only (zapis tylko wybranych pól — przyciski akcji), form (formularz publiczny: only + wymagalność i pola warunkowe jak w zwykłym formularzu; ['id', 'title'] trafia do $ctx['form'] haków i do autora w dzienniku), skip_scope (bez filtra wierszy — zgłoszenie z formularza), ver, dup_ok, dry, via (web \| api \| import \| action \| form).
11.2 Usuwanie — data_process_delete($meta, $ids, $via)
Dla każdego id: zakres użytkownika → before_delete → usunięcie → pliki → after_delete (dziennik z pełnym rekordem — można przywrócić z dziennika, bez plików).
11.3 Pliki
- Walidacja: rozmiar (
upload_max_mb), biała listaForm::IMAGE_EXT/Form::FILE_EXT(lubextensionsbez rozszerzeń wykonywalnych),fileinfo+ weryfikacja obrazków. - Zapis:
uploads/<tabela>/<RRRR>/<MM>/<16 hex>_<nazwa-slug>.<ext>, uprawnienia 0644. - Wydawanie: tylko trasa
data/file(iapi.php?a=file) po sprawdzeniu prawa przeglądania tabeli i tego, że rekord jest w zakresie użytkownika (własne rekordy, filtry wierszy);data_file_path()pilnuje, by ścieżka nie wyszła pozauploads/(realpath); w przeglądarce otwierają się tylko obrazki (JPEG/PNG/GIF/WEBP) i PDF — reszta jakoapplication/octet-stream+attachment; nagłówekContent-Security-Policy: default-src 'none'; img-src 'self'; style-src 'unsafe-inline'.
12. Język wyrażeń — Expr
Własny tokenizer, parser (AST) i interpreter — bez eval, bez dostępu do PHP, plików ani haseł. Używany w: formułach, show_if/require_if, przyciskach akcji (set[].expr, cond), szablonach wydruku ({= …}).
| Element | Składnia |
|---|---|
| Pole rekordu | {pole} |
| Pole rekordu powiązanego | {autor_id.nazwisko} |
| Użytkownik | {user.id}, {user.username}, {user.email}, {user.<atrybut>} |
| Literały | 12, 3.5, "tekst", TRUE, FALSE, NULL |
| Operatory | + - * / %, & (łączenie tekstu), = != <> < > <= >=, AND OR NOT, nawiasy |
| Separator argumentów | ; albo , |
Funkcje (Expr::FUNCS, liczba argumentów min–max):
| Grupa | Funkcje |
|---|---|
| Logika | IF(war; tak; nie), COALESCE(a; b…), EMPTY(x) |
| Liczby | ROUND(x; n), FLOOR, CEIL, ABS, MIN(a; b…), MAX(a; b…) |
| Tekst | CONCAT(…), UPPER, LOWER, LEN, LEFT(t; n), RIGHT(t; n), TRIM, CONTAINS(t; fraza) |
| Daty | TODAY(), NOW(), DAYS(od; do), DATEADD(data; dni), YEAR, MONTH, DAY, AGE(data) |
| Agregaty po rekordach podrzędnych | SUM(tabela.pole), AVG(…), MIN(…), MAX(…), COUNT(tabela) |
| Kwoty (2.7) | KWOTA(x) — tekst z dwoma miejscami po przecinku i twardą spacją tysięcy (1 234,50); SLOWNIE(x; waluta) — kwota słownie po polsku z groszami (jeden tysiąc dwieście trzydzieści cztery zł 50/100), waluta domyślnie zł, pusty tekst = sama liczba (Expr::words(), do miliardów, odmiana tysiąc/tysiące/tysięcy) |
API: Expr::parse(), Expr::validate($expr, $table) (komunikat błędu albo null — używane przy zapisie konfiguracji), Expr::refs(), ewaluacja przez Formula/Cond/Actions. Wyjątek błędu: ExprError.
Przeliczanie: wynik formuły jest zapisywany w kolumnie (sortowanie, filtry, eksport działają bez kosztu). Po zmianie rekordu podrzędnego Formula::afterChange() przelicza rodziców (Formula::parentsOf), z ograniczeniem głębokości. Po zmianie formuły — Formula::recomputeTable() (do 20 000 wierszy). Podgląd na żywo w formularzu: trasa data/live / api.php?a=live.
13. Zdarzenia, haki, dziennik, blokady, powiadomienia
13.1 Events
- Rejestracja:
Events::on($event, $table|'*', callable)wcustom/hooks.php(ładowany w bootstrapie; błąd pliku → log, aplikacja działa). - Zdarzenia:
before_insert,before_update,before_delete,after_insert,after_update,after_delete,after_login. before_*:fn(array &$data, array $ctx); zwrócenie tekstu albofalseprzerywa operację; wyjątekDomainException→ jego komunikat dla użytkownika.after_*: błędy tylko do logu, nie cofają operacji.$ctx:table,event,user,id,old,new,via,form(przy zgłoszeniu z formularza publicznego:['id', 'title']).DBS— API dla haków (bez uprawnień i bez wywoływania haków, żeby nie zapętlić):get,find,count,insert,update,delete,query,mail,user.
13.2 Audit (dziennik zmian)
2.7: oprócz
insert/update/deleteakcjaAudit::record('after_mail', $t, ['id', 'new' => ['wysłano' => 'szablon → adres']])po wysłaniu wydruku e-mailem; widoczna w historii rekordu („Wysłano e-mailem”) i w Kreator → Dziennik zmian (filtr „Wysłano e-mail”). Nie da się jej „przywrócić” (data_restoreprzyjmuje tylkoupdate).
Audit::record() zapisuje akcję, użytkownika, IP i zmienione pola (przed/po) do audit. Włączanie: globalnie audit_enabled + per tabela topt audit. Audit::forRecord() — historia rekordu; przywracanie: trasa data/restore. Retencja: audit_keep_days (0 = bez limitu), Audit::purge() raz na dobę (audit_purged_on).
13.3 Locks (blokada edytowanego rekordu)
Tabela locks; acquire() przy otwarciu formularza (z przejęciem force), odnawianie co minutę (data/lock z JS), release() po zapisie/wyjściu, czas lock_minutes (1–120). Niezależnie — kontrola wersji data_row_version (rozdz. 11).
13.4 Notify i Mailer
Notify::fire() dla reguł topt notify: odbiorcy (adresy, grupy, autor, pole rekordu), warunek „tylko gdy zmieni się pole”, znaczniki {pole}, {tabela}, {akcja}, {id}, {uzytkownik}, {data}, {zmiany}, {link} (Notify::fill, Notify::baseUrl). Mailer::send($to, $subject, $body, $html = null, $attachments = []) — mail() albo własny SMTP (STARTTLS/SSL), wpis do mail_log (z dopiskiem [+N zał.]). Z $html (2.7) wiadomość multipart/alternative: część tekstowa i HTML (obie base64, granica dbs-<losowa>). Z $attachments (2.8; [['name', 'type', 'data']]) całość jako multipart/mixed: treść (tekst albo alternative) + załączniki base64 (Content-Disposition: attachment, nazwa oczyszczona do [A-Za-z0-9._ -]). W trybie demo wysyłka jest wyłączona (tylko dziennik).
14. Raporty, pulpity, import, wydruki
Report: konfiguracja (JSON wreports.config, czyszczona przezReport::clean):type(kolumny, skumulowane, słupki, linia, pierścień),group+group_date(dzień/miesiąc/kwartał/rok),series+series_date,measure(count,sum,avg,min,max) +mfield,sort,limit,q,flt.Report::run()buduje jedno zapytanie agregujące z uwzględnieniemdata_scopei zwraca tabelę przestawną (rows,cols,cells, sumy).Chart::render()— SVG po stronie serwera, paleta dla daltonistów.Dash: pulpity (dashboards.config) — kafelki liczbowe (warunki, znaczniki{today},{month_start},{user.id}), raporty, listy ostatnich rekordów;home_dashboard= strona startowa.Import: CSV (wykrywanie separatora i kodowania UTF-8/Windows-1250) i XLSX (pierwszy arkusz, daty Excela),Import::guess()dopasowuje kolumny,Import::convert()zamienia wartości (etykiety list i powiązań, tak/nie, kwoty). Każdy wiersz →data_process_save(via: 'import'), tryb „Sprawdź” =dry. Limit 5000 wierszy. Pliki tymczasowe wdata/.Tpl(szablony wydruku, tabelatemplates):Tpl::sanitize()usuwa skrypty, formularze, ramki i atrybuty zdarzeń;Tpl::render()—{pole},{relacja.pole},{= wyrażenie},{dzis},{nr}, pętle{#tabela}…{lp}…{/tabela}. Wydruk listy do 500 rekordów (korespondencja seryjna). PDF — przez okno drukowania przeglądarki. Podgląd na liście i wysyłka e-mailem — rozdz. 14.6.
14.1 Eksport i import (przenoszenie) — Transfer
Ekran: Kreator → Eksport i import (trasa admin/transfer, kontroler admin_transfer(), widok views/admin/transfer.php). Przyciski „Struktura JSON” / „Dane CSV” także na stronie konfiguracji tabeli.
Formaty
| Plik | Zawartość |
|---|---|
paczka .zip |
structure.json + data/<tabela>.csv + opcjonalnie files/<ścieżka w uploads> + README.txt |
structure.json / <tabela>_struktura_….json |
{format: "databas-structure", version: 1, databas, exported_at, source, app, parts[], tables{}, groups[], perms[], perm_template{}, reports[], dashboards[], print_templates[], appearance{}, contents{data, files}} |
data/<tabela>.csv / <tabela>_dane_….csv |
nagłówek = nazwy kolumn; wartości surowe (id, daty RRRR-MM-DD, kropka dziesiętna); ;, UTF-8 z BOM; puste = NULL; apostrof przed wartością zaczynającą się od = + - @, tabulatora, CR lub ' (odwracalne — Transfer::cell / Transfer::uncell) |
Tabela w tables{}: meta (kolumny tabeli tables), pk, columns[{name, kind (typ pola dataBAS), pk, autoinc, nullable, sql}], fields{nazwa: {kolumny fields…, options{}}}, opts{menu_group, notify, unique_sets, actions, row_filters, audit}, relations_off[], rows (w paczce). Zasady przenośności: powiązanie z kontami użytkowników zapisywane jako @users (przedrostek systemowy może być inny), grupa gości jako @goscie, grupy zawsze jako nazwy (w perms, perm_template, actions[].groups, notify[].groups, row_filters[].groups); raporty i pulpity z identyfikatorami źródła (do przemapowania). Części (parts): automation, perms, reports, appearance (Transfer::PARTS); struktura tabel i pól jest zawsze.
Eksport — Transfer::structure($tables, $parts), Transfer::writeCsv($t, $fh), Transfer::files($tables), Transfer::zip(...) (tymczasowo w data/transfer/, plik usuwany po wysłaniu). Logo (do 1 MB) w appearance.logo jako base64. Nie są eksportowane: użytkownicy, hasła, 2FA, licencja, klucz API, poczta, dziennik zmian.
Import — dwa kroki: upload (plik do data/transfer/import-<token>.<ext>, token w sesji) → podgląd (Transfer::open → Transfer::clean → Transfer::plan) → import (Transfer::import). Kolejność wykonania:
- tabele w kolejności zależności powiązań (
Transfer::order): brakujące tworzone przenośnym DDL (Transfer::createTable: autoinkrementacja →Db::idColumn, typy zDb::sqlType, typ kolumny powiązania zgodny z kluczem tabeli docelowej, klucze obceON DELETE SET NULL; gdy baza odrzuci klucz obcy — tabela bez FK + uwaga w raporcie); istniejące dostają tylko brakujące kolumny; metadane pól z pliku dla nowych tabel albo przy „Nadpisz konfigurację”; błąd na tym etapie → usunięcie utworzonych tabel i metadanych; - sprawdzenie powiązań (brak tabeli docelowej → pole jako liczba + uwaga) i wyrażeń (
Expr::validate; niepoprawne → usunięte + uwaga); - grupy po nazwie (brakujące tworzone tylko z częścią
perms); - automatyzacja (
cleanOpt: tylko istniejące pola, poprawne wyrażenia, grupy → identyfikatory); - dane (
importRows): klucze obce wyłączone na czas wczytywania, partie po 500 w transakcji, zachowane identyfikatory; trybyappend(konflikt klucza → pominięcie),replace(najpierwDELETE),none; kolumny autora/właściciela z nieistniejącym kontem → importujący administrator; tak/nie normalizowane; limitMAX_ROWS = 200 000na tabelę; potemFormula::recomputeTable; - pliki z
files/— tylko ścieżkitabela/RRRR/MM/nazwa.ext(Transfer::safeRel), dozwolone rozszerzenia, tabele z importu, bez nadpisywania; - uprawnienia i szablon uprawnień; 8. raporty (
Report::clean), pulpity (przemapowane id raportów; nazwa zajęta → „(import)”), szablony wydruku (Tpl::sanitize); 9. wygląd (Menu::sanitize, walidacja koloru, języka, układu; logo jak przy przesyłaniu;home_dashboardprzemapowany).
Dane wczytywane są bezpośrednio (bez data_process_save): bez walidacji formularza, haków, powiadomień i dziennika zmian — to narzędzie migracji. Do arkuszy z etykietami służy zwykły import z listy danych (rozdz. 14).
Bezpieczeństwo: tylko administrator; CSRF; limity (MAX_JSON = 20 MB, MAX_UNZIPPED = 2 GB, MAX_ENTRIES = 50 000, wierszy na tabelę); nazwy tabel/kolumn z białej listy znaków, odrzucane tabele z przedrostkiem systemowym; typy pól i klucze opcji z białych list; pliki z ZIP nigdy nie są rozpakowywane według nazwy z paczki poza zweryfikowaną ścieżką w uploads/; wersja demonstracyjna — tylko eksport.
14.2 Formularze publiczne — PublicForm
Kreator → Formularze publiczne (admin/forms — lista, admin/form — edytor), strona dla odwiedzających form.php?f=<slug> (kontroler controllers/form.php: form_show, form_live; widok views/form/public.php — samodzielny dokument bez layout.php).
Konfiguracja (forms.config, domyślne PublicForm::DEFAULTS, oczyszczanie PublicForm::clean() — także przy imporcie): fields[{name, label, hint, required}] (kolejność = kolejność w formularzu), intro, success, button, again, presets[{field, value}] ({today}, {now}), consent{enabled, text, url}, confirm_field (pole e-mail), confirm_subject, confirm_body ({tytul}, {data}, {nr}), confirm_summary, notify, limit_ip, max_entries, opens_at, closes_at, files, embed_domains.
Pola dostępne (PublicForm::eligible): bez autoinkrementacji, pól wyliczanych, kolumn systemowych (auto), pola właściciela; pliki tylko z files. Lookupy pokazują publicznie listę rekordów tabeli docelowej — edytor ostrzega (PublicForm::warnings, także o polach wymaganych w tabeli, których nie ma w formularzu).
Zapis zgłoszenia (PublicForm::submit): wartości tylko z pól formularza → nadpisanie wartościami ukrytymi → wartości domyślne pól spoza formularza (rozwiązywane lokalnie, bez data_default(), która czyta ?v[…] z adresu) → dodatkowa wymagalność z formularza i zgoda (przy błędach — data_process_save(dry), żeby pokazać wszystkie błędy naraz) → data_process_save($meta, null, $vals, [], ['only', 'form', 'via' => 'form', 'skip_scope' => true, 'dup_ok' => true]) → wpis w form_log → e-maile (mails).
Ochrona: brak sesji i CSRF (ramka na obcej stronie nie wysyła ciasteczek SameSite=Lax); zamiast tego token czas.HMAC(slug|czas) (token/checkToken: wiek 3 s – 24 h; po błędach walidacji token jest zachowywany — keepToken); pole-pułapka website (udany wynik dla automatu, bez zapisu); limit limit_ip na godzinę (po skrócie IP) i PER_MINUTE = 30 dla formularza; strona „dziękujemy” przez podpisany parametr ok=<id>.<podpis>. Odpowiedzi no-store, X-Robots-Tag: noindex. Podgląd warunków (form/live) zwraca tylko widoczność i wymagalność pól formularza — bez wartości formuł. Potwierdzenie e-mailem ma stałą treść (zgłaszający może wpisać cudzy adres), podsumowanie danych tylko z confirm_summary.
Osadzanie: &embed=1 usuwa X-Frame-Options i ustawia Content-Security-Policy: frame-ancestors (* albo embed_domains); strona wysyła do rodzica postMessage({dbsForm: slug, h}) z wysokością treści (.pf-main, nie dokumentu — inaczej dopasowanie się zapętla); kod do wklejenia (PublicForm::embedCode) nasłuchuje i ustawia wysokość ramki.
Stan (PublicForm::open): włączony, istniejąca tabela, termin otwarcia/zamknięcia, limit zgłoszeń. Teksty strony przez t() (lang/en.php, &lang=en). Eksport i import: część forms w Transfer (adres zachowany, jeśli wolny).
14.3 Przypomnienia e-mail — Reminders
Kreator → Przypomnienia (admin/reminders — lista z akcjami toggle / run / delete, admin/reminder — edytor; act=test zapisuje i wysyła wiadomość próbną na adres zalogowanego administratora, bez wpisu w dzienniku).
Reguła (reminders.config, domyślne Reminders::DEFAULTS, oczyszczanie Reminders::clean()): date_field (pole date/datetime; puste ⇒ when = none), when (before \| on \| after \| none), days (0–365), conds[{field, op, value}] (operatory RowFilter::OPS, SQL z RowFilter::condSql — te same co filtry wierszy), to{emails, field, groups[], owner}, mode (each \| digest), repeat (0 = raz; N = co N dni; lista zbiorcza ≥ 1), hour, subject, body (bez HTML — wiadomości są tekstowe).
Okna dat (Reminders::matching, liczone od „dziś” serwera, DateTimeImmutable('today')):
when |
warunek SQL |
|---|---|
before, N |
pole >= dziś AND pole < dziś+N+1 — od dziś do N dni naprzód (przy N = 3 przypomnienie przyjdzie raz, gdy rekord wejdzie w okno) |
on |
pole >= dziś AND pole < jutro |
after, N |
pole < dziś−N+1, czyli pole ≤ dziś−N (N = 0 ⇒ od dnia terminu); obejmuje wszystkie starsze zaległości |
none |
tylko warunki |
Bez duplikatów (due): klucz rkey = d + wartość terminu (albo r dla none). repeat = 0 — wysyłka raz na (reminder_id, record_id, rkey); zmiana terminu w rekordzie daje nowy klucz. repeat = N — ponownie, gdy ostatnia udana wysyłka dla rekordu była ≥ N dni kalendarzowych temu. Lista zbiorcza: wpis z record_id = NULL, ponownie co repeat dni.
Odbiorcy: staticRecipients — adresy stałe + aktywni użytkownicy z email, których group_id albo user_groups należy do wybranych grup; recordRecipients — pole rekordu (email) albo ścieżka lookup.pole (rekord powiązany przez Form::lookupValueCol; nie dla tabeli użytkowników) oraz autor/właściciel (owner_field albo pole auto = user_id → users.email). Adresy normalizowane do małych liter, unikalne; każdy odbiorca dostaje osobną wiadomość (Mailer::send([$email], …)). Lista zbiorcza idzie tylko do odbiorców stałych. Brak adresu ⇒ wpis error „Brak adresu odbiorcy.”.
Treść (message, fill): {pole} i {powiązanie.pole} jako tekst (Form::plain, etykiety powiązań), {termin} (d.m.Y), {dni} (wartość bezwzględna różnicy), {link} (Reminders::baseUrl() + index.php?r=data/view…), {tabela}, {dzis}, {nazwa}; zbiorczo {lista} (do 200 pozycji z linkami; dopisywana, gdy brak znacznika) i {liczba}. Domyślny temat = nazwa reguły (zbiorczo nazwa: {liczba}).
Adres w linkach (Reminders::baseUrl): ustawienie base_url → seen_base_url (zapisywane przez rememberBaseUrl() przy wejściu administratora na admin/reminders / admin/reminder, tylko z index.php) → bieżący host. Nie ufamy nagłówkowi Host z dowolnego żądania, które akurat uruchomiło harmonogram.
Wysyłka: runDue() — włączone reguły, raz dziennie (last_run ≠ dziś) po godzinie hour; budżet MAX_PER_RUN = 300 wiadomości na uruchomienie (run() przerywa przed przekroczeniem i nie ustawia last_run, więc reszta pójdzie przy następnym uruchomieniu). „Wyślij teraz” i cron.php przypomnienia wołają run() z pominięciem godziny i last_run, ale dziennik i tak nie dopuści do duplikatów. Dziennik: reminder_log (w edytorze 20 ostatnich), poczta dodatkowo w mail_log. Podgląd (preview) — pierwsze 30 pasujących rekordów z odbiorcami i stanem „wyślemy / już wysłane”.
14.4 Harmonogram — Scheduler i tools/cron.php
Scheduler::tick($source, $force)— blokadaflock(data/cron.lock, LOCK_EX | LOCK_NB)(równoległe uruchomienie zwracanull), zapiscron_last/cron_source, potemReminders::runDue()iBackup::runDue()(każde w osobnymtry— błąd jednego nie zatrzymuje drugiego), wpis docron_logtylko, gdy coś się wydarzyło.- Z ruchu na stronie (
maybeWeb, trybauto): nie w CLI; gdy odcron_lastminęło ≥WEB_EVERY = 300s i jest co robić (włączone przypomnienie albo kopie automatyczne) —register_shutdown_function→ignore_user_abort(true)→fastcgi_finish_request()/litespeed_finish_request()(odpowiedź trafia do przeglądarki przed pracą) →tick('web')(ponowne sprawdzenie czasu już pod blokadą). Na Apache mod_php bez tych funkcji praca wydłuża to jedno żądanie — dlatego zalecany jest cron. - Z crona:
php tools/cron.php→tick('cron', true);kopia→Backup::create('reczna', true);przypomnienia→run()dla włączonych reguł;stan. Kod wyjścia 1 i komunikat na STDERR przy błędzie. Gotowa linia crona:Scheduler::cronLine()(ścieżka PHP zPHP_BINDIR,ROOT). Scheduler::status()['stale']— brak uruchomienia od 3 h ⇒ ostrzeżenie w ramceviews/admin/_sched.php(strony Przypomnień i Kopii). Przycisk „Uruchom harmonogram teraz” (admin/backups,act=tick) =tick('web', true).
14.5 Kopie zapasowe — Backup
Kreator → Kopie zapasowe (admin/backups; GET act=download&f=; POST act = settings \| create \| delete \| restore \| upload \| tick). Wymaga ZipArchive (Backup::available()).
Zakres (Backup::tables()): tabele bazowe (bez widoków) z Db::internalTables() oraz tabele aplikacji zgodne z filtrem przedrostków. Pliki uploads/ opcjonalnie (Demo::listFiles). Bez config.php.
Format ZIP (create): backup.json — format = databas-backup, version, databas (DBS_VERSION), schema, driver, prefix, created, kind, host, app, tables[{name, file, rows, cols}], files, file_count; db/<n>.json — {name, ddl[], cols[]} (DDL tej bazy: SHOW CREATE TABLE albo sqlite_master z indeksami); db/<n>.rows — JSONL, jeden wiersz = tablica wartości w kolejności cols (zapis strumieniowy przez plik tymczasowy, JSON_INVALID_UTF8_SUBSTITUTE); uploads/…. Plik powstaje jako .tmp i jest przemianowywany po close(); przed startem sprawdzane jest ≥ 50 MB wolnego miejsca.
Nazwy: databas-kopia_RRRR-MM-DD_GGMMSS_<rodzaj>[_xxxx].zip, rodzaje auto / reczna / przed (kopia bezpieczeństwa) / wgrana; Backup::path() przyjmuje wyłącznie nazwy zgodne z Backup::NAME (brak przejścia ścieżek). Katalog data/backups/ tworzy się z .htaccess (Require all denied) i index.html; pobieranie tylko przez kontroler administratora.
Rotacja (prune): zostaje backup_keep najnowszych auto i 3 najnowsze przed; reczna i wgrana usuwa tylko administrator. Automat (runDue): po godzinie backup_hour, codziennie (inny dzień niż backup_last) albo co tydzień (backup_last starsze niż 6 dni od dzisiejszej północy); backup_last zapisywane przed utworzeniem — błąd (np. brak miejsca) nie powoduje prób co 5 minut, trafia do cron_log.
Przywracanie (restore; w kontrolerze wymaga wpisania „PRZYWRÓĆ”, potem Auth::logout()):
check()— ta sama baza (driver), ten samprefix,schema≤Schema::VERSION.- Kopia bezpieczeństwa
przed(z plikami, jeśli kopia je zawiera). - Wyłączenie kluczy obcych (
FOREIGN_KEY_CHECKS = 0/PRAGMA foreign_keys = OFF),DROP+ DDL wszystkich tabel z kopii,DROPtabel aplikacji, których w kopii nie ma (tabele z przedrostkiem systemowym zostają). - Dane strumieniowo z
getStream(), w transakcjach po 1000 wierszy (DDL jest wcześniej — w MySQL DDL zatwierdza transakcję). - Pliki: rozpakowanie do
data/backups/.restore-*z odrzuceniem niebezpiecznych ścieżek (.., ścieżki bezwzględne,\,:,.php*,.phtml,.phar,.htaccess), potemDemo::syncDir()—uploads/dokładnie jak w kopii. - Wyczyszczenie pamięci podręcznej (
Db::forgetTables,Meta::forgetSettings,Meta::flush) iSchema::migrate()(kopia ze starszej wersji dostaje brakujące migracje).
Przy błędzie w krokach 3–4 komunikat wskazuje nazwę kopii bezpieczeństwa.
Wgrywanie (upload): plik musi mieć poprawne backup.json; niezgodność bazy/przedrostka jest pokazywana na liście (bez przycisku „Przywróć”; serwer i tak odrzuci żądanie). Demo: Demo::adminBlock blokuje całe admin/backups (kopie zawierają konta administratorów).
14.6 Szablony wydruku: podgląd i wysyłka e-mailem — Tpl
Ustawienia szablonu (templates.settings, JSON; jedno miejsce czyszczenia — Tpl::cleanSettings($in, $table), używane przez edytor print/template, instalator szablonów aplikacji i import w Transfer):
| Klucz | Znaczenie |
|---|---|
paper, margin, css |
jak wcześniej (Tpl::PAPER, 5–40 mm, CSS bez url(, @import, expression, javascript:) |
preview |
podgląd po najechaniu na rekord na liście (bool) |
email |
wysyłka wydruku e-mailem (bool; wyłączana, gdy brak email_field) |
email_pdf |
dołącz wydruk jako PDF (2.8; Tpl::pdf, nazwa pliku Tpl::pdfName) |
email_field |
pole email rekordu albo ścieżka powiązanie.pole (Tpl::emailPaths — jak w przypomnieniach; bez tabeli użytkowników) |
email_subject, email_body |
temat (≤ 200, jedna linia) i wstęp (≤ 3000, bez HTML) — znaczniki szablonu, wynik jako tekst (Tpl::plain = render(e($tpl)) → strip_tags → html_entity_decode) |
after[{field, value, when}] |
do 5 pól ustawianych po udanej wysyłce; value z {today} / {now}; when — warunek Expr (sprawdzany Expr::parse przy zapisie, błędny jest usuwany). Bez pól wyliczanych, systemowych (auto) i plików |
Trasy (controllers/print.php):
| Trasa | Działanie |
|---|---|
print/record&…&pdf=1, print/list&…&pdf=1 |
plik PDF (2.8) z tych samych stron co wydruk (print_out → Tpl::pdf), Content-Disposition: attachment (z &inline=1 — w przeglądarce), no-store; uprawnienia jak wydruk (details) |
print/record&…&embed=1 |
wydruk do ramki podglądu: bez paska, białe tło, strona o rzeczywistej szerokości papieru (skalowana w panelu przez transform: scale) |
print/peek (GET, JSON) |
dane panelu: title, frame, print, view, paper, send{to, url, tpl, t, id} albo null; wymaga zalogowania i Auth::can(t, 'details') (403), rekord przez data_record (filtry wierszy), pierwszy szablon z preview albo wskazany tpl |
print/send (POST, CSRF) |
data_need(details), rekord przez data_record, szablon tej tabeli → Tpl::send(); z &json=1 odpowiedź {ok, msg}, inaczej komunikat + powrót (_ret) |
Tpl::send($tpl, $meta, $row): adres wyłącznie z rekordu (Tpl::recipient — FILTER_VALIDATE_EMAIL; przeglądarka nie może go podać), limit SEND_LIMIT = 30 wysyłek na sesję w 10 minut, treść: render() szablonu + Tpl::css() (wspólny styl wydruku, strony i e-maila) w samodzielnym dokumencie HTML (wstęp, strona, stopka; @media print) oraz wersja tekstowa z HTML (komórki tabel → tabulatory). Po sukcesie: Audit::record('after_mail') i — tylko z prawem edit — data_process_save($meta, $id, $vals, [], ['only', 'via' => 'mail', 'dup_ok' => true]) dla pól after spełniających when (dziennik zmian, haki, formuły jak przy zwykłym zapisie). Obrazki pól image w e-mailu się nie wyświetlą (adres pliku wymaga logowania).
Interfejs: lista (views/data/list.php) — gdy tabela ma szablon z preview, zalogowany ma details: wiersze dostają data-peek (adres print/peek), przycisk 🧾 w kolumnie akcji i panel #peek (role="dialog", niemodalny, etykiety w data-l-* przez t()). assets/app.js: najechanie (tylko (hover: hover) and (pointer: fine)) — otwarcie po 450 ms (150 ms przy przejściu między wierszami), zamknięcie 350 ms po zjechaniu z wiersza i panelu; (2.9.1) nad elementami interaktywnymi wiersza (przyciski akcji, linki, pola) panel się nie otwiera, a nieprzypięty — chowa się; po powrocie kursora na zwykłą komórkę licznik otwarcia rusza od nowa (panel stoi przy prawej krawędzi i zasłaniał przyciski akcji); przycisk 🧾 — panel „przypięty”, fokus na „Zamknij”, Escape zamyka i oddaje fokus; < 760 px — panel jako arkusz u dołu ekranu; odpowiedzi peek w pamięci strony. Wysyłka z panelu — fetch z _csrf z data-csrf. Pasek wydruku (views/print/out.php) i menu „Drukuj” w widoku rekordu mają przycisk „Wyślij na <adres>” (wyłączony, gdy brak adresu).
14.7 PDF na serwerze — Pdf, PdfFont, HtmlPdf
Własny generator (2.8), bez bibliotek i usług zewnętrznych. Wejście: te same strony HTML co wydruk (Tpl::render() albo print_default()), CSS = Tpl::css($settings) (wspólny styl wydruku + CSS szablonu). Wejście do generatora: Tpl::pdf(array $pages, array $settings, string $title): string.
PdfFont — parser TrueType: tabele head, hhea, maxp, hmtx, cmap (format 4 i 12), OS/2, post, loca, glyf. encode() zamienia tekst na identyfikatory glifów (Identity-H) i zapamiętuje użyte glify oraz znaki (ToUnicode). subset() — podzbiór bez przenumerowania: zostają kontury użytych glifów i składowych glifów złożonych, pozostałe glify są puste, loca w formacie długim; tabele cvt , fpgm, prep, hhea, hmtx, maxp, head, OS/2, post (wersja 3). Faktura z dwoma odmianami fontu ≈ 40 kB.
Pdf — dokument PDF 1.7: strony (A4/A5/Letter, pion/poziom), układ współrzędnych od lewego górnego rogu (przeliczany), text() (Type0 + CIDFontType2, /W z szerokościami, /ToUnicode — tekst da się kopiować i wyszukiwać; Tc ustawiane zawsze, bo należy do stanu grafiki), rect(), line() (solid/dashed/dotted), image() (JPEG wprost DCTDecode, inne przez GD → RGB + SMask dla przezroczystości), link() (adnotacje URI dla http(s): i mailto:), przezroczystość przez ExtGState, strumienie FlateDecode, metadane (Title, Author, Producer), tablica xref.
HtmlPdf::render($pages, $opt) — opt: paper, margin (mm), css, title, author, resolver (src → bajty obrazu), page_numbers.
| Etap | Szczegóły |
|---|---|
| CSS | arkusz domyślny HtmlPdf::UA + CSS wydruku; @media print/all brane, screen/min-width/max-width pomijane, @page, @font-face, @import pomijane. Selektory: znacznik, .klasa, #id, potomek, >, +, ~, :first-child, :last-child, :only-child, :nth-child(even/odd/n), :empty, :not(proste); nieobsługiwane (np. :hover) — reguła pomijana. Specyficzność, kolejność, !important, atrybut style, atrybuty width/height/align/valign. Skróty: margin, padding, border*, background, font, list-style, page-break-*. |
| Style | dziedziczone: kolor, font, wysokość linii, wyrównanie, text-transform, letter-spacing, white-space, text-decoration; dziedziczona jest wartość obliczona (_fs, _ls, _lh). Jednostki: px (0,75 pt), pt, mm, cm, in, em, rem, %, słowa kluczowe. Kolory: #rgb(a), #rrggbb(aa), rgb()/rgba(), nazwy podstawowe. Font: Liberation Sans w 4 odmianach (waga ≥ 600 = pogrubiony). |
| Bloki | marginesy pionowe ściągane między sąsiadami, width/max-width/box-sizing, margin: auto (środek), tło i obramowania jako „ramki” otwarte w trakcie układu — rysowane po zamknięciu z numerem kolejności sprzed treści (seq), dzielone na fragmenty przy łamaniu strony (górna krawędź na pierwszym, dolna na ostatnim). Przezroczyste obramowanie boczne skraca linie górną/dolną (odstęp jak w przeglądarce). |
| Tekst | słowa z odstępami, sklejanie słów bez spacji między węzłami, łamanie zachłanne, słowa dłuższe niż wiersz dzielone po znakach, nowrap, pre/pre-wrap/pre-line, wyrównanie (także justify), wysokość linii z metryk fontu (half-leading), sub/super, podkreślenie i przekreślenie, tło elementów w linii, linki, atomy w linii: obrazy i inline-block (układ do bufora). |
| Tabele | wiersze z thead/tbody/tfoot/tr, colspan; szerokości: min/max treści każdej komórki (measure()), szerokość z CSS jako preferowana (najpierw dla kolumn z width, jeśli reszta mieści się w minimach), potem rozkład między min a max albo nadmiar dla kolumn automatycznych; komórki układane do bufora, wysokość wiersza = najwyższa komórka, vertical-align top/middle/bottom; wiersz nie jest dzielony — przechodzi na nową stronę, a wiersze thead są powtarzane. Model obramowań: rysowane krawędzie każdej komórki (odpowiednik border-collapse). |
| Flex | tylko kierunek wiersza: szerokości z flex-basis/width/max treści, flex/flex-grow, zwężanie do minimów, gap, justify-content (start, end, center, space-between/around/evenly), align-items (start, center, end). flex-direction: column = zwykłe bloki. |
| Strony | każdy element $pages od nowej strony (wydruk listy = rekord na stronę), break-before: page; po każdym dokumencie gotowe strony są od razu zapisywane do Pdf i pamięć zwalniana — 500 stron ≈ 6 s, ≈ 20 MB. Numery stron „n / N” w dolnym marginesie, gdy stron jest więcej niż jedna. |
Obrazy (Tpl::pdf → resolver): tylko index.php?r=data/file… dla pól typu image (po Auth::can(browse) i data_record — filtry wierszy) oraz index.php?r=brand/logo (logo rastrowe, bez SVG), a także data:image/…;base64. Adresy zewnętrzne nie są pobierane (brak SSRF), limit 15 MB na obraz.
Nieobsługiwane (pomijane bez błędu): float, position, transform, border-radius, cienie, gradienty, @font-face i inne fonty, dzielenie wyrazów, kerning, pisma od prawej do lewej, obrazy SVG.
16. Tłumaczenia — I18n
I18n::LANGS = ['pl' => 'Polski', 'en' => 'English']. Wybór: profil użytkownika (users.lang) →?lang=dla gości →default_lang.t('Tekst polski', ['zmienna' => …])— klucz to tekst polski; słowniklang/<kod>.phpzwraca tablicę['tekst polski' => 'translation']. Brak tłumaczenia → tekst polski.- Tłumaczona jest część użytkownika; Kreator i dane użytkownika (nazwy tabel/pól) — nie.
- Nowy język: skopiuj
lang/en.phpjakolang/xx.php, dopisz'xx' => 'Nazwa'wI18n::LANGS.
17. API JSON — api.php
17.1 Uwierzytelnianie
| Nagłówek | Wymagany | Opis |
|---|---|---|
X-Databas-Key |
zawsze | klucz z Kreator → Ustawienia → Integracja (api_key, porównanie hash_equals); API musi być włączone (api_enabled) |
Authorization: Bearer <token> |
dla zalogowanego | token z a=login; bez tokenu — uprawnienia gościa |
X-Databas-Token: <token> |
zapasowo | ten sam token — dla hostingów usuwających Authorization (Apache + FastCGI/CGI), od 2.3.2 |
X-Databas-Client-IP |
zalecany | IP odwiedzającego — limit prób logowania liczony per osoba, nie per serwer WordPress |
Odczyt nagłówków: api_header() — $_SERVER['HTTP_…'], REDIRECT_HTTP_…, getallheaders(). Niepoprawny lub wygasły token → 401 session_expired.
17.2 Akcje (api.php?a=…)
| Akcja | Metoda | Parametry | Wynik |
|---|---|---|---|
ping |
GET | — | ok, app, version, public, user |
me |
GET | — | user, guest, public |
tables |
GET | — | tables[{name, label, icon, description, perms}], user, public |
schema |
GET | t |
pola (typy, opcje, walidacja), uprawnienia, relacje, live |
list |
GET | t, p, n, s, d, q, f[pole] |
rekordy (wartości surowe i sformatowane), stronicowanie |
record |
GET | t, id |
rekord + dostępne przyciski akcji |
related |
GET | t, id |
panele rekordów podrzędnych |
options |
GET | t, f, q |
wyszukiwanie w dużych listach powiązań |
file |
GET | t, id, f, dl |
strumień pliku |
export |
GET | jak list |
CSV |
login |
POST | username, password, otp, client |
token, expires_in, user |
logout |
POST | — | ok |
save |
POST | t, id?, v[…], _remove[…], _file[…], dup_ok, _ver |
wynik data_process_save |
delete |
POST | t, ids[] |
wynik data_process_delete |
live |
POST | t, id?, v[…] |
wartości formuł, widoczność i wymagalność pól |
action |
POST | t, id, a (pole POST = id przycisku z topt actions) |
wykonanie przycisku akcji |
POST przyjmuje formularz albo JSON (Content-Type: application/json).
17.3 Kody odpowiedzi
| Kod | Znaczenie |
|---|---|
| 200 | sukces |
| 401 | zły klucz API, brak/wygasły token (session_expired), wymagane logowanie (login_required), brak/zły kod 2FA (otp_required) |
| 402 | brak ważnej licencji (license: false) |
| 403 | API wyłączone, brak uprawnień, wymagane skonfigurowanie 2FA (tfa_setup_required), filtr wierszy |
| 404 | brak tabeli/rekordu |
| 405 | operacja zapisu bez POST |
| 409 | konflikt wersji (conflict) albo ostrzeżenie o duplikacie (dup_warn) |
| 422 | błędy walidacji (errors: pole → komunikat), blokada duplikatu, odmowa haka |
| 423 | rekord zablokowany przez innego użytkownika (locked) |
| 429 | za dużo nieudanych logowań |
18. Licencjonowanie — License
- Stany (
License::state()): wersja próbna (TRIAL_DAYS = 14odinstall_date), aktywna, okres przejściowy (GRACE_DAYS = 14po wygaśnięciu tokenu bez kontaktu z serwerem), zablokowana. Domeny testowe (License::isDev: localhost, IP prywatne,*.local|test|localhost|example|invalid,staging.|stage.|test.|dev.) — bez zajmowania limitu. app/license.pub(JSON ze sklepu):server(adres REST serwera licencji),issuer(sklep),ed25519(klucz publiczny base64),rsa(PEM).- Protokół (POST JSON na
<server>/<akcja>, timeout 8 s, curl albo strumień): activate{key, domain, version}→{token};check{key, domain, version}→{token}albo{error, code}(expired,revoked,not_activated,invalid_key— usuwa token);deactivate{key, domain}→ zwolnienie domeny;- (2.9)
modules{key, domain, version}→{modules: [{id, name, version, icon, desc, features, price, requires, owned}], buy_url}; - (2.9)
module{key, domain, id, version}→{id, version, package: base64(.dbsmod)}— tylko dla domeny aktywowanej na tej licencji i kupionego modułu (timeout 30 s).License::api($akcja, $dane)to publiczne wejście do tych wywołań. - Token:
base64url(JSON).podpisEd25519.podpisRSA; ładunek zv = 1, plan, domena, ważność (30 dni), od 2.9modules— lista identyfikatorów kupionych modułów. Weryfikacja lokalna: najpierw Ed25519 (sodium), zapasowo RSA-SHA256 (openssl). - Odświeżanie: co
REFRESH_AFTER = 7 dni; po błędzie ponowna próba nie częściej niżRETRY_EVERY = 6 h(license_try). - Klucz:
DBS-XXXXX-XXXXX-XXXXX-XXXXX. - Skutki braku licencji: WWW — strona
locked(402), administrator tylkoadmin/license; API — 402. - Serwer licencji to wtyczka WordPress dbs-licencje (REST
dbs-license/v1/activate|check|deactivate) — rozdz. 22.
19. Tryb demonstracyjny — Demo
Konfiguracja w config.php:
'demo' => [
'enabled' => true,
'logins' => [['label' => 'Wejdź jako administrator', 'user' => 'admin', 'pass' => '12345678']], // przyciski logowania
'accounts' => ['gosc'], // konta demonstracyjne (domyślnie: loginy z 'logins')
'admins' => ['admin'], // konta demo z widokiem Kreatora (w bazie is_admin = 0)
'reset_hours' => 3, // automatyczne przywracanie (0 = tylko z crona)
'uploads' => false, // przesyłanie plików przez konta demo
'drop_new_tables' => true, // usuwanie tabel utworzonych po zapisaniu wzorca (domyślnie: tak, gdy są konta 'admins')
],
Demo::isAccount($u)— login na liścieaccounts/admins;Demo::isAdminAccount()— konto zadmins(dostajeAuth::isAdmin()).- Ograniczenia (
index.php+Demo::adminBlock): licencja, kopie zapasowe (admin/backupsw całości), sekcje ustawieńapi/system(poczta, bezpieczeństwo, integracja), konta administratorów i konta demo (Demo::protectedUser), nadawanieis_admin, przesyłanie plików (poza importem),tfa_setup, zmiana hasła/e-maila w profilu. Zwykłe konto demo — bez Kreatora, importu i edycji szablonów wydruku. W widokach maskowane: klucz API, adresy IP w dzienniku. Poczta tylko domail_log. - Wzorzec:
Demo::snapshot()→data/demo-snapshot.json.gz(DDL + dane tabel systemowych i tabel aplikacji zgodnych z filtrem, kopiauploads/wdata/demo-snapshot-uploads). - Przywracanie:
Demo::restore()— odtwarza dane i strukturę (usuwa nowe tabele, przebudowuje tabele ze zmienionymi kolumnami). Zachowuje: użytkownikówis_admin = 1, ustawienialicense_*,install_date,api_key,api_enabled,api_tokens,login_attempts,mail_log.Demo::maybeReset()— przy żądaniu po upływiereset_hours, z blokadą plikudata/demo-reset.lock; znacznikdata/demo-last-reset. - CLI:
php tools/demo_reset.php zapisz | przywroc | stan. - Wersja produkcyjna różni się wyłącznie brakiem sekcji
demo(kod jest ten sam).
20. Szablony aplikacji — Templates
Templates::all() zwraca definicje: wypozyczalnia, inwentarz, wycieczki, zgody, zajecia, pisma, faktury (2.7).
Struktura definicji:
'klucz' => [
'name' => 'Nazwa', 'icon' => '📚', 'desc' => '…', 'features' => ['…'],
'tables' => [
'nazwa_tabeli' => [
'label' => '…', 'icon' => '…', 'order' => 'pole', 'dir' => 'ASC|DESC', 'desc' => '…',
'perm' => 'view|edit', // uprawnienia grupy: słownik do odczytu / tabela robocza
'fields' => [
// [nazwa, typ, etykieta, kolumny_fields (required, in_list, readonly, hint, default_value…), opcje]
['pozycja_id', 'lookup', 'Pozycja', ['required' => 1], ['ref' => 'pozycje', 'label' => 'tytul']],
['dni', 'number', 'Dni', ['readonly' => 1], ['formula' => 'DAYS({data_od}; TODAY())']],
// opcje: choices, ref, label, formula, show_if, require_if, min, max…
],
'rows' => [ ['pole' => 'wartość', 'pozycja_id' => '@pozycje#1', 'data' => '{d-14}'] ], // @tabela#n = n-ty rekord, {d±N} = data względna
],
],
'actions' => [...], 'reports' => [...],
'prints' => [['table' => 'faktury', 'name' => 'Faktura VAT', 'body' => '<html szablonu>', 'settings' => [/* jak Tpl::cleanSettings */]]], // 2.7
]
Templates::install($key, ['prefix', 'sample', 'group']): sprawdza kolizje nazw (Templates::problems), tworzy tabele z kolumnami systemowymi, ustawia metadane pól, akcje, menu_group, dane przykładowe (z rozwiązaniem odwołań i dat), przelicza formuły, dodaje raporty, szablony wydruku (prints), uprawnienia grupy, dopisuje grupę do własnego menu (jeśli istnieje). Błąd → wycofanie (usunięcie utworzonych tabel i metadanych, także templates).
Nazwy tabel przy instalacji z przedrostkiem (poprawka 2.7): w formułach agregatów SUM|AVG|MIN|MAX|COUNT(tabela…) klucze tabel szablonu są zamieniane na nazwy nadane przy instalacji (np. SUM(pozycje_faktury.x) → SUM(b2_pozycje_faktury.x)), a w treści szablonów wydruku — pętle {#klucz}…{/klucz}. Wcześniej drugi egzemplarz szablonu liczył sumy z tabel pierwszego.
Szablon faktury: sprzedawcy (dane na fakturze, kod pocztowy z regex), nabywcy (email = adres wysyłki), faktury (numer z unique: block, sprzedawca_id domyślnie 1, termin_platnosci = DATEADD(data_wystawienia; dni_platnosci), suma_netto|vat|brutto = SUM(pozycje_faktury.…), status wystawiona/wyslana/oplacona, data_wyslania tylko do odczytu, data_zaplaty widoczna przy opłaconej, akcja „Opłacona”), pozycje_faktury (wartosc_netto = ROUND(ilosc * cena_netto; 2), kwota_vat = 0 dla zw, inaczej ROUND(netto * stawka / 100; 2) — VAT liczony od pozycji, wartosc_brutto). Wydruk „Faktura VAT” (preview, email na nabywca_id.email, after: status = wyslana gdy {status} = "wystawiona", data_wyslania = {now}); CSS w ustawieniach szablonu (klasy fv-*), kwoty przez KWOTA(), słownie SLOWNIE(), pieczątka „Zapłacono” przez {= IF(…)} i :empty. Raporty: sprzedaż brutto w miesiącach (wg statusu), faktury wg statusu. Zestawienia VAT według stawek nie ma — SUM() nie ma warunku.
Nowy szablon: jako moduł (zalecane od 2.9 — bez wydania dataBAS, 20.1) albo wbudowany: dopisz klucz w Templates::all(), opis w README i dokumentacji dla użytkowników, test instalacji na SQLite i MySQL (rozdz. 25).
20.1 Moduły — Modules i tools/module.php
Moduł (2.9) to szablon aplikacji dystrybuowany poza kodem dataBAS — kupowany do klucza licencji w sklepie (dbs-licencje 1.2) i wczytywany do instalacji. Zawiera wyłącznie dane (definicję), nigdy kod.
Paczka .dbsmod (ZIP, maks. 8 MB, module.json maks. 4 MB):
| Plik | Zawartość |
|---|---|
module.json |
{format: "databas-module", v: 1, id, version, requires, name, icon, desc, author, features[], template: {tables, reports, prints}} — template ma strukturę szablonu wbudowanego (rozdz. 20; akcje w tables.<t>.actions) |
signature.json |
{token} podpisany kluczem sklepu (DBSLIC_Keys::sign, Ed25519 + RSA); ładunek {v: 1, type: "module", id, version, sha256: sha256(module.json), iat} |
license.json |
{token} przydziału; ładunek {v: 1, type: "module-grant", id, lk, lic, iat}, gdzie `lk = substr(sha256("dbs-mod |
CZYTAJ.txt |
instrukcja dla kupującego (dodaje sklep) |
Weryfikacja — Modules::read($plik): rozmiar → ZIP → module.json → Modules::validate() → podpis (License::verify, typ module, zgodność id, version i SHA-256 surowego module.json przez hash_equals) → przydział (opcjonalny; nieważny przydział jest ignorowany). Podpis jest sprawdzany przy każdym odczycie zainstalowanej paczki, nie tylko przy instalacji.
Uprawnienie — Modules::entitled($pkg) → [bool, powód, sposób]:
- niepodpisana → tylko z
modules_dev(sposóbdev); requires>DBS_VERSION→ odmowa;- brak klucza lub tokenu licencji → odmowa;
idna liściemodulesw tokenie →licencja;- przydział dla skrótu klucza tej instalacji i (token bez pola
moduleslubgrant.iat >= token.iat) →plik(zakup po ostatnim odświeżeniu licencji). Starszy przydział nie wygrywa z nowszym tokenem — moduł odebrany w sklepie przestaje działać po odświeżeniu licencji (License::refresh, co 7 dni lub „Odśwież”).
Walidacja definicji (Modules::validate, nieznane klucze pomijane): id ^[a-z][a-z0-9-]{2,40}$, wersje N.N[.N], do 20 tabel i 100 pól na tabelę, nazwy ^[a-z][a-z0-9_]{0,40}$ bez id|created_at|updated_at|created_by, typ pola z field_types(), kolumny pól tylko z Modules::COLS, opcje tylko z Modules::OPTS; formula, show_if, require_if, wyrażenia i warunki akcji przez Expr::parse (nieznana funkcja = odrzucenie), regex kompilowany próbnie, lookup.ref tylko do tej samej lub wcześniejszej tabeli modułu, etykiety powiązań przycinane do pól tabeli docelowej; dane przykładowe do 300 wierszy na tabelę (tylko znane pola, wartości skalarne), do 12 akcji, 12 raportów, 8 wydruków (treść ≤ 300 kB — przy instalacji przechodzi Tpl::sanitize jak każdy szablon wydruku). Ta sama walidacja działa w narzędziu autora i przy odczycie.
Integracja z szablonami: Templates::available() = Templates::all() + Modules::templates() (klucze mod-<id>, tylko moduły uprawnione i bez błędu; pole module = {id, version, author}); Templates::get() korzysta z available(), więc instalacja aplikacji z modułu to zwykłe Templates::install (przedrostek, dane przykładowe, grupa, wycofanie przy błędzie). Aktualizacja lub usunięcie paczki nie zmienia utworzonych tabel.
Instalacja paczki: Modules::install($plik) (read + entitled, zapis atomowy data/modules/<id>.dbsmod przez .tmp + rename, komunikat „zainstalowano / zaktualizowano z X do Y / wgrano ponownie”); Modules::download($id) — License::api('module'), License::refresh() (token z aktualną listą), instalacja z pliku tymczasowego, wyczyszczenie module_catalog; Modules::remove($id); Modules::installed() (pamięć w obrębie żądania, paczki z błędem pokazywane z komunikatem).
Katalog: Modules::catalog($fresh) — License::api('modules'), wynik przycinany i oczyszczany (strip_tags, długości, buy_url tylko http(s)), zapisywany w module_catalog na 12 h; przy błędzie sieci zwracany zapisany katalog z komunikatem. Modules::buyUrl($id) = buy_url?modul=<id>&klucz=<klucz>.
Kreator (views/admin/templates.php, sekcja #moduly; trasa POST admin/modules, act = upload (pole file, rozszerzenie .dbsmod/.zip) \| install \| remove \| refresh, CSRF, tylko administrator, zablokowane dla kont demo) — karty #mod-<id> łączą katalog i zainstalowane paczki: status (kupiony, zainstalowany X, aktualizacja dostępna, brak uprawnienia z powodem), przyciski Zainstaluj / Zaktualizuj do X / Kup moduł ↗ / Usuń moduł; formularz „Wgraj moduł z pliku”; ostrzeżenie trybu autora. Karty szablonów z modułów mają znaczek „moduł”.
Narzędzie autora tools/module.php (CLI, w zainstalowanym dataBAS; paczki niepodpisane, tylko module.json):
| Polecenie | Działanie |
|---|---|
z-tabel <id> "<Nazwa>" t1,t2 [--dane] [plik] |
definicja z metadanych tabel instalacji: pola i opcje, formuły, warunki, akcje, raporty tych tabel, szablony wydruku; powiązania tylko między wybranymi tabelami (kolejność zależności); --dane — do 30 rekordów na tabelę z odwołaniami @tabela#n |
z-szablonu <klucz> <id> [plik] |
z szablonu wbudowanego (Templates::all()) |
pakuj <module.json> [plik] |
z ręcznie napisanej definicji |
sprawdz <plik> |
podsumowanie, podpis, przydział i uprawnienie w tej instalacji |
json <plik> |
wypisuje module.json |
Każde polecenie zapisujące przepuszcza definicję przez Modules::validate(). Podpis i przydział dodaje sklep przy każdym pobraniu (DBSLIC_Modules::package) — klucz prywatny nigdy nie trafia do dataBAS.
Sklep — dbs-licencje 1.2 (includes/class-modules.php, tabele <wp>dbslic_modules i <wp>dbslic_license_modules, DB_VERSION 2):
- wgrywanie (Licencje dataBAS → Moduły): sklep przechowuje tylko
module.json(uploads/dbslic-modules/, chronione), nowa wersja zastępuje starą z zachowaniem ceny; pola: cena (PLN, 0 = bezpłatny),stripe_price, kolejność, w sprzedaży; - REST
dbs-license/v1/modules(katalog z flagąowned, limit zapytań, nieaktywne moduły widoczne dla właścicieli),…/module(aktywowana domena + posiadanie → podpisana paczka, wpis w historii licencji),…/checkoutz parametremmodule(Stripe Checkout z metadanymimodule,module_license; bezpłatny — przydział od razu), webhookcheckout.session.completed→DBSLIC_Modules::grant(bez duplikatów) + e-mail z linkiem; - link pobrania
?dbslic_dl=mod&l=&m=&t=podpisany HMAC; ładunek tokenu licencji zawieramodules = DBSLIC_Modules::owned(id); - shortcode
(katalog, zakup do klucza,Hotel i pensjonat
wersja 1.0.0 · dataBAS 2.9.0+Obsługa małego hotelu lub pensjonatu: pokoje, goście, rezerwacje, meldunki, usługi, płatności, sprzątanie i usterki.
- 10 powiązanych tabel z przykładowymi danymi
- Rezerwacje i statusy przyjazdu, pobytu oraz wyjazdu
- Usługi dodatkowe i ewidencja płatności
- Sprzątanie i obsługa usterek
- 9 raportów i 4 szablony wydruków (w tym PDF)
- Akcje statusów i pola wyliczane
39 zł
MagazynPRO — gospodarka magazynowa
wersja 1.0.0 · dataBAS 2.9.0+Kartoteka towarów, magazyny, kontrahenci, PZ/WZ, inwentaryzacja, raporty i wydruki. Wersja demonstracyjna do dalszego wdrożenia.
- 10 powiązanych tabel i dane demonstracyjne
- Przyjęcia i wydania PZ/WZ z pozycjami i statusami
- Kartoteka SKU, EAN, cen i progów minimalnych
- Arkusze inwentaryzacji i różnice spisowe
- 10 raportów i 4 szablony wydruku PDF
- Przyciski do zatwierdzania dokumentów
44 zł
Rezerwacje sal i sprzętu
wersja 1.0.0 · dataBAS 2.9.0+Rezerwacje sal, pracowni i sprzętu: kto, kiedy, w jakich godzinach i po co. Potwierdzenie rezerwacji do wydruku lub wysłania e-mailem jako PDF.
- Przyciski „Potwierdź” i „Odwołaj” przy rezerwacji
- Potwierdzenie rezerwacji z PDF wysyłane e-mailem do rezerwującego
- Liczba rezerwacji przy każdej sali i sprzęcie
- Raporty: rezerwacje według sal i w miesiącach
Zobacz przykład działania modułu „Rezerwacje sal i sprzętu” (otwiera się w nowej karcie) ↗
19 zł
Salon samochodowy
wersja 1.0.0 · dataBAS 2.9.0+Zarządzanie salonem samochodowym: oferta pojazdów, klienci, sprzedawcy, jazdy próbne, rezerwacje oraz sprzedaż.
- Kartoteka samochodów z numerami VIN, cenami i zdjęciami
- Klienci i sprzedawcy, połączone rekordy
- Jazdy próbne z terminami i statusami
- Rezerwacje samochodów i zaliczki
- Oferty, sprzedaż, płatności i wydanie pojazdu
- Przyciski zmiany statusu, raporty i wydruki
Zobacz przykład działania modułu „Salon samochodowy” (otwiera się w nowej karcie) ↗
29 zł
SerwisPRO – naprawy i zlecenia
wersja 1.0.0 · dataBAS 2.9.0+Obsługa serwisu urządzeń, AGD, telefonów, komputerów i pojazdów: klienci, sprzęt, zlecenia, pracownicy, części, czynności, płatności i wizyty.
- 9 powiązanych tabel i historia obsługi klienta
- Przepływ statusów zleceń i przyciski akcji
- Rejestr robocizny oraz zużytych części z wyliczaniem wartości
- 7 raportów: obciążenie, statusy, sprzedaż, wpłaty i magazyn
- 4 szablony wydruków: przyjęcie, wycena, wydanie, wpłata
- Dane demonstracyjne dla 4 klientów, 4 urządzeń i 4 zleceń
29 zł
Zgłoszenia usterek i awarii
wersja 1.0.0 · dataBAS 2.9.0+Zgłaszanie i obsługa usterek w szkole lub firmie: co się zepsuło, gdzie, jak pilne, kto naprawia i ile to kosztowało. Termin naprawy liczy się sam z priorytetu, a zgłaszający dostaje protokół naprawy jako PDF.
- Termin naprawy liczony z priorytetu: awaria — 1 dzień, pilne — 3, normalne — 7, niskie — 14 dni
- Przyciski „Przyjmij”, „Rozpocznij”, „Zakończ” i „Odrzuć” — status i data zakończenia ustawiają się same
- Dni otwarcia i opóźnienie względem terminu liczone automatycznie
- Historia prac przy zgłoszeniu: wykonawca, czas i koszt — suma godzin i kosztów przy zgłoszeniu i przy miejscu
- Protokół naprawy z PDF wysyłany e-mailem do zgłaszającego
- Raporty: zgłoszenia według kategorii i w miesiącach, koszty napraw według miejsc
19 zł
Ceny brutto. Moduł przypisujemy do klucza licencji dataBAS — działa na wszystkich domenach tej licencji. Po płatności zainstalujesz go w dataBAS: Kreator → Szablony aplikacji → Moduły (albo z pliku z e-maila). Nie masz jeszcze licencji? Najpierw kup dataBAS.
?modul=i?klucz=wypełniają formularz), ustawieniemodules_page(celbuy_url), karta licencji: przyznaj / odbierz, panel klienta: lista modułów z linkami.
21. Bezpieczeństwo — przegląd mechanizmów
| Zagrożenie | Mechanizm | Gdzie |
|---|---|---|
| SQL injection | parametry PDO; identyfikatory tylko z metadanych + Db::q |
Db, wszystkie moduły |
| XSS | e() w widokach; Tpl::sanitize dla szablonów wydruku; Menu::sanitize; stopka bez HTML |
views/, Tpl, Menu |
| CSRF | token w sesji dla każdego POST; API — brak ciasteczek (klucz + token w nagłówkach) | index.php, helpers.php |
| Przejęcie sesji | HttpOnly, SameSite=Lax, Secure (HTTPS), regeneracja identyfikatora, unieważnienie po zmianie hasła/blokadzie |
bootstrap.php, Auth |
| Zgadywanie haseł | 8 prób / 15 min / IP (także API — po IP odwiedzającego), 5 prób kodu 2FA | Auth, auth_tfa |
| Pliki wykonywalne | biała lista rozszerzeń, czarna lista wykonywalnych, fileinfo, losowe nazwy, uploads/ zablokowany, wydawanie przez kontroler + CSP |
Form, data.php, .htaccess |
| Path traversal | realpath + kontrola prefiksu katalogu |
data_file_path |
| Otwarte przekierowania | data_safe_return (tylko wewnętrzne trasy) |
data.php |
| Wykonanie kodu z formuł | własny interpreter bez eval |
Expr |
| CSV injection | apostrof przed komórką zaczynającą się od =, +, -, @, tabulatora lub CR (poza liczbami) |
data_export |
| Clickjacking / MIME | X-Frame-Options, X-Content-Type-Options, Referrer-Policy |
bootstrap.php |
| Wyciek tokenów API | w bazie tylko SHA-256, wygasanie, hash_equals dla klucza |
api.php |
| Wyciek sekretów 2FA | kod QR generowany lokalnie; kody zapasowe jako skróty | Qr, Totp |
| Podrobienie licencji | podpis Ed25519 + RSA, klucz publiczny w license.pub |
License |
| Podrobiony lub zmieniony moduł, kod w module | podpis sklepu + SHA-256 module.json sprawdzane przy każdym odczycie; przydział do skrótu klucza; definicja bez kodu, ścisła walidacja i Expr::parse |
Modules |
| Dostęp do plików wewnętrznych | .htaccess w katalogach; reguły nginx w README |
22. Integracje z WordPressem
| Komponent | Rola | Powiązanie z dataBAS |
|---|---|---|
| databas-wp („dataBAS dla WordPress”) | tabele, aplikacja i logowanie dataBAS na stronach WP (shortcody , , , ) |
proxy REST wp-json/databas/v1/<akcja> → api.php; klucz API i token zostają na serwerze WP (transient), przeglądarka ma tylko identyfikator sesji (ciasteczko dbswp_sid + zapasowo nagłówek X-DBSWP-Session); WP wysyła X-Databas-Key, Authorization i X-Databas-Token, X-Databas-Client-IP |
| dbs-licencje („dataBAS — Licencje”) | sklep i serwer licencji (Stripe, plany, aktywacje, panel klienta), od 1.2 sprzedaż modułów | REST `dbs-license/v1/activate |
| dbs-konto („Konto klienta”) | logowanie/rejestracja klientów databas.pl, Panel klienta z licencjami, 2FA dla administratorów WP | korzysta z danych dbs-licencje; nie łączy się z dataBAS bezpośrednio |
| databas-theme | motyw databas.pl (dokumentacja, porównanie, demo, „Co nowego”, Centrum Pomocy, od 3.7.7 strona „Moduły” z ) |
treści inc/*-content.html aktualizowane przy wydaniu; od 3.7.7 istniejące strony „Co nowego” i „Dokumentacja” uzupełniane jednorazowo o brakujące rozdziały <h2> (dbas_content_sync) |
Zgodność wersji: wtyczka databas-wp 1.2.3+ wymaga dataBAS 2.3.2+ na hostingach usuwających nagłówek Authorization. Moduły: dataBAS 2.9+ i dbs-licencje 1.2+ (starszy sklep nie zna akcji modules/module — Kreator pokaże błąd katalogu, instalacja z pliku nadal działa dla paczek podpisanych).
23. Wdrożenie, aktualizacja, kopie zapasowe
Instalacja: wgraj katalog → zapis do data/, uploads/ (i jednorazowo katalogu głównego) → install.php (sprawdza wymagania, tworzy tabele systemowe, administratora, opcjonalnie przykładową bazę, podłącza istniejące tabele, zapisuje config.php) → usuń install.php.
Aktualizacja: podmień pliki bez config.php, data/ (w tym data/modules/ z kupionymi modułami), uploads/, custom/hooks.php i app/license.pub (jeśli masz własny). Migracje wykonają się same przy pierwszym żądaniu. W trybie demo po zmianach struktury: php tools/demo_reset.php zapisz.
Kopia zapasowa: Kreator → Kopie zapasowe (rozdz. 14.5) — cała aplikacja z kontami i plikami, automatycznie z harmonogramu; pobieraj pliki kopii poza serwer. Osobno zachowaj config.php i custom/. Paczka z Eksportu i importu (bez kont) służy do przenoszenia między MySQL a SQLite. Ręcznie: zrzut bazy (tabele z przedrostkiem systemowym i tabele aplikacji) + uploads/; dla SQLite plik z sqlite_path.
Cron (zalecany): */15 * * * * php /ścieżka/databas/tools/cron.php > /dev/null 2>&1 — gotowa linia w Kreatorze (Przypomnienia, Kopie zapasowe).
nginx: zablokuj app|views|data|uploads|custom|tools oraz config.php, config.sample.php, README.md (gotowe reguły w README).
Paczka do sprzedaży: zip katalogu databas/ bez config.php, data/*.sqlite*, data/transfer/, data/backups/, data/modules/, data/cron.lock; z aktualnym app/license.pub sklepu.
24. Rozszerzanie systemu — przepisy
| Zadanie | Kroki |
|---|---|
| Nowa trasa / ekran | wpis w $routes (index.php) → funkcja moduł_akcja() w app/controllers/ → widok w views/ → jeśli publiczny: $publicRoutes; jeśli demo ma ograniczenia: Demo::adminBlock; teksty dla użytkownika przez t() |
| Nowy typ pola | field_types() (helpers.php) → Db::sqlType() → Form::input(), Form::normalize(), Form::display()/text()/plain() → Import::convert() → Meta::guessType() (jeśli da się wykryć) → API schema (wtyczka WP) |
| Nowa opcja pola | biała lista w admin_field() → formularz w views/admin/field.php → użycie w Form/Rules → tabela 8.3 |
| Nowa funkcja w formułach | Expr::FUNCS (liczba argumentów) → case w interpreterze → ściąga w opcjach pola → tabela w rozdz. 12 |
| Nowa opcja tabeli | Meta::setTopt($t, 'klucz', …) w admin_automation/admin_tableSave → odczyt Meta::topt → tabela 8.4 |
| Nowe ustawienie globalne | formularz views/admin/settings.php → zapis w admin_settings() (właściwa sekcja; uwaga na blokady demo sekcji api/system) → tabela 8.5 |
| Nowa tabela systemowa / kolumna | migracja (rozdz. 7.3) → Db::internalTables() → uwzględnienie w Demo::restore (co zachować) |
| Nowe zdarzenie | wywołanie Events::before/after w miejscu operacji → opis w rozdz. 13 i custom/hooks.example.php |
| Nowa akcja API | case w api.php (GET/POST w $writeActions), uprawnienia przez api_table()/Auth::can → tabela 17.2 → obsługa w databas-wp (GET_ACTIONS/POST_ACTIONS w class-client.php) |
| Nowy język | rozdz. 16 |
| Nowy szablon aplikacji | rozdz. 20 |
| Nowa opcja formularza publicznego | PublicForm::DEFAULTS → PublicForm::clean() (oczyszczanie, także importu) → views/admin/form.php → użycie w submit/open/widoku publicznym; rozdz. 14.2 |
| Nowa opcja tabeli / ustawienie w przenoszeniu | dopisz klucz w Transfer::structure() (eksport), oczyszczanie w Transfer::clean() / cleanOpt() (import), opis w rozdz. 14.1; identyfikatory grup, raportów i pulpitów zawsze przez mapowanie |
| Nowe zadanie harmonogramu | metoda runDue() w klasie zadania (sama decyduje, czy jej pora; szybki powrót, gdy nie) → wywołanie w osobnym try w Scheduler::tick() → warunek „jest co robić” w maybeWeb() → opis w dzienniku (views/admin/backups.php) i w tools/cron.php stan |
| Nowa właściwość CSS w PDF | rozwinięcie skrótu w HtmlPdf::expand() → (jeśli dziedziczona) HtmlPdf::INHERIT → użycie w układzie (layoutBlock / layoutLines / placeRow…) → tabela w rozdz. 14.7; sprawdzić na wydrukach wszystkich szablonów (tpl + PDF) |
| Nowa opcja szablonu wydruku | klucz w Tpl::cleanSettings() (jedyne miejsce czyszczenia — edytor, instalator szablonów, import) → pole w views/print/template.php → zapis w print_template() → użycie w Tpl::send / print_peek / widokach; rozdz. 14.6 |
| Nowa funkcja w formułach (kwoty, teksty) | Expr::FUNCS + case w Expr::fn() → ściąga w edytorze wydruku/pól → tabela w rozdz. 12 |
| Nowy znacznik przypomnień | Reminders::vars() (albo message() dla listy zbiorczej) → ściąga w views/admin/reminder.php → rozdz. 14.3 |
| Nowa tabela systemowa a kopie | dopisanie do Db::internalTables() wystarcza — Backup::tables() obejmie ją automatycznie |
| Własna logika klienta | wyłącznie custom/hooks.php (przetrwa aktualizacje) |
| Nowy moduł do sprzedaży | zbuduj aplikację w Kreatorze na serwerze testowym → php tools/module.php z-tabel <id> "<Nazwa>" t1,t2 [--dane] → sprawdz → (opcjonalnie popraw json i pakuj) → test z modules_dev → wgraj do sklepu, ustaw cenę → test zakupu i instalacji; nowa wersja = wyższe version |
| Nowa opcja pola lub typ w modułach | dopisz klucz do Modules::OPTS / Modules::COLS (biała lista) i ewentualną walidację w Modules::validate; tools/module.php z-tabel eksportuje tylko klucze z listy |
25. Testowanie
Testy automatyczne są uruchamiane na kopii aplikacji w środowisku testowym (PHP wbudowany serwer + SQLite i MariaDB, przeglądarka Chromium przez Playwright, axe-core do dostępności). Każdy zestaw działa na obu bazach.
| Zestaw | Zakres |
|---|---|
| e2e / e2e_reg | instalacja, Kreator, pola, lista, formularz, eksport, uprawnienia (SQLite / MySQL) |
| e2eprefix, e2erename | filtr przedrostków, zmiana nazwy tabeli |
| e2e_md | powiązane rekordy (master/details) |
| e2ehide, e2elook | ukrywanie tabel, wygląd i menu |
| api_test | wszystkie akcje API, kody błędów, tokeny |
| e2es1 … e2es4 | dziennik, blokady, duplikaty, powiadomienia, haki; formuły, warunki, akcje, filtry wierszy; raporty, pulpity, import, wydruki; 2FA, języki |
| demotest, demoadmin_test | tryb demo: ograniczenia, przywracanie wzorca i struktury |
| tpl_test | instalacja wszystkich szablonów aplikacji, formuły, akcje, raporty |
| lic | licencje: wersja próbna, aktywacja, odświeżanie, blokada, przeniesienie |
| wpe2e, wps4 | wtyczka databas-wp z prawdziwym dataBAS |
| pf_test | formularze publiczne: edytor, pola i wartości ukryte, warunki na żywo bez logowania, wymagalność i zgoda, e-maile, token, pułapka, limity IP, XSS, osadzanie między domenami (bez zapętlenia wysokości), terminy i limit zgłoszeń, wersja angielska, eksport/import formularza |
| tr_test | eksport i import: paczka ZIP z plikami SQLite→MySQL→SQLite i MySQL→SQLite→MySQL, tryby dodaj/zastąp, pojedyncze JSON/CSV, przemapowanie grup/raportów/pulpitów, wartości specjalne w CSV, odporność na złośliwe paczki |
| rm_test | przypomnienia (okna przed / w dniu / po, granice, powtarzanie, zmiana terminu, lista zbiorcza z grupami, brak adresu, próba, odbiorca przez powiązanie, link z pełnym adresem — przez lokalny serwer SMTP), harmonogram (tools/cron.php, ruch na stronie co 5 min, tryb „tylko cron”, godzina wysyłki), kopie zapasowe (zawartość ZIP, pobieranie i ochrona ścieżek, przywracanie danych, tabel i plików z kopią bezpieczeństwa, wgrywanie, kopia z innej bazy, rotacja, co tydzień), axe, telefon |
| fv_test | (2.8) PDF: pobranie z paska (nagłówki, nazwa), poprawność qpdf, treść pdftotext (numer, pozycje, kwoty, słownie, pieczątka), fonty osadzone jako podzbiór z ToUnicode, rozmiar, PDF listy (strona na rekord), PDF wydruku standardowego, menu karty rekordu, przycisk w podglądzie, załącznik PDF w e-mailu, 403 bez prawa „szczegóły”; szablon „Faktury”: instalacja i dane, sumy z VAT i „zw.”, termin płatności, wydruk (pozycje, kwoty, słownie, pieczątka, tryb ramki), podgląd po najechaniu (pojawianie się, zostawanie nad panelem, chowanie, kolejne wiersze), wysyłka z panelu i z karty rekordu przez lokalny serwer SMTP (HTML + tekst, temat i wstęp ze znacznikami, status i data wysłania, warunek „tylko gdy”, dziennik zmian), brak adresu, CSRF, klawiatura i Escape, telefon, unikalny numer, nowa pozycja i przeliczenie, edytor ustawień, uprawnienia (bez details / bez edit), instalacja z przedrostkiem, axe |
| mod_test | (2.9) moduły z prawdziwym sklepem WP (dbs-licencje 1.2) i atrapą Stripe: odrzucenie niepodpisanego, wgranie do sklepu i ustawienia, katalog w Kreatorze i link „Kup moduł”, strona (axe), checkout + webhook (bez duplikatów), e-mail, strona podziękowania, blokada ponownego zakupu, lista modules w tokenie, instalacja jednym kliknięciem i aplikacja z modułu (dane, formuły, wydruk, PDF), odrzucenie: zmienionej paczki, cudzego podpisu, zakazanej funkcji, pliku nie-ZIP, paczki innej licencji, złego klucza; pobranie z linku w e-mailu i instalacja z pliku, odebranie i ponowne przyznanie, aktualizacja 1.1.0 (istniejąca aplikacja bez zmian), moduł bezpłatny, katalog dla właścicieli nieaktywnych modułów, usuwanie, panel klienta, z-tabel, sprawdz, tryb modules_dev |
| th377 | motyw 3.7.7: strona /moduly/ (tworzenie, ustawienie modules_page, karty, wyróżnienie z ?modul=&klucz=, ciemny/jasny, telefon, axe), stopka, rozdział w dokumentacji, „Co nowego”, jednorazowe uzupełnienie stron z treścią 3.7.6 i blokami Gutenberga (kolejność, ręczne zmiany, bez duplikatów), instrukcje Centrum Pomocy (axe), strona bez wtyczki |
| a11y | WCAG 2.1 AA (axe) dla głównych ekranów |
Ręczna lista przed wydaniem: instalacja od zera (SQLite i MySQL), aktualizacja z poprzedniej wersji na kopii produkcyjnej bazy, logowanie z 2FA, wtyczka WP na hostingu z FastCGI, wersja demo (zapis i przywrócenie wzorca), telefon (390 px) i ciemny motyw.
26. Wydanie nowej wersji — lista kontrolna
DBS_VERSIONwapp/bootstrap.php.- Jeśli zmienia się schemat —
Schema::VERSION+ migracja. README.md: nagłówek wersji, opis nowych funkcji, Historia zmian.lang/en.php— nowe teksty interfejsu użytkownika.config.sample.php— nowe klucze konfiguracji.- Testy (rozdz. 25) na SQLite i MySQL; regresja całości.
- Paczka:
databas.zipzapp/fonts/(fonty i licencja OFL) itools/(ztools/modules/), bezconfig.php,data/*.sqlite*,data/transfer/,data/backups/,data/modules/.
Nowe moduły wydaje się bez nowej wersji dataBAS (rozdz. 20.1) — chyba że wymagają nowych typów pól lub funkcji (requires).
- Strona databas.pl: wpis w „Co nowego” (
databas-theme/inc/changelog-content.html) i Dokumentacja (inc/docs-content.html); w razie potrzeby wersja motywu. - Zgodność z wtyczką databas-wp (nowe akcje API?) — rozdz. 22.
- Ten dokument: wersja systemu w nagłówku, zmienione rozdziały, wiersz w historii dokumentu.
27. Znane ograniczenia i dług techniczny
- Brak autoloadera — nowe klasy trzeba dopisać do
requirewbootstrap.php(kolejność zależności). - Kontrolery jako funkcje globalne z przedrostkiem modułu (nie klasy) — konwencja nazw jest jedyną ochroną przed kolizją.
- PDF z serwera (
HtmlPdf) obsługuje podzbiór HTML/CSS (rozdz. 14.7) — złożone układy zfloat/positionwyglądają inaczej niż w przeglądarce; wiersz tabeli wyższy niż strona nie jest dzielony. - Kreator i nazwy tabel/pól nie są tłumaczone.
- Formuły przeliczane synchronicznie;
recomputeTableograniczone do 20 000 wierszy na wywołanie. - Import do 5000 wierszy naraz; wydruk listy do 500 rekordów.
- Kontrola licencji w czystym PHP może zostać usunięta przez osobę z dostępem do kodu (rozważ ionCube/SourceGuardian dla
License.php,bootstrap.php,index.php,api.php). - Brak aktualizacji jednym kliknięciem i wysyłki kopii zapasowych poza serwer (FTP/chmura) — kandydaci na kolejne wersje.
- Moduły to wyłącznie definicje — nie dodają nowych typów pól, funkcji
Exprani widoków; aktualizacja modułu nie migruje aplikacji utworzonych ze starszej wersji. Instalacja jednym kliknięciem wymaga dostępu serwera do sklepu (inaczej plik z e-maila). Odebranie modułu działa dopiero po odświeżeniu tokenu licencji, a zainstalowana aplikacja (tabele i dane) zostaje. - Harmonogram bez crona zależy od ruchu na stronie; na Apache mod_php (bez
fastcgi_finish_request) zadanie wydłuża jedno żądanie. - Przypomnienia liczą „dziś” według strefy czasowej serwera PHP;
datetimeporównywane jak data (pole ≥ dziś 00:00). - Przywrócenie kopii z MySQL nie jest atomowe (DDL zatwierdza transakcje) — zabezpieczeniem jest kopia bezpieczeństwa tworzona wcześniej.
28. Historia dokumentu
| Wersja dokumentu | Data | Wersja dataBAS | Zmiany |
|---|---|---|---|
| 1.0 | 17.10.2026 | 2.3.2 | Pierwsze wydanie dokumentacji technicznej. |
| 1.1 | 17.10.2026 | 2.4.0 | Eksport i import (rozdz. 14.1), Transfer.php w strukturze, wymagania (zip), testy, kopia zapasowa, przepis rozszerzania. |
| 1.2 | 17.10.2026 | 2.5.0 | Formularze publiczne (rozdz. 14.2), migracja 9, ustawienie form_secret, wyjątki w strażnikach index.php, opcje form/skip_scope w zapisie, $ctx['form'] w hakach, testy. |
| 1.3 | 17.10.2026 | 2.6.0 | Przypomnienia (14.3), harmonogram i tools/cron.php (14.4), kopie zapasowe (14.5), migracja 10, ustawienia cron_*, backup_*, seen_base_url, blokada demo, przepisy, testy rm_test, paczka bez data/backups/. |
| 1.4 | 18.10.2026 | 2.7.0 | Szablon aplikacji „Faktury” (rozdz. 20), podgląd i wysyłka wydruków e-mailem (14.6), trasy print/peek i print/send, Tpl::cleanSettings, poczta HTML (13.4), akcja mail w dzienniku (13.2), funkcje KWOTA i SLOWNIE (12), poprawka nazw tabel w formułach przy instalacji z przedrostkiem, testy fv_test, przepisy. |
| 1.5 | 18.10.2026 | 2.8.0 | PDF na serwerze (rozdz. 14.7: Pdf, PdfFont, HtmlPdf), trasy &pdf=1, ustawienie email_pdf, załączniki w Mailer, wymagania (gd, zlib), struktura (app/fonts), testy, ograniczenia. |
| 1.6 | 18.10.2026 | 2.9.0 | Moduły (nowy rozdz. 20.1: format .dbsmod, podpis i przydział, uprawnienie, walidacja, Kreator, tools/module.php, sklep dbs-licencje 1.2), protokół licencji (modules, module, pole modules w tokenie), config.php → modules_dev, ustawienie module_catalog, struktura (Modules.php, tools/module.php, data/modules/), wymagania (zip), bezpieczeństwo, integracje (motyw 3.7.7), przepisy, testy mod_test i th377, paczka i aktualizacja bez data/modules/, ograniczenia. |
| 1.7 | 18.10.2026 | 2.9.1 | Podgląd wydruku (14.6): nie otwiera się nad elementami interaktywnymi wiersza (a, button, input, select, textarea, label, summary, details, form) i chowa się, gdy kursor na nie wjedzie (o ile nie jest przypięty). Drugi przykładowy moduł zgloszenia-usterek w strukturze. |
Nic nie znaleziono. Spróbuj innego słowa albo zajrzyj do pytań i odpowiedzi.
Ostatnia zmiana: 18 października 2026
