update
This commit is contained in:
@@ -13,7 +13,7 @@ Phases: 1 of 2 complete
|
|||||||
| Phase | Name | Plans | Status | Completed |
|
| Phase | Name | Plans | Status | Completed |
|
||||||
|-------|------|-------|--------|-----------|
|
|-------|------|-------|--------|-----------|
|
||||||
| 1 | StatLink Autolinking | 1 | Complete ✓ | 2026-04-09 |
|
| 1 | StatLink Autolinking | 1 | Complete ✓ | 2026-04-09 |
|
||||||
| 2 | Admin Panel Upgrade | 1 | Planning | - |
|
| 2 | Admin Panel Upgrade | 2 | Planning | - |
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ Phases: 1 of 2 complete
|
|||||||
|
|
||||||
### Phase 2: Admin Panel Upgrade
|
### Phase 2: Admin Panel Upgrade
|
||||||
|
|
||||||
**Goal:** Panel migracji bazy danych, nowoczesny sidebar (jak orderPRO), lista publikacji StatLink.
|
**Goal:** Panel migracji bazy danych, nowoczesny sidebar (jak orderPRO), lista publikacji StatLink oraz zdalne zarzadzanie komentarzami WordPress.
|
||||||
**Depends on:** Phase 1 (tabela statlink_links)
|
**Depends on:** Phase 1 (tabela statlink_links)
|
||||||
**Research:** Done (analiza orderPRO: Migrator, sidebar, CSS)
|
**Research:** Done (analiza orderPRO: Migrator, sidebar, CSS)
|
||||||
|
|
||||||
@@ -36,9 +36,12 @@ Phases: 1 of 2 complete
|
|||||||
- Migrator engine (port z orderPRO) + panel /settings/database
|
- Migrator engine (port z orderPRO) + panel /settings/database
|
||||||
- Nowy sidebar z grupami, ikonami SVG, collapse
|
- Nowy sidebar z grupami, ikonami SVG, collapse
|
||||||
- Widok /statlink z listą linkowanych artykułów
|
- Widok /statlink z listą linkowanych artykułów
|
||||||
|
- Zdalne wlaczanie/wylaczanie komentowania dla pojedynczej strony WordPress
|
||||||
|
- Lista komentarzy z danego serwisu z mozliwoscia usuwania
|
||||||
|
|
||||||
**Plans:**
|
**Plans:**
|
||||||
- [ ] 02-01: Migrator + sidebar + widok StatLink
|
- [ ] 02-01: Migrator + sidebar + widok StatLink
|
||||||
|
- [ ] 02-02: Zdalne zarzadzanie komentarzami WordPress
|
||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap created: 2026-04-09*
|
*Roadmap created: 2026-04-09*
|
||||||
|
|||||||
@@ -1,65 +1,21 @@
|
|||||||
# Project State
|
|
||||||
|
|
||||||
## Project Reference
|
|
||||||
|
|
||||||
See: .paul/PROJECT.md (updated 2026-04-09)
|
|
||||||
|
|
||||||
**Core value:** Zautomatyzowane tworzenie zaplecza SEO
|
|
||||||
**Current focus:** Phase 2 — Admin Panel Upgrade
|
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v0.1 Initial Release
|
Phase: 02-admin-panel-upgrade — In Progress
|
||||||
Phase: 2 of 2 (Admin Panel Upgrade) — Planning
|
Plan: 02-02 complete
|
||||||
Plan: 02-01 created, awaiting approval
|
Status: UNIFY complete. Loop complete — ready for next plan.
|
||||||
Status: PLAN created, ready for APPLY
|
Last activity: 2026-04-24T07:11:11.932Z
|
||||||
Last activity: 2026-04-09 — Phase 1 UNIFY completed, bugfixes applied
|
|
||||||
|
|
||||||
Progress:
|
|
||||||
- Milestone: [████░░░░░░] 40%
|
|
||||||
- Phase 1: [██████████] 100% ✓
|
|
||||||
- Phase 2: [░░░░░░░░░░] 0%
|
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
**Phase 1 (StatLink Auto-Linking):**
|
Current loop state:
|
||||||
```
|
```
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
✓ ✓ ✓ [Phase 1 complete]
|
✓ ✓ ✓ [Loop complete — ready for next plan]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Phase 2 (Admin Panel Upgrade):**
|
|
||||||
```
|
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
|
||||||
✓ ○ ○ [Plan 02-01 created, awaiting approval]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Accumulated Context
|
|
||||||
|
|
||||||
### Decisions
|
|
||||||
- StatLink.pl integration via Guzzle HTTP (cookie-based session)
|
|
||||||
- Login field name: "zaloguj" (not "loguj"), needs GET homepage first
|
|
||||||
- ilosc_dziennie: 0.02 (1 co 2 dni)
|
|
||||||
- Migrator: port z orderPRO z lock mechanism
|
|
||||||
- Sidebar: adaptacja orderPRO design do backPRO
|
|
||||||
- Anchor sanitization: Polish diacritics must be transliterated to ASCII for StatLink
|
|
||||||
- json_encode needs JSON_INVALID_UTF8_SUBSTITUTE when outputting scraped HTML
|
|
||||||
- OPcache reset required after FTP deploy for changes to take effect
|
|
||||||
- StatLink timeouts: connect_timeout=60s, timeout=120s, PHP set_time_limit=300s
|
|
||||||
|
|
||||||
### Deferred Issues
|
|
||||||
- StatLink: no max retry count for permanently failing links (could block queue)
|
|
||||||
- StatLink: cron not yet configured on server (only manual token URL trigger)
|
|
||||||
|
|
||||||
### Blockers/Concerns
|
|
||||||
None.
|
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-09
|
Last session: 2026-04-24
|
||||||
Stopped at: Phase 1 UNIFY complete, Phase 2 Plan 02-01 awaiting approval
|
Stopped at: Plan 02-02 complete
|
||||||
Next action: Review and approve plan 02-01, then run /paul:apply
|
Next action: paul_workflow('plan') for next plan
|
||||||
Resume file: .paul/phases/02-admin-panel-upgrade/02-01-PLAN.md
|
Resume file: .paul/phases/02-admin-panel-upgrade/02-02-SUMMARY.md
|
||||||
|
|
||||||
---
|
|
||||||
*STATE.md — Updated after every significant action*
|
|
||||||
66
.paul/STATE.md.bak
Normal file
66
.paul/STATE.md.bak
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Project State
|
||||||
|
|
||||||
|
## Project Reference
|
||||||
|
|
||||||
|
See: .paul/PROJECT.md (updated 2026-04-09)
|
||||||
|
|
||||||
|
**Core value:** Zautomatyzowane tworzenie zaplecza SEO
|
||||||
|
**Current focus:** Phase 2 — Admin Panel Upgrade
|
||||||
|
|
||||||
|
## Current Position
|
||||||
|
|
||||||
|
Milestone: v0.1 Initial Release
|
||||||
|
Phase: 2 of 2 (Admin Panel Upgrade) — Planning
|
||||||
|
Plan: 02-02 applied, awaiting UNIFY (depends on 02-01)
|
||||||
|
Status: APPLY complete — 3/3 PASS, ready for UNIFY
|
||||||
|
Last activity: 2026-04-24 - APPLY complete for .paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
|
||||||
|
|
||||||
|
Progress:
|
||||||
|
- Milestone: [████░░░░░░] 40%
|
||||||
|
- Phase 1: [██████████] 100% ✓
|
||||||
|
- Phase 2: [░░░░░░░░░░] 0%
|
||||||
|
|
||||||
|
## Loop Position
|
||||||
|
|
||||||
|
**Phase 1 (StatLink Auto-Linking):**
|
||||||
|
```
|
||||||
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
|
✓ ✓ ○ [APPLY complete, awaiting UNIFY]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 2 (Admin Panel Upgrade):**
|
||||||
|
```
|
||||||
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
|
✓ ○ ○ [Plan 02-02 created, awaiting approval; 02-01 still pending]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accumulated Context
|
||||||
|
|
||||||
|
### Decisions
|
||||||
|
- StatLink.pl integration via Guzzle HTTP (cookie-based session)
|
||||||
|
- Login field name: "zaloguj" (not "loguj"), needs GET homepage first
|
||||||
|
- ilosc_dziennie: 0.02 (1 co 2 dni)
|
||||||
|
- Migrator: port z orderPRO z lock mechanism
|
||||||
|
- Sidebar: adaptacja orderPRO design do backPRO
|
||||||
|
- Anchor sanitization: Polish diacritics must be transliterated to ASCII for StatLink
|
||||||
|
- json_encode needs JSON_INVALID_UTF8_SUBSTITUTE when outputting scraped HTML
|
||||||
|
- OPcache reset required after FTP deploy for changes to take effect
|
||||||
|
- WordPress comment management should use the existing BackPRO remote service for site options and WP REST API for comment list/delete.
|
||||||
|
- StatLink timeouts: connect_timeout=60s, timeout=120s, PHP set_time_limit=300s
|
||||||
|
|
||||||
|
### Deferred Issues
|
||||||
|
- StatLink: no max retry count for permanently failing links (could block queue)
|
||||||
|
- StatLink: cron not yet configured on server (only manual token URL trigger)
|
||||||
|
|
||||||
|
### Blockers/Concerns
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Session Continuity
|
||||||
|
|
||||||
|
Last session: 2026-04-09
|
||||||
|
Stopped at: Phase 2 Plan 02-02 APPLY complete
|
||||||
|
Next action: Run $paul-unify .paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
|
||||||
|
Resume file: .paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
|
||||||
|
|
||||||
|
---
|
||||||
|
*STATE.md — Updated after every significant action*
|
||||||
16
.paul/changelog/2026-04-24.md
Normal file
16
.paul/changelog/2026-04-24.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 2026-04-24
|
||||||
|
|
||||||
|
## Co zrobiono
|
||||||
|
|
||||||
|
- [02-admin-panel-upgrade, Plan 02]
|
||||||
|
- Task 1: Rozszerzyc WordPressService o operacje komentarzy
|
||||||
|
- Task 2: Dodac akcje kontrolera i trasy komentarzy
|
||||||
|
- Task 3: Zbudowac UI komentarzy dla pojedynczej strony
|
||||||
|
|
||||||
|
## Zmienione pliki
|
||||||
|
|
||||||
|
- `src/Services/WordPressService.php`
|
||||||
|
- `src/Controllers/SiteController.php`
|
||||||
|
- `config/routes.php`
|
||||||
|
- `templates/sites/dashboard.php`
|
||||||
|
- `templates/sites/comments.php`
|
||||||
@@ -2,3 +2,18 @@
|
|||||||
{"ts":"2026-04-10T07:18:51Z","tool":"Bash","cmd":"curl -s \"ftp://host700513.hostido.net.pl/public_html/storage/logs/openai_2026-04-10.log\" --user \"www@backpro.projectpro.pl:WGnT4LEn6dLYKvDkXZdd\" 2>&1\",\"timeout\":15000,\"description\":\"Download","cwd":"/c/visual studio code/projekty/backPRO"}
|
{"ts":"2026-04-10T07:18:51Z","tool":"Bash","cmd":"curl -s \"ftp://host700513.hostido.net.pl/public_html/storage/logs/openai_2026-04-10.log\" --user \"www@backpro.projectpro.pl:WGnT4LEn6dLYKvDkXZdd\" 2>&1\",\"timeout\":15000,\"description\":\"Download","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
{"ts":"2026-04-10T07:18:54Z","tool":"Bash","cmd":"curl -s \"ftp://host700513.hostido.net.pl/public_html/storage/logs/openai_2026-04-09.log\" --user \"www@backpro.projectpro.pl:WGnT4LEn6dLYKvDkXZdd\" 2>&1\",\"timeout\":15000,\"description\":\"Download","cwd":"/c/visual studio code/projekty/backPRO"}
|
{"ts":"2026-04-10T07:18:54Z","tool":"Bash","cmd":"curl -s \"ftp://host700513.hostido.net.pl/public_html/storage/logs/openai_2026-04-09.log\" --user \"www@backpro.projectpro.pl:WGnT4LEn6dLYKvDkXZdd\" 2>&1\",\"timeout\":15000,\"description\":\"Download","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
{"ts":"2026-04-10T07:19:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
{"ts":"2026-04-10T07:19:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:25:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:25:22Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:32:25Z","tool":"Bash","cmd":"php -r \"\\n\\\\$pdo = new PDO('mysql:host=host700513.hostido.net.pl;dbname=host700513_backpro;charset=utf8mb4', 'host700513_backpro', 'Mq9wH2B8KPeQh2wQ32Ya');\\n\\\\$stmt = \\\\$pdo->query(\\\\\"SELE","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:32:59Z","tool":"Bash","cmd":"php -r \"\\n\\\\$pdo = new PDO('mysql:host=host700513.hostido.net.pl;dbname=host700513_backpro;charset=utf8mb4', 'host700513_backpro', 'Mq9wH2B8KPeQh2wQ32Ya');\\n\\\\$stmt = \\\\$pdo->query(\\\\\"SELE","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:33:42Z","tool":"Bash","cmd":"php -r \"\\n\\\\$pdo = new PDO('mysql:host=host700513.hostido.net.pl;dbname=host700513_backpro;charset=utf8mb4', 'host700513_backpro', 'Mq9wH2B8KPeQh2wQ32Ya');\\n\\\\$stmt = \\\\$pdo->query(\\\\\"SELE","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:34:19Z","tool":"Bash","cmd":"php -r \"\\n\\\\$pdo = new PDO('mysql:host=host700513.hostido.net.pl;dbname=host700513_backpro;charset=utf8mb4', 'host700513_backpro', 'Mq9wH2B8KPeQh2wQ32Ya');\\n\\\\$stmt = \\\\$pdo->query(\\\\\"SELE","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:34:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:34:52Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:34:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:35:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:36:29Z","tool":"Bash","cmd":"php -r \"\\n\\\\$pdo = new PDO('mysql:host=host700513.hostido.net.pl;dbname=host700513_backpro;charset=utf8mb4', 'host700513_backpro', 'Mq9wH2B8KPeQh2wQ32Ya');\\n\\\\$stmt = \\\\$pdo->query(\\\\\"SELE","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:36:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:37:38Z","tool":"Bash","cmd":"php -r \"\\n\\\\$pdo = new PDO('mysql:host=host700513.hostido.net.pl;dbname=host700513_backpro;charset=utf8mb4', 'host700513_backpro', 'Mq9wH2B8KPeQh2wQ32Ya');\\n\\\\$stmt = \\\\$pdo->query(\\\\\"SELE","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:43:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
{"ts":"2026-04-10T18:43:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\backPRO\\\\src\\\\Services\\\\OpenAIService.php","cwd":"/c/visual studio code/projekty/backPRO"}
|
||||||
|
|||||||
224
.paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
Normal file
224
.paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
---
|
||||||
|
phase: 02-admin-panel-upgrade
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on: ["02-01"]
|
||||||
|
files_modified:
|
||||||
|
- src/Services/WordPressService.php
|
||||||
|
- src/Controllers/SiteController.php
|
||||||
|
- templates/sites/dashboard.php
|
||||||
|
- templates/sites/comments.php
|
||||||
|
- config/routes.php
|
||||||
|
autonomous: true
|
||||||
|
delegation: off
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Dodac do zarzadzania pojedyncza strona WordPress zdalna kontrole komentarzy:
|
||||||
|
1. Wlaczenie i wylaczenie mozliwosci komentowania na danym serwisie.
|
||||||
|
2. Pobranie listy komentarzy z danego serwisu.
|
||||||
|
3. Usuwanie komentarzy z poziomu BackPRO.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
BackPRO ma centralnie zarzadzac siecia stron zapleczowych. Komentarze sa ryzykiem moderacyjnym i spamowym, wiec operator powinien moc szybko wylaczyc komentowanie oraz usuwac niechciane komentarze bez logowania sie do kazdego panelu WordPress osobno.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Metody w `WordPressService` do statusu komentarzy, zmiany ustawien, listowania i usuwania komentarzy.
|
||||||
|
- Akcje w `SiteController` dla panelu komentarzy i operacji POST.
|
||||||
|
- Widok `/sites/{id}/comments` z tabela komentarzy, filtrami i akcja usuniecia.
|
||||||
|
- Karta/status komentarzy na dashboardzie strony.
|
||||||
|
- Nowe trasy w `config/routes.php`.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Prior Work
|
||||||
|
@.paul/phases/02-admin-panel-upgrade/02-01-PLAN.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Services/WordPressService.php
|
||||||
|
@src/Controllers/SiteController.php
|
||||||
|
@templates/sites/dashboard.php
|
||||||
|
@templates/sites/index.php
|
||||||
|
@config/routes.php
|
||||||
|
@src/Core/Controller.php
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Status i przelaczanie komentowania
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik jest zalogowany w BackPRO i ma skonfigurowany serwis WordPress z plikiem zdalnym BackPRO
|
||||||
|
When przechodzi do dashboardu strony
|
||||||
|
Then widzi aktualny status komentowania dla nowych wpisow
|
||||||
|
And moze wlaczyc lub wylaczyc komentowanie z poziomu BackPRO
|
||||||
|
And po akcji widzi komunikat sukcesu albo konkretny blad polaczenia
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Lista komentarzy z serwisu
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik jest zalogowany i strona ma poprawne dane WordPress API
|
||||||
|
When przechodzi do /sites/{id}/comments
|
||||||
|
Then widzi liste komentarzy z WordPressa z autorem, trescia, data, statusem i linkiem do wpisu
|
||||||
|
And moze filtrowac komentarze po statusie all/hold/approve/spam/trash
|
||||||
|
And widok poprawnie obsluguje pusta liste i blad pobrania danych
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Usuwanie komentarzy
|
||||||
|
```gherkin
|
||||||
|
Given uzytkownik widzi komentarz na liscie komentarzy strony
|
||||||
|
When klika usun i potwierdza operacje
|
||||||
|
Then BackPRO usuwa komentarz przez WordPress API
|
||||||
|
And uzytkownik wraca do listy komentarzy z komunikatem wyniku
|
||||||
|
And bledy 401/403/404 sa obsluzone czytelnym komunikatem
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rozszerzyc WordPressService o operacje komentarzy</name>
|
||||||
|
<files>src/Services/WordPressService.php</files>
|
||||||
|
<action>
|
||||||
|
Dodac publiczne metody:
|
||||||
|
- getCommentSettings(array $site): array
|
||||||
|
- Uzyc callRemoteService($site, 'get_comment_settings').
|
||||||
|
- Jezeli endpoint jest nieaktualny, wywolac ensureRemoteService(), odswiezyc site z bazy i ponowic probe.
|
||||||
|
- Zwracac success, default_comment_status, comments_enabled, message.
|
||||||
|
- setCommentsEnabled(array $site, bool $enabled): array
|
||||||
|
- Uzyc callRemoteService($site, 'set_comment_settings', ['comments_enabled' => '1'/'0']).
|
||||||
|
- Endpoint po stronie WordPress ma ustawiac option default_comment_status na open/closed.
|
||||||
|
- Nie zmieniac masowo istniejacych postow w tym planie, zeby nie zaskoczyc uzytkownika utrata historii dyskusji.
|
||||||
|
- getComments(array $site, string $status = 'all', int $page = 1, int $perPage = 20): array
|
||||||
|
- Uzyc WP REST `wp/v2/comments` z auth z buildAuthOption().
|
||||||
|
- Parametry: status, page, per_page, orderby=date, order=desc, context=edit.
|
||||||
|
- Zwracac success, comments, page, total_pages, total, message.
|
||||||
|
- Dla 401/403 zwrocic komunikat o braku uprawnien aplikacyjnego hasla/API uzytkownika.
|
||||||
|
- deleteComment(array $site, int $commentId): array
|
||||||
|
- Uzyc WP REST DELETE `wp/v2/comments/{id}` z auth i query force=true.
|
||||||
|
- 404 traktowac jako czytelny blad "komentarz nie istnieje" albo sukces idempotentny tylko jesli API jasno zwraca deleted=true.
|
||||||
|
|
||||||
|
Zaktualizowac BACKPRO_REMOTE_SERVICE_VERSION do kolejnej wersji i tresc getBackproRemoteServiceContent():
|
||||||
|
- ping ma zwracac nowa wersje.
|
||||||
|
- action=get_comment_settings zwraca default_comment_status i comments_enabled.
|
||||||
|
- action=set_comment_settings waliduje comments_enabled i zapisuje update_option('default_comment_status', 'open'/'closed').
|
||||||
|
|
||||||
|
Zachowac istniejace fallbacki requestWp() i nie ruszac logiki publikacji, mediow, permalinkow ani indeksowania.
|
||||||
|
</action>
|
||||||
|
<verify>php -l src/Services/WordPressService.php</verify>
|
||||||
|
<done>AC-1, AC-2 i AC-3 maja warstwe komunikacji z WordPressem.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Dodac akcje kontrolera i trasy komentarzy</name>
|
||||||
|
<files>src/Controllers/SiteController.php, config/routes.php</files>
|
||||||
|
<action>
|
||||||
|
Dodac do SiteController:
|
||||||
|
- comments(string $id): void
|
||||||
|
- Auth::requireLogin().
|
||||||
|
- Pobrac Site::find(), obsluzyc brak strony.
|
||||||
|
- Odczytac status z query `status` z whitelista: all, hold, approve, spam, trash.
|
||||||
|
- Odczytac page jako int >= 1.
|
||||||
|
- Wywolac WordPressService::getCommentSettings() oraz getComments().
|
||||||
|
- Renderowac `sites/comments` z site, commentSettings, commentsResult, selectedStatus, page.
|
||||||
|
- updateCommentsEnabled(string $id): void
|
||||||
|
- POST z polem enabled=1/0.
|
||||||
|
- Wywolac setCommentsEnabled().
|
||||||
|
- Flash success/danger i redirect do `/sites/{id}/comments`.
|
||||||
|
- deleteComment(string $id, string $commentId): void
|
||||||
|
- Walidowac commentId > 0.
|
||||||
|
- Wywolac deleteComment().
|
||||||
|
- Flash success/danger i redirect z zachowaniem statusu, jesli podany.
|
||||||
|
|
||||||
|
Dodac trasy:
|
||||||
|
- GET `/sites/{id}/comments` -> SiteController@comments
|
||||||
|
- POST `/sites/{id}/comments/settings` -> SiteController@updateCommentsEnabled
|
||||||
|
- POST `/sites/{id}/comments/{commentId}/delete` -> SiteController@deleteComment
|
||||||
|
|
||||||
|
Kontroler ma zostac cienki: mapuje request, wywoluje WordPressService, ustawia flash i przekazuje dane do widoku. Nie wkladac logiki REST API do kontrolera.
|
||||||
|
</action>
|
||||||
|
<verify>php -l src/Controllers/SiteController.php oraz php -l config/routes.php</verify>
|
||||||
|
<done>AC-1, AC-2 i AC-3 dostepne przez routing BackPRO.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Zbudowac UI komentarzy dla pojedynczej strony</name>
|
||||||
|
<files>templates/sites/dashboard.php, templates/sites/comments.php</files>
|
||||||
|
<action>
|
||||||
|
Utworzyc `templates/sites/comments.php`:
|
||||||
|
- Naglowek: nazwa strony, link powrotu do dashboardu i listy stron.
|
||||||
|
- Karta "Komentowanie" z badge ON/OFF wedlug commentSettings.
|
||||||
|
- Dwa formularze POST do `/sites/{id}/comments/settings`: wlacz i wylacz komentarze.
|
||||||
|
- Krotka informacja, ze przelacznik dotyczy domyslnego komentowania nowych wpisow.
|
||||||
|
- Filtry statusu jako linki: Wszystkie, Oczekujace, Zatwierdzone, Spam, Kosz.
|
||||||
|
- Tabela komentarzy: autor, email/URL jesli dostepne, fragment tresci bez HTML, data, status, link do wpisu, akcja usun.
|
||||||
|
- Usuwanie jako POST z `data-confirm`.
|
||||||
|
- Paginacja na podstawie total_pages i aktualnej strony.
|
||||||
|
- Wszystkie dane z WordPressa escape przez htmlspecialchars.
|
||||||
|
|
||||||
|
Zaktualizowac `templates/sites/dashboard.php`:
|
||||||
|
- Dodac karte lub przycisk "Komentarze" w sekcji zarzadzania strona.
|
||||||
|
- Pokazac status komentowania, jesli SiteController::dashboard przekaze commentSettings.
|
||||||
|
- Link do `/sites/{id}/comments`.
|
||||||
|
|
||||||
|
Zaktualizowac SiteController::dashboard w ramach Task 2 lub tego taska:
|
||||||
|
- pobrac commentSettings przez WordPressService i przekazac do widoku dashboardu.
|
||||||
|
|
||||||
|
Nie tworzyc osobnego globalnego ekranu komentarzy dla wszystkich stron w tym planie.
|
||||||
|
</action>
|
||||||
|
<verify>php -l templates/sites/dashboard.php oraz php -l templates/sites/comments.php</verify>
|
||||||
|
<done>AC-1, AC-2 i AC-3 maja kompletny interfejs w panelu strony.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- src/Services/PublisherService.php
|
||||||
|
- src/Models/Article.php
|
||||||
|
- src/Models/Topic.php
|
||||||
|
- migrations/* (brak zmian schematu bazy w tym planie)
|
||||||
|
- templates/articles/* (komentarze dotycza zarzadzania strona, nie artykulow BackPRO)
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Przelacznik komentowania dotyczy domyslnego komentowania nowych wpisow (`default_comment_status`), bez masowego zamykania komentarzy w istniejacych postach.
|
||||||
|
- Nie dodawac moderacji approve/spam/unspam w tym planie; tylko lista i usuwanie.
|
||||||
|
- Nie cache'owac komentarzy lokalnie w bazie BackPRO.
|
||||||
|
- Nie dodawac nowych zaleznosci Composer.
|
||||||
|
- Nie zmieniac sposobu przechowywania danych API/FTP stron.
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] php -l src/Services/WordPressService.php
|
||||||
|
- [ ] php -l src/Controllers/SiteController.php
|
||||||
|
- [ ] php -l config/routes.php
|
||||||
|
- [ ] php -l templates/sites/dashboard.php
|
||||||
|
- [ ] php -l templates/sites/comments.php
|
||||||
|
- [ ] /sites/{id}/dashboard pokazuje wejscie do komentarzy i status ustawienia
|
||||||
|
- [ ] /sites/{id}/comments pokazuje tabele albo czytelny blad pobrania
|
||||||
|
- [ ] POST ustawienia komentowania zmienia default_comment_status na WordPressie
|
||||||
|
- [ ] POST usuniecia komentarza usuwa komentarz albo pokazuje czytelny blad
|
||||||
|
- [ ] Aktualizacja pliku serwisowego BackPRO podnosi wersje remote service
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Operator moze wlaczyc i wylaczyc komentowanie dla wybranej strony.
|
||||||
|
- Operator widzi komentarze pobrane z danego serwisu WordPress.
|
||||||
|
- Operator moze usuwac komentarze z BackPRO.
|
||||||
|
- Bledy polaczenia i uprawnien sa czytelne w UI.
|
||||||
|
- Brak regresji w publikacji, permalinkach, StatLink i istniejacym dashboardzie strony.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/02-admin-panel-upgrade/02-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
39
.paul/phases/02-admin-panel-upgrade/02-02-SUMMARY.md
Normal file
39
.paul/phases/02-admin-panel-upgrade/02-02-SUMMARY.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
phase: 02-admin-panel-upgrade
|
||||||
|
plan: 02
|
||||||
|
completed: 2026-04-24T07:11:11.932Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 02-02 Summary
|
||||||
|
|
||||||
|
****
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status |
|
||||||
|
|-----------|--------|
|
||||||
|
| Task 1: Rozszerzyc WordPressService o operacje komentarzy | Pass — Dodano status i ustawianie komentarzy przez backpro-remote-service v1.5.0 oraz listowanie/usuwanie komentarzy przez WP REST API. Zweryfikowano php -l src/Services/WordPressService.php. |
|
||||||
|
| Task 2: Dodac akcje kontrolera i trasy komentarzy | Pass — Dodano akcje comments, updateCommentsEnabled i deleteComment w SiteController oraz trasy /sites/{id}/comments. Zweryfikowano php -l src/Controllers/SiteController.php i php -l config/routes.php. |
|
||||||
|
| Task 3: Zbudowac UI komentarzy dla pojedynczej strony | Pass — Dodano templates/sites/comments.php oraz kartę/link komentarzy na dashboardzie strony. Zweryfikowano php -l templates/sites/dashboard.php i php -l templates/sites/comments.php. |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Task 1: Rozszerzyc WordPressService o operacje komentarzy: Dodano status i ustawianie komentarzy przez backpro-remote-service v1.5.0 oraz listowanie/usuwanie komentarzy przez WP REST API. Zweryfikowano php -l src/Services/WordPressService.php.
|
||||||
|
- Task 2: Dodac akcje kontrolera i trasy komentarzy: Dodano akcje comments, updateCommentsEnabled i deleteComment w SiteController oraz trasy /sites/{id}/comments. Zweryfikowano php -l src/Controllers/SiteController.php i php -l config/routes.php.
|
||||||
|
- Task 3: Zbudowac UI komentarzy dla pojedynczej strony: Dodano templates/sites/comments.php oraz kartę/link komentarzy na dashboardzie strony. Zweryfikowano php -l templates/sites/dashboard.php i php -l templates/sites/comments.php.
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
- `src/Services/WordPressService.php`
|
||||||
|
- `src/Controllers/SiteController.php`
|
||||||
|
- `config/routes.php`
|
||||||
|
- `templates/sites/dashboard.php`
|
||||||
|
- `templates/sites/comments.php`
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
Live verification against a real WordPress site was not performed in this APPLY; automated PHP lint verification passed for all changed PHP/template files.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 02-admin-panel-upgrade, Plan: 02*
|
||||||
|
*Completed: 2026-04-24*
|
||||||
12
.vscode/ftp-kr.sync.cache.json
vendored
12
.vscode/ftp-kr.sync.cache.json
vendored
@@ -299,6 +299,12 @@
|
|||||||
"size": 28906,
|
"size": 28906,
|
||||||
"lmtime": 1775727816732,
|
"lmtime": 1775727816732,
|
||||||
"modified": false
|
"modified": false
|
||||||
|
},
|
||||||
|
"governance_2026-04-10.jsonl": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 4580,
|
||||||
|
"lmtime": 1775846590836,
|
||||||
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"phases": {
|
"phases": {
|
||||||
@@ -756,9 +762,9 @@
|
|||||||
},
|
},
|
||||||
"OpenAIService.php": {
|
"OpenAIService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 9032,
|
"size": 10203,
|
||||||
"lmtime": 1771375416097,
|
"lmtime": 1775846590297,
|
||||||
"modified": true
|
"modified": false
|
||||||
},
|
},
|
||||||
"PublisherService.php": {
|
"PublisherService.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ $router->post('/sites/{id}', 'SiteController', 'update');
|
|||||||
$router->post('/sites/{id}/delete', 'SiteController', 'destroy');
|
$router->post('/sites/{id}/delete', 'SiteController', 'destroy');
|
||||||
$router->post('/sites/{id}/test', 'SiteController', 'testConnection');
|
$router->post('/sites/{id}/test', 'SiteController', 'testConnection');
|
||||||
$router->get('/sites/{id}/dashboard', 'SiteController', 'dashboard');
|
$router->get('/sites/{id}/dashboard', 'SiteController', 'dashboard');
|
||||||
|
$router->get('/sites/{id}/comments', 'SiteController', 'comments');
|
||||||
|
$router->post('/sites/{id}/comments/settings', 'SiteController', 'updateCommentsEnabled');
|
||||||
|
$router->post('/sites/{id}/comments/{commentId}/delete', 'SiteController', 'deleteComment');
|
||||||
$router->post('/sites/{id}/dashboard/permalinks/enable', 'SiteController', 'enablePrettyPermalinks');
|
$router->post('/sites/{id}/dashboard/permalinks/enable', 'SiteController', 'enablePrettyPermalinks');
|
||||||
$router->post('/sites/{id}/dashboard/remote-service/update', 'SiteController', 'updateRemoteService');
|
$router->post('/sites/{id}/dashboard/remote-service/update', 'SiteController', 'updateRemoteService');
|
||||||
$router->post('/sites/{id}/dashboard/theme/install', 'SiteController', 'installBackproNewsTheme');
|
$router->post('/sites/{id}/dashboard/theme/install', 'SiteController', 'installBackproNewsTheme');
|
||||||
|
|||||||
@@ -215,14 +215,95 @@ class SiteController extends Controller
|
|||||||
$wp = new WordPressService();
|
$wp = new WordPressService();
|
||||||
$permalinkStatus = $wp->getPermalinkSettings($site);
|
$permalinkStatus = $wp->getPermalinkSettings($site);
|
||||||
$remoteServiceStatus = $wp->getRemoteServiceStatus($site);
|
$remoteServiceStatus = $wp->getRemoteServiceStatus($site);
|
||||||
|
$commentSettings = $wp->getCommentSettings($site);
|
||||||
|
|
||||||
$this->view('sites/dashboard', [
|
$this->view('sites/dashboard', [
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
'permalinkStatus' => $permalinkStatus,
|
'permalinkStatus' => $permalinkStatus,
|
||||||
'remoteServiceStatus' => $remoteServiceStatus,
|
'remoteServiceStatus' => $remoteServiceStatus,
|
||||||
|
'commentSettings' => $commentSettings,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function comments(string $id): void
|
||||||
|
{
|
||||||
|
Auth::requireLogin();
|
||||||
|
|
||||||
|
$site = Site::find((int) $id);
|
||||||
|
if (!$site) {
|
||||||
|
$this->flash('danger', 'Strona nie znaleziona.');
|
||||||
|
$this->redirect('/sites');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedStatuses = ['all', 'hold', 'approve', 'spam', 'trash'];
|
||||||
|
$selectedStatus = (string) $this->input('status', 'all');
|
||||||
|
if (!in_array($selectedStatus, $allowedStatuses, true)) {
|
||||||
|
$selectedStatus = 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = max(1, (int) $this->input('page', 1));
|
||||||
|
$wp = new WordPressService();
|
||||||
|
|
||||||
|
$this->view('sites/comments', [
|
||||||
|
'site' => $site,
|
||||||
|
'commentSettings' => $wp->getCommentSettings($site),
|
||||||
|
'commentsResult' => $wp->getComments($site, $selectedStatus, $page),
|
||||||
|
'selectedStatus' => $selectedStatus,
|
||||||
|
'page' => $page,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCommentsEnabled(string $id): void
|
||||||
|
{
|
||||||
|
Auth::requireLogin();
|
||||||
|
|
||||||
|
$site = Site::find((int) $id);
|
||||||
|
if (!$site) {
|
||||||
|
$this->flash('danger', 'Strona nie znaleziona.');
|
||||||
|
$this->redirect('/sites');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$enabled = (string) $this->input('enabled', '0') === '1';
|
||||||
|
$wp = new WordPressService();
|
||||||
|
$result = $wp->setCommentsEnabled($site, $enabled);
|
||||||
|
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
$this->flash('success', (string) ($result['message'] ?? 'Zmieniono ustawienia komentarzy.'));
|
||||||
|
} else {
|
||||||
|
$this->flash('danger', (string) ($result['message'] ?? 'Nie udalo sie zmienic ustawien komentarzy.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect("/sites/{$id}/comments");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteComment(string $id, string $commentId): void
|
||||||
|
{
|
||||||
|
Auth::requireLogin();
|
||||||
|
|
||||||
|
$site = Site::find((int) $id);
|
||||||
|
if (!$site) {
|
||||||
|
$this->flash('danger', 'Strona nie znaleziona.');
|
||||||
|
$this->redirect('/sites');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wp = new WordPressService();
|
||||||
|
$result = $wp->deleteComment($site, (int) $commentId);
|
||||||
|
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
$this->flash('success', (string) ($result['message'] ?? 'Komentarz zostal usuniety.'));
|
||||||
|
} else {
|
||||||
|
$this->flash('danger', (string) ($result['message'] ?? 'Nie udalo sie usunac komentarza.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = (string) $this->input('status', 'all');
|
||||||
|
$allowedStatuses = ['all', 'hold', 'approve', 'spam', 'trash'];
|
||||||
|
$statusQuery = in_array($status, $allowedStatuses, true) ? '?status=' . urlencode($status) : '';
|
||||||
|
$this->redirect("/sites/{$id}/comments{$statusQuery}");
|
||||||
|
}
|
||||||
|
|
||||||
public function seoPanel(string $id): void
|
public function seoPanel(string $id): void
|
||||||
{
|
{
|
||||||
Auth::requireLogin();
|
Auth::requireLogin();
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class OpenAIService
|
|||||||
$qualityFeedback = '';
|
$qualityFeedback = '';
|
||||||
$lastPrompt = '';
|
$lastPrompt = '';
|
||||||
|
|
||||||
for ($attempt = 1; $attempt <= 2; $attempt++) {
|
for ($attempt = 1; $attempt <= 3; $attempt++) {
|
||||||
$userPrompt = $this->buildUserPrompt(
|
$userPrompt = $this->buildUserPrompt(
|
||||||
$topicName,
|
$topicName,
|
||||||
$topicDescription,
|
$topicDescription,
|
||||||
@@ -105,7 +105,8 @@ class OpenAIService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::error('OpenAI generation failed after quality retries', 'openai');
|
Logger::error('OpenAI generation failed after quality retries. Last feedback: ' . $qualityFeedback, 'openai');
|
||||||
|
Logger::error('OpenAI generation failed after quality retries. Last feedback: ' . $qualityFeedback, 'publish');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,12 +154,17 @@ class OpenAIService
|
|||||||
int $maxWords
|
int $maxWords
|
||||||
): string {
|
): string {
|
||||||
$prompt = "Napisz artykul na temat: {$topicName}\n";
|
$prompt = "Napisz artykul na temat: {$topicName}\n";
|
||||||
$prompt .= "Docelowa dlugosc: {$minWords}-{$maxWords} slow.\n";
|
$prompt .= "KRYTYCZNE WYMAGANIE DLUGOSCI: Artykul MUSI miec minimum {$minWords} slow. Docelowo {$minWords}-{$maxWords} slow. Artykuly ponizej {$minWords} slow beda ODRZUCONE.\n";
|
||||||
|
$prompt .= "Aby osiagnac wymagana dlugosc:\n";
|
||||||
|
$prompt .= "- Kazda sekcja H2 musi miec minimum 150-200 slow z konkretnymi przykladami, danymi i scenariuszami.\n";
|
||||||
|
$prompt .= "- Uzyj minimum 5 sekcji H2 (nie liczac FAQ i zakonczenia).\n";
|
||||||
|
$prompt .= "- Rozwin kazdy punkt — nie pisz ogolnikow, podaj detale, porownania, liczby.\n\n";
|
||||||
$prompt .= "Tytul ma byc samodzielny i nie moze zaczynac sie od nazwy tematu ani kategorii.\n";
|
$prompt .= "Tytul ma byc samodzielny i nie moze zaczynac sie od nazwy tematu ani kategorii.\n";
|
||||||
$prompt .= "Tresc ma byc konkretna, praktyczna i naturalna. Bez ogolnikow.\n";
|
$prompt .= "Tresc ma byc konkretna, praktyczna i naturalna. Bez ogolnikow.\n";
|
||||||
$prompt .= "Wstep: 2-3 krotkie akapity i jasna obietnica, czego czytelnik sie dowie.\n";
|
$prompt .= "Wstep: 2-3 krotkie akapity i jasna obietnica, czego czytelnik sie dowie.\n";
|
||||||
$prompt .= "Srodek: minimum 3 sekcje H2, w kazdej przynajmniej jeden konkret (przyklad, liczba, scenariusz, checklista).\n";
|
$prompt .= "Srodek: minimum 5 sekcji H2, w kazdej przynajmniej jeden konkret (przyklad, liczba, scenariusz, checklista).\n";
|
||||||
$prompt .= "Wstaw jedna sekcje H2 o nazwie \"Najczestsze bledy\" i jedna H2 \"FAQ\" z 3 pytaniami i odpowiedziami.\n";
|
$prompt .= "Wstaw jedna sekcje H2 o nazwie \"Najczestsze bledy\".\n";
|
||||||
|
$prompt .= "Opcjonalnie dodaj sekcje H2 \"FAQ\" z 3 pytaniami i odpowiedziami — tylko jesli pasuje do tematu.\n";
|
||||||
$prompt .= "Zakonczenie ma byc praktyczne: \"Co warto zapamietac\" jako lista punktowana.\n";
|
$prompt .= "Zakonczenie ma byc praktyczne: \"Co warto zapamietac\" jako lista punktowana.\n";
|
||||||
$prompt .= "Uzywaj tylko HTML: <p>, <h2>, <h3>, <ul>, <ol>, <li>, <strong>, <em>, <blockquote>, <table>, <tr>, <th>, <td>.\n";
|
$prompt .= "Uzywaj tylko HTML: <p>, <h2>, <h3>, <ul>, <ol>, <li>, <strong>, <em>, <blockquote>, <table>, <tr>, <th>, <td>.\n";
|
||||||
|
|
||||||
@@ -188,8 +194,9 @@ class OpenAIService
|
|||||||
$issues[] = 'brak tresci';
|
$issues[] = 'brak tresci';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($wordCount < $minWords) {
|
$acceptableMin = (int) round($minWords * 0.6);
|
||||||
$issues[] = 'za malo slow (' . $wordCount . ')';
|
if ($wordCount < $acceptableMin) {
|
||||||
|
$issues[] = 'za malo slow (' . $wordCount . ', wymagane min ' . $acceptableMin . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
$h2Count = preg_match_all('/<h2\b[^>]*>/i', $content);
|
$h2Count = preg_match_all('/<h2\b[^>]*>/i', $content);
|
||||||
@@ -197,11 +204,8 @@ class OpenAIService
|
|||||||
$issues[] = 'za malo naglowkow H2';
|
$issues[] = 'za malo naglowkow H2';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('/<h2\b[^>]*>\s*faq\s*<\/h2>/iu', $content) !== 1) {
|
$normalizedContent = $this->stripDiacritics(mb_strtolower($content));
|
||||||
$issues[] = 'brak sekcji FAQ';
|
if (!str_contains($normalizedContent, 'co warto zapamietac')) {
|
||||||
}
|
|
||||||
|
|
||||||
if (!str_contains(mb_strtolower($content), 'co warto zapamietac')) {
|
|
||||||
$issues[] = 'brak sekcji koncowej z konkretami';
|
$issues[] = 'brak sekcji koncowej z konkretami';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +230,18 @@ class OpenAIService
|
|||||||
return count(array_filter($parts, static fn ($item) => $item !== ''));
|
return count(array_filter($parts, static fn ($item) => $item !== ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function stripDiacritics(string $text): string
|
||||||
|
{
|
||||||
|
$map = [
|
||||||
|
'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l',
|
||||||
|
'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 'ż' => 'z',
|
||||||
|
'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'E', 'Ł' => 'L',
|
||||||
|
'Ń' => 'N', 'Ó' => 'O', 'Ś' => 'S', 'Ź' => 'Z', 'Ż' => 'Z',
|
||||||
|
];
|
||||||
|
|
||||||
|
return strtr($text, $map);
|
||||||
|
}
|
||||||
|
|
||||||
private function sanitizeWordLimit(mixed $value, int $default): int
|
private function sanitizeWordLimit(mixed $value, int $default): int
|
||||||
{
|
{
|
||||||
$intValue = (int) $value;
|
$intValue = (int) $value;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class WordPressService
|
|||||||
{
|
{
|
||||||
private const BACKPRO_MU_PLUGIN_FILENAME = 'backpro-remote-tools.php';
|
private const BACKPRO_MU_PLUGIN_FILENAME = 'backpro-remote-tools.php';
|
||||||
private const BACKPRO_REMOTE_SERVICE_FILENAME = 'backpro-remote-service.php';
|
private const BACKPRO_REMOTE_SERVICE_FILENAME = 'backpro-remote-service.php';
|
||||||
private const BACKPRO_REMOTE_SERVICE_VERSION = '1.4.0';
|
private const BACKPRO_REMOTE_SERVICE_VERSION = '1.5.0';
|
||||||
private const BACKPRO_NEWS_THEME_SLUG = 'backpro-news-mag';
|
private const BACKPRO_NEWS_THEME_SLUG = 'backpro-news-mag';
|
||||||
private const BACKPRO_NEWS_THEME_SOURCE_DIR = 'assets/wp-theme-backpro-news';
|
private const BACKPRO_NEWS_THEME_SOURCE_DIR = 'assets/wp-theme-backpro-news';
|
||||||
private Client $client;
|
private Client $client;
|
||||||
@@ -497,6 +497,147 @@ class WordPressService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCommentSettings(array $site): array
|
||||||
|
{
|
||||||
|
$result = $this->callRemoteService($site, 'get_comment_settings');
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
return $this->formatCommentSettings($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ensure = $this->ensureRemoteService($site);
|
||||||
|
if (empty($ensure['success'])) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'comments_enabled' => false,
|
||||||
|
'default_comment_status' => '',
|
||||||
|
'message' => (string) ($ensure['message'] ?? $result['message'] ?? 'Brak endpointu BackPRO na WordPress.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
|
||||||
|
$retry = $this->callRemoteService($refreshedSite, 'get_comment_settings');
|
||||||
|
if (!empty($retry['success'])) {
|
||||||
|
return $this->formatCommentSettings($retry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'comments_enabled' => false,
|
||||||
|
'default_comment_status' => '',
|
||||||
|
'message' => (string) ($retry['message'] ?? 'Nie udalo sie pobrac ustawien komentarzy.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCommentsEnabled(array $site, bool $enabled): array
|
||||||
|
{
|
||||||
|
$params = ['comments_enabled' => $enabled ? '1' : '0'];
|
||||||
|
$result = $this->callRemoteService($site, 'set_comment_settings', $params);
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
return $this->formatCommentSettings($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ensure = $this->ensureRemoteService($site);
|
||||||
|
if (empty($ensure['success'])) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'comments_enabled' => !$enabled,
|
||||||
|
'default_comment_status' => '',
|
||||||
|
'message' => (string) ($ensure['message'] ?? $result['message'] ?? 'Brak endpointu BackPRO na WordPress.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$refreshedSite = !empty($site['id']) ? (Site::find((int) $site['id']) ?: $site) : $site;
|
||||||
|
$retry = $this->callRemoteService($refreshedSite, 'set_comment_settings', $params);
|
||||||
|
if (!empty($retry['success'])) {
|
||||||
|
return $this->formatCommentSettings($retry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'comments_enabled' => !$enabled,
|
||||||
|
'default_comment_status' => '',
|
||||||
|
'message' => (string) ($retry['message'] ?? 'Nie udalo sie zapisac ustawien komentarzy.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComments(array $site, string $status = 'all', int $page = 1, int $perPage = 20): array
|
||||||
|
{
|
||||||
|
$auth = $this->requireAuthOption($site, 'getComments');
|
||||||
|
if ($auth === null) {
|
||||||
|
return $this->buildCommentsFailure(1, 'Brak danych API WordPress dla tej strony.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = max(1, $page);
|
||||||
|
$perPage = max(1, min($perPage, 100));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->requestWp($site, 'GET', 'wp/v2/comments', [
|
||||||
|
'auth' => $auth,
|
||||||
|
'query' => $this->buildCommentsQuery($status, $page, $perPage),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = json_decode($response->getBody()->getContents(), true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
throw new \RuntimeException('Invalid JSON response from WordPress comments endpoint.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['code']) && isset($data['message'])) {
|
||||||
|
throw new \RuntimeException("WordPress API error: {$data['code']} {$data['message']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->buildCommentsSuccess($response, $data, $page);
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
Logger::error("WP getComments failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
|
||||||
|
return $this->buildCommentsFailure(
|
||||||
|
$page,
|
||||||
|
$this->formatWpRequestError($e, 'Nie udalo sie pobrac komentarzy.')
|
||||||
|
);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Logger::error("WP getComments failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
|
||||||
|
return $this->buildCommentsFailure($page, 'Nie udalo sie pobrac komentarzy: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteComment(array $site, int $commentId): array
|
||||||
|
{
|
||||||
|
if ($commentId <= 0) {
|
||||||
|
return ['success' => false, 'message' => 'Nieprawidlowy identyfikator komentarza.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = $this->requireAuthOption($site, 'deleteComment');
|
||||||
|
if ($auth === null) {
|
||||||
|
return ['success' => false, 'message' => 'Brak danych API WordPress dla tej strony.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->requestWp($site, 'DELETE', 'wp/v2/comments/' . $commentId, [
|
||||||
|
'auth' => $auth,
|
||||||
|
'query' => ['force' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = json_decode($response->getBody()->getContents(), true);
|
||||||
|
if (is_array($data) && (isset($data['deleted']) || isset($data['previous']) || isset($data['id']))) {
|
||||||
|
return ['success' => true, 'message' => 'Komentarz zostal usuniety.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => true, 'message' => 'Komentarz zostal usuniety.'];
|
||||||
|
} catch (RequestException $e) {
|
||||||
|
Logger::error("WP deleteComment failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
|
||||||
|
$statusCode = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
|
||||||
|
if ($statusCode === 404) {
|
||||||
|
return ['success' => false, 'message' => 'Komentarz nie istnieje albo zostal juz usuniety.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => $this->formatWpRequestError($e, 'Nie udalo sie usunac komentarza.'),
|
||||||
|
];
|
||||||
|
} catch (GuzzleException $e) {
|
||||||
|
Logger::error("WP deleteComment failed for {$site['url']}: " . $e->getMessage(), 'wordpress');
|
||||||
|
return ['success' => false, 'message' => 'Nie udalo sie usunac komentarza: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function ensureRemoteService(array $site): array
|
public function ensureRemoteService(array $site): array
|
||||||
{
|
{
|
||||||
$siteData = $this->prepareRemoteServiceMetadata($site);
|
$siteData = $this->prepareRemoteServiceMetadata($site);
|
||||||
@@ -938,12 +1079,16 @@ class WordPressService
|
|||||||
$response->getBody()->rewind();
|
$response->getBody()->rewind();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A valid REST API write response has 'id', an error has 'code'+'message'.
|
// A valid REST API write response has 'id'/'deleted', an error has 'code'+'message'.
|
||||||
// The REST API discovery/root response has 'namespaces' – that is NOT a write result.
|
// The REST API discovery/root response has 'namespaces' – that is NOT a write result.
|
||||||
if (!is_array($data)) {
|
if (!is_array($data)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isset($data['id']) || (isset($data['code']) && isset($data['message']))) {
|
if (isset($data['id'])
|
||||||
|
|| isset($data['deleted'])
|
||||||
|
|| isset($data['previous'])
|
||||||
|
|| (isset($data['code']) && isset($data['message']))
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -982,6 +1127,81 @@ class WordPressService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function formatCommentSettings(array $data): array
|
||||||
|
{
|
||||||
|
$status = (string) ($data['default_comment_status'] ?? '');
|
||||||
|
$enabled = array_key_exists('comments_enabled', $data)
|
||||||
|
? (bool) $data['comments_enabled']
|
||||||
|
: $status === 'open';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'default_comment_status' => $status !== '' ? $status : ($enabled ? 'open' : 'closed'),
|
||||||
|
'comments_enabled' => $enabled,
|
||||||
|
'message' => (string) ($data['message'] ?? 'OK'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCommentsQuery(string $status, int $page, int $perPage): array
|
||||||
|
{
|
||||||
|
$allowedStatuses = ['all', 'hold', 'approve', 'spam', 'trash'];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => in_array($status, $allowedStatuses, true) ? $status : 'all',
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => $perPage,
|
||||||
|
'orderby' => 'date',
|
||||||
|
'order' => 'desc',
|
||||||
|
'context' => 'edit',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCommentsSuccess($response, array $comments, int $page): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'comments' => $comments,
|
||||||
|
'page' => $page,
|
||||||
|
'total_pages' => max(1, (int) $response->getHeaderLine('X-WP-TotalPages')),
|
||||||
|
'total' => max(0, (int) $response->getHeaderLine('X-WP-Total')),
|
||||||
|
'message' => 'OK',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCommentsFailure(int $page, string $message): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'comments' => [],
|
||||||
|
'page' => $page,
|
||||||
|
'total_pages' => 1,
|
||||||
|
'total' => 0,
|
||||||
|
'message' => $message,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatWpRequestError(RequestException $e, string $fallback): string
|
||||||
|
{
|
||||||
|
$statusCode = $e->hasResponse() ? (int) $e->getResponse()->getStatusCode() : 0;
|
||||||
|
if (in_array($statusCode, [401, 403], true)) {
|
||||||
|
return 'Brak uprawnien WordPress API. Sprawdz uzytkownika i haslo aplikacyjne/API token.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($statusCode === 404) {
|
||||||
|
return 'Zasob WordPress nie istnieje albo endpoint REST API jest niedostepny.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e->hasResponse()) {
|
||||||
|
$body = (string) $e->getResponse()->getBody();
|
||||||
|
$data = json_decode($body, true);
|
||||||
|
if (is_array($data) && !empty($data['message'])) {
|
||||||
|
return (string) $data['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback . ' ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
private function buildRestRouteUrl(string $siteUrl, string $route, array $extraQuery = [], bool $useIndexPhp = false): string
|
private function buildRestRouteUrl(string $siteUrl, string $route, array $extraQuery = [], bool $useIndexPhp = false): string
|
||||||
{
|
{
|
||||||
$base = rtrim($siteUrl, '/');
|
$base = rtrim($siteUrl, '/');
|
||||||
@@ -1122,7 +1342,7 @@ if (!defined('ABSPATH')) {
|
|||||||
|
|
||||||
\$action = (string) (\$_POST['action'] ?? '');
|
\$action = (string) (\$_POST['action'] ?? '');
|
||||||
if (\$action === 'ping') {
|
if (\$action === 'ping') {
|
||||||
echo json_encode(['success' => true, 'message' => 'pong', 'version' => '1.4.0']);
|
echo json_encode(['success' => true, 'message' => 'pong', 'version' => '1.5.0']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1209,6 +1429,40 @@ if (\$action === 'set_blog_public') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (\$action === 'get_comment_settings') {
|
||||||
|
\$status = (string) get_option('default_comment_status', 'open');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'default_comment_status' => \$status,
|
||||||
|
'comments_enabled' => \$status === 'open',
|
||||||
|
'message' => 'OK',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\$action === 'set_comment_settings') {
|
||||||
|
\$enabledRaw = (string) (\$_POST['comments_enabled'] ?? '');
|
||||||
|
if (\$enabledRaw !== '0' && \$enabledRaw !== '1') {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'missing_comments_enabled']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
\$status = \$enabledRaw === '1' ? 'open' : 'closed';
|
||||||
|
update_option('default_comment_status', \$status);
|
||||||
|
\$applied = (string) get_option('default_comment_status', 'open');
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'default_comment_status' => \$applied,
|
||||||
|
'comments_enabled' => \$applied === 'open',
|
||||||
|
'message' => \$applied === 'open'
|
||||||
|
? 'Komentowanie nowych wpisow jest wlaczone.'
|
||||||
|
: 'Komentowanie nowych wpisow jest wylaczone.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (\$action === 'cleanup') {
|
if (\$action === 'cleanup') {
|
||||||
@unlink(__FILE__);
|
@unlink(__FILE__);
|
||||||
echo json_encode(['success' => true, 'message' => 'service_deleted']);
|
echo json_encode(['success' => true, 'message' => 'service_deleted']);
|
||||||
|
|||||||
206
templates/sites/comments.php
Normal file
206
templates/sites/comments.php
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<?php
|
||||||
|
$comments = $commentsResult['comments'] ?? [];
|
||||||
|
$totalPages = max(1, (int) ($commentsResult['total_pages'] ?? 1));
|
||||||
|
$currentPage = max(1, (int) ($commentsResult['page'] ?? $page ?? 1));
|
||||||
|
$selectedStatus = (string) ($selectedStatus ?? 'all');
|
||||||
|
$commentsEnabled = !empty($commentSettings['success']) && !empty($commentSettings['comments_enabled']);
|
||||||
|
|
||||||
|
$statusLabels = [
|
||||||
|
'all' => 'Wszystkie',
|
||||||
|
'hold' => 'Oczekujace',
|
||||||
|
'approve' => 'Zatwierdzone',
|
||||||
|
'spam' => 'Spam',
|
||||||
|
'trash' => 'Kosz',
|
||||||
|
];
|
||||||
|
|
||||||
|
$badgeClasses = [
|
||||||
|
'approved' => 'bg-success',
|
||||||
|
'approve' => 'bg-success',
|
||||||
|
'hold' => 'bg-warning text-dark',
|
||||||
|
'spam' => 'bg-danger',
|
||||||
|
'trash' => 'bg-secondary',
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-1">Komentarze: <?= htmlspecialchars((string) $site['name']) ?></h2>
|
||||||
|
<a href="<?= htmlspecialchars((string) $site['url']) ?>" target="_blank" class="text-muted small">
|
||||||
|
<?= htmlspecialchars((string) $site['url']) ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="/sites/<?= (int) $site['id'] ?>/dashboard" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-sliders me-1"></i>WP Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="/sites" class="btn btn-outline-dark">
|
||||||
|
<i class="bi bi-arrow-left me-1"></i>Lista stron
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Komentowanie nowych wpisow</h5>
|
||||||
|
<?php if (!empty($commentSettings['success'])): ?>
|
||||||
|
<?php if ($commentsEnabled): ?>
|
||||||
|
<span class="badge bg-success">ON</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">OFF</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning text-dark">Brak danych</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted small mb-3">
|
||||||
|
Przelacznik zmienia domyslne ustawienie komentowania dla nowych wpisow WordPress.
|
||||||
|
Nie zamyka komentarzy masowo w juz opublikowanych artykulach.
|
||||||
|
</p>
|
||||||
|
<p class="mb-3">
|
||||||
|
<?= htmlspecialchars((string) ($commentSettings['message'] ?? 'Brak informacji z WordPress.')) ?>
|
||||||
|
</p>
|
||||||
|
<div class="d-flex gap-2 flex-wrap">
|
||||||
|
<form method="post" action="/sites/<?= (int) $site['id'] ?>/comments/settings">
|
||||||
|
<input type="hidden" name="enabled" value="1">
|
||||||
|
<button type="submit" class="btn btn-success" <?= $commentsEnabled ? 'disabled' : '' ?>>
|
||||||
|
<i class="bi bi-chat-dots me-1"></i>Wlacz komentarze
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/sites/<?= (int) $site['id'] ?>/comments/settings" data-confirm="Wylaczyc komentowanie nowych wpisow na tej stronie?">
|
||||||
|
<input type="hidden" name="enabled" value="0">
|
||||||
|
<button type="submit" class="btn btn-outline-danger" <?= (!$commentsEnabled && !empty($commentSettings['success'])) ? 'disabled' : '' ?>>
|
||||||
|
<i class="bi bi-chat-slash me-1"></i>Wylacz komentarze
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Lista komentarzy</h5>
|
||||||
|
<span class="badge bg-primary"><?= (int) ($commentsResult['total'] ?? 0) ?> lacznie</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body border-bottom">
|
||||||
|
<div class="d-flex gap-2 flex-wrap">
|
||||||
|
<?php foreach ($statusLabels as $status => $label): ?>
|
||||||
|
<a
|
||||||
|
href="/sites/<?= (int) $site['id'] ?>/comments?status=<?= urlencode($status) ?>"
|
||||||
|
class="btn btn-sm <?= $selectedStatus === $status ? 'btn-primary' : 'btn-outline-secondary' ?>"
|
||||||
|
>
|
||||||
|
<?= htmlspecialchars($label) ?>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($commentsResult['success'])): ?>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-warning mb-0">
|
||||||
|
<?= htmlspecialchars((string) ($commentsResult['message'] ?? 'Nie udalo sie pobrac komentarzy.')) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php elseif (empty($comments)): ?>
|
||||||
|
<div class="card-body text-center text-muted py-5">
|
||||||
|
Brak komentarzy dla wybranego statusu.
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Autor</th>
|
||||||
|
<th>Komentarz</th>
|
||||||
|
<th>Data</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Wpis</th>
|
||||||
|
<th class="text-end">Akcje</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($comments as $comment): ?>
|
||||||
|
<?php
|
||||||
|
$commentId = (int) ($comment['id'] ?? 0);
|
||||||
|
$authorName = (string) ($comment['author_name'] ?? '');
|
||||||
|
$authorEmail = (string) ($comment['author_email'] ?? '');
|
||||||
|
$authorUrl = (string) ($comment['author_url'] ?? '');
|
||||||
|
$rawContent = $comment['content']['rendered'] ?? $comment['content']['raw'] ?? '';
|
||||||
|
$content = trim(preg_replace('/\s+/', ' ', strip_tags((string) $rawContent)) ?? '');
|
||||||
|
$content = mb_strlen($content) > 220 ? mb_substr($content, 0, 220) . '...' : $content;
|
||||||
|
$status = (string) ($comment['status'] ?? '');
|
||||||
|
$date = (string) ($comment['date'] ?? '');
|
||||||
|
$postId = (int) ($comment['post'] ?? 0);
|
||||||
|
$commentLink = (string) ($comment['link'] ?? '');
|
||||||
|
$badgeClass = $badgeClasses[$status] ?? 'bg-secondary';
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong><?= htmlspecialchars($authorName !== '' ? $authorName : 'Anonim') ?></strong>
|
||||||
|
<?php if ($authorEmail !== ''): ?>
|
||||||
|
<div class="small text-muted"><?= htmlspecialchars($authorEmail) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($authorUrl !== ''): ?>
|
||||||
|
<a href="<?= htmlspecialchars($authorUrl) ?>" target="_blank" class="small">URL autora</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="small" style="max-width: 520px;">
|
||||||
|
<?= htmlspecialchars($content !== '' ? $content : '-') ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
<?= $date !== '' ? htmlspecialchars(date('d.m.Y H:i', strtotime($date))) : '-' ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge <?= htmlspecialchars($badgeClass) ?>">
|
||||||
|
<?= htmlspecialchars($status !== '' ? $status : '-') ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($commentLink !== ''): ?>
|
||||||
|
<a href="<?= htmlspecialchars($commentLink) ?>" target="_blank">#<?= $postId ?></a>
|
||||||
|
<?php elseif ($postId > 0): ?>
|
||||||
|
#<?= $postId ?>
|
||||||
|
<?php else: ?>
|
||||||
|
-
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
action="/sites/<?= (int) $site['id'] ?>/comments/<?= $commentId ?>/delete"
|
||||||
|
class="d-inline"
|
||||||
|
data-confirm="Usunac ten komentarz z WordPress?"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="status" value="<?= htmlspecialchars($selectedStatus) ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-danger" <?= $commentId <= 0 ? 'disabled' : '' ?>>
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($commentsResult['success']) && $totalPages > 1): ?>
|
||||||
|
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||||
|
<span class="text-muted small">Strona <?= $currentPage ?> z <?= $totalPages ?></span>
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<a
|
||||||
|
href="/sites/<?= (int) $site['id'] ?>/comments?status=<?= urlencode($selectedStatus) ?>&page=<?= max(1, $currentPage - 1) ?>"
|
||||||
|
class="btn btn-outline-secondary <?= $currentPage <= 1 ? 'disabled' : '' ?>"
|
||||||
|
>
|
||||||
|
Poprzednia
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/sites/<?= (int) $site['id'] ?>/comments?status=<?= urlencode($selectedStatus) ?>&page=<?= min($totalPages, $currentPage + 1) ?>"
|
||||||
|
class="btn btn-outline-secondary <?= $currentPage >= $totalPages ? 'disabled' : '' ?>"
|
||||||
|
>
|
||||||
|
Nastepna
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
@@ -4,6 +4,9 @@
|
|||||||
<a href="/sites/<?= (int) $site['id'] ?>/seo" class="btn btn-outline-primary">
|
<a href="/sites/<?= (int) $site['id'] ?>/seo" class="btn btn-outline-primary">
|
||||||
<i class="bi bi-graph-up me-1"></i>SEO Panel
|
<i class="bi bi-graph-up me-1"></i>SEO Panel
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/sites/<?= (int) $site['id'] ?>/comments" class="btn btn-outline-primary">
|
||||||
|
<i class="bi bi-chat-dots me-1"></i>Komentarze
|
||||||
|
</a>
|
||||||
<a href="/sites/<?= $site['id'] ?>/edit" class="btn btn-outline-secondary">
|
<a href="/sites/<?= $site['id'] ?>/edit" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-pencil me-1"></i>Edytuj strone
|
<i class="bi bi-pencil me-1"></i>Edytuj strone
|
||||||
</a>
|
</a>
|
||||||
@@ -156,6 +159,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Komentarze</h5>
|
||||||
|
<?php if (!empty($commentSettings['success'])): ?>
|
||||||
|
<?php if (!empty($commentSettings['comments_enabled'])): ?>
|
||||||
|
<span class="badge bg-success">ON</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">OFF</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning text-dark">Brak danych</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-muted mb-3">
|
||||||
|
<?= htmlspecialchars((string) ($commentSettings['message'] ?? 'Brak informacji z WordPress.')) ?>
|
||||||
|
</p>
|
||||||
|
<a href="/sites/<?= (int) $site['id'] ?>/comments" class="btn btn-outline-primary">
|
||||||
|
<i class="bi bi-chat-dots me-1"></i>Zarzadzaj komentarzami
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">Info techniczne</h5>
|
<h5 class="mb-0">Info techniczne</h5>
|
||||||
|
|||||||
Reference in New Issue
Block a user