Przejdź do treści

Dokumentacja techniczna

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

  1. Założenia i ograniczenia
  2. Wymagania serwera
  3. Struktura katalogów
  4. Cykl życia żądania
  5. Konfiguracja — config.php
  6. Warstwa bazy danych — Db
  7. Tabele systemowe i migracje
  8. Metadane: tabele, pola, opcje, ustawienia
  9. Uwierzytelnianie, sesje, 2FA
  10. Uprawnienia i dostęp do wierszy
  11. Zapis i usuwanie rekordów, pliki
  12. Język wyrażeń — Expr
  13. Zdarzenia, haki, dziennik, blokady, powiadomienia
  14. Raporty, pulpity, import, wydruki
  1. Wygląd i menu
  2. Tłumaczenia — I18n
  3. API JSON — api.php
  4. Licencjonowanie — License
  5. Tryb demonstracyjny — Demo
  6. Szablony aplikacji — Templates
  1. Bezpieczeństwo — przegląd mechanizmów
  2. Integracje z WordPressem
  3. Wdrożenie, aktualizacja, kopie zapasowe
  4. Rozszerzanie systemu — przepisy
  5. Testowanie
  6. Wydanie nowej wersji — lista kontrolna
  7. Znane ograniczenia i dług techniczny
  8. 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 z uploads/).
  • 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 final ze statycznymi metodami (moduły), kontrolery jako funkcje moduł_akcja(), widoki jako szablony PHP w views/, 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)

  1. app/bootstrap.php
  2. stałe DBS_VERSION, ROOT; display_errors=0; strefa czasu; mb_internal_encoding('UTF-8');
  3. require wszystkich klas (bez autoloadera — kolejność ma znaczenie, patrz plik);
  4. brak config.php → przekierowanie do install.php;
  5. wczytanie konfiguracji ($DBS_CONFIG), debug → display_errors=1, timezone;
  6. (tylko WWW) sesja: nazwa DATABAS_<hash ścieżki>, HttpOnly, SameSite=Lax, Secure przy HTTPS (także X-Forwarded-Proto: https); nagłówki X-Content-Type-Options, X-Frame-Options: SAMEORIGIN, Referrer-Policy: same-origin;
  7. Db::connect() — przy błędzie 500 (JSON dla API);
  8. Schema::migrate() — migracje (raz po aktualizacji);
  9. Meta::applyTableFilter() — filtr przedrostków tabel;
  10. Demo::maybeReset() — automatyczne przywracanie danych demo;
  11. Audit::purge() — retencja dziennika (raz na dobę);
  12. Events::loadHooks() — custom/hooks.php.
  13. Routing: $routes w index.php mapuje r → [moduł, akcja]; nieznana trasa → 404. Kontroler to funkcja moduł_akcja() z app/controllers/<moduł>.php.
  14. Strażnicy (w tej kolejności):
  15. brak zalogowania → login (wyjątki: login, tfa, brand/logo, formularze publiczne form/form/live; przy dostępie publicznym — trasy z $publicRoutes);
  16. wymagane 2FA, a nieskonfigurowane → tfa_setup;
  17. licencja nieważna → administrator do admin/license, reszta: strona locked z kodem 402;
  18. konto demonstracyjne → Demo::adminBlock(), blokada plików, blokada Kreatora dla zwykłych kont demo, blokada tfa_setup;
  19. moduł admin tylko dla Auth::isAdmin() (403);
  20. każdy POST → csrf_check() — poza formularzami publicznymi ($publicForm), które chroni podpisany token formularza (rozdz. 14.2).
  21. 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 bloki if ($cur < N) i zapisuje Schema::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 — w fields. 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 rozpakowanym opt = JSON z options), 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 ustawieniu relations_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 = 8 w Auth::WINDOW = 900 s na adres IP (login_attempts). Udane logowanie czyści licznik IP.
  • Sesja: Auth::loginUser() regeneruje identyfikator sesji i zapisuje w niej uid oraz odcisk hasła (upw — 12 ostatnich znaków skrótu). Auth::user() przy każdym żądaniu sprawdza active = 1 i 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ń przez users.totp_last, 10 kodów zapasowych (tylko skróty SHA-256 w recovery_codes). Klucz QR generuje Qr::svg() lokalnie.
  • Przepływ WWW: Auth::attempt() zwraca 'tfa' → $_SESSION['tfa_uid'] (+ czas, odcisk hasła) → trasa tfa (5 prób) → loginUser.
  • Wymuszanie: Totp::required($u) wg tfa_required; strażnik w index.php kieruje do tfa_setup.
  • Wyjątek demo (2.3.1): konto demonstracyjne niebędące is_admin nie jest pytane o kod (hasło demo jest publiczne); prawdziwi administratorzy zawsze podają kod.
  • API: logowanie przez a=login (opcjonalnie otp), token 64 hex, w bazie tylko SHA-256, ważność API_TOKEN_TTL = 8 h przedłużana przy użyciu.
  • CSRF: csrf_token() w sesji, csrf_field() w formularzach, csrf_check() dla każdego POST w index.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 + flaga own_only.
  • is_admin = 1 (oraz konto z demo.admins) — pełny dostęp, bez filtrów wierszy.
  • Goście (bez logowania): specjalna grupa Auth::GUEST_GROUP, tylko Auth::GUEST_ACTIONS = browse, details, export, i tylko gdy public_access = 1.
  • Szablon uprawnień (perm_template, wiersz „✦ Nowe tabele”) — nadawany automatycznie nowo utworzonym/wykrytym tabelom (Auth::applyTemplate).

10.2 Auth::can($table, $action)

  1. tabela musi istnieć w metadanych;
  2. gość → tylko przy dostępie publicznym, tylko akcje gościa, tylko tabele włączone;
  3. tabela wyłączona → tylko administrator;
  4. details/edit/delete wymagają klucza głównego (bez PK tabela jest tylko do odczytu);
  5. administrator → true;
  6. bez can_browse → false; w przeciwnym razie flaga can_<akcja> (suma grup).

10.3 Ograniczenia wierszy

  • Auth::ownOnly($t) — gdy grupa ma own_only i tabela ma owner_field: tylko rekordy, których właścicielem jest użytkownik.
  • RowFilter — reguły z row_filters dla 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 zapisie RowFilter::allows() — rekord nie może wyjść poza zakres; RowFilter::defaults() podpowiada wartości przy dodawaniu.
  • Wspólne miejsce: data_scope($meta, &$params) w controllers/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):

  1. edycja: rekord musi istnieć w zakresie użytkownika (data_record — z filtrami) → 404;
  2. 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);
  3. pola warunkowe (Cond::state): ukryte nie są zapisywane ani wymagane; require_if dodaje wymagalność; brak wartości → „pole wymagane”; błędy → 422 z mapą errors;
  4. formuły (Formula::compute) po uwzględnieniu ukrytych pól;
  5. filtr wierszy RowFilter::allows → 403;
  6. blokada rekordu przez kogoś innego (Locks::heldByOther) → 423; konflikt wersji (opts.ver ≠ data_row_version($old)) → 409 conflict;
  7. duplikaty (data_find_duplicates): block → 422, warn bez opts.dup_ok → 409 dup_warn;
  8. opts.dry → koniec (tryb „Sprawdź” importu);
  9. haki before_insert / before_update (mogą zmienić $data albo przerwać komunikatem) → 422; ponowne przeliczenie formuł;
  10. zapis plików (data_store_upload) i rekordu; przy błędzie — usunięcie zapisanych plików, 500;
  11. usunięcie plików zastąpionych/skasowanych, zwolnienie blokady;
  12. 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 lista Form::IMAGE_EXT / Form::FILE_EXT (lub extensions bez rozszerzeń wykonywalnych), fileinfo + weryfikacja obrazków.
  • Zapis: uploads/<tabela>/<RRRR>/<MM>/<16 hex>_<nazwa-slug>.<ext>, uprawnienia 0644.
  • Wydawanie: tylko trasa data/file (i api.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 poza uploads/ (realpath); w przeglądarce otwierają się tylko obrazki (JPEG/PNG/GIF/WEBP) i PDF — reszta jako application/octet-stream + attachment; nagłówek Content-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) w custom/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 albo false przerywa operację; wyjątek DomainException → 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 / delete akcja mail — Audit::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_restore przyjmuje tylko update).

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 w reports.config, czyszczona przez Report::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ędnieniem data_scope i 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 w data/.
  • Tpl (szablony wydruku, tabela templates): 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:

  1. tabele w kolejności zależności powiązań (Transfer::order): brakujące tworzone przenośnym DDL (Transfer::createTable: autoinkrementacja → Db::idColumn, typy z Db::sqlType, typ kolumny powiązania zgodny z kluczem tabeli docelowej, klucze obce ON 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;
  2. sprawdzenie powiązań (brak tabeli docelowej → pole jako liczba + uwaga) i wyrażeń (Expr::validate; niepoprawne → usunięte + uwaga);
  3. grupy po nazwie (brakujące tworzone tylko z częścią perms);
  4. automatyzacja (cleanOpt: tylko istniejące pola, poprawne wyrażenia, grupy → identyfikatory);
  5. dane (importRows): klucze obce wyłączone na czas wczytywania, partie po 500 w transakcji, zachowane identyfikatory; tryby append (konflikt klucza → pominięcie), replace (najpierw DELETE), none; kolumny autora/właściciela z nieistniejącym kontem → importujący administrator; tak/nie normalizowane; limit MAX_ROWS = 200 000 na tabelę; potem Formula::recomputeTable;
  6. pliki z files/ — tylko ścieżki tabela/RRRR/MM/nazwa.ext (Transfer::safeRel), dozwolone rozszerzenia, tabele z importu, bez nadpisywania;
  7. 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_dashboard przemapowany).

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) — blokada flock(data/cron.lock, LOCK_EX | LOCK_NB) (równoległe uruchomienie zwraca null), zapis cron_last/cron_source, potem Reminders::runDue() i Backup::runDue() (każde w osobnym try — błąd jednego nie zatrzymuje drugiego), wpis do cron_log tylko, gdy coś się wydarzyło.
  • Z ruchu na stronie (maybeWeb, tryb auto): nie w CLI; gdy od cron_last minęło ≥ WEB_EVERY = 300 s 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 z PHP_BINDIR, ROOT).
  • Scheduler::status()['stale'] — brak uruchomienia od 3 h ⇒ ostrzeżenie w ramce views/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()):

  1. check() — ta sama baza (driver), ten sam prefix, schema ≤ Schema::VERSION.
  2. Kopia bezpieczeństwa przed (z plikami, jeśli kopia je zawiera).
  3. Wyłączenie kluczy obcych (FOREIGN_KEY_CHECKS = 0 / PRAGMA foreign_keys = OFF), DROP + DDL wszystkich tabel z kopii, DROP tabel aplikacji, których w kopii nie ma (tabele z przedrostkiem systemowym zostają).
  4. Dane strumieniowo z getStream(), w transakcjach po 1000 wierszy (DDL jest wcześniej — w MySQL DDL zatwierdza transakcję).
  5. Pliki: rozpakowanie do data/backups/.restore-* z odrzuceniem niebezpiecznych ścieżek (.., ścieżki bezwzględne, \, :, .php*, .phtml, .phar, .htaccess), potem Demo::syncDir() — uploads/ dokładnie jak w kopii.
  6. Wyczyszczenie pamięci podręcznej (Db::forgetTables, Meta::forgetSettings, Meta::flush) i Schema::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.

15. Wygląd i menu

  • Brand: nazwa, logo (plik w uploads/_brand/, wydawany przez trasę brand/logo — działa też na ekranie logowania), tryb i wysokość logo, kolor przewodni, stopka (mini-markdown: [tekst](adres), **pogrubienie**, {rok}, {nazwa}).
  • Menu: automatyczne (Menu::auto() — tabele pogrupowane wg topt menu_group) albo własne (menu JSON). Element: {type: home|table|url|group, label, icon, visible: all|users|guests|admins, target (tabela), url, new_tab, children}; Menu::MAX_DEPTH = 3, MAX_ITEMS = 200; Menu::sanitize() + Menu::validUrl() (tylko http(s)://… albo wewnętrzne index.php?…, maks. 500 znaków). Układy (menu_layout): left, right, top, tree, icons, drawer.

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łownik lang/<kod>.php zwraca 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.php jako lang/xx.php, dopisz 'xx' => 'Nazwa' w I18n::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 = 14 od install_date), aktywna, okres przejściowy (GRACE_DAYS = 14 po 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 z v = 1, plan, domena, ważność (30 dni), od 2.9 modules — 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 tylko admin/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ście accounts/admins; Demo::isAdminAccount() — konto z admins (dostaje Auth::isAdmin()).
  • Ograniczenia (index.php + Demo::adminBlock): licencja, kopie zapasowe (admin/backups w całości), sekcje ustawień api/system (poczta, bezpieczeństwo, integracja), konta administratorów i konta demo (Demo::protectedUser), nadawanie is_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 do mail_log.
  • Wzorzec: Demo::snapshot() → data/demo-snapshot.json.gz (DDL + dane tabel systemowych i tabel aplikacji zgodnych z filtrem, kopia uploads/ w data/demo-snapshot-uploads).
  • Przywracanie: Demo::restore() — odtwarza dane i strukturę (usuwa nowe tabele, przebudowuje tabele ze zmienionymi kolumnami). Zachowuje: użytkowników is_admin = 1, ustawienia license_*, install_date, api_key, api_enabled, api_tokens, login_attempts, mail_log. Demo::maybeReset() — przy żądaniu po upływie reset_hours, z blokadą pliku data/demo-reset.lock; znacznik data/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]:

  1. niepodpisana → tylko z modules_dev (sposób dev);
  2. requires > DBS_VERSION → odmowa;
  3. brak klucza lub tokenu licencji → odmowa;
  4. id na liście modules w tokenie → licencja;
  5. przydział dla skrótu klucza tej instalacji i (token bez pola modules lub grant.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), …/checkout z parametrem module (Stripe Checkout z metadanymi module, module_license; bezpłatny — przydział od razu), webhook checkout.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 zawiera modules = DBSLIC_Modules::owned(id);
  • shortcode

    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.

    (katalog, zakup do klucza, ?modul= i ?klucz= wypełniają formularz), ustawienie modules_page (cel buy_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 ,
Wczytywanie…
, ,
Wczytywanie…
)
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

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.

)
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

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.

(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

  1. DBS_VERSION w app/bootstrap.php.
  2. Jeśli zmienia się schemat — Schema::VERSION + migracja.
  3. README.md: nagłówek wersji, opis nowych funkcji, Historia zmian.
  4. lang/en.php — nowe teksty interfejsu użytkownika.
  5. config.sample.php — nowe klucze konfiguracji.
  6. Testy (rozdz. 25) na SQLite i MySQL; regresja całości.
  7. Paczka: databas.zip z app/fonts/ (fonty i licencja OFL) i tools/ (z tools/modules/), bez config.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).

  1. Strona databas.pl: wpis w „Co nowego” (databas-theme/inc/changelog-content.html) i Dokumentacja (inc/docs-content.html); w razie potrzeby wersja motywu.
  2. Zgodność z wtyczką databas-wp (nowe akcje API?) — rozdz. 22.
  3. 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 require w bootstrap.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 z float/position wyglą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; recomputeTable ograniczone 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 Expr ani 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; datetime poró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.

Ostatnia zmiana: 18 października 2026