diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index a52cd24..f1dbb4f 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -13,7 +13,7 @@ Phases: 1 of 2 complete
| Phase | Name | Plans | Status | Completed |
|-------|------|-------|--------|-----------|
| 1 | StatLink Autolinking | 1 | Complete ✓ | 2026-04-09 |
-| 2 | Admin Panel Upgrade | 1 | Planning | - |
+| 2 | Admin Panel Upgrade | 2 | Planning | - |
## Phase Details
@@ -28,7 +28,7 @@ Phases: 1 of 2 complete
### 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)
**Research:** Done (analiza orderPRO: Migrator, sidebar, CSS)
@@ -36,9 +36,12 @@ Phases: 1 of 2 complete
- Migrator engine (port z orderPRO) + panel /settings/database
- Nowy sidebar z grupami, ikonami SVG, collapse
- Widok /statlink z listą linkowanych artykułów
+- Zdalne wlaczanie/wylaczanie komentowania dla pojedynczej strony WordPress
+- Lista komentarzy z danego serwisu z mozliwoscia usuwania
**Plans:**
- [ ] 02-01: Migrator + sidebar + widok StatLink
+- [ ] 02-02: Zdalne zarzadzanie komentarzami WordPress
---
*Roadmap created: 2026-04-09*
diff --git a/.paul/STATE.md b/.paul/STATE.md
index a0a81df..737899d 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -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
-Milestone: v0.1 Initial Release
-Phase: 2 of 2 (Admin Panel Upgrade) — Planning
-Plan: 02-01 created, awaiting approval
-Status: PLAN created, ready for APPLY
-Last activity: 2026-04-09 — Phase 1 UNIFY completed, bugfixes applied
-
-Progress:
-- Milestone: [████░░░░░░] 40%
-- Phase 1: [██████████] 100% ✓
-- Phase 2: [░░░░░░░░░░] 0%
+Phase: 02-admin-panel-upgrade — In Progress
+Plan: 02-02 complete
+Status: UNIFY complete. Loop complete — ready for next plan.
+Last activity: 2026-04-24T07:11:11.932Z
## Loop Position
-**Phase 1 (StatLink Auto-Linking):**
+Current loop state:
```
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
-Last session: 2026-04-09
-Stopped at: Phase 1 UNIFY complete, Phase 2 Plan 02-01 awaiting approval
-Next action: Review and approve plan 02-01, then run /paul:apply
-Resume file: .paul/phases/02-admin-panel-upgrade/02-01-PLAN.md
-
----
-*STATE.md — Updated after every significant action*
+Last session: 2026-04-24
+Stopped at: Plan 02-02 complete
+Next action: paul_workflow('plan') for next plan
+Resume file: .paul/phases/02-admin-panel-upgrade/02-02-SUMMARY.md
\ No newline at end of file
diff --git a/.paul/STATE.md.bak b/.paul/STATE.md.bak
new file mode 100644
index 0000000..261f30b
--- /dev/null
+++ b/.paul/STATE.md.bak
@@ -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*
diff --git a/.paul/changelog/2026-04-24.md b/.paul/changelog/2026-04-24.md
new file mode 100644
index 0000000..a3dadb4
--- /dev/null
+++ b/.paul/changelog/2026-04-24.md
@@ -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`
\ No newline at end of file
diff --git a/.paul/governance/governance_2026-04-10.jsonl b/.paul/governance/governance_2026-04-10.jsonl
index 644ab94..febc41d 100644
--- a/.paul/governance/governance_2026-04-10.jsonl
+++ b/.paul/governance/governance_2026-04-10.jsonl
@@ -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: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-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"}
diff --git a/.paul/phases/02-admin-panel-upgrade/02-02-PLAN.md b/.paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
new file mode 100644
index 0000000..476f0aa
--- /dev/null
+++ b/.paul/phases/02-admin-panel-upgrade/02-02-PLAN.md
@@ -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
+---
+
+
,
,,
, , .\n"; @@ -188,8 +194,9 @@ class OpenAIService $issues[] = 'brak tresci'; } - if ($wordCount < $minWords) { - $issues[] = 'za malo slow (' . $wordCount . ')'; + $acceptableMin = (int) round($minWords * 0.6); + if ($wordCount < $acceptableMin) { + $issues[] = 'za malo slow (' . $wordCount . ', wymagane min ' . $acceptableMin . ')'; } $h2Count = preg_match_all('/ ]*>/i', $content); @@ -197,11 +204,8 @@ class OpenAIService $issues[] = 'za malo naglowkow H2'; } - if (preg_match('/
]*>\s*faq\s*<\/h2>/iu', $content) !== 1) { - $issues[] = 'brak sekcji FAQ'; - } - - if (!str_contains(mb_strtolower($content), 'co warto zapamietac')) { + $normalizedContent = $this->stripDiacritics(mb_strtolower($content)); + if (!str_contains($normalizedContent, 'co warto zapamietac')) { $issues[] = 'brak sekcji koncowej z konkretami'; } @@ -226,6 +230,18 @@ class OpenAIService 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 { $intValue = (int) $value; diff --git a/src/Services/WordPressService.php b/src/Services/WordPressService.php index 05be314..f68d2d2 100644 --- a/src/Services/WordPressService.php +++ b/src/Services/WordPressService.php @@ -12,7 +12,7 @@ class WordPressService { 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_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_SOURCE_DIR = 'assets/wp-theme-backpro-news'; 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 { $siteData = $this->prepareRemoteServiceMetadata($site); @@ -938,12 +1079,16 @@ class WordPressService $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. if (!is_array($data)) { 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; } @@ -982,6 +1127,81 @@ class WordPressService 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 { $base = rtrim($siteUrl, '/'); @@ -1122,7 +1342,7 @@ if (!defined('ABSPATH')) { \$action = (string) (\$_POST['action'] ?? ''); 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; } @@ -1209,6 +1429,40 @@ if (\$action === 'set_blog_public') { 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') { @unlink(__FILE__); echo json_encode(['success' => true, 'message' => 'service_deleted']); diff --git a/templates/sites/comments.php b/templates/sites/comments.php new file mode 100644 index 0000000..07c7a9c --- /dev/null +++ b/templates/sites/comments.php @@ -0,0 +1,206 @@ + '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', +]; +?> + +
++ +++ +Komentarze: = htmlspecialchars((string) $site['name']) ?>
+ + = htmlspecialchars((string) $site['url']) ?> + +++ +++Komentowanie nowych wpisow
+ + + ON + + OFF + + + Brak danych + ++++ Przelacznik zmienia domyslne ustawienie komentowania dla nowych wpisow WordPress. + Nie zamyka komentarzy masowo w juz opublikowanych artykulach. +
++ = htmlspecialchars((string) ($commentSettings['message'] ?? 'Brak informacji z WordPress.')) ?> +
++ + +++diff --git a/templates/sites/dashboard.php b/templates/sites/dashboard.php index 941f7fc..3eb2512 100644 --- a/templates/sites/dashboard.php +++ b/templates/sites/dashboard.php @@ -4,6 +4,9 @@ SEO Panel + + Komentarze + Edytuj strone @@ -156,6 +159,29 @@ +++Lista komentarzy
+ = (int) ($commentsResult['total'] ?? 0) ?> lacznie +++ + ++ $label): ?> + + = htmlspecialchars($label) ?> + + ++++ ++ = htmlspecialchars((string) ($commentsResult['message'] ?? 'Nie udalo sie pobrac komentarzy.')) ?> +++ Brak komentarzy dla wybranego statusu. ++ +++ + + 1): ?> + + ++ +
++ + + + + 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'; + ?> +Autor +Komentarz +Data +Status +Wpis +Akcje ++ + + ++ = htmlspecialchars($authorName !== '' ? $authorName : 'Anonim') ?> + + += htmlspecialchars($authorEmail) ?>+ + + URL autora + ++ = htmlspecialchars($content !== '' ? $content : '-') ?> + ++ = $date !== '' ? htmlspecialchars(date('d.m.Y H:i', strtotime($date))) : '-' ?> + ++ + = htmlspecialchars($status !== '' ? $status : '-') ?> + + ++ + #= $postId ?> + 0): ?> + #= $postId ?> + + - + + ++ + +++++Komentarze
+ + + ON + + OFF + + + Brak danych + ++++ = htmlspecialchars((string) ($commentSettings['message'] ?? 'Brak informacji z WordPress.')) ?> +
+ + Zarzadzaj komentarzami + +Info techniczne