From 352e6d9e2227ad6f71c1b030cd35aeda903f181b Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Mon, 6 Jul 2026 20:16:00 +0200 Subject: [PATCH] first commit --- .claude/memory/MEMORY.md | 5 + .gitignore | 126 + .htaccess | 30 + .paul/PROJECT.md | 21 + .paul/STATE.md | 35 + .paul/changelog/2026-07-06.md | 39 + .paul/codebase/impact_map.md | 36 + .paul/codebase/quality_risks.md | 19 + .paul/codebase/tooling_status.md | 16 + .paul/config.md | 46 + .../PLAN.md | 235 ++ .../SUMMARY.md | 92 + .../20260706-1430-modul-inwestycji/PLAN.md | 288 +++ .../20260706-1430-modul-inwestycji/SUMMARY.md | 88 + LICENSE | 22 + README.md | 69 + app/.htaccess | 6 + app/Common.php | 15 + app/Config/App.php | 202 ++ app/Config/Autoload.php | 92 + app/Config/Boot/development.php | 34 + app/Config/Boot/production.php | 25 + app/Config/Boot/testing.php | 38 + app/Config/CURLRequest.php | 36 + app/Config/Cache.php | 198 ++ app/Config/Constants.php | 79 + app/Config/ContentSecurityPolicy.php | 216 ++ app/Config/Cookie.php | 107 + app/Config/Cors.php | 105 + app/Config/Database.php | 215 ++ app/Config/DocTypes.php | 43 + app/Config/Email.php | 126 + app/Config/Encryption.php | 109 + app/Config/Events.php | 57 + app/Config/Exceptions.php | 106 + app/Config/Feature.php | 37 + app/Config/Filters.php | 111 + app/Config/ForeignCharacters.php | 12 + app/Config/Format.php | 73 + app/Config/Generators.php | 44 + app/Config/Honeypot.php | 42 + app/Config/Hostnames.php | 40 + app/Config/Images.php | 33 + app/Config/Kint.php | 63 + app/Config/Logger.php | 151 ++ app/Config/Migrations.php | 65 + app/Config/Mimes.php | 534 +++++ app/Config/Modules.php | 82 + app/Config/Optimize.php | 32 + app/Config/Pager.php | 37 + app/Config/Paths.php | 90 + app/Config/Publisher.php | 28 + app/Config/Routes.php | 60 + app/Config/Routing.php | 149 ++ app/Config/Security.php | 86 + app/Config/Services.php | 32 + app/Config/Session.php | 128 + app/Config/Toolbar.php | 147 ++ app/Config/UserAgents.php | 262 ++ app/Config/Validation.php | 44 + app/Config/View.php | 73 + app/Config/WorkerMode.php | 62 + app/Controllers/Auth.php | 46 + app/Controllers/BaseController.php | 45 + app/Controllers/Categories.php | 94 + app/Controllers/Dashboard.php | 63 + app/Controllers/Home.php | 11 + app/Controllers/Instruments.php | 79 + app/Controllers/InvOperations.php | 98 + app/Controllers/Investments.php | 31 + app/Controllers/Operations.php | 104 + app/Controllers/Valuations.php | 90 + app/Database/Migrations/.gitkeep | 0 .../2026-07-06-100001_CreateCategories.php | 36 + .../2026-07-06-100002_CreateOperations.php | 34 + ...2026-07-06-140001_CreateInvInstruments.php | 26 + .../2026-07-06-140002_CreateInvOperations.php | 34 + .../2026-07-06-140003_CreateInvValuations.php | 31 + app/Database/Seeds/.gitkeep | 0 .../Seeds/DefaultCategoriesSeeder.php | 37 + .../Seeds/DefaultInstrumentsSeeder.php | 26 + app/Filters/.gitkeep | 0 app/Filters/AuthFilter.php | 25 + app/Helpers/.gitkeep | 0 app/Language/.gitkeep | 0 app/Language/en/Validation.php | 4 + app/Libraries/.gitkeep | 0 app/Models/.gitkeep | 0 app/Models/CategoryModel.php | 85 + app/Models/InstrumentModel.php | 158 ++ app/Models/InvOperationModel.php | 48 + app/Models/OperationModel.php | 147 ++ app/Models/ValuationModel.php | 43 + app/ThirdParty/.gitkeep | 0 app/Views/auth/login.php | 25 + app/Views/categories/form.php | 41 + app/Views/categories/index.php | 43 + app/Views/dashboard/finances.php | 94 + app/Views/dashboard/index.php | 52 + app/Views/errors/cli/error_404.php | 7 + app/Views/errors/cli/error_exception.php | 65 + app/Views/errors/cli/production.php | 5 + app/Views/errors/html/debug.css | 194 ++ app/Views/errors/html/debug.js | 116 + app/Views/errors/html/error_400.php | 84 + app/Views/errors/html/error_404.php | 84 + app/Views/errors/html/error_exception.php | 429 ++++ app/Views/errors/html/production.php | 25 + app/Views/instruments/form.php | 23 + app/Views/instruments/index.php | 49 + app/Views/inv_operations/form.php | 52 + app/Views/inv_operations/index.php | 89 + app/Views/investments/index.php | 88 + app/Views/layout/main.php | 66 + app/Views/operations/form.php | 72 + app/Views/operations/index.php | 107 + app/Views/valuations/form.php | 39 + app/Views/valuations/index.php | 62 + app/Views/welcome_message.php | 331 +++ app/index.html | 11 + builds | 128 + composer.json | 43 + composer.lock | 2117 +++++++++++++++++ phpunit.dist.xml | 64 + preload.php | 112 + public/.htaccess | 49 + public/favicon.ico | Bin 0 -> 5430 bytes public/index.php | 59 + public/robots.txt | 2 + spark | 87 + tests/.htaccess | 6 + tests/README.md | 118 + .../2020-02-22-222222_example_migration.php | 37 + .../_support/Database/Seeds/ExampleSeeder.php | 41 + tests/_support/Libraries/ConfigReader.php | 19 + tests/_support/Models/ExampleModel.php | 24 + tests/database/ExampleDatabaseTest.php | 46 + tests/index.html | 11 + tests/session/ExampleSessionTest.php | 17 + tests/unit/HealthTest.php | 49 + writable/.htaccess | 6 + writable/cache/index.html | 11 + writable/debugbar/index.html | 11 + writable/index.html | 11 + writable/logs/index.html | 11 + writable/session/index.html | 11 + writable/uploads/index.html | 11 + 147 files changed, 12242 insertions(+) create mode 100644 .claude/memory/MEMORY.md create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 .paul/PROJECT.md create mode 100644 .paul/STATE.md create mode 100644 .paul/changelog/2026-07-06.md create mode 100644 .paul/codebase/impact_map.md create mode 100644 .paul/codebase/quality_risks.md create mode 100644 .paul/codebase/tooling_status.md create mode 100644 .paul/config.md create mode 100644 .paul/plans/20260706-1248-fundament-wydatki-przychody/PLAN.md create mode 100644 .paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md create mode 100644 .paul/plans/20260706-1430-modul-inwestycji/PLAN.md create mode 100644 .paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.htaccess create mode 100644 app/Common.php create mode 100644 app/Config/App.php create mode 100644 app/Config/Autoload.php create mode 100644 app/Config/Boot/development.php create mode 100644 app/Config/Boot/production.php create mode 100644 app/Config/Boot/testing.php create mode 100644 app/Config/CURLRequest.php create mode 100644 app/Config/Cache.php create mode 100644 app/Config/Constants.php create mode 100644 app/Config/ContentSecurityPolicy.php create mode 100644 app/Config/Cookie.php create mode 100644 app/Config/Cors.php create mode 100644 app/Config/Database.php create mode 100644 app/Config/DocTypes.php create mode 100644 app/Config/Email.php create mode 100644 app/Config/Encryption.php create mode 100644 app/Config/Events.php create mode 100644 app/Config/Exceptions.php create mode 100644 app/Config/Feature.php create mode 100644 app/Config/Filters.php create mode 100644 app/Config/ForeignCharacters.php create mode 100644 app/Config/Format.php create mode 100644 app/Config/Generators.php create mode 100644 app/Config/Honeypot.php create mode 100644 app/Config/Hostnames.php create mode 100644 app/Config/Images.php create mode 100644 app/Config/Kint.php create mode 100644 app/Config/Logger.php create mode 100644 app/Config/Migrations.php create mode 100644 app/Config/Mimes.php create mode 100644 app/Config/Modules.php create mode 100644 app/Config/Optimize.php create mode 100644 app/Config/Pager.php create mode 100644 app/Config/Paths.php create mode 100644 app/Config/Publisher.php create mode 100644 app/Config/Routes.php create mode 100644 app/Config/Routing.php create mode 100644 app/Config/Security.php create mode 100644 app/Config/Services.php create mode 100644 app/Config/Session.php create mode 100644 app/Config/Toolbar.php create mode 100644 app/Config/UserAgents.php create mode 100644 app/Config/Validation.php create mode 100644 app/Config/View.php create mode 100644 app/Config/WorkerMode.php create mode 100644 app/Controllers/Auth.php create mode 100644 app/Controllers/BaseController.php create mode 100644 app/Controllers/Categories.php create mode 100644 app/Controllers/Dashboard.php create mode 100644 app/Controllers/Home.php create mode 100644 app/Controllers/Instruments.php create mode 100644 app/Controllers/InvOperations.php create mode 100644 app/Controllers/Investments.php create mode 100644 app/Controllers/Operations.php create mode 100644 app/Controllers/Valuations.php create mode 100644 app/Database/Migrations/.gitkeep create mode 100644 app/Database/Migrations/2026-07-06-100001_CreateCategories.php create mode 100644 app/Database/Migrations/2026-07-06-100002_CreateOperations.php create mode 100644 app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php create mode 100644 app/Database/Migrations/2026-07-06-140002_CreateInvOperations.php create mode 100644 app/Database/Migrations/2026-07-06-140003_CreateInvValuations.php create mode 100644 app/Database/Seeds/.gitkeep create mode 100644 app/Database/Seeds/DefaultCategoriesSeeder.php create mode 100644 app/Database/Seeds/DefaultInstrumentsSeeder.php create mode 100644 app/Filters/.gitkeep create mode 100644 app/Filters/AuthFilter.php create mode 100644 app/Helpers/.gitkeep create mode 100644 app/Language/.gitkeep create mode 100644 app/Language/en/Validation.php create mode 100644 app/Libraries/.gitkeep create mode 100644 app/Models/.gitkeep create mode 100644 app/Models/CategoryModel.php create mode 100644 app/Models/InstrumentModel.php create mode 100644 app/Models/InvOperationModel.php create mode 100644 app/Models/OperationModel.php create mode 100644 app/Models/ValuationModel.php create mode 100644 app/ThirdParty/.gitkeep create mode 100644 app/Views/auth/login.php create mode 100644 app/Views/categories/form.php create mode 100644 app/Views/categories/index.php create mode 100644 app/Views/dashboard/finances.php create mode 100644 app/Views/dashboard/index.php create mode 100644 app/Views/errors/cli/error_404.php create mode 100644 app/Views/errors/cli/error_exception.php create mode 100644 app/Views/errors/cli/production.php create mode 100644 app/Views/errors/html/debug.css create mode 100644 app/Views/errors/html/debug.js create mode 100644 app/Views/errors/html/error_400.php create mode 100644 app/Views/errors/html/error_404.php create mode 100644 app/Views/errors/html/error_exception.php create mode 100644 app/Views/errors/html/production.php create mode 100644 app/Views/instruments/form.php create mode 100644 app/Views/instruments/index.php create mode 100644 app/Views/inv_operations/form.php create mode 100644 app/Views/inv_operations/index.php create mode 100644 app/Views/investments/index.php create mode 100644 app/Views/layout/main.php create mode 100644 app/Views/operations/form.php create mode 100644 app/Views/operations/index.php create mode 100644 app/Views/valuations/form.php create mode 100644 app/Views/valuations/index.php create mode 100644 app/Views/welcome_message.php create mode 100644 app/index.html create mode 100644 builds create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 phpunit.dist.xml create mode 100644 preload.php create mode 100644 public/.htaccess create mode 100644 public/favicon.ico create mode 100644 public/index.php create mode 100644 public/robots.txt create mode 100644 spark create mode 100644 tests/.htaccess create mode 100644 tests/README.md create mode 100644 tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php create mode 100644 tests/_support/Database/Seeds/ExampleSeeder.php create mode 100644 tests/_support/Libraries/ConfigReader.php create mode 100644 tests/_support/Models/ExampleModel.php create mode 100644 tests/database/ExampleDatabaseTest.php create mode 100644 tests/index.html create mode 100644 tests/session/ExampleSessionTest.php create mode 100644 tests/unit/HealthTest.php create mode 100644 writable/.htaccess create mode 100644 writable/cache/index.html create mode 100644 writable/debugbar/index.html create mode 100644 writable/index.html create mode 100644 writable/logs/index.html create mode 100644 writable/session/index.html create mode 100644 writable/uploads/index.html diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md new file mode 100644 index 0000000..e7f451d --- /dev/null +++ b/.claude/memory/MEMORY.md @@ -0,0 +1,5 @@ +# Memory Index — finansePRO + +Jedna linia na wspomnienie. Treść trzymaj w osobnych plikach. + +_Brak zapisanych wspomnień._ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56b9f10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,126 @@ +#------------------------- +# Operating Specific Junk Files +#------------------------- + +# OS X +.DS_Store +.AppleDouble +.LSOverride + +# OS X Thumbnails +._* + +# Windows image file caches +Thumbs.db +ehthumbs.db +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Linux +*~ + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +#------------------------- +# Environment Files +#------------------------- +# These should never be under version control, +# as it poses a security risk. +.env +.vagrant +Vagrantfile + +#------------------------- +# Temporary Files +#------------------------- +/writable/cache/* +!/writable/cache/index.html + +/writable/logs/* +!/writable/logs/index.html + +/writable/session/* +!/writable/session/index.html + +/writable/uploads/* +!/writable/uploads/index.html + +/writable/debugbar/* +!/writable/debugbar/index.html + +php_errors.log + +#------------------------- +# User Guide Temp Files +#------------------------- +/user_guide_src/build/* +/user_guide_src/cilexer/build/* +/user_guide_src/cilexer/dist/* +/user_guide_src/cilexer/pycilexer.egg-info/* + +#------------------------- +# Test Files +#------------------------- +/tests/coverage* + +# Don't save phpunit under version control. +/phpunit + +#------------------------- +# Composer +#------------------------- +vendor/ + +#------------------------- +# IDE / Development Files +#------------------------- + +# Modules Testing +_modules/* + +# phpenv local config +.php-version + +# Jetbrains editors (PHPStorm, etc) +.idea/ +*.iml + +# NetBeans +/nbproject/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/nbactions.xml +/nb-configuration.xml +/.nb-gradle/ + +# Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +*.sublime-workspace +*.sublime-project +.phpintel +/api/ + +# Visual Studio Code +.vscode/ + +/results/ +/phpunit.xml diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..6acf10b --- /dev/null +++ b/.htaccess @@ -0,0 +1,30 @@ +# finansePRO — root .htaccess dla wspoldzielonego hostingu (hostido), +# gdy docroot domeny = katalog projektu, a CI4 trzyma front controller w public/. +# +# DOCELOWO lepiej: ustawic docroot domeny finanse.projectpro.pl na .../finansePRO/public +# (bezpieczniejsze — app/, writable/, vendor/, .env poza katalogiem webowym) i usunac ten plik. + + + RewriteEngine On + RewriteBase / + + # www -> bez www (jak crmPRO) + RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC] + RewriteRule ^(.*)$ https://%1/$1 [R=301,L] + + # wymus HTTPS (wzorzec z crmPRO — dziala na hostido bez petli przekierowan) + RewriteCond %{SERVER_PORT} !=443 + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + + # caly ruch kierujemy do public/ (front controller CI4) + RewriteCond %{REQUEST_URI} !^/public/ + RewriteRule ^(.*)$ public/$1 [L] + + +# Zapora dla plikow wrazliwych, gdyby mod_rewrite byl niedostepny + + Require all denied + + +# Bez listowania katalogow +Options -Indexes diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md new file mode 100644 index 0000000..f93f319 --- /dev/null +++ b/.paul/PROJECT.md @@ -0,0 +1,21 @@ +# PROJECT — finansePRO + +**Utworzono:** 2026-07-06 +**Status:** Inicjalizacja (pusty projekt) + +## Nazwa +finansePRO + +## Krótki opis +Projekt na wczesnym etapie — katalog nie zawiera jeszcze kodu (poza konfiguracją `.vscode`). Cel i zakres do doprecyzowania. + +## Wartość podstawowa +_Do uzupełnienia — czego finansePRO ma dostarczać użytkownikowi._ + +## Aktualny status +- Repozytorium bez kodu źródłowego. +- PAUL zainicjalizowany w trybie plan-first. +- Brak zdefiniowanych wymagań i stosu technologicznego. + +## Trwałe wymagania / ograniczenia +_Brak znanych na tę chwilę. Uzupełnij przy pierwszym `$paul-plan` lub `$paul-discuss`._ diff --git a/.paul/STATE.md b/.paul/STATE.md new file mode 100644 index 0000000..7266c5a --- /dev/null +++ b/.paul/STATE.md @@ -0,0 +1,35 @@ +# STATE — finansePRO + +**Aktualizacja:** 2026-07-06T14:55 + +## Project Reference +CodeIgniter 4.7.3 + MySQL (baza `host700513_finansepro` na hostido, czytana z `.env`). PHP 8.2 (XAMPP), Bootstrap 5 + ApexCharts (CDN). Logowanie: jeden użytkownik z `.env`. Tryb PAUL: plan-first. + +## Current Position +- Plan `20260706-1248-fundament-wydatki-przychody`: **UKOŃCZONY**. +- Plan `20260706-1430-modul-inwestycji`: **UKOŃCZONY** (PLAN → APPLY → UNIFY). Wszystkie AC (AC-1..AC-5) Pass; SUMMARY utworzone. +- Gotowe do kolejnego planu. + +## Loop Position +``` +PLAN ──▶ APPLY ──▶ UNIFY + ✓ ✓ ✓ [Pętla zamknięta — gotowe do następnego PLAN] +``` + +## Session Continuity +- Ostatnia aktywność: UNIFY planu 2 — SUMMARY + changelog + radar + STATE. Moduł inwestycji + pulpit globalny `/dashboard` + pulpit finansów `/finances`. Dane testowe wyczyszczone, `.env` na `production`. +- Next action: `$paul-plan` dla kolejnego planu, lub wdrożenie produkcyjne (patrz Aktywne sprawy). +- Resume file: `.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md` + +## Roadmapa (nieformalna) +- ✓ Plan 1: fundament + wydatki/przychody bieżące. +- ✓ Plan 2: moduł inwestycji (instrumenty, wpłaty/wypłaty, wyceny) + pulpit inwestycji + pulpit globalny. +- ▷ Kolejne: do doprecyzowania (np. wykres per instrument, eksport, historia wartości). + +## Aktywne sprawy / do weryfikacji +- Wdrożenie produkcyjne `https://finanse.projectpro.pl/`: wgrać pliki + `vendor/`, uruchomić `php spark migrate` na serwerze (3 nowe tabele `inv_*`), root `.htaccess`/docroot→`public/`, prawa `writable/`, `.env` z `CI_ENVIRONMENT=production` + `app.baseURL`. +- Lokalny dev: `.env` bywa przełączany na `CI_ENVIRONMENT=development` (używa `db_host_remote`, bo lokalny MySQL zgaszony); po pracy wraca na `production`. Aktualnie: `production`. + +## Uwagi +- Radar: degraded (weryfikacja runtime zamiast pełnego indeksu). Pełny `codebase-memory-mcp` przez `$paul-map-codebase`. +- Repo nie jest git — brak commitów fazowych. diff --git a/.paul/changelog/2026-07-06.md b/.paul/changelog/2026-07-06.md new file mode 100644 index 0000000..36dfa50 --- /dev/null +++ b/.paul/changelog/2026-07-06.md @@ -0,0 +1,39 @@ +# 2026-07-06 + +## Co zrobiono + +- [Plan 20260706-1248] Fundament finansePRO na CodeIgniter 4.7.3 + moduł wydatków/przychodów bieżących. +- Inicjalizacja PAUL (plan-first, Quality Radar), junction memory. +- Instalacja CI4 (Composer), konfiguracja DB czytana z `.env`, włączenie `intl` w XAMPP. +- Logowanie jednego użytkownika z `.env` (filtr `auth`, `Auth::login/logout`). +- Migracje `categories` (hierarchia+typ) i `operations` (+FK), seeder 9 kategorii. +- CRUD kategorii i operacji: filtry (data/kategoria/typ), sumy, blokada usunięcia używanej kategorii, typ operacji wymuszany przez kategorię. +- Dashboard z 3 wykresami ApexCharts (bilans miesięczny, wydatki wg kategorii, saldo skumulowane). +- Weryfikacja end-to-end przez `php spark serve` + curl; dane testowe wyczyszczone. +- Naprawa wdrożenia hostido: root `.htaccess` (forward do `public/`, 403), sekcja produkcyjna w `.env` (`CI_ENVIRONMENT=production`, `app.baseURL`). +- [Plan 20260706-1430] Moduł inwestycji: instrumenty (Obligacje/ETF), wpłaty/wypłaty, ręczne wyceny. +- Migracje `inv_instruments`, `inv_operations`, `inv_valuations` (FK RESTRICT), seeder instrumentów. +- CRUD instrumentów/operacji/wycen + filtry + blokada usunięcia instrumentu z powiązaniami. +- Pulpit inwestycji `/investments`: kafelki, wykres 3-seryjny w czasie, tabela per instrument (zarobek netto = ostatnia wycena − (Σwpłat − Σwypłat)). +- Pulpit globalny `/dashboard` (majątek razem = saldo bieżące + wartość inwestycji); dotychczasowy pulpit finansów przepięty pod `/finances`. +- Nawigacja zgrupowana w dropdowny: „Finanse bieżące" i „Inwestycje" + top-level „Pulpit". +- Weryfikacja end-to-end curl (ETF: netto 800, wartość 950, zarobek 150 = 18,75%); dane testowe wyczyszczone. + +## Zmienione pliki + +- `composer.json`, `composer.lock`, `vendor/`, `spark`, `public/`, `writable/` +- `app/Config/Database.php`, `app/Config/Routes.php`, `app/Config/Filters.php`, `app/Config/App.php` +- `app/Controllers/BaseController.php`, `app/Controllers/Auth.php`, `app/Controllers/Categories.php`, `app/Controllers/Operations.php`, `app/Controllers/Dashboard.php` +- `app/Filters/AuthFilter.php` +- `app/Models/CategoryModel.php`, `app/Models/OperationModel.php` +- `app/Database/Migrations/2026-07-06-100001_CreateCategories.php`, `app/Database/Migrations/2026-07-06-100002_CreateOperations.php` +- `app/Database/Seeds/DefaultCategoriesSeeder.php` +- `app/Views/layout/main.php`, `app/Views/auth/login.php`, `app/Views/categories/index.php`, `app/Views/categories/form.php`, `app/Views/operations/index.php`, `app/Views/operations/form.php`, `app/Views/dashboard/index.php` +- `.env`, `.htaccess` +- `app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php`, `...140002_CreateInvOperations.php`, `...140003_CreateInvValuations.php` +- `app/Database/Seeds/DefaultInstrumentsSeeder.php` +- `app/Models/InstrumentModel.php`, `app/Models/InvOperationModel.php`, `app/Models/ValuationModel.php` +- `app/Controllers/Instruments.php`, `app/Controllers/InvOperations.php`, `app/Controllers/Valuations.php`, `app/Controllers/Investments.php`, `app/Controllers/Dashboard.php` +- `app/Views/instruments/{index,form}.php`, `app/Views/inv_operations/{index,form}.php`, `app/Views/valuations/{index,form}.php`, `app/Views/investments/index.php` +- `app/Views/dashboard/index.php` (globalny), `app/Views/dashboard/finances.php` (finansowy) +- `app/Config/Routes.php`, `app/Views/layout/main.php` diff --git a/.paul/codebase/impact_map.md b/.paul/codebase/impact_map.md new file mode 100644 index 0000000..3325a68 --- /dev/null +++ b/.paul/codebase/impact_map.md @@ -0,0 +1,36 @@ +# Impact Map + +**Aktualizacja:** 2026-07-06 (post-apply Plan 2 — degraded) + +## Kontekst +CodeIgniter 4.7.3 + MySQL (`.env`). Radar degraded — brak indeksu codebase-memory-mcp; weryfikacja runtime. + +## Moduły +### Finanse bieżące (Plan 1) +- Kategorie/operacje: `app/Models/{Category,Operation}Model.php`, `app/Controllers/{Categories,Operations}.php`, tabele `categories`/`operations`. +- Pulpit finansów: `Dashboard::finances` → `app/Views/dashboard/finances.php`, trasa `/finances`. + +### Inwestycje (Plan 2) +- Instrumenty: `InstrumentModel` (`withStats`, `portfolioTimeline`, `hasChildren`), `Instruments.php`, widoki `instruments/`, tabela `inv_instruments`. +- Operacje: `InvOperationModel`, `InvOperations.php`, widoki `inv_operations/`, tabela `inv_operations` (FK RESTRICT → inv_instruments). +- Wyceny: `ValuationModel`, `Valuations.php`, widoki `valuations/`, tabela `inv_valuations` (FK RESTRICT → inv_instruments). +- Pulpit inwestycji: `Investments::index` → `investments/index.php`, trasa `/investments`. + +### Pulpit globalny +- `Dashboard::index` → `app/Views/dashboard/index.php`, trasy `/` i `/dashboard`. Majątek = saldo bieżące (`OperationModel::totals`) + wartość inwestycji (`InstrumentModel::withStats`). + +## Routing / nawigacja +- `app/Config/Routes.php` — grupa `auth`: `/`, `dashboard`, `finances`, `operations/*`, `categories/*`, `investments`, `instruments/*`, `investments/operations/*`, `investments/valuations/*`. +- `app/Views/layout/main.php` — top-level „Pulpit" + dropdowny „Finanse bieżące" / „Inwestycje". + +## Dane / config +- MySQL z `.env` (dev → `db_host_remote`, prod → `db_host`) w `Database.php`. +- Tabele inwestycji: `inv_instruments` (name unique), `inv_operations` (instrument_id, date, amount, type enum deposit/withdraw), `inv_valuations` (instrument_id, date, value). + +## Ukryte sprzężenia +- Zarobek netto liczony wyłącznie w `InstrumentModel::withStats` — kontrolery/widoki konsumują (jedno źródło prawdy). +- FK RESTRICT: instrument z operacjami/wycenami nieusuwalny (spójne z blokadą w `Instruments::delete`). +- Pulpit globalny zależny od obu domen (finanse + inwestycje). + +## Weryfikacja manualna +- Migracje `inv_*`; CRUD 3 encji; filtry; blokada usunięcia; wzór zarobku; pulpity `/dashboard`, `/finances`, `/investments`. diff --git a/.paul/codebase/quality_risks.md b/.paul/codebase/quality_risks.md new file mode 100644 index 0000000..ab59d85 --- /dev/null +++ b/.paul/codebase/quality_risks.md @@ -0,0 +1,19 @@ +# Quality Risks + +**Aktualizacja:** 2026-07-06 (post-apply Plan 2 — degraded) + +## Zaakceptowane świadomie +- **Kopiowanie wzorców zapytań** z `OperationModel` do `InvOperationModel`/`ValuationModel` (filtered/join). Osobna domena (brak type income/expense, dochodzą wyceny) — nie konsolidować w bazowy model, kod krótki. +- **Format kwoty** `number_format(...,2,',',' ').' zł'` powtórzony w widokach jako lokalna funkcja `$fmt`. Trywialny, świadomie nie wydzielony do helpera (YAGNI). + +## Rozwiązane +- Zarobek netto: jedno źródło prawdy `InstrumentModel::withStats` (`net_invested`, `profit`, `percent`); nie liczony w widokach. +- Typ operacji inwestycyjnej: ENUM `deposit|withdraw` w DB + walidacja `in_list` w modelu + sanity w kontrolerze. +- Integralność: FK RESTRICT na `inv_operations`/`inv_valuations`. + +## Odłożone +- Pełna indeksacja `codebase-memory-mcp` — `$paul-map-codebase`. +- jscpd/ast-grep — wyłączone polityką w `config.md` (skan on-demand). + +## Do obserwacji +- `portfolioTimeline` liczony w PHP O(daty × operacje) — trywialne przy ręcznych wycenach; przy dużej liczbie wpisów rozważyć agregację SQL. diff --git a/.paul/codebase/tooling_status.md b/.paul/codebase/tooling_status.md new file mode 100644 index 0000000..8b41cc4 --- /dev/null +++ b/.paul/codebase/tooling_status.md @@ -0,0 +1,16 @@ +# Tooling Status + +**Timestamp:** 2026-07-06T13:10 +**Scan mode:** post-apply (plan 20260706-1248-fundament-wydatki-przychody) + +## Narzędzia +- `codebase-memory-mcp`: enabled, indeks nieodświeżony (ponytail: pełna indeksacja odłożona do `$paul-map-codebase`; kod świeżo dodany, weryfikacja przeszła runtime'owo). +- `jscpd`: disabled by policy +- `ast-grep`: disabled by policy + +## Weryfikacja runtime (zamiast skanu statycznego) +- `php spark migrate` / `db:seed` — OK (zdalna baza hostido). +- `php spark serve` + curl: login/logout, CRUD kategorii i operacji, filtry, sumy, 3 wykresy dashboardu, blokada usunięcia używanej kategorii, wymuszenie typu operacji przez kategorię — wszystko potwierdzone. + +## Następna akcja +`$paul-map-codebase` (pełny indeks codebase-memory-mcp) po ustabilizowaniu modułu, lub przed Planem 2 (inwestycje). diff --git a/.paul/config.md b/.paul/config.md new file mode 100644 index 0000000..698b461 --- /dev/null +++ b/.paul/config.md @@ -0,0 +1,46 @@ +# Project Config + +**Project:** finansePRO +**Created:** 2026-07-06 + +## Project Settings + +```yaml +project: + name: finansePRO + version: 0.0.0 +``` + +## Integrations + +### Quality Radar + +```yaml +quality_radar: + enabled: true + auto_install: true + tools: + codebase_memory_mcp: true + jscpd: false # manual/on-demand duplicate scan + ast_grep: false # manual/on-demand structural pattern scan + reports: + update_on_plan: true + update_on_apply: true + update_on_verify: true +``` + +## Preferences + +```yaml +preferences: + auto_commit: false + verbose_output: false + parallel_agents: false + plan_storage: plans + state_autocompress: true + state_autocompress_max_lines: 500 +``` + +--- + +*Config created: 2026-07-06* diff --git a/.paul/plans/20260706-1248-fundament-wydatki-przychody/PLAN.md b/.paul/plans/20260706-1248-fundament-wydatki-przychody/PLAN.md new file mode 100644 index 0000000..4abc939 --- /dev/null +++ b/.paul/plans/20260706-1248-fundament-wydatki-przychody/PLAN.md @@ -0,0 +1,235 @@ +--- +plan_id: 20260706-1248-fundament-wydatki-przychody +title: Fundament finansePRO + moduł wydatków/przychodów bieżących +storage: plan-first +legacy_phase: null +created: 2026-07-06T12:48:27+02:00 +status: planned +type: execute +autonomous: false +delegation: auto +files_modified: + - composer.json + - .env + - app/Config/Database.php + - app/Config/Routes.php + - app/Config/Filters.php + - app/Filters/AuthFilter.php + - app/Controllers/BaseController.php + - app/Controllers/Auth.php + - app/Controllers/Categories.php + - app/Controllers/Operations.php + - app/Controllers/Dashboard.php + - app/Models/CategoryModel.php + - app/Models/OperationModel.php + - app/Database/Migrations/*_create_categories.php + - app/Database/Migrations/*_create_operations.php + - app/Database/Seeds/DefaultCategoriesSeeder.php + - app/Views/layout/main.php + - app/Views/auth/login.php + - app/Views/categories/* + - app/Views/operations/* + - app/Views/dashboard/index.php +quality_radar: degraded +--- + + +## Cel +Postawić fundament aplikacji finansePRO na CodeIgniter 4 (MySQL z `.env`), logowanie jednego użytkownika oraz kompletny moduł finansów bieżących: kategorie (hierarchiczne, typ przychód/wydatek), operacje (CRUD + filtry) i dashboard z wykresami ApexCharts. + +## Po co +To pierwsza, uruchamialna wersja narzędzia do analizy finansów osobistych. Daje działający szkielet (auth + DB + layout), na którym Plan 2 dołoży moduł inwestycji. Wzorzec domenowy (kategorie/operacje) świadomie czerpie z modułu finansów crmPRO, ale kod pisany jest od zera w CI4 — bez przenoszenia frameworka crmPRO. + +## Wynik +Działająca aplikacja CI4: logowanie → dashboard z wykresami → zarządzanie kategoriami i operacjami. Migracje tworzą schemat, seeder wrzuca startowe kategorie. + + + +## Dokumenty projektu +@.paul/PROJECT.md +@.paul/STATE.md +@.paul/config.md +@.paul/codebase/impact_map.md +@.paul/codebase/quality_risks.md + +## Referencja (NIE modyfikować — tylko wzorzec domenowy) +@C:/visual-studio-code/crmPRO/autoload/Domain/Finances/FinanceRepository.php +@C:/visual-studio-code/crmPRO/templates/finances/main-view.php +@C:/visual-studio-code/crmPRO/templates/finances/operations-list.php + +## Konfiguracja środowiska +@.env + + + +- Stack: **CodeIgniter 4** (użytkownik zna go z innej aplikacji, wdrożenie na hostido OK). +- Logowanie: **jeden użytkownik wprost z `.env`** (`user_email` + `user_password`), bez tabeli users, bez rejestracji, bez remember-tokenów. +- Zakres tego planu: **fundament + moduł bieżących wydatków/przychodów**. Moduł inwestycji to osobny Plan 2 (ręczne snapshoty wartości). +- Kategorie: hierarchiczne (`parent_id`), z typem przychód/wydatek — wzorzec jak `finance_categories` w crmPRO. +- Wykresy: **ApexCharts** (znane z crmPRO). +- Hasło w `.env` jest jawne — logowanie porównuje wprost (narzędzie jednoosobowe). Podniesienie do hasha zostawione jako możliwa późniejsza zmiana. + + + +## Quality Radar + +**Status:** degraded (finansePRO to greenfield — brak kodu do skanowania; codebase-memory-mcp nie ma czego indeksować) +**Tools:** codebase-memory-mcp (brak indeksu — projekt pusty); jscpd/ast-grep wyłączone polityką + +## Obszary objęte planem +- fundament CI4 — `app/Config/*`, `composer.json`, bootstrap +- auth (jeden user z .env) — `app/Filters/AuthFilter.php`, `app/Controllers/Auth.php` +- domena finansów bieżących — `app/Models/{Category,Operation}Model.php`, `app/Controllers/{Categories,Operations,Dashboard}.php`, migracje, widoki +- config DB — `app/Config/Database.php` czyta klucze z istniejącego `.env` + +## Ryzyka duplikacji / hardcode +- **Drugie źródło prawdy dla configu DB** — istniejący `.env` używa własnych kluczy (`db_host`, `db_user`, `db_name`, `db_password`), NIE standardowych CI4 (`database.default.*`). Obsłużone w Task 1: `Database.php` czyta te klucze przez `env('db_host')` itd., bez dublowania wartości i bez zmiany `.env`. +- **Typ operacji (przychód/wydatek)** — nie utrwalać go w dwóch miejscach niespójnie. Obsłużone w Task 2: `type` jest kolumną enum na kategorii i na operacji; walidacja spójności w `OperationModel`. +- **Wzorzec z crmPRO** — czerpiemy schemat domenowy, ale nie kopiujemy kodu 1:1 (inny framework). Brak ryzyka copy-paste między repo. + +## Świadome odroczenia +- Hashowanie hasła użytkownika — odroczone (jawne `.env`, narzędzie jednoosobowe). +- Pełny skan radaru (codebase-memory-mcp index) — po zbudowaniu kodu, w `$paul-apply`/`$paul-map-codebase`. +- Moduł inwestycji — Plan 2. + + + +Brak SPECIAL-FLOWS.md — sekcja skills pominięta. + + + + +## AC-1: Aplikacja startuje i łączy się z bazą z .env +```gherkin +Given świeżo zainstalowany CodeIgniter 4 z konfiguracją z .env +When uruchomię `php spark serve` i otworzę stronę główną +Then aplikacja ładuje się bez błędu połączenia z MySQL, używając db_host/db_user/db_name/db_password z .env +``` + +## AC-2: Logowanie jednego użytkownika z .env +```gherkin +Given niezalogowany użytkownik wchodzi na dowolną chronioną trasę +When zostaje przekierowany na /login i poda email oraz hasło zgodne z user_email/user_password z .env +Then zostaje zalogowany (sesja) i przekierowany na dashboard; błędne dane pokazują komunikat, a /logout kończy sesję +``` + +## AC-3: Zarządzanie kategoriami (hierarchia + typ) +```gherkin +Given zalogowany użytkownik w widoku kategorii +When doda/edytuje/usunie kategorię z nazwą, typem (przychód/wydatek) i opcjonalnym rodzicem +Then kategoria zapisuje się w tabeli categories, respektuje parent_id, a usunięcie kategorii z operacjami jest zablokowane lub przenosi operacje zgodnie z regułą (patrz Task 3) +``` + +## AC-4: CRUD operacji z filtrami +```gherkin +Given zalogowany użytkownik w widoku operacji +When doda operację (data, kwota, typ, kategoria, opis) i użyje filtra po zakresie dat oraz kategorii +Then operacja zapisuje się w tabeli operations, lista pokazuje przefiltrowane wyniki i sumy (przychody, wydatki, saldo) +``` + +## AC-5: Dashboard z wykresami +```gherkin +Given istnieją operacje w bazie +When otworzę dashboard +Then widzę wykresy ApexCharts: (a) bilans miesięczny przychody vs wydatki, (b) wydatki wg kategorii (donut), (c) saldo skumulowane w czasie — z danymi zgodnymi z operacjami +``` + + + + + + + Task 1: Bootstrap CI4 + konfiguracja DB z .env + logowanie jednego użytkownika + composer.json, app/Config/Database.php, app/Config/Routes.php, app/Config/Filters.php, app/Filters/AuthFilter.php, app/Controllers/Auth.php, app/Views/auth/login.php, app/Views/layout/main.php + + - Zainstalować szkielet CodeIgniter 4 (`composer create-project codeigniter4/appstarter` lub równoważnie) w katalogu projektu, zachowując istniejący `.env`, `.paul/`, `.claude/`, `.vscode/`. + - W `app/Config/Database.php` ustawić `default` z istniejących kluczy .env: `hostname = env('db_host')`, `username = env('db_user')`, `database = env('db_name')`, `password = env('db_password')`, `DBDriver = 'MySQLi'`, `charset = 'utf8mb4'`. NIE dodawać nowych kluczy do `.env` (żeby nie tworzyć drugiego źródła prawdy). Zdalny host `db_host_remote` zostawić jako komentarz/opcję na wdrożenie. + - Utworzyć `AuthFilter` sprawdzający `session()->get('logged_in')`; niezalogowanych przekierowuje na `/login`. Zarejestrować w `Filters.php` jako alias `auth` i nałożyć na grupę tras chronionych w `Routes.php`. + - `Auth` controller: `login` (GET formularz, POST weryfikacja wobec `env('user_email')`/`env('user_password')` — porównanie wprost; // ponytail: jawne haslo z .env, podniesc do password_hash jesli pojawi sie wiecej userow), `logout` (czyści sesję). + - `app/Views/layout/main.php`: wspólny layout Bootstrap + miejsce na treść + nawigacja (Dashboard, Operacje, Kategorie, Wyloguj). ApexCharts i Bootstrap wpiąć (CDN lub `public/`). + - Trasa domyślna `/` → redirect na dashboard (gdy zalogowany) lub `/login`. + + `php spark serve`, wejście na chronioną trasę przekierowuje na /login; poprawne dane z .env logują i przekierowują na dashboard; złe dane = komunikat; /logout wylogowuje. Brak błędu połączenia z DB. + AC-1, AC-2 + + + + Task 2: Schemat DB (migracje) + modele + seeder kategorii + app/Database/Migrations/*_create_categories.php, app/Database/Migrations/*_create_operations.php, app/Models/CategoryModel.php, app/Models/OperationModel.php, app/Database/Seeds/DefaultCategoriesSeeder.php + + - Migracja `categories`: `id` PK, `name` VARCHAR, `type` ENUM('income','expense'), `parent_id` INT NULL (FK self, ON DELETE SET NULL), `created_at`/`updated_at`. Indeks na `parent_id`, `type`. + - Migracja `operations`: `id` PK, `date` DATE, `amount` DECIMAL(12,2) (dodatnia), `type` ENUM('income','expense'), `category_id` INT NULL (FK categories, ON DELETE SET NULL), `description` VARCHAR NULL, `created_at`/`updated_at`. Indeksy na `date`, `category_id`, `type`. + - `CategoryModel`: allowedFields, walidacja (name wymagane, type in income/expense, parent_id istnieje lub null), metoda `flatList()`/`treeList()` i `usedByOperations($id)`. + - `OperationModel`: allowedFields, walidacja (date, amount > 0, type in income/expense, category_id istnieje lub null); reguła spójności: `type` operacji musi zgadzać się z `type` wybranej kategorii (jedno źródło prawdy — kategoria narzuca typ). Metody agregujące dla dashboardu: `monthlyBalance($from,$to)`, `byCategory($from,$to,$type)`, `runningBalance($from,$to)`. + - `DefaultCategoriesSeeder`: kilka startowych kategorii przychodów (np. Wynagrodzenie, Inne przychody) i wydatków (np. Jedzenie, Mieszkanie, Transport, Rozrywka). + + `php spark migrate` tworzy tabele bez błędów; `php spark db:seed DefaultCategoriesSeeder` wstawia kategorie; szybki tinker/SQL potwierdza strukturę i FK. + AC-3 (schemat), AC-4 (schemat), AC-5 (agregacje) + + + + Task 3: Moduł kategorii i operacji (CRUD + filtry + sumy) + app/Controllers/Categories.php, app/Controllers/Operations.php, app/Controllers/BaseController.php, app/Views/categories/*, app/Views/operations/*, app/Config/Routes.php + + - `Categories` controller + widoki: lista (drzewko/płaska z wcięciem wg parent_id), formularz dodaj/edytuj (name, type, parent_id), usuwanie. Usunięcie kategorii używanej przez operacje: zablokować z komunikatem LUB pozwolić (operacje przechodzą na category_id=NULL dzięki FK) — wybrać blokadę z jasnym komunikatem dla bezpieczeństwa danych. + - `Operations` controller + widoki: lista z filtrami (zakres dat — datepicker/``, kategoria, typ), formularz dodaj/edytuj (date, amount, type auto z kategorii, category_id, description), usuwanie. Wybór kategorii ustawia typ operacji (spójność jak w Task 2). Lista pokazuje sumy: przychody, wydatki, saldo dla aktualnego filtra. + - Trasy CRUD w `Routes.php` w grupie chronionej filtrem `auth`. + - Walidacja po stronie serwera przez modele; komunikaty błędów w widokach. + + Ręcznie: dodać/edytować/usunąć kategorię i operację; filtr po dacie i kategorii zawęża listę; sumy się zgadzają; próba usunięcia używanej kategorii jest zablokowana z komunikatem. + AC-3, AC-4 + + + + Task 4: Dashboard z wykresami ApexCharts + app/Controllers/Dashboard.php, app/Views/dashboard/index.php, app/Config/Routes.php + + - `Dashboard` controller: pobiera z `OperationModel` dane dla domyślnego zakresu (np. bieżący rok) i oddaje do widoku jako JSON dla ApexCharts. Opcjonalny filtr zakresu dat u góry dashboardu. + - Wykresy ApexCharts w `dashboard/index.php`: + (a) bilans miesięczny — słupki przychody vs wydatki per miesiąc, + (b) wydatki wg kategorii — donut (tylko type=expense), + (c) saldo skumulowane w czasie — linia (cumsum operacji: +przychód, −wydatek). + - Kafelki podsumowania: suma przychodów, suma wydatków, saldo dla wybranego zakresu. + - Trasa `/dashboard` (i `/`) w grupie chronionej. + + Ręcznie: po dodaniu operacji dashboard pokazuje trzy wykresy z poprawnymi danymi; zmiana zakresu dat aktualizuje wykresy i kafelki. + AC-5 + + + + + +## Nie zmieniać +- `.env` — czytamy istniejące klucze, nie dodajemy nowych ani nie zmieniamy wartości. +- `.paul/`, `.claude/`, `.vscode/` — infrastruktura projektu. +- Repozytorium crmPRO (`C:/visual-studio-code/crmPRO/`) — wyłącznie referencja wzorca, zero modyfikacji. + +## Poza zakresem +- Moduł inwestycji (operacje inwestycyjne, wyceny, wykres wpłaty vs zarobek) — Plan 2. +- Tabela users, rejestracja, reset hasła, wielu użytkowników. +- Import z Fakturownia / integracje zewnętrzne (były w crmPRO, tu niepotrzebne). +- Hashowanie hasła, 2FA, remember-me. +- Wdrożenie produkcyjne na hostido (osobny krok po akceptacji lokalnej). + + + +- [ ] `php spark serve` startuje, `/` przekierowuje wg stanu logowania. +- [ ] Logowanie wobec `.env` działa (poprawne/błędne dane, logout). +- [ ] `php spark migrate` i seeder wykonują się bez błędów. +- [ ] CRUD kategorii i operacji działa, filtry i sumy poprawne. +- [ ] Dashboard renderuje 3 wykresy ApexCharts z danymi zgodnymi z operacjami. +- [ ] Config DB czyta wyłącznie z `.env` (brak drugiego źródła prawdy). +- [ ] Typ operacji spójny z typem kategorii (jedno źródło prawdy). +- [ ] Quality Radar: ryzyka z `` obsłużone lub odroczone świadomie. + + + +- [ ] Wszystkie AC (AC-1..AC-5) przechodzą. +- [ ] Weryfikacja kompletna. +- [ ] Radar/impact_map zaktualizowane po zbudowaniu kodu (w `$paul-apply`). +- [ ] Fundament gotowy pod Plan 2 (inwestycje). + + + +SUMMARY.md path: `.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md` + diff --git a/.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md b/.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md new file mode 100644 index 0000000..0835bae --- /dev/null +++ b/.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md @@ -0,0 +1,92 @@ +--- +plan_id: 20260706-1248-fundament-wydatki-przychody +title: Fundament finansePRO + moduł wydatków/przychodów bieżących +completed: 2026-07-06T13:20:00+02:00 +storage: plan-first +quality_radar: degraded +--- + +# Summary: Fundament finansePRO + moduł wydatków/przychodów bieżących + +## Objective + +Postawić fundament aplikacji finansePRO na CodeIgniter 4 (MySQL z `.env`), logowanie jednego użytkownika oraz moduł finansów bieżących: kategorie hierarchiczne z typem, operacje z filtrami i dashboard z wykresami ApexCharts. + +## What Was Built + +| Area | Result | +|------|--------| +| Fundament CI4 | CodeIgniter 4.7.3 zainstalowany (Composer), `Database.php` czyta klucze z istniejącego `.env`, czyste URL-e (`indexPage=''`) | +| Logowanie | Filtr `auth` + `Auth::login/logout` weryfikuje wobec `user_email`/`user_password` z `.env`, sesja `logged_in` | +| Schemat DB | Migracje `categories` (hierarchia `parent_id`, `type`, FK self `SET NULL`) i `operations` (FK `category_id` `SET NULL`), seeder 9 kategorii | +| CRUD kategorii/operacji | Pełny CRUD, filtry (data/kategoria/typ), sumy przychody/wydatki/saldo, blokada usunięcia używanej kategorii | +| Dashboard | 3 wykresy ApexCharts (bilans miesięczny, wydatki wg kategorii donut, saldo skumulowane) + kafelki podsumowań | +| Deployment | Root `.htaccess` (forward do `public/`, HTTPS jak crmPRO), sekcja produkcyjna w `.env` | + +## Files Modified + +- `composer.json`, `composer.lock`, `vendor/`, `spark`, `public/`, `writable/` — szkielet CI4 +- `app/Config/Database.php` — hostname/username/password/database z `.env` (jedno źródło prawdy) +- `app/Config/Routes.php` — trasy publiczne + grupa chroniona filtrem `auth` +- `app/Config/Filters.php` — alias `auth` +- `app/Config/App.php` — `indexPage=''` +- `app/Controllers/BaseController.php` — helpery `form`, `url` +- `app/Filters/AuthFilter.php` — ochrona tras +- `app/Controllers/{Auth,Categories,Operations,Dashboard}.php` +- `app/Models/{CategoryModel,OperationModel}.php` +- `app/Database/Migrations/2026-07-06-100001_CreateCategories.php`, `...100002_CreateOperations.php` +- `app/Database/Seeds/DefaultCategoriesSeeder.php` +- `app/Views/layout/main.php`, `auth/login.php`, `categories/{index,form}.php`, `operations/{index,form}.php`, `dashboard/index.php` +- `.env` — `CI_ENVIRONMENT`, `app.baseURL` (produkcja) +- `.htaccess` (root) — forward do `public/` dla hostido +- `C:/xampp/php/php.ini` — włączone `extension=intl` (poza repo, wymóg CI4) + +## Acceptance Criteria Results + +| Criterion | Status | Evidence | +|-----------|--------|----------| +| AC-1: start + połączenie z DB | Pass | `php spark serve` startuje, `migrate` łączy się z bazą hostido | +| AC-2: logowanie z `.env` | Pass | curl: poprawne dane → 303 `/dashboard`; złe → powrót; `/logout` → `/login`; trasa chroniona → `/login` | +| AC-3: kategorie (hierarchia+typ) | Pass | CRUD OK; `parent_id` respektowany; usunięcie używanej kategorii zablokowane komunikatem | +| AC-4: CRUD operacji + filtry | Pass | filtry income/expense/zakres dat zwracają dokładne kwoty; sumy 5000/450/4550 | +| AC-5: dashboard z wykresami | Pass | JSON `monthly`/`byCat`/`running` poprawne; 3 wykresy ApexCharts renderowane | + +## Verification Results + +| Check | Result | Notes | +|-------|--------|-------| +| `php spark migrate` | Pass | 2 migracje na bazie hostido | +| `php spark db:seed DefaultCategoriesSeeder` | Pass | 9 kategorii | +| `php spark serve` + curl (login/logout/CRUD/filtry/dashboard) | Pass | pełny przepływ end-to-end | +| Wymuszenie typu operacji przez kategorię | Pass | wysłane złe typy zostały poprawione wg kategorii | +| Blokada usunięcia używanej kategorii | Pass | redirect + komunikat | +| Dane testowe | Cleaned | operacje: 0, kategorie seed: 9 | + +## Quality Radar Results + +**Status:** degraded + +- New risks: brak nowych ryzyk duplikacji (jscpd/ast-grep wyłączone polityką) +- Resolved risks: config DB — jedno źródło prawdy (`.env`); typ operacji — wymuszany przez kategorię +- Deferred risks: pełna indeksacja `codebase-memory-mcp` odłożona do `$paul-map-codebase`; weryfikacja przeprowadzona runtime'owo +- Raw outputs: `.paul/codebase/tooling_status.md` + +## Deviations + +- Dodano `CI_ENVIRONMENT` i `app.baseURL` do `.env` (potrzebne dla dev/prod; nie dubluje configu DB). +- `app/Config/App.php`: `indexPage=''` (czyste URL-e) — poza pierwotną listą plików. +- Włączono `intl` w XAMPP `php.ini`; zainstalowano Composer (środowisko). +- Dev łączy się do zdalnej bazy hostido (lokalny MySQL zgaszony); produkcyjnie `localhost`. +- Dodano root `.htaccess` (naprawa 403 na hostido) — poza pierwotnym zakresem, wynikło z wdrożenia. + +## Key Decisions / Patterns + +- **Jedno źródło prawdy dla DB**: `Database.php` czyta wyłącznie klucze `.env`, wybór host wg `ENVIRONMENT`. +- **Typ operacji narzuca kategoria**: spójność wymuszana po stronie serwera (`Operations::persist`) + UX w JS. +- **CI4 na współdzielonym hostingu**: root `.htaccess` forwarduje do `public/` (wzorzec z crmPRO dla HTTPS). + +## Follow-up + +- Plan 2: moduł inwestycji — `inv_operations` (wpłaty/wypłaty) + `inv_valuations` (ręczne snapshoty wartości), wykres suma wpłat vs bieżąca wartość vs zarobek. +- Opcjonalnie: `$paul-map-codebase` (pełny indeks) przed Planem 2. +- Weryfikacja wdrożenia produkcyjnego na `https://finanse.projectpro.pl/` (docroot/`.htaccess`, `vendor/`, prawa `writable/`). diff --git a/.paul/plans/20260706-1430-modul-inwestycji/PLAN.md b/.paul/plans/20260706-1430-modul-inwestycji/PLAN.md new file mode 100644 index 0000000..cd88de8 --- /dev/null +++ b/.paul/plans/20260706-1430-modul-inwestycji/PLAN.md @@ -0,0 +1,288 @@ +--- +plan_id: 20260706-1430-modul-inwestycji +title: Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit +storage: plan-first +legacy_phase: null +created: 2026-07-06T14:30:00+02:00 +status: planned +type: execute +autonomous: true +delegation: auto +files_modified: + - app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php + - app/Database/Migrations/2026-07-06-140002_CreateInvOperations.php + - app/Database/Migrations/2026-07-06-140003_CreateInvValuations.php + - app/Database/Seeds/DefaultInstrumentsSeeder.php + - app/Models/InstrumentModel.php + - app/Models/InvOperationModel.php + - app/Models/ValuationModel.php + - app/Controllers/Instruments.php + - app/Controllers/InvOperations.php + - app/Controllers/Valuations.php + - app/Controllers/Investments.php + - app/Views/instruments/index.php + - app/Views/instruments/form.php + - app/Views/inv_operations/index.php + - app/Views/inv_operations/form.php + - app/Views/valuations/index.php + - app/Views/valuations/form.php + - app/Views/investments/index.php + - app/Config/Routes.php + - app/Views/layout/main.php +quality_radar: degraded +--- + + +## Goal +Dodać do finansePRO moduł inwestycji: płaska lista instrumentów (kategorie główne, np. obligacje, ETF), operacje wpłat/wypłat per instrument, ręczne snapshoty wyceny per instrument oraz pulpit inwestycji z wykresem zbiorczym w czasie i tabelą rozbicia per instrument. + +## Purpose +Domknięcie Planu 2 z roadmapy. Użytkownik chce śledzić, ile realnie włożył w inwestycje (netto), ile są teraz warte i ile zarobił — bez integracji z API kursów (wyceny ręczne). + +## Output +3 migracje (`inv_instruments`, `inv_operations`, `inv_valuations`), seeder instrumentów, 3 modele, 4 kontrolery (CRUD + pulpit), widoki, trasy i pozycja w nawigacji. + + + +## Project Docs +@.paul/PROJECT.md +@.paul/STATE.md +@.paul/codebase/impact_map.md +@.paul/codebase/quality_risks.md +@.paul/plans/20260706-1248-fundament-wydatki-przychody/SUMMARY.md + +## Source Files (wzorce do naśladowania) +@app/Models/OperationModel.php +@app/Controllers/Operations.php +@app/Controllers/Dashboard.php +@app/Config/Routes.php +@app/Views/layout/main.php +@app/Views/dashboard/index.php +@app/Database/Migrations/2026-07-06-100002_CreateOperations.php + + + +- Instrumenty: płaska lista kategorii głównych (obligacje, ETF, …), bez podkategorii i bez rozróżniania konkretnych funduszy. +- Zarobek per instrument: `zarobek = ostatnia_wycena − (Σwpłat − Σwypłat)`. Wypłaty zmniejszają zainwestowany kapitał (zainwestowane netto). +- Wyceny: wyłącznie ręczne snapshoty (`inv_valuations`), brak integracji z API kursów. +- Pulpit: jeden wykres zbiorczy w czasie (Σwpłat_netto | wartość | zarobek) + tabela rozbicia per instrument (wpłacono | wartość | zarobek | %). + + + +## Quality Radar + +**Status:** degraded (weryfikacja runtime + odczyt plików źródłowych; pełny `codebase-memory-mcp` odłożony — jscpd/ast-grep wyłączone polityką w `config.md`) +**Tools:** codebase-memory-mcp (nieindeksowany), fallback: bezpośredni odczyt plików + +## Affected Areas + +- Domena inwestycji (nowa): `app/Models/{Instrument,InvOperation,Valuation}Model.php`, `app/Controllers/{Instruments,InvOperations,Valuations,Investments}.php`, migracje `inv_*`, widoki `instruments/`, `inv_operations/`, `valuations/`, `investments/`. +- Routing: `app/Config/Routes.php` — nowa grupa tras w bloku `auth`. +- Nawigacja: `app/Views/layout/main.php` — jedna pozycja „Inwestycje”. +- Baza: nowe tabele `inv_instruments`, `inv_operations`, `inv_valuations` (FK do `inv_instruments`). + +## Duplicate / Hardcoded Risks + +- Ryzyko duplikacji logiki filtrów/sum wobec `OperationModel` — świadomie osobne modele (inna domena: brak `type income/expense`, dochodzą wyceny). Wzorce zapytań kopiowane celowo, kod trzymany krótki. Obsłużone: brak współdzielonego „drugiego źródła prawdy” dla wartości domenowych. +- Typ operacji inwestycyjnej `deposit|withdraw` jako ENUM w migracji + reguła walidacji `in_list` w modelu — jedno źródło (ENUM DB), walidacja to strażnik wejścia. Obsłużone w Task 1. +- Definicja zarobku (netto) liczona w jednym miejscu — `InstrumentModel`/`Investments` (metoda agregująca), nie powielana w widokach. Obsłużone w Task 3. + +## Explicit Deferrals + +- Pełna indeksacja `codebase-memory-mcp` — odłożona do `$paul-map-codebase` (spójne z Planem 1). +- Integracja kursów z API, waluty obce, podatek Belki — poza zakresem (wyceny ręczne w PLN). + + + +Brak `.paul/SPECIAL-FLOWS.md` — sekcja skills pominięta. + + + + +## AC-1: Instrumenty (CRUD, płaska lista) +```gherkin +Given zalogowany użytkownik +When otwiera /instruments i dodaje instrument "Obligacje" +Then instrument zapisuje się i pojawia na liście +And próba usunięcia instrumentu z operacjami lub wycenami jest zablokowana komunikatem +``` + +## AC-2: Operacje inwestycyjne (wpłaty/wypłaty) +```gherkin +Given istnieje instrument "ETF" +When użytkownik dodaje wpłatę 1000 zł i wypłatę 200 zł na "ETF" +Then obie operacje zapisują się z poprawnym typem (deposit/withdraw) +And lista pokazuje je z nazwą instrumentu, filtrowanie po instrumencie zwraca właściwe wiersze +``` + +## AC-3: Ręczne wyceny (snapshoty) +```gherkin +Given istnieje instrument "ETF" z wpłatami +When użytkownik dodaje wycenę 950 zł na dziś +Then wycena zapisuje się per instrument z datą +And ostatnia wycena instrumentu jest używana do wyliczenia zarobku +``` + +## AC-4: Zarobek per instrument (netto) +```gherkin +Given "ETF": Σwpłat = 1000, Σwypłat = 200, ostatnia wycena = 950 +When system liczy zarobek +Then wpłacone_netto = 800 (1000 − 200) +And zarobek = 150 (950 − 800) +``` + +## AC-5: Pulpit inwestycji (wykres zbiorczy + tabela) +```gherkin +Given istnieją instrumenty z operacjami i wycenami +When użytkownik otwiera /investments +Then widzi kafelki (wpłacono netto, wartość, zarobek) dla całego portfela +And wykres w czasie z 3 seriami: Σwpłat_netto, wartość, zarobek +And tabelę per instrument: wpłacono | wartość | zarobek | % zwrotu +``` + + + + + + + Task 1: Schemat DB + modele + seeder instrumentów + + app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php, + app/Database/Migrations/2026-07-06-140002_CreateInvOperations.php, + app/Database/Migrations/2026-07-06-140003_CreateInvValuations.php, + app/Database/Seeds/DefaultInstrumentsSeeder.php, + app/Models/InstrumentModel.php, + app/Models/InvOperationModel.php, + app/Models/ValuationModel.php + + + Migracje (wzorzec: CreateOperations — InnoDB, timestamps DATETIME null, indeksy): + - `inv_instruments`: id, name VARCHAR(120), created_at/updated_at. Unikalny indeks na `name`. + - `inv_operations`: id, instrument_id (unsigned, NOT NULL), date DATE, amount DECIMAL(12,2), + type ENUM('deposit','withdraw'), description VARCHAR(255) null, timestamps. + Indeksy: date, instrument_id, type. FK instrument_id → inv_instruments(id) ON DELETE RESTRICT + (nie kasujemy instrumentu z operacjami — spójne z blokadą w kontrolerze). + - `inv_valuations`: id, instrument_id (unsigned, NOT NULL), date DATE, value DECIMAL(12,2), timestamps. + Indeksy: date, instrument_id. FK instrument_id → inv_instruments(id) ON DELETE RESTRICT. + + Modele (returnType array, useTimestamps true, validationRules): + - InstrumentModel (tabela inv_instruments): allowedFields=['name']; reguła name required|max_length[120]. + Metoda `withStats()`: zwraca instrumenty z Σwpłat, Σwypłat, wpłacone_netto, ostatnia wycena (value + z max(date) per instrument — podzapytanie/JOIN), zarobek = ostatnia_wycena − wpłacone_netto, + procent = wpłacone_netto>0 ? zarobek/wpłacone_netto*100 : null. Metoda `hasChildren($id)`: + true jeśli instrument ma operacje lub wyceny (do blokady usunięcia). + - InvOperationModel (tabela inv_operations): allowedFields=['instrument_id','date','amount','type','description']; + reguły: instrument_id required|is_natural_no_zero, date required|valid_date[Y-m-d], + amount required|decimal|greater_than[0], type required|in_list[deposit,withdraw], + description permit_empty|max_length[255]. Metoda `filtered($f)`: JOIN nazwa instrumentu, + opcjonalny filtr instrument_id/type/from/to, orderBy date DESC. + - ValuationModel (tabela inv_valuations): allowedFields=['instrument_id','date','value']; + reguły: instrument_id required|is_natural_no_zero, date required|valid_date[Y-m-d], + value required|decimal|greater_than_equal_to[0]. Metoda `filtered($f)`: JOIN nazwa instrumentu, + orderBy date DESC. + + Seeder DefaultInstrumentsSeeder: wstaw "Obligacje" i "ETF" (idempotentnie — pomiń jeśli name istnieje). + + php spark migrate && php spark db:seed DefaultInstrumentsSeeder — 3 tabele utworzone, 2 instrumenty + AC-1 (schemat instrumentów), AC-2 (schemat operacji), AC-3 (schemat wycen), AC-4 (logika zarobku w withStats) + + + + Task 2: Kontrolery CRUD + widoki + routing + nawigacja + + app/Controllers/Instruments.php, + app/Controllers/InvOperations.php, + app/Controllers/Valuations.php, + app/Views/instruments/index.php, + app/Views/instruments/form.php, + app/Views/inv_operations/index.php, + app/Views/inv_operations/form.php, + app/Views/valuations/index.php, + app/Views/valuations/form.php, + app/Config/Routes.php, + app/Views/layout/main.php + + + Kontrolery (wzorzec: Operations/Categories — metoda model(), index/new/create/edit/update/delete, + persist() z redirectem i flashdata): + - Instruments: CRUD instrumentów. delete() blokuje gdy hasChildren($id) → redirect z error. + - InvOperations: CRUD wpłat/wypłat. index z filtrami (instrument_id/type/from/to), przekazuje listę + instrumentów do selecta. type walidowany do deposit|withdraw. + - Valuations: CRUD wycen. index z filtrem instrument_id, select instrumentów w formularzu. + + Widoki (wzorzec: operations/index + form, Bootstrap 5, flashdata z layoutu): + - instruments/index: tabela instrumentów + link do withStats? (statystyki zostają na pulpicie — tu prosty CRUD). + - inv_operations/index: filtry (instrument, typ, zakres dat) + tabela (data, instrument, typ badge, + kwota, opis, akcje). form: select instrument, date, amount, radio/select typ deposit/withdraw, opis. + - valuations/index: filtr instrument + tabela (data, instrument, wartość, akcje). form: select instrument, date, value. + + Routes.php: w grupie 'auth' dodać trasy REST-lite (jak operations) dla: + instruments, investments/operations (kontroler InvOperations), investments/valuations (kontroler Valuations), + oraz investments (Investments::index z Task 3). Prefiksy URL czytelne, bez kolizji z istniejącym /operations. + + layout/main.php: dodać jedną pozycję nav "Inwestycje" → site_url('investments') między "Operacje" a "Kategorie". + + php spark serve + curl: dodanie instrumentu, wpłaty, wypłaty, wyceny; filtr po instrumencie; blokada usunięcia instrumentu z operacjami + AC-1, AC-2, AC-3 (pełny CRUD i filtry przez UI/HTTP) + + + + Task 3: Pulpit inwestycji — agregacja, wykres zbiorczy, tabela per instrument + + app/Controllers/Investments.php, + app/Views/investments/index.php + + + Investments::index (wzorzec: Dashboard::index): + - Kafelki portfela: Σwpłacone_netto, Σwartość (suma ostatnich wycen per instrument), Σzarobek. + - Tabela per instrument z InstrumentModel::withStats(): nazwa | wpłacono netto | wartość | zarobek | % zwrotu + (zarobek kolorem: dodatni income / ujemny expense — klasy z layoutu). + - Szereg czasowy zbiorczy do wykresu: zbierz wszystkie daty wycen (distinct). Dla każdej daty: + wartość = suma po instrumentach ostatniej wyceny o dacie ≤ ta data; wpłacone_netto = Σ(deposit−withdraw) + o dacie ≤ ta data; zarobek = wartość − wpłacone_netto. Policz w PHP (metoda w Investments lub + InstrumentModel::portfolioTimeline()) — nie duplikuj logiki zarobku w widoku. + // ponytail: timeline liczony w PHP z kilku zapytań, bez okien SQL; przy setkach wycen i tak trywialne. + + Widok investments/index (wzorzec dashboard/index + ApexCharts z layoutu): + - 3 kafelki, wykres liniowy (area/line) z 3 seriami: "Wpłacono netto", "Wartość", "Zarobek", + xaxis = daty wycen; noData gdy pusto. + - tabela per instrument pod wykresem. + + curl /investments: kafelki i tabela zgodne z ręcznym wyliczeniem (ETF: netto 800, wartość 950, zarobek 150); JSON serii wykresu poprawny + AC-4 (zarobek netto), AC-5 (pulpit: kafelki + wykres + tabela) + + + + + +## Do Not Change +- Moduł finansów bieżących (categories, operations, dashboard) — działa, nie ruszać poza dodaniem 1 pozycji nav. +- `app/Config/Database.php`, `.env`, auth/filtry — bez zmian. +- Istniejące migracje `categories`/`operations` i seeder kategorii. + +## Scope Limits +- Bez integracji API kursów, walut obcych, podatków, prowizji. +- Bez podkategorii instrumentów (płaska lista). +- Bez edycji wielu instrumentów jednym formularzem / importu CSV. + + + +- [ ] `php spark migrate` — 3 nowe tabele `inv_*` na bazie hostido. +- [ ] `php spark db:seed DefaultInstrumentsSeeder` — Obligacje, ETF. +- [ ] CRUD instrumentów/operacji/wycen przez HTTP (create/edit/delete, filtry). +- [ ] Blokada usunięcia instrumentu z operacjami/wycenami (FK RESTRICT + kontroler). +- [ ] Zarobek per instrument = ostatnia wycena − (Σwpłat − Σwypłat); przykład ETF = 150. +- [ ] Pulpit /investments: kafelki + wykres 3 serie + tabela per instrument. +- [ ] Nawigacja pokazuje "Inwestycje"; brak regresji w istniejących modułach. +- [ ] Quality Radar relevant risks handled or deferred. + + + +- [ ] Wszystkie AC (AC-1..AC-5) Pass. +- [ ] Weryfikacja runtime kompletna (migrate/seed/serve/curl). +- [ ] STATE.md wskazuje ten plan; radar delta zapisana w UNIFY. + + + +SUMMARY.md path: `.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md` + diff --git a/.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md b/.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md new file mode 100644 index 0000000..99e4813 --- /dev/null +++ b/.paul/plans/20260706-1430-modul-inwestycji/SUMMARY.md @@ -0,0 +1,88 @@ +--- +plan_id: 20260706-1430-modul-inwestycji +title: Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit +completed: 2026-07-06T14:55:00+02:00 +storage: plan-first +quality_radar: degraded +--- + +# Summary: Moduł inwestycji — instrumenty, wpłaty/wypłaty, ręczne wyceny, pulpit + +## Objective + +Dodać moduł inwestycji: płaska lista instrumentów (kategorie główne, np. Obligacje, ETF), operacje wpłat/wypłat per instrument, ręczne snapshoty wyceny per instrument oraz pulpit inwestycji z wykresem zbiorczym w czasie i tabelą per instrument. W trakcie sesji dodano też pulpit globalny (`/dashboard`) i przepięto dotychczasowy pulpit finansów pod `/finances`. + +## What Was Built + +| Area | Result | +|------|--------| +| Schemat DB | 3 migracje `inv_instruments`, `inv_operations` (FK RESTRICT), `inv_valuations` (FK RESTRICT); seeder Obligacje/ETF | +| Modele | `InstrumentModel` (`withStats`, `portfolioTimeline`, `hasChildren` — logika zarobku netto w jednym miejscu), `InvOperationModel`, `ValuationModel` | +| CRUD | Instrumenty, operacje inwestycyjne (wpłaty/wypłaty), wyceny — pełny CRUD, filtry, blokada usunięcia instrumentu z powiązaniami | +| Pulpit inwestycji | `/investments`: kafelki (netto/wartość/zarobek), wykres liniowy 3-seryjny w czasie, tabela per instrument z % zwrotu | +| Pulpit globalny | `/dashboard`: kafel „Majątek razem" (saldo bieżące + wartość inwestycji) + sekcje Finanse/Inwestycje z linkami | +| Pulpit finansów | dotychczasowy dashboard przepięty pod `/finances` (`Dashboard::finances`) | +| Nawigacja | top-level „Pulpit" + dropdown „Finanse bieżące" (Pulpit finansów/Operacje/Kategorie) + dropdown „Inwestycje" (Pulpit/Wpłaty-wypłaty/Wyceny/Instrumenty) | + +## Files Modified + +- `app/Database/Migrations/2026-07-06-140001_CreateInvInstruments.php`, `...140002_CreateInvOperations.php`, `...140003_CreateInvValuations.php` — schemat inwestycji +- `app/Database/Seeds/DefaultInstrumentsSeeder.php` — idempotentny seed Obligacje/ETF +- `app/Models/InstrumentModel.php`, `InvOperationModel.php`, `ValuationModel.php` — domena inwestycji +- `app/Controllers/Instruments.php`, `InvOperations.php`, `Valuations.php`, `Investments.php` — CRUD + pulpit inwestycji +- `app/Controllers/Dashboard.php` — `index()` globalny + `finances()` +- `app/Views/instruments/{index,form}.php`, `inv_operations/{index,form}.php`, `valuations/{index,form}.php`, `investments/index.php` — widoki inwestycji +- `app/Views/dashboard/index.php` (globalny, przepisany), `app/Views/dashboard/finances.php` (przeniesiony finansowy) +- `app/Config/Routes.php` — trasy `investments/*`, `instruments/*`, `finances` +- `app/Views/layout/main.php` — nawigacja zgrupowana w dropdowny + +## Acceptance Criteria Results + +| Criterion | Status | Evidence | +|-----------|--------|----------| +| AC-1: instrumenty CRUD + blokada usunięcia | Pass | curl: dodanie OK; usunięcie ETF z operacjami → 302 + „Nie można usunąć instrumentu z operacjami lub wycenami" | +| AC-2: operacje wpłata/wypłata + filtry | Pass | curl: deposit/withdraw zapisane; filtr `type=withdraw` zwraca wiersz 200,00; `instrument_id=1` → brak | +| AC-3: ręczne wyceny per instrument | Pass | curl: wycena 950 na 2026-07-01 zapisana, użyta do zarobku | +| AC-4: zarobek netto | Pass | ETF: netto 800,00 (1000−200), zarobek 150,00 (950−800), 18,75% | +| AC-5: pulpit inwestycji | Pass | kafelki 800/950/150; `timeline=[{date:2026-07-01,net:800,value:950,profit:150}]`; tabela per instrument | + +## Verification Results + +| Check | Result | Notes | +|-------|--------|-------| +| `php spark migrate` | Pass | 3 tabele `inv_*` na bazie hostido | +| `php spark db:seed DefaultInstrumentsSeeder` | Pass | Obligacje, ETF (idempotentnie) | +| `php -l` (7 nowych plików PHP + Dashboard) | Pass | brak błędów składni | +| `php spark serve` + curl (login/CRUD/filtry/blokada/pulpity) | Pass | pełny przepływ end-to-end | +| Zarobek netto ETF = 150 (18,75%) | Pass | zgodne z ręcznym wyliczeniem | +| `/dashboard` globalny + `/finances` | Pass | globalny: majątek + sekcje; finances: 200 + 3 wykresy | +| Dane testowe | Cleaned | `inv_operations=0`, `inv_valuations=0`, seed instrumentów zachowany | + +## Quality Radar Results + +**Status:** degraded + +- New risks: brak nowych ryzyk duplikacji (jscpd/ast-grep wyłączone polityką). Kopiowanie wzorców zapytań z `OperationModel` świadome — osobna domena. +- Resolved risks: logika zarobku netto w jednym miejscu (`InstrumentModel::withStats`), nie powielana w widokach; typ operacji `deposit|withdraw` jako ENUM + walidacja `in_list`. +- Deferred risks: pełna indeksacja `codebase-memory-mcp` odłożona do `$paul-map-codebase`; weryfikacja runtime. +- Raw outputs: `.paul/codebase/tooling_status.md` + +## Deviations + +- **Wykonanie inline zamiast `delegation: auto`** — 3 zadania ściśle sekwencyjne, wspólne pliki (`Routes.php`, modele); delegacja tylko powielałaby odczyty wzorców. +- **`.env` toggle** — tymczasowo `production→development` na czas migracji/testów (lokalny MySQL zgaszony, dev łączy do zdalnej hostido), po weryfikacji przywrócony na `production`. Netto zero zmian, boundary `.env` zachowany. +- **Nawigacja rozszerzona** — zamiast jednego linku „Inwestycje" powstały dwa dropdowny (na prośbę użytkownika o zgrupowanie wcześniejszych pozycji). +- **Pulpit globalny + `/finances`** — poza pierwotnym zakresem planu; dodane na prośbę użytkownika (globalny `/dashboard`, finansowy przepięty pod `/finances`). + +## Key Decisions / Patterns + +- **Jedno źródło prawdy dla zarobku**: `InstrumentModel::withStats` liczy `net_invested = Σwpłat − Σwypłat`, `profit = ostatnia_wycena − net_invested`; kontrolery/widoki tylko konsumują. +- **FK RESTRICT** na `inv_operations`/`inv_valuations` — instrument z powiązaniami nie da się skasować (spójne z blokadą w kontrolerze). +- **Szereg czasowy portfela** liczony w PHP (`portfolioTimeline`) z kilku prostych zapytań — bez okien SQL; trywialne przy ręcznych wycenach. +- **Pulpit globalny** agreguje majątek = saldo bieżące + wartość inwestycji; szczegóły delegowane do `/finances` i `/investments`. + +## Follow-up + +- Wdrożenie produkcyjne `https://finanse.projectpro.pl/` — wgrać pliki + `vendor/`, uruchomić `php spark migrate` na serwerze (3 nowe tabele `inv_*`); `CI_ENVIRONMENT=production`. Schemat finansów bez zmian. +- Opcjonalnie: `$paul-map-codebase` (pełny indeks codebase-memory-mcp) przed kolejnym planem. +- Możliwe rozszerzenia: wykres per instrument, historia wartości vs wpłat na osi czasu z interpolacją, eksport. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..24728f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014-2019 British Columbia Institute of Technology +Copyright (c) 2019-present CodeIgniter Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..45f98af --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# CodeIgniter 4 Application Starter + +## What is CodeIgniter? + +CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure. +More information can be found at the [official site](https://codeigniter.com). + +This repository holds a composer-installable app starter. +It has been built from the +[development repository](https://github.com/codeigniter4/CodeIgniter4). + +More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums. + +You can read the [user guide](https://codeigniter.com/user_guide/) +corresponding to the latest version of the framework. + +## Installation & updates + +`composer create-project codeigniter4/appstarter` then `composer update` whenever +there is a new release of the framework. + +When updating, check the release notes to see if there are any changes you might need to apply +to your `app` folder. The affected files can be copied or merged from +`vendor/codeigniter4/framework/app`. + +## Setup + +Copy `env` to `.env` and tailor for your app, specifically the baseURL +and any database settings. + +## Important Change with index.php + +`index.php` is no longer in the root of the project! It has been moved inside the *public* folder, +for better security and separation of components. + +This means that you should configure your web server to "point" to your project's *public* folder, and +not to the project root. A better practice would be to configure a virtual host to point there. A poor practice would be to point your web server to the project root and expect to enter *public/...*, as the rest of your logic and the +framework are exposed. + +**Please** read the user guide for a better explanation of how CI4 works! + +## Repository Management + +We use GitHub issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages. +We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss +FEATURE REQUESTS. + +This repository is a "distribution" one, built by our release preparation script. +Problems with it can be raised on our forum, or as issues in the main repository. + +## Server Requirements + +PHP version 8.2 or higher is required, with the following extensions installed: + +- [intl](http://php.net/manual/en/intl.requirements.php) +- [mbstring](http://php.net/manual/en/mbstring.installation.php) + +> [!WARNING] +> - The end of life date for PHP 7.4 was November 28, 2022. +> - The end of life date for PHP 8.0 was November 26, 2023. +> - The end of life date for PHP 8.1 was December 31, 2025. +> - If you are still using below PHP 8.2, you should upgrade immediately. +> - The end of life date for PHP 8.2 will be December 31, 2026. + +Additionally, make sure that the following extensions are enabled in your PHP: + +- json (enabled by default - don't turn it off) +- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL +- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3462048 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/app/Common.php b/app/Common.php new file mode 100644 index 0000000..95f5544 --- /dev/null +++ b/app/Common.php @@ -0,0 +1,15 @@ + + */ + public array $allowedHostnames = []; + + /** + * -------------------------------------------------------------------------- + * Index File + * -------------------------------------------------------------------------- + * + * Typically, this will be your `index.php` file, unless you've renamed it to + * something else. If you have configured your web server to remove this file + * from your site URIs, set this variable to an empty string. + */ + public string $indexPage = ''; + + /** + * -------------------------------------------------------------------------- + * URI PROTOCOL + * -------------------------------------------------------------------------- + * + * This item determines which server global should be used to retrieve the + * URI string. The default setting of 'REQUEST_URI' works for most servers. + * If your links do not seem to work, try one of the other delicious flavors: + * + * 'REQUEST_URI': Uses $_SERVER['REQUEST_URI'] + * 'QUERY_STRING': Uses $_SERVER['QUERY_STRING'] + * 'PATH_INFO': Uses $_SERVER['PATH_INFO'] + * + * WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded! + */ + public string $uriProtocol = 'REQUEST_URI'; + + /* + |-------------------------------------------------------------------------- + | Allowed URL Characters + |-------------------------------------------------------------------------- + | + | This lets you specify which characters are permitted within your URLs. + | When someone tries to submit a URL with disallowed characters they will + | get a warning message. + | + | As a security measure you are STRONGLY encouraged to restrict URLs to + | as few characters as possible. + | + | By default, only these are allowed: `a-z 0-9~%.:_-` + | + | Set an empty string to allow all characters -- but only if you are insane. + | + | The configured value is actually a regular expression character group + | and it will be used as: '/\A[]+\z/iu' + | + | DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! + | + */ + public string $permittedURIChars = 'a-z 0-9~%.:_\-'; + + /** + * -------------------------------------------------------------------------- + * Default Locale + * -------------------------------------------------------------------------- + * + * The Locale roughly represents the language and location that your visitor + * is viewing the site from. It affects the language strings and other + * strings (like currency markers, numbers, etc), that your program + * should run under for this request. + */ + public string $defaultLocale = 'en'; + + /** + * -------------------------------------------------------------------------- + * Negotiate Locale + * -------------------------------------------------------------------------- + * + * If true, the current Request object will automatically determine the + * language to use based on the value of the Accept-Language header. + * + * If false, no automatic detection will be performed. + */ + public bool $negotiateLocale = false; + + /** + * -------------------------------------------------------------------------- + * Supported Locales + * -------------------------------------------------------------------------- + * + * If $negotiateLocale is true, this array lists the locales supported + * by the application in descending order of priority. If no match is + * found, the first locale will be used. + * + * IncomingRequest::setLocale() also uses this list. + * + * @var list + */ + public array $supportedLocales = ['en']; + + /** + * -------------------------------------------------------------------------- + * Application Timezone + * -------------------------------------------------------------------------- + * + * The default timezone that will be used in your application to display + * dates with the date helper, and can be retrieved through app_timezone() + * + * @see https://www.php.net/manual/en/timezones.php for list of timezones + * supported by PHP. + */ + public string $appTimezone = 'UTC'; + + /** + * -------------------------------------------------------------------------- + * Default Character Set + * -------------------------------------------------------------------------- + * + * This determines which character set is used by default in various methods + * that require a character set to be provided. + * + * @see http://php.net/htmlspecialchars for a list of supported charsets. + */ + public string $charset = 'UTF-8'; + + /** + * -------------------------------------------------------------------------- + * Force Global Secure Requests + * -------------------------------------------------------------------------- + * + * If true, this will force every request made to this application to be + * made via a secure connection (HTTPS). If the incoming request is not + * secure, the user will be redirected to a secure version of the page + * and the HTTP Strict Transport Security (HSTS) header will be set. + */ + public bool $forceGlobalSecureRequests = false; + + /** + * -------------------------------------------------------------------------- + * Reverse Proxy IPs + * -------------------------------------------------------------------------- + * + * If your server is behind a reverse proxy, you must whitelist the proxy + * IP addresses from which CodeIgniter should trust headers such as + * X-Forwarded-For or Client-IP in order to properly identify + * the visitor's IP address. + * + * You need to set a proxy IP address or IP address with subnets and + * the HTTP header for the client IP address. + * + * Here are some examples: + * [ + * '10.0.1.200' => 'X-Forwarded-For', + * '192.168.5.0/24' => 'X-Real-IP', + * ] + * + * @var array + */ + public array $proxyIPs = []; + + /** + * -------------------------------------------------------------------------- + * Content Security Policy + * -------------------------------------------------------------------------- + * + * Enables the Response's Content Secure Policy to restrict the sources that + * can be used for images, scripts, CSS files, audio, video, etc. If enabled, + * the Response object will populate default values for the policy from the + * `ContentSecurityPolicy.php` file. Controllers can always add to those + * restrictions at run time. + * + * For a better understanding of CSP, see these documents: + * + * @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/ + * @see http://www.w3.org/TR/CSP/ + */ + public bool $CSPEnabled = false; +} diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php new file mode 100644 index 0000000..9a92824 --- /dev/null +++ b/app/Config/Autoload.php @@ -0,0 +1,92 @@ +|string> + */ + public $psr4 = [ + APP_NAMESPACE => APPPATH, + ]; + + /** + * ------------------------------------------------------------------- + * Class Map + * ------------------------------------------------------------------- + * The class map provides a map of class names and their exact + * location on the drive. Classes loaded in this manner will have + * slightly faster performance because they will not have to be + * searched for within one or more directories as they would if they + * were being autoloaded through a namespace. + * + * Prototype: + * $classmap = [ + * 'MyClass' => '/path/to/class/file.php' + * ]; + * + * @var array + */ + public $classmap = []; + + /** + * ------------------------------------------------------------------- + * Files + * ------------------------------------------------------------------- + * The files array provides a list of paths to __non-class__ files + * that will be autoloaded. This can be useful for bootstrap operations + * or for loading functions. + * + * Prototype: + * $files = [ + * '/path/to/my/file.php', + * ]; + * + * @var list + */ + public $files = []; + + /** + * ------------------------------------------------------------------- + * Helpers + * ------------------------------------------------------------------- + * Prototype: + * $helpers = [ + * 'form', + * ]; + * + * @var list + */ + public $helpers = []; +} diff --git a/app/Config/Boot/development.php b/app/Config/Boot/development.php new file mode 100644 index 0000000..a868447 --- /dev/null +++ b/app/Config/Boot/development.php @@ -0,0 +1,34 @@ + + * + * @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect + */ + public array $shareConnectionOptions = [ + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_DNS, + ]; + + /** + * -------------------------------------------------------------------------- + * CURLRequest Share Options + * -------------------------------------------------------------------------- + * + * Whether share options between requests or not. + * + * If true, all the options won't be reset between requests. + * It may cause an error request with unnecessary headers. + */ + public bool $shareOptions = false; +} diff --git a/app/Config/Cache.php b/app/Config/Cache.php new file mode 100644 index 0000000..38ac541 --- /dev/null +++ b/app/Config/Cache.php @@ -0,0 +1,198 @@ + WRITEPATH . 'cache/', + 'mode' => 0640, + ]; + + /** + * ------------------------------------------------------------------------- + * Memcached settings + * ------------------------------------------------------------------------- + * + * Your Memcached servers can be specified below, if you are using + * the Memcached drivers. + * + * @see https://codeigniter.com/user_guide/libraries/caching.html#memcached + * + * @var array{host?: string, port?: int, weight?: int, raw?: bool} + */ + public array $memcached = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1, + 'raw' => false, + ]; + + /** + * ------------------------------------------------------------------------- + * Redis settings + * ------------------------------------------------------------------------- + * + * Your Redis server can be specified below, if you are using + * the Redis or Predis drivers. + * + * @var array{ + * host?: string, + * password?: string|null, + * port?: int, + * timeout?: int, + * async?: bool, + * persistent?: bool, + * database?: int + * } + */ + public array $redis = [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'timeout' => 0, + 'async' => false, // specific to Predis and ignored by the native Redis extension + 'persistent' => false, + 'database' => 0, + ]; + + /** + * -------------------------------------------------------------------------- + * Available Cache Handlers + * -------------------------------------------------------------------------- + * + * This is an array of cache engine alias' and class names. Only engines + * that are listed here are allowed to be used. + * + * @var array> + */ + public array $validHandlers = [ + 'apcu' => ApcuHandler::class, + 'dummy' => DummyHandler::class, + 'file' => FileHandler::class, + 'memcached' => MemcachedHandler::class, + 'predis' => PredisHandler::class, + 'redis' => RedisHandler::class, + 'wincache' => WincacheHandler::class, + ]; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Include Query String + * -------------------------------------------------------------------------- + * + * Whether to take the URL query string into consideration when generating + * output cache files. Valid options are: + * + * false = Disabled + * true = Enabled, take all query parameters into account. + * Please be aware that this may result in numerous cache + * files generated for the same page over and over again. + * ['q'] = Enabled, but only take into account the specified list + * of query parameters. + * + * @var bool|list + */ + public $cacheQueryString = false; + + /** + * -------------------------------------------------------------------------- + * Web Page Caching: Cache Status Codes + * -------------------------------------------------------------------------- + * + * HTTP status codes that are allowed to be cached. Only responses with + * these status codes will be cached by the PageCache filter. + * + * Default: [] - Cache all status codes (backward compatible) + * + * Recommended: [200] - Only cache successful responses + * + * You can also use status codes like: + * [200, 404, 410] - Cache successful responses and specific error codes + * [200, 201, 202, 203, 204] - All 2xx successful responses + * + * WARNING: Using [] may cache temporary error pages (404, 500, etc). + * Consider restricting to [200] for production applications to avoid + * caching errors that should be temporary. + * + * @var list + */ + public array $cacheStatusCodes = []; +} diff --git a/app/Config/Constants.php b/app/Config/Constants.php new file mode 100644 index 0000000..fb56bb1 --- /dev/null +++ b/app/Config/Constants.php @@ -0,0 +1,79 @@ +|string|null + */ + public $defaultSrc; + + /** + * Lists allowed scripts' URLs. + * + * @var list|string + */ + public $scriptSrc = 'self'; + + /** + * Specifies valid sources for JavaScript +endSection() ?> diff --git a/app/Views/dashboard/index.php b/app/Views/dashboard/index.php new file mode 100644 index 0000000..c4f84f0 --- /dev/null +++ b/app/Views/dashboard/index.php @@ -0,0 +1,52 @@ +extend('layout/main') ?> + +section('content') ?> + number_format((float) $v, 2, ',', ' ') . ' zł'; ?> + +

Pulpit

+ +
+
+
Majątek razem (saldo bieżące + wartość inwestycji)
+
+ +
+
+
+ +
+
+
+
+

Finanse bieżące (rok )

+ Szczegóły → +
+
+
Przychody
+
+
Wydatki
+
+
Saldo
+
+
+
+
+ +
+
+
+

Inwestycje

+ Szczegóły → +
+
+
Wpłacono netto
+
+
Wartość
+
+
Zarobek
+
+
+
+
+
+endSection() ?> diff --git a/app/Views/errors/cli/error_404.php b/app/Views/errors/cli/error_404.php new file mode 100644 index 0000000..456ea3e --- /dev/null +++ b/app/Views/errors/cli/error_404.php @@ -0,0 +1,7 @@ +getFile()) . ':' . $exception->getLine(), 'green')); +CLI::newLine(); + +$last = $exception; + +while ($prevException = $last->getPrevious()) { + $last = $prevException; + + CLI::write(' Caused by:'); + CLI::write(' [' . $prevException::class . ']', 'red'); + CLI::write(' ' . $prevException->getMessage()); + CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green')); + CLI::newLine(); +} + +// The backtrace +if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { + $backtraces = $last->getTrace(); + + if ($backtraces) { + CLI::write('Backtrace:', 'green'); + } + + foreach ($backtraces as $i => $error) { + $padFile = ' '; // 4 spaces + $padClass = ' '; // 7 spaces + $c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT); + + if (isset($error['file'])) { + $filepath = clean_path($error['file']) . ':' . $error['line']; + + CLI::write($c . $padFile . CLI::color($filepath, 'yellow')); + } else { + CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow')); + } + + $function = ''; + + if (isset($error['class'])) { + $type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type']; + $function .= $padClass . $error['class'] . $type . $error['function']; + } elseif (! isset($error['class']) && isset($error['function'])) { + $function .= $padClass . $error['function']; + } + + $args = implode(', ', array_map(static fn ($value): string => match (true) { + is_object($value) => 'Object(' . $value::class . ')', + is_array($value) => $value !== [] ? '[...]' : '[]', + $value === null => 'null', // return the lowercased version + default => var_export($value, true), + }, array_values($error['args'] ?? []))); + + $function .= '(' . $args . ')'; + + CLI::write($function); + CLI::newLine(); + } +} diff --git a/app/Views/errors/cli/production.php b/app/Views/errors/cli/production.php new file mode 100644 index 0000000..7db744e --- /dev/null +++ b/app/Views/errors/cli/production.php @@ -0,0 +1,5 @@ + + + + + <?= lang('Errors.badRequest') ?> + + + + +
+

400

+ +

+ + + + + +

+
+ + diff --git a/app/Views/errors/html/error_404.php b/app/Views/errors/html/error_404.php new file mode 100644 index 0000000..e506f08 --- /dev/null +++ b/app/Views/errors/html/error_404.php @@ -0,0 +1,84 @@ + + + + + <?= lang('Errors.pageNotFound') ?> + + + + +
+

404

+ +

+ + + + + +

+
+ + diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php new file mode 100644 index 0000000..2c4e009 --- /dev/null +++ b/app/Views/errors/html/error_exception.php @@ -0,0 +1,429 @@ + + + + + + + + <?= esc($title) ?> + + + + + + + +
+
+ Displayed at — + PHP: — + CodeIgniter: -- + Environment: +
+
+

getCode() ? ' #' . $exception->getCode() : '') ?>

+

+ getMessage())) ?> + getMessage())) ?>" + rel="noreferrer" target="_blank">search → +

+
+
+ + +
+

at line

+ + +
+ +
+ +
+ +
+ getPrevious()) { + $last = $prevException; + ?> + +
+    Caused by:
+    getCode() ? ' #' . $prevException->getCode() : '') ?>
+
+    getMessage())) ?>
+    getMessage())) ?>"
+       rel="noreferrer" target="_blank">search →
+    getFile()) . ':' . $prevException->getLine()) ?>
+    
+ + +
+ + +
+ + + +
+ + +
+ +
    + $row) : ?> + +
  1. +

    + + + + + {PHP internal code} + + + + +   —   + + + ( arguments ) +

    + + + getParameters(); + } + + foreach ($row['args'] as $key => $value) : ?> + + + + + + +
    name : "#{$key}") ?>
    +
    + + () + + + + +   —   () + +

    + + + +
    + +
    + +
  2. + + +
+ +
+ + +
+ + + +

$

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + +
+ +
+ + + + + + +

Constants

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + +
+ +
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathgetUri()) ?>
HTTP MethodgetMethod()) ?>
IP AddressgetIPAddress()) ?>
Is AJAX Request?isAJAX() ? 'yes' : 'no' ?>
Is CLI Request?isCLI() ? 'yes' : 'no' ?>
Is Secure Request?isSecure() ? 'yes' : 'no' ?>
User AgentgetUserAgent()->getAgentString()) ?>
+ + + + + + + + +

$

+ + + + + + + + + + $value) : ?> + + + + + + +
KeyValue
+ + + +
+ +
+ + + + + +
+ No $_GET, $_POST, or $_COOKIE Information to show. +
+ + + + headers(); ?> + + +

Headers

+ + + + + + + + + + $value) : ?> + + + + + + +
HeaderValue
+ getValueLine(), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +
+ + +
+ + + setStatusCode(http_response_code()); + ?> +
+ + + + + +
Response StatusgetStatusCode() . ' - ' . $response->getReasonPhrase()) ?>
+ + headers(); ?> + +

Headers

+ + + + + + + + + + $value) : ?> + + + + + + +
HeaderValue
+ getHeaderLine($name), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +
+ + +
+ + +
+ + +
    + +
  1. + +
+
+ + +
+ + + + + + + + + + + + + + + + +
Memory Usage
Peak Memory Usage:
Memory Limit:
+ +
+ +
+ +
+ + + + diff --git a/app/Views/errors/html/production.php b/app/Views/errors/html/production.php new file mode 100644 index 0000000..2f59a8d --- /dev/null +++ b/app/Views/errors/html/production.php @@ -0,0 +1,25 @@ + + + + + + + <?= lang('Errors.whoops') ?> + + + + + +
+ +

+ +

+ +
+ + + + diff --git a/app/Views/instruments/form.php b/app/Views/instruments/form.php new file mode 100644 index 0000000..45f7bee --- /dev/null +++ b/app/Views/instruments/form.php @@ -0,0 +1,23 @@ +extend('layout/main') ?> + +section('content') ?> +

+ +
+
+ + 'row g-3']) ?> +
+ + +
+
+ + Anuluj +
+ +
+
+endSection() ?> diff --git a/app/Views/instruments/index.php b/app/Views/instruments/index.php new file mode 100644 index 0000000..779ce6c --- /dev/null +++ b/app/Views/instruments/index.php @@ -0,0 +1,49 @@ +extend('layout/main') ?> + +section('content') ?> + number_format((float) $v, 2, ',', ' ') . ' zł'; ?> + +
+

Instrumenty

+ + Nowy instrument +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
InstrumentWpłacono nettoWartośćZarobek%Akcje
Brak instrumentów.
brak wyceny' ?> + + + + + Edytuj + Usuń +
+
+
+endSection() ?> diff --git a/app/Views/inv_operations/form.php b/app/Views/inv_operations/form.php new file mode 100644 index 0000000..0b28117 --- /dev/null +++ b/app/Views/inv_operations/form.php @@ -0,0 +1,52 @@ +extend('layout/main') ?> + +section('content') ?> +

+ +
+
+ + 'row g-3']) ?> +
+ + + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + Anuluj +
+ +
+
+endSection() ?> diff --git a/app/Views/inv_operations/index.php b/app/Views/inv_operations/index.php new file mode 100644 index 0000000..2f40f9f --- /dev/null +++ b/app/Views/inv_operations/index.php @@ -0,0 +1,89 @@ +extend('layout/main') ?> + +section('content') ?> + number_format((float) $v, 2, ',', ' ') . ' zł'; ?> + +
+

Operacje inwestycyjne

+ + Nowa operacja +
+ +
+
+ 'get', 'class' => 'row g-2 align-items-end']) ?> +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
DataInstrumentTypOpisKwotaAkcje
Brak operacji dla wybranych filtrów.
+ + + + + + + Edytuj + Usuń +
+
+
+endSection() ?> diff --git a/app/Views/investments/index.php b/app/Views/investments/index.php new file mode 100644 index 0000000..b4ad04f --- /dev/null +++ b/app/Views/investments/index.php @@ -0,0 +1,88 @@ +extend('layout/main') ?> + +section('content') ?> + number_format((float) $v, 2, ',', ' ') . ' zł'; ?> + +
+

Pulpit inwestycji

+ +
+ +
+
+
Wpłacono netto
+
+
+
+
Bieżąca wartość
+
+
+
+
Zarobek
+
+
+
+ +
+

Portfel w czasie

+
+
+ +
+

Rozbicie na instrumenty

+
+ + + + + + + + + + + + + + + + + + + + + + + +
InstrumentWpłacono nettoWartośćZarobek% zwrotu
Brak instrumentów.
brak wyceny' ?> + + + +
+
+
+endSection() ?> + +section('scripts') ?> + +endSection() ?> diff --git a/app/Views/layout/main.php b/app/Views/layout/main.php new file mode 100644 index 0000000..6d39c35 --- /dev/null +++ b/app/Views/layout/main.php @@ -0,0 +1,66 @@ + + + + + + <?= esc($title ?? 'finansePRO') ?> + + + + +get('logged_in')): ?> + + + +
+ getFlashdata('error')): ?> +
getFlashdata('error')) ?>
+ + getFlashdata('success')): ?> +
getFlashdata('success')) ?>
+ + + renderSection('content') ?> +
+ + + +renderSection('scripts') ?> + + diff --git a/app/Views/operations/form.php b/app/Views/operations/form.php new file mode 100644 index 0000000..210d300 --- /dev/null +++ b/app/Views/operations/form.php @@ -0,0 +1,72 @@ +extend('layout/main') ?> + +section('content') ?> +

+ +
+
+ + 'row g-3']) ?> +
+ + +
+
+ + +
+
+ + + +
Wybór kategorii ustawia typ automatycznie.
+
+
+ + + +
+
+ + +
+
+ + Anuluj +
+ +
+
+endSection() ?> + +section('scripts') ?> + +endSection() ?> diff --git a/app/Views/operations/index.php b/app/Views/operations/index.php new file mode 100644 index 0000000..b45b37f --- /dev/null +++ b/app/Views/operations/index.php @@ -0,0 +1,107 @@ +extend('layout/main') ?> + +section('content') ?> + number_format((float) $v, 2, ',', ' ') . ' zł'; ?> + +
+

Operacje

+ + Nowa operacja +
+ +
+
+ 'get', 'class' => 'row g-2 align-items-end']) ?> +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+ +
+
+
Przychody
+
+
+
+
Wydatki
+
+
+
+
Saldo
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
DataKategoriaOpisKwotaAkcje
Brak operacji dla wybranych filtrów.
+ + + + + Edytuj + Usuń +
+
+
+endSection() ?> diff --git a/app/Views/valuations/form.php b/app/Views/valuations/form.php new file mode 100644 index 0000000..e7d1438 --- /dev/null +++ b/app/Views/valuations/form.php @@ -0,0 +1,39 @@ +extend('layout/main') ?> + +section('content') ?> +

+ +
+
+ + 'row g-3']) ?> +
+ + + +
+
+ + +
+
+ + +
+
+ + Anuluj +
+ +
+
+endSection() ?> diff --git a/app/Views/valuations/index.php b/app/Views/valuations/index.php new file mode 100644 index 0000000..4795e5f --- /dev/null +++ b/app/Views/valuations/index.php @@ -0,0 +1,62 @@ +extend('layout/main') ?> + +section('content') ?> + number_format((float) $v, 2, ',', ' ') . ' zł'; ?> + +
+

Wyceny

+ + Nowa wycena +
+ +
+
+ 'get', 'class' => 'row g-2 align-items-end']) ?> +
+ + +
+
+ +
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
DataInstrumentWartośćAkcje
Brak wycen.
+ Edytuj + Usuń +
+
+
+endSection() ?> diff --git a/app/Views/welcome_message.php b/app/Views/welcome_message.php new file mode 100644 index 0000000..c18eca3 --- /dev/null +++ b/app/Views/welcome_message.php @@ -0,0 +1,331 @@ + + + + + Welcome to CodeIgniter 4! + + + + + + + + + + + +
+ + + +
+ +

Welcome to CodeIgniter

+ +

The small framework with powerful features

+ +
+ +
+ + + +
+ +

About this page

+ +

The page you are looking at is being generated dynamically by CodeIgniter.

+ +

If you would like to edit this page you will find it located at:

+ +
app/Views/welcome_message.php
+ +

The corresponding controller for this page can be found at:

+ +
app/Controllers/Home.php
+ +
+ +
+ +
+ +

Go further

+ +

+ + Learn +

+ +

The User Guide contains an introduction, tutorial, a number of "how to" + guides, and then reference documentation for the components that make up + the framework. Check the User Guide !

+ +

+ + Discuss +

+ +

CodeIgniter is a community-developed open source project, with several + venues for the community members to gather and exchange ideas. View all + the threads on CodeIgniter's forum, or chat on Slack !

+ +

+ + Contribute +

+ +

CodeIgniter is a community driven project and accepts contributions + of code and documentation from the community. Why not + + join us ?

+ +
+ +
+ + + +
+
+ +

Page rendered in {elapsed_time} seconds using {memory_usage} MB of memory.

+ +

Environment:

+ +
+ +
+ +

© CodeIgniter Foundation. CodeIgniter is open source project released under the MIT + open source licence.

+ +
+ +
+ + + + + + + + + diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..69df4e1 --- /dev/null +++ b/app/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/builds b/builds new file mode 100644 index 0000000..c68c089 --- /dev/null +++ b/builds @@ -0,0 +1,128 @@ +#!/usr/bin/env php + 'vcs', + 'url' => GITHUB_URL, + ]; + } + + $array['require']['codeigniter4/codeigniter4'] = $branch === 'next' ? NEXT_MINOR : DEVELOP_BRANCH; + unset($array['require']['codeigniter4/framework']); + } else { + unset($array['minimum-stability']); + + if (isset($array['repositories'])) { + foreach ($array['repositories'] as $i => $repository) { + if ($repository['url'] === GITHUB_URL) { + unset($array['repositories'][$i]); + break; + } + } + + if ($array['repositories'] === []) { + unset($array['repositories']); + } + } + + $array['require']['codeigniter4/framework'] = LATEST_RELEASE; + unset($array['require']['codeigniter4/codeigniter4']); + } + + file_put_contents($file, json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL); + + $modified[] = $file; + } else { + echo 'Warning: Unable to decode composer.json! Skipping...' . PHP_EOL; + } + } else { + echo 'Warning: Unable to read composer.json! Skipping...' . PHP_EOL; + } +} + +$files = [ + __DIR__ . DIRECTORY_SEPARATOR . 'app/Config/Paths.php', + __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.dist.xml', + __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml', +]; + +foreach ($files as $file) { + if (is_file($file)) { + $contents = file_get_contents($file); + + if ($branch !== 'release') { + $contents = str_replace('vendor/codeigniter4/framework', 'vendor/codeigniter4/codeigniter4', $contents); + } else { + $contents = str_replace('vendor/codeigniter4/codeigniter4', 'vendor/codeigniter4/framework', $contents); + } + + file_put_contents($file, $contents); + + $modified[] = $file; + } +} + +if ($modified === []) { + echo 'No files modified.' . PHP_EOL; +} else { + echo 'The following files were modified:' . PHP_EOL; + + foreach ($modified as $file) { + echo " * {$file}" . PHP_EOL; + } +} + +echo 'Run `composer update` to sync changes with your vendor folder.' . PHP_EOL; +echo 'Don\'t forget to update the project files in app folder from "vendor/codeigniter4/*/app/".' . PHP_EOL; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d47149e --- /dev/null +++ b/composer.json @@ -0,0 +1,43 @@ +{ + "name": "codeigniter4/appstarter", + "description": "CodeIgniter4 starter app", + "license": "MIT", + "type": "project", + "homepage": "https://codeigniter.com", + "support": { + "forum": "https://forum.codeigniter.com/", + "source": "https://github.com/codeigniter4/CodeIgniter4", + "slack": "https://codeigniterchat.slack.com" + }, + "require": { + "php": "^8.2", + "codeigniter4/framework": "^4.7" + }, + "require-dev": { + "fakerphp/faker": "^1.9", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^10.5.16" + }, + "autoload": { + "psr-4": { + "App\\": "app/", + "Config\\": "app/Config/" + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] + }, + "autoload-dev": { + "psr-4": { + "Tests\\Support\\": "tests/_support" + } + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "scripts": { + "test": "phpunit" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..deed142 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2117 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f5cce40800fa5dae1504b9364f585e6a", + "packages": [ + { + "name": "codeigniter4/framework", + "version": "v4.7.3", + "source": { + "type": "git", + "url": "https://github.com/codeigniter4/framework.git", + "reference": "ab9bf33caa3ccc25b1a4652234d7d0eb1d1937de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeigniter4/framework/zipball/ab9bf33caa3ccc25b1a4652234d7d0eb1d1937de", + "reference": "ab9bf33caa3ccc25b1a4652234d7d0eb1d1937de", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "ext-mbstring": "*", + "laminas/laminas-escaper": "^2.18", + "php": "^8.2", + "psr/log": "^3.0" + }, + "require-dev": { + "codeigniter/coding-standard": "^1.7", + "fakerphp/faker": "^1.24", + "friendsofphp/php-cs-fixer": "^3.47.1", + "kint-php/kint": "^6.1", + "mikey179/vfsstream": "^1.6.12", + "nexusphp/cs-config": "^3.6", + "phpunit/phpunit": "^10.5.16 || ^11.2", + "predis/predis": "^3.0" + }, + "suggest": { + "ext-apcu": "If you use Cache class ApcuHandler", + "ext-curl": "If you use CURLRequest class", + "ext-dom": "If you use TestResponse", + "ext-exif": "If you run Image class tests", + "ext-fileinfo": "Improves mime type detection for files", + "ext-gd": "If you use Image class GDHandler", + "ext-imagick": "If you use Image class ImageMagickHandler", + "ext-libxml": "If you use TestResponse", + "ext-memcache": "If you use Cache class MemcachedHandler with Memcache", + "ext-memcached": "If you use Cache class MemcachedHandler with Memcached", + "ext-mysqli": "If you use MySQL", + "ext-oci8": "If you use Oracle Database", + "ext-pcntl": "If you use Signals", + "ext-pgsql": "If you use PostgreSQL", + "ext-posix": "If you use Signals", + "ext-readline": "Improves CLI::input() usability", + "ext-redis": "If you use Cache class RedisHandler", + "ext-simplexml": "If you format XML", + "ext-sodium": "If you use Encryption SodiumHandler", + "ext-sqlite3": "If you use SQLite3", + "ext-sqlsrv": "If you use SQL Server", + "ext-xdebug": "If you use CIUnitTestCase::assertHeaderEmitted()" + }, + "type": "project", + "autoload": { + "psr-4": { + "CodeIgniter\\": "system/" + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The CodeIgniter framework v4", + "homepage": "https://codeigniter.com", + "support": { + "forum": "https://forum.codeigniter.com/", + "slack": "https://codeigniterchat.slack.com", + "source": "https://github.com/codeigniter4/CodeIgniter4" + }, + "time": "2026-05-22T11:21:33+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.18.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "06f211dfffff18d91844c1f55250d5d13c007e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/06f211dfffff18d91844c1f55250d5d13c007e18", + "reference": "06f211dfffff18d91844c1f55250d5d13c007e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "conflict": { + "zendframework/zend-escaper": "*" + }, + "require-dev": { + "infection/infection": "^0.31.0", + "laminas/laminas-coding-standard": "~3.1.0", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", + "keywords": [ + "escaper", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-escaper/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "source": "https://github.com/laminas/laminas-escaper" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-10-14T18:31:13+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + } + ], + "packages-dev": [ + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "mikey179/vfsstream", + "version": "v1.6.12", + "source": { + "type": "git", + "url": "https://github.com/bovigo/vfsStream.git", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5||^8.5||^9.6", + "yoast/phpunit-polyfills": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "support": { + "issues": "https://github.com/bovigo/vfsStream/issues", + "source": "https://github.com/bovigo/vfsStream/tree/master", + "wiki": "https://github.com/bovigo/vfsStream/wiki" + }, + "time": "2024-08-29T18:43:31+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.63", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:48:37+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:09:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:50:56+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/phpunit.dist.xml b/phpunit.dist.xml new file mode 100644 index 0000000..d9d2c6a --- /dev/null +++ b/phpunit.dist.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + ./tests + + + + + + + + + + + + ./app + + + ./app/Views + ./app/Config/Routes.php + + + + + + + + + + + + + + + + diff --git a/preload.php b/preload.php new file mode 100644 index 0000000..288b30b --- /dev/null +++ b/preload.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +use CodeIgniter\Boot; +use Config\Paths; + +/* + *--------------------------------------------------------------- + * Sample file for Preloading + *--------------------------------------------------------------- + * See https://www.php.net/manual/en/opcache.preloading.php + * + * How to Use: + * 0. Copy this file to your project root folder. + * 1. Set the $paths property of the preload class below. + * 2. Set opcache.preload in php.ini. + * php.ini: + * opcache.preload=/path/to/preload.php + */ + +// Load the paths config file +require __DIR__ . '/app/Config/Paths.php'; + +// Path to the front controller +define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); + +class preload +{ + /** + * @var array Paths to preload. + */ + private array $paths = [ + [ + 'include' => __DIR__ . '/vendor/codeigniter4/framework/system', // Change this path if using manual installation + 'exclude' => [ + // Not needed if you don't use them. + '/system/Database/OCI8/', + '/system/Database/Postgre/', + '/system/Database/SQLite3/', + '/system/Database/SQLSRV/', + // Not needed for web apps. + '/system/Database/Seeder.php', + '/system/Test/', + '/system/CLI/', + '/system/Commands/', + '/system/Publisher/', + '/system/ComposerScripts.php', + // Not Class/Function files. + '/system/Config/Routes.php', + '/system/Language/', + '/system/bootstrap.php', + '/system/util_bootstrap.php', + '/system/rewrite.php', + '/Views/', + // Errors occur. + '/system/ThirdParty/', + ], + ], + ]; + + public function __construct() + { + $this->loadAutoloader(); + } + + private function loadAutoloader(): void + { + $paths = new Paths(); + require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'Boot.php'; + + Boot::preload($paths); + } + + /** + * Load PHP files. + */ + public function load(): void + { + foreach ($this->paths as $path) { + $directory = new RecursiveDirectoryIterator($path['include']); + $fullTree = new RecursiveIteratorIterator($directory); + $phpFiles = new RegexIterator( + $fullTree, + '/.+((? $file) { + foreach ($path['exclude'] as $exclude) { + if (str_contains($file[0], $exclude)) { + continue 2; + } + } + + require_once $file[0]; + // Uncomment only for debugging (to inspect which files are included). + // Never use this in production - preload scripts must not generate output. + // echo 'Loaded: ' . $file[0] . "\n"; + } + } + } +} + +(new preload())->load(); diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..abac3cb --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,49 @@ +# Disable directory browsing +Options -Indexes + +# ---------------------------------------------------------------------- +# Rewrite engine +# ---------------------------------------------------------------------- + +# Turning on the rewrite engine is necessary for the following rules and features. +# FollowSymLinks must be enabled for this to work. + + Options +FollowSymlinks + RewriteEngine On + + # If you installed CodeIgniter in a subfolder, you will need to + # change the following line to match the subfolder you need. + # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase + # RewriteBase / + + # Redirect Trailing Slashes... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Rewrite "www.example.com -> example.com" + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] + + # Checks to see if the user is attempting to access a valid file, + # such as an image or css document, if this isn't true it sends the + # request to the front controller, index.php + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA] + + # Ensure Authorization header is passed along + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + + + # If we don't have mod_rewrite installed, all 404's + # can be sent to index.php, and everything works as normal. + ErrorDocument 404 index.php + + +# Disable server signature start +ServerSignature Off +# Disable server signature end diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ecfce214cbdbedc15d8348babeff5cd7e720488 GIT binary patch literal 5430 zcmeHL&npB`9Dn5Kuy>cuLGmAPauLohTgugsgTofC9NbV!aa0tgTofgxl%f=?Y?Sh& zWMxI9hibZ5LZ@~9&$;7J-7ai$2=7ByD3A3P1*hfQz|8$LL= zUz~w$;90HjJ=)Ss!V^NuI2Za%(z#lli~7u{+nbyOi;~<-#pGX{K<~tb_C6Lj^YSY9 zuHBEb(bRJ+>$(mjNuamQtlmY!MgdXJjDiJ*`fSvC!*oskp+}sjm(MWyz%r29BW!f*m(x|(w?cOiYFaD0y8pq z9eCfscMhGYa>i@!YdSYJ(-6XZ(W@wc#d9lm1l3S%FF!rC_waiB@V}AM)ez?!Gp$os wgQ-p&yk8A*^lZ5Jw#)Gj@LI_qy{H^P{^jt7C;ag)RHAYMwkN>;6;PJx7hHm1jsO4v literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..8e834f2 --- /dev/null +++ b/public/index.php @@ -0,0 +1,59 @@ +systemDirectory . '/Boot.php'; + +exit(Boot::bootWeb($paths)); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..9e60f97 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/spark b/spark new file mode 100644 index 0000000..61bc572 --- /dev/null +++ b/spark @@ -0,0 +1,87 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +use CodeIgniter\Boot; +use Config\Paths; + +/* + * -------------------------------------------------------------------- + * CODEIGNITER COMMAND-LINE TOOLS + * -------------------------------------------------------------------- + * The main entry point into the CLI system and allows you to run + * commands and perform maintenance on your application. + */ + +/* + *--------------------------------------------------------------- + * CHECK SERVER API + *--------------------------------------------------------------- + */ + +// Refuse to run when called from php-cgi +if (str_starts_with(PHP_SAPI, 'cgi')) { + exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n"); +} + +/* + *--------------------------------------------------------------- + * CHECK PHP VERSION + *--------------------------------------------------------------- + */ + +$minPhpVersion = '8.2'; // If you update this, don't forget to update `public/index.php`. +if (version_compare(PHP_VERSION, $minPhpVersion, '<')) { + $message = sprintf( + 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s', + $minPhpVersion, + PHP_VERSION, + ); + + exit($message); +} + +// We want errors to be shown when using it from the CLI. +error_reporting(E_ALL); +ini_set('display_errors', '1'); + +/* + *--------------------------------------------------------------- + * SET THE CURRENT DIRECTORY + *--------------------------------------------------------------- + */ + +// Path to the front controller +define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); + +// Ensure the current directory is pointing to the front controller's directory +chdir(FCPATH); + +/* + *--------------------------------------------------------------- + * BOOTSTRAP THE APPLICATION + *--------------------------------------------------------------- + * This process sets up the path constants, loads and registers + * our autoloader, along with Composer's, loads our constants + * and fires up an environment-specific bootstrapping. + */ + +// LOAD OUR PATHS CONFIG FILE +// This is the line that might need to be changed, depending on your folder structure. +require FCPATH . '../app/Config/Paths.php'; +// ^^^ Change this line if you move your application folder + +$paths = new Paths(); + +// LOAD THE FRAMEWORK BOOTSTRAP FILE +require $paths->systemDirectory . '/Boot.php'; + +exit(Boot::bootSpark($paths)); diff --git a/tests/.htaccess b/tests/.htaccess new file mode 100644 index 0000000..3462048 --- /dev/null +++ b/tests/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..d5b12ee --- /dev/null +++ b/tests/README.md @@ -0,0 +1,118 @@ +# Running Application Tests + +This is the quick-start to CodeIgniter testing. Its intent is to describe what +it takes to set up your application and get it ready to run unit tests. +It is not intended to be a full description of the test features that you can +use to test your application. Those details can be found in the documentation. + +## Resources + +* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html) +* [PHPUnit docs](https://phpunit.de/documentation.html) +* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830) + +## Requirements + +It is recommended to use the latest version of PHPUnit. At the time of this +writing, we are running version 9.x. Support for this has been built into the +**composer.json** file that ships with CodeIgniter and can easily be installed +via [Composer](https://getcomposer.org/) if you don't already have it installed globally. + +```console +> composer install +``` + +If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer. + +```console +> ln -s ./vendor/bin/phpunit ./phpunit +``` + +You also need to install [XDebug](https://xdebug.org/docs/install) in order +for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage. + +## Setting Up + +A number of the tests use a running database. +In order to set up the database edit the details for the `tests` group in +**app/Config/Database.php** or **.env**. +Make sure that you provide a database engine that is currently running on your machine. +More details on a test database setup are in the +[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation. + +## Running the tests + +The entire test suite can be run by simply typing one command-line command from the main directory. + +```console +> ./phpunit +``` + +If you are using Windows, use the following command. + +```console +> vendor\bin\phpunit +``` + +You can limit tests to those within a single test directory by specifying the +directory name after phpunit. + +```console +> ./phpunit app/Models +``` + +## Generating Code Coverage + +To generate coverage information, including HTML reports you can view in your browser, +you can use the following command: + +```console +> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m +``` + +This runs all of the tests again collecting information about how many lines, +functions, and files are tested. It also reports the percentage of the code that is covered by tests. +It is collected in two formats: a simple text file that provides an overview as well +as a comprehensive collection of HTML files that show the status of every line of code in the project. + +The text file can be found at **tests/coverage.txt**. +The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser. + +## PHPUnit XML Configuration + +The repository has a ``phpunit.dist.xml`` file in the project root that's used for +PHPUnit configuration. This is used to provide a default configuration if you +do not have your own configuration file in the project root. + +The normal practice would be to copy ``phpunit.dist.xml`` to ``phpunit.xml`` +(which is git ignored), and to tailor it as you see fit. +For instance, you might wish to exclude database tests, or automatically generate +HTML code coverage reports. + +## Test Cases + +Every test needs a *test case*, or class that your tests extend. CodeIgniter 4 +provides one class that you may use directly: +* `CodeIgniter\Test\CIUnitTestCase` + +Most of the time you will want to write your own test cases that extend `CIUnitTestCase` +to hold functions and services common to your test suites. + +## Creating Tests + +All tests go in the **tests/** directory. Each test file is a class that extends a +**Test Case** (see above) and contains methods for the individual tests. These method +names must start with the word "test" and should have descriptive names for precisely what +they are testing: +`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()` + +Writing tests is an art, and there are many resources available to help learn how. +Review the links above and always pay attention to your code coverage. + +### Database Tests + +Tests can include migrating, seeding, and testing against a mock or live database. +Be sure to modify the test case (or create your own) to point to your seed and migrations +and include any additional steps to be run before tests in the `setUp()` method. +See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) +for details. diff --git a/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php new file mode 100644 index 0000000..a73356d --- /dev/null +++ b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php @@ -0,0 +1,37 @@ +forge->addField('id'); + $this->forge->addField([ + 'name' => ['type' => 'varchar', 'constraint' => 31], + 'uid' => ['type' => 'varchar', 'constraint' => 31], + 'class' => ['type' => 'varchar', 'constraint' => 63], + 'icon' => ['type' => 'varchar', 'constraint' => 31], + 'summary' => ['type' => 'varchar', 'constraint' => 255], + 'created_at' => ['type' => 'datetime', 'null' => true], + 'updated_at' => ['type' => 'datetime', 'null' => true], + 'deleted_at' => ['type' => 'datetime', 'null' => true], + ]); + + $this->forge->addKey('name'); + $this->forge->addKey('uid'); + $this->forge->addKey(['deleted_at', 'id']); + $this->forge->addKey('created_at'); + + $this->forge->createTable('factories'); + } + + public function down(): void + { + $this->forge->dropTable('factories'); + } +} diff --git a/tests/_support/Database/Seeds/ExampleSeeder.php b/tests/_support/Database/Seeds/ExampleSeeder.php new file mode 100644 index 0000000..619fc27 --- /dev/null +++ b/tests/_support/Database/Seeds/ExampleSeeder.php @@ -0,0 +1,41 @@ + 'Test Factory', + 'uid' => 'test001', + 'class' => 'Factories\Tests\NewFactory', + 'icon' => 'fas fa-puzzle-piece', + 'summary' => 'Longer sample text for testing', + ], + [ + 'name' => 'Widget Factory', + 'uid' => 'widget', + 'class' => 'Factories\Tests\WidgetPlant', + 'icon' => 'fas fa-puzzle-piece', + 'summary' => 'Create widgets in your factory', + ], + [ + 'name' => 'Evil Factory', + 'uid' => 'evil-maker', + 'class' => 'Factories\Evil\MyFactory', + 'icon' => 'fas fa-book-dead', + 'summary' => 'Abandon all hope, ye who enter here', + ], + ]; + + $builder = $this->db->table('factories'); + + foreach ($factories as $factory) { + $builder->insert($factory); + } + } +} diff --git a/tests/_support/Libraries/ConfigReader.php b/tests/_support/Libraries/ConfigReader.php new file mode 100644 index 0000000..0bb4a8f --- /dev/null +++ b/tests/_support/Libraries/ConfigReader.php @@ -0,0 +1,19 @@ +findAll(); + + // Make sure the count is as expected + $this->assertCount(3, $objects); + } + + public function testSoftDeleteLeavesRow(): void + { + $model = new ExampleModel(); + $this->setPrivateProperty($model, 'useSoftDeletes', true); + $this->setPrivateProperty($model, 'tempUseSoftDeletes', true); + + /** @var stdClass $object */ + $object = $model->first(); + $model->delete($object->id); + + // The model should no longer find it + $this->assertNull($model->find($object->id)); + + // ... but it should still be in the database + $result = $model->builder()->where('id', $object->id)->get()->getResult(); + + $this->assertCount(1, $result); + } +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/tests/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/tests/session/ExampleSessionTest.php b/tests/session/ExampleSessionTest.php new file mode 100644 index 0000000..33242a4 --- /dev/null +++ b/tests/session/ExampleSessionTest.php @@ -0,0 +1,17 @@ +set('logged_in', 123); + $this->assertSame(123, $session->get('logged_in')); + } +} diff --git a/tests/unit/HealthTest.php b/tests/unit/HealthTest.php new file mode 100644 index 0000000..1df24df --- /dev/null +++ b/tests/unit/HealthTest.php @@ -0,0 +1,49 @@ +assertTrue(defined('APPPATH')); + } + + public function testBaseUrlHasBeenSet(): void + { + $validation = service('validation'); + + $env = false; + + // Check the baseURL in .env + if (is_file(HOMEPATH . '.env')) { + $env = preg_grep('/^app\.baseURL = ./', file(HOMEPATH . '.env')) !== false; + } + + if ($env) { + // BaseURL in .env is a valid URL? + // phpunit.dist.xml sets app.baseURL in $_SERVER + // So if you set app.baseURL in .env, it takes precedence + $config = new App(); + $this->assertTrue( + $validation->check($config->baseURL, 'valid_url'), + 'baseURL "' . $config->baseURL . '" in .env is not valid URL', + ); + } + + // Get the baseURL in app/Config/App.php + // You can't use Config\App, because phpunit.dist.xml sets app.baseURL + $reader = new ConfigReader(); + + // BaseURL in app/Config/App.php is a valid URL? + $this->assertTrue( + $validation->check($reader->baseURL, 'valid_url'), + 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL', + ); + } +} diff --git a/writable/.htaccess b/writable/.htaccess new file mode 100644 index 0000000..3462048 --- /dev/null +++ b/writable/.htaccess @@ -0,0 +1,6 @@ + + Require all denied + + + Deny from all + diff --git a/writable/cache/index.html b/writable/cache/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/writable/cache/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/writable/debugbar/index.html b/writable/debugbar/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/writable/debugbar/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/writable/index.html b/writable/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/writable/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/writable/logs/index.html b/writable/logs/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/writable/logs/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/writable/session/index.html b/writable/session/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/writable/session/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + + diff --git a/writable/uploads/index.html b/writable/uploads/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/writable/uploads/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

Directory access is forbidden.

+ + +