feat(06-admin-base): Admin\ base infrastructure — Form Edit System + Support layer (Phase 6)
Phase 6 zamknięta po 2 planach. Pełny fundament dla Phase 7-13 (migracja 17 admin controllers do Admin\ namespace). 06-01 (Forms infrastructure): - Admin\ViewModels\Forms\* — 5 ViewModeli (687 L) - Admin\Validation\FormValidator (196 L) - composer.json: php >=7.4, PSR-4 paths cross-platform safe (Admin\ → autoload/admin/, Frontend\ → autoload/front/) 06-02 (Support layer): - Admin\Support\TableListRequestFactory (99 L) — parser list z $_GET - Admin\Support\Forms\FormRequestHandler (159 L) — POST + CSRF + walidacja + persist - Admin\Support\Forms\FormFieldRenderer (494 L) — renderer HTML pól Decyzje: - Brak BaseController — Phase 7+ kontrolery jako POJOs z DI (jak shopPRO) - PSR-4 filename fix: TableListRequestFactory.php (bez shopPRO 'class.' prefix) - PascalCase namespace (Admin\Support) na lowercase folder admin/ ze względu na Windows fs case-insensitivity vs legacy admin/controls/ Pliki: 8 nowych klas, 1635 L kodu PHP 7.4-kompatybilnego, zero regresji. Smoke test: walidacja e-maila zwraca PL komunikat, factory parsuje ?page=&per_page=&sort=&filter=, Domain/Shared nadal ładują się. PHPUnit: 37/37 OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
300
.paul/phases/06-admin-base/06-02-PLAN.md
Normal file
300
.paul/phases/06-admin-base/06-02-PLAN.md
Normal file
@@ -0,0 +1,300 @@
|
||||
---
|
||||
phase: 06-admin-base
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["06-01"]
|
||||
files_modified:
|
||||
- autoload/admin/Support/TableListRequestFactory.php
|
||||
- autoload/admin/Support/Forms/FormRequestHandler.php
|
||||
- autoload/admin/Support/Forms/FormFieldRenderer.php
|
||||
autonomous: true
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Domknąć Phase 6 (Admin Base Infrastructure) przez przeniesienie z shopPRO 3 klas warstwy Support: `TableListRequestFactory` (parser requestów listy z filtrami/sortowaniem/paginacją), `FormRequestHandler` (obsługa POST/persist/walidacji formularza), `FormFieldRenderer` (renderer HTML pól formularza). Po 06-02 cała infrastruktura Phase 7+ (kontrolery) jest gotowa.
|
||||
|
||||
## Purpose
|
||||
Phase 7-13 będą migrować 17 modułów Admin do nowych namespace'ów. Każdy z tych kontrolerów będzie potrzebował:
|
||||
- `TableListRequestFactory::fromRequest()` — uniformowy parsing list z `?page=&per_page=&sort=&filter[]=`
|
||||
- `FormRequestHandler` — uniformowy handler POST → walidacja → persist → redirect
|
||||
- `FormFieldRenderer` — generowanie HTML pól z `FormField` ViewModeli (zastępuje Shared\Html\Html dla nowych formularzy)
|
||||
|
||||
Bez tych helperów każdy kontroler musiałby duplikować logikę paginacji i obsługi POST. Pakiet 06-01 + 06-02 = pełny fundament Admin\ gotowy do Phase 7.
|
||||
|
||||
**Decyzja architektoniczna:** Brak `BaseController` — shopPRO używa POJOs z DI (np. `BannerController(BannerRepository, LanguagesRepository)`), nie dziedziczenia. Phase 7+ kontrolery będą zwykłymi klasami z Domain repos i tymi helperami w konstruktorze.
|
||||
|
||||
## Output
|
||||
- `autoload/admin/Support/TableListRequestFactory.php`
|
||||
- `autoload/admin/Support/Forms/FormRequestHandler.php`
|
||||
- `autoload/admin/Support/Forms/FormFieldRenderer.php`
|
||||
- `.paul/phases/06-admin-base/06-02-SUMMARY.md`
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
<clarifications>
|
||||
- **BaseCtrl** — Czy tworzymy `BaseController`?
|
||||
→ Odpowiedź: NIE. POJOs z DI jak shopPRO. ROADMAP zaktualizowany — wpis BaseController usunięty ze scope Phase 6.
|
||||
- **Helpers** — Które helpery z `Admin/Support/Forms/` w 06-02?
|
||||
→ Odpowiedź: Oba — `FormRequestHandler` (159 L) + `FormFieldRenderer` (494 L). Pełny pakiet, żeby Phase 7+ miało gotowe wszystko.
|
||||
- **Filename** — Konwencja nazwy pliku?
|
||||
→ Odpowiedź: PSR-4 czyste — `TableListRequestFactory.php` (bez `class.` prefiksu z shopPRO). Zgodne z resztą cmsPRO Domain/Shared (np. `ArticlesRepository.php`).
|
||||
</clarifications>
|
||||
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/06-admin-base/06-01-SUMMARY.md
|
||||
|
||||
## Reference Source (shopPRO — wzorzec do skopiowania)
|
||||
@C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\class.TableListRequestFactory.php
|
||||
@C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormRequestHandler.php
|
||||
@C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormFieldRenderer.php
|
||||
|
||||
## cmsPRO target paths
|
||||
- `autoload/admin/Support/TableListRequestFactory.php` (`Admin\Support\TableListRequestFactory`)
|
||||
- `autoload/admin/Support/Forms/FormRequestHandler.php` (`Admin\Support\Forms\FormRequestHandler`)
|
||||
- `autoload/admin/Support/Forms/FormFieldRenderer.php` (`Admin\Support\Forms\FormFieldRenderer`)
|
||||
|
||||
Uwaga: PSR-4 mapping w composer.json (po 06-01) to `"Admin\\": "autoload/admin/"`. Folder na disku jest lowercase `admin/` (legacy collision z PascalCase Windows fs), ale namespace pozostaje PascalCase `Admin\Support`.
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Wszystkie 3 klasy ładują się przez PSR-4
|
||||
```gherkin
|
||||
Given composer dump-autoload wykonany po stworzeniu plików
|
||||
When `php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\Support\\TableListRequestFactory'), class_exists('Admin\\Support\\Forms\\FormRequestHandler'), class_exists('Admin\\Support\\Forms\\FormFieldRenderer'));"`
|
||||
Then otrzymam `bool(true) bool(true) bool(true)`
|
||||
```
|
||||
|
||||
## AC-2: TableListRequestFactory parsuje request
|
||||
```gherkin
|
||||
Given klasa Admin\Support\TableListRequestFactory załadowana
|
||||
When wywołam `TableListRequestFactory::fromRequest([], ['name', 'date_add'], 'date_add')` w smoke teście (z `$_GET = ['page'=>'2','per_page'=>'25','sort'=>'name']`)
|
||||
Then otrzymam tablicę z `page=2`, `perPage=25`, `sortColumn='name'` (lub fallback do default jeśli nie jest w sortableColumns), oraz nieputą `viewFilters`/`queryFilters`
|
||||
```
|
||||
|
||||
## AC-3: FormRequestHandler i FormFieldRenderer mają poprawne `use` do 06-01
|
||||
```gherkin
|
||||
Given pliki utworzone w Admin\Support\Forms
|
||||
When przeczytam pierwsze 10 linii FormRequestHandler.php i FormFieldRenderer.php
|
||||
Then znajdę:
|
||||
- FormRequestHandler: `use Admin\ViewModels\Forms\FormEditViewModel;`, `use Admin\ViewModels\Forms\FormFieldType;`, `use Admin\Validation\FormValidator;`
|
||||
- FormFieldRenderer: `use Admin\ViewModels\Forms\FormEditViewModel;`, `use Admin\ViewModels\Forms\FormField;`, `use Admin\ViewModels\Forms\FormFieldType;`
|
||||
And żaden plik nie ma `namespace admin\` (lowercase) ani `use admin\` (lowercase)
|
||||
```
|
||||
|
||||
## AC-4: Smoke test integracyjny — handler + renderer + Form VMs współpracują
|
||||
```gherkin
|
||||
Given wszystkie 3 nowe klasy + Form VMs z 06-01
|
||||
When utworzę `FormField('email', FormFieldType::EMAIL, 'E-mail')`, opakowuję w `FormEditViewModel`, wywołuję `FormFieldRenderer::renderField($field, $vm)` w smoke teście
|
||||
Then otrzymam string HTML zawierający `<input` i `name="email"`
|
||||
And nie wystąpi fatal error o brakującej zależności
|
||||
```
|
||||
|
||||
## AC-5: Zero regresji — Phase 5 i 06-01 nadal działają
|
||||
```gherkin
|
||||
Given commit przed 06-02
|
||||
When `php -r "require 'vendor/autoload.php'; foreach (['Domain\\Articles\\ArticlesRepository','Shared\\Helpers\\Helpers','Admin\\ViewModels\\Forms\\FormField','Admin\\Validation\\FormValidator'] as \$c) { var_dump(class_exists(\$c)); }"`
|
||||
Then 4× `bool(true)` — żaden istniejący namespace nie został rozbity
|
||||
And `git diff --stat autoload/admin/controls/ autoload/admin/factory/ autoload/admin/view/` pokazuje 0 zmian (legacy nietknięte)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Skopiuj TableListRequestFactory do Admin\Support z PSR-4 nazwą pliku</name>
|
||||
<files>autoload/admin/Support/TableListRequestFactory.php</files>
|
||||
<action>
|
||||
Utwórz katalog `autoload/admin/Support/` i skopiuj zawartość ze shopPRO:
|
||||
- Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\class.TableListRequestFactory.php`
|
||||
- Cel: `autoload/admin/Support/TableListRequestFactory.php` (BEZ `class.` prefiksu — PSR-4 wymaga match nazwa pliku = nazwa klasy)
|
||||
|
||||
Zmiana namespace: `namespace admin\Support;` → `namespace Admin\Support;` (PascalCase pierwszy segment, jak 06-01).
|
||||
|
||||
Cała reszta klasy (stałe `DEFAULT_PER_PAGE_OPTIONS`, `DEFAULT_PER_PAGE`, metoda statyczna `fromRequest()`, logika parsowania `$_GET` przez `\Shared\Helpers\Helpers::get()`) bez zmian.
|
||||
|
||||
Avoid:
|
||||
- Pozostawienie prefiksu `class.` w nazwie pliku — psuje PSR-4
|
||||
- Zmiany sygnatur metod (Phase 7+ zakłada API z shopPRO)
|
||||
- Zmiany domyślnego sortColumn w sygnaturze
|
||||
</action>
|
||||
<verify>
|
||||
`php -l autoload/admin/Support/TableListRequestFactory.php` → "No syntax errors detected"
|
||||
`grep -c "^namespace Admin\\\\Support;" autoload/admin/Support/TableListRequestFactory.php` → 1
|
||||
Po `composer dump-autoload`: `php -r "require 'vendor/autoload.php'; var_dump(class_exists('Admin\\\\Support\\\\TableListRequestFactory'));"` → `bool(true)`
|
||||
</verify>
|
||||
<done>AC-1 częściowo (TableListRequestFactory ładuje się). AC-2 (smoke test factory) wykonywany w Task 3.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Skopiuj FormRequestHandler + FormFieldRenderer do Admin\Support\Forms\</name>
|
||||
<files>autoload/admin/Support/Forms/FormRequestHandler.php, autoload/admin/Support/Forms/FormFieldRenderer.php</files>
|
||||
<action>
|
||||
Utwórz katalog `autoload/admin/Support/Forms/` i skopiuj 2 pliki ze shopPRO:
|
||||
|
||||
**FormRequestHandler.php** (159 L):
|
||||
- Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormRequestHandler.php`
|
||||
- Cel: `autoload/admin/Support/Forms/FormRequestHandler.php`
|
||||
- Zmiany namespace/use:
|
||||
- `namespace admin\Support\Forms;` → `namespace Admin\Support\Forms;`
|
||||
- `use admin\ViewModels\Forms\FormEditViewModel;` → `use Admin\ViewModels\Forms\FormEditViewModel;`
|
||||
- `use admin\ViewModels\Forms\FormFieldType;` → `use Admin\ViewModels\Forms\FormFieldType;`
|
||||
- `use admin\Validation\FormValidator;` → `use Admin\Validation\FormValidator;`
|
||||
- Cała reszta logiki (handle POST, persist do `$_SESSION`, redirect, walidacja przez `FormValidator`) bez zmian.
|
||||
|
||||
**FormFieldRenderer.php** (494 L):
|
||||
- Źródło: `C:\visual studio code\projekty\shopPRO\autoload\Admin\Support\Forms\FormFieldRenderer.php`
|
||||
- Cel: `autoload/admin/Support/Forms/FormFieldRenderer.php`
|
||||
- Zmiany namespace/use:
|
||||
- `namespace admin\Support\Forms;` → `namespace Admin\Support\Forms;`
|
||||
- `use admin\ViewModels\Forms\FormEditViewModel;` → `use Admin\ViewModels\Forms\FormEditViewModel;`
|
||||
- `use admin\ViewModels\Forms\FormField;` → `use Admin\ViewModels\Forms\FormField;`
|
||||
- `use admin\ViewModels\Forms\FormFieldType;` → `use Admin\ViewModels\Forms\FormFieldType;`
|
||||
- JEŻELI plik referuje również `FormTab` lub `FormAction` — analogicznie zaktualizuj `use`
|
||||
- Cała reszta (metody `renderField`, `renderTabs`, `renderLangSection` itp.) bez zmian.
|
||||
|
||||
Po zapisaniu obu plików: `php composer.phar dump-autoload`.
|
||||
|
||||
Avoid:
|
||||
- Refaktoringu generowanego HTML (UI templates Phase 7+ liczą na konkretny markup)
|
||||
- Pozostawienia jakichkolwiek `admin\` (lowercase) referencji w obu plikach
|
||||
- Zmiany w inkluzji innych klas spoza Admin\* (Shared\Html, Shared\Helpers)
|
||||
</action>
|
||||
<verify>
|
||||
`php -l autoload/admin/Support/Forms/FormRequestHandler.php` → "No syntax errors detected"
|
||||
`php -l autoload/admin/Support/Forms/FormFieldRenderer.php` → "No syntax errors detected"
|
||||
`grep -c "^namespace Admin\\\\Support\\\\Forms;" autoload/admin/Support/Forms/*.php` → 2
|
||||
`grep -E "^use admin\\\\" autoload/admin/Support/Forms/*.php` → brak wyników (zero lowercase use)
|
||||
Po dump-autoload: `class_exists('Admin\\\\Support\\\\Forms\\\\FormRequestHandler')` i `class_exists('Admin\\\\Support\\\\Forms\\\\FormFieldRenderer')` → `bool(true)` × 2
|
||||
</verify>
|
||||
<done>AC-1 satisfied (wszystkie 3 klasy 06-02 ładują się), AC-3 satisfied (poprawne `use` Admin\... do 06-01).</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Smoke test integracyjny — TableListRequestFactory + FormFieldRenderer z VM</name>
|
||||
<files>(brak modyfikacji — tylko runtime weryfikacja)</files>
|
||||
<action>
|
||||
Uruchom inline PHP smoke test sprawdzający 3 zachowania:
|
||||
|
||||
**A) TableListRequestFactory parsuje request:**
|
||||
```
|
||||
php -r "require 'vendor/autoload.php';
|
||||
\$_GET = ['page'=>'2','per_page'=>'25','sort'=>'name','status'=>'1'];
|
||||
\$ctx = Admin\Support\TableListRequestFactory::fromRequest(
|
||||
[['key'=>'status','label'=>'Status','type'=>'select','options'=>['1'=>'aktywny']]],
|
||||
['name','date_add'],
|
||||
'date_add'
|
||||
);
|
||||
echo 'page='.\$ctx['page'].' perPage='.\$ctx['perPage'].' sort='.\$ctx['sortColumn'].PHP_EOL;
|
||||
echo 'filters_status='.\$ctx['filters']['status'].PHP_EOL;
|
||||
echo 'OK_FACTORY'.PHP_EOL;"
|
||||
```
|
||||
Oczekuję: `page=2 perPage=25 sort=name`, `filters_status=1`, `OK_FACTORY`.
|
||||
|
||||
**B) FormFieldRenderer renderuje pole:**
|
||||
```
|
||||
php -r "require 'vendor/autoload.php';
|
||||
\$f = new Admin\ViewModels\Forms\FormField('email', Admin\ViewModels\Forms\FormFieldType::EMAIL, 'E-mail', 'a@b.c', 'main', true);
|
||||
\$vm = new Admin\ViewModels\Forms\FormEditViewModel('test_form', 'Test', [], [\$f], [new Admin\ViewModels\Forms\FormTab('main','Main')]);
|
||||
\$html = Admin\Support\Forms\FormFieldRenderer::renderField(\$f, \$vm);
|
||||
echo 'HTML_HAS_INPUT='.(strpos(\$html,'<input')!==false?'YES':'NO').PHP_EOL;
|
||||
echo 'HTML_HAS_NAME='.(strpos(\$html,'name=\"email\"')!==false?'YES':'NO').PHP_EOL;
|
||||
echo 'OK_RENDERER'.PHP_EOL;"
|
||||
```
|
||||
Oczekuję: `HTML_HAS_INPUT=YES`, `HTML_HAS_NAME=YES`, `OK_RENDERER`.
|
||||
|
||||
Uwaga: Jeśli `renderField` jest niestatyczna lub ma inną sygnaturę niż wyżej zakłada — dostosuj wywołanie wedle faktycznej implementacji shopPRO (NIE modyfikuj klasy; popraw smoke test).
|
||||
|
||||
**C) FormRequestHandler ładuje się i ma referencję do FormValidator:**
|
||||
```
|
||||
php -r "require 'vendor/autoload.php';
|
||||
\$h = new Admin\Support\Forms\FormRequestHandler();
|
||||
echo get_class(\$h).PHP_EOL;
|
||||
echo 'OK_HANDLER'.PHP_EOL;"
|
||||
```
|
||||
Oczekuję: `Admin\Support\Forms\FormRequestHandler`, `OK_HANDLER`.
|
||||
|
||||
**D) Zero regresji:**
|
||||
```
|
||||
php -r "require 'vendor/autoload.php';
|
||||
foreach (['Domain\Articles\ArticlesRepository','Shared\Helpers\Helpers','Admin\ViewModels\Forms\FormField','Admin\Validation\FormValidator'] as \$c) {
|
||||
echo \$c.': '.(class_exists(\$c)?'OK':'FAIL').PHP_EOL;
|
||||
}"
|
||||
```
|
||||
Oczekuję: 4× OK.
|
||||
|
||||
**Lint na nowych 3 plikach:**
|
||||
`for f in autoload/admin/Support/TableListRequestFactory.php autoload/admin/Support/Forms/*.php; do php -l "$f"; done`
|
||||
|
||||
Avoid:
|
||||
- Tworzenia trwałych plików testowych
|
||||
- Modyfikacji shopPRO source files (read-only)
|
||||
- Modyfikacji legacy `autoload/admin/controls/`, `factory/`, `view/`
|
||||
</action>
|
||||
<verify>
|
||||
Output zawiera: `OK_FACTORY`, `OK_RENDERER`, `OK_HANDLER`, oraz 4× `OK` dla zero-regression check
|
||||
`php -l` zielony × 3 dla nowych plików
|
||||
`git diff --stat autoload/admin/controls/ autoload/admin/factory/ autoload/admin/view/` → 0 zmian
|
||||
</verify>
|
||||
<done>AC-2, AC-4, AC-5 satisfied (factory działa, renderer produkuje HTML, zero regresji).</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `autoload/admin/controls/`, `factory/`, `view/` (legacy 50+ plików — boundaries Phase 19)
|
||||
- `autoload/admin/ViewModels/`, `autoload/admin/Validation/` (utworzone w 06-01, stabilne)
|
||||
- `autoload/Domain/**`, `autoload/Shared/**` (Phase 1-5 closed)
|
||||
- `autoload/autoloader.php`
|
||||
- `composer.json` — TYLKO regen `vendor/composer/autoload_*` przez dump-autoload, BEZ edycji `composer.json` (pre 06-01 ustawienia są wystarczające)
|
||||
- `composer.lock` — bez `composer update`
|
||||
- shopPRO source files — read-only
|
||||
|
||||
## SCOPE LIMITS
|
||||
- BaseController — explicit out-of-scope (decyzja: POJOs, brak BaseController)
|
||||
- `Admin\App.php` (logowanie, special_actions, routing) → Phase 12 (Users + 2FA)
|
||||
- Migracja istniejących admin\controls\* na nowe wzorce — Phase 7+
|
||||
- Templates HTML wykorzystujące FormFieldRenderer — Phase 7+ (renderer już produkuje HTML, ale integracja z templates_admin/ to później)
|
||||
- Testy PHPUnit dla nowych klas — Phase 18
|
||||
- Refaktoring 494-liniowego FormFieldRenderer (np. split na mniejsze rendery per typ) — deferred
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Przed declared complete:
|
||||
- [ ] `php -l` zielony dla 3 nowych plików
|
||||
- [ ] `composer dump-autoload` zwraca exit 0 bez warningów PSR-4
|
||||
- [ ] Smoke test (Task 3) — wszystkie 4 sekcje (A/B/C/D) zwracają oczekiwane outputy
|
||||
- [ ] `grep -r "namespace admin\\\\Support" autoload/admin/Support/` → brak wyników (zero lowercase)
|
||||
- [ ] `git diff --stat` pokazuje TYLKO: 3 nowe pliki + regen vendor/composer/autoload_*
|
||||
- [ ] AC-1..AC-5 spełnione
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 3 klasy Support dostępne: `Admin\Support\TableListRequestFactory`, `Admin\Support\Forms\FormRequestHandler`, `Admin\Support\Forms\FormFieldRenderer`
|
||||
- TableListRequestFactory poprawnie parsuje `?page=&per_page=&sort=&filter[]=` z `$_GET`
|
||||
- FormFieldRenderer produkuje HTML pola formularza z `FormField` ViewModelu
|
||||
- Zero regresji: Domain, Shared, Admin\ViewModels, Admin\Validation działają jak przed 06-02
|
||||
- Phase 6 zamknięta — Phase 7+ ma cały fundament
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Po wykonaniu utworzyć `.paul/phases/06-admin-base/06-02-SUMMARY.md` z:
|
||||
- Lista skopiowanych plików (z liczbą linii)
|
||||
- Output 4 części smoke testu (literalnie)
|
||||
- Wszelkie deferred issues (np. refaktoring FormFieldRenderer 494 L)
|
||||
- Setup dla Phase 7 (jakie założenia: TableListRequestFactory + FormRequestHandler + FormFieldRenderer dostępne, brak BaseController — POJO pattern)
|
||||
- Aktualizacja `.paul/codebase/architecture.md` (deferred — robione w UNIFY)
|
||||
</output>
|
||||
Reference in New Issue
Block a user