--- phase: 01-statlink-autolinking plan: 01 type: execute wave: 1 depends_on: [] files_modified: - migrations/013_statlink_tracking.sql - src/Services/StatLinkService.php - src/Controllers/SettingsController.php - templates/settings/index.php - .env autonomous: true delegation: off --- ## Goal Zbudować mechanizm automatycznego dodawania opublikowanych artykułów do StatLink.pl oraz ich automatycznego usuwania po 60 dniach. ## Purpose Każdy opublikowany artykuł na stronach zapleczowych powinien automatycznie otrzymywać linkowanie w systemie StatLink.pl na okres 60 dni, co zwiększy efektywność pozycjonowania. Po 60 dniach link jest automatycznie usuwany, aby nie marnować punktów. ## Output - `StatLinkService.php` — serwis PHP z logowaniem do StatLink via Guzzle (cookies), dodawaniem i usuwaniem linków - Migracja SQL do śledzenia linków w StatLink (statlink_id, article_id, added_at, expires_at) - Endpoint cron do automatycznego uruchamiania (dodaj nowe / usuń wygasłe) ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Source Files @src/Services/PublisherService.php @src/Models/Article.php @src/Controllers/SettingsController.php @.env ## StatLink.pl Research (from browser exploration) - Login: POST to https://statlink.pl/ with fields: email (textbox), haslo (textbox), submit ZALOGUJ - Session: cookie-based (PHP session) - Add link: POST to /148,twoje-linki#lista with fields: - niepozwol: CSRF token (must be scraped from page) - https: 1 (radio, 0=http, 1=https) - link: URL without protocol (e.g. "example.com/article-slug") - anchor: anchor text (article title or topic keyword) - fraza_kluczowa1, fraza_kluczowa2, fraza_kluczowa3: (empty) - wylacznosc: unchecked - frazowy: unchecked - tylko_https: unchecked - min_ilosc_znakow: 0 - statrank_min: 0, statrank_max: 10 - semstorm_keywords_top_min: 0 - ilosc_dziennie: 0.14 (= 1 link co 2 dni) - ilosc_max: 10 - ilosc_nofollow: 0 - max_ilosc_domena: (default 5) - id_kategorie_multiple[]: all category values selected - zapisz: DODAJ - Delete link: POST to /148,twoje-linki#lista0 with fields: - statlink_id: ID of the link - usun: Usuń - Category checkboxes: multiple id_kategorie_multiple[] values (all selected) - NOWY LINK form is inside div#nowy_link2vis - Each link row has Edytuj and Usuń buttons with statlink_id hidden field ## AC-1: Login do StatLink ```gherkin Given dane logowania w .env (statlink_url, statlink_login, statlink_password) When StatLinkService wykonuje login via Guzzle z CookieJar Then sesja jest utrzymana i kolejne requesty zwracają stronę zalogowanego użytkownika ``` ## AC-2: Dodawanie linku do StatLink ```gherkin Given opublikowany artykuł z wp_post_url i tytułem When StatLinkService::addLink() jest wywołane Then link zostaje dodany w StatLink.pl z prawidłowymi parametrami (anchor=tytuł/keyword, ilosc_dziennie=0.14, ilosc_max=10, wszystkie kategorie) And statlink_id zostaje zapisany w tabeli statlink_links ``` ## AC-3: Usuwanie wygasłych linków ```gherkin Given link w tabeli statlink_links z expires_at < NOW() When StatLinkService::removeExpiredLinks() jest wywołane Then link zostaje usunięty ze StatLink.pl via POST z usun And rekord w tabeli statlink_links zostaje oznaczony jako removed ``` ## AC-4: Cron endpoint ```gherkin Given endpoint /cron/statlink z tokenem autoryzacyjnym When endpoint jest wywołany Then nowe opublikowane artykuły (bez wpisu w statlink_links) dostają linki w StatLink And wygasłe linki (expires_at < NOW()) są usuwane ze StatLink ``` ## AC-5: Tracking w bazie danych ```gherkin Given tabela statlink_links When artykuł dostaje link w StatLink Then zapisywany jest: article_id, site_id, statlink_id, anchor, added_at, expires_at (added_at + 60 dni), status ``` Task 1: Migracja SQL + Model śledzenia StatLink migrations/013_statlink_tracking.sql Utworzyć migrację tworzącą tabelę statlink_links: - id INT AUTO_INCREMENT PRIMARY KEY - article_id INT NOT NULL (FK do articles) - site_id INT NOT NULL (FK do sites) - statlink_id INT NULL (ID linku w systemie StatLink — parsowany z odpowiedzi) - anchor VARCHAR(500) NOT NULL - link_url VARCHAR(500) NOT NULL - added_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP - expires_at DATETIME NOT NULL (added_at + 60 dni) - removed_at DATETIME NULL - status ENUM('active', 'expired', 'removed', 'failed') DEFAULT 'active' - error_message TEXT NULL - created_at DATETIME DEFAULT CURRENT_TIMESTAMP Indeksy: (article_id), (status, expires_at), (site_id) SQL jest poprawny składniowo, tabela zawiera wszystkie kolumny AC-5 satisfied: tabela statlink_links gotowa do śledzenia linków Task 2: StatLinkService — login, dodawanie, usuwanie linków src/Services/StatLinkService.php Utworzyć StatLinkService z metodami: 1. **login()** — POST do statlink.pl z email+haslo, utrzymuj CookieJar w Guzzle - Sprawdź odpowiedź czy zawiera "Zalogowano" - Rzuć wyjątek jeśli login się nie powiedzie 2. **addLink(array $article, string $anchor)** — dodaje link do StatLink: - Najpierw GET /148,twoje-linki aby pobrać token CSRF (pole "niepozwol" — regex z HTML) - POST do /148,twoje-linki#lista z parametrami: - niepozwol: token z GET - https: 1 (jeśli URL artykułu jest HTTPS) lub 0 - link: URL artykułu bez protokołu (np. "domena.pl/slug") - anchor: tytuł artykułu lub keyword tematu (naprzemiennie) - fraza_kluczowa1/2/3: puste - ilosc_dziennie: 0.14 - ilosc_max: 10 - ilosc_nofollow: 0 - statrank_min: 0, statrank_max: 10 - id_kategorie_multiple[]: wszystkie kategorie (pobrać listę z HTML) - zapisz: DODAJ - Parsuj statlink_id z odpowiedzi (szukaj nowego ID w tabeli linków) - Return statlink_id lub null 3. **removeLink(int $statlinkId)** — usuwa link ze StatLink: - POST do /148,twoje-linki#lista0 z statlink_id + usun=Usuń - Sprawdź czy usunięcie się powiodło 4. **getExistingLinkIds()** — parsuje listę linków z /148,twoje-linki - Zwraca tablicę statlink_id dla weryfikacji 5. **scrapeCategories()** — parsuje checkboxy kategorii z formularza - Zwraca tablicę wartości id_kategorie_multiple[] do zaznaczenia 6. **processNewArticles()** — główna metoda: - Pobierz opublikowane artykuły bez wpisu w statlink_links - Zaloguj się do StatLink - Dla każdego artykułu: addLink() + zapisz do statlink_links z expires_at = NOW + 60 dni - Anchor naprzemiennie: tytuł artykułu / keyword tematu 7. **removeExpiredLinks()** — główna metoda usuwania: - Pobierz linki z status='active' AND expires_at < NOW() - Zaloguj się do StatLink - Dla każdego: removeLink() + ustaw status='removed', removed_at=NOW() Użyj GuzzleHttp\Client z CookieJar. Loguj operacje przez Logger (kanał 'statlink'). Odporność: try-catch per link, nie przerywaj całego batch przy błędzie jednego. Avoid: nie wysyłaj więcej niż 5 linków w jednym uruchomieniu crona (rate limiting). Klasa kompiluje się bez błędów, metody mają prawidłowe sygnatury AC-1, AC-2, AC-3 satisfied: serwis loguje się, dodaje i usuwa linki Task 3: Cron endpoint + integracja z routerem src/Controllers/SettingsController.php, src/Core/Router.php Dodać endpoint /cron/statlink w routerze (wzorowany na istniejących cron endpointach): - Walidacja tokenu (SEO_TRIGGER_TOKEN lub nowy STATLINK_TRIGGER_TOKEN) - Wywołanie StatLinkService::processNewArticles() — dodaj nowe - Wywołanie StatLinkService::removeExpiredLinks() — usuń wygasłe - Zwróć JSON z podsumowaniem (added: N, removed: N, errors: N) Sprawdź jak działają istniejące cron endpointy w projekcie i zastosuj ten sam wzorzec. Endpoint /cron/statlink odpowiada JSON-em z podsumowaniem AC-4 satisfied: cron endpoint do automatycznego zarządzania linkami StatLink ## DO NOT CHANGE - src/Services/PublisherService.php (nie modyfikuj flow publikacji) - src/Models/Article.php (nie zmieniaj istniejących metod) - migrations/001-012 (istniejące migracje niemodyfikowalne) - src/Services/InternalLinkService.php (osobny mechanizm linkowania) ## SCOPE LIMITS - Ten plan NIE integruje StatLink z procesem publikacji (to osobny cron) - Nie dodajemy UI do zarządzania StatLink w panelu backPRO (może w przyszłości) - Nie modyfikujemy istniejących endpointów cron Before declaring plan complete: - [ ] Migracja 013 tworzy tabelę statlink_links - [ ] StatLinkService loguje się do statlink.pl (test ręczny) - [ ] StatLinkService dodaje link (test ręczny z jednym artykułem) - [ ] StatLinkService usuwa link (test ręczny) - [ ] Endpoint /cron/statlink zwraca JSON - [ ] Logger zapisuje operacje na kanale 'statlink' - [ ] Nie więcej niż 5 linków dodanych per uruchomienie crona - Wszystkie taski wykonane - StatLinkService działa end-to-end (login → add → track → remove po 60 dniach) - Endpoint cron działa z tokenem - Brak błędów w istniejącej funkcjonalności After completion, create `.paul/phases/01-statlink-autolinking/01-01-SUMMARY.md`