update
This commit is contained in:
@@ -215,14 +215,95 @@ class SiteController extends Controller
|
||||
$wp = new WordPressService();
|
||||
$permalinkStatus = $wp->getPermalinkSettings($site);
|
||||
$remoteServiceStatus = $wp->getRemoteServiceStatus($site);
|
||||
$commentSettings = $wp->getCommentSettings($site);
|
||||
|
||||
$this->view('sites/dashboard', [
|
||||
'site' => $site,
|
||||
'permalinkStatus' => $permalinkStatus,
|
||||
'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
|
||||
{
|
||||
Auth::requireLogin();
|
||||
|
||||
@@ -57,7 +57,7 @@ class OpenAIService
|
||||
$qualityFeedback = '';
|
||||
$lastPrompt = '';
|
||||
|
||||
for ($attempt = 1; $attempt <= 2; $attempt++) {
|
||||
for ($attempt = 1; $attempt <= 3; $attempt++) {
|
||||
$userPrompt = $this->buildUserPrompt(
|
||||
$topicName,
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -153,12 +154,17 @@ class OpenAIService
|
||||
int $maxWords
|
||||
): string {
|
||||
$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 .= "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 .= "Srodek: minimum 3 sekcje 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 .= "Srodek: minimum 5 sekcji H2, w kazdej przynajmniej jeden konkret (przyklad, liczba, scenariusz, checklista).\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 .= "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';
|
||||
}
|
||||
|
||||
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('/<h2\b[^>]*>/i', $content);
|
||||
@@ -197,11 +204,8 @@ class OpenAIService
|
||||
$issues[] = 'za malo naglowkow H2';
|
||||
}
|
||||
|
||||
if (preg_match('/<h2\b[^>]*>\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;
|
||||
|
||||
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user